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 2017 ForgeRock AS. 015 */ 016package org.opends.server.replication.plugin; 017 018import static org.forgerock.opendj.ldap.Assertion.UNDEFINED_ASSERTION; 019import static org.opends.messages.ReplicationMessages.*; 020import static org.opends.server.replication.plugin.HistoricalCsnOrderingMatchingRuleImpl.ORDERING_ID; 021import static org.opends.server.replication.plugin.HistoricalCsnOrderingMatchingRuleImpl.normalizeCsnAttributeValue; 022import static org.opends.server.replication.plugin.HistoricalCsnRangeMatchingRuleImpl.Operator.*; 023import java.util.Collection; 024import java.util.Collections; 025import java.util.List; 026import java.util.regex.Matcher; 027import java.util.regex.Pattern; 028 029import org.forgerock.opendj.ldap.Assertion; 030import org.forgerock.opendj.ldap.ByteSequence; 031import org.forgerock.opendj.ldap.ByteString; 032import org.forgerock.opendj.ldap.ConditionResult; 033import org.forgerock.opendj.ldap.DecodeException; 034import org.forgerock.opendj.ldap.schema.MatchingRuleImpl; 035import org.forgerock.opendj.ldap.schema.Schema; 036import org.forgerock.opendj.ldap.spi.IndexQueryFactory; 037import org.forgerock.opendj.ldap.spi.Indexer; 038import org.forgerock.opendj.ldap.spi.IndexingOptions; 039import org.forgerock.util.Function; 040import org.forgerock.util.promise.NeverThrowsException; 041import org.opends.server.replication.common.CSN; 042 043/** 044 * Extensible matching rule to allow a range search on CSNs for a given replica. 045 * <p> 046 * It uses the same index as {@link HistoricalCsnOrderingMatchingRuleImpl}, but emulates a range search on 047 * a multivalued attribute. We can do that because the index is ordered by replica id and we're interested on 048 * a range search for a single replica only. 049 */ 050public final class HistoricalCsnRangeMatchingRuleImpl implements MatchingRuleImpl { 051 052 private final Collection<? extends Indexer> indexers = 053 Collections.singleton(new HistoricalCsnOrderingMatchingRuleImpl.HistoricalIndexer()); 054 055 /** 056 * Rule operator 057 */ 058 enum Operator { 059 LESS_THAN("<", new Function<Integer, Boolean, NeverThrowsException>() { 060 public Boolean apply(final Integer v) throws NeverThrowsException { 061 return v < 0; 062 } 063 }), 064 GREATER_THAN(">", new Function<Integer, Boolean, NeverThrowsException>() { 065 public Boolean apply(final Integer v) throws NeverThrowsException { 066 return v > 0; 067 } 068 }), 069 LESS_OR_EQUAL_THAN("<=", new Function<Integer, Boolean, NeverThrowsException>() { 070 public Boolean apply(final Integer v) throws NeverThrowsException { 071 return v <= 0; 072 } 073 }), 074 GREATER_OR_EQUAL_THAN(">=", new Function<Integer, Boolean, NeverThrowsException>() { 075 public Boolean apply(final Integer v) throws NeverThrowsException { 076 return v >= 0; 077 } 078 }); 079 080 private final String comparator; 081 private final Function<Integer, Boolean, NeverThrowsException> evaluationFunction; 082 083 Operator(String op, Function<Integer, Boolean, NeverThrowsException> evaluationFunction) { 084 this.comparator = op; 085 this.evaluationFunction = evaluationFunction; 086 } 087 088 public boolean evaluate(int value) { 089 return evaluationFunction.apply(value); 090 } 091 092 public static Operator parse(String s) throws DecodeException { 093 for (Operator op : values()) { 094 if (op.comparator.equals(s)) { 095 return op; 096 } 097 } 098 throw DecodeException.error(ERR_INVALID_CSN_RANGE_COMPARISON_OPERATOR.get(s)); 099 } 100 } 101 102 /** 103 * Represents a filter for a CSN range on a serverID. 104 * <p> 105 * Expected syntax is: "OP1 CSN1[, OP2 CSN2]"; where OP1 is one of ">", ">=", OP2 is one of "<", "<=" or the other 106 * way around. 107 * <p> 108 * Example of valid filters: 109 * <ul> 110 * <li><= 0000018b6fc1f1861f3003d3e3c1, > 0000018b6fc1f1861f3003d3e3a0</li> 111 * <li>> 0000018b6fc1f1861f3003d3e3a0, <= 0000018b6fc1f1861f3003d3e3c1 (same as before)</li> 112 * <li>>= 0000018b6fc1f1861f3003d3e3c1</li> 113 * <li>< 0000018b6fc1f1861f3003d3e3a0</li> 114 * </ul> 115 * When the specified range is an open interval, the other endpoint is automatically computed 116 * to be the maximum or minimum possible CSN for the serverID in the specified CSN. 117 */ 118 private static final class CsnRangeFilter { 119 private static final String ENDPOINT = "(<|<=|>|>=)\\s*([a-fA-F0-9]{28})"; 120 private static final Pattern PATTERN = Pattern.compile(ENDPOINT + "(\\s*,\\s*" + ENDPOINT + ")?"); 121 final ByteString greaterThanCsn; 122 final ByteString lessThanCsn; 123 final Operator greaterThanOp; 124 final Operator lessThanOp; 125 126 static CsnRangeFilter buildFromAssertion(ByteSequence assertionValue) throws DecodeException { 127 final Matcher matcher = PATTERN.matcher(assertionValue.toString()); 128 if (!matcher.matches()) { 129 throw DecodeException.error(ERR_INVALID_CSN_RANGE_ASSERTION_SYNTAX.get(assertionValue.toString())); 130 } 131 final Operator op1 = Operator.parse(matcher.group(1)); 132 final CSN csn1 = CSN.valueOf(matcher.group(2)); 133 if (matcher.group(3) == null) { 134 return buildFromSingleOperation(op1, csn1); 135 } 136 final Operator op2 = Operator.parse(matcher.group(4)); 137 final CSN csn2 = CSN.valueOf(matcher.group(5)); 138 return buildFromTwoOperations(op1, csn1, op2, csn2); 139 } 140 141 private static CsnRangeFilter buildFromSingleOperation(Operator op, CSN csn) throws DecodeException { 142 if (op == LESS_OR_EQUAL_THAN || op == LESS_THAN) { 143 final CSN minCsn = new CSN(0, 0, csn.getServerId()); 144 return new CsnRangeFilter(GREATER_OR_EQUAL_THAN, minCsn, op, csn); 145 } else { 146 final CSN maxCsn = new CSN(Long.MAX_VALUE, Integer.MAX_VALUE, csn.getServerId()); 147 return new CsnRangeFilter(op, csn, LESS_OR_EQUAL_THAN, maxCsn); 148 } 149 } 150 151 private static CsnRangeFilter buildFromTwoOperations(Operator op1, CSN csn1, Operator op2, CSN csn2) 152 throws DecodeException { 153 if (csn1.getServerId() != csn2.getServerId()) { 154 throw DecodeException.error(ERR_CSN_RANGE_INCLUDES_MORE_THAN_ONE_SERVER.get(csn1, csn2)); 155 } 156 if (op1 == LESS_OR_EQUAL_THAN || op1 == LESS_THAN) { 157 if (op2 == LESS_THAN || op2 == LESS_OR_EQUAL_THAN) { 158 throw DecodeException.error(ERR_CSN_RANGE_SAME_OPERATOR_TYPE.get(op1, op2)); 159 } 160 return new CsnRangeFilter(op2, csn2, op1, csn1); 161 } else { 162 if (op2 == GREATER_THAN || op2 == GREATER_OR_EQUAL_THAN) { 163 throw DecodeException.error(ERR_CSN_RANGE_SAME_OPERATOR_TYPE.get(op1, op2)); 164 } 165 return new CsnRangeFilter(op1, csn1, op2, csn2); 166 } 167 } 168 169 private CsnRangeFilter(Operator greaterThanOp, CSN greaterThanCsn, Operator lessThanOp, CSN lessThanCsn) 170 throws DecodeException { 171 this.greaterThanOp = greaterThanOp; 172 this.greaterThanCsn = normalizeCsnAttributeValue(greaterThanCsn.toString()); 173 this.lessThanOp = lessThanOp; 174 this.lessThanCsn = normalizeCsnAttributeValue(lessThanCsn.toString()); 175 } 176 177 boolean evaluate(final ByteSequence csnValue) { 178 return lessThanOp.evaluate(csnValue.compareTo(lessThanCsn)) 179 && greaterThanOp.evaluate(csnValue.compareTo(greaterThanCsn)); 180 } 181 } 182 183 @Override 184 public Assertion getAssertion(Schema schema, ByteSequence assertionValue) throws DecodeException { 185 final CsnRangeFilter filter = CsnRangeFilter.buildFromAssertion(assertionValue); 186 187 return new Assertion() { 188 @Override 189 public ConditionResult matches(final ByteSequence attributeValue) { 190 return ConditionResult.valueOf(filter.evaluate(attributeValue)); 191 } 192 193 @Override 194 public <T> T createIndexQuery(IndexQueryFactory<T> factory) throws DecodeException { 195 return factory.createRangeMatchQuery(ORDERING_ID, 196 filter.greaterThanCsn, 197 filter.lessThanCsn, 198 filter.greaterThanOp == GREATER_OR_EQUAL_THAN, 199 filter.lessThanOp == LESS_OR_EQUAL_THAN); 200 } 201 }; 202 } 203 204 @Override 205 public Assertion getSubstringAssertion(Schema schema, ByteSequence subInitial, 206 List<? extends ByteSequence> subAnyElements, ByteSequence subFinal) throws DecodeException { 207 return UNDEFINED_ASSERTION; 208 } 209 210 @Override 211 public Assertion getGreaterOrEqualAssertion(Schema schema, ByteSequence value) throws DecodeException { 212 return UNDEFINED_ASSERTION; 213 } 214 215 @Override 216 public Assertion getLessOrEqualAssertion(Schema schema, ByteSequence value) throws DecodeException { 217 return UNDEFINED_ASSERTION; 218 } 219 220 @Override 221 public ByteString normalizeAttributeValue(Schema schema, ByteSequence value) throws DecodeException { 222 return HistoricalCsnOrderingMatchingRuleImpl.normalizeAttributeValue(value); 223 } 224 225 @Override 226 public Collection<? extends Indexer> createIndexers(IndexingOptions options) { 227 return indexers; 228 } 229}