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 73613c3fca..66402022f4 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,25 @@ "properties" : [ <#list properties as property> { - "name" : "${property.shortQname}", - "longqname" : "${property.qname?string}", + "name" : "${property.shortQname}" + <#if property.qname??> + ,"longqname" : "${property.qname?string}" + <#else> + ,"longqname" : "${property.shortQname}" + <#if property.title??> - "title" : "${property.title}", + ,"title" : "${property.title}" - "displayName" : "${property.displayName}", + ,"displayName" : "${property.displayName}" <#if property.containerClassType??> - "containerClassType" : "${property.containerClassType.prefixString}", + ,"containerClassType" : "${property.containerClassType.prefixString}" + + <#if property.dataType??> + ,"dataType" : "${property.dataType.prefixString}" + + <#if property.modelQname??> + ,"modelQName" : "${property.modelQname.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 42175fa818..0ae0d91df7 100644 --- a/source/java/org/alfresco/repo/web/scripts/facet/FacetablePropertiesGet.java +++ b/source/java/org/alfresco/repo/web/scripts/facet/FacetablePropertiesGet.java @@ -30,6 +30,10 @@ import java.util.TreeSet; import org.alfresco.repo.i18n.StaticMessageLookup; import org.alfresco.repo.search.impl.solr.facet.SolrFacetService.SyntheticPropertyDefinition; +import org.alfresco.repo.web.scripts.facet.FacetablePropertyFTL.FacetablePropertyFTLComparator; +import org.alfresco.repo.web.scripts.facet.FacetablePropertyFTL.SpecialFacetablePropertyFTL; +import org.alfresco.repo.web.scripts.facet.FacetablePropertyFTL.StandardFacetablePropertyFTL; +import org.alfresco.repo.web.scripts.facet.FacetablePropertyFTL.SyntheticFacetablePropertyFTL; import org.alfresco.service.cmr.dictionary.PropertyDefinition; import org.alfresco.service.cmr.i18n.MessageLookup; import org.alfresco.service.namespace.NamespaceException; @@ -101,7 +105,7 @@ public class FacetablePropertiesGet extends AbstractSolrFacetConfigAdminWebScrip // 'synthetic' properties like size and mimetype. See below for more details. final Map model = new HashMap<>(); - final SortedSet facetableProperties; + final SortedSet> facetableProperties; if (contentClassQName == null) { facetableProperties = toFacetablePropertyModel(facetService.getFacetableProperties(), userLocale); @@ -117,6 +121,10 @@ public class FacetablePropertiesGet extends AbstractSolrFacetConfigAdminWebScrip facetableProperties.addAll(toFacetablePropertyModel_(facetableSyntheticProperties, userLocale)); } + // Always add some hard-coded facetable "properties" + facetableProperties.add(new SpecialFacetablePropertyFTL("TAG", "Tag")); + facetableProperties.add(new SpecialFacetablePropertyFTL("SITE", "Site")); + // The webscript allows for some further filtering of results: List filters = new ArrayList<>(); @@ -126,7 +134,7 @@ public class FacetablePropertiesGet extends AbstractSolrFacetConfigAdminWebScrip { filters.add(new ResultFilter() { - @Override public boolean filter(FacetablePropertyFTLModel facetableProperty) + @Override public boolean filter(FacetablePropertyFTL facetableProperty) { final QName propQName = facetableProperty.getQname(); Collection prefixes = namespaceService.getPrefixes(propQName.getNamespaceURI()); @@ -135,7 +143,7 @@ public class FacetablePropertiesGet extends AbstractSolrFacetConfigAdminWebScrip }); } - List filteredFacetableProperties = filter(facetableProperties, filters); + List> filteredFacetableProperties = filter(facetableProperties, filters); if (logger.isDebugEnabled()) { @@ -154,24 +162,24 @@ public class FacetablePropertiesGet extends AbstractSolrFacetConfigAdminWebScrip } /** - * This type defines the (inclusion) filtering of {@link FacetablePropertyFTLModel property data} + * This type defines the (inclusion) filtering of {@link FacetablePropertyFTL property data} * in the response to this webscript. */ private static interface ResultFilter { /** @return {@code true} if the specified property should be included. */ - public boolean filter(FacetablePropertyFTLModel facetableProperty); + public boolean filter(FacetablePropertyFTL facetableProperty); } /** - * This method returns a new List instance containing only those {@link FacetablePropertyFTLModel property data} + * This method returns a new List instance containing only those {@link FacetablePropertyFTL property data} * that satisfy all {@link ResultFilter filters}. */ - private List filter(Collection propsData, List filters) + private List> filter(Collection> propsData, List filters) { - final List filteredResult = new ArrayList<>(); + final List> filteredResult = new ArrayList<>(); - for (FacetablePropertyFTLModel prop : propsData) + for (FacetablePropertyFTL prop : propsData) { boolean passedAllFilters = true; for (ResultFilter filter : filters) @@ -184,16 +192,16 @@ public class FacetablePropertiesGet extends AbstractSolrFacetConfigAdminWebScrip return filteredResult; } - /** This method returns a {@link FacetablePropertyFTLModel} for the specified {@link PropertyDefinition}. */ - private FacetablePropertyFTLModel toFacetablePropertyModel(PropertyDefinition propDef, Locale locale) + /** This method returns a {@link FacetablePropertyFTL} for the specified {@link PropertyDefinition}. */ + private FacetablePropertyFTL toFacetablePropertyModel(PropertyDefinition propDef, Locale locale) { String title = propDef.getTitle(messageLookup, locale); - return new FacetablePropertyFTLModel(propDef, title); + return new StandardFacetablePropertyFTL(propDef, title); } - /** This method returns a {@link FacetablePropertyFTLModel} for the specified {@link SyntheticPropertyDefinition}. */ - private FacetablePropertyFTLModel toFacetablePropertyModel(SyntheticPropertyDefinition propDef, - Locale locale) + /** This method returns a {@link FacetablePropertyFTL} for the specified {@link SyntheticPropertyDefinition}. */ + private FacetablePropertyFTL toFacetablePropertyModel(SyntheticPropertyDefinition propDef, + Locale locale) { // 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. @@ -203,16 +211,16 @@ public class FacetablePropertiesGet extends AbstractSolrFacetConfigAdminWebScrip 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); + return new SyntheticFacetablePropertyFTL(propDef.containingPropertyDef, + localisedTitle, + propDef.syntheticPropertyName, + propDef.dataTypeDefinition); } - private SortedSet toFacetablePropertyModel(Collection propDefs, - Locale locale) + private SortedSet> toFacetablePropertyModel(Collection propDefs, + Locale locale) { - SortedSet result = new TreeSet<>(); + SortedSet> result = new TreeSet<>(new FacetablePropertyFTLComparator()); for (PropertyDefinition propDef : propDefs) { result.add(toFacetablePropertyModel(propDef, locale)); @@ -221,153 +229,16 @@ public class FacetablePropertiesGet extends AbstractSolrFacetConfigAdminWebScrip } // Note: the trailing underscore in this method name is to prevent a clash between this method and the - // one that takes a Collection as type erasure means that both methods would have the - // same signature without the trailing underscore. - private SortedSet toFacetablePropertyModel_(Collection propDefs, - Locale locale) + // one that takes a Collection as Java's type erasure means that both methods would have the + // same signature, without the trailing underscore. + private SortedSet> toFacetablePropertyModel_(Collection propDefs, + Locale locale) { - SortedSet result = new TreeSet<>(); + SortedSet> result = new TreeSet<>(new FacetablePropertyFTLComparator()); 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 FacetablePropertyFTLModel implements Comparable - { - /** The Alfresco property definition which declares this facetable property. */ - protected final PropertyDefinition propDef; - - /** 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 = getShortQname() + (localisedTitle == null ? "" : " (" + localisedTitle + ")"); - } - - // We use "*Qname*" (small 'n') in these accessors to make the FTL less ambiguous. - public String getShortQname() { return propDef.getName().getPrefixString(); } - - 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; } - - FacetablePropertyFTLModel other = (FacetablePropertyFTLModel) obj; - if (displayName == null) - { - if (other.displayName != null) { return false; } - } else if (!displayName.equals(other.displayName)) { return false; } - if (localisedTitle == null) - { - if (other.localisedTitle != null) - return false; - } else if (!localisedTitle.equals(other.localisedTitle)) - return false; - if (propDef == null) - { - if (other.propDef != null) - return false; - } else if (!propDef.equals(other.propDef)) - return false; - return true; - } - - @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 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". - * @param datatype The datatype of the synthetic property. - */ - 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 getQname() - { - final QName containingPropQName = super.getQname(); - return QName.createQName(containingPropQName.getNamespaceURI(), - containingPropQName.getLocalName() + "." + this.syntheticPropertyName); - } - - @Override public QName getDataType() { return datatype; } - } } diff --git a/source/java/org/alfresco/repo/web/scripts/facet/FacetablePropertyFTL.java b/source/java/org/alfresco/repo/web/scripts/facet/FacetablePropertyFTL.java new file mode 100644 index 0000000000..05731a554d --- /dev/null +++ b/source/java/org/alfresco/repo/web/scripts/facet/FacetablePropertyFTL.java @@ -0,0 +1,363 @@ +/* + * Copyright (C) 2005-2014 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +package org.alfresco.repo.web.scripts.facet; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; + +import org.alfresco.service.cmr.dictionary.PropertyDefinition; +import org.alfresco.service.namespace.QName; + +/** + * This interface defines a simple POJO/DTO for use in the FTL model and rendering in the JSON API. + * @param T a type to ensure that the comparator is implemented in a typesafe way. + * @since 5.0 + */ +public abstract class FacetablePropertyFTL implements Comparable +{ + /** The localised title for this property. */ + protected final String localisedTitle; + + public FacetablePropertyFTL(String localisedTitle) { this.localisedTitle = localisedTitle; } + + // We use "*Qname*" (small 'n') in these accessors to make the FTL less ambiguous. + public abstract String getShortQname(); + public abstract QName getQname(); + public abstract String getDisplayName(); + public abstract QName getContainerClassType(); + public abstract QName getDataType(); + public abstract QName getModelQname(); + + public String getTitle() { return localisedTitle; } + + /** This class represents a normal Alfresco property which is facetable. */ + public static class StandardFacetablePropertyFTL extends FacetablePropertyFTL + { + /** The Alfresco property definition which declares this facetable property. */ + protected final PropertyDefinition propDef; + + /** A display name for this property. */ + protected final String displayName; + + /** + * @param propDef The {@link PropertyDefinition}. + * @param localisedTitle The localised title for this property e.g. "Titre". + */ + public StandardFacetablePropertyFTL(PropertyDefinition propDef, String localisedTitle) + { + super(localisedTitle); + + this.propDef = propDef; + this.displayName = getShortQname() + (localisedTitle == null ? "" : " (" + localisedTitle + ")"); + } + + @Override public String getShortQname() { return propDef.getName().getPrefixString(); } + + @Override public QName getQname() { return propDef.getName(); } + + @Override public String getDisplayName() { return displayName; } + + @Override public QName getContainerClassType() { return propDef.getContainerClass().getName(); } + + @Override public QName getDataType() { return propDef.getDataType().getName(); } + + @Override 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; } + + StandardFacetablePropertyFTL other = (StandardFacetablePropertyFTL) obj; + if (displayName == null) + { + if (other.displayName != null) { return false; } + } else if (!displayName.equals(other.displayName)) { return false; } + if (localisedTitle == null) + { + if (other.localisedTitle != null) + return false; + } else if (!localisedTitle.equals(other.localisedTitle)) + return false; + if (propDef == null) + { + if (other.propDef != null) + return false; + } else if (!propDef.equals(other.propDef)) + return false; + return true; + } + + @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(StandardFacetablePropertyFTL 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()); + + final int result; + if (modelComparison != 0) { result = modelComparison; } + else if (classComparison != 0) { result = classComparison; } + else { result = propComparison; } + + return result; + } + } + + /** + * This class represents a facetable property, which is not actually an Alfresco + * content property, but is closely associated with one. + * Examples are the {@code size} and {@code MIME type} fields within the {@code cm:content} property type. + */ + public static class SyntheticFacetablePropertyFTL extends FacetablePropertyFTL + { + /** The PropertyDefinition which 'contains' this synthetic property. */ + private final PropertyDefinition containingPropDef; + + /** 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; + + private final String displayName; + + /** + * @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". + * @param datatype The datatype of the synthetic property. + */ + public SyntheticFacetablePropertyFTL(PropertyDefinition containingPropDef, + String localisedTitle, + String syntheticPropertyName, + QName datatype) + { + super(localisedTitle); + this.containingPropDef = containingPropDef; + this.syntheticPropertyName = syntheticPropertyName; + this.datatype = datatype; + this.displayName = getShortQname() + (localisedTitle == null ? "" : " (" + localisedTitle + ")"); + } + + @Override public String getShortQname() + { + return containingPropDef.getName().getPrefixString() + "." + this.syntheticPropertyName; + } + + @Override public QName getQname() + { + final QName containingPropQName = containingPropDef.getName(); + return QName.createQName(containingPropQName.getNamespaceURI(), + containingPropQName.getLocalName() + "." + this.syntheticPropertyName); + } + + @Override public QName getDataType() { return datatype; } + + @Override public QName getContainerClassType() { return containingPropDef.getContainerClass().getName(); }; + + @Override public QName getModelQname() { return containingPropDef.getModel().getName(); } + + @Override public String getDisplayName() { return displayName; } + + @Override public int compareTo(SyntheticFacetablePropertyFTL that) + { + final int modelComparison = this.containingPropDef.getModel().getName().compareTo(that.containingPropDef.getModel().getName()); + final int classComparison = this.containingPropDef.getContainerClass().getName().compareTo(that.containingPropDef.getContainerClass().getName()); + final int propComparison = this.containingPropDef.getName().compareTo(that.containingPropDef.getName()); + final int displayNameComparison = this.displayName.compareTo(that.displayName); + + final int result; + if (modelComparison != 0) { result = modelComparison; } + else if (classComparison != 0) { result = classComparison; } + else if (propComparison != 0) { result = propComparison; } + else { result = displayNameComparison; } + + return result; + } + + @Override public int hashCode() + { + final int prime = 31; + int result = super.hashCode(); + result = prime + * result + + ((containingPropDef == null) ? 0 : containingPropDef + .hashCode()); + result = prime * result + + ((datatype == null) ? 0 : datatype.hashCode()); + result = prime * result + + ((displayName == null) ? 0 : displayName.hashCode()); + result = prime + * result + + ((syntheticPropertyName == null) ? 0 + : syntheticPropertyName.hashCode()); + return result; + } + + @Override public boolean equals(Object obj) + { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (getClass() != obj.getClass()) + return false; + SyntheticFacetablePropertyFTL other = (SyntheticFacetablePropertyFTL) obj; + if (containingPropDef == null) + { + if (other.containingPropDef != null) + return false; + } else if (!containingPropDef.equals(other.containingPropDef)) + return false; + if (datatype == null) + { + if (other.datatype != null) + return false; + } else if (!datatype.equals(other.datatype)) + return false; + if (displayName == null) + { + if (other.displayName != null) + return false; + } else if (!displayName.equals(other.displayName)) + return false; + if (syntheticPropertyName == null) + { + if (other.syntheticPropertyName != null) + return false; + } else if (!syntheticPropertyName + .equals(other.syntheticPropertyName)) + return false; + return true; + } + } + + /** + * This class represents a hard-coded facetable pseudo-property. It is not an Alfresco property + * and yet it is something that Alfresco and SOLR can use for facetting. + * Examples are the {@code TAG} and {@code SITE} facets. + */ + public static class SpecialFacetablePropertyFTL extends FacetablePropertyFTL + { + /** This is the name of the property e.g. "SITE". Not localised. */ + private final String name; + + private final String displayName; + + /** + * @param localisedTitle The localised title of this synthetic property e.g. "taille". + */ + public SpecialFacetablePropertyFTL(String name, String localisedTitle) + { + super(localisedTitle); + this.name = name; + this.displayName = localisedTitle; + } + + @Override public String getShortQname() { return name; } + + @Override public QName getQname() { return null; } + + @Override public QName getDataType() { return null; } + + @Override public QName getContainerClassType() { return null; } + + @Override public QName getModelQname() { return null; } + + @Override public String getDisplayName() { return displayName; } + + @Override public int compareTo(SpecialFacetablePropertyFTL that) + { + return this.name.compareTo(that.name); + } + + @Override public int hashCode() + { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + + ((displayName == null) ? 0 : displayName.hashCode()); + result = prime * result + ((name == null) ? 0 : name.hashCode()); + return result; + } + + @Override public boolean equals(Object obj) + { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (getClass() != obj.getClass()) + return false; + SpecialFacetablePropertyFTL other = (SpecialFacetablePropertyFTL) obj; + if (displayName == null) + { + if (other.displayName != null) + return false; + } else if (!displayName.equals(other.displayName)) + return false; + if (name == null) + { + if (other.name != null) + return false; + } else if (!name.equals(other.name)) + return false; + return true; + } + } + + /** + * In order to give deterministic responses when getting facetable properties, + * all {@link FacetablePropertyFTL} instances are sorted. This comparator provides + * the sorting implementation. + */ + public static class FacetablePropertyFTLComparator implements Comparator> + { + /** Used when sorting two objects of different types. */ + private final List> typeOrder = Arrays.asList(new Class[] { SpecialFacetablePropertyFTL.class, + SyntheticFacetablePropertyFTL.class, + StandardFacetablePropertyFTL.class} ); + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Override public int compare(FacetablePropertyFTL left, FacetablePropertyFTL right) + { + // First of all sort by the class according to the list above. + if ( !left.getClass().equals(right.getClass())) { return typeOrder.indexOf(left.getClass()) - + typeOrder.indexOf(right.getClass()); } + else + { + // Otherwise we have two facetable properties of the same class. + return left.compareTo(right); + } + } + } +} \ No newline at end of file