/* * ModeShape (http://www.modeshape.org) * * Licensed 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.modeshape.jcr.spi.index.provider; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.modeshape.common.annotation.Immutable; import org.modeshape.jcr.ExecutionContext; import org.modeshape.jcr.JcrLexicon; import org.modeshape.jcr.ModeShapeLexicon; import org.modeshape.jcr.cache.CachedNode.Properties; import org.modeshape.jcr.cache.NodeKey; import org.modeshape.jcr.cache.change.AbstractPropertyChange; import org.modeshape.jcr.cache.change.ChangeSetAdapter.NodeTypePredicate; import org.modeshape.jcr.cache.change.PropertyAdded; import org.modeshape.jcr.cache.change.PropertyChanged; import org.modeshape.jcr.cache.change.PropertyRemoved; import org.modeshape.jcr.value.BinaryValue; import org.modeshape.jcr.value.Name; import org.modeshape.jcr.value.Path; import org.modeshape.jcr.value.PathFactory; import org.modeshape.jcr.value.Property; import org.modeshape.jcr.value.ValueFactory; import org.modeshape.jcr.value.binary.BinaryStoreException; /** * Utility for creating generic {@link IndexChangeAdapter} instances, which support both single and multi-valued properties. * * @author Randall Hauch (rhauch@redhat.com) * @author Horia Chiorean (hchiorea@redhat.com) */ @Immutable public class IndexChangeAdapters { /** * Creates a composite change adapter which handles the case when an index has multiple columns. * * @param context the execution context; may not be null * @param matcher the node type matcher used to determine which nodes should be included in the index; may not be null * @param workspaceName the name of the workspace; may not be null * @param index the index that should be used; may not be null * @param adapters an {@link Iterable} of existing "discrete" adapters. * @return the new {@link IndexChangeAdapter}; never null */ public static IndexChangeAdapter forMultipleColumns( ExecutionContext context, NodeTypePredicate matcher, String workspaceName, ProvidedIndex<?> index, Iterable<IndexChangeAdapter> adapters ) { return new MultiColumnChangeAdapter(context ,workspaceName, matcher, index, adapters); } /** * Create an {@link IndexChangeAdapter} implementation that handles the "mode:nodeDepth" property. * * @param context the execution context; may not be null * @param matcher the node type matcher used to determine which nodes should be included in the index; may not be null * @param workspaceName the name of the workspace; may not be null * @param index the index that should be used; may not be null * @return the new {@link IndexChangeAdapter}; never null */ public static IndexChangeAdapter forNodeDepth( ExecutionContext context, NodeTypePredicate matcher, String workspaceName, ProvidedIndex<?> index ) { return new NodeDepthChangeAdapter(context, matcher, workspaceName, index); } /** * Create an {@link IndexChangeAdapter} implementation that handles the "jcr:name" property. * * @param context the execution context; may not be null * @param matcher the node type matcher used to determine which nodes should be included in the index; may not be null * @param workspaceName the name of the workspace; may not be null * @param index the local index that should be used; may not be null * @return the new {@link IndexChangeAdapter}; never null */ public static IndexChangeAdapter forNodeName( ExecutionContext context, NodeTypePredicate matcher, String workspaceName, ProvidedIndex<?> index ) { return new NodeNameChangeAdapter(context, matcher, workspaceName, index); } /** * Create an {@link IndexChangeAdapter} implementation that handles the "mode:localName" property. * * @param context the execution context; may not be null * @param matcher the node type matcher used to determine which nodes should be included in the index; may not be null * @param workspaceName the name of the workspace; may not be null * @param index the local index that should be used; may not be null * @return the new {@link IndexChangeAdapter}; never null */ public static IndexChangeAdapter forNodeLocalName( ExecutionContext context, NodeTypePredicate matcher, String workspaceName, ProvidedIndex<?> index ) { return new NodeLocalNameChangeAdapter(context, matcher, workspaceName, index); } /** * Create an {@link IndexChangeAdapter} implementation that handles the "jcr:path" property. * * @param context the execution context; may not be null * @param matcher the node type matcher used to determine which nodes should be included in the index; may not be null * @param workspaceName the name of the workspace; may not be null * @param index the local index that should be used; may not be null * @return the new {@link IndexChangeAdapter}; never null */ public static IndexChangeAdapter forNodePath( ExecutionContext context, NodeTypePredicate matcher, String workspaceName, ProvidedIndex<?> index ) { return new NodePathChangeAdapter(context, matcher, workspaceName, index); } /** * Create an {@link IndexChangeAdapter} implementation that handles the "jcr:primaryType" property. * * @param context the execution context; may not be null * @param matcher the node type matcher used to determine which nodes should be included in the index; may not be null * @param workspaceName the name of the workspace; may not be null * @param index the local index that should be used; may not be null * @return the new {@link IndexChangeAdapter}; never null */ public static IndexChangeAdapter forPrimaryType( ExecutionContext context, NodeTypePredicate matcher, String workspaceName, ProvidedIndex<?> index ) { return new PrimaryTypeChangeAdapter(context, matcher, workspaceName, index); } /** * Create an {@link IndexChangeAdapter} implementation that handles the "jcr:mixinTypes" property. * * @param context the execution context; may not be null * @param matcher the node type matcher used to determine which nodes should be included in the index; may not be null * @param workspaceName the name of the workspace; may not be null * @param index the local index that should be used; may not be null * @return the new {@link IndexChangeAdapter}; never null */ public static IndexChangeAdapter forMixinTypes( ExecutionContext context, NodeTypePredicate matcher, String workspaceName, ProvidedIndex<?> index ) { return new MixinTypesChangeAdapter(context, matcher, workspaceName, index); } /** * Create an {@link IndexChangeAdapter} implementation that handles a node property, either single or multi-valued. * * @param context the execution context; may not be null * @param matcher the node type matcher used to determine which nodes should be included in the index; may not be null * @param workspaceName the name of the workspace; may not be null * @param propertyName the name of the property; may not be null * @param factory the value factory for the property's value type; may not be null * @param index the local index that should be used; may not be null * @return the new {@link IndexChangeAdapter}; never null */ public static <T> IndexChangeAdapter forProperty( ExecutionContext context, NodeTypePredicate matcher, String workspaceName, Name propertyName, ValueFactory<T> factory, ProvidedIndex<?> index ) { return new PropertyChangeAdapter<>(context, matcher, workspaceName, propertyName, factory, index); } /** * Create an {@link IndexChangeAdapter} implementation that handles a unique-valued property, where every property value is * unique across all nodes. * * @param context the execution context; may not be null * @param matcher the node type matcher used to determine which nodes should be included in the index; may not be null * @param workspaceName the name of the workspace; may not be null * @param propertyName the name of the property; may not be null * @param factory the value factory for the property's value type; may not be null * @param index the local index that should be used; may not be null * @return the new {@link IndexChangeAdapter}; never null */ public static <T> IndexChangeAdapter forUniqueValuedProperty( ExecutionContext context, NodeTypePredicate matcher, String workspaceName, Name propertyName, ValueFactory<T> factory, ProvidedIndex<?> index ) { return new UniquePropertyChangeAdapter<>(context, matcher, workspaceName, propertyName, factory, index); } /** * Create an {@link IndexChangeAdapter} implementation that handles a enumerated properties, either single or multi-valued. * * @param context the execution context; may not be null * @param matcher the node type matcher used to determine which nodes should be included in the index; may not be null * @param workspaceName the name of the workspace; may not be null * @param propertyName the name of the property; may not be null * @param index the local index that should be used; may not be null * @return the new {@link IndexChangeAdapter}; never null */ public static IndexChangeAdapter forEnumeratedProperty( ExecutionContext context, NodeTypePredicate matcher, String workspaceName, Name propertyName, ProvidedIndex<?> index ) { return new EnumeratedPropertyChangeAdapter(context, matcher, workspaceName, propertyName, index); } /** * Create an {@link IndexChangeAdapter} implementation that handles node type information. * * @param propertyName a symbolic name of the property that will be sent to the {@link ProvidedIndex} when the adapter * notices that there are either primary type of mixin type changes. * @param context the execution context; may not be null * @param matcher the node type matcher used to determine which nodes should be included in the index; may not be null * @param workspaceName the name of the workspace; may not be null * @param index the local index that should be used; may not be null * @return the new {@link IndexChangeAdapter}; never null */ public static IndexChangeAdapter forNodeTypes( String propertyName, ExecutionContext context, NodeTypePredicate matcher, String workspaceName, ProvidedIndex<?> index ) { return new NodeTypesChangeAdapter(propertyName, context, matcher, workspaceName, index); } /** * Create an {@link IndexChangeAdapter} implementation that handles full text information. * * @param context the execution context; may not be null * @param matcher the node type matcher used to determine which nodes should be included in the index; may not be null * @param workspaceName the name of the workspace; may not be null * @param propertyName the name of the property; may not be null * @param factory the value factory for the property's value type; may not be null * @param index the local index that should be used; may not be null * @return the new {@link IndexChangeAdapter}; never null */ public static IndexChangeAdapter forTextProperty( ExecutionContext context, NodeTypePredicate matcher, String workspaceName, Name propertyName, ValueFactory<String> factory, ProvidedIndex<?> index ) { return new TextPropertyChangeAdapter(context, matcher, workspaceName, propertyName, factory,index); } private IndexChangeAdapters() { } protected static class MultiColumnChangeAdapter extends IndexChangeAdapter { private List<PathBasedChangeAdapter<?>> pathAdapters; private List<AbstractPropertyChangeAdapter<?>> propertyAdapters; protected MultiColumnChangeAdapter( ExecutionContext context, String workspaceName, NodeTypePredicate predicate, ProvidedIndex<?> index, Iterable<IndexChangeAdapter> adapters ) { super(context, workspaceName, predicate, index); pathAdapters = new ArrayList<>(); propertyAdapters = new ArrayList<>(); for (IndexChangeAdapter adapter : adapters) { if (adapter instanceof PathBasedChangeAdapter) { pathAdapters.add((PathBasedChangeAdapter<?>) adapter); } else if (adapter instanceof AbstractPropertyChangeAdapter) { propertyAdapters.add((AbstractPropertyChangeAdapter<?>) adapter); } } assert !pathAdapters.isEmpty() || !propertyAdapters.isEmpty(); } @Override protected void modifyProperties( NodeKey key, Name primaryType, Set<Name> mixinTypes, Map<Name, AbstractPropertyChange> propChanges ) { // only the property adapters should be interested in this if (propertyAdapters.isEmpty()) { return; } for (AbstractPropertyChangeAdapter<?> propertyChangeAdapter : propertyAdapters) { propertyChangeAdapter.modifyProperties(key, primaryType, mixinTypes, propChanges); } } @Override protected void addNode( String workspaceName, NodeKey key, Path path, Name primaryType, Set<Name> mixinTypes, Properties properties ) { // only the path based adapters should be interested in this since properties are handled via modify properties if (pathAdapters.isEmpty()) { return; } for (PathBasedChangeAdapter<?> pathAdapter : pathAdapters) { pathAdapter.addNode(workspaceName, key, path, primaryType, mixinTypes, properties); } } @Override protected void reindexNode( String workspaceName, NodeKey key, Path path, Name primaryType, Set<Name> mixinTypes, Properties properties, boolean queryable ) { //first remove all data for the given key... String nodeKey = nodeKey(key); index().remove(nodeKey); //then based on each of the adapters types, add information back to the index... if (!pathAdapters.isEmpty()) { for (PathBasedChangeAdapter<?> pathAdapter : pathAdapters) { index().add(nodeKey, pathAdapter.propertyName, pathAdapter.valueOf(path)); } } if (!propertyAdapters.isEmpty()) { for (AbstractPropertyChangeAdapter<?> propertyAdapter : propertyAdapters) { Property property = properties.getProperty(propertyAdapter.propertyName); if (property != null) { propertyAdapter.addValues(key, property); } } } } @Override protected void removeNode( String workspaceName, NodeKey key, NodeKey parentKey, Path path, Name primaryType, Set<Name> mixinTypes ) { // just remove everything for that node from the index... index().remove(nodeKey(key)); } @Override protected void moveNode( String workspaceName, NodeKey key, Name primaryType, Set<Name> mixinTypes, NodeKey oldParent, NodeKey newParent, Path newPath, Path oldPath ) { // only the path based adapters should be interested in this since properties are handled via modify properties if (pathAdapters.isEmpty()) { return; } for (PathBasedChangeAdapter<?> pathAdapter : pathAdapters) { pathAdapter.moveNode(workspaceName, key, primaryType, mixinTypes, oldParent, newParent, newPath, oldPath); } } @Override protected void renameNode( String workspaceName, NodeKey key, Path newPath, Path.Segment oldSegment, Name primaryType, Set<Name> mixinTypes ) { // only the path based adapters should be interested in this since properties are handled via modify properties if (pathAdapters.isEmpty()) { return; } for (PathBasedChangeAdapter<?> pathAdapter : pathAdapters) { pathAdapter.renameNode(workspaceName, key, newPath, oldSegment, primaryType, mixinTypes); } } @Override protected void reorderNode( String workspaceName, NodeKey key, Name primaryType, Set<Name> mixinTypes, NodeKey parent, Path newPath, Path oldPath, Path reorderedBeforePath, Map<NodeKey, Map<Path, Path>> snsPathChangesByNodeKey ) { // only the path based adapters should be interested in this since properties are handled via modify properties if (pathAdapters.isEmpty()) { return; } for (PathBasedChangeAdapter<?> pathAdapter : pathAdapters) { pathAdapter.reorderNode(workspaceName, key, primaryType, mixinTypes, parent, newPath, oldPath, reorderedBeforePath, snsPathChangesByNodeKey); } } } protected static abstract class PathBasedChangeAdapter<T> extends IndexChangeAdapter { protected final String propertyName; protected PathBasedChangeAdapter( ExecutionContext context, NodeTypePredicate matcher, String workspaceName, ProvidedIndex<?> index, Name propertyName) { super(context, workspaceName, matcher, index); assert propertyName != null; this.propertyName = propertyName.getString(context.getNamespaceRegistry()); } protected T valueOf(Path path) { return path.isRoot() ? convertRoot(path) : convert(path); } protected abstract T convertRoot( Path path ); protected abstract T convert( Path path ); @Override protected void addNode( String workspaceName, NodeKey key, Path path, Name primaryType, Set<Name> mixinTypes, Properties properties ) { index().add(nodeKey(key), propertyName, valueOf(path)); } @Override protected void reindexNode( String workspaceName, NodeKey key, Path path, Name primaryType, Set<Name> mixinTypes, Properties properties, boolean queryable ) { String nodeKey = nodeKey(key); index().remove(nodeKey, propertyName, valueOf(path)); if (queryable) { index().add(nodeKey(key), propertyName, valueOf(path)); } } @Override protected void moveNode( String workspaceName, NodeKey key, Name primaryType, Set<Name> mixinTypes, NodeKey oldParent, NodeKey newParent, Path newPath, Path oldPath ) { String nodeKey = nodeKey(key); index().remove(nodeKey, propertyName, valueOf(oldPath)); index().add(nodeKey, propertyName, valueOf(newPath)); } @Override protected void removeNode( String workspaceName, NodeKey key, NodeKey parentKey, Path path, Name primaryType, Set<Name> mixinTypes ) { index().remove(nodeKey(key)); } @Override protected void renameNode( String workspaceName, NodeKey key, Path newPath, Path.Segment oldSegment, Name primaryType, Set<Name> mixinTypes ) { PathFactory pathFactory = context.getValueFactories().getPathFactory(); Path oldPath = pathFactory.create(newPath.subpath(0, newPath.size()), oldSegment); String nodeKey = nodeKey(key); index().remove(nodeKey, propertyName, valueOf(oldPath)); index().add(nodeKey, propertyName, valueOf(newPath)); } @Override protected void reorderNode( String workspaceName, NodeKey key, Name primaryType, Set<Name> mixinTypes, NodeKey parent, Path newPath, Path oldPath, Path reorderedBeforePath, Map<NodeKey, Map<Path, Path>> snsPathChangesByNodeKey ) { if (newPath.getLastSegment().hasIndex() || oldPath.getLastSegment().hasIndex()) { // SNS reorderings can cause a path to change (see https://issues.jboss.org/browse/MODE-2510) String nodeKey = nodeKey(key); index().remove(nodeKey, propertyName, valueOf(oldPath)); index().add(nodeKey, propertyName, valueOf(newPath)); // now look if there are additional SNSs which changed their path and if so, update the indexes for each snsPathChangesByNodeKey.forEach(( snsKey, pathMap ) -> { String snsKeyString = nodeKey(snsKey); Map.Entry<Path, Path> pathChangeEntry = pathMap.entrySet().iterator().next(); index().remove(snsKeyString, propertyName, valueOf(pathChangeEntry.getKey())); index().add(snsKeyString, propertyName, valueOf(pathChangeEntry.getValue())); }); } //otherwise no SNS are involved so the nodekey and path information doesn't need to change } @Override public String toString() { return getClass().getSimpleName() + "(\"" + index.getName() + "\" : \"" + propertyName + "\")"; } } protected static final class NodeDepthChangeAdapter extends PathBasedChangeAdapter<Long> { public NodeDepthChangeAdapter( ExecutionContext context, NodeTypePredicate matcher, String workspaceName, ProvidedIndex<?> index ) { super(context, matcher, workspaceName, index, ModeShapeLexicon.DEPTH); } @Override protected Long convert( Path path ) { return (long)path.size(); } @Override protected Long convertRoot( Path path ) { return convert(path); } } protected static final class NodeNameChangeAdapter extends PathBasedChangeAdapter<Name> { public NodeNameChangeAdapter( ExecutionContext context, NodeTypePredicate matcher, String workspaceName, ProvidedIndex<?> index) { super(context, matcher, workspaceName, index, JcrLexicon.NAME); } @Override protected Name convert( Path path ) { return path.getLastSegment().getName(); } @Override protected Name convertRoot( Path path ) { return Path.ROOT_NAME; } @Override protected void reorderNode( String workspaceName, NodeKey key, Name primaryType, Set<Name> mixinTypes, NodeKey parent, Path newPath, Path oldPath, Path reorderedBeforePath, Map<NodeKey, Map<Path, Path>> snsPathChangesByNodeKey ) { // reordering should not really change the name... } } protected static final class NodeLocalNameChangeAdapter extends PathBasedChangeAdapter<String> { public NodeLocalNameChangeAdapter( ExecutionContext context, NodeTypePredicate matcher, String workspaceName, ProvidedIndex<?> index ) { super(context, matcher, workspaceName, index, ModeShapeLexicon.LOCALNAME); } @Override protected String convert( Path path ) { return path.getLastSegment().getName().getLocalName(); } @Override protected String convertRoot( Path path ) { return ""; } @Override protected void reorderNode( String workspaceName, NodeKey key, Name primaryType, Set<Name> mixinTypes, NodeKey parent, Path newPath, Path oldPath, Path reorderedBeforePath, Map<NodeKey, Map<Path, Path>> snsPathChangesByNodeKey ) { // reordering should not really change the name... } } protected static final class NodePathChangeAdapter extends PathBasedChangeAdapter<Path> { public NodePathChangeAdapter( ExecutionContext context, NodeTypePredicate matcher, String workspaceName, ProvidedIndex<?> index ) { super(context, matcher, workspaceName, index, JcrLexicon.PATH); } @Override protected Path convert( Path path ) { return path; } @Override protected Path convertRoot( Path path ) { return path; } } protected static abstract class AbstractPropertyChangeAdapter<T> extends IndexChangeAdapter { protected final Name propertyName; protected final ValueFactory<T> valueFactory; public AbstractPropertyChangeAdapter( ExecutionContext context, NodeTypePredicate matcher, String workspaceName, Name propertyName, ValueFactory<T> valueFactory, ProvidedIndex<?> index) { super(context, workspaceName, matcher, index); this.propertyName = propertyName; this.valueFactory = valueFactory; } protected final T convert( Object value ) { return valueFactory.create(value); } protected abstract void addValues( NodeKey key, Property property ); protected abstract void addValue( NodeKey key, Object value ); protected abstract void removeValues(NodeKey key, Property property); protected final String propertyName() { return propertyName.getString(context.getNamespaceRegistry()); } @Override protected void addNode( String workspaceName, NodeKey key, Path path, Name primaryType, Set<Name> mixinTypes, Properties properties ) { // Properties on new nodes are always represented as 'PropertyAdded' events, and handled via 'modifyProperties' ... } @Override protected void reindexNode( String workspaceName, NodeKey key, Path path, Name primaryType, Set<Name> mixinTypes, Properties properties, boolean queryable ) { if (properties != null) { assert propertyName != null; Property prop = properties.getProperty(propertyName); if (prop != null) { removeValues(key, prop); if (queryable) { addValues(key, prop); } } } } @Override protected void modifyProperties( NodeKey key, Name primaryType, Set<Name> mixinTypes, Map<Name, AbstractPropertyChange> propChanges ) { AbstractPropertyChange propChange = propChanges.get(propertyName); if (propChange instanceof PropertyChanged) { PropertyChanged change = (PropertyChanged)propChange; removeValues(key, change.getOldProperty()); addValues(key, change.getNewProperty()); } else if (propChange instanceof PropertyAdded) { PropertyAdded added = (PropertyAdded)propChange; addValues(key, added.getProperty()); } else if (propChange instanceof PropertyRemoved) { removeValues(key, propChange.getProperty()); } } @Override protected void removeNode( String workspaceName, NodeKey key, NodeKey parentKey, Path path, Name primaryType, Set<Name> mixinTypes ) { index().remove(nodeKey(key)); } @Override public String toString() { return getClass().getSimpleName() + "(\"" + index.getName() + "\" : \"" + propertyName() + "\")"; } } protected static class PropertyChangeAdapter<T> extends AbstractPropertyChangeAdapter<T> { public PropertyChangeAdapter( ExecutionContext context, NodeTypePredicate matcher, String workspaceName, Name propertyName, ValueFactory<T> valueFactory, ProvidedIndex<?> index ) { super(context, matcher, workspaceName, propertyName, valueFactory, index); } @Override protected void addValues( NodeKey key, Property property ) { if (property.isEmpty()) { return; } String nodeKey = nodeKey(key); String propertyName = propertyName(); if (property.isMultiple()) { index().add(nodeKey, propertyName, property.getValuesAsArray(valueFactory)); } else { index().add(nodeKey, propertyName, convert(property.getFirstValue())); } } @Override protected void addValue( NodeKey key, Object value ) { if (value == null) { return; } index().add(nodeKey(key), propertyName(), convert(value)); } @Override protected void removeValues( NodeKey key, Property property ) { if (property.isEmpty()) { return; } String nodeKey = nodeKey(key); String propertyName = propertyName(); if (property.isMultiple()) { index().remove(nodeKey, propertyName, property.getValuesAsArray(valueFactory)); } else { index().remove(nodeKey, propertyName, convert(property.getFirstValue())); } } } protected static final class PrimaryTypeChangeAdapter extends PropertyChangeAdapter<Name> { public PrimaryTypeChangeAdapter( ExecutionContext context, NodeTypePredicate matcher, String workspaceName, ProvidedIndex<?> index ) { super(context, matcher, workspaceName, JcrLexicon.PRIMARY_TYPE, context.getValueFactories().getNameFactory(), index); } } protected static final class MixinTypesChangeAdapter extends PropertyChangeAdapter<Name> { public MixinTypesChangeAdapter( ExecutionContext context, NodeTypePredicate matcher, String workspaceName, ProvidedIndex<?> index ) { super(context, matcher, workspaceName, JcrLexicon.MIXIN_TYPES, context.getValueFactories().getNameFactory(), index); } } protected static final class UniquePropertyChangeAdapter<T> extends AbstractPropertyChangeAdapter<T> { public UniquePropertyChangeAdapter( ExecutionContext context, NodeTypePredicate matcher, String workspaceName, Name propertyName, ValueFactory<T> valueFactory, ProvidedIndex<?> index ) { super(context, matcher, workspaceName, propertyName, valueFactory, index); } @Override protected void addValues( NodeKey key, Property property ) { index().add(nodeKey(key), propertyName(), convert(property.getFirstValue())); } @Override protected final void addValue( NodeKey key, Object value ) { index().add(nodeKey(key), propertyName(), convert(value)); } @Override protected void removeValues( NodeKey key, Property property ) { index().remove(nodeKey(key), propertyName(), convert(property.getFirstValue())); } } protected static class EnumeratedPropertyChangeAdapter extends PropertyChangeAdapter<String> { public EnumeratedPropertyChangeAdapter( ExecutionContext context, NodeTypePredicate matcher, String workspaceName, Name propertyName, ProvidedIndex<?> index ) { super(context, matcher, workspaceName, propertyName, context.getValueFactories().getStringFactory(), index); } } protected static final class NodeTypesChangeAdapter extends EnumeratedPropertyChangeAdapter { private final String property; public NodeTypesChangeAdapter( String property, ExecutionContext context, NodeTypePredicate matcher, String workspaceName, ProvidedIndex<?> index ) { // note that this doesn't care about the property, for which it will use the name of the index super(context, matcher, workspaceName, null, index); this.property = property; assert this.property != null; } @Override protected void modifyProperties( NodeKey key, Name primaryType, Set<Name> mixinTypes, Map<Name, AbstractPropertyChange> propChanges ) { List<Object> newValues = new ArrayList<>(); List<Object> oldValues = new ArrayList<>(); AbstractPropertyChange primaryTypeChange = propChanges.get(JcrLexicon.PRIMARY_TYPE); if (primaryTypeChange instanceof PropertyChanged) { PropertyChanged change = (PropertyChanged)primaryTypeChange; oldValues.add(change.getOldProperty().getFirstValue()); newValues.add(change.getNewProperty().getFirstValue()); } else if (primaryTypeChange instanceof PropertyAdded) { newValues.add(primaryTypeChange.getProperty().getFirstValue()); } else if (primaryTypeChange instanceof PropertyRemoved) { oldValues.add(primaryTypeChange.getProperty().getFirstValue()); } AbstractPropertyChange mixinsTypeChange = propChanges.get(JcrLexicon.MIXIN_TYPES); if (mixinsTypeChange instanceof PropertyChanged) { PropertyChanged change = (PropertyChanged)mixinsTypeChange; Property oldProperty = change.getOldProperty(); if (!oldProperty.isEmpty()) { oldValues.addAll(Arrays.asList(oldProperty.getValuesAsArray())); } Property newProperty = change.getNewProperty(); if (!newProperty.isEmpty()) { newValues.addAll(Arrays.asList(newProperty.getValuesAsArray())); } } else if (mixinsTypeChange instanceof PropertyAdded) { if (!mixinsTypeChange.getProperty().isEmpty()) { newValues.addAll(Arrays.asList(mixinsTypeChange.getProperty().getValuesAsArray())); } } else if (mixinsTypeChange instanceof PropertyRemoved) { if (!mixinsTypeChange.getProperty().isEmpty()) { oldValues.addAll(Arrays.asList(mixinsTypeChange.getProperty().getValuesAsArray())); } } if (primaryTypeChange == null && mixinsTypeChange == null) { // neither the primary nor the mixins have changed, so nothing to do... return; } if (primaryTypeChange == null) { // only the mixins have changed so to have the complete information we need to add the primary type newValues.add(primaryType); } else if (mixinsTypeChange == null && mixinTypes != null) { // only the primary type has changed, to have the complete information we need to add the newValues.addAll(mixinTypes); } String nodeKey = nodeKey(key); if (!oldValues.isEmpty()) { // there are values which require removal, so remove all of them because we'll submit a complete set of values // below index().remove(nodeKey); } assert !newValues.isEmpty(); index().add(nodeKey, property, valueFactory.create(newValues.toArray())); } @Override protected void removeNode( String workspaceName, NodeKey key, NodeKey parentKey, Path path, Name primaryType, Set<Name> mixinTypes ) { index().remove(nodeKey(key)); } @Override protected void reindexNode( String workspaceName, NodeKey key, Path path, Name primaryType, Set<Name> mixinTypes, Properties properties, boolean queryable ) { String nodeKey = nodeKey(key); index().remove(nodeKey); if (!queryable) { return; } addTypeInformation(key, primaryType, mixinTypes); } private void addTypeInformation( NodeKey key, Name primaryType, Set<Name> mixinTypes ) { List<Name> values = new ArrayList<>(); values.add(primaryType); if (!mixinTypes.isEmpty()) { values.addAll(mixinTypes); } index().add(nodeKey(key), property, valueFactory.create(values.toArray())); } } protected static final class TextPropertyChangeAdapter extends PropertyChangeAdapter<String> { public TextPropertyChangeAdapter( ExecutionContext context, NodeTypePredicate matcher, String workspaceName, Name propertyName, ValueFactory<String> valueFactory, ProvidedIndex<?> index ) { super(context, matcher, workspaceName, propertyName, valueFactory, index); } @Override protected void addValues( NodeKey key, Property property ) { StringBuilder builder = textFrom(property); if (builder.length() > 0) { index().add(nodeKey(key), propertyName(), builder.toString()); } } @Override protected void removeValues( NodeKey key, Property property ) { StringBuilder builder = textFrom(property); if (builder.length() > 0) { index().remove(nodeKey(key),propertyName(), builder.toString()); } } protected StringBuilder textFrom(Property property) { StringBuilder builder = new StringBuilder(); if (property.isEmpty()) { return builder; } if (!property.isBinary()) { String[] values = property.getValuesAsArray(valueFactory); for (int i = 0; i < values.length; i++) { builder.append(values[i]); if (i < values.length - 1) { builder.append(" "); } } } else { for (Iterator<Object> valuesIterator = property.iterator(); valuesIterator.hasNext();) { Object value = valuesIterator.next(); assert value instanceof BinaryValue; BinaryValue binaryValue = (BinaryValue) value; try { String extractedText = context.getBinaryStore().getText(binaryValue); builder.append(extractedText); if (valuesIterator.hasNext()) { builder.append(" "); } } catch (BinaryStoreException e) { logger.debug(e, "Error trying to get extracted text for {0}", binaryValue); } } } return builder; } } }