| | |
| | | */ |
| | | package org.opends.server.schema; |
| | | |
| | | import java.nio.ByteBuffer; |
| | | import java.util.ArrayList; |
| | | import java.util.Calendar; |
| | | import java.util.Collection; |
| | | import java.util.Collections; |
| | | import java.util.GregorianCalendar; |
| | | import java.util.HashSet; |
| | | import java.util.List; |
| | | import java.util.Set; |
| | | import java.util.TimeZone; |
| | | |
| | | import org.forgerock.i18n.LocalizableMessage; |
| | | import org.forgerock.i18n.slf4j.LocalizedLogger; |
| | | import org.forgerock.opendj.config.server.ConfigException; |
| | | import org.forgerock.opendj.ldap.Assertion; |
| | | import org.forgerock.opendj.ldap.ByteSequence; |
| | | import org.forgerock.opendj.ldap.ByteString; |
| | | import org.forgerock.opendj.ldap.ByteStringBuilder; |
| | | import org.forgerock.opendj.ldap.ConditionResult; |
| | | import org.forgerock.opendj.ldap.DecodeException; |
| | | import org.forgerock.opendj.ldap.schema.Schema; |
| | | import org.forgerock.opendj.ldap.spi.IndexQueryFactory; |
| | | import org.forgerock.opendj.ldap.spi.IndexingOptions; |
| | | import org.forgerock.opendj.ldap.schema.CoreSchema; |
| | | import org.forgerock.opendj.ldap.schema.MatchingRule; |
| | | import org.opends.server.admin.std.server.MatchingRuleCfg; |
| | | import org.opends.server.api.AbstractMatchingRule; |
| | | import org.opends.server.api.ExtensibleIndexer; |
| | | import org.opends.server.api.ExtensibleMatchingRule; |
| | | import org.opends.server.api.MatchingRule; |
| | | import org.opends.server.api.MatchingRuleFactory; |
| | | import org.opends.server.api.OrderingMatchingRule; |
| | | import org.opends.server.core.DirectoryServer; |
| | | import org.opends.server.types.DirectoryException; |
| | | import org.opends.server.types.InitializationException; |
| | | import org.opends.server.util.StaticUtils; |
| | | |
| | | import static org.opends.messages.SchemaMessages.*; |
| | | import static org.opends.server.schema.GeneralizedTimeSyntax.*; |
| | | import static org.opends.server.schema.SchemaConstants.*; |
| | | import static org.opends.server.util.ServerConstants.*; |
| | | import static org.opends.server.util.StaticUtils.*; |
| | | import static org.opends.server.util.TimeThread.*; |
| | | |
| | | /** |
| | | * This class acts as a factory for time-based matching rules. |
| | |
| | | extends MatchingRuleFactory<MatchingRuleCfg> |
| | | { |
| | | |
| | | private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); |
| | | |
| | | /** Greater-than RelativeTimeMatchingRule. */ |
| | | private MatchingRule greaterThanRTMRule; |
| | | |
| | | /** Less-than RelativeTimeMatchingRule. */ |
| | | private MatchingRule lessThanRTMRule; |
| | | |
| | | /** PartialDayAndTimeMatchingRule. */ |
| | | private MatchingRule partialDTMatchingRule; |
| | | |
| | | /** A Collection of matching rules managed by this factory. */ |
| | | private Set<MatchingRule> matchingRules; |
| | | |
| | | private static final TimeZone TIME_ZONE_UTC_OBJ = |
| | | TimeZone.getTimeZone(TIME_ZONE_UTC); |
| | | |
| | | |
| | | /** Constants for generating keys. */ |
| | | private static final char SECOND = 's'; |
| | | private static final char MINUTE = 'm'; |
| | | private static final char HOUR = 'h'; |
| | | private static final char MONTH = 'M'; |
| | | private static final char DATE = 'D'; |
| | | private static final char YEAR = 'Y'; |
| | | |
| | | |
| | | /** {@inheritDoc} */ |
| | | @Override |
| | | public void initializeMatchingRule(MatchingRuleCfg configuration) |
| | | throws ConfigException, InitializationException |
| | | { |
| | | matchingRules = new HashSet<MatchingRule>(); |
| | | greaterThanRTMRule = new RelativeTimeGTOrderingMatchingRule(); |
| | | matchingRules.add(greaterThanRTMRule); |
| | | lessThanRTMRule = new RelativeTimeLTOrderingMatchingRule(); |
| | | matchingRules.add(lessThanRTMRule); |
| | | partialDTMatchingRule = new PartialDateAndTimeMatchingRule(); |
| | | matchingRules.add(partialDTMatchingRule); |
| | | // relative time greater than |
| | | matchingRules.add(CoreSchema.getInstance().getMatchingRule("1.3.6.1.4.1.26027.1.4.5")); |
| | | // relative time less than |
| | | matchingRules.add(CoreSchema.getInstance().getMatchingRule("1.3.6.1.4.1.26027.1.4.6")); |
| | | // partial date and time |
| | | matchingRules.add(CoreSchema.getInstance().getMatchingRule("1.3.6.1.4.1.26027.1.4.7")); |
| | | } |
| | | |
| | | |
| | | |
| | | /** {@inheritDoc} */ |
| | | @Override |
| | | public Collection<MatchingRule> getMatchingRules() |
| | |
| | | return Collections.unmodifiableCollection(matchingRules); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * This class defines a matching rule which is used for time-based searches. |
| | | */ |
| | | private abstract class TimeBasedMatchingRule extends AbstractMatchingRule |
| | | implements ExtensibleMatchingRule |
| | | { |
| | | /** {@inheritDoc} */ |
| | | @Override |
| | | public String getDescription() |
| | | { |
| | | //There is no standard definition. |
| | | return null; |
| | | } |
| | | |
| | | |
| | | |
| | | /** {@inheritDoc} */ |
| | | @Override |
| | | public String getSyntaxOID() |
| | | { |
| | | return SYNTAX_GENERALIZED_TIME_OID; |
| | | } |
| | | |
| | | |
| | | |
| | | /** {@inheritDoc} */ |
| | | @Override |
| | | public ByteString normalizeAttributeValue(ByteSequence value) |
| | | throws DecodeException |
| | | { |
| | | try |
| | | { |
| | | long timestamp = decodeGeneralizedTimeValue(value); |
| | | return ByteString.valueOf(timestamp); |
| | | } |
| | | catch (DirectoryException de) |
| | | { |
| | | switch (DirectoryServer.getSyntaxEnforcementPolicy()) |
| | | { |
| | | case REJECT: |
| | | throw DecodeException.error(de.getMessageObject(), de); |
| | | |
| | | case WARN: |
| | | logger.error(de.getMessageObject()); |
| | | break; |
| | | } |
| | | return value.toByteString(); |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * This class defines a matching rule which matches the relative time for |
| | | * time-based searches. |
| | | */ |
| | | private abstract class RelativeTimeOrderingMatchingRule |
| | | extends TimeBasedMatchingRule |
| | | 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 = -3501812894473163490L; |
| | | |
| | | |
| | | |
| | | /** |
| | | * Indexer associated with this instance. |
| | | */ |
| | | protected ExtensibleIndexer indexer; |
| | | |
| | | |
| | | /** {@inheritDoc} */ |
| | | @Override |
| | | public ByteString normalizeAssertionValue(ByteSequence value) |
| | | throws DecodeException |
| | | { |
| | | /** |
| | | An assertion value may contain one of the following: |
| | | s = second |
| | | m = minute |
| | | h = hour |
| | | d = day |
| | | w = week |
| | | |
| | | An example assertion is OID:=(-)1d, where a '-' means that the user |
| | | intends to search only the expired events. In this example we are |
| | | searching for an event expired 1 day back. |
| | | |
| | | Use this method to parse, validate and normalize the assertion value |
| | | into a format to be recognized by the valuesMatch routine. This method |
| | | takes the assertion value, adds/substracts it to/from the current time |
| | | and calculates a time which will be used as a relative time by inherited |
| | | rules. |
| | | */ |
| | | |
| | | int index = 0; |
| | | boolean signed = false; |
| | | byte firstByte = value.byteAt(0); |
| | | |
| | | if(firstByte == '-') |
| | | { |
| | | //Turn the sign on to go back in past. |
| | | signed = true; |
| | | index = 1; |
| | | } |
| | | else if(firstByte == '+') |
| | | { |
| | | //'+" is not required but we won't reject it either. |
| | | index = 1; |
| | | } |
| | | |
| | | long second = 0; |
| | | long minute = 0; |
| | | long hour = 0; |
| | | long day = 0; |
| | | long week = 0; |
| | | |
| | | boolean containsTimeUnit = false; |
| | | int number = 0; |
| | | |
| | | for(; index<value.length(); index++) |
| | | { |
| | | byte b = value.byteAt(index); |
| | | if(isDigit((char)b)) |
| | | { |
| | | number = multiplyByTenThenAddUnits(number, b); |
| | | } |
| | | else |
| | | { |
| | | LocalizableMessage message = null; |
| | | if(containsTimeUnit) |
| | | { |
| | | //We already have time unit found by now. |
| | | message = WARN_ATTR_CONFLICTING_ASSERTION_FORMAT.get(value); |
| | | } |
| | | else |
| | | { |
| | | switch(b) |
| | | { |
| | | case 's': |
| | | second = number; |
| | | break; |
| | | case 'm': |
| | | minute = number; |
| | | break; |
| | | case 'h': |
| | | hour = number; |
| | | break; |
| | | case 'd': |
| | | day = number; |
| | | break; |
| | | case 'w': |
| | | week = number; |
| | | break; |
| | | default: |
| | | message = WARN_ATTR_INVALID_RELATIVE_TIME_ASSERTION_FORMAT.get(value, (char) b); |
| | | } |
| | | } |
| | | if(message !=null) |
| | | { |
| | | //Log the message and throw an exception. |
| | | logger.error(message); |
| | | throw DecodeException.error(message); |
| | | } |
| | | containsTimeUnit = true; |
| | | number = 0; |
| | | } |
| | | } |
| | | |
| | | if(!containsTimeUnit) |
| | | { |
| | | //There was no time unit so assume it is seconds. |
| | | second = number; |
| | | } |
| | | |
| | | long delta = (second + minute*60 + hour*3600 + day*24*3600 + |
| | | week*7*24*3600)*1000 ; |
| | | long now = getTime(); |
| | | return ByteString.valueOf(signed ? now - delta : now + delta); |
| | | } |
| | | |
| | | |
| | | |
| | | /** {@inheritDoc} */ |
| | | @Override |
| | | public int compareValues(ByteSequence value1, ByteSequence value2) |
| | | { |
| | | return value1.compareTo(value2); |
| | | } |
| | | |
| | | |
| | | |
| | | /** {@inheritDoc} */ |
| | | @Override |
| | | public int compare(byte[] arg0, byte[] arg1) |
| | | { |
| | | return StaticUtils.compare(arg0, arg1); |
| | | } |
| | | |
| | | |
| | | |
| | | /** {@inheritDoc} */ |
| | | @Override |
| | | public Collection<ExtensibleIndexer> getIndexers() |
| | | { |
| | | if(indexer == null) |
| | | { |
| | | indexer = new RelativeTimeExtensibleIndexer(this); |
| | | } |
| | | return Collections.singletonList(indexer); |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * This class defines a matching rule which calculates the "greater-than" |
| | | * relative time for time-based searches. |
| | | */ |
| | | private final class RelativeTimeGTOrderingMatchingRule |
| | | extends RelativeTimeOrderingMatchingRule |
| | | { |
| | | /** All the names for this matching rule. */ |
| | | private final List<String> names; |
| | | |
| | | |
| | | |
| | | /** |
| | | * 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 = 7247241496402474136L; |
| | | |
| | | |
| | | RelativeTimeGTOrderingMatchingRule() |
| | | { |
| | | names = new ArrayList<String>(); |
| | | names.add(EXT_OMR_RELATIVE_TIME_GT_NAME); |
| | | names.add(EXT_OMR_RELATIVE_TIME_GT_ALT_NAME); |
| | | } |
| | | |
| | | |
| | | /** {@inheritDoc} */ |
| | | @Override |
| | | public Collection<String> getNames() |
| | | { |
| | | return Collections.unmodifiableList(names); |
| | | } |
| | | |
| | | |
| | | |
| | | /** {@inheritDoc} */ |
| | | @Override |
| | | public String getOID() |
| | | { |
| | | return EXT_OMR_RELATIVE_TIME_GT_OID; |
| | | } |
| | | |
| | | |
| | | |
| | | /** {@inheritDoc} */ |
| | | @Override |
| | | public ConditionResult valuesMatch(ByteSequence attributeValue, |
| | | ByteSequence assertionValue) |
| | | { |
| | | int ret = compareValues(attributeValue, assertionValue); |
| | | return ConditionResult.valueOf(ret > 0); |
| | | } |
| | | |
| | | /** {@inheritDoc} */ |
| | | @Override |
| | | public Assertion getAssertion(final ByteSequence value) |
| | | throws DecodeException |
| | | { |
| | | final ByteString assertionValue = normalizeAssertionValue(value); |
| | | return new Assertion() |
| | | { |
| | | @Override |
| | | public ConditionResult matches(ByteSequence attributeValue) |
| | | { |
| | | return valuesMatch(attributeValue, assertionValue); |
| | | } |
| | | |
| | | @Override |
| | | public <T> T createIndexQuery(IndexQueryFactory<T> factory) |
| | | throws DecodeException |
| | | { |
| | | return factory.createRangeMatchQuery(indexer.getExtensibleIndexID(), |
| | | assertionValue, ByteString.empty(), false, false); |
| | | } |
| | | }; |
| | | } |
| | | |
| | | |
| | | /** {@inheritDoc} */ |
| | | @Override |
| | | public <T> T createIndexQuery(ByteSequence assertionValue, |
| | | IndexQueryFactory<T> factory) throws DecodeException |
| | | { |
| | | return getAssertion(assertionValue).createIndexQuery(factory); |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * This class defines a matching rule which calculates the "less-than" |
| | | * relative time for time-based searches. |
| | | */ |
| | | private final class RelativeTimeLTOrderingMatchingRule |
| | | extends RelativeTimeOrderingMatchingRule |
| | | { |
| | | /** All the names for this matching rule. */ |
| | | private final List<String> names; |
| | | |
| | | |
| | | |
| | | /** |
| | | * 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 = -5122459830973558441L; |
| | | |
| | | |
| | | |
| | | RelativeTimeLTOrderingMatchingRule() |
| | | { |
| | | names = new ArrayList<String>(); |
| | | names.add(EXT_OMR_RELATIVE_TIME_LT_NAME); |
| | | names.add(EXT_OMR_RELATIVE_TIME_LT_ALT_NAME); |
| | | } |
| | | |
| | | |
| | | /** {@inheritDoc} */ |
| | | @Override |
| | | public Collection<String> getNames() |
| | | { |
| | | return Collections.unmodifiableList(names); |
| | | } |
| | | |
| | | |
| | | |
| | | /** {@inheritDoc} */ |
| | | @Override |
| | | public String getOID() |
| | | { |
| | | return EXT_OMR_RELATIVE_TIME_LT_OID; |
| | | } |
| | | |
| | | |
| | | |
| | | /** {@inheritDoc} */ |
| | | @Override |
| | | public ConditionResult valuesMatch(ByteSequence attributeValue, |
| | | ByteSequence assertionValue) |
| | | { |
| | | int ret = compareValues(attributeValue, assertionValue); |
| | | return ConditionResult.valueOf(ret < 0); |
| | | } |
| | | |
| | | /** {@inheritDoc} */ |
| | | @Override |
| | | public Assertion getAssertion(final ByteSequence value) |
| | | throws DecodeException |
| | | { |
| | | final ByteString assertionValue = normalizeAssertionValue(value); |
| | | return new Assertion() |
| | | { |
| | | @Override |
| | | public ConditionResult matches(ByteSequence attributeValue) |
| | | { |
| | | return valuesMatch(attributeValue, assertionValue); |
| | | } |
| | | |
| | | @Override |
| | | public <T> T createIndexQuery(IndexQueryFactory<T> factory) |
| | | throws DecodeException |
| | | { |
| | | return factory.createRangeMatchQuery(indexer.getExtensibleIndexID(), |
| | | ByteString.empty(), assertionValue, false, false); |
| | | } |
| | | }; |
| | | } |
| | | |
| | | /** {@inheritDoc} */ |
| | | @Override |
| | | public <T> T createIndexQuery(ByteSequence assertionValue, |
| | | IndexQueryFactory<T> factory) throws DecodeException |
| | | { |
| | | return getAssertion(assertionValue).createIndexQuery(factory); |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Extensible Indexer class for Relative Time Matching rules which share |
| | | * the same index. This Indexer is shared by both greater than and less than |
| | | * Relative Time Matching Rules. |
| | | */ |
| | | private final class RelativeTimeExtensibleIndexer extends |
| | | ExtensibleIndexer |
| | | { |
| | | |
| | | /** |
| | | * The Extensible Matching Rule. |
| | | */ |
| | | private final RelativeTimeOrderingMatchingRule matchingRule; |
| | | |
| | | |
| | | |
| | | /** |
| | | * Creates a new instance of RelativeTimeExtensibleIndexer. |
| | | * |
| | | * @param matchingRule The relative time Matching Rule. |
| | | */ |
| | | private RelativeTimeExtensibleIndexer( |
| | | RelativeTimeOrderingMatchingRule matchingRule) |
| | | { |
| | | this.matchingRule = matchingRule; |
| | | } |
| | | |
| | | |
| | | |
| | | /** {@inheritDoc} */ |
| | | @Override |
| | | public String getExtensibleIndexID() |
| | | { |
| | | return EXTENSIBLE_INDEXER_ID_DEFAULT; |
| | | } |
| | | |
| | | |
| | | |
| | | /** {@inheritDoc} */ |
| | | @Override |
| | | public final void createKeys(Schema schema, ByteSequence value2, |
| | | IndexingOptions options, Collection<ByteString> keys) |
| | | throws DecodeException |
| | | { |
| | | keys.add(matchingRule.normalizeAttributeValue(value2)); |
| | | } |
| | | |
| | | /** {@inheritDoc} */ |
| | | @Override |
| | | public String getIndexID() |
| | | { |
| | | return RELATIVE_TIME_INDEX_NAME + "." + getExtensibleIndexID(); |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * This class performs the partial date and time matching capabilities. |
| | | */ |
| | | private final class PartialDateAndTimeMatchingRule |
| | | extends TimeBasedMatchingRule |
| | | { |
| | | /** |
| | | * Indexer associated with this instance. |
| | | */ |
| | | private ExtensibleIndexer indexer; |
| | | |
| | | |
| | | |
| | | /** {@inheritDoc} */ |
| | | @Override |
| | | public String getOID() |
| | | { |
| | | return EXT_PARTIAL_DATE_TIME_OID; |
| | | } |
| | | |
| | | |
| | | |
| | | /** {@inheritDoc} */ |
| | | @Override |
| | | public Collection<String> getNames() |
| | | { |
| | | return Collections.singleton(EXT_PARTIAL_DATE_TIME_NAME); |
| | | } |
| | | |
| | | |
| | | |
| | | /** {@inheritDoc} */ |
| | | @Override |
| | | public ByteString normalizeAssertionValue(ByteSequence value) |
| | | throws DecodeException |
| | | { |
| | | /** |
| | | An assertion value may contain one or all of the following: |
| | | D = day |
| | | M = month |
| | | Y = year |
| | | h = hour |
| | | m = month |
| | | s = second |
| | | |
| | | An example assertion is OID:=04M. In this example we are |
| | | searching for entries corresponding to month of april. |
| | | |
| | | Use this method to parse, validate and normalize the assertion value |
| | | into a format to be recognized by the compare routine. The normalized |
| | | value is actually the format of : smhDMY. |
| | | */ |
| | | final int initDate = 0; |
| | | final int initVal = -1; |
| | | int second = initVal; |
| | | int minute = initVal; |
| | | int hour = initVal; |
| | | int date = initDate; |
| | | int month = initVal; |
| | | int year = initDate; |
| | | int number = 0; |
| | | |
| | | int length = value.length(); |
| | | for(int index=0; index<length; index++) |
| | | { |
| | | byte b = value.byteAt(index); |
| | | if(isDigit((char)b)) |
| | | { |
| | | number = multiplyByTenThenAddUnits(number, b); |
| | | } |
| | | else |
| | | { |
| | | LocalizableMessage message = null; |
| | | switch(b) |
| | | { |
| | | case 's': |
| | | if (second != initVal) |
| | | { |
| | | message = WARN_ATTR_DUPLICATE_SECOND_ASSERTION_FORMAT.get(value, date); |
| | | } |
| | | else |
| | | { |
| | | second = number; |
| | | } |
| | | break; |
| | | case 'm': |
| | | if (minute != initVal) |
| | | { |
| | | message = WARN_ATTR_DUPLICATE_MINUTE_ASSERTION_FORMAT.get(value, date); |
| | | } |
| | | else |
| | | { |
| | | minute = number; |
| | | } |
| | | break; |
| | | case 'h': |
| | | if (hour != initVal) |
| | | { |
| | | message = WARN_ATTR_DUPLICATE_HOUR_ASSERTION_FORMAT.get(value, date); |
| | | } |
| | | else |
| | | { |
| | | hour = number; |
| | | } |
| | | break; |
| | | case 'D': |
| | | if(number == 0) |
| | | { |
| | | message = WARN_ATTR_INVALID_DATE_ASSERTION_FORMAT.get(value, number); |
| | | } |
| | | else if (date != initDate) |
| | | { |
| | | message = WARN_ATTR_DUPLICATE_DATE_ASSERTION_FORMAT.get(value, date); |
| | | } |
| | | else |
| | | { |
| | | date = number; |
| | | } |
| | | break; |
| | | case 'M': |
| | | if (number == 0) |
| | | { |
| | | message = WARN_ATTR_INVALID_MONTH_ASSERTION_FORMAT.get(value, number); |
| | | } |
| | | else if (month != initVal) |
| | | { |
| | | message = WARN_ATTR_DUPLICATE_MONTH_ASSERTION_FORMAT.get(value, month); |
| | | } |
| | | else |
| | | { |
| | | month = number; |
| | | } |
| | | break; |
| | | case 'Y': |
| | | if(number == 0) |
| | | { |
| | | message = WARN_ATTR_INVALID_YEAR_ASSERTION_FORMAT.get(value, number); |
| | | } |
| | | else if (year != initDate) |
| | | { |
| | | message = WARN_ATTR_DUPLICATE_YEAR_ASSERTION_FORMAT.get(value, year); |
| | | } |
| | | else |
| | | { |
| | | year = number; |
| | | } |
| | | break; |
| | | default: |
| | | message = WARN_ATTR_INVALID_PARTIAL_TIME_ASSERTION_FORMAT.get(value, (char) b); |
| | | } |
| | | if(message !=null) |
| | | { |
| | | logger.error(message); |
| | | throw DecodeException.error(message); |
| | | } |
| | | number = 0; |
| | | } |
| | | } |
| | | |
| | | month = toCalendarMonth(month, value); |
| | | |
| | | //Validate year, month , date , hour, minute and second in that order. |
| | | // -1 values are allowed when these values have not been provided |
| | | if (year < 0) |
| | | { |
| | | //A future date is allowed. |
| | | logAndThrow(WARN_ATTR_INVALID_YEAR_ASSERTION_FORMAT.get(value, year)); |
| | | } |
| | | if (isDateInvalid(date, month, year)) |
| | | { |
| | | logAndThrow(WARN_ATTR_INVALID_DATE_ASSERTION_FORMAT.get(value, date)); |
| | | } |
| | | if (hour < initVal || hour > 23) |
| | | { |
| | | logAndThrow(WARN_ATTR_INVALID_HOUR_ASSERTION_FORMAT.get(value, hour)); |
| | | } |
| | | if (minute < initVal || minute > 59) |
| | | { |
| | | logAndThrow(WARN_ATTR_INVALID_MINUTE_ASSERTION_FORMAT.get(value, minute)); |
| | | } |
| | | if (second < initVal || second > 60) // Consider leap seconds. |
| | | { |
| | | logAndThrow(WARN_ATTR_INVALID_SECOND_ASSERTION_FORMAT.get(value, second)); |
| | | } |
| | | |
| | | // Since we reached here we have a valid assertion value. |
| | | // Construct a normalized value in the order: SECOND MINUTE HOUR DATE MONTH YEAR. |
| | | ByteBuffer bb = ByteBuffer.allocate(6*4); |
| | | bb.putInt(second); |
| | | bb.putInt(minute); |
| | | bb.putInt(hour); |
| | | bb.putInt(date); |
| | | bb.putInt(month); |
| | | bb.putInt(year); |
| | | return ByteString.wrap(bb.array()); |
| | | } |
| | | |
| | | private void logAndThrow(LocalizableMessage message) throws DecodeException |
| | | { |
| | | logger.warn(message); |
| | | throw DecodeException.error(message); |
| | | } |
| | | |
| | | private boolean isDateInvalid(int date, int month, int year) |
| | | { |
| | | switch (date) |
| | | { |
| | | case 29: |
| | | return month == Calendar.FEBRUARY && !isLeapYear(year); |
| | | case 30: |
| | | return month == Calendar.FEBRUARY; |
| | | case 31: |
| | | return month != -1 && month != Calendar.JANUARY |
| | | && month != Calendar.MARCH && month != Calendar.MAY |
| | | && month != Calendar.JULY && month != Calendar.AUGUST |
| | | && month != Calendar.OCTOBER && month != Calendar.DECEMBER; |
| | | default: |
| | | return date < 0 || date > 31; |
| | | } |
| | | } |
| | | |
| | | private boolean isLeapYear(int year) |
| | | { |
| | | if (year % 400 == 0) |
| | | { |
| | | return true; |
| | | } |
| | | if (year % 100 == 0) |
| | | { |
| | | return false; |
| | | } |
| | | return year % 4 == 0; |
| | | } |
| | | |
| | | private int toCalendarMonth(int month, ByteSequence value) throws DecodeException |
| | | { |
| | | switch (month) |
| | | { |
| | | case -1: |
| | | // just allow this. |
| | | return -1; |
| | | case 1: |
| | | return Calendar.JANUARY; |
| | | case 2: |
| | | return Calendar.FEBRUARY; |
| | | case 3: |
| | | return Calendar.MARCH; |
| | | case 4: |
| | | return Calendar.APRIL; |
| | | case 5: |
| | | return Calendar.MAY; |
| | | case 6: |
| | | return Calendar.JUNE; |
| | | case 7: |
| | | return Calendar.JULY; |
| | | case 8: |
| | | return Calendar.AUGUST; |
| | | case 9: |
| | | return Calendar.SEPTEMBER; |
| | | case 10: |
| | | return Calendar.OCTOBER; |
| | | case 11: |
| | | return Calendar.NOVEMBER; |
| | | case 12: |
| | | return Calendar.DECEMBER; |
| | | default: |
| | | LocalizableMessage message = WARN_ATTR_INVALID_MONTH_ASSERTION_FORMAT.get(value, month); |
| | | logger.warn(message); |
| | | throw DecodeException.error(message); |
| | | } |
| | | } |
| | | |
| | | /** {@inheritDoc} */ |
| | | @Override |
| | | public ConditionResult valuesMatch(ByteSequence attributeValue, |
| | | ByteSequence assertionValue) |
| | | { |
| | | // Build the information from the attribute value. |
| | | GregorianCalendar cal = new GregorianCalendar(TIME_ZONE_UTC_OBJ); |
| | | cal.setLenient(false); |
| | | cal.setTimeInMillis(((ByteString) attributeValue).toLong()); |
| | | int second = cal.get(Calendar.SECOND); |
| | | int minute = cal.get(Calendar.MINUTE); |
| | | int hour = cal.get(Calendar.HOUR_OF_DAY); |
| | | int date = cal.get(Calendar.DATE); |
| | | int month = cal.get(Calendar.MONTH); |
| | | int year = cal.get(Calendar.YEAR); |
| | | |
| | | //Build the information from the assertion value. |
| | | ByteBuffer bb = ByteBuffer.wrap(assertionValue.toByteArray()); |
| | | int assertSecond = bb.getInt(0); |
| | | int assertMinute = bb.getInt(4); |
| | | int assertHour = bb.getInt(8); |
| | | int assertDate = bb.getInt(12); |
| | | int assertMonth = bb.getInt(16); |
| | | int assertYear = bb.getInt(20); |
| | | |
| | | // All the non-zero and non -1 values should match. |
| | | if ((assertSecond != -1 && assertSecond != second) |
| | | || (assertMinute != -1 && assertMinute != minute) |
| | | || (assertHour != -1 && assertHour != hour) |
| | | || (assertDate != 0 && assertDate != date) |
| | | || (assertMonth != -1 && assertMonth != month) |
| | | || (assertYear != 0 && assertYear != year)) |
| | | { |
| | | return ConditionResult.FALSE; |
| | | } |
| | | return ConditionResult.TRUE; |
| | | } |
| | | |
| | | |
| | | |
| | | /** {@inheritDoc} */ |
| | | @Override |
| | | public Collection<ExtensibleIndexer> getIndexers() |
| | | { |
| | | if(indexer == null) |
| | | { |
| | | indexer = new PartialDateAndTimeExtensibleIndexer(this); |
| | | } |
| | | return Collections.singletonList(indexer); |
| | | } |
| | | |
| | | |
| | | |
| | | /** {@inheritDoc} */ |
| | | @Override |
| | | public <T> T createIndexQuery(ByteSequence assertionValue, |
| | | IndexQueryFactory<T> factory) throws DecodeException |
| | | { |
| | | //Build the information from the assertion value. |
| | | byte[] arr = normalizeAssertionValue(assertionValue).toByteArray(); |
| | | ByteBuffer bb = ByteBuffer.wrap(arr); |
| | | |
| | | int assertSecond = bb.getInt(0); |
| | | int assertMinute = bb.getInt(4); |
| | | int assertHour = bb.getInt(8); |
| | | int assertDate = bb.getInt(12); |
| | | int assertMonth = bb.getInt(16); |
| | | int assertYear = bb.getInt(20); |
| | | |
| | | List<T> queries = new ArrayList<T>(); |
| | | if(assertSecond >= 0) |
| | | { |
| | | queries.add(createExactMatchQuery(factory, assertSecond, SECOND)); |
| | | } |
| | | if(assertMinute >=0) |
| | | { |
| | | queries.add(createExactMatchQuery(factory, assertMinute, MINUTE)); |
| | | } |
| | | if(assertHour >=0) |
| | | { |
| | | queries.add(createExactMatchQuery(factory, assertHour, HOUR)); |
| | | } |
| | | if(assertDate >0) |
| | | { |
| | | queries.add(createExactMatchQuery(factory, assertDate, DATE)); |
| | | } |
| | | if(assertMonth >=0) |
| | | { |
| | | queries.add(createExactMatchQuery(factory, assertMonth, MONTH)); |
| | | } |
| | | if(assertYear > 0) |
| | | { |
| | | queries.add(createExactMatchQuery(factory, assertYear, YEAR)); |
| | | } |
| | | return factory.createIntersectionQuery(queries); |
| | | } |
| | | |
| | | private <T> T createExactMatchQuery(IndexQueryFactory<T> factory, |
| | | int assertionValue, char type) |
| | | { |
| | | return factory.createExactMatchQuery( |
| | | indexer.getExtensibleIndexID(), getKey(assertionValue, type)); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Decomposes an attribute value into a set of partial date and time index |
| | | * keys. |
| | | * |
| | | * @param attValue |
| | | * The normalized attribute value |
| | | * @param set |
| | | * A set into which the keys will be inserted. |
| | | */ |
| | | private void timeKeys(ByteSequence attributeValue, Collection<ByteString> keys) |
| | | { |
| | | long timeInMS = 0L; |
| | | try |
| | | { |
| | | timeInMS = decodeGeneralizedTimeValue(attributeValue); |
| | | } |
| | | catch(DirectoryException de) |
| | | { |
| | | //If the schema check is on this should never reach here. If not then we |
| | | //would return from here. |
| | | return; |
| | | } |
| | | //Build the information from the attribute value. |
| | | GregorianCalendar cal = new GregorianCalendar(TIME_ZONE_UTC_OBJ); |
| | | cal.setTimeInMillis(timeInMS); |
| | | addKeyIfNotZero(keys, cal, Calendar.SECOND, SECOND); |
| | | addKeyIfNotZero(keys, cal, Calendar.MINUTE, MINUTE); |
| | | addKeyIfNotZero(keys, cal, Calendar.HOUR_OF_DAY, HOUR); |
| | | addKeyIfNotZero(keys, cal, Calendar.DATE, DATE); |
| | | addKeyIfNotZero(keys, cal, Calendar.MONTH, MONTH); |
| | | addKeyIfNotZero(keys, cal, Calendar.YEAR, YEAR); |
| | | } |
| | | |
| | | private void addKeyIfNotZero(Collection<ByteString> keys, |
| | | GregorianCalendar cal, int calField, char type) |
| | | { |
| | | int value = cal.get(calField); |
| | | if (value >= 0) |
| | | { |
| | | keys.add(getKey(value, type)); |
| | | } |
| | | } |
| | | |
| | | private ByteString getKey(int value, char type) |
| | | { |
| | | ByteStringBuilder builder = new ByteStringBuilder(); |
| | | builder.append(type); |
| | | builder.append(value); |
| | | return builder.toByteString(); |
| | | } |
| | | } |
| | | |
| | | private int multiplyByTenThenAddUnits(int number, byte b) |
| | | { |
| | | switch (b) |
| | | { |
| | | case '0': |
| | | return number * 10; |
| | | case '1': |
| | | return number * 10 + 1; |
| | | case '2': |
| | | return number * 10 + 2; |
| | | case '3': |
| | | return number * 10 + 3; |
| | | case '4': |
| | | return number * 10 + 4; |
| | | case '5': |
| | | return number * 10 + 5; |
| | | case '6': |
| | | return number * 10 + 6; |
| | | case '7': |
| | | return number * 10 + 7; |
| | | case '8': |
| | | return number * 10 + 8; |
| | | case '9': |
| | | return number * 10 + 9; |
| | | } |
| | | return number; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Extensible Indexer class for Partial Date and Time Matching rules. |
| | | */ |
| | | private final class PartialDateAndTimeExtensibleIndexer extends |
| | | ExtensibleIndexer |
| | | { |
| | | /** The partial date and Time matching Rule. */ |
| | | private final PartialDateAndTimeMatchingRule matchingRule; |
| | | |
| | | |
| | | |
| | | /** |
| | | * Creates a new instance of PartialDateAndTimeExtensibleIndexer. |
| | | * |
| | | * @param matchingRule |
| | | * The PartialDateAndTime Rule. |
| | | */ |
| | | private PartialDateAndTimeExtensibleIndexer( |
| | | PartialDateAndTimeMatchingRule matchingRule) |
| | | { |
| | | this.matchingRule = matchingRule; |
| | | } |
| | | |
| | | |
| | | |
| | | /** {@inheritDoc} */ |
| | | @Override |
| | | public void createKeys(Schema schema, ByteSequence value, |
| | | IndexingOptions options, Collection<ByteString> keys) |
| | | { |
| | | matchingRule.timeKeys(value, keys); |
| | | } |
| | | |
| | | /** {@inheritDoc} */ |
| | | @Override |
| | | public String getIndexID() |
| | | { |
| | | return PARTIAL_DATE_TIME_INDEX_NAME + "." + getExtensibleIndexID(); |
| | | } |
| | | |
| | | |
| | | |
| | | /** {@inheritDoc} */ |
| | | @Override |
| | | public String getExtensibleIndexID() |
| | | { |
| | | return EXTENSIBLE_INDEXER_ID_DEFAULT; |
| | | } |
| | | } |
| | | } |