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