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-2017 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.schema.SchemaConstants.*;
040import static org.opends.server.util.StaticUtils.*;
041
042/**
043 * Matching rule used to establish an order between historical information and index them.
044 */
045public final class HistoricalCsnOrderingMatchingRuleImpl implements MatchingRuleImpl
046{
047    static final String ORDERING_ID = "changeSequenceNumberOrderingMatch";
048
049    private final Collection<? extends Indexer> indexers = Collections.singleton(new HistoricalIndexer());
050
051    /** Indexer for the matching rule. */
052    static final class HistoricalIndexer implements Indexer {
053        @Override
054        public void createKeys(Schema schema, ByteSequence value, Collection<ByteString> keys) throws DecodeException {
055            keys.add(normalizeAttributeValue(value));
056        }
057
058        @Override
059        public String getIndexID() {
060            return ORDERING_ID;
061        }
062
063        @Override
064        public String keyToHumanReadableString(ByteSequence key) {
065            final ByteString bs = new ByteStringBuilder()
066                    .appendBytes(key.subSequence(2, 10))
067                    .appendBytes(key.subSequence(0, 2))
068                    .appendBytes(key.subSequence(10, 14))
069                    .toByteString();
070            return CSN.valueOf(bs).toStringUI();
071        }
072    }
073
074    @Override
075    public ByteString normalizeAttributeValue(Schema schema, ByteSequence value) throws DecodeException {
076        return normalizeAttributeValue(value);
077    }
078
079    static ByteString normalizeAttributeValue(ByteSequence value) throws DecodeException {
080        // Attribute value is expected to follow the pattern: "attributeName:CSN:operation:value"
081        // Only the CSN is extracted to be normalized
082        final int csnIndex = value.toString().indexOf(':') + 1;
083        final String csn = value.subSequence(csnIndex, csnIndex + 28).toString();
084        return normalizeCsnAttributeValue(csn);
085    }
086
087    static ByteString normalizeCsnAttributeValue(String csn) throws DecodeException {
088        // Change the format of the value to index and start with the serverId.
089        // In that manner, the search response time is optimized for a particular serverId.
090        // The format of the key is now : serverId + timestamp + seqNum
091        try {
092            return new ByteStringBuilder(14)
093                    .appendBytes(hexStringToByteArray(csn.substring(16, 20))) // serverId
094                    .appendBytes(hexStringToByteArray(csn.substring(0, 16)))  // timeStamp
095                    .appendBytes(hexStringToByteArray(csn.substring(20, 28))) // seqNum
096                    .toByteString();
097        } catch (Exception e) {
098            // This should never occur in practice since these attributes are managed internally.
099            throw DecodeException.error(WARN_INVALID_SYNC_HIST_VALUE.get(csn), e);
100        }
101    }
102
103  /** {@inheritDoc} */
104  @Override
105  public Assertion getAssertion(final Schema schema, final ByteSequence value) throws DecodeException
106  {
107    final ByteString normAssertion = normalizeAttributeValue(schema, value);
108    return new Assertion()
109    {
110      @Override
111      public ConditionResult matches(final ByteSequence attributeValue)
112      {
113        return ConditionResult.valueOf(attributeValue.compareTo(normAssertion) < 0);
114      }
115
116      @Override
117      public <T> T createIndexQuery(IndexQueryFactory<T> factory) throws DecodeException
118      {
119        return factory.createRangeMatchQuery(ORDERING_ID, ByteString.empty(), normAssertion, false, false);
120      }
121    };
122  }
123
124  /** {@inheritDoc} */
125  @Override
126  public Assertion getSubstringAssertion(Schema schema, ByteSequence subInitial,
127      List<? extends ByteSequence> subAnyElements, ByteSequence subFinal) throws DecodeException
128  {
129    return UNDEFINED_ASSERTION;
130  }
131
132  /** {@inheritDoc} */
133  @Override
134  public Assertion getGreaterOrEqualAssertion(Schema schema, ByteSequence value) throws DecodeException
135  {
136    final ByteString normAssertion = normalizeAttributeValue(schema, value);
137    return new Assertion()
138    {
139      @Override
140      public ConditionResult matches(final ByteSequence normalizedAttributeValue)
141      {
142        return ConditionResult.valueOf(normalizedAttributeValue.compareTo(normAssertion) >= 0);
143      }
144
145      @Override
146      public <T> T createIndexQuery(IndexQueryFactory<T> factory) throws DecodeException
147      {
148        return factory.createRangeMatchQuery(ORDERING_ID, normAssertion, ByteString.empty(), true, false);
149      }
150    };
151  }
152
153  /** {@inheritDoc} */
154  @Override
155  public Assertion getLessOrEqualAssertion(Schema schema, ByteSequence value) throws DecodeException
156  {
157    final ByteString normAssertion = normalizeAttributeValue(schema, value);
158    return new Assertion()
159    {
160      @Override
161      public ConditionResult matches(final ByteSequence normalizedAttributeValue)
162      {
163        return ConditionResult.valueOf(normalizedAttributeValue.compareTo(normAssertion) <= 0);
164      }
165
166      @Override
167      public <T> T createIndexQuery(IndexQueryFactory<T> factory) throws DecodeException
168      {
169        return factory.createRangeMatchQuery(ORDERING_ID, ByteString.empty(), normAssertion, false, true);
170      }
171    };
172  }
173
174  @Override
175  public Collection<? extends Indexer> createIndexers(IndexingOptions options)
176  {
177    return indexers;
178  }
179
180  /**
181   * Adds the historical csn ordering matching rule to the provided schema builder.
182   *
183   * @param builder
184   *          where to add the historical csn ordering matching rule
185   * @return the provided builder
186   */
187  public static SchemaBuilder addHistoricalCsnOrderingMatchingRule(SchemaBuilder builder)
188  {
189    return builder
190        .buildMatchingRule(OMR_HISTORICAL_CSN_OID)
191        .names(OMR_HISTORICAL_CSN_NAME)
192        .syntaxOID(SYNTAX_OCTET_STRING_OID)
193        .implementation(new HistoricalCsnOrderingMatchingRuleImpl())
194        .addToSchema();
195  }
196}