From e2dded6a183feb33a1d88e40ac6d6d90788bb091 Mon Sep 17 00:00:00 2001 From: Neil McErlean Date: Tue, 30 Sep 2014 11:42:55 +0000 Subject: [PATCH] Fix for issue blocking ACE-2932. This checkin means that the SolrFacetConfigAdmin{Post,Put} webscripts will accept both long and short-form qname strings. git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@85991 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- .../web/scripts/facet/FacetQNameUtils.java | 69 +++++++++++++ .../facet/SolrFacetConfigAdminPost.java | 5 +- .../facet/SolrFacetConfigAdminPut.java | 2 +- .../scripts/facet/FacetQNameUtilsTest.java | 96 +++++++++++++++++++ .../web/scripts/facet/FacetRestApiTest.java | 36 ++++++- 5 files changed, 204 insertions(+), 4 deletions(-) create mode 100644 source/java/org/alfresco/repo/web/scripts/facet/FacetQNameUtils.java create mode 100644 source/test-java/org/alfresco/repo/web/scripts/facet/FacetQNameUtilsTest.java diff --git a/source/java/org/alfresco/repo/web/scripts/facet/FacetQNameUtils.java b/source/java/org/alfresco/repo/web/scripts/facet/FacetQNameUtils.java new file mode 100644 index 0000000000..a00ba23cb6 --- /dev/null +++ b/source/java/org/alfresco/repo/web/scripts/facet/FacetQNameUtils.java @@ -0,0 +1,69 @@ +/* + * 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 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. + * 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 + { + // 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/web/scripts/facet/SolrFacetConfigAdminPost.java b/source/java/org/alfresco/repo/web/scripts/facet/SolrFacetConfigAdminPost.java index 490dca46a8..36a1cf67b2 100644 --- a/source/java/org/alfresco/repo/web/scripts/facet/SolrFacetConfigAdminPost.java +++ b/source/java/org/alfresco/repo/web/scripts/facet/SolrFacetConfigAdminPost.java @@ -80,7 +80,10 @@ public class SolrFacetConfigAdminPost extends AbstractSolrFacetConfigAdminWebScr validateFilterID(filterID); final String facetQNameStr = json.getString(PARAM_FACET_QNAME); - final QName facetQName = QName.createQName(facetQNameStr, namespaceService); + // Note: we're using this util class here because we need to be able to deal with + // qnames without a URI e.g. "{}SITE" and *not* have them default to the cm: namespace + // which happens with the 'normal' Alfresco QName code. + final QName facetQName = FacetQNameUtils.createQName(facetQNameStr, namespaceService); final String displayName = json.getString(PARAM_DISPLAY_NAME); final String displayControl = json.getString(PARAM_DISPLAY_CONTROL); final int maxFilters = json.getInt(PARAM_MAX_FILTERS); diff --git a/source/java/org/alfresco/repo/web/scripts/facet/SolrFacetConfigAdminPut.java b/source/java/org/alfresco/repo/web/scripts/facet/SolrFacetConfigAdminPut.java index 6288b2311f..08291915a3 100644 --- a/source/java/org/alfresco/repo/web/scripts/facet/SolrFacetConfigAdminPut.java +++ b/source/java/org/alfresco/repo/web/scripts/facet/SolrFacetConfigAdminPut.java @@ -141,7 +141,7 @@ public class SolrFacetConfigAdminPut extends AbstractSolrFacetConfigAdminWebScri // 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. - final QName facetQName = (facetQNameStr == null) ? null : QName.createQName(facetQNameStr, namespaceService); + final QName facetQName = (facetQNameStr == null) ? null : FacetQNameUtils.createQName(facetQNameStr, namespaceService); final String displayName = getValue(String.class, json.opt(PARAM_DISPLAY_NAME), null); final String displayControl = getValue(String.class, json.opt(PARAM_DISPLAY_CONTROL), null); final int maxFilters = getValue(Integer.class, json.opt(PARAM_MAX_FILTERS), -1); diff --git a/source/test-java/org/alfresco/repo/web/scripts/facet/FacetQNameUtilsTest.java b/source/test-java/org/alfresco/repo/web/scripts/facet/FacetQNameUtilsTest.java new file mode 100644 index 0000000000..026879bd16 --- /dev/null +++ b/source/test-java/org/alfresco/repo/web/scripts/facet/FacetQNameUtilsTest.java @@ -0,0 +1,96 @@ +/* + * 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 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)); + } + + // Note: it doesn't really make sense to have a short-form qname with no prefix. + @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 diff --git a/source/test-java/org/alfresco/repo/web/scripts/facet/FacetRestApiTest.java b/source/test-java/org/alfresco/repo/web/scripts/facet/FacetRestApiTest.java index b2b39d2174..42ece36c25 100644 --- a/source/test-java/org/alfresco/repo/web/scripts/facet/FacetRestApiTest.java +++ b/source/test-java/org/alfresco/repo/web/scripts/facet/FacetRestApiTest.java @@ -35,14 +35,18 @@ import org.alfresco.service.cmr.security.AuthorityService; import org.alfresco.service.cmr.security.AuthorityType; import org.alfresco.service.cmr.security.MutableAuthenticationService; import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.util.GUID; import org.alfresco.util.PropertyMap; import org.alfresco.util.collections.CollectionUtils; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.json.JSONTokener; +import org.springframework.extensions.webscripts.TestWebScriptServer.DeleteRequest; +import org.springframework.extensions.webscripts.TestWebScriptServer.GetRequest; +import org.springframework.extensions.webscripts.TestWebScriptServer.PostRequest; +import org.springframework.extensions.webscripts.TestWebScriptServer.PutRequest; import org.springframework.extensions.webscripts.TestWebScriptServer.Response; -import org.springframework.extensions.webscripts.TestWebScriptServer.*; /** * This class tests the ReST API of the {@link SolrFacetService}. @@ -504,7 +508,35 @@ public class FacetRestApiTest extends BaseWebScriptTest }, SEARCH_ADMIN_USER); } - + + /** The REST API should accept both 'cm:name' and '{http://www.alfresco.org/model/content/1.0}name' forms of filter IDs. */ + public void testCreateFacetWithLongFormQnameFilterId() throws Exception + { + final JSONObject filter = new JSONObject(); + final String filterName = "filter" + GUID.generate(); + filters.add(filterName); + filter.put("filterID", filterName); + // This is the long-form qname that needs to be acceptable. + filter.put("facetQName", "{http://www.alfresco.org/model/content/1.0}testLongQname"); + filter.put("displayName", "facet-menu.facet.testLongQname"); + filter.put("displayControl", "alfresco/search/FacetFilters/testLongQname"); + filter.put("maxFilters", 5); + filter.put("hitThreshold", 1); + filter.put("minFilterValueLength", 4); + filter.put("sortBy", "ALPHABETICALLY"); + + AuthenticationUtil.runAs(new RunAsWork() + { + @Override + public Void doWork() throws Exception + { + // Post the filter + sendRequest(new PostRequest(POST_FACETS_URL, filter.toString(), "application/json"), 200); + return null; + } + }, SEARCH_ADMIN_USER); + } + public void testUpdateSingleValue() throws Exception { // Build the Filter object