diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/facet/facetable-properties.get.json.ftl b/config/alfresco/templates/webscripts/org/alfresco/repository/facet/facetable-properties.get.json.ftl index 4850dd756c..73613c3fca 100644 --- a/config/alfresco/templates/webscripts/org/alfresco/repository/facet/facetable-properties.get.json.ftl +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/facet/facetable-properties.get.json.ftl @@ -5,17 +5,17 @@ "properties" : [ <#list properties as property> { - "name" : "${property.propertyDefinition.name.prefixString}", - "longqname" : "${property.propertyDefinition.name?string}", - <#if property.localisedTitle??> - "title" : "${property.localisedTitle}", + "name" : "${property.shortQname}", + "longqname" : "${property.qname?string}", + <#if property.title??> + "title" : "${property.title}", "displayName" : "${property.displayName}", - <#if property.propertyDefinition.containerClass.name??> - "containerClassType" : "${property.propertyDefinition.containerClass.name.prefixString}", + <#if property.containerClassType??> + "containerClassType" : "${property.containerClassType.prefixString}", - "dataType" : "${property.propertyDefinition.dataType.name.prefixString}", - "modelQName" : "${property.propertyDefinition.model.name.prefixString}" + "dataType" : "${property.dataType.prefixString}", + "modelQName" : "${property.modelQname.prefixString}" }<#if property_has_next>, ] diff --git a/source/java/org/alfresco/repo/web/scripts/facet/FacetablePropertiesGet.java b/source/java/org/alfresco/repo/web/scripts/facet/FacetablePropertiesGet.java index b8d4307b1e..04f9935135 100644 --- a/source/java/org/alfresco/repo/web/scripts/facet/FacetablePropertiesGet.java +++ b/source/java/org/alfresco/repo/web/scripts/facet/FacetablePropertiesGet.java @@ -29,7 +29,7 @@ import java.util.SortedSet; import java.util.TreeSet; import org.alfresco.repo.i18n.StaticMessageLookup; -import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.repo.search.impl.solr.facet.SolrFacetService.SyntheticPropertyDefinition; import org.alfresco.service.cmr.dictionary.PropertyDefinition; import org.alfresco.service.cmr.i18n.MessageLookup; import org.alfresco.service.namespace.NamespaceException; @@ -39,6 +39,7 @@ import org.alfresco.util.ModelUtil; import org.alfresco.util.ScriptPagingDetails; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.extensions.surf.util.I18NUtil; import org.springframework.extensions.webscripts.Cache; import org.springframework.extensions.webscripts.Status; import org.springframework.extensions.webscripts.WebScriptException; @@ -53,6 +54,7 @@ import org.springframework.extensions.webscripts.WebScriptRequest; public class FacetablePropertiesGet extends AbstractSolrFacetConfigAdminWebScript { public static final Log logger = LogFactory.getLog(FacetablePropertiesGet.class); + public static final String PROPERTIES_KEY = "properties"; private static final String QUERY_PARAM_MAX_ITEMS = "maxItems"; @@ -64,17 +66,15 @@ public class FacetablePropertiesGet extends AbstractSolrFacetConfigAdminWebScrip private static final String QUERY_PARAM_LOCALE = "locale"; private NamespaceService namespaceService; - private MessageLookup messageLookup; + private MessageLookup messageLookup; + + public FacetablePropertiesGet() { messageLookup = new StaticMessageLookup(); } - public FacetablePropertiesGet() - { - messageLookup = new StaticMessageLookup(); - } public void setNamespaceService (NamespaceService service) { this.namespaceService = service; } @Override protected Map executeImpl(final WebScriptRequest req, final Status status, final Cache cache) { - // Allow all authenticated users view the filters + // Allow all authenticated users in return unprotectedExecuteImpl(req, status, cache); } @@ -84,7 +84,7 @@ public class FacetablePropertiesGet extends AbstractSolrFacetConfigAdminWebScrip final Locale userLocale = (userLocaleString == null) ? Locale.getDefault() : new Locale(userLocaleString); // There are multiple defined URIs for this REST endpoint. Some define a "classname" template var. - Map templateVars = req.getServiceMatch().getTemplateVars(); + final Map templateVars = req.getServiceMatch().getTemplateVars(); final String contentClassName = templateVars.get(TEMPLATE_VAR_CLASSNAME); QName contentClassQName; @@ -98,14 +98,20 @@ public class FacetablePropertiesGet extends AbstractSolrFacetConfigAdminWebScrip final Map model = new HashMap<>(); - final SortedSet facetableProperties; + final SortedSet facetableProperties; if (contentClassQName == null) { - facetableProperties = toFacetablePropertyDataSet(facetService.getFacetableProperties(), userLocale); + facetableProperties = toFacetablePropertyModel(facetService.getFacetableProperties(), userLocale); + + final List facetableSyntheticProperties = facetService.getFacetableSyntheticProperties(); + facetableProperties.addAll(toFacetablePropertyModel_(facetableSyntheticProperties, userLocale)); } else { - facetableProperties = toFacetablePropertyDataSet(facetService.getFacetableProperties(contentClassQName), userLocale); + facetableProperties = toFacetablePropertyModel(facetService.getFacetableProperties(contentClassQName), userLocale); + + final List facetableSyntheticProperties = facetService.getFacetableSyntheticProperties(contentClassQName); + facetableProperties.addAll(toFacetablePropertyModel_(facetableSyntheticProperties, userLocale)); } // The webscript allows for some further filtering of results: @@ -117,16 +123,16 @@ public class FacetablePropertiesGet extends AbstractSolrFacetConfigAdminWebScrip { filters.add(new ResultFilter() { - @Override public boolean filter(FacetablePropertyData facetableProperty) + @Override public boolean filter(FacetablePropertyFTLModel facetableProperty) { - final QName propQName = facetableProperty.getPropertyDefinition().getName(); + final QName propQName = facetableProperty.getQname(); Collection prefixes = namespaceService.getPrefixes(propQName.getNamespaceURI()); return prefixes.contains(namespaceFilter); } }); } - List filteredFacetableProperties = filter(facetableProperties, filters); + List filteredFacetableProperties = filter(facetableProperties, filters); if (logger.isDebugEnabled()) { @@ -144,94 +150,131 @@ public class FacetablePropertiesGet extends AbstractSolrFacetConfigAdminWebScrip return model; } - /** This type defines the (inclusion) filtering of {@link FacetablePropertyData} in the response to this webscript. */ + /** + * This type defines the (inclusion) filtering of {@link FacetablePropertyFTLModel} + * in the response to this webscript. + */ private static interface ResultFilter { - public boolean filter(FacetablePropertyData facetableProperty); + public boolean filter(FacetablePropertyFTLModel facetableProperty); } /** - * This method returns a new List instance containing only those {@link FacetablePropertyData data} that + * This method returns a new List instance containing only those {@link FacetablePropertyFTLModel data} that * satisfy all {@link ResultFilter filters}. */ - private List filter(Collection propsData, List filters) + private List filter(Collection propsData, List filters) { - List filteredResult = new ArrayList<>(); + List filteredResult = new ArrayList<>(); - for (FacetablePropertyData prop : propsData) + for (FacetablePropertyFTLModel prop : propsData) { boolean passedAllFilters = true; for (ResultFilter filter : filters) { if (!filter.filter(prop)) { passedAllFilters = false; } } - if (passedAllFilters) { filteredResult.add(prop); } + if (passedAllFilters) { filteredResult.add(prop); } } return filteredResult; } - /** This method returns a {@link FacetablePropertyData} for the specified {@link PropertyDefinition}. */ - private FacetablePropertyData toFacetablePropertyData(PropertyDefinition propDef, Locale locale) + /** This method returns a {@link FacetablePropertyFTLModel} for the specified {@link PropertyDefinition}. */ + private FacetablePropertyFTLModel toFacetablePropertyModel(PropertyDefinition propDef, Locale locale) { String title = propDef.getTitle(messageLookup, locale); - return new FacetablePropertyData(propDef, title); + return new FacetablePropertyFTLModel(propDef, title); } - private SortedSet toFacetablePropertyDataSet(Collection propDefs, Locale locale) + private FacetablePropertyFTLModel toFacetablePropertyModel_(SyntheticPropertyDefinition propDef, + Locale locale) { - SortedSet result = new TreeSet<>(); + // Note the hard-coded assumption here that all synthetic properties are defined only + // within the cm:content property type. This code is not designed to be extended. + // TODO We may need to make this code extensible in a future release. + // + // See e.g. content-model.properties for usage of this i18n key. + final String i18nKeyPrefix = "cm_contentmodel.property.cm_content.cm_content."; + final String localisedTitle = I18NUtil.getMessage(i18nKeyPrefix + propDef.syntheticPropertyName, locale); + + return new SyntheticFacetablePropertyFTLModel(propDef.containingPropertyDef, + localisedTitle, + propDef.syntheticPropertyName, + propDef.dataTypeDefinition); + } + + private SortedSet toFacetablePropertyModel(Collection propDefs, + Locale locale) + { + SortedSet result = new TreeSet<>(); for (PropertyDefinition propDef : propDefs) { - result.add(toFacetablePropertyData(propDef, locale)); + result.add(toFacetablePropertyModel(propDef, locale)); + } + return result; + } + + private SortedSet toFacetablePropertyModel_(Collection propDefs, + Locale locale) + { + SortedSet result = new TreeSet<>(); + for (SyntheticPropertyDefinition propDef : propDefs) + { + result.add(toFacetablePropertyModel_(propDef, locale)); } return result; } /** A simple POJO/DTO intended primarily for use in an FTL model and rendering in the JSON API. */ - public static class FacetablePropertyData implements Comparable + public static class FacetablePropertyFTLModel implements Comparable { - private final PropertyDefinition propDef; - private final String localisedTitle; - private final String displayName; + /** The Alfresco property definition which declares this facetable property. */ + protected final PropertyDefinition propDef; - public FacetablePropertyData(PropertyDefinition propDef, String localisedTitle) + /** The localised title for this property. */ + protected final String localisedTitle; + + /** A display name for this property. */ + protected String displayName; + + /** + * @param propDef The {@link PropertyDefinition}. + * @param localisedTitle The localised title for this property e.g. "Titre". + */ + public FacetablePropertyFTLModel(PropertyDefinition propDef, String localisedTitle) { this.propDef = propDef; this.localisedTitle = localisedTitle; - this.displayName = propDef.getName().getPrefixString() + - (localisedTitle == null ? "" : " (" + localisedTitle + ")"); + this.displayName = getShortQname() + (localisedTitle == null ? "" : " (" + localisedTitle + ")"); } - public PropertyDefinition getPropertyDefinition() { return this.propDef; } - public String getLocalisedTitle() { return this.localisedTitle; } - public String getDisplayName() { return this.displayName; } + // We use "*Qname*" (small 'n') in these accessors to make the FTL easier to write. + public String getShortQname() { return propDef.getName().getPrefixString(); } - @Override public int hashCode() - { - final int prime = 31; - int result = 1; - result = prime * result + ((displayName == null) ? 0 : displayName.hashCode()); - result = prime * result + ((localisedTitle == null) ? 0 : localisedTitle.hashCode()); - result = prime * result + ((propDef == null) ? 0 : propDef.hashCode()); - return result; - } + public QName getQname() { return propDef.getName(); } + + public String getTitle() { return localisedTitle; } + + public String getDisplayName() { return displayName; } + + public QName getContainerClassType() { return propDef.getContainerClass().getName(); } + + public QName getDataType() { return propDef.getDataType().getName(); } + + public QName getModelQname() { return propDef.getModel().getName(); } @Override public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - FacetablePropertyData other = (FacetablePropertyData) obj; + if (this == obj) { return true; } + if (obj == null) { return false; } + if (getClass() != obj.getClass()) { return false; } + + FacetablePropertyFTLModel other = (FacetablePropertyFTLModel) obj; if (displayName == null) { - if (other.displayName != null) - return false; - } else if (!displayName.equals(other.displayName)) - return false; + if (other.displayName != null) { return false; } + } else if (!displayName.equals(other.displayName)) { return false; } if (localisedTitle == null) { if (other.localisedTitle != null) @@ -247,18 +290,66 @@ public class FacetablePropertiesGet extends AbstractSolrFacetConfigAdminWebScrip return true; } - @Override public int compareTo(FacetablePropertyData that) + @Override public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + ((displayName == null) ? 0 : displayName.hashCode()); + result = prime * result + ((localisedTitle == null) ? 0 : localisedTitle.hashCode()); + result = prime * result + ((propDef == null) ? 0 : propDef.hashCode()); + return result; + } + + @Override public int compareTo(FacetablePropertyFTLModel that) { final int modelComparison = this.propDef.getModel().getName().compareTo(that.propDef.getModel().getName()); final int classComparison = this.propDef.getContainerClass().getName().compareTo(that.propDef.getContainerClass().getName()); final int propComparison = this.propDef.getName().compareTo(that.propDef.getName()); + // this comparison matters because it incorporates SyntheticProperties like size & mimetype. See below. + final int propWithSynthetic = this.getShortQname().compareTo(that.getShortQname()); final int result; if (modelComparison != 0) { result = modelComparison; } else if (classComparison != 0) { result = classComparison; } - else { result = propComparison; } + else if (propComparison != 0) { result = propComparison; } + else { result = propWithSynthetic; } return result; } } + + /** + * This class represents a facetable property, which is not actually an Alfresco + * content property. Examples are the {@code size} and {@code MIME type} fields + * within the {@code cm:content} property type. + */ + public static class SyntheticFacetablePropertyFTLModel extends FacetablePropertyFTLModel + { + /** This is the name of the synthetic property e.g. "size". Not localised. */ + private final String syntheticPropertyName; + + /** The type of this synthetic data property. */ + private final QName datatype; + + /** + * @param containingPropDef The {@link PropertyDefinition}. + * @param localisedTitle The localised title of this synthetic property e.g. "taille". + * @param syntheticPropertyName The synthetic property name e.g. "size". + */ + public SyntheticFacetablePropertyFTLModel(PropertyDefinition containingPropDef, + String localisedTitle, + String syntheticPropertyName, + QName datatype) + { + super(containingPropDef, localisedTitle); + this.syntheticPropertyName = syntheticPropertyName; + this.datatype = datatype; + this.displayName = getShortQname() + (localisedTitle == null ? "" : " (" + localisedTitle + ")"); + } + + @Override public String getShortQname() { return super.getShortQname() + + "." + this.syntheticPropertyName; } + + @Override public QName getDataType() { return datatype; } + } }