diff --git a/source/java/org/alfresco/repo/search/impl/solr/facet/FacetQNameUtils.java b/source/java/org/alfresco/repo/search/impl/solr/facet/FacetQNameUtils.java new file mode 100644 index 0000000000..0ba4965684 --- /dev/null +++ b/source/java/org/alfresco/repo/search/impl/solr/facet/FacetQNameUtils.java @@ -0,0 +1,77 @@ +/* + * 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.search.impl.solr.facet; + +import org.alfresco.service.namespace.NamespacePrefixResolver; +import org.alfresco.service.namespace.QName; + +/** + * This class provides some simple utility methods for dealing with {@link QName QNames} + * within the faceted search feature. The whole thing should be considered a hack to handle 'special cases'. + *

+ * These are not intended for general use, or else they'd be in the {@link QName} class. + * @since 5.0 + */ +public abstract class FacetQNameUtils +{ + /** + * This method converts the supplied qname string into a {@link QName} object. + * It accepts both short and long form qname strings. + * + * @param s a qname string, such as "cm:name" or "{http://www.alfresco.org/model/content/1.0}name" + * @param resolver this is needed to convert any qname prefixes into their long form. + * @return the QName instance. + * @throws NullPointerException if the provided string is {@code null}. + * @throws IllegalArgumentException if the provided string could not be recognised as a valid QName. + */ + public static QName createQName(String s, NamespacePrefixResolver resolver) + { + final QName result; + + if (s.length() < 2) { throw new IllegalArgumentException("Cannot convert string '" + s + "'"); } + + if (s.charAt(0) == QName.NAMESPACE_BEGIN && + s.substring(1).contains(Character.toString(QName.NAMESPACE_END))) + { + // Assume it's a long-form qname. + result = QName.createQName(s); + } + else if ( !s.contains(Character.toString(QName.NAMESPACE_BEGIN)) && + s.contains(Character.toString(QName.NAMESPACE_PREFIX))) + { + // Assume it's a short-form qname. + result = QName.createQName(s, resolver); + } + else if (!s.contains(Character.toString(QName.NAMESPACE_BEGIN)) && + !s.contains(Character.toString(QName.NAMESPACE_END)) && + !s.contains(Character.toString(QName.NAMESPACE_PREFIX))) + { + // No '{', '}' or ':' means it's a prefixless QName (SITE or TAG, in our case). + result = QName.createQName(null, s); + } + else + { + // We're not sure what sort of qname this is supposed to be. + throw new IllegalArgumentException("Cannot convert string '" + s + "'"); + } + + return result; + } +} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/search/impl/solr/facet/SolrFacetConfig.java b/source/java/org/alfresco/repo/search/impl/solr/facet/SolrFacetConfig.java index 8eb6390adf..a801af1b68 100644 --- a/source/java/org/alfresco/repo/search/impl/solr/facet/SolrFacetConfig.java +++ b/source/java/org/alfresco/repo/search/impl/solr/facet/SolrFacetConfig.java @@ -365,7 +365,10 @@ public class SolrFacetConfig extends AbstractLifecycleBean private static QName resolveToQName(NamespaceService namespaceService, String qnameStr) { - QName typeQName = QName.resolveToQName(namespaceService, qnameStr); + // Note that in resolving the QName string here, we expect there to be some facet QNames which are not + // really QNames. These are SOLR/SearchService 'specials', examples being "SITE" or "TAG". + // These will be resolved here to a QName with no namespace. + QName typeQName = FacetQNameUtils.createQName( qnameStr, namespaceService); if (logger.isDebugEnabled()) { logger.debug("Resolved facet field [" + qnameStr + "] into [" + typeQName + "]"); diff --git a/source/test-java/org/alfresco/AllUnitTestsSuite.java b/source/test-java/org/alfresco/AllUnitTestsSuite.java index f7628d091d..eefa90dcb3 100644 --- a/source/test-java/org/alfresco/AllUnitTestsSuite.java +++ b/source/test-java/org/alfresco/AllUnitTestsSuite.java @@ -99,6 +99,7 @@ public class AllUnitTestsSuite extends TestSuite suite.addTest(new JUnit4TestAdapter(org.alfresco.repo.search.impl.solr.SolrQueryHTTPClientTest.class)); suite.addTest(new JUnit4TestAdapter(org.alfresco.repo.search.impl.solr.SolrStatsResultTest.class)); suite.addTest(new JUnit4TestAdapter(org.alfresco.repo.search.impl.solr.facet.SolrFacetComparatorTest.class)); + suite.addTest(new JUnit4TestAdapter(org.alfresco.repo.search.impl.solr.facet.FacetQNameUtilsTest.class)); suite.addTest(new JUnit4TestAdapter(org.alfresco.util.BeanExtenderUnitTest.class)); suite.addTest(new JUnit4TestAdapter(org.alfresco.repo.search.impl.solr.SpellCheckDecisionManagerTest.class)); suite.addTest(new JUnit4TestAdapter(org.alfresco.repo.search.impl.solr.SolrStoreMappingWrapperTest.class)); diff --git a/source/test-java/org/alfresco/repo/search/impl/solr/facet/FacetQNameUtilsTest.java b/source/test-java/org/alfresco/repo/search/impl/solr/facet/FacetQNameUtilsTest.java new file mode 100644 index 0000000000..6d51964d69 --- /dev/null +++ b/source/test-java/org/alfresco/repo/search/impl/solr/facet/FacetQNameUtilsTest.java @@ -0,0 +1,103 @@ +/* + * 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.search.impl.solr.facet; + +import static org.junit.Assert.assertEquals; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import org.alfresco.service.namespace.NamespaceException; +import org.alfresco.service.namespace.NamespacePrefixResolver; +import org.alfresco.service.namespace.QName; +import org.junit.Test; + +/** + * Unit tests for {@link FacetQNameUtils}. + * @since 5.0 + */ +public class FacetQNameUtilsTest +{ + /** A test-only namespace resolver. */ + private final NamespacePrefixResolver resolver = new NamespacePrefixResolver() + { + private final List uris = Arrays.asList(new String[] { "http://www.alfresco.org/model/foo/1.0" }); + private final List prefixes = Arrays.asList(new String[] { "foo" }); + + @Override public Collection getURIs() { return this.uris; } + @Override public Collection getPrefixes() { return this.prefixes; } + + @Override public Collection getPrefixes(String namespaceURI) throws NamespaceException + { + if (uris.contains(namespaceURI)) + { return prefixes; } + else + { throw new NamespaceException("Unrecognised namespace: " + namespaceURI); } + } + + @Override public String getNamespaceURI(String prefix) throws NamespaceException + { + if (prefixes.contains(prefix)) + { return "http://www.alfresco.org/model/foo/1.0"; } + else + { throw new NamespaceException("Unrecognised prefix: " + prefix); } + } + }; + + @Test public void canCreateFromShortForm() throws Exception + { + assertEquals(QName.createQName("http://www.alfresco.org/model/foo/1.0", "localName"), + FacetQNameUtils.createQName("foo:localName", resolver)); + } + + @Test public void canCreateFromLongForm() throws Exception + { + assertEquals(QName.createQName("http://www.alfresco.org/model/foo/1.0", "localName"), + FacetQNameUtils.createQName("{http://www.alfresco.org/model/foo/1.0}localName", resolver)); + } + + @Test public void canCreateFromShortFormWithNoPrefix() throws Exception + { + // The sensibleness of this from an Alfresco perspective is questionable. + // But this is what we must do to support 'QName' strings like "Site" or "Tag" in the REST API. + assertEquals(QName.createQName(null, "localName"), + FacetQNameUtils.createQName("localName", resolver)); + } + + @Test public void canCreateFromLongFormWithNoPrefix() throws Exception + { + assertEquals(QName.createQName(null, "localName"), + FacetQNameUtils.createQName("{}localName", resolver)); + } + + @Test public void canCreateLongFormQnameWithUnrecognisedUri() throws Exception + { + // Intentionally no validation of URIs against dictionary. + assertEquals(QName.createQName("http://www.alfresco.org/model/illegal/1.0", "localName"), + FacetQNameUtils.createQName("{http://www.alfresco.org/model/illegal/1.0}localName", resolver)); + } + + @Test (expected=NamespaceException.class) + public void shortFormQnameWithUnrecognisedPrefixFails() throws Exception + { + FacetQNameUtils.createQName("illegal:localName", resolver); + } +} \ No newline at end of file