/* * 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.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentNavigableMap; import java.util.concurrent.ConcurrentSkipListMap; import java.util.concurrent.atomic.AtomicLong; import javax.jcr.query.qom.Constraint; import org.mapdb.BTreeKeySerializer; import org.mapdb.DB; import org.modeshape.jcr.index.local.IndexValues.Converter; import org.modeshape.jcr.spi.index.IndexConstraints; /** * An index for enumerated values. This index only supports string-based values, since all enumerated values are discrete. * * @author Randall Hauch (rhauch@redhat.com) */ final class LocalEnumeratedIndex extends LocalIndex<String> { static LocalEnumeratedIndex create( String name, String workspaceName, DB db, Converter<String> converter, BTreeKeySerializer<String> valueSerializer, Set<String> enumeratedValues ) { return new LocalEnumeratedIndex(name, workspaceName, db, converter, valueSerializer, enumeratedValues); } static LocalEnumeratedIndex create( String name, String workspaceName, DB db, Converter<String> converter, BTreeKeySerializer<String> valueSerializer ) { return new LocalEnumeratedIndex(name, workspaceName, db, converter, valueSerializer, null); } protected final ConcurrentNavigableMap<String, Set<String>> nodeKeySetsByValue; private final Converter<String> converter; private final Set<String> possibleValues; private final boolean isNew; private final AtomicLong totalCount; LocalEnumeratedIndex( String name, String workspaceName, DB db, Converter<String> converter, BTreeKeySerializer<String> valueSerializer, Set<String> possibleValues ) { super(name, workspaceName, db); this.totalCount = new AtomicLong(0); this.converter = converter; this.possibleValues = possibleValues != null ? new HashSet<String>(possibleValues) : new HashSet<String>(); this.nodeKeySetsByValue = new ConcurrentSkipListMap<>(); // Read all of the existing collections ... boolean foundContent = false; for (String collectionName : db.getAll().keySet()) { String prefix = this.name + "/enumerated/"; if (collectionName.startsWith(prefix)) { foundContent = true; if (collectionName.length() > prefix.length()) { String valueString = collectionName.substring(prefix.length()); Set<String> keysForValue = createOrGetKeySet(valueString); nodeKeySetsByValue.put(valueString, keysForValue); totalCount.addAndGet(keysForValue.size()); } } } // Add any that were not found in the DB ... for (String possibleValue : this.possibleValues) { if (!nodeKeySetsByValue.containsKey(possibleValue)) { Set<String> keysForValue = createOrGetKeySet(possibleValue); nodeKeySetsByValue.put(possibleValue, keysForValue); totalCount.addAndGet(keysForValue.size()); } } this.isNew = !foundContent; } private Set<String> createOrGetKeySet( String value ) { String collectionName = collectionName(value); if (logger.isDebugEnabled()) { if (db.exists(collectionName)) { logger.debug("Reopening enum storage '{0}' for '{1}' index in workspace '{2}'", collectionName, name, workspace); } else { logger.debug("Creating enum storage '{0}' for '{1}' index in workspace '{2}'", collectionName, name, workspace); } } // Try to create the set ... Set<String> keySet = db.createHashSet(collectionName).counterEnable().makeOrGet(); // make sure this is ATOMIC ! Set<String> previous = nodeKeySetsByValue.putIfAbsent(value, keySet); if (previous != null) keySet = previous; return keySet; } private String collectionName( String value ) { return name + "/enumerated/" + value; } @Override public String getName() { return name; } public String getWorkspaceName() { return workspace; } @Override public boolean requiresReindexing() { return isNew; } protected final Converter<String> converter() { return converter; } @Override public long estimateTotalCount() { return totalCount.get(); } @Override public Results filter(IndexConstraints filter, long cardinalityEstimate) { // Find all sets that match the name pattern ... return Operations.createEnumeratedFilter(nodeKeySetsByValue, converter, filter.getConstraints(), filter.getVariables()) .getResults(); } @Override public long estimateCardinality( List<Constraint> andedConstraints, Map<String, Object> variables ) { return Operations.createEnumeratedFilter(nodeKeySetsByValue, converter, andedConstraints, variables) .estimateCount(); } @Override public void add( String nodeKey, String propertyName, String value ) { // Find the set ... Set<String> keySet = nodeKeySetsByValue.computeIfAbsent(value, this::createOrGetKeySet); if (keySet.add(nodeKey)) { totalCount.incrementAndGet(); } } @Override public void remove( String nodeKey ) { for (Set<String> nodeKeySet : nodeKeySetsByValue.values()) { if (nodeKeySet.remove(nodeKey)) { totalCount.decrementAndGet(); } } } @Override public void remove( String nodeKey, String propertyName, String value ) { Set<String> nodeKeySet = nodeKeySetsByValue.get(value); if (nodeKeySet != null) { if (nodeKeySet.remove(nodeKey)) { totalCount.decrementAndGet(); } } } @Override public synchronized void clearAllData() { for (Map.Entry<String, Set<String>> entry : nodeKeySetsByValue.entrySet()) { entry.getValue().clear(); String collectionName = collectionName(entry.getKey()); if (db.exists(collectionName)) { db.delete(collectionName); } } nodeKeySetsByValue.clear(); totalCount.set(0); } @Override public synchronized void shutdown( boolean destroyed ) { if (destroyed) { // Remove the database since the index was destroyed ... for (String value : nodeKeySetsByValue.keySet()) { String collectionName = collectionName(value); if (db.exists(collectionName)) { db.delete(collectionName); } } nodeKeySetsByValue.clear(); totalCount.set(0); } } }