diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/solr/facet/solr-facet-config-admin.get.json.ftl b/config/alfresco/templates/webscripts/org/alfresco/repository/solr/facet/solr-facet-config-admin.get.json.ftl index 5a8d1c104e..71103c4aff 100644 --- a/config/alfresco/templates/webscripts/org/alfresco/repository/solr/facet/solr-facet-config-admin.get.json.ftl +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/solr/facet/solr-facet-config-admin.get.json.ftl @@ -16,7 +16,7 @@ ], - <#if facet.customProperties?size != 0> + <#if facet.customProperties?? && facet.customProperties?size != 0> "customProperties" : { <#list facet.customProperties as propDetails> diff --git a/source/java/org/alfresco/repo/web/scripts/solr/facet/AbstractSolrFacetConfigAdminWebScript.java b/source/java/org/alfresco/repo/web/scripts/solr/facet/AbstractSolrFacetConfigAdminWebScript.java index 17e8e2fa7b..a6effbd3af 100644 --- a/source/java/org/alfresco/repo/web/scripts/solr/facet/AbstractSolrFacetConfigAdminWebScript.java +++ b/source/java/org/alfresco/repo/web/scripts/solr/facet/AbstractSolrFacetConfigAdminWebScript.java @@ -19,9 +19,9 @@ package org.alfresco.repo.web.scripts.solr.facet; -import java.io.IOException; import java.io.Serializable; import java.util.ArrayList; +import java.util.Collections; import java.util.HashSet; import java.util.Map; import java.util.Set; @@ -29,7 +29,6 @@ import java.util.Set; import javax.servlet.http.HttpServletResponse; import org.alfresco.repo.search.impl.solr.facet.SolrFacetModel; -import org.alfresco.repo.search.impl.solr.facet.SolrFacetProperties; import org.alfresco.repo.search.impl.solr.facet.SolrFacetProperties.CustomProperties; import org.alfresco.repo.search.impl.solr.facet.SolrFacetService; import org.alfresco.repo.security.authentication.AuthenticationUtil; @@ -39,7 +38,6 @@ import org.apache.commons.logging.LogFactory; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; -import org.json.JSONTokener; import org.springframework.extensions.webscripts.Cache; import org.springframework.extensions.webscripts.DeclarativeWebScript; import org.springframework.extensions.webscripts.Status; @@ -100,69 +98,7 @@ public abstract class AbstractSolrFacetConfigAdminWebScript extends DeclarativeW } } - protected SolrFacetProperties parseRequestForFacetProperties(WebScriptRequest req) - { - JSONObject json = null; - try - { - json = new JSONObject(new JSONTokener(req.getContent().getContent())); - - final String filterID = json.getString(PARAM_FILTER_ID); - final String facetQNameStr = json.getString(PARAM_FACET_QNAME); - if (filterID == null || facetQNameStr == null) - { - String requiredProp = (filterID == null) ? "filterID" : "facetQName"; - throw new WebScriptException(Status.STATUS_BAD_REQUEST, requiredProp + " not provided."); - } - - final QName facetQName = QName.createQName(facetQNameStr); - final String displayName = json.getString(PARAM_DISPLAY_NAME); - final String displayControl = json.getString(PARAM_DISPLAY_CONTROL); - final int maxFilters = json.getInt(PARAM_MAX_FILTERS); - final int hitThreshold = json.getInt(PARAM_HIT_THRESHOLD); - final int minFilterValueLength = json.getInt(PARAM_MIN_FILTER_VALUE_LENGTH); - final String sortBy = json.getString(PARAM_SORT_BY); - final String scope = getValue(String.class, json.opt(PARAM_SCOPE), "ALL"); - final boolean isEnabled = getValue(Boolean.class, json.opt(PARAM_IS_ENABLED), false); - JSONArray scopedSitesJsonArray = getValue(JSONArray.class, json.opt(PARAM_SCOPED_SITES), null); - Set scopedSites = null; - if (scopedSitesJsonArray != null) - { - scopedSites = new HashSet(scopedSitesJsonArray.length()); - for (int i = 0, length = scopedSitesJsonArray.length(); i < length; i++) - { - String site = scopedSitesJsonArray.getString(i); - scopedSites.add(site); - } - } - final JSONObject customPropJsonObj = getValue(JSONObject.class, json.opt(PARAM_CUSTOM_PROPERTIES), null); - Set customProps = getCustomProperties(customPropJsonObj); - SolrFacetProperties fp = new SolrFacetProperties.Builder() - .filterID(filterID) - .facetQName(facetQName) - .displayName(displayName) - .displayControl(displayControl) - .maxFilters(maxFilters) - .hitThreshold(hitThreshold) - .minFilterValueLength(minFilterValueLength) - .sortBy(sortBy) - .scope(scope) - .isEnabled(isEnabled) - .scopedSites(scopedSites) - .customProperties(customProps).build(); - return fp; - } - catch (IOException e) - { - throw new WebScriptException(Status.STATUS_BAD_REQUEST, "Could not read content from req.", e); - } - catch (JSONException e) - { - throw new WebScriptException(Status.STATUS_BAD_REQUEST, "Could not parse JSON from req.", e); - } - } - - private T getValue(Class clazz, Object value, T defaultValue) throws JSONException + protected T getValue(Class clazz, Object value, T defaultValue) throws JSONException { if (JSONObject.NULL.equals(value)) { @@ -179,7 +115,7 @@ public abstract class AbstractSolrFacetConfigAdminWebScript extends DeclarativeW } } - private Set getCustomProperties(JSONObject customPropsJsonObj) throws JSONException + protected Set getCustomProperties(JSONObject customPropsJsonObj) throws JSONException { if (customPropsJsonObj == null) { @@ -188,9 +124,9 @@ public abstract class AbstractSolrFacetConfigAdminWebScript extends DeclarativeW JSONArray keys = customPropsJsonObj.names(); if (keys == null) { - return null; + return Collections.emptySet(); } - + Set customProps = new HashSet<>(keys.length()); for (int i = 0, length = keys.length(); i < length; i++) { @@ -229,6 +165,22 @@ public abstract class AbstractSolrFacetConfigAdminWebScript extends DeclarativeW return customProps; } + protected Set getScopedSites(JSONArray scopedSitesJsonArray) throws JSONException + { + if (scopedSitesJsonArray == null) + { + return null; + } + + Set scopedSites = new HashSet(scopedSitesJsonArray.length()); + for (int i = 0, length = scopedSitesJsonArray.length(); i < length; i++) + { + String site = scopedSitesJsonArray.getString(i); + scopedSites.add(site); + } + return scopedSites; + } + private void validateMandatoryCustomProps(Object obj, String paramName) throws JSONException { if (obj == null) diff --git a/source/java/org/alfresco/repo/web/scripts/solr/facet/SolrFacetConfigAdminPost.java b/source/java/org/alfresco/repo/web/scripts/solr/facet/SolrFacetConfigAdminPost.java index 9a12cbefe7..f75ed18363 100644 --- a/source/java/org/alfresco/repo/web/scripts/solr/facet/SolrFacetConfigAdminPost.java +++ b/source/java/org/alfresco/repo/web/scripts/solr/facet/SolrFacetConfigAdminPost.java @@ -19,12 +19,20 @@ package org.alfresco.repo.web.scripts.solr.facet; +import java.io.IOException; import java.util.HashMap; import java.util.Map; +import java.util.Set; import org.alfresco.repo.search.impl.solr.facet.SolrFacetProperties; +import org.alfresco.repo.search.impl.solr.facet.SolrFacetProperties.CustomProperties; +import org.alfresco.service.namespace.QName; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.json.JSONTokener; import org.springframework.extensions.webscripts.Cache; import org.springframework.extensions.webscripts.Status; import org.springframework.extensions.webscripts.WebScriptException; @@ -60,4 +68,53 @@ public class SolrFacetConfigAdminPost extends AbstractSolrFacetConfigAdminWebScr Map model = new HashMap(1); return model; } + + private SolrFacetProperties parseRequestForFacetProperties(WebScriptRequest req) + { + JSONObject json = null; + try + { + json = new JSONObject(new JSONTokener(req.getContent().getContent())); + + final String filterID = json.getString(PARAM_FILTER_ID); + final String facetQNameStr = json.getString(PARAM_FACET_QNAME); + final QName facetQName = QName.createQName(facetQNameStr); + final String displayName = json.getString(PARAM_DISPLAY_NAME); + final String displayControl = json.getString(PARAM_DISPLAY_CONTROL); + final int maxFilters = json.getInt(PARAM_MAX_FILTERS); + final int hitThreshold = json.getInt(PARAM_HIT_THRESHOLD); + final int minFilterValueLength = json.getInt(PARAM_MIN_FILTER_VALUE_LENGTH); + final String sortBy = json.getString(PARAM_SORT_BY); + // Optional params + final String scope = getValue(String.class, json.opt(PARAM_SCOPE), "ALL"); + final boolean isEnabled = getValue(Boolean.class, json.opt(PARAM_IS_ENABLED), false); + JSONArray scopedSitesJsonArray = getValue(JSONArray.class, json.opt(PARAM_SCOPED_SITES), null); + final Set scopedSites = getScopedSites(scopedSitesJsonArray); + final JSONObject customPropJsonObj = getValue(JSONObject.class, json.opt(PARAM_CUSTOM_PROPERTIES), null); + final Set customProps = getCustomProperties(customPropJsonObj); + + SolrFacetProperties fp = new SolrFacetProperties.Builder() + .filterID(filterID) + .facetQName(facetQName) + .displayName(displayName) + .displayControl(displayControl) + .maxFilters(maxFilters) + .hitThreshold(hitThreshold) + .minFilterValueLength(minFilterValueLength) + .sortBy(sortBy) + .scope(scope) + .isEnabled(isEnabled) + .scopedSites(scopedSites) + .customProperties(customProps).build(); + return fp; + } + catch (IOException e) + { + throw new WebScriptException(Status.STATUS_BAD_REQUEST, "Could not read content from req.", e); + } + catch (JSONException e) + { + throw new WebScriptException(Status.STATUS_BAD_REQUEST, "Could not parse JSON from req.", e); + } + } } diff --git a/source/java/org/alfresco/repo/web/scripts/solr/facet/SolrFacetConfigAdminPut.java b/source/java/org/alfresco/repo/web/scripts/solr/facet/SolrFacetConfigAdminPut.java index 16a9b9aa07..9e63acea1b 100644 --- a/source/java/org/alfresco/repo/web/scripts/solr/facet/SolrFacetConfigAdminPut.java +++ b/source/java/org/alfresco/repo/web/scripts/solr/facet/SolrFacetConfigAdminPut.java @@ -19,16 +19,24 @@ package org.alfresco.repo.web.scripts.solr.facet; +import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import org.alfresco.repo.search.impl.solr.facet.Exceptions.UnrecognisedFacetId; +import org.alfresco.repo.search.impl.solr.facet.SolrFacetProperties.CustomProperties; import org.alfresco.repo.search.impl.solr.facet.SolrFacetProperties; +import org.alfresco.service.namespace.QName; import org.alfresco.util.collections.CollectionUtils; import org.alfresco.util.collections.Function; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.json.JSONTokener; import org.springframework.extensions.webscripts.Cache; import org.springframework.extensions.webscripts.Status; import org.springframework.extensions.webscripts.WebScriptException; @@ -47,7 +55,7 @@ public class SolrFacetConfigAdminPut extends AbstractSolrFacetConfigAdminWebScri protected static final String PARAM_RELATIVE_POS = "relativePos"; protected static final String URL_PARAM_FILTER_ID = "filterID"; - + @Override protected Map unprotectedExecuteImpl(WebScriptRequest req, Status status, Cache cache) { @@ -119,4 +127,52 @@ public class SolrFacetConfigAdminPut extends AbstractSolrFacetConfigAdminWebScri return model; } + private SolrFacetProperties parseRequestForFacetProperties(WebScriptRequest req) + { + JSONObject json = null; + try + { + json = new JSONObject(new JSONTokener(req.getContent().getContent())); + + final String filterID = json.getString(PARAM_FILTER_ID); // Must exist + + final String facetQNameStr = getValue(String.class, json.opt(PARAM_FACET_QNAME), null); + final QName facetQName = (facetQNameStr == null) ? null : QName.createQName(facetQNameStr); + 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); + final int hitThreshold = getValue(Integer.class, json.opt(PARAM_HIT_THRESHOLD), -1); + final int minFilterValueLength = getValue(Integer.class, json.opt(PARAM_MIN_FILTER_VALUE_LENGTH), -1); + final String sortBy = getValue(String.class, json.opt(PARAM_SORT_BY), null); + final String scope = getValue(String.class, json.opt(PARAM_SCOPE), null); + final Boolean isEnabled = getValue(Boolean.class, json.opt(PARAM_IS_ENABLED), null); + JSONArray scopedSitesJsonArray = getValue(JSONArray.class, json.opt(PARAM_SCOPED_SITES), null); + final Set scopedSites = getScopedSites(scopedSitesJsonArray); + final JSONObject customPropJsonObj = getValue(JSONObject.class, json.opt(PARAM_CUSTOM_PROPERTIES), null); + final Set customProps = getCustomProperties(customPropJsonObj); + + SolrFacetProperties fp = new SolrFacetProperties.Builder() + .filterID(filterID) + .facetQName(facetQName) + .displayName(displayName) + .displayControl(displayControl) + .maxFilters(maxFilters) + .hitThreshold(hitThreshold) + .minFilterValueLength(minFilterValueLength) + .sortBy(sortBy) + .scope(scope) + .isEnabled(isEnabled) + .scopedSites(scopedSites) + .customProperties(customProps).build(); + return fp; + } + catch (IOException e) + { + throw new WebScriptException(Status.STATUS_BAD_REQUEST, "Could not read content from req.", e); + } + catch (JSONException e) + { + throw new WebScriptException(Status.STATUS_BAD_REQUEST, "Could not parse JSON from req.", e); + } + } } diff --git a/source/test-java/org/alfresco/repo/web/scripts/solr/facet/FacetRestApiTest.java b/source/test-java/org/alfresco/repo/web/scripts/solr/facet/FacetRestApiTest.java index c3bb5cd63c..bd1f8b6ed9 100644 --- a/source/test-java/org/alfresco/repo/web/scripts/solr/facet/FacetRestApiTest.java +++ b/source/test-java/org/alfresco/repo/web/scripts/solr/facet/FacetRestApiTest.java @@ -46,7 +46,7 @@ import org.springframework.extensions.webscripts.TestWebScriptServer.*; /** * This class tests the ReST API of the {@link SolrFacetService}. - * + * * @author Neil Mc Erlean * @author Jamal Kaabi-Mofrad * @since 5.0 @@ -474,6 +474,124 @@ public class FacetRestApiTest extends BaseWebScriptTest } + public void testUpdateSingleValue() throws Exception + { + // Build the Filter object + final JSONObject filter = new JSONObject(); + final String filterName = "filter" + System.currentTimeMillis(); + filters.add(filterName); + filter.put("filterID", filterName); + filter.put("facetQName", "{http://www.alfresco.org/model/content/1.0}test"); + filter.put("displayName", "facet-menu.facet.test1"); + filter.put("displayControl", "alfresco/search/FacetFilters/test"); + filter.put("maxFilters", 5); + filter.put("hitThreshold", 1); + filter.put("minFilterValueLength", 4); + filter.put("sortBy", "ALPHABETICALLY"); + filter.put("isEnabled", true); + + JSONObject customProp = new JSONObject(); + // 1st custom prop + JSONObject blockIncludeRequest = new JSONObject(); + blockIncludeRequest.put("name", "blockIncludeFacetRequest"); + blockIncludeRequest.put("value", "true"); + customProp.put("blockIncludeFacetRequest", blockIncludeRequest); + filter.put("customProperties", customProp); + + 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); + + // Admin updates displayName and facetQName in 2 put requests + AuthenticationUtil.runAs(new RunAsWork() + { + @Override + public Void doWork() throws Exception + { + // Retrieve the created filter + Response response = sendRequest(new GetRequest(GET_FACETS_URL + "/" + filterName), 200); + JSONObject jsonRsp = new JSONObject(new JSONTokener(response.getContentAsString())); + + assertEquals(filterName, jsonRsp.getString("filterID")); + assertEquals("facet-menu.facet.test1", jsonRsp.getString("displayName")); + assertEquals("{http://www.alfresco.org/model/content/1.0}test", jsonRsp.getString("facetQName")); + assertTrue(jsonRsp.getBoolean("isEnabled")); + + // Just supply the filterID and the required value + JSONObject singleValueJson = new JSONObject(); + singleValueJson.put("filterID", filterName); + // Change the displayName value and update + singleValueJson.put("displayName", "facet-menu.facet.modifiedValue"); + sendRequest(new PutRequest(PUT_FACETS_URL, singleValueJson.toString(), "application/json"), 200); + + // Change the isEnabled value and update + // We simulate two PUT requests without refreshing the page in + // between updates + singleValueJson = new JSONObject(); + singleValueJson.put("filterID", filterName); + singleValueJson.put("isEnabled", false); + sendRequest(new PutRequest(PUT_FACETS_URL, singleValueJson.toString(), "application/json"), 200); + + response = sendRequest(new GetRequest(GET_FACETS_URL + "/" + filterName), 200); + jsonRsp = new JSONObject(new JSONTokener(response.getContentAsString())); + + // Now see if the two changes have been persisted + assertEquals("facet-menu.facet.modifiedValue", jsonRsp.getString("displayName")); + assertFalse(jsonRsp.getBoolean("isEnabled")); + // Make sure the rest of values haven't been changed + assertEquals(filterName, jsonRsp.getString("filterID")); + assertEquals("{http://www.alfresco.org/model/content/1.0}test", jsonRsp.getString("facetQName")); + assertEquals("alfresco/search/FacetFilters/test", jsonRsp.getString("displayControl")); + assertEquals(5, jsonRsp.getInt("maxFilters")); + assertEquals(1, jsonRsp.getInt("hitThreshold")); + assertEquals(4, jsonRsp.getInt("minFilterValueLength")); + assertEquals("ALPHABETICALLY", jsonRsp.getString("sortBy")); + assertEquals("ALL", jsonRsp.getString("scope")); + assertFalse(jsonRsp.getBoolean("isDefault")); + // Make sure custom properties haven't been deleted + JSONObject retrievedCustomProp = jsonRsp.getJSONObject("customProperties"); + JSONObject retrievedBlockIncludeRequest = retrievedCustomProp.getJSONObject("blockIncludeFacetRequest"); + assertEquals("{http://www.alfresco.org/model/solrfacetcustomproperty/1.0}blockIncludeFacetRequest", retrievedBlockIncludeRequest.get("name")); + assertEquals("true", retrievedBlockIncludeRequest.get("value")); + + // Change the facetQName value and update + singleValueJson = new JSONObject(); + singleValueJson.put("filterID", filterName); + singleValueJson.put("facetQName", "{http://www.alfresco.org/model/content/1.0}testModifiedValue"); + // We simulate that 'testModifiedValue' QName doesn't have custom properties + singleValueJson.put("customProperties", new JSONObject()); + sendRequest(new PutRequest(PUT_FACETS_URL, singleValueJson.toString(), "application/json"), 200); + + response = sendRequest(new GetRequest(GET_FACETS_URL + "/" + filterName), 200); + jsonRsp = new JSONObject(new JSONTokener(response.getContentAsString())); + + // Now see if the facetQName and its side-effect have been persisted + assertEquals("{http://www.alfresco.org/model/content/1.0}testModifiedValue",jsonRsp.getString("facetQName")); + assertNull("Custom properties should have been deleted.", jsonRsp.opt("customProperties")); + // Make sure the rest of values haven't been changed + assertEquals(filterName, jsonRsp.getString("filterID")); + assertEquals("facet-menu.facet.modifiedValue", jsonRsp.getString("displayName")); + assertEquals("alfresco/search/FacetFilters/test", jsonRsp.getString("displayControl")); + assertEquals(5, jsonRsp.getInt("maxFilters")); + assertEquals(1, jsonRsp.getInt("hitThreshold")); + assertEquals(4, jsonRsp.getInt("minFilterValueLength")); + assertEquals("ALPHABETICALLY", jsonRsp.getString("sortBy")); + assertFalse(jsonRsp.getBoolean("isDefault")); + assertEquals("ALL", jsonRsp.getString("scope")); + assertFalse(jsonRsp.getBoolean("isEnabled")); + + return null; + } + }, SEARCH_ADMIN_USER); + } + private List getListFromJsonArray(JSONArray facetsArray) throws JSONException { List result = new ArrayList<>();