/* * 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.index.local; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.NavigableSet; import java.util.concurrent.ConcurrentMap; import javax.jcr.query.qom.Constraint; import org.mapdb.BTreeKeySerializer; import org.mapdb.BTreeMap; import org.mapdb.Bind; import org.mapdb.DB; import org.mapdb.Fun; import org.mapdb.Serializer; import org.modeshape.jcr.index.local.IndexValues.Converter; import org.modeshape.jcr.spi.index.IndexConstraints; import org.modeshape.jcr.value.ValueComparators; /** * Abstract map-based index. * * @author Randall Hauch (rhauch@redhat.com) * @param <T> the type of value to be indexed * @param <V> the raw type of value to be added */ abstract class LocalMapIndex<T, V> extends LocalIndex<V> { protected final BTreeMap<T, String> keysByValue; protected final NavigableSet<Fun.Tuple2<String, T>> valuesByKey; protected final ConcurrentMap<String, Object> options; private final Converter<T> converter; protected final Comparator<T> comparator; private final boolean isNew; LocalMapIndex( String name, String workspaceName, DB db, Converter<T> converter, BTreeKeySerializer<T> valueSerializer, Serializer<T> valueRawSerializer ) { super(name, workspaceName, db); assert converter != null; assert valueSerializer != null; this.converter = converter; if (db.exists(name)) { logger.debug("Reopening storage for '{0}' index in workspace '{1}'", name, workspaceName); this.options = db.getHashMap(name + "/options"); this.keysByValue = db.getTreeMap(name); this.valuesByKey = db.getTreeSet(name + "/inverse"); this.isNew = false; } else { logger.debug("Creating storage for '{0}' index in workspace '{1}'", name, workspaceName); this.isNew = true; this.options = db.createHashMap(name + "/options").makeOrGet(); this.keysByValue = db.createTreeMap(name).counterEnable().comparator(valueSerializer.getComparator()) .keySerializer(valueSerializer).makeOrGet(); // Create the TreeSet used in the reverse mapping, but we have to set a comparator that works in terms of the // Fun.Tuple2<String,T> ... final Comparator<String> strComparator = ValueComparators.STRING_COMPARATOR; final Serializer<String> strSerializer = Serializer.STRING; final Comparator<T> valueComparator = valueSerializer.getComparator(); final Comparator<Fun.Tuple2<String, T>> revComparator = MapDB.tupleComparator(strComparator, valueComparator); final BTreeKeySerializer<Fun.Tuple2<String, T>> revSerializer = MapDB.tupleBTreeSerializer(strComparator, strSerializer, valueRawSerializer, revComparator); this.valuesByKey = db.createTreeSet(name + "/inverse").comparator(revComparator).serializer(revSerializer).makeOrGet(); } this.comparator = valueSerializer.getComparator(); // Bind the map and the set together so the set is auto-updated as the map is changed ... Bind.mapInverse(this.keysByValue, this.valuesByKey); } @Override public String getName() { return name; } public String getWorkspaceName() { return workspace; } @Override public boolean requiresReindexing() { return isNew; } @Override public long estimateTotalCount() { return keysByValue.sizeLong(); } protected final Converter<T> converter() { return converter; } @Override public Results filter(IndexConstraints filter, long cardinalityEstimate) { return Operations.createFilter(keysByValue, converter, filter.getConstraints(), filter.getVariables()).getResults(); } @Override public long estimateCardinality( List<Constraint> andedConstraints, Map<String, Object> variables ) { return Operations.createFilter(keysByValue, converter, andedConstraints, variables).estimateCount(); } @Override public void clearAllData() { keysByValue.clear(); } @Override public void shutdown( boolean destroyed ) { if (destroyed) { // Remove the database since the index was destroyed ... db.delete(name); } } }