/*
* 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.List;
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.NodeTypes;
import org.modeshape.jcr.api.index.IndexColumnDefinition;
import org.modeshape.jcr.api.index.IndexDefinition;
import org.modeshape.jcr.cache.change.ChangeSetAdapter;
import org.modeshape.jcr.spi.index.IndexFeedback;
import org.modeshape.jcr.value.Name;
import org.modeshape.jcr.value.PropertyType;
import org.modeshape.jcr.value.ValueFactory;
/**
* Class responsible for building {@link ManagedIndex} instances and providing them to the repository. Index providers may either
* choose to ignore class and use their own logic for creating/updating managed indexes or may extend this and simply provide
* the mechanism for building the 5 different index types:
* <ul>
* <li>{@link #buildMultiValueIndex(ExecutionContext, IndexDefinition, String, NodeTypes.Supplier, ChangeSetAdapter.NodeTypePredicate)}</li>
* <li>{@link #buildUniqueValueIndex(ExecutionContext, IndexDefinition, String, NodeTypes.Supplier, ChangeSetAdapter.NodeTypePredicate)}</li>
* <li>{@link #buildEnumeratedIndex(ExecutionContext, IndexDefinition, String, NodeTypes.Supplier, ChangeSetAdapter.NodeTypePredicate)}</li>
* <li>{@link #buildNodeTypeIndex(ExecutionContext, IndexDefinition, String, NodeTypes.Supplier, ChangeSetAdapter.NodeTypePredicate)}</li>
* <li>{@link #buildTextIndex(ExecutionContext, IndexDefinition, String, NodeTypes.Supplier, ChangeSetAdapter.NodeTypePredicate)}</li>
* </ul>
*
* @author Horia Chiorean (hchiorea@redhat.com)
* @see IndexProvider#createIndex(IndexDefinition, String, NodeTypes.Supplier, ChangeSetAdapter.NodeTypePredicate, IndexFeedback)
* @see IndexProvider#updateIndex(IndexDefinition, IndexDefinition, ManagedIndex, String, NodeTypes.Supplier, ChangeSetAdapter.NodeTypePredicate, IndexFeedback)
*
* @since 4.5
*/
@Immutable
public abstract class ManagedIndexBuilder {
protected final ExecutionContext context;
protected final IndexDefinition defn;
protected final NodeTypes.Supplier nodeTypesSupplier;
protected final ChangeSetAdapter.NodeTypePredicate matcher;
protected final String workspaceName;
protected ManagedIndexBuilder( ExecutionContext context,
IndexDefinition defn,
String workspaceName,
NodeTypes.Supplier nodeTypesSupplier,
ChangeSetAdapter.NodeTypePredicate matcher ) {
this.context = context;
this.workspaceName = workspaceName;
this.defn = defn;
this.nodeTypesSupplier = nodeTypesSupplier;
this.matcher = matcher;
}
/**
* Creates a new managed index which wraps a provider-specific index plus a change adapter.
*
* @return a {@link ManagedIndex} instance, never {@code null}
* *@see ProvidedIndexDelegate#wrap(ProvidedIndex, IndexChangeAdapter)
*/
@SuppressWarnings("unchecked")
public ManagedIndex build() {
List<IndexChangeAdapter> changeAdapters = new ArrayList<>();
ProvidedIndex<?> index = null;
switch (defn.getKind()) {
case VALUE:
index = buildMultiValueIndex(context, defn, workspaceName, nodeTypesSupplier, matcher);
for (int i = 0; i < defn.size(); i++) {
IndexColumnDefinition columnDef = defn.getColumnDefinition(i);
PropertyType type = determineActualPropertyType(columnDef);
if (isPrimaryTypeIndex(columnDef, type)) {
changeAdapters.add(IndexChangeAdapters.forPrimaryType(context, matcher, workspaceName, index));
} else if (isMixinTypesIndex(columnDef, type)) {
changeAdapters.add(IndexChangeAdapters.forMixinTypes(context, matcher, workspaceName, index));
} else if (isNodeNameIndex(columnDef, type)) {
changeAdapters.add(IndexChangeAdapters.forNodeName(context, matcher, workspaceName, index));
} else if (isNodeLocalNameIndex(columnDef, type)) {
changeAdapters.add(IndexChangeAdapters.forNodeLocalName(context, matcher, workspaceName, index));
} else if (isNodePathIndex(columnDef, type)) {
changeAdapters.add(IndexChangeAdapters.forNodePath(context, matcher, workspaceName, index));
} else if (isNodeDepthIndex(columnDef, type)) {
changeAdapters.add(IndexChangeAdapters.forNodeDepth(context, matcher, workspaceName, index));
} else {
// This is a generic index for a property ...
Name propertyName = name(columnDef.getPropertyName());
assert propertyName != null;
ValueFactory<?> valueFactory = context.getValueFactories().getValueFactory(type);
changeAdapters.add(IndexChangeAdapters.forProperty(context, matcher, workspaceName,
propertyName, valueFactory, index));
}
}
break;
case UNIQUE_VALUE:
index = buildUniqueValueIndex(context, defn, workspaceName, nodeTypesSupplier, matcher);
for (int i = 0; i < defn.size(); i++) {
IndexColumnDefinition columnDef = defn.getColumnDefinition(i);
PropertyType type = determineActualPropertyType(columnDef);
Name propertyName = name(columnDef.getPropertyName());
assert propertyName != null;
ValueFactory<?> valueFactory = context.getValueFactories().getValueFactory(type);
changeAdapters.add(IndexChangeAdapters.forUniqueValuedProperty(context, matcher, workspaceName,
propertyName,
valueFactory, index));
}
break;
case ENUMERATED_VALUE:
index = buildEnumeratedIndex(context, defn, workspaceName, nodeTypesSupplier, matcher);
for (int i = 0; i < defn.size(); i++) {
IndexColumnDefinition columnDef = defn.getColumnDefinition(i);
Name propertyName = name(columnDef.getPropertyName());
assert propertyName != null;
changeAdapters.add(IndexChangeAdapters.forEnumeratedProperty(context, matcher, workspaceName,
propertyName, index));
}
break;
case NODE_TYPE:
index = buildNodeTypeIndex(context, defn, workspaceName, nodeTypesSupplier, matcher);
if (defn.size() > 1) {
throw new IllegalArgumentException("Cannot have a multi column node-type index");
}
String property = defn.getColumnDefinition(0).getPropertyName();
if (property == null) {
property = defn.getName();
}
changeAdapters.add(IndexChangeAdapters.forNodeTypes(property, context, matcher, workspaceName, index));
break;
case TEXT:
index = buildTextIndex(context, defn, workspaceName, nodeTypesSupplier, matcher);
ValueFactory<String> valueFactory = (ValueFactory<String>)context.getValueFactories().getValueFactory(PropertyType.STRING);
for (int i = 0; i < defn.size(); i++) {
IndexColumnDefinition columnDef = defn.getColumnDefinition(i);
PropertyType type = determineActualPropertyType(columnDef);
Name propertyName = name(columnDef.getPropertyName());
assert propertyName != null;
if (isNodeNameIndex(columnDef, type)) {
// FTS on jcr:name
changeAdapters.add(IndexChangeAdapters.forNodeName(context, matcher, workspaceName, index));
} else if (isNodeLocalNameIndex(columnDef, type)) {
// FTS on mode:localName
changeAdapters.add(IndexChangeAdapters.forNodeLocalName(context, matcher, workspaceName, index));
} else if (isNodePathIndex(columnDef, type)) {
// FTS on jcr:path
changeAdapters.add(IndexChangeAdapters.forNodePath(context, matcher, workspaceName, index));
} else {
// default to a property....
changeAdapters.add(IndexChangeAdapters.forTextProperty(context, matcher, workspaceName,
propertyName, valueFactory, index));
}
}
break;
default: {
throw new IllegalArgumentException("Unexpected index kind on: " + defn);
}
}
assert !changeAdapters.isEmpty();
IndexChangeAdapter adapter = changeAdapters.size() == 1 ?
changeAdapters.get(0) :
IndexChangeAdapters.forMultipleColumns(context, matcher, workspaceName, index,
changeAdapters);
return new DefaultManagedIndex(index, adapter);
}
protected boolean isPrimaryTypeIndex( IndexColumnDefinition columnDefn, PropertyType type ) {
return matches(columnDefn, JcrLexicon.PRIMARY_TYPE) && isType(type, PropertyType.NAME);
}
protected boolean isMixinTypesIndex( IndexColumnDefinition columnDefn, PropertyType type ) {
return matches(columnDefn, JcrLexicon.MIXIN_TYPES) && isType(type, PropertyType.NAME);
}
protected boolean isNodeNameIndex( IndexColumnDefinition columnDefn, PropertyType type ) {
return matches(columnDefn, JcrLexicon.NAME) && isType(type, PropertyType.NAME);
}
protected boolean isNodeLocalNameIndex( IndexColumnDefinition columnDefn, PropertyType type ) {
return matches(columnDefn, ModeShapeLexicon.LOCALNAME) && isType(type, PropertyType.STRING);
}
protected boolean isNodeDepthIndex( IndexColumnDefinition columnDefn, PropertyType type ) {
return matches(columnDefn, ModeShapeLexicon.DEPTH) && isType(type, PropertyType.LONG);
}
protected boolean isNodePathIndex( IndexColumnDefinition columnDefn, PropertyType type ) {
return matches(columnDefn, JcrLexicon.PATH) && isType(type, PropertyType.PATH);
}
protected final Name name( String name ) {
return context.getValueFactories().getNameFactory().create(name);
}
protected final boolean matches( IndexColumnDefinition defn,
Name name ) {
return defn.getPropertyName().equals(name.getString(context.getNamespaceRegistry()));
}
protected final boolean matches( String actual,
Name name ) {
return actual.equals(name.getString(context.getNamespaceRegistry()));
}
protected final boolean isType( PropertyType propType,
PropertyType expected ) {
return propType == expected;
}
protected static PropertyType determineActualPropertyType( IndexColumnDefinition columnDefn ) {
PropertyType type = PropertyType.valueFor(columnDefn.getColumnType());
switch (type) {
case BOOLEAN:
case DATE:
case DECIMAL:
case DOUBLE:
case LONG:
case STRING:
case NAME:
case PATH:
// These types are all usable as-is
return type; // no conversion
case BINARY:
case OBJECT:
case REFERENCE:
case SIMPLEREFERENCE:
case WEAKREFERENCE:
case URI:
// These types are all represented in the indexes as STRING
return PropertyType.STRING;
}
assert false : "should never get here";
return type;
}
protected abstract ProvidedIndex<?> buildMultiValueIndex( ExecutionContext context,
IndexDefinition defn,
String workspaceName,
NodeTypes.Supplier nodeTypesSupplier,
ChangeSetAdapter.NodeTypePredicate matcher );
protected abstract ProvidedIndex<?> buildUniqueValueIndex( ExecutionContext context,
IndexDefinition defn,
String workspaceName,
NodeTypes.Supplier nodeTypesSupplier,
ChangeSetAdapter.NodeTypePredicate matcher );
protected abstract ProvidedIndex<?> buildEnumeratedIndex( ExecutionContext context,
IndexDefinition defn,
String workspaceName,
NodeTypes.Supplier nodeTypesSupplier,
ChangeSetAdapter.NodeTypePredicate matcher );
protected abstract ProvidedIndex<?> buildTextIndex( ExecutionContext context,
IndexDefinition defn,
String workspaceName,
NodeTypes.Supplier nodeTypesSupplier,
ChangeSetAdapter.NodeTypePredicate matcher );
protected abstract ProvidedIndex<?> buildNodeTypeIndex( ExecutionContext context,
IndexDefinition defn,
String workspaceName,
NodeTypes.Supplier nodeTypesSupplier,
ChangeSetAdapter.NodeTypePredicate matcher );
}