/*
|
* 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.
|
*/
|
|
|
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.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 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;
|
}
|
}
|
}
|