/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.exoplatform.services.jcr.impl.core.query.lucene; import org.apache.lucene.analysis.Analyzer; import org.exoplatform.commons.utils.ClassLoading; import org.exoplatform.services.jcr.core.NamespaceAccessor; import org.exoplatform.services.jcr.core.nodetype.NodeTypeDataManager; import org.exoplatform.services.jcr.dataflow.ItemDataConsumer; import org.exoplatform.services.jcr.datamodel.IllegalNameException; import org.exoplatform.services.jcr.datamodel.InternalQName; import org.exoplatform.services.jcr.datamodel.NodeData; import org.exoplatform.services.jcr.datamodel.PropertyData; import org.exoplatform.services.jcr.datamodel.QPath; import org.exoplatform.services.jcr.datamodel.QPathEntry; import org.exoplatform.services.jcr.datamodel.ValueData; import org.exoplatform.services.jcr.impl.Constants; import org.exoplatform.services.jcr.impl.core.LocationFactory; import org.exoplatform.services.jcr.impl.core.query.AdditionalNamespaceResolver; import org.exoplatform.services.jcr.impl.core.query.QueryHandlerContext; import org.exoplatform.services.jcr.impl.core.query.misc.Pattern; import org.exoplatform.services.jcr.impl.dataflow.ValueDataUtil; import org.exoplatform.services.jcr.impl.util.ISO9075; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Attr; import org.w3c.dom.CharacterData; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; /** * <code>IndexingConfigurationImpl</code> implements a concrete indexing * configuration. */ public class IndexingConfigurationImpl implements IndexingConfiguration { /** * The logger instance for this class */ private static final Logger LOG = LoggerFactory.getLogger("exo.jcr.component.core.IndexingConfigurationImpl"); /** * A namespace resolver for parsing QNames in the configuration. */ private LocationFactory resolver; /** * The item state manager to retrieve additional item states. */ private ItemDataConsumer ism; /** * The {@link IndexingRule}s inside this configuration. */ private Map<InternalQName, List<IndexingRule>> configElements = new HashMap<InternalQName, List<IndexingRule>>(); /** * The indexing aggregates inside this configuration. */ private AggregateRule[] aggregateRules; /** * The configured analyzers for indexing properties. */ private Map<String, Analyzer> analyzers = new HashMap<String, Analyzer>(); private Set<ExcludingRule> excludingRules = new HashSet<ExcludingRule>(); /** * {@inheritDoc} */ public void init(Element config, QueryHandlerContext context, NamespaceMappings nsMappings) throws Exception { ism = context.getItemStateManager(); NamespaceAccessor nsResolver = new AdditionalNamespaceResolver(getNamespaces(config)); resolver = new LocationFactory(nsResolver);// new // ParsingNameResolver(NameFactoryImpl.getInstance(), // nsResolver); NodeTypeDataManager ntReg = context.getNodeTypeDataManager(); //List<NodeTypeData> ntNames = ntReg.getAllNodeTypes(); List<AggregateRuleImpl> idxAggregates = new ArrayList<AggregateRuleImpl>(); NodeList indexingConfigs = config.getChildNodes(); for (int i = 0; i < indexingConfigs.getLength(); i++) { Node configNode = indexingConfigs.item(i); if (configNode.getNodeName().equals("index-rule")) { IndexingRule element = new IndexingRule(configNode); // register under node type and all its sub types LOG.debug("Found rule '{}' for NodeType '{}'", element, element.getNodeTypeName()); Set<InternalQName> subs = ntReg.getSubtypes(element.getNodeTypeName()); subs.add(element.getNodeTypeName()); for (InternalQName subTypeName : subs) { List<IndexingRule> perNtConfig = configElements.get(subTypeName); if (perNtConfig == null) { perNtConfig = new ArrayList<IndexingRule>(); configElements.put(subTypeName, perNtConfig); } LOG.debug("Registering it for name '{}'", subTypeName); perNtConfig.add(new IndexingRule(element, subTypeName)); } } else if (configNode.getNodeName().equals("aggregate")) { idxAggregates.add(new AggregateRuleImpl(configNode, resolver, ism)); } else if (configNode.getNodeName().equals("analyzers")) { NodeList childNodes = configNode.getChildNodes(); for (int j = 0; j < childNodes.getLength(); j++) { Node analyzerNode = childNodes.item(j); if (analyzerNode.getNodeName().equals("analyzer")) { String analyzerClassName = analyzerNode.getAttributes().getNamedItem("class").getNodeValue(); try { Class<?> clazz = ClassLoading.forName(analyzerClassName, this); if (clazz == JcrStandartAnalyzer.class) { LOG.warn("Not allowed to configure " + JcrStandartAnalyzer.class.getName() + " for a property. " + "Using default analyzer for that property."); } else if (Analyzer.class.isAssignableFrom(clazz)) { Analyzer analyzer = (Analyzer)clazz.newInstance(); NodeList propertyChildNodes = analyzerNode.getChildNodes(); for (int k = 0; k < propertyChildNodes.getLength(); k++) { Node propertyNode = propertyChildNodes.item(k); if (propertyNode.getNodeName().equals("property")) { // get property name InternalQName propName = resolver.parseJCRName(getTextContent(propertyNode)).getInternalName(); String fieldName = nsMappings.translateName(propName); // set analyzer for the fulltext // property fieldname int idx = fieldName.indexOf(':'); fieldName = fieldName.substring(0, idx + 1) + FieldNames.FULLTEXT_PREFIX + fieldName.substring(idx + 1); Object prevAnalyzer = analyzers.put(fieldName, analyzer); if (prevAnalyzer != null) { LOG.warn("Property " + propName.getName() + " has been configured for multiple analyzers. " + " Last configured analyzer is used"); } } } } else { LOG.warn("org.apache.lucene.analysis.Analyzer is not a superclass of " + analyzerClassName + ". Ignoring this configure analyzer"); } } catch (ClassNotFoundException e) { LOG.warn("Analyzer class not found: " + analyzerClassName, e); } } } } else if (configNode.getNodeName().equals("exclude")) { excludingRules.add(new ExcludingRuleImpl(configNode, ntReg, resolver)); } } aggregateRules = idxAggregates.toArray(new AggregateRule[idxAggregates.size()]); } /** * Returns the configured indexing aggregate rules or <code>null</code> if * none exist. * * @return the configured rules or <code>null</code> if none exist. */ public AggregateRule[] getAggregateRules() { return aggregateRules; } /** * Returns <code>true</code> if the property with the given name is fulltext * indexed according to this configuration. * * @param state * the node state. * @param propertyName * the name of a property. * @return <code>true</code> if the property is fulltext indexed; * <code>false</code> otherwise. */ public boolean isIndexed(NodeData state, InternalQName propertyName) { IndexingRule rule = getApplicableIndexingRule(state); if (rule != null) { return rule.isIndexed(propertyName); } // none of the configs matches -> index property return true; } /** * {@inheritDoc} */ public boolean isExcluded(NodeData state) { for (ExcludingRule rule : excludingRules) { if (rule.suiteFor(state)) { return true; } } return false; } /** * Returns the boost value for the given property name. If there is no * configuration entry for the property name the {@link #DEFAULT_BOOST} is * returned. * * @param state * the node state. * @param propertyName * the name of a property. * @return the boost value for the property. */ public float getPropertyBoost(NodeData state, InternalQName propertyName) { IndexingRule rule = getApplicableIndexingRule(state); if (rule != null) { return rule.getBoost(propertyName); } return DEFAULT_BOOST; } /** * Returns the boost for the node scope fulltext index field. * * @param state * the node state. * @return the boost for the node scope fulltext index field. */ public float getNodeBoost(NodeData state) { IndexingRule rule = getApplicableIndexingRule(state); if (rule != null) { return rule.getNodeBoost(); } return DEFAULT_BOOST; } /** * Returns <code>true</code> if the property with the given name should be * included in the node scope fulltext index. If there is not configuration * entry for that propery <code>false</code> is returned. * * @param state * the node state. * @param propertyName * the name of a property. * @return <code>true</code> if the property should be included in the node * scope fulltext index. */ public boolean isIncludedInNodeScopeIndex(NodeData state, InternalQName propertyName) { IndexingRule rule = getApplicableIndexingRule(state); if (rule != null) { return rule.isIncludedInNodeScopeIndex(propertyName); } // none of the config elements matched -> default is to include return true; } /** * Returns <code>true</code> if the content of the property with the given * name should show up in an excerpt. If there is no configuration entry for * that property <code>true</code> is returned. * * @param state * the node state. * @param propertyName * the name of a property. * @return <code>true</code> if the content of the property should be * included in an excerpt; <code>false</code> otherwise. */ public boolean useInExcerpt(NodeData state, InternalQName propertyName) { IndexingRule rule = getApplicableIndexingRule(state); if (rule != null) { return rule.useInExcerpt(propertyName); } // none of the config elements matched -> default is to include return true; } /** * Returns the analyzer configured for the property with this fieldName (the * string representation ,JCR-style name, of the given * <code>InternalQName</code> prefixed with * <code>FieldNames.FULLTEXT_PREFIX</code>)), and <code>null</code> if none * is configured, or the configured analyzer cannot be found. If * <code>null</code> is returned, the default Analyzer is used. * * @param fieldName * the string representation ,JCR-style name, of the given * <code>InternalQName</code> prefixed with * <code>FieldNames.FULLTEXT_PREFIX</code>)) * @return the <code>analyzer</code> to use for indexing this property */ public Analyzer getPropertyAnalyzer(String fieldName) { if (analyzers.containsKey(fieldName)) { return analyzers.get(fieldName); } return null; } // ---------------------------------< internal // >----------------------------- /** * Returns the first indexing rule that applies to the given node * <code>state</code>. * * @param state * a node state. * @return the indexing rule or <code>null</code> if none applies. */ private IndexingRule getApplicableIndexingRule(NodeData state) { List<IndexingRule> rules = null; List<IndexingRule> r = configElements.get(state.getPrimaryTypeName()); if (r != null) { rules = new ArrayList<IndexingRule>(); rules.addAll(r); } InternalQName[] mixTypes = state.getMixinTypeNames(); for (InternalQName mixType : mixTypes) { r = configElements.get(mixType); if (r != null) { if (rules == null) { rules = new ArrayList<IndexingRule>(); } rules.addAll(r); } } if (rules != null) { for (IndexingRule ir : rules) { if (ir.appliesTo(state)) { return ir; } } } // no applicable rule return null; } /** * Returns the namespaces declared on the <code>node</code>. * * @param node * a DOM node. * @return the namespaces */ private Properties getNamespaces(Node node) { Properties namespaces = new Properties(); NamedNodeMap attributes = node.getAttributes(); for (int i = 0; i < attributes.getLength(); i++) { Attr attribute = (Attr)attributes.item(i); if (attribute.getName().startsWith("xmlns:")) { namespaces.setProperty(attribute.getName().substring(6), attribute.getValue()); } } return namespaces; } /** * Creates property configurations defined in the <code>config</code>. * * @param config * the fulltext indexing configuration. * @param propConfigs * will be filled with exact <code>InternalQName</code> to * <code>PropertyConfig</code> mappings. * @param namePatterns * will be filled with <code>NamePattern</code>s. * @throws IllegalNameException * if the node type name contains illegal characters. * @throws RepositoryException */ private void createPropertyConfigs(Node config, Map<InternalQName, PropertyConfig> propConfigs, List<NamePattern> namePatterns) throws IllegalNameException, RepositoryException { NodeList childNodes = config.getChildNodes(); for (int i = 0; i < childNodes.getLength(); i++) { Node n = childNodes.item(i); if (n.getNodeName().equals("property")) { NamedNodeMap attributes = n.getAttributes(); // get boost value float boost = 1.0f; Node boostAttr = attributes.getNamedItem("boost"); if (boostAttr != null) { try { boost = Float.parseFloat(boostAttr.getNodeValue()); } catch (NumberFormatException e) { if (LOG.isTraceEnabled()) { LOG.trace("An exception occurred: " + e.getMessage()); } } } // get nodeScopeIndex flag boolean nodeScopeIndex = true; Node nsIndex = attributes.getNamedItem("nodeScopeIndex"); if (nsIndex != null) { nodeScopeIndex = Boolean.valueOf(nsIndex.getNodeValue()).booleanValue(); } // get isRegexp flag boolean isRegexp = false; Node regexp = attributes.getNamedItem("isRegexp"); if (regexp != null) { isRegexp = Boolean.valueOf(regexp.getNodeValue()).booleanValue(); } // get useInExcerpt flag boolean useInExcerpt = true; Node excerpt = attributes.getNamedItem("useInExcerpt"); if (excerpt != null) { useInExcerpt = Boolean.valueOf(excerpt.getNodeValue()).booleanValue(); } PropertyConfig pc = new PropertyConfig(boost, nodeScopeIndex, useInExcerpt); if (isRegexp) { namePatterns.add(new NamePattern(getTextContent(n), pc, resolver)); } else { InternalQName propName = resolver.parseJCRName(getTextContent(n)).getInternalName(); propConfigs.put(propName, pc); } } } } /** * Gets the condition expression from the configuration. * * @param config * the config node. * @return the condition expression or <code>null</code> if there is no * condition set on the <code>config</code>. * @throws MalformedPathException * if the condition string is malformed. * @throws IllegalNameException * if a name contains illegal characters. * @throws RepositoryException */ private PathExpression getCondition(Node config) throws IllegalNameException, RepositoryException { Node conditionAttr = config.getAttributes().getNamedItem("condition"); if (conditionAttr == null) { return null; } String conditionString = conditionAttr.getNodeValue(); int idx; int axis; InternalQName elementTest = null; InternalQName nameTest = null; InternalQName propertyName; String propertyValue; // parse axis if (conditionString.startsWith("ancestor::")) { axis = PathExpression.ANCESTOR; idx = "ancestor::".length(); } else if (conditionString.startsWith("parent::")) { axis = PathExpression.PARENT; idx = "parent::".length(); } else if (conditionString.startsWith("@")) { axis = PathExpression.SELF; idx = "@".length(); } else { axis = PathExpression.CHILD; idx = 0; } try { if (conditionString.startsWith("element(", idx)) { int colon = conditionString.indexOf(',', idx + "element(".length()); String name = conditionString.substring(idx + "element(".length(), colon).trim(); if (!name.equals("*")) { nameTest = resolver.parseJCRName(ISO9075.decode(name)).getInternalName(); } idx = conditionString.indexOf(")/@", colon); String type = conditionString.substring(colon + 1, idx).trim(); elementTest = resolver.parseJCRName(ISO9075.decode(type)).getInternalName(); idx += ")/@".length(); } else { if (axis == PathExpression.ANCESTOR || axis == PathExpression.CHILD || axis == PathExpression.PARENT) { // simple name test String name = conditionString.substring(idx, conditionString.indexOf('/', idx)); if (!name.equals("*")) { nameTest = resolver.parseJCRName(ISO9075.decode(name)).getInternalName(); } idx += name.length() + "/@".length(); } } // parse property name int eq = conditionString.indexOf('=', idx); String name = conditionString.substring(idx, eq).trim(); propertyName = resolver.parseJCRName(ISO9075.decode(name)).getInternalName(); // parse string value int quote = conditionString.indexOf('\'', eq) + 1; propertyValue = conditionString.substring(quote, conditionString.indexOf('\'', quote)); } catch (IndexOutOfBoundsException e) { throw new RepositoryException(conditionString); } return new PathExpression(axis, elementTest, nameTest, propertyName, propertyValue); } /** * @param node * a node. * @return the text content of the <code>node</code>. */ private static String getTextContent(Node node) { StringBuilder content = new StringBuilder(); NodeList nodes = node.getChildNodes(); for (int i = 0; i < nodes.getLength(); i++) { Node n = nodes.item(i); if (n.getNodeType() == Node.TEXT_NODE) { content.append(((CharacterData)n).getData()); } } return content.toString(); } /** * A property name pattern. */ private static final class NamePattern { /** * The pattern to match. */ private final Pattern pattern; /** * The associated configuration. */ private final PropertyConfig config; /** * Creates a new name pattern. * * @param pattern * the pattern as read from the configuration file. * @param config * the associated configuration. * @param resolver * a namespace resolver for parsing name from the * configuration. * @throws IllegalNameException * if the prefix of the name pattern is illegal. * @throws RepositoryException */ private NamePattern(String pattern, PropertyConfig config, LocationFactory resolver) throws IllegalNameException, RepositoryException { String uri = Constants.NS_DEFAULT_URI; String localPattern = pattern; int idx = pattern.indexOf(':'); if (idx != -1) { String prefix = pattern.substring(0, idx); if (prefix.equals(".*")) { // match all namespaces uri = prefix; } else { // use a dummy local name to get namespace uri uri = resolver.parseJCRName(pattern.substring(0, idx) + ":a").getNamespace(); } localPattern = pattern.substring(idx + 1); } else if (pattern.equals(".*")) { uri = ".*"; } this.pattern = Pattern.name(uri, localPattern); this.config = config; } /** * @param path * the path to match. * @return <code>true</code> if <code>path</code> matches this name * pattern; <code>false</code> otherwise. */ boolean matches(QPath path) { return pattern.match(path).isFullMatch(); } /** * @return the property configuration for this name pattern. */ PropertyConfig getConfig() { return config; } } private class IndexingRule { /** * The node type of this fulltext indexing rule. */ private final InternalQName nodeTypeName; /** * Map of {@link PropertyConfig}. Key=InternalQName of property. */ private final Map<InternalQName, PropertyConfig> propConfigs; /** * List of {@link NamePattern}s. */ private final List<NamePattern> namePatterns; /** * An expression based on a relative path. */ private final PathExpression condition; /** * The boost value for this config element. */ private final float boost; /** * Creates a new indexing rule base on an existing one, but for a * different node type name. * * @param original * the existing rule. * @param nodeTypeName * the node type name for the rule. */ IndexingRule(IndexingRule original, InternalQName nodeTypeName) { this.nodeTypeName = nodeTypeName; this.propConfigs = original.propConfigs; this.namePatterns = original.namePatterns; this.condition = original.condition; this.boost = original.boost; } /** * * @param config * the configuration for this rule. * @throws MalformedPathException * if the condition expression is malformed. * @throws IllegalNameException * if a name contains illegal characters. * @throws RepositoryException */ IndexingRule(Node config) throws IllegalNameException, RepositoryException { this.nodeTypeName = getNodeTypeName(config); this.condition = getCondition(config); this.boost = getNodeBoost(config); this.propConfigs = new HashMap<InternalQName, PropertyConfig>(); this.namePatterns = new ArrayList<NamePattern>(); createPropertyConfigs(config, propConfigs, namePatterns); } /** * Returns the name of the node type where this rule applies to. * * @return name of the node type. */ public InternalQName getNodeTypeName() { return nodeTypeName; } /** * @return the value for the node boost. */ public float getNodeBoost() { return boost; } /** * Returns <code>true</code> if the property with the given name is * indexed according to this rule. * * @param propertyName * the name of a property. * @return <code>true</code> if the property is indexed; * <code>false</code> otherwise. */ public boolean isIndexed(InternalQName propertyName) { return getConfig(propertyName) != null; } /** * Returns the boost value for the given property name. If there is no * configuration entry for the property name the default boost value is * returned. * * @param propertyName * the name of a property. * @return the boost value for the property. */ public float getBoost(InternalQName propertyName) { PropertyConfig config = getConfig(propertyName); if (config != null) { return config.boost; } else { return DEFAULT_BOOST; } } /** * Returns <code>true</code> if the property with the given name should * be included in the node scope fulltext index. If there is no * configuration entry for that propery <code>false</code> is returned. * * @param propertyName * the name of a property. * @return <code>true</code> if the property should be included in the * node scope fulltext index. */ public boolean isIncludedInNodeScopeIndex(InternalQName propertyName) { PropertyConfig config = getConfig(propertyName); if (config != null) { return config.nodeScopeIndex; } else { return false; } } /** * Returns <code>true</code> if the content of the property with the * given name should show up in an excerpt. If there is no configuration * entry for that property <code>true</code> is returned. * * @param propertyName * the name of a property. * @return <code>true</code> if the content of the property should be * included in an excerpt; <code>false</code> otherwise. */ public boolean useInExcerpt(InternalQName propertyName) { PropertyConfig config = getConfig(propertyName); if (config != null) { return config.useInExcerpt; } else { return true; } } /** * Returns <code>true</code> if this rule applies to the given node * <code>state</code>. * * @param state * the state to check. * @return <code>true</code> the rule applies to the given node; * <code>false</code> otherwise. */ public boolean appliesTo(NodeData state) { if (!nodeTypeName.equals(state.getPrimaryTypeName())) { return false; } if (condition == null) { return true; } else { return condition.evaluate(state); } } // -------------------------< internal // >--------------------------------- /** * @param propertyName * name of a property. * @return the property configuration or <code>null</code> if this * indexing rule does not contain a configuration for the given * property. */ private PropertyConfig getConfig(InternalQName propertyName) { PropertyConfig config = propConfigs.get(propertyName); if (config != null) { return config; } else if (namePatterns.size() > 0) { QPath path = new QPath(new QPathEntry[]{new QPathEntry(propertyName, 1)}); // check patterns for (Iterator<NamePattern> it = namePatterns.iterator(); it.hasNext();) { NamePattern np = it.next(); if (np.matches(path)) { return np.getConfig(); } } } return null; } /** * Reads the node type of the root node of the indexing rule. * * @param config * the configuration. * @return the name of the node type. * @throws IllegalNameException * if the node type name contains illegal characters. * @throws RepositoryException */ private InternalQName getNodeTypeName(Node config) throws IllegalNameException, RepositoryException { String ntString = config.getAttributes().getNamedItem("nodeType").getNodeValue(); return resolver.parseJCRName(ntString).getInternalName(); } /** * Returns the node boost from the <code>config</code>. * * @param config * the configuration. * @return the configured node boost or the default boost if none is * configured. */ private float getNodeBoost(Node config) { Node boost = config.getAttributes().getNamedItem("boost"); if (boost != null) { try { return Float.parseFloat(boost.getNodeValue()); } catch (NumberFormatException e) { if (LOG.isTraceEnabled()) { LOG.trace("An exception occurred: " + e.getMessage()); } } } return DEFAULT_BOOST; } } /** * Simple class that holds boost and nodeScopeIndex flag. */ private class PropertyConfig { /** * The boost value for a property. */ final float boost; /** * Flag that indicates whether a property is included in the node scope * fulltext index of its parent. */ final boolean nodeScopeIndex; /** * Flag that indicates whether the content of a property should be used * to create an excerpt. */ final boolean useInExcerpt; PropertyConfig(float boost, boolean nodeScopeIndex, boolean useInExcerpt) { this.boost = boost; this.nodeScopeIndex = nodeScopeIndex; this.useInExcerpt = useInExcerpt; } } private class PathExpression { static final int SELF = 0; static final int CHILD = 1; static final int ANCESTOR = 2; static final int PARENT = 3; private final int axis; private final InternalQName elementTest; private final InternalQName nameTest; private final InternalQName propertyName; private final String propertyValue; PathExpression(int axis, InternalQName elementTest, InternalQName nameTest, InternalQName propertyName, String propertyValue) { this.axis = axis; this.elementTest = elementTest; this.nameTest = nameTest; this.propertyName = propertyName; this.propertyValue = propertyValue; } /** * Evaluates this expression and returns <code>true</code> if the * condition matches using <code>state</code> as the context node state. * * @param context * the context from where the expression should be evaluated. * @return expression result. */ boolean evaluate(final NodeData context) { // get iterator along specified axis Iterator<NodeData> nodeStates; if (axis == SELF) { nodeStates = Collections.singletonList(context).iterator(); } else if (axis == CHILD) { List<NodeData> childs; try { childs = ism.getChildNodesData(context); nodeStates = childs.iterator(); } catch (RepositoryException e) { nodeStates = Collections.<NodeData> emptyList().iterator(); } } else if (axis == ANCESTOR) { try { nodeStates = new Iterator<NodeData>() { private NodeData next = (NodeData)ism.getItemData(context.getParentIdentifier()); public void remove() { throw new UnsupportedOperationException(); } public boolean hasNext() { return next != null; } public NodeData next() { NodeData tmp = next; try { if (next.getParentIdentifier() != null) { next = (NodeData)ism.getItemData(next.getParentIdentifier()); } else { next = null; } } catch (RepositoryException e) { next = null; } return tmp; } }; } catch (RepositoryException e) { nodeStates = Collections.<NodeData>emptyList().iterator(); } } else if (axis == PARENT) { try { if (context.getParentIdentifier() != null) { NodeData state = (NodeData)ism.getItemData(context.getParentIdentifier()); nodeStates = Collections.singletonList(state).iterator(); } else { nodeStates = Collections.<NodeData>emptyList().iterator(); } } catch (RepositoryException e) { nodeStates = Collections.<NodeData>emptyList().iterator(); } } else { // unsupported axis nodeStates = Collections.<NodeData>emptyList().iterator(); } // check node type, name and property value for each while (nodeStates.hasNext()) { try { NodeData current = nodeStates.next(); if ((elementTest != null) && !current.getPrimaryTypeName().equals(elementTest)) { continue; } if ((nameTest != null) && !current.getQPath().getName().equals(nameTest)) { continue; } List<PropertyData> childProps = ism.getChildPropertiesData(current); PropertyData propState = null; for (PropertyData propertyData : childProps) { if (propertyData.getQPath().getName().equals(propertyName)) { propState = propertyData; break; } } if (propState == null) { continue; } List<ValueData> values = propState.getValues(); // if (values.get(i).toString().equals(propertyValue)) { // return true; // } if (propState.getType() == PropertyType.BINARY) { // skip binary values continue; } for (int i = 0; i < values.size(); i++) { String val = ValueDataUtil.getString(values.get(i)); if (val.equals(propertyValue)) { return true; } } } catch (RepositoryException e) { LOG.error(e.getLocalizedMessage()); } } return false; } } public Analyzer addPropertyAnalyzer(String propertyName, Analyzer analyzer) { return analyzers.put(propertyName, analyzer); } }