Java tutorial
/** * ========================================================================================== * = JAHIA'S DUAL LICENSING - IMPORTANT INFORMATION = * ========================================================================================== * * http://www.jahia.com * * Copyright (C) 2002-2017 Jahia Solutions Group SA. All rights reserved. * * THIS FILE IS AVAILABLE UNDER TWO DIFFERENT LICENSES: * 1/GPL OR 2/JSEL * * 1/ GPL * ================================================================================== * * IF YOU DECIDE TO CHOOSE THE GPL LICENSE, YOU MUST COMPLY WITH THE FOLLOWING TERMS: * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * * * 2/ JSEL - Commercial and Supported Versions of the program * =================================================================================== * * IF YOU DECIDE TO CHOOSE THE JSEL LICENSE, YOU MUST COMPLY WITH THE FOLLOWING TERMS: * * Alternatively, commercial and supported versions of the program - also known as * Enterprise Distributions - must be used in accordance with the terms and conditions * contained in a separate written agreement between you and Jahia Solutions Group SA. * * If you are unsure which license is appropriate for your use, * please contact the sales department at sales@jahia.com. */ package org.apache.jackrabbit.core.query.lucene; import org.apache.commons.lang.StringUtils; import org.apache.jackrabbit.core.SessionImpl; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.commons.name.NameFactoryImpl; import org.apache.lucene.index.IndexReader; import org.apache.lucene.queryParser.QueryParser; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.util.OpenBitSet; import org.apache.solr.client.solrj.response.FacetField; import org.apache.solr.client.solrj.response.RangeFacet; import org.apache.solr.client.solrj.util.ClientUtils; import org.apache.solr.common.params.FacetParams; import org.apache.solr.common.params.SolrParams; import org.apache.solr.common.util.NamedList; import org.apache.solr.schema.FieldType; import org.jahia.services.content.nodetypes.ExtendedPropertyDefinition; import org.jahia.services.content.nodetypes.NodeTypeRegistry; import org.jahia.services.search.facets.JahiaQueryParser; import org.jahia.services.search.facets.SimpleJahiaJcrFacets; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.jcr.NamespaceException; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.nodetype.NoSuchNodeTypeException; import javax.jcr.query.qom.PropertyValue; import javax.jcr.query.qom.Selector; import java.io.IOException; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Handle facet queries */ public class FacetHandler { public static final int FACET_COLUMNS = 0x1; public static final int ONLY_FACET_COLUMNS = 0x2; /** * The logger instance for this class */ private static final Logger log = LoggerFactory.getLogger(FacetHandler.class); private static final String RANGEFROM_INCLUSIVE_PREFIX = ":["; private static final String RANGEFROM_EXCLUSIVE_PREFIX = ":{"; /** * The name of the facet function without prefix but with left parenthesis. */ private static final String FACET_FUNC_LPAR = "facet("; /** * The start Name for the rep:facet function: rep:facet( */ private static final Name REP_FACET_LPAR = NameFactoryImpl.getInstance().create(Name.NS_REP_URI, FACET_FUNC_LPAR); private static final String FIELD_SPECIFIC_PREFIX = "f."; private static final Pattern valueWithQuery = Pattern.compile("(.*)##q\\->##(.*)"); // Facet stuff private Map<String, Long> _facetQuery = null; private List<FacetField> _facetFields = null; private List<FacetField> _limitingFacets = null; private List<FacetField> _facetDates = null; private List<RangeFacet> _facetRanges = null; /** * Component context of the current session */ protected final SessionImpl session; protected Selector selector; /** * The columns to select. */ protected final Map<String, PropertyValue> columns; private long totalSize = 1; OpenBitSet docIdSet; SearchIndex index; NamespaceMappings nsMappings; public FacetHandler(Map<String, PropertyValue> columns, Selector selector, OpenBitSet docIdSet, SearchIndex index, SessionImpl session, NamespaceMappings nsMappings) { this.columns = columns; this.selector = selector; this.session = session; this.docIdSet = docIdSet; this.index = index; this.nsMappings = nsMappings; totalSize = docIdSet.cardinality(); } public static int hasFacetFunctions(Map<String, PropertyValue> columns, SessionImpl session) { int hasFacetRequest = !columns.isEmpty() ? ONLY_FACET_COLUMNS : 0; for (String column : columns.keySet()) { if (isFacetFunction(column, session)) { hasFacetRequest |= FACET_COLUMNS; } else { hasFacetRequest &= ~ONLY_FACET_COLUMNS; } } return hasFacetRequest; } /** * @param name a String. * @return <code>true</code> if <code>name</code> is the rep:facet function, <code>false</code> otherwise. */ private static boolean isFacetFunction(String name, SessionImpl session) { try { return name.trim().startsWith(session.getJCRName(REP_FACET_LPAR)); } catch (NamespaceException e) { // will never happen return false; } } /** * Process facets information for the specified IndexReader. * * @param reader */ public void handleFacets(IndexReader reader) { IndexSearcher searcher = new IndexSearcher(reader); try { String facetFunctionPrefix = session.getJCRName(REP_FACET_LPAR); NamedList<Object> parameters = new NamedList<Object>(); int counter = 0; for (Map.Entry<String, PropertyValue> column : columns.entrySet()) { if (isFacetFunction(column.getKey(), session)) { extractFacetParameters(facetFunctionPrefix, parameters, counter, column); counter++; } } SolrParams solrParams = SolrParams.toSolrParams(parameters); SimpleJahiaJcrFacets facets = new SimpleJahiaJcrFacets(searcher, docIdSet, solrParams, index, session, nsMappings); extractFacetInfo(facets.getFacetCounts(), solrParams); } catch (Exception ex) { log.warn("Problem creating facets: ", ex); } finally { try { searcher.close(); } catch (IOException e) { log.warn("Unable to close searcher: " + e); } } } private void extractFacetParameters(String facetFunctionPrefix, NamedList<Object> parameters, int counter, Map.Entry<String, PropertyValue> column) throws RepositoryException { // first extract options from rep:facet() from column key final String key = column.getKey(); final String facetOptions = key.substring(key.indexOf(facetFunctionPrefix) + facetFunctionPrefix.length(), key.lastIndexOf(")")); // remember nodetype and query values if encountered so that we can process them once the whole facet is parsed String nodeType = null; List<String> unparsedQueries = null; // loop invariants final String columnPropertyName = column.getValue().getPropertyName(); final String propertyName = columnPropertyName + SimpleJahiaJcrFacets.PROPNAME_INDEX_SEPARATOR + counter; // we can assert the facet type by checking whether the the options String contains date or range, otherwise, the type is field final boolean isQuery = facetOptions.contains(FacetParams.FACET_QUERY); String facetType = FacetParams.FACET_FIELD; // default facet type if (isQuery) { facetType = FacetParams.FACET_QUERY; } else if (facetOptions.contains("date")) { facetType = FacetParams.FACET_DATE; } else if (facetOptions.contains("range")) { facetType = FacetParams.FACET_RANGE; } parameters.add(facetType, propertyName); // populate parameters // each parameter name/value pair is separated from the next one by & so split on this final String[] paramPairs = StringUtils.split(facetOptions, "&"); for (String paramPair : paramPairs) { // for each pair, extract the name and value separated by = int separator = paramPair.indexOf('='); if (separator >= 0) { // todo: what should we do if a pair doesn't have an equal sign in it? final String paramName = paramPair.substring(0, separator); final String paramValue = paramPair.substring(separator + 1); // some parameters need to be specially processed and not be added as others so process them and exit current iteration when encountered if (paramName.equals("nodetype")) { nodeType = paramValue; // remember node type value for later processing continue; } else if (paramName.contains("query")) { if (unparsedQueries == null) { unparsedQueries = new LinkedList<String>(); } unparsedQueries.add(paramValue); // remember query value for later processing continue; } // create full parameter name and add its value to the parameters String facetOption = getFacetOption(paramName); parameters.add(getFullParameterName(propertyName, facetOption), paramValue); } } // node type parameter if (StringUtils.isEmpty(nodeType)) { // if we didn't have a node type specified in the given options, extract it from the selector name and create the associated parameter nodeType = getNodeTypeFromSelector(column.getValue().getSelectorName(), columnPropertyName); } // only add node type parameter if we're not dealing with a query if (!isQuery) { parameters.add(getFullParameterName(propertyName, getFacetOption("nodetype")), nodeType); } // deal with embedded query if needed, at this point, nodeType will have been either extracted or asserted from selector name if (unparsedQueries != null) { ExtendedPropertyDefinition epd = NodeTypeRegistry.getInstance().getNodeType(nodeType) .getPropertyDefinition(columnPropertyName); for (String unparsedQuery : unparsedQueries) { if (unparsedQuery.split("(?<!\\\\):").length == 1 && !columnPropertyName.equals("rep:facet()")) { if (epd != null) { String fieldNameInIndex = getFieldNameInIndex(propertyName, epd, ""); unparsedQuery = QueryParser.escape(fieldNameInIndex) + ":" + unparsedQuery; } } parameters.add(getFullParameterName(propertyName, "query"), unparsedQuery); } } } private String getFullParameterName(String propertyName, String facetOption) { return FIELD_SPECIFIC_PREFIX + propertyName + "." + facetOption; } private String getFacetOption(String paramName) { final String prefix = FacetParams.FACET + "."; return paramName.startsWith(prefix) ? paramName : prefix + paramName; } public String getFieldNameInIndex(String field, ExtendedPropertyDefinition epd, String langCode) { String fieldName = field; try { fieldName = session.getJCRName(NameFactoryImpl.getInstance() .create(session.getNamespaceURI(epd.getPrefix()), epd.getLocalName())); int idx = fieldName.indexOf(':'); fieldName = fieldName.substring(0, idx + 1) + (epd != null && epd.isFacetable() ? JahiaNodeIndexer.FACET_PREFIX : FieldNames.FULLTEXT_PREFIX) + fieldName.substring(idx + 1); } catch (RepositoryException e) { // will never happen } return fieldName; } private String getNodeTypeFromSelector(String selectorName, String propertyName) throws RepositoryException { selectorName = StringUtils.removeEnd(selectorName, "translationAdded"); Selector foundSelector = selector; // for (SelectorImpl selector : ((SourceImpl) qomTree.getSource()).getSelectors()) { // if (StringUtils.isEmpty(selectorName) || selectorName.equals(selector.getSelectorName())) { // foundSelector = selector; // break; // } // } return foundSelector.getNodeTypeName(); } private void extractFacetInfo(NamedList<Object> info, SolrParams solrParams) { // Parse the queries _facetQuery = new LinkedHashMap<String, Long>(); NamedList<Long> fq = (NamedList<Long>) info.get("facet_queries"); if (fq != null) { for (Map.Entry<String, Long> entry : fq) { _facetQuery.put(entry.getKey(), entry.getValue()); } } // Parse the facet info into fields // TODO?? The list could be <int> or <long>? If always <long> then we can switch to <Long> NamedList<NamedList<Number>> ff = (NamedList<NamedList<Number>>) info.get("facet_fields"); Map<String, FieldType> fieldTypeMap = new HashMap<>(); if (ff != null) { _facetFields = new ArrayList<FacetField>(ff.size()); _limitingFacets = new ArrayList<FacetField>(ff.size()); long minsize = totalSize; for (Map.Entry<String, NamedList<Number>> facet : ff) { String key = StringUtils.substringBeforeLast(facet.getKey(), SimpleJahiaJcrFacets.PROPNAME_INDEX_SEPARATOR); String fieldInIndex = StringUtils.substringAfterLast(facet.getKey(), SimpleJahiaJcrFacets.PROPNAME_INDEX_SEPARATOR); FacetField f = new FacetField(key); if (!fieldTypeMap.containsKey(key)) { try { //Find a key like f.field_name#unknownumber.facet.nodetype Pattern facetNodetype = Pattern.compile("f\\." + key + "#[0-9]+\\.facet\\.nodetype"); String nodetypeName = null; Iterator<String> parameterNamesIterator = solrParams.getParameterNamesIterator(); while (parameterNamesIterator.hasNext()) { String next = parameterNamesIterator.next(); if (facetNodetype.matcher(next).matches()) { nodetypeName = solrParams.get(next); break; } } ExtendedPropertyDefinition epd = NodeTypeRegistry.getInstance().getNodeType(nodetypeName) .getPropertyDefinition(key); fieldTypeMap.put(key, getType(epd)); } catch (NoSuchNodeTypeException e) { log.error(e.getMessage(), e); } } for (Map.Entry<String, Number> entry : facet.getValue()) { String facetValue = entry.getKey(); String query = fieldTypeMap.get(key).toInternal(entry.getKey()); Matcher matcher = valueWithQuery.matcher(facetValue); if (matcher.matches()) { query = matcher.group(2); facetValue = matcher.replaceFirst("$1"); } f.add(facetValue, entry.getValue().longValue()); f.getValues().get(f.getValueCount() - 1).setFilterQuery( ClientUtils.escapeQueryChars(fieldInIndex) + ":" + ClientUtils.escapeQueryChars(query)); } _facetFields.add(f); FacetField nl = f.getLimitingFields(minsize); if (nl.getValueCount() > 0) { _limitingFacets.add(nl); } } } // Parse date facets NamedList<NamedList<Object>> df = (NamedList<NamedList<Object>>) info.get("facet_dates"); if (df != null) { // System.out.println(df); _facetDates = new ArrayList<FacetField>(df.size()); for (Map.Entry<String, NamedList<Object>> facet : df) { // System.out.println("Key: " + facet.getKey() + " Value: " + facet.getValue()); NamedList<Object> values = facet.getValue(); String gap = (String) values.get("gap"); Date end = (Date) values.get("end"); FacetField f = new FacetField(StringUtils.substringBeforeLast(facet.getKey(), SimpleJahiaJcrFacets.PROPNAME_INDEX_SEPARATOR), gap, end); for (Map.Entry<String, Object> entry : values) { try { String key = StringUtils.substringBeforeLast(entry.getKey(), SimpleJahiaJcrFacets.PROPNAME_INDEX_SEPARATOR); String query = StringUtils.substringAfterLast(entry.getKey(), SimpleJahiaJcrFacets.PROPNAME_INDEX_SEPARATOR); f.add(key, Long.parseLong(entry.getValue().toString())); if (!StringUtils.isEmpty(query)) { String rangePrefix = null; if (query.contains(RANGEFROM_EXCLUSIVE_PREFIX)) { rangePrefix = RANGEFROM_EXCLUSIVE_PREFIX; } else if (query.contains(RANGEFROM_INCLUSIVE_PREFIX)) { rangePrefix = RANGEFROM_INCLUSIVE_PREFIX; } if (!StringUtils.isEmpty(rangePrefix)) { f.getValues().get(f.getValueCount() - 1) .setFilterQuery(ClientUtils .escapeQueryChars(StringUtils.substringBefore(query, rangePrefix)) + rangePrefix + StringUtils.substringAfter(query, rangePrefix)); } } } catch (NumberFormatException e) { // Ignore for non-number responses which are already handled above } } _facetDates.add(f); } } // Parse range facets NamedList<NamedList<Object>> rf = (NamedList<NamedList<Object>>) info.get("facet_ranges"); if (rf != null) { // System.out.println(df); _facetRanges = new ArrayList<RangeFacet>(rf.size()); for (Map.Entry<String, NamedList<Object>> facet : rf) { NamedList<Object> values = facet.getValue(); Object rawGap = values.get("gap"); RangeFacet rangeFacet; if (rawGap instanceof Number) { Number gap = (Number) rawGap; Number start = (Number) values.get("start"); Number end = (Number) values.get("end"); Number before = (Number) values.get("before"); Number after = (Number) values.get("after"); rangeFacet = new RangeFacet.Numeric(StringUtils.substringBeforeLast(facet.getKey(), SimpleJahiaJcrFacets.PROPNAME_INDEX_SEPARATOR), start, end, gap, before, after); } else { String gap = (String) rawGap; Date start = (Date) values.get("start"); Date end = (Date) values.get("end"); Number before = (Number) values.get("before"); Number after = (Number) values.get("after"); rangeFacet = new RangeFacet.Date(StringUtils.substringBeforeLast(facet.getKey(), SimpleJahiaJcrFacets.PROPNAME_INDEX_SEPARATOR), start, end, gap, before, after); } NamedList<Integer> counts = (NamedList<Integer>) values.get("counts"); for (Map.Entry<String, Integer> entry : counts) { try { String key = StringUtils.substringBeforeLast(entry.getKey(), SimpleJahiaJcrFacets.PROPNAME_INDEX_SEPARATOR); String query = StringUtils.substringAfterLast(entry.getKey(), SimpleJahiaJcrFacets.PROPNAME_INDEX_SEPARATOR); rangeFacet.addCount(key, entry.getValue()); if (!StringUtils.isEmpty(query)) { String rangePrefix = null; if (query.contains(RANGEFROM_EXCLUSIVE_PREFIX)) { rangePrefix = RANGEFROM_EXCLUSIVE_PREFIX; } else if (query.contains(RANGEFROM_INCLUSIVE_PREFIX)) { rangePrefix = RANGEFROM_INCLUSIVE_PREFIX; } if (!StringUtils.isEmpty(rangePrefix)) { ((RangeFacet.Count) rangeFacet.getCounts().get(rangeFacet.getCounts().size() - 1)) .setFilterQuery(ClientUtils .escapeQueryChars(StringUtils.substringBefore(query, rangePrefix)) + rangePrefix + StringUtils.substringAfter(query, rangePrefix)); } } } catch (NumberFormatException e) { // Ignore for non-number responses which are already handled above } } _facetRanges.add(rangeFacet); } } } public FacetRow getFacetsRow() { FacetRow row = new FacetRow(); row.setFacetFields(_facetFields); row.setLimitingFacets(_limitingFacets); row.setFacetDates(_facetDates); row.setRangeFacets(_facetRanges); row.setFacetQuery(_facetQuery); return row; } private FieldType getType(ExtendedPropertyDefinition epd) { FieldType type = null; switch (epd.getRequiredType()) { case PropertyType.BINARY: type = JahiaQueryParser.BINARY_TYPE; break; case PropertyType.BOOLEAN: type = JahiaQueryParser.BOOLEAN_TYPE; break; case PropertyType.DATE: type = JahiaQueryParser.DATE_TYPE; break; case PropertyType.DOUBLE: type = JahiaQueryParser.SORTABLE_DOUBLE_TYPE; break; case PropertyType.LONG: type = JahiaQueryParser.SORTABLE_LONG_TYPE; break; case PropertyType.NAME: type = JahiaQueryParser.STRING_TYPE; break; case PropertyType.PATH: type = JahiaQueryParser.STRING_TYPE; break; case PropertyType.REFERENCE: type = JahiaQueryParser.STRING_TYPE; break; case PropertyType.STRING: type = JahiaQueryParser.STRING_TYPE; break; case PropertyType.URI: type = JahiaQueryParser.STRING_TYPE; break; case PropertyType.WEAKREFERENCE: type = JahiaQueryParser.STRING_TYPE; break; case PropertyType.DECIMAL: throw new UnsupportedOperationException(); } return type; } }