001/*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License").  You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
010 * or http://forgerock.org/license/CDDLv1.0.html.
011 * See the License for the specific language governing permissions
012 * and limitations under the License.
013 *
014 * When distributing Covered Code, include this CDDL HEADER in each
015 * file and include the License file at legal-notices/CDDLv1_0.txt.
016 * If applicable, add the following below this CDDL HEADER, with the
017 * fields enclosed by brackets "[]" replaced with your own identifying
018 * information:
019 *      Portions Copyright [yyyy] [name of copyright owner]
020 *
021 * CDDL HEADER END
022 *
023 *
024 *      Copyright 2006-2010 Sun Microsystems, Inc.
025 *      Portions Copyright 2012-2015 ForgeRock AS.
026 */
027package org.opends.server.replication.plugin;
028
029import java.util.Collection;
030import java.util.Collections;
031import java.util.List;
032
033import org.forgerock.opendj.ldap.Assertion;
034import org.forgerock.opendj.ldap.ByteSequence;
035import org.forgerock.opendj.ldap.ByteString;
036import org.forgerock.opendj.ldap.ByteStringBuilder;
037import org.forgerock.opendj.ldap.ConditionResult;
038import org.forgerock.opendj.ldap.DecodeException;
039import org.forgerock.opendj.ldap.schema.MatchingRuleImpl;
040import org.forgerock.opendj.ldap.schema.Schema;
041import org.forgerock.opendj.ldap.spi.IndexQueryFactory;
042import org.forgerock.opendj.ldap.spi.Indexer;
043import org.forgerock.opendj.ldap.spi.IndexingOptions;
044import org.opends.server.replication.common.CSN;
045
046import static org.forgerock.opendj.ldap.Assertion.*;
047import static org.opends.messages.ReplicationMessages.*;
048import static org.opends.server.util.StaticUtils.*;
049
050/**
051 * Matching rule used to establish an order between historical information and index them.
052 */
053public final class HistoricalCsnOrderingMatchingRuleImpl implements MatchingRuleImpl
054{
055  private static final String ORDERING_ID = "changeSequenceNumberOrderingMatch";
056
057  private final Collection<? extends Indexer> indexers = Collections.singleton(new HistoricalIndexer());
058
059  /** Indexer for the matching rule. */
060  private final class HistoricalIndexer implements Indexer
061  {
062    @Override
063    public void createKeys(Schema schema, ByteSequence value, Collection<ByteString> keys) throws DecodeException
064    {
065      keys.add(normalizeAttributeValue(schema, value));
066    }
067
068    @Override
069    public String getIndexID()
070    {
071      return ORDERING_ID;
072    }
073
074    @Override
075    public String keyToHumanReadableString(ByteSequence key)
076    {
077      ByteStringBuilder bsb = new ByteStringBuilder();
078      bsb.appendBytes(key.subSequence(2, 10));
079      bsb.appendBytes(key.subSequence(0, 2));
080      bsb.appendBytes(key.subSequence(10, 14));
081      CSN csn = CSN.valueOf(bsb.toByteString());
082      return csn.toStringUI();
083    }
084  }
085
086  /** {@inheritDoc} */
087  @Override
088  public ByteString normalizeAttributeValue(Schema schema, ByteSequence value) throws DecodeException
089  {
090    /*
091     * Change the format of the value to index and start with the serverId. In
092     * that manner, the search response time is optimized for a particular
093     * serverId. The format of the key is now : serverId + timestamp + seqNum
094     */
095    try
096    {
097      int csnIndex = value.toString().indexOf(':') + 1;
098      String csn = value.subSequence(csnIndex, csnIndex + 28).toString();
099      ByteStringBuilder builder = new ByteStringBuilder(14);
100      builder.appendBytes(hexStringToByteArray(csn.substring(16, 20)));
101      builder.appendBytes(hexStringToByteArray(csn.substring(0, 16)));
102      builder.appendBytes(hexStringToByteArray(csn.substring(20, 28)));
103      return builder.toByteString();
104    }
105    catch (Exception e)
106    {
107      // This should never occur in practice since these attributes are managed
108      // internally.
109      throw DecodeException.error(WARN_INVALID_SYNC_HIST_VALUE.get(value), e);
110    }
111  }
112
113  /** {@inheritDoc} */
114  @Override
115  public Assertion getAssertion(final Schema schema, final ByteSequence value) throws DecodeException
116  {
117    final ByteString normAssertion = normalizeAttributeValue(schema, value);
118    return new Assertion()
119    {
120      @Override
121      public ConditionResult matches(final ByteSequence attributeValue)
122      {
123        return ConditionResult.valueOf(attributeValue.compareTo(normAssertion) < 0);
124      }
125
126      @Override
127      public <T> T createIndexQuery(IndexQueryFactory<T> factory) throws DecodeException
128      {
129        return factory.createRangeMatchQuery(ORDERING_ID, ByteString.empty(), normAssertion, false, false);
130      }
131    };
132  }
133
134  /** {@inheritDoc} */
135  @Override
136  public Assertion getSubstringAssertion(Schema schema, ByteSequence subInitial,
137      List<? extends ByteSequence> subAnyElements, ByteSequence subFinal) throws DecodeException
138  {
139    return UNDEFINED_ASSERTION;
140  }
141
142  /** {@inheritDoc} */
143  @Override
144  public Assertion getGreaterOrEqualAssertion(Schema schema, ByteSequence value) throws DecodeException
145  {
146    final ByteString normAssertion = normalizeAttributeValue(schema, value);
147    return new Assertion()
148    {
149      @Override
150      public ConditionResult matches(final ByteSequence normalizedAttributeValue)
151      {
152        return ConditionResult.valueOf(normalizedAttributeValue.compareTo(normAssertion) >= 0);
153      }
154
155      @Override
156      public <T> T createIndexQuery(IndexQueryFactory<T> factory) throws DecodeException
157      {
158        return factory.createRangeMatchQuery(ORDERING_ID, normAssertion, ByteString.empty(), true, false);
159      }
160    };
161  }
162
163  /** {@inheritDoc} */
164  @Override
165  public Assertion getLessOrEqualAssertion(Schema schema, ByteSequence value) throws DecodeException
166  {
167    final ByteString normAssertion = normalizeAttributeValue(schema, value);
168    return new Assertion()
169    {
170      @Override
171      public ConditionResult matches(final ByteSequence normalizedAttributeValue)
172      {
173        return ConditionResult.valueOf(normalizedAttributeValue.compareTo(normAssertion) <= 0);
174      }
175
176      @Override
177      public <T> T createIndexQuery(IndexQueryFactory<T> factory) throws DecodeException
178      {
179        return factory.createRangeMatchQuery(ORDERING_ID, ByteString.empty(), normAssertion, false, true);
180      }
181    };
182  }
183
184  /** {@inheritDoc} */
185  @Override
186  public Collection<? extends Indexer> createIndexers(IndexingOptions options)
187  {
188    return indexers;
189  }
190
191}