/*
* 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 2009 Sun Microsystems, Inc.
* Portions Copyright 2011 ForgeRock AS
*/
package org.opends.server.schema;
import org.opends.server.util.StaticUtils;
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.Map;
import java.util.Set;
import java.util.TimeZone;
import org.opends.messages.Message;
import org.opends.server.api.ExtensibleIndexer;
import org.opends.server.api.IndexQueryFactory;
import org.opends.server.api.MatchingRule;
import org.opends.server.api.MatchingRuleFactory;
import org.opends.server.admin.std.server.MatchingRuleCfg;
import org.opends.server.api.AbstractMatchingRule;
import org.opends.server.api.ExtensibleMatchingRule;
import org.opends.server.api.OrderingMatchingRule;
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.ByteStringBuilder;
import org.opends.server.types.ConditionResult;
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 static org.opends.server.util.StaticUtils.*;
import static org.opends.server.util.TimeThread.*;
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.schema.GeneralizedTimeSyntax.*;
/**
* This class acts as a factory for time-based matching rules.
*/
public final class TimeBasedMatchingRuleFactory
extends MatchingRuleFactory<MatchingRuleCfg>
{
//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);
}
/**
* {@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 normalizeValue(ByteSequence value)
throws DirectoryException
{
try
{
long timestamp = decodeGeneralizedTimeValue(value);
return ByteString.valueOf(timestamp);
}
catch (DirectoryException de)
{
switch (DirectoryServer.getSyntaxEnforcementPolicy())
{
case REJECT:
throw de;
case WARN:
logError(de.getMessageObject());
return value.toByteString();
default:
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 DirectoryException
{
/**
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;
long number = 0;
for(; index<value.length(); index++)
{
byte b = value.byteAt(index);
if(isDigit((char)b))
{
switch (value.byteAt(index))
{
case '0':
number = (number * 10);
break;
case '1':
number = (number * 10) + 1;
break;
case '2':
number = (number * 10) + 2;
break;
case '3':
number = (number * 10) + 3;
break;
case '4':
number = (number * 10) + 4;
break;
case '5':
number = (number * 10) + 5;
break;
case '6':
number = (number * 10) + 6;
break;
case '7':
number = (number * 10) + 7;
break;
case '8':
number = (number * 10) + 8;
break;
case '9':
number = (number * 10) + 9;
break;
}
}
else
{
Message message = null;
if(containsTimeUnit)
{
//We already have time unit found by now.
message = WARN_ATTR_CONFLICTING_ASSERTION_FORMAT.
get(value.toString());
}
else
{
switch(value.byteAt(index))
{
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.toString(),(char)value.byteAt(index));
}
}
if(message !=null)
{
//Log the message and throw an exception.
logError(message);
throw new DirectoryException(
ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
}
else
{
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 signed?ByteString.valueOf(now-delta):
ByteString.valueOf(now+delta);
}
/**
* {@inheritDoc}
*/
public int compareValues(ByteSequence value1, ByteSequence value2)
{
return value1.compareTo(value2);
}
/**
* {@inheritDoc}
*/
public int compare(byte[] arg0, byte[] arg1)
{
return StaticUtils.compare(arg0, arg1);
}
/**
* {@inheritDoc}
*/
public Collection<ExtensibleIndexer> getIndexers(IndexConfig config)
{
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 String getName()
{
return EXT_OMR_RELATIVE_TIME_GT_NAME;
}
/**
* {@inheritDoc}
*/
@Override
public Collection<String> getAllNames()
{
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);
if (ret > 0)
{
return ConditionResult.TRUE;
}
else
{
return ConditionResult.FALSE;
}
}
/**
* {@inheritDoc}
*/
public <T> T createIndexQuery(ByteSequence assertionValue,
IndexQueryFactory<T> factory) throws DirectoryException
{
return factory.createRangeMatchQuery(indexer
.getExtensibleIndexID(), normalizeAssertionValue(assertionValue),
ByteString.empty(), false, false);
}
}
/**
* 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 String getName()
{
return EXT_OMR_RELATIVE_TIME_LT_NAME;
}
/**
* {@inheritDoc}
*/
@Override
public Collection<String> getAllNames()
{
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);
if (ret < 0)
{
return ConditionResult.TRUE;
}
else
{
return ConditionResult.FALSE;
}
}
/**
* {@inheritDoc}
*/
public <T> T createIndexQuery(ByteSequence assertionValue,
IndexQueryFactory<T> factory) throws DirectoryException
{
return factory.createRangeMatchQuery(indexer
.getExtensibleIndexID(), ByteString.empty(),
normalizeAssertionValue(assertionValue),false, false);
}
}
/**
* 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 getKeys(AttributeValue value, Set<byte[]> keys)
{
ByteString key;
try
{
key = matchingRule.normalizeValue(value.getValue());
keys.add(key.toByteArray());
}
catch (DirectoryException de)
{
//don't do anything.
}
}
/**
* {@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 RELATIVE_TIME_INDEX_NAME;
}
}
/**
* This class performs the partial date and time matching capabilities.
*/
private final class PartialDateAndTimeMatchingRule
extends TimeBasedMatchingRule
implements ExtensibleMatchingRule
{
/**
* Indexer associated with this instance.
*/
private ExtensibleIndexer indexer;
/**
* {@inheritDoc}
*/
@Override
public String getName()
{
return EXT_PARTIAL_DATE_TIME_NAME;
}
/**
* {@inheritDoc}
*/
@Override
public String getOID()
{
return EXT_PARTIAL_DATE_TIME_OID;
}
/**
* {@inheritDoc}
*/
@Override
public Collection<String> getAllNames()
{
return Collections.singleton(getName());
}
/**
* {@inheritDoc}
*/
@Override
public ByteString normalizeAssertionValue(ByteSequence value)
throws DirectoryException
{
/**
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.
*/
int second = -1;
int minute = -1;
int hour = -1;
int date = 0;
int year = 0;
int number = 0;
int month = -1;
int length = value.length();
for(int index=0; index<length; index++)
{
byte b = value.byteAt(index);
if(isDigit((char)b))
{
switch (value.byteAt(index))
{
case '0':
number = (number * 10);
break;
case '1':
number = (number * 10) + 1;
break;
case '2':
number = (number * 10) + 2;
break;
case '3':
number = (number * 10) + 3;
break;
case '4':
number = (number * 10) + 4;
break;
case '5':
number = (number * 10) + 5;
break;
case '6':
number = (number * 10) + 6;
break;
case '7':
number = (number * 10) + 7;
break;
case '8':
number = (number * 10) + 8;
break;
case '9':
number = (number * 10) + 9;
break;
}
}
else
{
Message message = null;
switch(value.byteAt(index))
{
case 's':
if(second >0)
{
message =
WARN_ATTR_DUPLICATE_SECOND_ASSERTION_FORMAT.get(
value.toString(),date);
}
else
{
second = number;
}
break;
case 'm':
if(minute >0)
{
message =
WARN_ATTR_DUPLICATE_MINUTE_ASSERTION_FORMAT.get(
value.toString(),date);
}
else
{
minute = number;
}
break;
case 'h':
if(hour >0)
{
message =
WARN_ATTR_DUPLICATE_HOUR_ASSERTION_FORMAT.get(
value.toString(),date);
}
else
{
hour = number;
}
break;
case 'D':
if(number == 0)
{
message =
WARN_ATTR_INVALID_DATE_ASSERTION_FORMAT.get(
value.toString(), number);
}
else if(date > 0)
{
message =
WARN_ATTR_DUPLICATE_DATE_ASSERTION_FORMAT.get(
value.toString(),date);
}
else
{
date = number;
}
break;
case 'M':
if(number == 0)
{
message =
WARN_ATTR_INVALID_MONTH_ASSERTION_FORMAT.
get(value.toString(),number);
}
else if(month > 0)
{
message =
WARN_ATTR_DUPLICATE_MONTH_ASSERTION_FORMAT.get(
value.toString(),month);
}
else
{
month = number;
}
break;
case 'Y':
if(number == 0)
{
message =
WARN_ATTR_INVALID_YEAR_ASSERTION_FORMAT.
get(value.toString(),number);
}
else if(year >0)
{
message = WARN_ATTR_DUPLICATE_YEAR_ASSERTION_FORMAT.
get(value.toString(),year);
}
else
{
year = number;
}
break;
default:
message =
WARN_ATTR_INVALID_PARTIAL_TIME_ASSERTION_FORMAT.
get(value.toString(),(char)value.byteAt(index));
}
if(message !=null)
{
logError(message);
throw new DirectoryException(
ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
}
else
{
number = 0;
}
}
}
//Validate year, month , date , hour, minute and second in that order.
if(year < 0)
{
//A future date is allowed.
Message message =
WARN_ATTR_INVALID_YEAR_ASSERTION_FORMAT.
get(value.toString(),year);
logError(message);
throw new DirectoryException(
ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
}
switch(month)
{
case -1:
//just allow this.
break;
case 1:
month = Calendar.JANUARY;
break;
case 2:
month = Calendar.FEBRUARY;
break;
case 3:
month = Calendar.MARCH;
break;
case 4:
month = Calendar.APRIL;
break;
case 5:
month = Calendar.MAY;
break;
case 6:
month = Calendar.JUNE;
break;
case 7:
month = Calendar.JULY;
break;
case 8:
month = Calendar.AUGUST;
break;
case 9:
month = Calendar.SEPTEMBER;
break;
case 10:
month = Calendar.OCTOBER;
break;
case 11:
month = Calendar.NOVEMBER;
break;
case 12:
month = Calendar.DECEMBER;
break;
default:
Message message =
WARN_ATTR_INVALID_MONTH_ASSERTION_FORMAT.
get(value.toString(),month);
logError(message);
throw new DirectoryException(
ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
}
boolean invalidDate = false;
switch(date)
{
case 29:
if(month == Calendar.FEBRUARY && year%4 !=0)
{
invalidDate = true;
}
break;
case 31:
if(month != -1 && month != Calendar.JANUARY && month!= Calendar.MARCH
&& month != Calendar.MAY && month != Calendar.JULY
&& month != Calendar.AUGUST && month != Calendar.OCTOBER
&& month != Calendar.DECEMBER)
{
invalidDate = true;
}
break;
default:
if(!(date >=0 && date <=31))
{
invalidDate = true;
}
}
if(invalidDate)
{
Message message =
WARN_ATTR_INVALID_DATE_ASSERTION_FORMAT.
get(value.toString(),date);
logError(message);
throw new DirectoryException(
ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
}
if(!(hour >=-1 && hour <=23))
{
Message message =
WARN_ATTR_INVALID_HOUR_ASSERTION_FORMAT.
get(value.toString(),date);
logError(message);
throw new DirectoryException(
ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
}
if(!(minute >=-1 && minute <=59))
{
Message message =
WARN_ATTR_INVALID_MINUTE_ASSERTION_FORMAT.
get(value.toString(),date);
logError(message);
throw new DirectoryException(
ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
}
if(!(second >=-1 && second <=60)) //Consider leap seconds.
{
Message message =
WARN_ATTR_INVALID_SECOND_ASSERTION_FORMAT.
get(value.toString(),date);
logError(message);
throw new DirectoryException(
ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
}
/**
* 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());
}
/**
* {@inheritDoc}
*/
@Override
public ConditionResult valuesMatch(ByteSequence attributeValue,
ByteSequence assertionValue)
{
long timeInMS = ((ByteString)attributeValue).toLong();
//Build the information from the attribute value.
GregorianCalendar cal = new GregorianCalendar(TIME_ZONE_UTC_OBJ);
cal.setLenient(false);
cal.setTimeInMillis(timeInMS);
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);
if(assertSecond != -1 && assertSecond !=second)
{
return ConditionResult.FALSE;
}
if(assertMinute !=-1 && assertMinute !=minute)
{
return ConditionResult.FALSE;
}
if(assertHour !=-1 && assertHour !=hour)
{
return ConditionResult.FALSE;
}
//All the non-zero values should match.
if(assertDate !=0 && assertDate != date)
{
return ConditionResult.FALSE;
}
if(assertMonth !=-1 && assertMonth != month)
{
return ConditionResult.FALSE;
}
if(assertYear !=0 && assertYear != year)
{
return ConditionResult.FALSE;
}
return ConditionResult.TRUE;
}
/**
* {@inheritDoc}
*/
public Collection<ExtensibleIndexer> getIndexers(IndexConfig config)
{
if(indexer == null)
{
indexer = new PartialDateAndTimeExtensibleIndexer(this);
}
return Collections.singletonList(indexer);
}
/**
* {@inheritDoc}
*/
public <T> T createIndexQuery(ByteSequence assertionValue,
IndexQueryFactory<T> factory) throws DirectoryException
{
//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(factory.createExactMatchQuery(
indexer.getExtensibleIndexID(),
getKey(assertSecond,SECOND)));
}
if(assertMinute >=0)
{
queries.add(factory.createExactMatchQuery(
indexer.getExtensibleIndexID(),
getKey(assertMinute,MINUTE)));
}
if(assertHour >=0)
{
queries.add(factory.createExactMatchQuery(
indexer.getExtensibleIndexID(),
getKey(assertHour,HOUR)));
}
if(assertDate >0)
{
queries.add(factory.createExactMatchQuery(
indexer.getExtensibleIndexID(),
getKey(assertDate,DATE)));
}
if(assertMonth >=0)
{
queries.add(factory.createExactMatchQuery(
indexer.getExtensibleIndexID(),
getKey(assertMonth,MONTH)));
}
if(assertYear > 0)
{
queries.add(factory.createExactMatchQuery(
indexer.getExtensibleIndexID(),
getKey(assertYear,YEAR)));
}
return factory.createIntersectionQuery(queries);
}
/**
* 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(ByteString attributeValue, Set<byte[]> 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);
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);
if (second >=0)
{
keys.add(getKey(second,SECOND).toByteArray());
}
if(minute >=0)
{
keys.add(getKey(minute,MINUTE).toByteArray());
}
if(hour >=0)
{
keys.add(getKey(hour,HOUR).toByteArray());
}
//Insert date.
if(date > 0)
{
keys.add(getKey(date,DATE).toByteArray());
}
//Insert month.
if(month >=0)
{
keys.add(getKey(month,MONTH).toByteArray());
}
if(year > 0)
{
keys.add(getKey(year,YEAR).toByteArray());
}
}
private ByteString getKey(int value, char type)
{
ByteStringBuilder builder = new ByteStringBuilder();
builder.append(type);
builder.append(value);
return builder.toByteString();
}
}
/**
* 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 getKeys(AttributeValue value, Set<byte[]> keys)
{
matchingRule.timeKeys(value.getValue(), keys);
}
/**
* {@inheritDoc}
*/
@Override
public void getKeys(AttributeValue attValue,
Map<byte[], Boolean> modifiedKeys, Boolean insert)
{
Set<byte[]> keys = new HashSet<byte[]>();
getKeys(attValue, 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 PARTIAL_DATE_TIME_INDEX_NAME;
}
/**
* {@inheritDoc}
*/
@Override
public String getExtensibleIndexID()
{
return EXTENSIBLE_INDEXER_ID_DEFAULT;
}
}
}