Java tutorial
/* * 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 <http://www.gnu.org/licenses/>. */ package org.alfresco.solr.component; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import com.google.common.collect.HashBiMap; import org.alfresco.solr.AlfrescoSolrDataModel; import org.alfresco.solr.AlfrescoSolrDataModel.FieldUse; import org.alfresco.solr.query.MimetypeGroupingQParserPlugin; import org.apache.commons.lang.StringUtils; import org.apache.solr.common.params.CommonParams; import org.apache.solr.common.params.ModifiableSolrParams; import org.apache.solr.common.params.ShardParams; import org.apache.solr.common.params.SolrParams; import org.apache.solr.handler.component.ResponseBuilder; import org.apache.solr.handler.component.SearchComponent; import org.apache.solr.handler.component.ShardRequest; import org.apache.solr.request.SolrQueryRequest; /** * Update facet parameters to reference Solr fields rather than ACS properties. * * @author Andy */ public class RewriteFacetParametersComponent extends SearchComponent { /* (non-Javadoc) * @see org.apache.solr.handler.component.SearchComponent#prepare(org.apache.solr.handler.component.ResponseBuilder) */ @Override public void prepare(ResponseBuilder rb) throws IOException { SolrQueryRequest req = rb.req; SolrParams params = req.getParams(); ModifiableSolrParams fixed = new ModifiableSolrParams(); ModifiableSolrParams allParamsWithFix = new ModifiableSolrParams(params); fixFilterQueries(fixed, params, rb); fixFacetParams(fixed, params, rb); fixRows(fixed, params, rb); Set<String> fixedParameterNames = fixed.getParameterNames(); for (String fixedParam : fixedParameterNames) { allParamsWithFix.set(fixedParam, fixed.getParams(fixedParam)); } if (allParamsWithFix.get(CommonParams.SORT) != null) { allParamsWithFix.remove(CommonParams.RQ); } req.setParams(allParamsWithFix); } /** * Prevents users from requesting a large number of rows by * replacing an enormous row value with a maximum value that will * not cause a run time exception. * @param fixed * @param params * @param rb */ private void fixRows(ModifiableSolrParams fixed, SolrParams params, ResponseBuilder rb) { String rows = params.get("rows"); if (rows != null && !rows.isEmpty()) { Integer row = Integer.valueOf(rows); // Avoid +1 in SOLR code which produces null:java.lang.NegativeArraySizeException at at org.apache.lucene.util.PriorityQueue.<init>(PriorityQueue.java:56) if (row > 1000000) { fixed.remove("rows"); fixed.add("rows", "1000000"); } } } private void fixFilterQueries(ModifiableSolrParams fixed, SolrParams params, ResponseBuilder rb) { for (Iterator<String> it = params.getParameterNamesIterator(); it.hasNext(); /**/) { String name = it.next(); if (name.equals("fq")) { String[] values = params.getParams(name); if (values != null) { String[] fixedValues = new String[values.length]; for (int i = 0; i < values.length; i++) { String value = values[i]; if (value.startsWith("{!")) { fixedValues[i] = value; } else { if (value.startsWith("contentSize():")) { value = "cm:content.size:" + removeQuotes(value.substring("contentSize():".length())); } else if (value.startsWith("mimetype():")) { value = removeQuotes(value.substring("mimetype():".length())); ArrayList<String> expand = MimetypeGroupingQParserPlugin.getReverseMappings() .get(value); if (expand == null) { value = "cm:content.mimetype:\"" + value + "\""; } else { StringBuilder builder = new StringBuilder(); builder.append("cm:content.mimetype:("); for (int j = 0; j < expand.size(); j++) { if (j > 0) { builder.append(" OR "); } builder.append('"'); builder.append(expand.get(j)); builder.append('"'); } builder.append(')'); value = builder.toString(); } } fixedValues[i] = "{!afts}" + value; } } fixed.add(name, fixedValues); } } } } private String removeQuotes(String quoted) { if (quoted.startsWith("\"") && quoted.endsWith("\"")) { return quoted.substring(1, quoted.length() - 1); } else { return quoted; } } /** * @param fixed ModifiableSolrParams * @param params SolrParams * @param rb ResponseBuilder * @return SolrParams */ private SolrParams fixFacetParams(ModifiableSolrParams fixed, SolrParams params, ResponseBuilder rb) { // Use BiMaps so that these can be quickly inverted in RewriteFacetCountsComponent. Map<String, String> fieldMappings = HashBiMap.create(); Map<String, String> dateMappings = HashBiMap.create(); Map<String, String> rangeMappings = HashBiMap.create(); Map<String, String> pivotMappings = HashBiMap.create(); Map<String, String> intervalMappings = HashBiMap.create(); Map<String, String> statsFieldMappings = HashBiMap.create(); Map<String, String> statsFacetMappings = HashBiMap.create(); Map<String, String> functionMappings = HashBiMap.create(); rewriteFacetFieldList(fixed, params, "facet.field", fieldMappings, rb.req); rewriteFacetFieldList(fixed, params, "facet.date", dateMappings, rb.req); rewriteFacetFieldList(fixed, params, "facet.range", rangeMappings, rb.req); rewriteFacetFieldList(fixed, params, "facet.pivot", pivotMappings, rb.req); rewriteFacetFieldList(fixed, params, "facet.interval", intervalMappings, rb.req); rewriteFacetFieldList(fixed, params, "stats.field", statsFieldMappings, rb.req); rewriteFacetFieldList(fixed, params, "stats.facet", statsFacetMappings, rb.req); mapFacetFunctions(fixed, params, "facet.field", functionMappings); rewriteFacetFieldOptions(fixed, params, "facet.prefix", fieldMappings); rewriteFacetFieldOptions(fixed, params, "facet.contains", fieldMappings); rewriteFacetFieldOptions(fixed, params, "facet.contains.ignoreCase", fieldMappings); rewriteFacetFieldOptions(fixed, params, "facet.sort", fieldMappings); rewriteFacetFieldOptions(fixed, params, "facet.limit", fieldMappings); rewriteFacetFieldOptions(fixed, params, "facet.offset", fieldMappings); rewriteMincountFacetFieldOption(fixed, params, "facet.mincount", fieldMappings, rb.req); rewriteFacetFieldOptions(fixed, params, "facet.missing", fieldMappings); rewriteFacetFieldOptions(fixed, params, "facet.method", fieldMappings); rewriteFacetFieldOptions(fixed, params, "facet.enum.cache.minDF", fieldMappings); rewriteFacetFieldOptions(fixed, params, "facet.enum.cache.minDF", fieldMappings); rewriteFacetFieldOptions(fixed, params, "facet.range.start", rangeMappings); rewriteFacetFieldOptions(fixed, params, "facet.range.end", rangeMappings); rewriteFacetFieldOptions(fixed, params, "facet.range.gap", rangeMappings); rewriteFacetFieldOptions(fixed, params, "facet.range.hardend", rangeMappings); rewriteFacetFieldOptions(fixed, params, "facet.range.include", rangeMappings); rewriteFacetFieldOptions(fixed, params, "facet.range.other", rangeMappings); rewriteFacetFieldOptions(fixed, params, "facet.range.method", rangeMappings); rewriteFacetFieldOptions(fixed, params, "facet.range.limit", rangeMappings); rewriteMincountFacetFieldOption(fixed, params, "facet.pivot.mincount", pivotMappings, rb.req); rewriteFacetFieldOptions(fixed, params, "facet.sort", pivotMappings); rewriteFacetFieldOptions(fixed, params, "facet.limit", pivotMappings); rewriteFacetFieldOptions(fixed, params, "facet.offset", pivotMappings); rewriteFacetFieldOptions(fixed, params, "facet.interval.set", intervalMappings); // TODO: // f.<stats_field>.stats.facet=<new Field> // would require a more complex rewrite rb.rsp.add("_original_parameters_", params); rb.rsp.add("_field_mappings_", fieldMappings); rb.rsp.add("_date_mappings_", dateMappings); rb.rsp.add("_range_mappings_", rangeMappings); rb.rsp.add("_pivot_mappings_", pivotMappings); rb.rsp.add("_interval_mappings_", intervalMappings); rb.rsp.add("_stats_field_mappings_", statsFieldMappings); rb.rsp.add("_stats_facet_mappings_", statsFacetMappings); rb.rsp.add("_facet_function_mappings_", functionMappings); return fixed; } /** * Populate a map with the mappings from functions in the query to functions including the Solr filter handler. * * @param fixed The updated params object. * @param params The original params object. * @param paramName The name of the parameter (e.g. "facet.field"). * @param facetFunctionMappings The map to update. */ private void mapFacetFunctions(ModifiableSolrParams fixed, SolrParams params, String paramName, Map<String, String> facetFunctionMappings) { String[] facetFieldsOrig = params.getParams(paramName); if (facetFieldsOrig != null) { for (String facetFields : facetFieldsOrig) { String[] fields = facetFields.split(","); for (String field : fields) { field = field.trim(); if (field.endsWith("()")) { if (isMimetypeAndHasFQ(params, field)) { String function = "{!" + field.substring(0, field.length() - 2) + " group=false }"; fixed.add("fq", function); facetFunctionMappings.put(field, function); } else { String function = "{!" + field.substring(0, field.length() - 2) + " group=true}"; fixed.add("fq", function); facetFunctionMappings.put(field, function); } } } } } } /** * @param params * @param field * @return */ private boolean isMimetypeAndHasFQ(SolrParams params, String field) { if (!field.equals("mimetype()")) { return false; } else { String[] filterQueries = params.getParams("fq"); if (filterQueries != null) { for (String fq : filterQueries) { if (fq.startsWith("mimetype():")) { return true; } } } } return false; } /** * Ensure the mincount for all given facets is at least 1 to prevent exposing sensitive buckets to users without permission. * * @param fixed The updated params object. * @param params The original params object. * @param paramName The name of the mincount parameter to rewrite (e.g. "facet.mincount" or "facet.pivot.mincount"). * @param fieldMappings A list of mappings from Alfresco property names to Solr field names. * @param req The Solr request */ protected void rewriteMincountFacetFieldOption(ModifiableSolrParams fixed, SolrParams params, String paramName, Map<String, String> fieldMappings, SolrQueryRequest req) { rewriteFacetFieldOptions(fixed, params, paramName, fieldMappings); String shardPurpose = req.getParams().get(ShardParams.SHARDS_PURPOSE); boolean isInitialDistributedRequest = (shardPurpose == null); /* * After the initial Search request, in case of Sharding the Solr node will build additional http requests to the other shards. * These requests may have specific parameters rewritten by Solr internally. * It's not recommended to rewrite them, because some side effect may happen. * Only the initial request parameters may be rewritten. * */ if (isInitialDistributedRequest) { // Update any existing mincount entries for facets. Map<String, Integer> updatedValues = new HashMap<>(); for (Iterator<String> renamedParameters = fixed.getParameterNamesIterator(); renamedParameters .hasNext();) { String solrParameter = renamedParameters.next(); if (solrParameter.endsWith("." + paramName)) { // If mincount is set then renamedParameters must be an integer. int value = Integer.valueOf(fixed.get(solrParameter)); // Don't directly edit the params while we're iterating through the entries. updatedValues.put(solrParameter, Math.max(value, 1)); } } // Now we've finished iterating, update the fixed parameters. for (Map.Entry<String, Integer> updatedParameterValues : updatedValues.entrySet()) { fixed.set(updatedParameterValues.getKey(), updatedParameterValues.getValue()); } String paramValue = params.get(paramName); int value = 0; if (paramValue != null) { value = Integer.valueOf(paramValue); } fixed.set(paramName, Math.max(value, 1)); } } /** * Update any existing facet parameters to use the Solr field names. * * @param fixed The updated params object. * @param params The original params object. * @param paramName The name of the parameter to rewrite (e.g. "facet.limit" or "f.fieldName.facet.limit"). * @param fieldMappings A list of mappings from Alfresco property names to Solr field names. */ private void rewriteFacetFieldOptions(ModifiableSolrParams fixed, SolrParams params, String paramName, Map<String, String> fieldMappings) { for (Iterator<String> originalParamsIterator = params.getParameterNamesIterator(); originalParamsIterator .hasNext(); /**/) { String originalSolrParam = originalParamsIterator.next(); //it contains the alfresco field name if (originalSolrParam.startsWith("f.")) { if (originalSolrParam.endsWith("." + paramName)) { String originalAlfrescoFieldName = originalSolrParam.substring(2, originalSolrParam.length() - paramName.length() - 1); if (fieldMappings.containsKey(originalAlfrescoFieldName)) { String solrFieldName = fieldMappings.get(originalAlfrescoFieldName); fixed.set("f." + solrFieldName + "." + paramName, params.getParams(originalSolrParam)); } else { fixed.set(originalSolrParam, params.getParams(originalSolrParam)); } } } } } /** * Tokenizes a string based on comma's except for the ones in single or double * qoutes. * @param line * @return */ public static String[] parseFacetField(String line) { if (StringUtils.isEmpty(line)) { throw new RuntimeException("String input is requried"); } String[] tokens = line.split(",(?=(?:[^'|\"]*\"[^'|\"]*\")*[^'|\"]*$)", -1); return tokens; } /** * Update the facet fields to use Solr field names rather than ACS property names. * * @param fixed The updated params object. * @param params The original params object. * @param paramName The name of the mincount parameter to rewrite (e.g. "facet.mincount" or "facet.pivot.mincount"). * @param fieldMappings A list of mappings from Alfresco property names to Solr field names. * @param req The original request. * @return An array of the facet field names. */ private List<String> rewriteFacetFieldList(ModifiableSolrParams fixed, SolrParams params, String paramName, Map<String, String> fieldMappings, SolrQueryRequest req) { String shardPurpose = req.getParams().get(ShardParams.SHARDS_PURPOSE); boolean isRefinementRequest = (shardPurpose != null) ? (shardPurpose.equals(String.valueOf(ShardRequest.PURPOSE_REFINE_FACETS))) || (shardPurpose.equals(String.valueOf(ShardRequest.PURPOSE_REFINE_PIVOT_FACETS))) : false; String[] facetFieldsOrig = params.getParams(paramName); List<String> facetFieldList = new ArrayList<>(); if (facetFieldsOrig != null) { ArrayList<String> newFacetFields = new ArrayList<String>(); for (String facetFields : facetFieldsOrig) { StringBuilder commaSeparated = new StringBuilder(); StringBuilder mapping = new StringBuilder(); StringBuilder unmapped = new StringBuilder(); String[] fields = parseFacetField(facetFields); for (String field : fields) { String prefix = ""; field = field.trim(); if (field.endsWith("()")) { // skip facet functions continue; } if (field.startsWith("{!") && !(isRefinementRequest)) { int index = field.indexOf("}"); if ((index > 0) && (index < (field.length() - 1))) { prefix = field.substring(0, index + 1); field = field.substring(index + 1); } } boolean noMappingIsRequired = req.getSchema().getFieldOrNull(field) != null || isRefinementRequest; if (noMappingIsRequired) { if (commaSeparated.length() > 0) { commaSeparated.append(","); mapping.append(","); unmapped.append(","); } commaSeparated.append(prefix).append(field); mapping.append(field); unmapped.append(field); facetFieldList.add(field); } else { String mappedField = AlfrescoSolrDataModel.getInstance().mapProperty(field, FieldUse.FACET, req); if (commaSeparated.length() > 0) { commaSeparated.append(","); mapping.append(","); unmapped.append(","); } commaSeparated.append(prefix).append(mappedField); mapping.append(mappedField); unmapped.append(field); facetFieldList.add(mappedField); } } if (!facetFields.equals(commaSeparated.toString())) { fieldMappings.put(unmapped.toString(), mapping.toString()); } if (commaSeparated.length() > 0) { newFacetFields.add(commaSeparated.toString()); } } fixed.set(paramName, newFacetFields.toArray(new String[newFacetFields.size()])); } return facetFieldList; } /* (non-Javadoc) * @see org.apache.solr.handler.component.SearchComponent#process(org.apache.solr.handler.component.ResponseBuilder) */ @Override public void process(ResponseBuilder rb) throws IOException { } /* (non-Javadoc) * @see org.apache.solr.handler.component.SearchComponent#getDescription() */ @Override public String getDescription() { return "RewriteFacetParameters"; } /* (non-Javadoc) * @see org.apache.solr.handler.component.SearchComponent#getSource() */ @Override public String getSource() { return ""; } }