/* * 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 2008-2009 Sun Microsystems, Inc. * Portions Copyright 2012 ForgeRock AS */ package org.opends.server.schema; import static org.opends.messages.ConfigMessages.*; import static org.opends.messages.CoreMessages.*; import static org.opends.messages.SchemaMessages.*; import static org.opends.server.loggers.ErrorLogger.*; import static org.opends.server.schema.SchemaConstants.*; import static org.opends.server.util.ServerConstants.*; import static org.opends.server.util.StaticUtils.*; import java.nio.CharBuffer; import java.text.CollationKey; import java.text.Collator; import java.util.*; import org.opends.messages.Message; import org.opends.server.admin.server.ConfigurationChangeListener; import org.opends.server.admin.std.meta. CollationMatchingRuleCfgDefn.MatchingRuleType; import org.opends.server.admin.std.server.CollationMatchingRuleCfg; import org.opends.server.api.AbstractMatchingRule; import org.opends.server.api.ExtensibleIndexer; import org.opends.server.api.ExtensibleMatchingRule; import org.opends.server.api.IndexQueryFactory; import org.opends.server.api.MatchingRule; import org.opends.server.api.MatchingRuleFactory; import org.opends.server.api.OrderingMatchingRule; import org.opends.server.backends.jeb.AttributeIndex; import org.opends.server.config.ConfigException; import org.opends.server.core.DirectoryServer; import org.opends.server.types.AttributeValue; import org.opends.server.types.ByteSequence; import org.opends.server.types.ByteString; import org.opends.server.types.ConditionResult; import org.opends.server.types.ConfigChangeResult; import org.opends.server.types.DirectoryException; import org.opends.server.types.IndexConfig; import org.opends.server.types.InitializationException; import org.opends.server.types.ResultCode; import org.opends.server.util.StaticUtils; /** * This class is a factory class for Collation matching rules. It * creates different matching rules based on the configuration entries. */ public final class CollationMatchingRuleFactory extends MatchingRuleFactory<CollationMatchingRuleCfg> implements ConfigurationChangeListener<CollationMatchingRuleCfg> { // Whether equality matching rules are enabled. private boolean equalityMatchingRuleType; // Whether less-than matching rules are enabled. private boolean lessThanMatchingRuleType; // Whether less-than-equal-to matching rules are enabled. private boolean lessThanEqualToMatchingRuleType; // Whether less-than-equal-to matching rules are enabled. private boolean greaterThanMatchingRuleType; // Whether greater-than matching rules are enabled. private boolean greaterThanEqualToMatchingRuleType; // Whether greater-than-equal-to matching rules are enabled. private boolean substringMatchingRuleType; // Stores the list of available locales on this JVM. private static final Set<Locale> supportedLocales; // Current Configuration. private CollationMatchingRuleCfg currentConfig; // Map of OID and the Matching Rule. private final Map<String, MatchingRule> matchingRules; static { supportedLocales = new HashSet<Locale>(); supportedLocales.addAll(Arrays.asList(Locale.getAvailableLocales())); } /** * Creates a new instance of CollationMatchingRuleFactory. */ public CollationMatchingRuleFactory() { // Initialize the matchingRules. matchingRules = new HashMap<String, MatchingRule>(); } /** * {@inheritDoc} */ @Override public final Collection<MatchingRule> getMatchingRules() { return Collections.unmodifiableCollection(matchingRules.values()); } /** * Adds a new mapping of OID and MatchingRule. * * @param oid * OID of the matching rule * @param matchingRule * instance of a MatchingRule. */ private void addMatchingRule(String oid, MatchingRule matchingRule) { matchingRules.put(oid, matchingRule); } /** * Returns the Matching rule for the specified OID. * * @param oid * OID of the matching rule to be searched. * @return MatchingRule corresponding to an OID. */ private MatchingRule getMatchingRule(String oid) { return matchingRules.get(oid); } /** * Clears the Map containing matching Rules. */ private void resetRules() { matchingRules.clear(); } /** * Reads the configuration and initializes matching rule types. * * @param ruleTypes * The Set containing allowed matching rule types. */ private void initializeMatchingRuleTypes( SortedSet<MatchingRuleType> ruleTypes) { for (MatchingRuleType type : ruleTypes) { switch (type) { case EQUALITY: equalityMatchingRuleType = true; break; case LESS_THAN: lessThanMatchingRuleType = true; break; case LESS_THAN_OR_EQUAL_TO: lessThanEqualToMatchingRuleType = true; break; case GREATER_THAN: greaterThanMatchingRuleType = true; break; case GREATER_THAN_OR_EQUAL_TO: greaterThanEqualToMatchingRuleType = true; break; case SUBSTRING: substringMatchingRuleType = true; break; default: // No default values allowed. } } } /** * Creates a new Collator instance. * * @param locale * Locale for the collator * @return Returns a new Collator instance */ private Collator createCollator(Locale locale) { Collator collator = Collator.getInstance(locale); collator.setStrength(Collator.PRIMARY); collator.setDecomposition(Collator.FULL_DECOMPOSITION); return collator; } /** * {@inheritDoc} */ @Override public void initializeMatchingRule( CollationMatchingRuleCfg configuration) throws ConfigException, InitializationException { initializeMatchingRuleTypes(configuration.getMatchingRuleType()); for (String collation : configuration.getCollation()) { CollationMapper mapper = new CollationMapper(collation); String nOID = mapper.getNumericOID(); String languageTag = mapper.getLanguageTag(); if (nOID == null || languageTag == null) { Message msg = WARN_ATTR_INVALID_COLLATION_MATCHING_RULE_FORMAT .get(collation); logError(msg); continue; } Locale locale = getLocale(languageTag); if (locale != null) { createLessThanMatchingRule(mapper, locale); createLessThanOrEqualToMatchingRule(mapper, locale); createEqualityMatchingRule(mapper, locale); createGreaterThanOrEqualToMatchingRule(mapper, locale); createGreaterThanMatchingRule(mapper, locale); createSubstringMatchingRule(mapper, locale); } else { // This locale is not supported by JVM. Message msg = WARN_ATTR_INVALID_COLLATION_MATCHING_RULE_LOCALE.get( collation, configuration.dn().toNormalizedString(), languageTag); logError(msg); } } // Save this configuration. currentConfig = configuration; // Register for change events. currentConfig.addCollationChangeListener(this); } /** * {@inheritDoc} */ @Override public void finalizeMatchingRule() { // De-register the listener. currentConfig.removeCollationChangeListener(this); } /** * {@inheritDoc} */ @Override public ConfigChangeResult applyConfigurationChange( CollationMatchingRuleCfg configuration) { ResultCode resultCode = ResultCode.SUCCESS; boolean adminActionRequired = false; ArrayList<Message> messages = new ArrayList<Message>(); if (!configuration.isEnabled() || currentConfig.isEnabled() != configuration.isEnabled()) { // Don't do anything if: // 1. The configuration is disabled. // 2. There is a change in the enable status // i.e. (disable->enable or enable->disable). In this case, the // ConfigManager will have already created the new Factory object. return new ConfigChangeResult(resultCode, adminActionRequired, messages); } // Since we have come here it means that this Factory is enabled and // there is a change in the CollationMatchingRuleFactory's // configuration. // Deregister all the Matching Rule corresponding to this factory. for (MatchingRule rule : getMatchingRules()) { DirectoryServer.deregisterMatchingRule(rule); } // Clear the associated matching rules. resetRules(); initializeMatchingRuleTypes(configuration.getMatchingRuleType()); for (String collation : configuration.getCollation()) { CollationMapper mapper = new CollationMapper(collation); String languageTag = mapper.getLanguageTag(); Locale locale = getLocale(languageTag); createLessThanMatchingRule(mapper, locale); createLessThanOrEqualToMatchingRule(mapper, locale); createEqualityMatchingRule(mapper, locale); createGreaterThanOrEqualToMatchingRule(mapper, locale); createGreaterThanMatchingRule(mapper, locale); createSubstringMatchingRule(mapper, locale); } try { for (MatchingRule matchingRule : getMatchingRules()) { DirectoryServer.registerMatchingRule(matchingRule, false); } } catch (DirectoryException de) { Message message = WARN_CONFIG_SCHEMA_MR_CONFLICTING_MR.get(String .valueOf(configuration.dn()), de.getMessageObject()); adminActionRequired = true; messages.add(message); } currentConfig = configuration; return new ConfigChangeResult(resultCode, adminActionRequired, messages); } /** * {@inheritDoc} */ @Override public boolean isConfigurationChangeAcceptable( CollationMatchingRuleCfg configuration, List<Message> unacceptableReasons) { boolean configAcceptable = true; // If the new configuration disables this factory, don't do // anything. if (!configuration.isEnabled()) { return configAcceptable; } // If it comes here we don't need to verify MatchingRuleType; it // should be okay as its syntax is verified by the admin framework. // Iterate over the collations and verify if the format is okay. // Also, // verify if the locale is allowed by the JVM. for (String collation : configuration.getCollation()) { CollationMapper mapper = new CollationMapper(collation); String nOID = mapper.getNumericOID(); String languageTag = mapper.getLanguageTag(); if (nOID == null || languageTag == null) { configAcceptable = false; Message msg = WARN_ATTR_INVALID_COLLATION_MATCHING_RULE_FORMAT .get(collation); unacceptableReasons.add(msg); continue; } Locale locale = getLocale(languageTag); if (locale == null) { Message msg = WARN_ATTR_INVALID_COLLATION_MATCHING_RULE_LOCALE.get( collation, configuration.dn().toNormalizedString(), languageTag); unacceptableReasons.add(msg); configAcceptable = false; continue; } } return configAcceptable; } /** * Creates Less-than Matching Rule. * * @param mapper * CollationMapper containing OID and the language Tag. * @param locale * Locale value */ private void createLessThanMatchingRule(CollationMapper mapper, Locale locale) { if (!lessThanMatchingRuleType) return; String oid = mapper.getNumericOID() + ".1"; String lTag = mapper.getLanguageTag(); Collection<String> names = new HashSet<String>(); MatchingRule matchingRule = getMatchingRule(oid); if (matchingRule != null) { for (String name : matchingRule.getAllNames()) { names.add(name); } } names.add(lTag + ".lt"); names.add(lTag + ".1"); matchingRule = new CollationLessThanMatchingRule(oid, names, locale); addMatchingRule(oid, matchingRule); } /** * Creates Less-Than-Equal-To Matching Rule. * * @param mapper * CollationMapper containing OID and the language Tag. * @param locale * Locale value */ private void createLessThanOrEqualToMatchingRule( CollationMapper mapper, Locale locale) { if (!lessThanEqualToMatchingRuleType) return; String oid = mapper.getNumericOID() + ".2"; String lTag = mapper.getLanguageTag(); Collection<String> names = new HashSet<String>(); MatchingRule matchingRule = getMatchingRule(oid); if (matchingRule != null) { for (String name : matchingRule.getAllNames()) { names.add(name); } } names.add(lTag + ".lte"); names.add(lTag + ".2"); matchingRule = new CollationLessThanOrEqualToMatchingRule(oid, names, locale); addMatchingRule(oid, matchingRule); } /** * Creates Equality Matching Rule. * * @param mapper * CollationMapper containing OID and the language Tag. * @param locale * Locale value */ private void createEqualityMatchingRule(CollationMapper mapper, Locale locale) { if (!equalityMatchingRuleType) { return; } // Register the default OID as equality matching rule. String lTag = mapper.getLanguageTag(); String nOID = mapper.getNumericOID(); MatchingRule matchingRule = getMatchingRule(nOID); Collection<String> defaultNames = new HashSet<String>(); if (matchingRule != null) { for (String name : matchingRule.getAllNames()) { defaultNames.add(name); } } defaultNames.add(lTag); matchingRule = new CollationEqualityMatchingRule(nOID, defaultNames, locale); addMatchingRule(nOID, matchingRule); Collection<String> names = new HashSet<String>(); // Register OID.3 as the equality matching rule. String OID = mapper.getNumericOID() + ".3"; MatchingRule equalityMatchingRule = getMatchingRule(OID); if (equalityMatchingRule != null) { for (String name : equalityMatchingRule.getAllNames()) { names.add(name); } } names.add(lTag + ".eq"); names.add(lTag + ".3"); equalityMatchingRule = new CollationEqualityMatchingRule(OID, names, locale); addMatchingRule(OID, equalityMatchingRule); } /** * Creates Greater-than-equal-to Matching Rule. * * @param mapper * CollationMapper containing OID and the language Tag. * @param locale * Locale value */ private void createGreaterThanOrEqualToMatchingRule( CollationMapper mapper, Locale locale) { if (!greaterThanEqualToMatchingRuleType) return; String oid = mapper.getNumericOID() + ".4"; String lTag = mapper.getLanguageTag(); Collection<String> names = new HashSet<String>(); MatchingRule matchingRule = getMatchingRule(oid); if (matchingRule != null) { for (String name : matchingRule.getAllNames()) { names.add(name); } } names.add(lTag + ".gte"); names.add(lTag + ".4"); matchingRule = new CollationGreaterThanOrEqualToMatchingRule(oid, names, locale); addMatchingRule(oid, matchingRule); } /** * Creates Greater-than Matching Rule. * * @param mapper * CollationMapper containing OID and the language Tag. * @param locale * Locale value */ private void createGreaterThanMatchingRule(CollationMapper mapper, Locale locale) { if (!greaterThanMatchingRuleType) return; String oid = mapper.getNumericOID() + ".5"; String lTag = mapper.getLanguageTag(); Collection<String> names = new HashSet<String>(); MatchingRule matchingRule = getMatchingRule(oid); if (matchingRule != null) { for (String name : matchingRule.getAllNames()) { names.add(name); } } names.add(lTag + ".gt"); names.add(lTag + ".5"); matchingRule = new CollationGreaterThanMatchingRule(oid, names, locale); addMatchingRule(oid, matchingRule); } /** * Creates substring Matching Rule. * * @param mapper * CollationMapper containing OID and the language Tag. * @param locale * Locale value */ private void createSubstringMatchingRule(CollationMapper mapper, Locale locale) { if (!substringMatchingRuleType) return; String oid = mapper.getNumericOID() + ".6"; String lTag = mapper.getLanguageTag(); Collection<String> names = new HashSet<String>(); MatchingRule matchingRule = getMatchingRule(oid); if (matchingRule != null) { for (String name : matchingRule.getAllNames()) { names.add(name); } } names.add(lTag + ".sub"); names.add(lTag + ".6"); matchingRule = new CollationSubstringMatchingRule(oid, names, locale); addMatchingRule(oid, matchingRule); } /** * Verifies if the locale is supported by the JVM. * * @param lTag * The language tag specified in the configuration. * @return Locale The locale corresponding to the languageTag. */ private Locale getLocale(String lTag) { // Separates the language and the country from the locale. Locale locale; int countryIndex = lTag.indexOf("-"); int variantIndex = lTag.lastIndexOf("-"); if (countryIndex > 0) { String lang = lTag.substring(0, countryIndex); String country; if (variantIndex > countryIndex) { country = lTag.substring(countryIndex + 1, variantIndex); String variant = lTag.substring(variantIndex + 1, lTag.length()); locale = new Locale(lang, country, variant); } else { country = lTag.substring(countryIndex + 1, lTag.length()); locale = new Locale(lang, country); } } else { locale = new Locale(lTag); } if (!supportedLocales.contains(locale)) { // This locale is not supported by this JVM. locale = null; } return locale; } /** * Collation Extensible matching rule. */ private abstract class CollationMatchingRule extends AbstractMatchingRule implements ExtensibleMatchingRule { // Names for this class. private final Collection<String> names; // Collator for performing equality match. protected final Collator collator; // Numeric OID of the rule. private final String nOID; // Locale associated with this rule. private final Locale locale; // Indexer of this rule. protected ExtensibleIndexer indexer; /** * Constructs a new CollationMatchingRule. * * @param nOID * OID of the collation matching rule * @param names * names of this matching rule * @param locale * Locale of the collation matching rule */ private CollationMatchingRule(String nOID, Collection<String> names, Locale locale) { this.names = names; this.collator = createCollator(locale); this.locale = locale; this.nOID = nOID; } /** * {@inheritDoc} */ @Override public String getName() { //This is called when there is only 1 name. return names.iterator().next(); } /** * {@inheritDoc} */ @Override public Collection<String> getAllNames() { return Collections.unmodifiableCollection(names); } /** * {@inheritDoc} */ @Override public String getOID() { return nOID; } /** * {@inheritDoc} */ @Override public String getDescription() { // There is no standard description for this matching rule. return null; } /** * {@inheritDoc} */ @Override public String getSyntaxOID() { return SYNTAX_DIRECTORY_STRING_OID; } /** * Returns the name of the index database for this matching rule. An * index name for this rule will be based upon the Locale. This will * ensure that multiple collation matching rules corresponding to * the same Locale can share the same index database. * * @return The name of the index for this matching rule. */ public String getIndexName() { String language = locale.getLanguage(); String country = locale.getCountry(); String variant = locale.getVariant(); StringBuilder builder = new StringBuilder(language); if (country != null && country.length() > 0) { builder.append("_"); builder.append(locale.getCountry()); } if (variant != null && variant.length() > 0) { builder.append("_"); builder.append(locale.getVariant()); } return builder.toString(); } /** * {@inheritDoc} */ @Override public Collection<ExtensibleIndexer> getIndexers(IndexConfig config) { if (indexer == null) { // The default implementation contains shared indexer and // doesn't use the config. indexer = new CollationSharedExtensibleIndexer(this); } return Collections.singletonList(indexer); } } /** * Collation rule for Equality matching rule. */ private final class CollationEqualityMatchingRule extends CollationMatchingRule implements OrderingMatchingRule { /** * The serial version identifier required to satisfy the compiler because * this class implements the <CODE>java.io.Serializable</CODE> interface. * This value was generated using the <CODE>serialver</CODE> command-line * utility included with the Java SDK. */ private static final long serialVersionUID = 3990778178484159862L; /** * Constructs a new CollationEqualityMatchingRule. * * @param nOID * OID of the collation matching rule * @param names * names of this matching rule * @param locale * Locale of the collation matching rule */ private CollationEqualityMatchingRule(String nOID, Collection<String> names, Locale locale) { super(nOID, names, locale); } /** * {@inheritDoc} */ @Override public ByteString normalizeValue(ByteSequence value) throws DirectoryException { CollationKey key = collator.getCollationKey(value.toString()); return ByteString.wrap(key.toByteArray()); } /** * {@inheritDoc} */ @Override public ConditionResult valuesMatch(ByteSequence attributeValue, ByteSequence assertionValue) { if (assertionValue.equals(attributeValue)) { return ConditionResult.TRUE; } else { return ConditionResult.FALSE; } } /** * {@inheritDoc} */ @Override public <T> T createIndexQuery(ByteSequence assertionValue, IndexQueryFactory<T> factory) throws DirectoryException { // Normalize the assertion value. return factory.createExactMatchQuery(indexer .getExtensibleIndexID(), normalizeValue(assertionValue)); } /** * {@inheritDoc} */ @Override public int compare(byte[] arg0, byte[] arg1) { return StaticUtils.compare(arg0, arg1); } /** * {@inheritDoc} */ public int compareValues(ByteSequence value1, ByteSequence value2) { return value1.compareTo(value2); } } /** * Collation rule for Substring matching rule. */ private final class CollationSubstringMatchingRule extends CollationMatchingRule { // Substring Indexer associated with this instance. private CollationSubstringExtensibleIndexer subIndexer; /** * Constructs a new CollationSubstringMatchingRule. * * @param nOID * OID of the collation matching rule * @param names * names of this matching rule * @param locale * Locale of the collation matching rule */ private CollationSubstringMatchingRule(String nOID, Collection<String> names, Locale locale) { super(nOID, names, locale); } /** * {@inheritDoc} */ @Override public ByteString normalizeValue(ByteSequence value) throws DirectoryException { CollationKey key = collator.getCollationKey(value.toString()); return ByteString.wrap(key.toByteArray()); } /** * Utility class which abstracts a substring assertion value. */ private final class Assertion { // Initial part of the substring filter. private String subInitial; // any parts of the substring filter. private List<String> subAny; // Final part of the substring filter. private String subFinal; /** * Creates a new instance of Assertion. * * @param subInitial * Initial part of the filter. * @param subAny * Any part of the filter. * @param subFinal * Final part of the filter. */ private Assertion(String subInitial, List<String> subAny, String subFinal) { this.subInitial = subInitial; this.subAny = subAny; this.subFinal = subFinal; } /** * Returns the Initial part of the assertion. * * @return Initial part of assertion. */ private String getInitial() { return subInitial; } /** * Returns the any part of the assertion. * * @return Any part of the assertion. */ private List<String> getAny() { return subAny; } /** * Returns the final part of the assertion. * * @return Final part of the assertion. */ private String getFinal() { return subFinal; } } /** * Parses the assertion from a given value. * * @param value * The value that needs to be parsed. * @return The parsed Assertion object containing the * @throws org.opends.server.types.DirectoryException */ private Assertion parseAssertion(ByteSequence value) throws DirectoryException { // Get a string representation of the value. String filterString = value.toString(); int endPos = filterString.length(); // Find the locations of all the asterisks in the value. Also, // check to see if there are any escaped values, since they will // need special treatment. boolean hasEscape = false; LinkedList<Integer> asteriskPositions = new LinkedList<Integer>(); for (int i = 0; i < endPos; i++) { if (filterString.charAt(i) == 0x2A) // The asterisk. { asteriskPositions.add(i); } else if (filterString.charAt(i) == 0x5C) // The backslash. { hasEscape = true; } } // If there were no asterisks, then this isn't a substring filter. if (asteriskPositions.isEmpty()) { Message message = ERR_SEARCH_FILTER_SUBSTRING_NO_ASTERISKS.get(filterString, 0, endPos); throw new DirectoryException(ResultCode.PROTOCOL_ERROR, message); } // If the value starts with an asterisk, then there is no // subInitial component. Otherwise, parse out the subInitial. String subInitial; int firstPos = asteriskPositions.removeFirst(); if (firstPos == 0) { subInitial = null; } else { if (hasEscape) { CharBuffer buffer = CharBuffer.allocate(firstPos); for (int i = 0; i < firstPos; i++) { if (filterString.charAt(i) == 0x5C) { char escapeValue = hexToEscapedChar(filterString, i + 1); i += 2; // Move to the next sequence. buffer.put(escapeValue); } else { buffer.put(filterString.charAt(i)); } } char[] subInitialChars = new char[buffer.position()]; buffer.flip(); buffer.get(subInitialChars); subInitial = new String(subInitialChars); } else { subInitial = filterString.substring(0, firstPos); } } // Next, process through the rest of the asterisks to get the // subAny values. List<String> subAny = new ArrayList<String>(); for (int asteriskPos : asteriskPositions) { int length = asteriskPos - firstPos - 1; if (hasEscape) { CharBuffer buffer = CharBuffer.allocate(length); for (int i = firstPos + 1; i < asteriskPos; i++) { if (filterString.charAt(i) == 0x5C) { char escapeValue = hexToEscapedChar(filterString, i + 1); i += 2; // Move to the next sequence. buffer.put(escapeValue); } else { buffer.put(filterString.charAt(i)); } } char[] subAnyChars = new char[buffer.position()]; buffer.flip(); buffer.get(subAnyChars); subAny.add(new String(subAnyChars)); } else { subAny.add(filterString.substring(firstPos + 1, firstPos + length + 1)); } firstPos = asteriskPos; } // Finally, see if there is anything after the last asterisk, // which would be the subFinal value. String subFinal; if (firstPos == (endPos - 1)) { subFinal = null; } else { int length = endPos - firstPos - 1; if (hasEscape) { CharBuffer buffer = CharBuffer.allocate(length); for (int i = firstPos + 1; i < endPos; i++) { if (filterString.charAt(i) == 0x5C) { char escapeValue = hexToEscapedChar(filterString, i + 1); i += 2; // Move to the next sequence. buffer.put(escapeValue); } else { buffer.put(filterString.charAt(i)); } } char[] subFinalChars = new char[buffer.position()]; buffer.flip(); buffer.get(subFinalChars); subFinal = new String(subFinalChars); } else { subFinal = filterString.substring(firstPos + 1, length + firstPos + 1); } } return new Assertion(subInitial, subAny, subFinal); } /** * {@inheritDoc} */ @Override public ByteString normalizeAssertionValue(ByteSequence value) throws DirectoryException { Assertion assertion = parseAssertion(value); String subInitial = assertion.getInitial(); // Normalize the Values in the following format: // initialLength, initial, numberofany, anyLength1, any1, // anyLength2, any2, ..., anyLengthn, anyn, finalLength, // final CollationKey key; List<Integer> normalizedList = new ArrayList<Integer>(); if (subInitial == null) { normalizedList.add(0); } else { key = collator.getCollationKey(subInitial); byte[] initialBytes = key.toByteArray(); // Last 4 bytes are 0s with PRIMARY strength. int length = initialBytes.length - 4; normalizedList.add(length); for (int i = 0; i < length; i++) { normalizedList.add((int) initialBytes[i]); } } List<String> subAny = assertion.getAny(); if (subAny.isEmpty()) { normalizedList.add(0); } else { normalizedList.add(subAny.size()); for (String any : subAny) { key = collator.getCollationKey(any); byte[] anyBytes = key.toByteArray(); int length = anyBytes.length - 4; normalizedList.add(length); for (int i = 0; i < length; i++) { normalizedList.add((int) anyBytes[i]); } } } String subFinal = assertion.getFinal(); if (subFinal == null) { normalizedList.add(0); } else { key = collator.getCollationKey(subFinal); byte[] subFinalBytes = key.toByteArray(); int length = subFinalBytes.length - 4; normalizedList.add(length); for (int i = 0; i < length; i++) { normalizedList.add((int) subFinalBytes[i]); } } byte[] normalizedBytes = new byte[normalizedList.size()]; for (int i = 0; i < normalizedList.size(); i++) { normalizedBytes[i] = normalizedList.get(i).byteValue(); } return ByteString.wrap(normalizedBytes); } /** * {@inheritDoc} */ @Override public ConditionResult valuesMatch(ByteSequence attributeValue, ByteSequence assertionValue) { int valueLength = attributeValue.length() - 4; int valuePos = 0; // position in the value bytes array. // First byte is the length of subInitial. int subInitialLength = 0xFF & assertionValue.byteAt(0); if (subInitialLength != 0) { if (subInitialLength > valueLength) { return ConditionResult.FALSE; } for (; valuePos < subInitialLength; valuePos++) { if (attributeValue.byteAt(valuePos) != assertionValue .byteAt(valuePos + 1)) { return ConditionResult.FALSE; } } } int assertPos = subInitialLength + 1; int anySize = 0xFF & assertionValue.byteAt(assertPos++); if (anySize != 0) { while (anySize-- > 0) { int anyLength = 0xFF & assertionValue.byteAt(assertPos++); int end = valueLength - anyLength; boolean match = false; for (; valuePos <= end; valuePos++) { if (assertionValue.byteAt(assertPos) == attributeValue .byteAt(valuePos)) { boolean subMatch = true; for (int i = 1; i < anyLength; i++) { if (assertionValue.byteAt(assertPos + i) != attributeValue .byteAt(valuePos + i)) { subMatch = false; break; } } if (subMatch) { match = subMatch; break; } } } if (match) { valuePos += anyLength; } else { return ConditionResult.FALSE; } assertPos = assertPos + anyLength; } } int finalLength = 0xFF & assertionValue.byteAt(assertPos++); if (finalLength != 0) { if ((valueLength - finalLength) < valuePos) { return ConditionResult.FALSE; } if (finalLength != assertionValue.length() - assertPos) { // Some issue with the encoding. return ConditionResult.FALSE; } valuePos = valueLength - finalLength; for (int i = 0; i < finalLength; i++, valuePos++) { if (assertionValue.byteAt(assertPos + i) != attributeValue .byteAt(valuePos)) { return ConditionResult.FALSE; } } } return ConditionResult.TRUE; } /** * {@inheritDoc} */ @Override public final Collection<ExtensibleIndexer> getIndexers( IndexConfig config) { Collection<ExtensibleIndexer> indexers = new ArrayList<ExtensibleIndexer>(); int substrLength = 6; // Default substring length; if (subIndexer == null) { if (config != null) { substrLength = config.getSubstringLength(); } subIndexer = new CollationSubstringExtensibleIndexer(this, substrLength); } else { if (config != null) { if (config.getSubstringLength() != subIndexer .gerSubstringLength()) { subIndexer.setSubstringLength(substrLength); } } } if (indexer == null) { indexer = new CollationSharedExtensibleIndexer(this); } indexers.add(subIndexer); indexers.add(indexer); return indexers; } /** * Decomposes an attribute value into a set of substring index keys. * * @param attValue * The normalized attribute value * @param set * A set into which the keys will be inserted. */ private void subtringKeys(ByteString attValue, Set<byte[]> keys) { String value = attValue.toString(); int keyLength = subIndexer.gerSubstringLength(); for (int i = 0, remain = value.length(); remain > 0; i++, remain--) { int len = Math.min(keyLength, remain); byte[] keyBytes = makeSubstringKey(value, i, len); keys.add(keyBytes); } } /** * Decomposes an attribute value into a set of substring index keys. * * @param value * The normalized attribute value * @param modifiedKeys * The map into which the modified keys will be inserted. * @param insert * <code>true</code> if generated keys should be inserted * or <code>false</code> otherwise. */ private void substringKeys(ByteString attValue, Map<byte[], Boolean> modifiedKeys, Boolean insert) { String value = attValue.toString(); int keyLength = subIndexer.gerSubstringLength(); for (int i = 0, remain = value.length(); remain > 0; i++, remain--) { int len = Math.min(keyLength, remain); byte[] keyBytes = makeSubstringKey(value, i, len); Boolean cinsert = modifiedKeys.get(keyBytes); if (cinsert == null) { modifiedKeys.put(keyBytes, insert); } else if (!cinsert.equals(insert)) { modifiedKeys.remove(keyBytes); } } } /** * Makes a byte array representing a substring index key for one * substring of a value. * * @param value * The String 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(String value, int pos, int len) { String sub = value.substring(pos, pos + len); CollationKey col = collator.getCollationKey(sub); byte[] origKey = col.toByteArray(); byte[] newKey = new byte[origKey.length - 4]; System.arraycopy(origKey, 0, newKey, 0, newKey.length); return newKey; } /** * 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 <T> T matchInitialSubstring(String value, IndexQueryFactory<T> factory) { byte[] lower = makeSubstringKey(value, 0, value.length()); byte[] upper = new byte[lower.length]; System.arraycopy(lower, 0, upper, 0, lower.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; } } // Use the shared equality indexer. return factory.createRangeMatchQuery(indexer .getExtensibleIndexID(), ByteString.wrap(lower), ByteString .wrap(upper), true, false); } /** * Retrieves the Index Records that might contain a given substring. * * @param value * A String representing the attribute value. * @param factory * An IndexQueryFactory which issues calls to the backend. * @param substrLength * The length of the substring. * @return The candidate entry IDs. */ private <T> T matchSubstring(String value, IndexQueryFactory<T> factory) { T intersectionQuery; int substrLength = subIndexer.gerSubstringLength(); if (value.length() < substrLength) { byte[] lower = makeSubstringKey(value, 0, value.length()); byte[] upper = makeSubstringKey(value, 0, value.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. intersectionQuery = factory.createRangeMatchQuery(subIndexer .getExtensibleIndexID(), ByteString.wrap(lower), ByteString.wrap(upper), true, false); } else { List<T> queryList = new ArrayList<T>(); Set<byte[]> set = new TreeSet<byte[]>(new AttributeIndex.KeyComparator()); for (int first = 0, last = substrLength; last <= value.length(); first++, last++) { byte[] keyBytes; keyBytes = makeSubstringKey(value, first, substrLength); set.add(keyBytes); } for (byte[] keyBytes : set) { T single = factory.createExactMatchQuery(subIndexer .getExtensibleIndexID(), ByteString.wrap(keyBytes)); queryList.add(single); } intersectionQuery = factory.createIntersectionQuery(queryList); } return intersectionQuery; } /** * {@inheritDoc} */ @Override public <T> T createIndexQuery(ByteSequence assertionValue, IndexQueryFactory<T> factory) throws DirectoryException { Assertion assertion = parseAssertion(assertionValue); String subInitial = assertion.getInitial(); List<String> subAny = assertion.getAny(); String subFinal = assertion.getFinal(); List<T> queries = new ArrayList<T>(); if (subInitial == null && subAny.isEmpty() && subFinal == null) { // Can happen with a filter like "cn:en.6:=*". // Just return an empty record. return factory.createMatchAllQuery(); } List<String> elements = new ArrayList<String>(); if (subInitial != null) { // Always use the shared indexer for initial match. T query = matchInitialSubstring(subInitial, factory); queries.add(query); } if (subAny != null && subAny.size() > 0) { elements.addAll(subAny); } if (subFinal != null) { elements.add(subFinal); } for (String element : elements) { queries.add(matchSubstring(element, factory)); } return factory.createIntersectionQuery(queries); } } /** * An abstract Collation rule for Ordering matching rule. */ private abstract class CollationOrderingMatchingRule extends CollationMatchingRule implements OrderingMatchingRule { /** * The serial version identifier required to satisfy the compiler because * this class implements the <CODE>java.io.Serializable</CODE> interface. * This value was generated using the <CODE>serialver</CODE> command-line * utility included with the Java SDK. */ private static final long serialVersionUID = 7354051060508436941L; /** * Constructs a new CollationOrderingMatchingRule. * * @param nOID * OID of the collation matching rule * @param names * names of this matching rule * @param locale * Locale of the collation matching rule */ private CollationOrderingMatchingRule(String nOID, Collection<String> names, Locale locale) { super(nOID, names, locale); } /** * {@inheritDoc} */ @Override public ByteString normalizeValue(ByteSequence value) throws DirectoryException { CollationKey key = collator.getCollationKey(value.toString()); return ByteString.wrap(key.toByteArray()); } /** * {@inheritDoc} */ @Override public int compare(byte[] arg0, byte[] arg1) { return StaticUtils.compare(arg0, arg1); } /** * {@inheritDoc} */ @Override public int compareValues(ByteSequence value1, ByteSequence value2) { return value1.compareTo(value2); } } /** * Collation matching rule for Less-than matching rule. */ private final class CollationLessThanMatchingRule extends CollationOrderingMatchingRule { /** * The serial version identifier required to satisfy the compiler because * this class implements the <CODE>java.io.Serializable</CODE> interface. * This value was generated using the <CODE>serialver</CODE> command-line * utility included with the Java SDK. */ private static final long serialVersionUID = -7578406829946732713L; /** * Constructs a new CollationLessThanMatchingRule. * * @param nOID * OID of the collation matching rule * @param names * names of this matching rule * @param locale * Locale of the collation matching rule */ private CollationLessThanMatchingRule(String nOID, Collection<String> names, Locale locale) { super(nOID, names, locale); } /** * {@inheritDoc} */ @Override public ConditionResult valuesMatch(ByteSequence attributeValue, ByteSequence assertionValue) { int ret = attributeValue.compareTo(assertionValue); if (ret < 0) { return ConditionResult.TRUE; } else { return ConditionResult.FALSE; } } /** * {@inheritDoc} */ @Override public <T> T createIndexQuery(ByteSequence assertionValue, IndexQueryFactory<T> factory) throws DirectoryException { return factory.createRangeMatchQuery(indexer .getExtensibleIndexID(), ByteString.empty(), normalizeValue(assertionValue), false, false); } } /** * Collation rule for less-than-equal-to matching rule. */ private final class CollationLessThanOrEqualToMatchingRule extends CollationOrderingMatchingRule { /** * The serial version identifier required to satisfy the compiler because * this class implements the <CODE>java.io.Serializable</CODE> interface. * This value was generated using the <CODE>serialver</CODE> command-line * utility included with the Java SDK. */ private static final long serialVersionUID = 7222067708233629974L; /** * Constructs a new CollationLessThanOrEqualToMatchingRule. * * @param nOID * OID of the collation matching rule * @param names * names of this matching rule * @param locale * Locale of the collation matching rule */ private CollationLessThanOrEqualToMatchingRule(String nOID, Collection<String> names, Locale locale) { super(nOID, names, locale); } /** * {@inheritDoc} */ @Override public ConditionResult valuesMatch(ByteSequence attributeValue, ByteSequence assertionValue) { int ret = attributeValue.compareTo(assertionValue); if (ret <= 0) { return ConditionResult.TRUE; } else { return ConditionResult.FALSE; } } /** * {@inheritDoc} */ @Override public <T> T createIndexQuery(ByteSequence assertionValue, IndexQueryFactory<T> factory) throws DirectoryException { // Read the range: lower < keys <= upper. return factory.createRangeMatchQuery(indexer .getExtensibleIndexID(), ByteString.empty(), normalizeValue(assertionValue), false, true); } } /** * Collation rule for greater-than matching rule. */ private final class CollationGreaterThanMatchingRule extends CollationOrderingMatchingRule { /** * The serial version identifier required to satisfy the compiler because * this class implements the <CODE>java.io.Serializable</CODE> interface. * This value was generated using the <CODE>serialver</CODE> command-line * utility included with the Java SDK. */ private static final long serialVersionUID = 1204368277332957024L; /** * Constructs a new CollationGreaterThanMatchingRule. * * @param nOID * OID of the collation matching rule * @param names * names of this matching rule * @param locale * Locale of the collation matching rule */ private CollationGreaterThanMatchingRule(String nOID, Collection<String> names, Locale locale) { super(nOID, names, locale); } /** * {@inheritDoc} */ @Override public ConditionResult valuesMatch(ByteSequence attributeValue, ByteSequence assertionValue) { int ret = attributeValue.compareTo(assertionValue); if (ret > 0) { return ConditionResult.TRUE; } else { return ConditionResult.FALSE; } } /** * {@inheritDoc} */ @Override public <T> T createIndexQuery(ByteSequence assertionValue, IndexQueryFactory<T> factory) throws DirectoryException { return factory.createRangeMatchQuery(indexer .getExtensibleIndexID(), normalizeValue(assertionValue), ByteString.empty(), false, false); } } /** * Collation rule for greater-than-equal-to matching rule. */ private final class CollationGreaterThanOrEqualToMatchingRule extends CollationOrderingMatchingRule { /** * The serial version identifier required to satisfy the compiler because * this class implements the <CODE>java.io.Serializable</CODE> interface. * This value was generated using the <CODE>serialver</CODE> command-line * utility included with the Java SDK. */ private static final long serialVersionUID = -5212358378014047933L; /** * Constructs a new CollationGreaterThanOrEqualToMatchingRule. * * @param nOID * OID of the collation matching rule * @param names * names of this matching rule * @param locale * Locale of the collation matching rule */ private CollationGreaterThanOrEqualToMatchingRule(String nOID, Collection<String> names, Locale locale) { super(nOID, names, locale); } /** * {@inheritDoc} */ @Override public ConditionResult valuesMatch(ByteSequence attributeValue, ByteSequence assertionValue) { int ret = attributeValue.compareTo(assertionValue); if (ret >= 0) { return ConditionResult.TRUE; } else { return ConditionResult.FALSE; } } /** * {@inheritDoc} */ @Override public <T> T createIndexQuery(ByteSequence assertionValue, IndexQueryFactory<T> factory) throws DirectoryException { // Read the range: lower <= keys < upper. return factory.createRangeMatchQuery(indexer .getExtensibleIndexID(), normalizeValue(assertionValue), ByteString.empty(), true, false); } } /** * Extensible Indexer class for Collation Matching rules which share * the same index. This Indexer is shared by Equality and Ordering * Collation Matching Rules. */ private final class CollationSharedExtensibleIndexer extends ExtensibleIndexer { /** * The Extensible Matching Rule. */ private final CollationMatchingRule matchingRule; /** * Creates a new instance of CollationSharedExtensibleIndexer. * * @param matchingRule * The Collation Matching Rule. */ private CollationSharedExtensibleIndexer( CollationMatchingRule matchingRule) { this.matchingRule = matchingRule; } /** * {@inheritDoc} */ @Override public String getExtensibleIndexID() { return EXTENSIBLE_INDEXER_ID_SHARED; } /** * {@inheritDoc} */ @Override public final void getKeys(AttributeValue value, Set<byte[]> keys) { ByteString key; try { key = matchingRule.normalizeValue(value.getValue()); keys.add(key.toByteArray()); } catch (DirectoryException de) { } } /** * {@inheritDoc} */ @Override public final void getKeys(AttributeValue value, Map<byte[], Boolean> modifiedKeys, Boolean insert) { Set<byte[]> keys = new HashSet<byte[]>(); getKeys(value, keys); for (byte[] key : keys) { Boolean cInsert = modifiedKeys.get(key); if (cInsert == null) { modifiedKeys.put(key, insert); } else if (!cInsert.equals(insert)) { modifiedKeys.remove(key); } } } /** * {@inheritDoc} */ @Override public String getPreferredIndexName() { return matchingRule.getIndexName(); } } /** * Extensible Indexer class for Collation Substring Matching rules. * This Indexer is used by Substring Collation Matching Rules. */ private final class CollationSubstringExtensibleIndexer extends ExtensibleIndexer { // The CollationSubstringMatching Rule. private final CollationSubstringMatchingRule matchingRule; // The substring length. private int substringLen; /** * Creates a new instance of CollationSubstringExtensibleIndexer. * * @param matchingRule * The CollationSubstringMatching Rule. * @param substringLen * The substring length. */ private CollationSubstringExtensibleIndexer( CollationSubstringMatchingRule matchingRule, int substringLen) { this.matchingRule = matchingRule; this.substringLen = substringLen; } /** * {@inheritDoc} */ @Override public void getKeys(AttributeValue value, Set<byte[]> keys) { matchingRule.subtringKeys(value.getValue(), keys); } /** * {@inheritDoc} */ @Override public void getKeys(AttributeValue attValue, Map<byte[], Boolean> modifiedKeys, Boolean insert) { matchingRule.substringKeys(attValue.getValue(), modifiedKeys, insert); } /** * {@inheritDoc} */ @Override public String getPreferredIndexName() { return matchingRule.getIndexName(); } /** * {@inheritDoc} */ @Override public String getExtensibleIndexID() { return EXTENSIBLE_INDEXER_ID_SUBSTRING; } /** * Returns the substring length. * * @return The length of the substring. */ private int gerSubstringLength() { return substringLen; } /** * Sets the substring length. * * @param substringLen * The substring length. */ private void setSubstringLength(int substringLen) { this.substringLen = substringLen; } } /** * A utility class for extracting the OID and Language Tag from the * configuration entry. */ private final class CollationMapper { // OID of the collation rule. private String oid; // Language Tag. private String lTag; /** * Creates a new instance of CollationMapper. * * @param collation * The collation text in the LOCALE:OID format. */ private CollationMapper(String collation) { int index = collation.indexOf(":"); if (index > 0) { oid = collation.substring(index + 1, collation.length()); lTag = collation.substring(0, index); } } /** * Returns the OID part of the collation text. * * @return OID part of the collation text. */ private String getNumericOID() { return oid; } /** * Returns the language Tag of collation text. * * @return Language Tag part of the collation text. */ private String getLanguageTag() { return lTag; } } }