/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at
* trunk/opends/resource/legal-notices/OpenDS.LICENSE
* or https://OpenDS.dev.java.net/OpenDS.LICENSE.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at
* trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
* add the following below this CDDL HEADER, with the fields enclosed
* by brackets "[]" replaced with your own identifying information:
* Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*
*
* Copyright 2006-2010 Sun Microsystems, Inc.
* Portions Copyright 2011 ForgeRock AS
*/
package org.opends.server.backends.jeb;
import org.opends.messages.Message;
import java.util.*;
import com.sleepycat.je.*;
import org.opends.server.api.SubstringMatchingRule;
import org.opends.server.api.OrderingMatchingRule;
import org.opends.server.api.ApproximateMatchingRule;
import static org.opends.server.loggers.debug.DebugLogger.*;
import org.opends.server.monitors.DatabaseEnvironmentMonitor;
import org.opends.server.loggers.debug.DebugTracer;
import org.opends.server.types.*;
import org.opends.server.admin.std.server.LocalDBIndexCfg;
import org.opends.server.admin.std.meta.LocalDBIndexCfgDefn;
import org.opends.server.admin.server.ConfigurationChangeListener;
import org.opends.server.api.EqualityMatchingRule;
import org.opends.server.api.ExtensibleIndexer;
import org.opends.server.api.ExtensibleMatchingRule;
import org.opends.server.api.IndexQueryFactory;
import org.opends.server.config.ConfigException;
import static org.opends.messages.JebMessages.*;
import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.loggers.ErrorLogger.*;
import static org.opends.server.util.StaticUtils.toLowerCase;
import org.opends.server.core.DirectoryServer;
import org.opends.server.util.StaticUtils;
/**
* Class representing an attribute index.
* We have a separate database for each type of indexing, which makes it easy
* to tell which attribute indexes are configured. The different types of
* indexing are equality, presence, substrings and ordering. The keys in the
* ordering index are ordered by setting the btree comparator to the ordering
* matching rule comparator.
* Note that the values in the equality index are normalized by the equality
* matching rule, whereas the values in the ordering index are normalized
* by the ordering matching rule. If these could be guaranteed to be identical
* then we would not need a separate ordering index.
*/
public class AttributeIndex
implements ConfigurationChangeListener<LocalDBIndexCfg>
{
/**
* The tracer object for the debug logger.
*/
private static final DebugTracer TRACER = getTracer();
/**
* A database key for the presence index.
*/
public static final DatabaseEntry presenceKey =
new DatabaseEntry("+".getBytes());
/**
* The entryContainer in which this attribute index resides.
*/
private EntryContainer entryContainer;
private Environment env;
/**
* The attribute index configuration.
*/
private LocalDBIndexCfg indexConfig;
/**
* The index database for attribute equality.
*/
Index equalityIndex = null;
/**
* The index database for attribute presence.
*/
Index presenceIndex = null;
/**
* The index database for attribute substrings.
*/
Index substringIndex = null;
/**
* The index database for attribute ordering.
*/
Index orderingIndex = null;
/**
* The index database for attribute approximate.
*/
Index approximateIndex = null;
/**
* The ExtensibleMatchingRuleIndex instance for ExtensibleMatchingRule
* indexes.
*/
private ExtensibleMatchingRuleIndex extensibleIndexes = null;
private State state;
private int cursorEntryLimit = 100000;
/**
* Create a new attribute index object.
* @param entryContainer The entryContainer of this attribute index.
* @param state The state database to persist index state info.
* @param env The JE environment handle.
* @param indexConfig The attribute index configuration.
* @throws DatabaseException if a JE database error occurs.
* @throws ConfigException if a configuration related error occurs.
*/
public AttributeIndex(LocalDBIndexCfg indexConfig, State state,
Environment env,
EntryContainer entryContainer)
throws DatabaseException, ConfigException
{
this.entryContainer = entryContainer;
this.env = env;
this.indexConfig = indexConfig;
this.state = state;
AttributeType attrType = indexConfig.getAttribute();
String name =
entryContainer.getDatabasePrefix() + "_" + attrType.getNameOrOID();
int indexEntryLimit = indexConfig.getIndexEntryLimit();
if (indexConfig.getIndexType().contains(
LocalDBIndexCfgDefn.IndexType.EQUALITY))
{
if (attrType.getEqualityMatchingRule() == null)
{
Message message = ERR_CONFIG_INDEX_TYPE_NEEDS_MATCHING_RULE.get(
String.valueOf(attrType), "equality");
throw new ConfigException(message);
}
Indexer equalityIndexer = new EqualityIndexer(attrType);
this.equalityIndex = new Index(name + ".equality",
equalityIndexer,
state,
indexEntryLimit,
cursorEntryLimit,
false,
env,
entryContainer);
}
if (indexConfig.getIndexType().contains(
LocalDBIndexCfgDefn.IndexType.PRESENCE))
{
Indexer presenceIndexer = new PresenceIndexer(attrType);
this.presenceIndex = new Index(name + ".presence",
presenceIndexer,
state,
indexEntryLimit,
cursorEntryLimit,
false,
env,
entryContainer);
}
if (indexConfig.getIndexType().contains(
LocalDBIndexCfgDefn.IndexType.SUBSTRING))
{
if (attrType.getSubstringMatchingRule() == null)
{
Message message = ERR_CONFIG_INDEX_TYPE_NEEDS_MATCHING_RULE.get(
String.valueOf(attrType), "substring");
throw new ConfigException(message);
}
Indexer substringIndexer = new SubstringIndexer(attrType,
indexConfig.getSubstringLength());
this.substringIndex = new Index(name + ".substring",
substringIndexer,
state,
indexEntryLimit,
cursorEntryLimit,
false,
env,
entryContainer);
}
if (indexConfig.getIndexType().contains(
LocalDBIndexCfgDefn.IndexType.ORDERING))
{
if (attrType.getOrderingMatchingRule() == null)
{
Message message = ERR_CONFIG_INDEX_TYPE_NEEDS_MATCHING_RULE.get(
String.valueOf(attrType), "ordering");
throw new ConfigException(message);
}
Indexer orderingIndexer = new OrderingIndexer(attrType);
this.orderingIndex = new Index(name + ".ordering",
orderingIndexer,
state,
indexEntryLimit,
cursorEntryLimit,
false,
env,
entryContainer);
}
if (indexConfig.getIndexType().contains(
LocalDBIndexCfgDefn.IndexType.APPROXIMATE))
{
if (attrType.getApproximateMatchingRule() == null)
{
Message message = ERR_CONFIG_INDEX_TYPE_NEEDS_MATCHING_RULE.get(
String.valueOf(attrType), "approximate");
throw new ConfigException(message);
}
Indexer approximateIndexer = new ApproximateIndexer(attrType);
this.approximateIndex = new Index(name + ".approximate",
approximateIndexer,
state,
indexEntryLimit,
cursorEntryLimit,
false,
env,
entryContainer);
}
if (indexConfig.getIndexType().contains(
LocalDBIndexCfgDefn.IndexType.EXTENSIBLE))
{
Set<String> extensibleRules =
indexConfig.getIndexExtensibleMatchingRule();
if(extensibleRules == null || extensibleRules.isEmpty())
{
Message message = ERR_CONFIG_INDEX_TYPE_NEEDS_MATCHING_RULE.get(
String.valueOf(attrType), "extensible");
throw new ConfigException(message);
}
extensibleIndexes = new ExtensibleMatchingRuleIndex();
//Iterate through the Set and create the index only if necessary.
//Collation equality and Ordering matching rules share the same
//indexer and index. A Collation substring matching rule is treated
// differently as it uses a separate indexer and index.
IndexConfig config = new JEIndexConfig(indexConfig.getSubstringLength());
for(String ruleName:extensibleRules)
{
ExtensibleMatchingRule rule =
DirectoryServer.getExtensibleMatchingRule(
toLowerCase(ruleName));
if(rule == null)
{
Message message =
ERR_CONFIG_INDEX_TYPE_NEEDS_VALID_MATCHING_RULE.get(
String.valueOf(attrType),ruleName);
logError(message);
continue;
}
Map<String,Index> indexMap = new HashMap<String,Index>();
for(ExtensibleIndexer indexer : rule.getIndexers(config))
{
String indexerId = indexer.getExtensibleIndexID();
String indexID =
attrType.getNameOrOID() + "."
+ indexer.getPreferredIndexName()
+ "." + indexerId;
if(!extensibleIndexes.isIndexPresent(indexID))
{
//There is no index available for this index id. Create a new index.
Indexer extensibleIndexer =
new JEExtensibleIndexer(attrType,
rule,
indexer);
String indexName = entryContainer.getDatabasePrefix() + "_"
+ indexID;
Index extensibleIndex = new Index(indexName,
extensibleIndexer,
state,
indexEntryLimit,
cursorEntryLimit,
false,
env,
entryContainer);
extensibleIndexes.addIndex(extensibleIndex,indexID);
}
extensibleIndexes.addRule(indexID, rule);
indexMap.put(indexer.getExtensibleIndexID(),
extensibleIndexes.getIndex(indexID));
}
IndexQueryFactory<IndexQuery> factory =
new IndexQueryFactoryImpl(indexMap);
extensibleIndexes.addQueryFactory(rule, factory);
}
}
this.indexConfig.addChangeListener(this);
}
/**
* Open the attribute index.
*
* @throws DatabaseException if a JE database error occurs while
* openning the index.
*/
public void open() throws DatabaseException
{
if (equalityIndex != null)
{
equalityIndex.open();
}
if (presenceIndex != null)
{
presenceIndex.open();
}
if (substringIndex != null)
{
substringIndex.open();
}
if (orderingIndex != null)
{
orderingIndex.open();
}
if (approximateIndex != null)
{
approximateIndex.open();
}
if(extensibleIndexes!=null)
{
for(Index extensibleIndex:extensibleIndexes.getIndexes())
{
extensibleIndex.open();
}
}
}
/**
* Close the attribute index.
*
* @throws DatabaseException if a JE database error occurs while
* closing the index.
*/
public void close() throws DatabaseException
{
if (equalityIndex != null)
{
equalityIndex.close();
}
if (presenceIndex != null)
{
presenceIndex.close();
}
if (substringIndex != null)
{
substringIndex.close();
}
if (orderingIndex != null)
{
orderingIndex.close();
}
if (approximateIndex != null)
{
approximateIndex.close();
}
if(extensibleIndexes!=null)
{
for(Index extensibleIndex:extensibleIndexes.getIndexes())
{
extensibleIndex.close();
}
}
indexConfig.removeChangeListener(this);
// The entryContainer is responsible for closing the JE databases.
}
/**
* Get the attribute type of this attribute index.
* @return The attribute type of this attribute index.
*/
public AttributeType getAttributeType()
{
return indexConfig.getAttribute();
}
/**
* Get the JE index configuration used by this index.
* @return The configuration in effect.
*/
public LocalDBIndexCfg getConfiguration()
{
return indexConfig;
}
/**
* Update the attribute index for a new entry.
*
* @param buffer The index buffer to use to store the added keys
* @param entryID The entry ID.
* @param entry The contents of the new entry.
* @return True if all the index keys for the entry are added. False if the
* entry ID already exists for some keys.
* @throws DatabaseException If an error occurs in the JE database.
* @throws DirectoryException If a Directory Server error occurs.
*/
public boolean addEntry(IndexBuffer buffer, EntryID entryID,
Entry entry)
throws DatabaseException, DirectoryException
{
boolean success = true;
if (equalityIndex != null)
{
if(!equalityIndex.addEntry(buffer, entryID, entry))
{
success = false;
}
}
if (presenceIndex != null)
{
if(!presenceIndex.addEntry(buffer, entryID, entry))
{
success = false;
}
}
if (substringIndex != null)
{
if(!substringIndex.addEntry(buffer, entryID, entry))
{
success = false;
}
}
if (orderingIndex != null)
{
if(!orderingIndex.addEntry(buffer, entryID, entry))
{
success = false;
}
}
if (approximateIndex != null)
{
if(!approximateIndex.addEntry(buffer, entryID, entry))
{
success = false;
}
}
if(extensibleIndexes!=null)
{
for(Index extensibleIndex:extensibleIndexes.getIndexes())
{
if(!extensibleIndex.addEntry(buffer, entryID,entry))
{
success = false;
}
}
}
return success;
}
/**
* Update the attribute index for a new entry.
*
* @param txn The database transaction to be used for the insertions.
* @param entryID The entry ID.
* @param entry The contents of the new entry.
* @return True if all the index keys for the entry are added. False if the
* entry ID already exists for some keys.
* @throws DatabaseException If an error occurs in the JE database.
* @throws DirectoryException If a Directory Server error occurs.
*/
public boolean addEntry(Transaction txn, EntryID entryID, Entry entry)
throws DatabaseException, DirectoryException
{
boolean success = true;
if (equalityIndex != null)
{
if(!equalityIndex.addEntry(txn, entryID, entry))
{
success = false;
}
}
if (presenceIndex != null)
{
if(!presenceIndex.addEntry(txn, entryID, entry))
{
success = false;
}
}
if (substringIndex != null)
{
if(!substringIndex.addEntry(txn, entryID, entry))
{
success = false;
}
}
if (orderingIndex != null)
{
if(!orderingIndex.addEntry(txn, entryID, entry))
{
success = false;
}
}
if (approximateIndex != null)
{
if(!approximateIndex.addEntry(txn, entryID, entry))
{
success = false;
}
}
if(extensibleIndexes!=null)
{
for(Index extensibleIndex:extensibleIndexes.getIndexes())
{
if(!extensibleIndex.addEntry(txn, entryID,entry))
{
success = false;
}
}
}
return success;
}
/**
* Update the attribute index for a deleted entry.
*
* @param buffer The index buffer to use to store the deleted keys
* @param entryID The entry ID
* @param entry The contents of the deleted entry.
* @throws DatabaseException If an error occurs in the JE database.
* @throws DirectoryException If a Directory Server error occurs.
*/
public void removeEntry(IndexBuffer buffer, EntryID entryID,
Entry entry)
throws DatabaseException, DirectoryException
{
if (equalityIndex != null)
{
equalityIndex.removeEntry(buffer, entryID, entry);
}
if (presenceIndex != null)
{
presenceIndex.removeEntry(buffer, entryID, entry);
}
if (substringIndex != null)
{
substringIndex.removeEntry(buffer, entryID, entry);
}
if (orderingIndex != null)
{
orderingIndex.removeEntry(buffer, entryID, entry);
}
if(approximateIndex != null)
{
approximateIndex.removeEntry(buffer, entryID, entry);
}
if(extensibleIndexes!=null)
{
for(Index extensibleIndex:extensibleIndexes.getIndexes())
{
extensibleIndex.removeEntry(buffer, entryID, entry);
}
}
}
/**
* Update the attribute index for a deleted entry.
*
* @param txn The database transaction to be used for the deletions
* @param entryID The entry ID
* @param entry The contents of the deleted entry.
* @throws DatabaseException If an error occurs in the JE database.
* @throws DirectoryException If a Directory Server error occurs.
*/
public void removeEntry(Transaction txn, EntryID entryID, Entry entry)
throws DatabaseException, DirectoryException
{
if (equalityIndex != null)
{
equalityIndex.removeEntry(txn, entryID, entry);
}
if (presenceIndex != null)
{
presenceIndex.removeEntry(txn, entryID, entry);
}
if (substringIndex != null)
{
substringIndex.removeEntry(txn, entryID, entry);
}
if (orderingIndex != null)
{
orderingIndex.removeEntry(txn, entryID, entry);
}
if(approximateIndex != null)
{
approximateIndex.removeEntry(txn, entryID, entry);
}
if(extensibleIndexes!=null)
{
for(Index extensibleIndex:extensibleIndexes.getIndexes())
{
extensibleIndex.removeEntry(txn, entryID, entry);
}
}
}
/**
* Update the index to reflect a sequence of modifications in a Modify
* operation.
*
* @param txn The JE transaction to use for database updates.
* @param entryID The ID of the entry that was modified.
* @param oldEntry The entry before the modifications were applied.
* @param newEntry The entry after the modifications were applied.
* @param mods The sequence of modifications in the Modify operation.
* @throws DatabaseException If an error occurs during an operation on a
* JE database.
*/
public void modifyEntry(Transaction txn,
EntryID entryID,
Entry oldEntry,
Entry newEntry,
List<Modification> mods)
throws DatabaseException
{
if (equalityIndex != null)
{
equalityIndex.modifyEntry(txn, entryID, oldEntry, newEntry, mods);
}
if (presenceIndex != null)
{
presenceIndex.modifyEntry(txn, entryID, oldEntry, newEntry, mods);
}
if (substringIndex != null)
{
substringIndex.modifyEntry(txn, entryID, oldEntry, newEntry, mods);
}
if (orderingIndex != null)
{
orderingIndex.modifyEntry(txn, entryID, oldEntry, newEntry, mods);
}
if (approximateIndex != null)
{
approximateIndex.modifyEntry(txn, entryID, oldEntry, newEntry, mods);
}
if(extensibleIndexes!=null)
{
for(Index extensibleIndex:extensibleIndexes.getIndexes())
{
extensibleIndex.modifyEntry(txn, entryID, oldEntry, newEntry, mods);
}
}
}
/**
* Update the index to reflect a sequence of modifications in a Modify
* operation.
*
* @param buffer The index buffer used to buffer up the index changes.
* @param entryID The ID of the entry that was modified.
* @param oldEntry The entry before the modifications were applied.
* @param newEntry The entry after the modifications were applied.
* @param mods The sequence of modifications in the Modify operation.
* @throws DatabaseException If an error occurs during an operation on a
* JE database.
*/
public void modifyEntry(IndexBuffer buffer,
EntryID entryID,
Entry oldEntry,
Entry newEntry,
List<Modification> mods)
throws DatabaseException
{
if (equalityIndex != null)
{
equalityIndex.modifyEntry(buffer, entryID, oldEntry, newEntry, mods);
}
if (presenceIndex != null)
{
presenceIndex.modifyEntry(buffer, entryID, oldEntry, newEntry, mods);
}
if (substringIndex != null)
{
substringIndex.modifyEntry(buffer, entryID, oldEntry, newEntry, mods);
}
if (orderingIndex != null)
{
orderingIndex.modifyEntry(buffer, entryID, oldEntry, newEntry, mods);
}
if (approximateIndex != null)
{
approximateIndex.modifyEntry(buffer, entryID, oldEntry, newEntry, mods);
}
if(extensibleIndexes!=null)
{
for(Index extensibleIndex:extensibleIndexes.getIndexes())
{
extensibleIndex.modifyEntry(buffer, entryID, oldEntry, newEntry, mods);
}
}
}
/**
* Makes a byte array representing a substring index key for
* one substring of a value.
*
* @param bytes The byte array containing the value.
* @param pos The starting position of the substring.
* @param len The length of the substring.
* @return A byte array containing a substring key.
*/
private byte[] makeSubstringKey(byte[] bytes, int pos, int len)
{
byte[] keyBytes = new byte[len];
System.arraycopy(bytes, pos, keyBytes, 0, len);
return keyBytes;
}
/**
* Decompose an attribute value into a set of substring index keys.
* The ID of the entry containing this value should be inserted
* into the list of each of these keys.
*
* @param value A byte array containing the normalized attribute value.
* @return A set of index keys.
*/
Set<ByteString> substringKeys(byte[] value)
{
// Eliminate duplicates by putting the keys into a set.
// Sorting the keys will ensure database record locks are acquired
// in a consistent order and help prevent transaction deadlocks between
// concurrent writers.
Set<ByteString> set = new HashSet<ByteString>();
int substrLength = indexConfig.getSubstringLength();
byte[] keyBytes;
// Example: The value is ABCDE and the substring length is 3.
// We produce the keys ABC BCD CDE DE E
// To find values containing a short substring such as DE,
// iterate through keys with prefix DE. To find values
// containing a longer substring such as BCDE, read keys
// BCD and CDE.
for (int i = 0, remain = value.length; remain > 0; i++, remain--)
{
int len = Math.min(substrLength, remain);
keyBytes = makeSubstringKey(value, i, len);
set.add(ByteString.wrap(keyBytes));
}
return set;
}
/**
* Retrieve the entry IDs that might contain a given substring.
* @param bytes A normalized substring of an attribute value.
* @return The candidate entry IDs.
*/
private EntryIDSet matchSubstring(byte[] bytes)
{
int substrLength = indexConfig.getSubstringLength();
// There are two cases, depending on whether the user-provided
// substring is smaller than the configured index substring length or not.
if (bytes.length < substrLength)
{
// Iterate through all the keys that have this value as the prefix.
// Set the lower bound for a range search.
byte[] lower = makeSubstringKey(bytes, 0, bytes.length);
// Set the upper bound for a range search.
// We need a key for the upper bound that is of equal length
// but slightly greater than the lower bound.
byte[] upper = makeSubstringKey(bytes, 0, bytes.length);
for (int i = upper.length-1; i >= 0; i--)
{
if (upper[i] == 0xFF)
{
// We have to carry the overflow to the more significant byte.
upper[i] = 0;
}
else
{
// No overflow, we can stop.
upper[i] = (byte) (upper[i] + 1);
break;
}
}
// Read the range: lower <= keys < upper.
return substringIndex.readRange(lower, upper, true, false);
}
else
{
// Break the value up into fragments of length equal to the
// index substring length, and read those keys.
// Eliminate duplicates by putting the keys into a set.
Set<byte[]> set =
new TreeSet<byte[]>(substringIndex.indexer.getComparator());
// Example: The value is ABCDE and the substring length is 3.
// We produce the keys ABC BCD CDE.
for (int first = 0, last = substrLength;
last <= bytes.length; first++, last++)
{
byte[] keyBytes;
keyBytes = makeSubstringKey(bytes, first, substrLength);
set.add(keyBytes);
}
EntryIDSet results = new EntryIDSet();
DatabaseEntry key = new DatabaseEntry();
for (byte[] keyBytes : set)
{
// Read the key.
key.setData(keyBytes);
EntryIDSet list = substringIndex.readKey(key, null, LockMode.DEFAULT);
// Incorporate them into the results.
results.retainAll(list);
// We may have reached the point of diminishing returns where
// it is quicker to stop now and process the current small number of
// candidates.
if (results.isDefined() &&
results.size() <= IndexFilter.FILTER_CANDIDATE_THRESHOLD)
{
break;
}
}
return results;
}
}
/**
* Uses an equality index to retrieve the entry IDs that might contain a
* given initial substring.
* @param bytes A normalized initial substring of an attribute value.
* @return The candidate entry IDs.
*/
private EntryIDSet matchInitialSubstring(byte[] bytes)
{
// Iterate through all the keys that have this value as the prefix.
// Set the lower bound for a range search.
byte[] lower = bytes;
// Set the upper bound for a range search.
// We need a key for the upper bound that is of equal length
// but slightly greater than the lower bound.
byte[] upper = new byte[bytes.length];
System.arraycopy(bytes,0, upper, 0, bytes.length);
for (int i = upper.length-1; i >= 0; i--)
{
if (upper[i] == 0xFF)
{
// We have to carry the overflow to the more significant byte.
upper[i] = 0;
}
else
{
// No overflow, we can stop.
upper[i] = (byte) (upper[i] + 1);
break;
}
}
// Read the range: lower <= keys < upper.
return equalityIndex.readRange(lower, upper, true, false);
}
/**
* Retrieve the entry IDs that might match an equality filter.
*
* @param equalityFilter The equality filter.
* @param debugBuffer If not null, a diagnostic string will be written
* which will help determine how the indexes contributed
* to this search.
* @param monitor The database environment monitor provider that will keep
* index filter usage statistics.
* @return The candidate entry IDs that might contain the filter
* assertion value.
*/
public EntryIDSet evaluateEqualityFilter(SearchFilter equalityFilter,
StringBuilder debugBuffer,
DatabaseEnvironmentMonitor monitor)
{
if (equalityIndex == null)
{
if(monitor.isFilterUseEnabled())
{
monitor.updateStats(equalityFilter,
INFO_JEB_INDEX_FILTER_INDEX_TYPE_DISABLED.get("equality",
indexConfig.getAttribute().getNameOrOID()));
}
return new EntryIDSet();
}
try
{
// Make a key from the normalized assertion value.
byte[] keyBytes =
equalityFilter.getAssertionValue().getNormalizedValue().toByteArray();
DatabaseEntry key = new DatabaseEntry(keyBytes);
if(debugBuffer != null)
{
debugBuffer.append("[INDEX:");
debugBuffer.append(indexConfig.getAttribute().getNameOrOID());
debugBuffer.append(".");
debugBuffer.append("equality]");
}
// Read the key.
EntryIDSet idSet = equalityIndex.readKey(key, null, LockMode.DEFAULT);
if(monitor.isFilterUseEnabled())
{
if(idSet.isDefined())
{
monitor.updateStats(equalityFilter, idSet.size());
}
else if(!equalityIndex.isTrusted())
{
monitor.updateStats(equalityFilter,
INFO_JEB_INDEX_FILTER_INDEX_NOT_TRUSTED.get(
equalityIndex.getName()));
}
else if(equalityIndex.isRebuildRunning())
{
monitor.updateStats(equalityFilter,
INFO_JEB_INDEX_FILTER_INDEX_REBUILD_IN_PROGRESS.get(
equalityIndex.getName()));
}
else
{
monitor.updateStats(equalityFilter,
INFO_JEB_INDEX_FILTER_INDEX_LIMIT_EXCEEDED.get(
equalityIndex.getName()));
}
}
return idSet;
}
catch (DirectoryException e)
{
if (debugEnabled())
{
TRACER.debugCaught(DebugLogLevel.ERROR, e);
}
return new EntryIDSet();
}
}
/**
* Retrieve the entry IDs that might match a presence filter.
*
* @param filter The presence filter.
* @param debugBuffer If not null, a diagnostic string will be written
* which will help determine how the indexes contributed
* to this search.
* @param monitor The database environment monitor provider that will keep
* index filter usage statistics.
* @return The candidate entry IDs that might contain one or more
* values of the attribute type in the filter.
*/
public EntryIDSet evaluatePresenceFilter(SearchFilter filter,
StringBuilder debugBuffer,
DatabaseEnvironmentMonitor monitor)
{
if (presenceIndex == null)
{
if(monitor.isFilterUseEnabled())
{
monitor.updateStats(filter,
INFO_JEB_INDEX_FILTER_INDEX_TYPE_DISABLED.get("presence",
indexConfig.getAttribute().getNameOrOID()));
}
return new EntryIDSet();
}
if(debugBuffer != null)
{
debugBuffer.append("[INDEX:");
debugBuffer.append(indexConfig.getAttribute().getNameOrOID());
debugBuffer.append(".");
debugBuffer.append("presence]");
}
// Read the presence key
EntryIDSet idSet =
presenceIndex.readKey(presenceKey, null, LockMode.DEFAULT);
if(monitor.isFilterUseEnabled())
{
if(idSet.isDefined())
{
monitor.updateStats(filter, idSet.size());
}
else if(!presenceIndex.isTrusted())
{
monitor.updateStats(filter,
INFO_JEB_INDEX_FILTER_INDEX_NOT_TRUSTED.get(
presenceIndex.getName()));
}
else if(presenceIndex.isRebuildRunning())
{
monitor.updateStats(filter,
INFO_JEB_INDEX_FILTER_INDEX_REBUILD_IN_PROGRESS.get(
presenceIndex.getName()));
}
else
{
monitor.updateStats(filter,
INFO_JEB_INDEX_FILTER_INDEX_LIMIT_EXCEEDED.get(
presenceIndex.getName()));
}
}
return idSet;
}
/**
* Retrieve the entry IDs that might match a greater-or-equal filter.
*
* @param filter The greater-or-equal filter.
* @param debugBuffer If not null, a diagnostic string will be written
* which will help determine how the indexes contributed
* to this search.
* @param monitor The database environment monitor provider that will keep
* index filter usage statistics.
* @return The candidate entry IDs that might contain a value
* greater than or equal to the filter assertion value.
*/
public EntryIDSet evaluateGreaterOrEqualFilter(
SearchFilter filter, StringBuilder debugBuffer,
DatabaseEnvironmentMonitor monitor)
{
if (orderingIndex == null)
{
if(monitor.isFilterUseEnabled())
{
monitor.updateStats(filter,
INFO_JEB_INDEX_FILTER_INDEX_TYPE_DISABLED.get("ordering",
indexConfig.getAttribute().getNameOrOID()));
}
return new EntryIDSet();
}
try
{
// Set the lower bound for a range search.
// Use the ordering matching rule to normalize the value.
OrderingMatchingRule orderingRule =
filter.getAttributeType().getOrderingMatchingRule();
byte[] lower = orderingRule.normalizeValue(
filter.getAssertionValue().getValue()).toByteArray();
// Set the upper bound to 0 to search all keys greater then the lower
// bound.
byte[] upper = new byte[0];
if(debugBuffer != null)
{
debugBuffer.append("[INDEX:");
debugBuffer.append(indexConfig.getAttribute().getNameOrOID());
debugBuffer.append(".");
debugBuffer.append("ordering]");
}
// Read the range: lower <= keys < upper.
EntryIDSet idSet = orderingIndex.readRange(lower, upper, true, false);
if(monitor.isFilterUseEnabled())
{
if(idSet.isDefined())
{
monitor.updateStats(filter, idSet.size());
}
else if(!orderingIndex.isTrusted())
{
monitor.updateStats(filter,
INFO_JEB_INDEX_FILTER_INDEX_NOT_TRUSTED.get(
orderingIndex.getName()));
}
else if(orderingIndex.isRebuildRunning())
{
monitor.updateStats(filter,
INFO_JEB_INDEX_FILTER_INDEX_REBUILD_IN_PROGRESS.get(
orderingIndex.getName()));
}
else
{
monitor.updateStats(filter,
INFO_JEB_INDEX_FILTER_INDEX_LIMIT_EXCEEDED.get(
orderingIndex.getName()));
}
}
return idSet;
}
catch (DirectoryException e)
{
if (debugEnabled())
{
TRACER.debugCaught(DebugLogLevel.ERROR, e);
}
return new EntryIDSet();
}
}
/**
* Retrieve the entry IDs that might match a less-or-equal filter.
*
* @param filter The less-or-equal filter.
* @param debugBuffer If not null, a diagnostic string will be written
* which will help determine how the indexes contributed
* to this search.
* @param monitor The database environment monitor provider that will keep
* index filter usage statistics.
* @return The candidate entry IDs that might contain a value
* less than or equal to the filter assertion value.
*/
public EntryIDSet evaluateLessOrEqualFilter(SearchFilter filter,
StringBuilder debugBuffer,
DatabaseEnvironmentMonitor monitor)
{
if (orderingIndex == null)
{
if(monitor.isFilterUseEnabled())
{
monitor.updateStats(filter,
INFO_JEB_INDEX_FILTER_INDEX_TYPE_DISABLED.get("ordering",
indexConfig.getAttribute().getNameOrOID()));
}
return new EntryIDSet();
}
try
{
// Set the lower bound to 0 to start the range search from the smallest
// key.
byte[] lower = new byte[0];
// Set the upper bound for a range search.
// Use the ordering matching rule to normalize the value.
OrderingMatchingRule orderingRule =
filter.getAttributeType().getOrderingMatchingRule();
byte[] upper = orderingRule.normalizeValue(
filter.getAssertionValue().getValue()).toByteArray();
if(debugBuffer != null)
{
debugBuffer.append("[INDEX:");
debugBuffer.append(indexConfig.getAttribute().getNameOrOID());
debugBuffer.append(".");
debugBuffer.append("ordering]");
}
// Read the range: lower < keys <= upper.
EntryIDSet idSet = orderingIndex.readRange(lower, upper, false, true);
if(monitor.isFilterUseEnabled())
{
if(idSet.isDefined())
{
monitor.updateStats(filter, idSet.size());
}
else if(!orderingIndex.isTrusted())
{
monitor.updateStats(filter,
INFO_JEB_INDEX_FILTER_INDEX_NOT_TRUSTED.get(
orderingIndex.getName()));
}
else if(orderingIndex.isRebuildRunning())
{
monitor.updateStats(filter,
INFO_JEB_INDEX_FILTER_INDEX_REBUILD_IN_PROGRESS.get(
orderingIndex.getName()));
}
else
{
monitor.updateStats(filter,
INFO_JEB_INDEX_FILTER_INDEX_LIMIT_EXCEEDED.get(
orderingIndex.getName()));
}
}
return idSet;
}
catch (DirectoryException e)
{
if (debugEnabled())
{
TRACER.debugCaught(DebugLogLevel.ERROR, e);
}
return new EntryIDSet();
}
}
/**
* Retrieve the entry IDs that might match a substring filter.
*
* @param filter The substring filter.
* @param debugBuffer If not null, a diagnostic string will be written
* which will help determine how the indexes contributed
* to this search.
* @param monitor The database environment monitor provider that will keep
* index filter usage statistics.
* @return The candidate entry IDs that might contain a value
* that matches the filter substrings.
*/
public EntryIDSet evaluateSubstringFilter(SearchFilter filter,
StringBuilder debugBuffer,
DatabaseEnvironmentMonitor monitor)
{
SubstringMatchingRule matchRule =
filter.getAttributeType().getSubstringMatchingRule();
try
{
ArrayList<ByteString> elements = new ArrayList<ByteString>();
EntryIDSet results = new EntryIDSet();
if (filter.getSubInitialElement() != null)
{
// Use the equality index for initial substrings if possible.
if ((equalityIndex != null) && (matchRule != null))
{
ByteString normValue =
matchRule.normalizeSubstring(filter.getSubInitialElement());
byte[] normBytes = normValue.toByteArray();
EntryIDSet list = matchInitialSubstring(normBytes);
results.retainAll(list);
if (results.isDefined() &&
results.size() <= IndexFilter.FILTER_CANDIDATE_THRESHOLD)
{
if(debugBuffer != null)
{
debugBuffer.append("[INDEX:");
debugBuffer.append(indexConfig.getAttribute().
getNameOrOID());
debugBuffer.append(".");
debugBuffer.append("equality]");
}
if(monitor.isFilterUseEnabled())
{
monitor.updateStats(filter, results.size());
}
return results;
}
}
else
{
elements.add(filter.getSubInitialElement());
}
}
if (substringIndex == null)
{
if(monitor.isFilterUseEnabled())
{
if(!results.isDefined())
{
if(filter.getSubInitialElement() != null)
{
if(equalityIndex == null)
{
monitor.updateStats(filter,
INFO_JEB_INDEX_FILTER_INDEX_TYPE_DISABLED.get("equality",
indexConfig.getAttribute().getNameOrOID()));
}
else if(!equalityIndex.isTrusted())
{
monitor.updateStats(filter,
INFO_JEB_INDEX_FILTER_INDEX_NOT_TRUSTED.get(
equalityIndex.getName()));
}
else if(equalityIndex.isRebuildRunning())
{
monitor.updateStats(filter,
INFO_JEB_INDEX_FILTER_INDEX_REBUILD_IN_PROGRESS.get(
equalityIndex.getName()));
}
else
{
monitor.updateStats(filter,
INFO_JEB_INDEX_FILTER_INDEX_LIMIT_EXCEEDED.get(
equalityIndex.getName()));
}
}
else
{
monitor.updateStats(filter,
INFO_JEB_INDEX_FILTER_INDEX_TYPE_DISABLED.get("substring",
indexConfig.getAttribute().getNameOrOID()));
}
}
else
{
monitor.updateStats(filter, results.size());
}
}
return results;
}
// We do not distinguish between sub and final elements
// in the substring index. Put all the elements into a single list.
elements.addAll(filter.getSubAnyElements());
if (filter.getSubFinalElement() != null)
{
elements.add(filter.getSubFinalElement());
}
// Iterate through each substring element.
for (ByteString element : elements)
{
// Normalize the substring according to the substring matching rule.
ByteString normValue = matchRule.normalizeSubstring(element);
byte[] normBytes = normValue.toByteArray();
// Get the candidate entry IDs from the index.
EntryIDSet list = matchSubstring(normBytes);
// Incorporate them into the results.
results.retainAll(list);
// We may have reached the point of diminishing returns where
// it is quicker to stop now and process the current small number of
// candidates.
if (results.isDefined() &&
results.size() <= IndexFilter.FILTER_CANDIDATE_THRESHOLD)
{
break;
}
}
if(debugBuffer != null)
{
debugBuffer.append("[INDEX:");
debugBuffer.append(indexConfig.getAttribute().getNameOrOID());
debugBuffer.append(".");
debugBuffer.append("substring]");
}
if(monitor.isFilterUseEnabled())
{
if(results.isDefined())
{
monitor.updateStats(filter, results.size());
}
else if(!substringIndex.isTrusted())
{
monitor.updateStats(filter,
INFO_JEB_INDEX_FILTER_INDEX_NOT_TRUSTED.get(
substringIndex.getName()));
}
else if(substringIndex.isRebuildRunning())
{
monitor.updateStats(filter,
INFO_JEB_INDEX_FILTER_INDEX_REBUILD_IN_PROGRESS.get(
substringIndex.getName()));
}
else
{
monitor.updateStats(filter,
INFO_JEB_INDEX_FILTER_INDEX_LIMIT_EXCEEDED.get(
substringIndex.getName()));
}
}
return results;
}
catch (DirectoryException e)
{
if (debugEnabled())
{
TRACER.debugCaught(DebugLogLevel.ERROR, e);
}
return new EntryIDSet();
}
}
/**
* Retrieve the entry IDs that might have a value greater than or
* equal to the lower bound value, and less than or equal to the
* upper bound value.
*
* @param lowerValue The lower bound value
* @param upperValue The upper bound value
* @return The candidate entry IDs.
*/
public EntryIDSet evaluateBoundedRange(AttributeValue lowerValue,
AttributeValue upperValue)
{
if (orderingIndex == null)
{
return new EntryIDSet();
}
try
{
// Use the ordering matching rule to normalize the values.
OrderingMatchingRule orderingRule =
getAttributeType().getOrderingMatchingRule();
// Set the lower bound for a range search.
byte[] lower =
orderingRule.normalizeValue(lowerValue.getValue()).toByteArray();
// Set the upper bound for a range search.
byte[] upper =
orderingRule.normalizeValue(upperValue.getValue()).toByteArray();
// Read the range: lower <= keys <= upper.
return orderingIndex.readRange(lower, upper, true, true);
}
catch (DirectoryException e)
{
if (debugEnabled())
{
TRACER.debugCaught(DebugLogLevel.ERROR, e);
}
return new EntryIDSet();
}
}
/**
* The default lexicographic byte array comparator.
* Is there one available in the Java platform?
*/
static public class KeyComparator implements Comparator<byte[]>
{
/**
* Compares its two arguments for order. Returns a negative integer,
* zero, or a positive integer as the first argument is less than, equal
* to, or greater than the second.
*
* @param a the first object to be compared.
* @param b the second object to be compared.
* @return a negative integer, zero, or a positive integer as the
* first argument is less than, equal to, or greater than the
* second.
*/
public int compare(byte[] a, byte[] b)
{
int i;
for (i = 0; i < a.length && i < b.length; i++)
{
if (a[i] > b[i])
{
return 1;
}
else if (a[i] < b[i])
{
return -1;
}
}
if (a.length == b.length)
{
return 0;
}
if (a.length > b.length)
{
return 1;
}
else
{
return -1;
}
}
}
/**
* Retrieve the entry IDs that might match an approximate filter.
*
* @param approximateFilter The approximate filter.
* @param debugBuffer If not null, a diagnostic string will be written
* which will help determine how the indexes contributed
* to this search.
* @param monitor The database environment monitor provider that will keep
* index filter usage statistics.
* @return The candidate entry IDs that might contain the filter
* assertion value.
*/
public EntryIDSet evaluateApproximateFilter(SearchFilter approximateFilter,
StringBuilder debugBuffer,
DatabaseEnvironmentMonitor monitor)
{
if (approximateIndex == null)
{
if(monitor.isFilterUseEnabled())
{
monitor.updateStats(approximateFilter,
INFO_JEB_INDEX_FILTER_INDEX_TYPE_DISABLED.get("approximate",
indexConfig.getAttribute().getNameOrOID()));
}
return new EntryIDSet();
}
try
{
ApproximateMatchingRule approximateMatchingRule =
approximateFilter.getAttributeType().getApproximateMatchingRule();
// Make a key from the normalized assertion value.
byte[] keyBytes =
approximateMatchingRule.normalizeValue(
approximateFilter.getAssertionValue().getValue()).toByteArray();
DatabaseEntry key = new DatabaseEntry(keyBytes);
if(debugBuffer != null)
{
debugBuffer.append("[INDEX:");
debugBuffer.append(indexConfig.getAttribute().getNameOrOID());
debugBuffer.append(".");
debugBuffer.append("approximate]");
}
// Read the key.
EntryIDSet idSet = approximateIndex.readKey(key, null, LockMode.DEFAULT);
if(monitor.isFilterUseEnabled())
{
if(idSet.isDefined())
{
monitor.updateStats(approximateFilter, idSet.size());
}
else if(!approximateIndex.isTrusted())
{
monitor.updateStats(approximateFilter,
INFO_JEB_INDEX_FILTER_INDEX_NOT_TRUSTED.get(
approximateIndex.getName()));
}
else if(approximateIndex.isRebuildRunning())
{
monitor.updateStats(approximateFilter,
INFO_JEB_INDEX_FILTER_INDEX_REBUILD_IN_PROGRESS.get(
approximateIndex.getName()));
}
else
{
monitor.updateStats(approximateFilter,
INFO_JEB_INDEX_FILTER_INDEX_LIMIT_EXCEEDED.get(
approximateIndex.getName()));
}
}
return idSet;
}
catch (DirectoryException e)
{
if (debugEnabled())
{
TRACER.debugCaught(DebugLogLevel.ERROR, e);
}
return new EntryIDSet();
}
}
/**
* Close cursors related to the attribute indexes.
*
* @throws DatabaseException If a database error occurs.
*/
public void closeCursors() throws DatabaseException {
if (equalityIndex != null)
{
equalityIndex.closeCursor();
}
if (presenceIndex != null)
{
presenceIndex.closeCursor();
}
if (substringIndex != null)
{
substringIndex.closeCursor();
}
if (orderingIndex != null)
{
orderingIndex.closeCursor();
}
if (approximateIndex != null)
{
approximateIndex.closeCursor();
}
if(extensibleIndexes!=null)
{
for(Index extensibleIndex:extensibleIndexes.getIndexes())
{
extensibleIndex.closeCursor();
}
}
}
/**
* Return the number of values that have exceeded the entry limit since this
* object was created.
*
* @return The number of values that have exceeded the entry limit.
*/
public long getEntryLimitExceededCount()
{
long entryLimitExceededCount = 0;
if (equalityIndex != null)
{
entryLimitExceededCount += equalityIndex.getEntryLimitExceededCount();
}
if (presenceIndex != null)
{
entryLimitExceededCount += presenceIndex.getEntryLimitExceededCount();
}
if (substringIndex != null)
{
entryLimitExceededCount += substringIndex.getEntryLimitExceededCount();
}
if (orderingIndex != null)
{
entryLimitExceededCount += orderingIndex.getEntryLimitExceededCount();
}
if (approximateIndex != null)
{
entryLimitExceededCount +=
approximateIndex.getEntryLimitExceededCount();
}
if(extensibleIndexes!=null)
{
for(Index extensibleIndex:extensibleIndexes.getIndexes())
{
entryLimitExceededCount +=
extensibleIndex.getEntryLimitExceededCount();
}
}
return entryLimitExceededCount;
}
/**
* Get a list of the databases opened by this attribute index.
* @param dbList A list of database containers.
*/
public void listDatabases(List<DatabaseContainer> dbList)
{
if (equalityIndex != null)
{
dbList.add(equalityIndex);
}
if (presenceIndex != null)
{
dbList.add(presenceIndex);
}
if (substringIndex != null)
{
dbList.add(substringIndex);
}
if (orderingIndex != null)
{
dbList.add(orderingIndex);
}
if (approximateIndex != null)
{
dbList.add(approximateIndex);
}
if(extensibleIndexes!=null)
{
for(Index extensibleIndex:extensibleIndexes.getIndexes())
{
dbList.add(extensibleIndex);
}
}
}
/**
* Get a string representation of this object.
* @return return A string representation of this object.
*/
@Override
public String toString()
{
return getName();
}
/**
* {@inheritDoc}
*/
public synchronized boolean isConfigurationChangeAcceptable(
LocalDBIndexCfg cfg,
List<Message> unacceptableReasons)
{
AttributeType attrType = cfg.getAttribute();
if (cfg.getIndexType().contains(LocalDBIndexCfgDefn.IndexType.EQUALITY))
{
if (equalityIndex == null && attrType.getEqualityMatchingRule() == null)
{
Message message = ERR_CONFIG_INDEX_TYPE_NEEDS_MATCHING_RULE.get(
String.valueOf(String.valueOf(attrType)), "equality");
unacceptableReasons.add(message);
return false;
}
}
if (cfg.getIndexType().contains(LocalDBIndexCfgDefn.IndexType.SUBSTRING))
{
if (substringIndex == null && attrType.getSubstringMatchingRule() == null)
{
Message message = ERR_CONFIG_INDEX_TYPE_NEEDS_MATCHING_RULE.get(
String.valueOf(attrType), "substring");
unacceptableReasons.add(message);
return false;
}
}
if (cfg.getIndexType().contains(LocalDBIndexCfgDefn.IndexType.ORDERING))
{
if (orderingIndex == null && attrType.getOrderingMatchingRule() == null)
{
Message message = ERR_CONFIG_INDEX_TYPE_NEEDS_MATCHING_RULE.get(
String.valueOf(attrType), "ordering");
unacceptableReasons.add(message);
return false;
}
}
if (cfg.getIndexType().contains(LocalDBIndexCfgDefn.IndexType.APPROXIMATE))
{
if (approximateIndex == null &&
attrType.getApproximateMatchingRule() == null)
{
Message message = ERR_CONFIG_INDEX_TYPE_NEEDS_MATCHING_RULE.get(
String.valueOf(attrType), "approximate");
unacceptableReasons.add(message);
return false;
}
}
if (cfg.getIndexType().contains(LocalDBIndexCfgDefn.IndexType.EXTENSIBLE))
{
Set<String> newRules =
cfg.getIndexExtensibleMatchingRule();
if (newRules == null || newRules.isEmpty())
{
Message message = ERR_CONFIG_INDEX_TYPE_NEEDS_MATCHING_RULE.get(
String.valueOf(attrType), "extensible");
unacceptableReasons.add(message);
return false;
}
}
return true;
}
/**
* {@inheritDoc}
*/
public synchronized ConfigChangeResult applyConfigurationChange(
LocalDBIndexCfg cfg)
{
ConfigChangeResult ccr;
boolean adminActionRequired = false;
ArrayList<Message> messages = new ArrayList<Message>();
try
{
AttributeType attrType = cfg.getAttribute();
String name =
entryContainer.getDatabasePrefix() + "_" + attrType.getNameOrOID();
int indexEntryLimit = cfg.getIndexEntryLimit();
if (cfg.getIndexType().contains(LocalDBIndexCfgDefn.IndexType.EQUALITY))
{
if (equalityIndex == null)
{
// Adding equality index
Indexer equalityIndexer = new EqualityIndexer(attrType);
equalityIndex = new Index(name + ".equality",
equalityIndexer,
state,
indexEntryLimit,
cursorEntryLimit,
false,
env,
entryContainer);
equalityIndex.open();
if(!equalityIndex.isTrusted())
{
adminActionRequired = true;
messages.add(NOTE_JEB_INDEX_ADD_REQUIRES_REBUILD.get(
equalityIndex.getName()));
}
}
else
{
// already exists. Just update index entry limit.
if(this.equalityIndex.setIndexEntryLimit(indexEntryLimit))
{
adminActionRequired = true;
Message message =
NOTE_JEB_CONFIG_INDEX_ENTRY_LIMIT_REQUIRES_REBUILD.get(
equalityIndex.getName());
messages.add(message);
this.equalityIndex.setIndexEntryLimit(indexEntryLimit);
}
}
}
else
{
if (equalityIndex != null)
{
entryContainer.exclusiveLock.lock();
try
{
entryContainer.deleteDatabase(equalityIndex);
equalityIndex = null;
}
catch(DatabaseException de)
{
messages.add(Message.raw(
StaticUtils.stackTraceToSingleLineString(de)));
ccr = new ConfigChangeResult(
DirectoryServer.getServerErrorResultCode(), false, messages);
return ccr;
}
finally
{
entryContainer.exclusiveLock.unlock();
}
}
}
if (cfg.getIndexType().contains(LocalDBIndexCfgDefn.IndexType.PRESENCE))
{
if(presenceIndex == null)
{
Indexer presenceIndexer = new PresenceIndexer(attrType);
presenceIndex = new Index(name + ".presence",
presenceIndexer,
state,
indexEntryLimit,
cursorEntryLimit,
false,
env,
entryContainer);
presenceIndex.open();
if(!presenceIndex.isTrusted())
{
adminActionRequired = true;
messages.add(NOTE_JEB_INDEX_ADD_REQUIRES_REBUILD.get(
presenceIndex.getName()));
}
}
else
{
// already exists. Just update index entry limit.
if(this.presenceIndex.setIndexEntryLimit(indexEntryLimit))
{
adminActionRequired = true;
Message message =
NOTE_JEB_CONFIG_INDEX_ENTRY_LIMIT_REQUIRES_REBUILD.get(
presenceIndex.getName());
messages.add(message);
}
}
}
else
{
if (presenceIndex != null)
{
entryContainer.exclusiveLock.lock();
try
{
entryContainer.deleteDatabase(presenceIndex);
presenceIndex = null;
}
catch(DatabaseException de)
{
messages.add(Message.raw(
StaticUtils.stackTraceToSingleLineString(de)));
ccr = new ConfigChangeResult(
DirectoryServer.getServerErrorResultCode(), false, messages);
return ccr;
}
finally
{
entryContainer.exclusiveLock.unlock();
}
}
}
if (cfg.getIndexType().contains(LocalDBIndexCfgDefn.IndexType.SUBSTRING))
{
if(substringIndex == null)
{
Indexer substringIndexer = new SubstringIndexer(
attrType, cfg.getSubstringLength());
substringIndex = new Index(name + ".substring",
substringIndexer,
state,
indexEntryLimit,
cursorEntryLimit,
false,
env,
entryContainer);
substringIndex.open();
if(!substringIndex.isTrusted())
{
adminActionRequired = true;
messages.add(NOTE_JEB_INDEX_ADD_REQUIRES_REBUILD.get(
substringIndex.getName()));
}
}
else
{
// already exists. Just update index entry limit.
if(this.substringIndex.setIndexEntryLimit(indexEntryLimit))
{
adminActionRequired = true;
Message message =
NOTE_JEB_CONFIG_INDEX_ENTRY_LIMIT_REQUIRES_REBUILD.get(
substringIndex.getName());
messages.add(message);
}
if(indexConfig.getSubstringLength() !=
cfg.getSubstringLength())
{
Indexer substringIndexer = new SubstringIndexer(
attrType, cfg.getSubstringLength());
this.substringIndex.setIndexer(substringIndexer);
}
}
}
else
{
if (substringIndex != null)
{
entryContainer.exclusiveLock.lock();
try
{
entryContainer.deleteDatabase(substringIndex);
substringIndex = null;
}
catch(DatabaseException de)
{
messages.add(Message.raw(
StaticUtils.stackTraceToSingleLineString(de)));
ccr = new ConfigChangeResult(
DirectoryServer.getServerErrorResultCode(), false, messages);
return ccr;
}
finally
{
entryContainer.exclusiveLock.unlock();
}
}
}
if (cfg.getIndexType().contains(LocalDBIndexCfgDefn.IndexType.ORDERING))
{
if(orderingIndex == null)
{
Indexer orderingIndexer = new OrderingIndexer(attrType);
orderingIndex = new Index(name + ".ordering",
orderingIndexer,
state,
indexEntryLimit,
cursorEntryLimit,
false,
env,
entryContainer);
orderingIndex.open();
if(!orderingIndex.isTrusted())
{
adminActionRequired = true;
messages.add(NOTE_JEB_INDEX_ADD_REQUIRES_REBUILD.get(
orderingIndex.getName()));
}
}
else
{
// already exists. Just update index entry limit.
if(this.orderingIndex.setIndexEntryLimit(indexEntryLimit))
{
adminActionRequired = true;
Message message =
NOTE_JEB_CONFIG_INDEX_ENTRY_LIMIT_REQUIRES_REBUILD.get(
orderingIndex.getName());
messages.add(message);
}
}
}
else
{
if (orderingIndex != null)
{
entryContainer.exclusiveLock.lock();
try
{
entryContainer.deleteDatabase(orderingIndex);
orderingIndex = null;
}
catch(DatabaseException de)
{
messages.add(Message.raw(
StaticUtils.stackTraceToSingleLineString(de)));
ccr = new ConfigChangeResult(
DirectoryServer.getServerErrorResultCode(), false, messages);
return ccr;
}
finally
{
entryContainer.exclusiveLock.unlock();
}
}
}
if (cfg.getIndexType().contains(
LocalDBIndexCfgDefn.IndexType.APPROXIMATE))
{
if(approximateIndex == null)
{
Indexer approximateIndexer = new ApproximateIndexer(attrType);
approximateIndex = new Index(name + ".approximate",
approximateIndexer,
state,
indexEntryLimit,
cursorEntryLimit,
false,
env,
entryContainer);
approximateIndex.open();
if(!approximateIndex.isTrusted())
{
adminActionRequired = true;
messages.add(NOTE_JEB_INDEX_ADD_REQUIRES_REBUILD.get(
approximateIndex.getName()));
}
}
else
{
// already exists. Just update index entry limit.
if(this.approximateIndex.setIndexEntryLimit(indexEntryLimit))
{
adminActionRequired = true;
Message message =
NOTE_JEB_CONFIG_INDEX_ENTRY_LIMIT_REQUIRES_REBUILD.get(
approximateIndex.getName());
messages.add(message);
}
}
}
else
{
if (approximateIndex != null)
{
entryContainer.exclusiveLock.lock();
try
{
entryContainer.deleteDatabase(approximateIndex);
approximateIndex = null;
}
catch(DatabaseException de)
{
messages.add(
Message.raw(StaticUtils.stackTraceToSingleLineString(de)));
ccr = new ConfigChangeResult(
DirectoryServer.getServerErrorResultCode(), false, messages);
return ccr;
}
finally
{
entryContainer.exclusiveLock.unlock();
}
}
}
if (cfg.getIndexType().contains(
LocalDBIndexCfgDefn.IndexType.EXTENSIBLE))
{
Set<String> extensibleRules =
cfg.getIndexExtensibleMatchingRule();
Set<ExtensibleMatchingRule> validRules =
new HashSet<ExtensibleMatchingRule>();
if(extensibleIndexes == null)
{
extensibleIndexes = new ExtensibleMatchingRuleIndex();
}
IndexConfig config = new JEIndexConfig(cfg.getSubstringLength());
for(String ruleName:extensibleRules)
{
ExtensibleMatchingRule rule =
DirectoryServer.getExtensibleMatchingRule(
toLowerCase(ruleName));
if(rule == null)
{
Message message =
ERR_CONFIG_INDEX_TYPE_NEEDS_VALID_MATCHING_RULE.get(
String.valueOf(attrType),ruleName);
logError(message);
continue;
}
validRules.add(rule);
Map<String,Index> indexMap = new HashMap<String,Index>();
for(ExtensibleIndexer indexer: rule.getIndexers(config))
{
String indexerId = indexer.getExtensibleIndexID();
String indexID =
attrType.getNameOrOID() + "."
+ indexer.getPreferredIndexName()
+ "." + indexerId;
if(!extensibleIndexes.isIndexPresent(indexID))
{
Indexer extensibleIndexer =
new JEExtensibleIndexer(attrType,
rule,
indexer);
String indexName = entryContainer.getDatabasePrefix() + "_"
+ indexID;
Index extensibleIndex = new Index(indexName,
extensibleIndexer,
state,
indexEntryLimit,
cursorEntryLimit,
false,
env,
entryContainer);
extensibleIndexes.addIndex(extensibleIndex,indexID);
extensibleIndex.open();
if(!extensibleIndex.isTrusted())
{
adminActionRequired = true;
messages.add(NOTE_JEB_INDEX_ADD_REQUIRES_REBUILD.get(
extensibleIndex.getName()));
}
}
else
{
Index extensibleIndex = extensibleIndexes.getIndex(indexID);
if(extensibleIndex.setIndexEntryLimit(indexEntryLimit))
{
adminActionRequired = true;
Message message =
NOTE_JEB_CONFIG_INDEX_ENTRY_LIMIT_REQUIRES_REBUILD.get(
extensibleIndex.getName());
messages.add(message);
}
if(indexConfig.getSubstringLength() !=
cfg.getSubstringLength())
{
Indexer extensibleIndexer =
new JEExtensibleIndexer(attrType,
rule,
indexer);
extensibleIndex.setIndexer(extensibleIndexer);
}
}
extensibleIndexes.addRule(indexID, rule);
indexMap.put(indexerId,extensibleIndexes.getIndex(indexID));
}
IndexQueryFactory<IndexQuery> factory =
new IndexQueryFactoryImpl(indexMap);
extensibleIndexes.addQueryFactory(rule, factory);
}
//Some rules might have been removed from the configuration.
Set<ExtensibleMatchingRule> deletedRules =
new HashSet<ExtensibleMatchingRule>();
for(ExtensibleMatchingRule r:extensibleIndexes.getRules())
{
if(!validRules.contains(r))
{
deletedRules.add(r);
}
}
if(deletedRules.size() > 0)
{
entryContainer.exclusiveLock.lock();
try
{
for(ExtensibleMatchingRule rule:deletedRules)
{
Set<ExtensibleMatchingRule> rules =
new HashSet<ExtensibleMatchingRule>();
List<String> ids = new ArrayList<String>();
for(ExtensibleIndexer indexer: rule.getIndexers(config))
{
String id = attrType.getNameOrOID() + "."
+ indexer.getPreferredIndexName()
+ "." + indexer.getExtensibleIndexID();
rules.addAll(extensibleIndexes.getRules(id));
ids.add(id);
}
if(rules.isEmpty())
{
//Rule has been already deleted.
continue;
}
//If all the rules are part of the deletedRules, delete
//this index.
if(deletedRules.containsAll(rules))
{
//it is safe to delete this index as it is not shared.
for(String indexID : ids)
{
Index extensibleIndex = extensibleIndexes.getIndex(indexID);
entryContainer.deleteDatabase(extensibleIndex);
extensibleIndex = null;
extensibleIndexes.deleteIndex(indexID);
extensibleIndexes.deleteRule(indexID);
}
}
else
{
for(String indexID : ids)
{
extensibleIndexes.deleteRule(rule, indexID);
}
}
}
}
catch(DatabaseException de)
{
messages.add(
Message.raw(StaticUtils.stackTraceToSingleLineString(de)));
ccr = new ConfigChangeResult(
DirectoryServer.getServerErrorResultCode(), false, messages);
return ccr;
}
finally
{
entryContainer.exclusiveLock.unlock();
}
}
}
else
{
if(extensibleIndexes != null)
{
entryContainer.exclusiveLock.lock();
try
{
for(Index extensibleIndex:extensibleIndexes.getIndexes())
{
entryContainer.deleteDatabase(extensibleIndex);
extensibleIndex = null;
}
extensibleIndexes.deleteAll();
}
catch(DatabaseException de)
{
messages.add(
Message.raw(StaticUtils.stackTraceToSingleLineString(de)));
ccr = new ConfigChangeResult(
DirectoryServer.getServerErrorResultCode(), false, messages);
return ccr;
}
finally
{
entryContainer.exclusiveLock.unlock();
}
}
}
indexConfig = cfg;
return new ConfigChangeResult(ResultCode.SUCCESS, adminActionRequired,
messages);
}
catch(Exception e)
{
messages.add(Message.raw(StaticUtils.stackTraceToSingleLineString(e)));
ccr = new ConfigChangeResult(DirectoryServer.getServerErrorResultCode(),
adminActionRequired,
messages);
return ccr;
}
}
/**
* Set the index truststate.
* @param txn A database transaction, or null if none is required.
* @param trusted True if this index should be trusted or false
* otherwise.
* @throws DatabaseException If an error occurs in the JE database.
*/
public synchronized void setTrusted(Transaction txn, boolean trusted)
throws DatabaseException
{
if (equalityIndex != null)
{
equalityIndex.setTrusted(txn, trusted);
}
if (presenceIndex != null)
{
presenceIndex.setTrusted(txn, trusted);
}
if (substringIndex != null)
{
substringIndex.setTrusted(txn, trusted);
}
if (orderingIndex != null)
{
orderingIndex.setTrusted(txn, trusted);
}
if (approximateIndex != null)
{
approximateIndex.setTrusted(txn, trusted);
}
if(extensibleIndexes!=null)
{
for(Index extensibleIndex:extensibleIndexes.getIndexes())
{
extensibleIndex.setTrusted(txn, trusted);
}
}
}
/**
* Return true iff this index is trusted.
* @return the trusted state of this index
*/
public boolean isTrusted()
{
if (equalityIndex != null && !equalityIndex.isTrusted())
{
return false;
}
if (presenceIndex != null && !presenceIndex.isTrusted())
{
return false;
}
if (substringIndex != null && !substringIndex.isTrusted())
{
return false;
}
if (orderingIndex != null && !orderingIndex.isTrusted())
{
return false;
}
if (approximateIndex != null && !approximateIndex.isTrusted())
{
return false;
}
if(extensibleIndexes!=null)
{
for(Index extensibleIndex:extensibleIndexes.getIndexes())
{
if(extensibleIndex !=null && !extensibleIndex.isTrusted())
{
return false;
}
}
}
return true;
}
/**
* Set the rebuild status of this index.
* @param rebuildRunning True if a rebuild process on this index
* is running or False otherwise.
*/
public synchronized void setRebuildStatus(boolean rebuildRunning)
{
if (equalityIndex != null)
{
equalityIndex.setRebuildStatus(rebuildRunning);
}
if (presenceIndex != null)
{
presenceIndex.setRebuildStatus(rebuildRunning);
}
if (substringIndex != null)
{
substringIndex.setRebuildStatus(rebuildRunning);
}
if (orderingIndex != null)
{
orderingIndex.setRebuildStatus(rebuildRunning);
}
if (approximateIndex != null)
{
approximateIndex.setRebuildStatus(rebuildRunning);
}
if(extensibleIndexes!=null)
{
for(Index extensibleIndex:extensibleIndexes.getIndexes())
{
extensibleIndex.setRebuildStatus(rebuildRunning);
}
}
}
/**
* Get the JE database name prefix for indexes in this attribute
* index.
*
* @return JE database name for this database container.
*/
public String getName()
{
StringBuilder builder = new StringBuilder();
builder.append(entryContainer.getDatabasePrefix());
builder.append("_");
builder.append(indexConfig.getAttribute().getNameOrOID());
return builder.toString();
}
/**
* Return the equality index.
*
* @return The equality index.
*/
public Index getEqualityIndex() {
return equalityIndex;
}
/**
* Return the approximate index.
*
* @return The approximate index.
*/
public Index getApproximateIndex() {
return approximateIndex;
}
/**
* Return the ordering index.
*
* @return The ordering index.
*/
public Index getOrderingIndex() {
return orderingIndex;
}
/**
* Return the substring index.
*
* @return The substring index.
*/
public Index getSubstringIndex() {
return substringIndex;
}
/**
* Return the presence index.
*
* @return The presence index.
*/
public Index getPresenceIndex() {
return presenceIndex;
}
/**
* Return the mapping of extensible index types and indexes.
*
* @return The Map of extensible index types and indexes.
*/
public Map<String,Collection<Index>> getExtensibleIndexes()
{
if(extensibleIndexes == null)
{
return Collections.emptyMap();
}
return extensibleIndexes.getIndexMap();
}
/**
* Retrieves all the indexes used by this attribute index.
*
* @return A collection of all indexes in use by this attribute
* index.
*/
public Collection<Index> getAllIndexes() {
LinkedHashSet<Index> indexes = new LinkedHashSet<Index>();
if (equalityIndex != null)
{
indexes.add(equalityIndex);
}
if (presenceIndex != null)
{
indexes.add(presenceIndex);
}
if (substringIndex != null)
{
indexes.add(substringIndex);
}
if (orderingIndex != null)
{
indexes.add(orderingIndex);
}
if (approximateIndex != null)
{
indexes.add(approximateIndex);
}
if(extensibleIndexes!=null)
{
for(Index extensibleIndex:extensibleIndexes.getIndexes())
{
indexes.add(extensibleIndex);
}
}
return indexes;
}
/**
* Retrieve the entry IDs that might match an extensible filter.
*
* @param extensibleFilter The extensible filter.
* @param debugBuffer If not null, a diagnostic string will be written
* which will help determine how the indexes contributed
* to this search.
* @param monitor The database environment monitor provider that will keep
* index filter usage statistics.
* @return The candidate entry IDs that might contain the filter
* assertion value.
*/
public EntryIDSet evaluateExtensibleFilter(SearchFilter extensibleFilter,
StringBuilder debugBuffer,
DatabaseEnvironmentMonitor monitor)
{
//Get the Matching Rule OID of the filter.
String nOID = extensibleFilter.getMatchingRuleID();
/**
* Use the default equality index in two conditions:
* 1. There is no matching rule provided
* 2. The matching rule specified is actually the default equality.
*/
EqualityMatchingRule eqRule =
indexConfig.getAttribute().getEqualityMatchingRule();
if(nOID ==null || nOID.equals(eqRule.getOID()) ||
nOID.equalsIgnoreCase(eqRule.getName()))
{
//No matching rule is defined; use the default equality matching rule.
if(equalityIndex == null)
{
// There is no index on this matching rule.
if(monitor.isFilterUseEnabled())
{
monitor.updateStats(extensibleFilter,
INFO_JEB_INDEX_FILTER_INDEX_TYPE_DISABLED.get("equality",
indexConfig.getAttribute().getNameOrOID()));
}
return IndexQuery.createNullIndexQuery().evaluate(null);
}
try
{
// Make a key from the normalized assertion value.
byte[] keyBytes =
extensibleFilter.getAssertionValue().getNormalizedValue().
toByteArray();
DatabaseEntry key = new DatabaseEntry(keyBytes);
if(debugBuffer != null)
{
debugBuffer.append("[INDEX:");
debugBuffer.append(indexConfig.getAttribute().getNameOrOID());
debugBuffer.append(".");
debugBuffer.append("equality]");
}
// Read the key.
EntryIDSet idSet = equalityIndex.readKey(key, null, LockMode.DEFAULT);
if(monitor.isFilterUseEnabled())
{
if(idSet.isDefined())
{
monitor.updateStats(extensibleFilter, idSet.size());
}
else if(!equalityIndex.isTrusted())
{
monitor.updateStats(extensibleFilter,
INFO_JEB_INDEX_FILTER_INDEX_NOT_TRUSTED.get(
equalityIndex.getName()));
}
else if(equalityIndex.isRebuildRunning())
{
monitor.updateStats(extensibleFilter,
INFO_JEB_INDEX_FILTER_INDEX_REBUILD_IN_PROGRESS.get(
equalityIndex.getName()));
}
else
{
monitor.updateStats(extensibleFilter,
INFO_JEB_INDEX_FILTER_INDEX_LIMIT_EXCEEDED.get(
equalityIndex.getName()));
}
}
return idSet;
}
catch (DirectoryException e)
{
if (debugEnabled())
{
TRACER.debugCaught(DebugLogLevel.ERROR, e);
}
return IndexQuery.createNullIndexQuery().evaluate(null);
}
}
ExtensibleMatchingRule rule =
DirectoryServer.getExtensibleMatchingRule(nOID);
IndexQueryFactory<IndexQuery> factory = null;
if(extensibleIndexes == null
|| (factory = extensibleIndexes.getQueryFactory(rule))==null)
{
// There is no index on this matching rule.
if(monitor.isFilterUseEnabled())
{
monitor.updateStats(extensibleFilter,
INFO_JEB_INDEX_FILTER_MATCHING_RULE_NOT_INDEXED.get(nOID,
indexConfig.getAttribute().getNameOrOID()));
}
return IndexQuery.createNullIndexQuery().evaluate(null);
}
try
{
if(debugBuffer != null)
{
debugBuffer.append("[INDEX:");
IndexConfig config =
new JEIndexConfig(indexConfig.getSubstringLength());
for(ExtensibleIndexer indexer : rule.getIndexers(config))
{
String indexerID = indexer.getExtensibleIndexID();
String indexName = indexer.getPreferredIndexName();
String indexID = " "
+ extensibleFilter.getAttributeType().getNameOrOID()
+ "." + indexName
+ "." +indexerID;
debugBuffer.append(indexID);
}
debugBuffer.append("]");
}
ByteString assertionValue =
extensibleFilter.getAssertionValue().getValue();
IndexQuery expression = rule.createIndexQuery(assertionValue, factory);
List<Message> debugMessages =
monitor.isFilterUseEnabled() ? new ArrayList<Message>() : null;
EntryIDSet idSet = expression.evaluate(debugMessages);
if(monitor.isFilterUseEnabled())
{
if(idSet.isDefined())
{
monitor.updateStats(extensibleFilter, idSet.size());
}
else
{
if(debugMessages != null && !debugMessages.isEmpty())
{
monitor.updateStats(extensibleFilter, debugMessages.get(0));
}
else
{
monitor.updateStats(extensibleFilter, Message.EMPTY);
}
}
}
return idSet;
}
catch (DirectoryException e)
{
if (debugEnabled())
{
TRACER.debugCaught(DebugLogLevel.ERROR, e);
}
return IndexQuery.createNullIndexQuery().evaluate(null);
}
}
/**
* This class manages all the configured extensible matching rules and
* their corresponding indexes.
*/
private class ExtensibleMatchingRuleIndex
{
/**
* The mapping of index ID and Index database.
*/
private final Map<String,Index> id2IndexMap;
/**
* The mapping of Index ID and Set the matching rules.
*/
private final Map<String,Set<ExtensibleMatchingRule>> id2RulesMap;
/**
* The Map of configured ExtensibleMatchingRule and the corresponding
* IndexQueryFactory.
*/
private final Map<ExtensibleMatchingRule,
IndexQueryFactory<IndexQuery>> rule2FactoryMap;
/**
* Creates a new instance of ExtensibleMatchingRuleIndex.
*/
private ExtensibleMatchingRuleIndex()
{
id2IndexMap = new HashMap<String,Index>();
id2RulesMap = new HashMap<String,Set<ExtensibleMatchingRule>>();
rule2FactoryMap = new HashMap<ExtensibleMatchingRule,
IndexQueryFactory<IndexQuery>>();
}
/**
* Returns all configured ExtensibleMatchingRule instances.
* @return A Set of extensible matching rules.
*/
private Set<ExtensibleMatchingRule> getRules()
{
return rule2FactoryMap.keySet();
}
/**
* Returns ExtensibleMatchingRule instances for an index.
* @param indexID The index ID of an extensible matching rule index.
* @return A Set of extensible matching rules corresponding to
* an index ID.
*/
private Set<ExtensibleMatchingRule>
getRules(String indexID)
{
Set<ExtensibleMatchingRule> rules = id2RulesMap.get(indexID);
if(rules == null)
{
return Collections.emptySet();
}
else
{
return Collections.unmodifiableSet(id2RulesMap.get(indexID));
}
}
/**
* Returns whether an index is present or not.
* @param indexID The index ID of an extensible matching rule index.
* @return True if an index is present. False if there is no matching index.
*/
private boolean isIndexPresent(String indexID)
{
return id2IndexMap.containsKey(indexID);
}
/**
* Returns the index corresponding to an index ID.
* @param indexID The ID of an index.
* @return The extensible rule index corresponding to the index ID.
*/
private Index getIndex(String indexID)
{
return id2IndexMap.get(indexID);
}
/**
* Adds a new matching Rule and the name of the associated index.
* @indexName Name of the index.
* @rule An ExtensibleMatchingRule instance that needs to be indexed.
*/
private void addRule(String indexName,ExtensibleMatchingRule rule)
{
Set<ExtensibleMatchingRule> rules = id2RulesMap.get(indexName);
if(rules == null)
{
rules = new HashSet<ExtensibleMatchingRule>();
id2RulesMap.put(indexName, rules);
}
rules.add(rule);
}
/**
* Adds a new Index and its name.
* @param index The extensible matching rule index.
* @indexName The name of the index.
*/
private void addIndex(Index index,String indexName)
{
id2IndexMap.put(indexName, index);
}
/**
* Returns all the configured extensible indexes.
* @return All the available extensible matching rule indexes.
*/
private Collection<Index> getIndexes()
{
return Collections.unmodifiableCollection(id2IndexMap.values());
}
/**
* Returns a map of all the configured extensible indexes and their types.
* @return A map of all the available extensible matching rule indexes
* and their types.
*/
private Map<String,Collection<Index>> getIndexMap()
{
if(id2IndexMap.isEmpty())
{
return Collections.emptyMap();
}
Collection<Index> substring = new ArrayList<Index>();
Collection<Index> shared = new ArrayList<Index>();
for(Map.Entry<String,Index> entry : id2IndexMap.entrySet())
{
String indexID = entry.getKey();
if(indexID.endsWith(EXTENSIBLE_INDEXER_ID_SUBSTRING))
{
substring.add(entry.getValue());
}
else
{
shared.add(entry.getValue());
}
}
Map<String,Collection<Index>> indexMap =
new HashMap<String,Collection<Index>>();
indexMap.put(EXTENSIBLE_INDEXER_ID_SUBSTRING, substring);
indexMap.put(EXTENSIBLE_INDEXER_ID_SHARED, shared);
return Collections.unmodifiableMap(indexMap);
}
/**
* Deletes an index corresponding to the index ID.
* @param indexID Name of the index.
*/
private void deleteIndex(String indexID)
{
id2IndexMap.remove(indexID);
}
/**
* Deletes an extensible matching rule from the list of available rules.
* @param rule The ExtensibleMatchingRule that needs to be removed.
* @param indexID The name of the index corresponding to the rule.
*/
private void deleteRule(ExtensibleMatchingRule rule,String indexID)
{
Set<ExtensibleMatchingRule> rules = id2RulesMap.get(indexID);
rules.remove(rule);
if(rules.isEmpty())
{
id2RulesMap.remove(indexID);
}
rule2FactoryMap.remove(rule);
}
/**
* Adds an ExtensibleMatchingRule and its corresponding IndexQueryFactory.
* @param rule An ExtensibleMatchingRule that needs to be added.
* @param query A query factory matching the rule.
*/
private void addQueryFactory(ExtensibleMatchingRule rule,
IndexQueryFactory<IndexQuery> query)
{
rule2FactoryMap.put(rule, query);
}
/**
* Returns the query factory associated with the rule.
* @param rule An ExtensibleMatchingRule that needs to be searched.
* @return An IndexQueryFactory corresponding to the matching rule.
*/
private IndexQueryFactory<IndexQuery> getQueryFactory(
ExtensibleMatchingRule rule)
{
return rule2FactoryMap.get(rule);
}
/**
* Deletes extensible matching rules from the list of available rules.
* @param indexID The name of the index corresponding to the rules.
*/
private void deleteRule(String indexID)
{
Set<ExtensibleMatchingRule> rules = id2RulesMap.get(indexID);
for (ExtensibleMatchingRule rule : rules)
{
rule2FactoryMap.remove(rule);
}
rules.clear();
id2RulesMap.remove(indexID);
}
/**
* Deletes all references to matching rules and the indexes.
*/
private void deleteAll()
{
id2IndexMap.clear();
id2RulesMap.clear();
rule2FactoryMap.clear();
}
}
/**
* This class extends the IndexConfig for JE Backend.
*/
private class JEIndexConfig extends IndexConfig
{
//The length of the substring index.
private int substringLength;
/**
* Creates a new JEIndexConfig instance.
* @param substringLength The length of the substring.
*/
private JEIndexConfig(int substringLength)
{
this.substringLength = substringLength;
}
/**
* Returns the length of the substring.
* @return the length of the substring.
*/
public int getSubstringLength()
{
return substringLength;
}
}
}