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 2013-2017 ForgeRock AS.
015 */
016package org.opends.server.replication.server.changelog.api;
017
018import java.io.Closeable;
019import java.util.Objects;
020
021import net.jcip.annotations.NotThreadSafe;
022
023import org.opends.server.replication.common.CSN;
024
025/**
026 * Generic cursor interface into the changelog database. Once it is not used
027 * anymore, a cursor must be closed to release all the resources into the
028 * database.
029 * <p>
030 * The cursor provides a java.sql.ResultSet like API : it is positioned before
031 * the first requested record and needs to be moved forward by calling
032 * {@link DBCursor#next()}.
033 * <p>
034 * Usage:
035 * <pre>
036 *  DBCursor cursor = ...;
037 *  try {
038 *    while (cursor.next()) {
039 *      Record record = cursor.getRecord();
040 *      // ... can call cursor.getRecord() again: it will return the same result
041 *    }
042 *  }
043 *  finally {
044 *    close(cursor);
045 *  }
046 * }
047 * </pre>
048 *
049 * A cursor can be initialised from a key, using a {@code KeyMatchingStrategy} and
050 * a {@code PositionStrategy}, to determine the exact starting position.
051 * <p>
052 * Let's call Kp the highest key lower than K and Kn the lowest key higher
053 * than K : Kp &lt; K &lt; Kn
054 * <ul>
055 *  <li>When using EQUAL_TO_KEY on key K :
056 *   <ul>
057 *    <li>with ON_MATCHING_KEY, cursor is positioned on key K (if K exists in log),
058 *        otherwise it is empty</li>
059 *    <li>with AFTER_MATCHING_KEY, cursor is positioned on key Kn (if K exists in log),
060 *        otherwise it is empty</li>
061 *   </ul>
062 *  </li>
063 *  <li>When using LESS_THAN_OR_EQUAL_TO_KEY on key K :
064 *   <ul>
065 *    <li>with ON_MATCHING_KEY, cursor is positioned on key K (if K exists in log)
066 *        or else Kp (if Kp exists in log), otherwise it is empty</li>
067 *    <li>with AFTER_MATCHING_KEY, cursor is positioned on key Kn (if Kp or K exist in log),
068 *        otherwise it is empty</li>
069 *   </ul>
070 *  </li>
071 *  <li>When using GREATER_THAN_OR_EQUAL_TO_KEY on key K :
072 *   <ul>
073 *    <li>with ON_MATCHING_KEY, cursor is positioned on key K (if K exists in log)
074 *        or else Kn (if Kn exists in log), otherwise it is empty</li>
075 *    <li>with AFTER_MATCHING_KEY, cursor is positioned on key Kn (if K or Kn exist in log),
076 *        otherwise it is empty</li>
077 *   </ul>
078 *  </li>
079 * </ul>
080 *
081 * @param <T>
082 *          type of the record being returned
083 */
084@NotThreadSafe
085public interface DBCursor<T> extends Closeable
086{
087
088  /**
089   * Represents a cursor key matching strategy, which allow to choose if only
090   * the exact key must be found or if any key equal or lower/higher should match.
091   */
092  public enum KeyMatchingStrategy {
093    /** Matches if the key or a lower key is found. */
094    LESS_THAN_OR_EQUAL_TO_KEY,
095    /** Matches only if the exact key is found. */
096    EQUAL_TO_KEY,
097    /** Matches if the key or a greater key is found. */
098    GREATER_THAN_OR_EQUAL_TO_KEY
099  }
100
101  /**
102   * Represents a cursor positioning strategy, which allow to choose if the start point
103   * corresponds to the record at the provided key or the record just after the provided
104   * key.
105   */
106  public enum PositionStrategy {
107    /** Start point is on the matching key. */
108    ON_MATCHING_KEY,
109    /** Start point is after the matching key. */
110    AFTER_MATCHING_KEY
111  }
112
113  /** Options to create a cursor. */
114  public static final class CursorOptions
115  {
116    private final KeyMatchingStrategy keyMatchingStrategy;
117    private final PositionStrategy positionStrategy;
118    private final CSN defaultCSN;
119
120    /**
121     * Creates options with provided strategies.
122     *
123     * @param keyMatchingStrategy
124     *          The key matching strategy
125     * @param positionStrategy
126     *          The position strategy
127     */
128    public CursorOptions(KeyMatchingStrategy keyMatchingStrategy, PositionStrategy positionStrategy)
129    {
130      this(keyMatchingStrategy, positionStrategy, null);
131    }
132
133    /**
134     * Creates options with provided strategies and default CSN.
135     *
136     * @param keyMatchingStrategy
137     *          The key matching strategy
138     * @param positionStrategy
139     *          The position strategy
140     * @param defaultCSN
141     *            The default CSN to use for replica DB cursors without an associated CSN. May be {@code null}.
142     *            When creating a replica DB Cursor, some replicas may not have an associated CSN within the
143     *            provided server state / multi-domain server state. In that case, the cursors will be opened
144     *            relative to this CSN, in accordance with the supplied matching and positioning strategy.
145     *            For example, providing a default CSN with {@code KeyMatchingStrategy.GREATER_THAN_OR_EQUAL_TO_KEY}
146     *            and {@code PositionStrategy.AFTER_MATCHING_KEY}, means the cursor will first return the record
147     *            immediately newer than the provided default CSN.
148     */
149    public CursorOptions(KeyMatchingStrategy keyMatchingStrategy, PositionStrategy positionStrategy, CSN defaultCSN)
150    {
151      this.keyMatchingStrategy = keyMatchingStrategy;
152      this.positionStrategy = positionStrategy;
153      this.defaultCSN = defaultCSN;
154    }
155
156    /**
157     * Returns the key matching strategy.
158     *
159     * @return the key matching strategy
160     */
161    public KeyMatchingStrategy getKeyMatchingStrategy()
162    {
163      return keyMatchingStrategy;
164    }
165
166    /**
167     * Returns the position strategy.
168     *
169     * @return the position strategy
170     */
171    public PositionStrategy getPositionStrategy()
172    {
173      return positionStrategy;
174    }
175
176    /**
177     * Returns the default CSN.
178     *
179     * @return the default CSN
180     */
181    public CSN getDefaultCSN()
182    {
183      return defaultCSN;
184    }
185
186    @Override
187    public boolean equals(Object obj)
188    {
189      if (this == obj) {
190        return true;
191      }
192      if (obj instanceof CursorOptions) {
193        CursorOptions other = (CursorOptions) obj;
194        return keyMatchingStrategy == other.keyMatchingStrategy
195            && positionStrategy == other.positionStrategy
196            && Objects.equals(defaultCSN, other.defaultCSN);
197      }
198      return false;
199    }
200
201    @Override
202    public int hashCode()
203    {
204      final int prime = 31;
205      int result = 1;
206      result = prime * result + ((keyMatchingStrategy == null) ? 0 : keyMatchingStrategy.hashCode());
207      result = prime * result + ((positionStrategy == null) ? 0 : positionStrategy.hashCode());
208      result = prime * result + ((defaultCSN == null) ? 0 : defaultCSN.hashCode());
209      return result;
210    }
211
212    @Override
213    public String toString()
214    {
215      return getClass().getSimpleName()
216          + " [keyMatchingStrategy=" + keyMatchingStrategy
217          + ", positionStrategy=" + positionStrategy
218          + ", defaultCSN=" + defaultCSN + "]";
219    }
220  }
221
222  /**
223   * Getter for the current record.
224   *
225   * @return The current record.
226   */
227  T getRecord();
228
229  /**
230   * Skip to the next record of the database.
231   *
232   * @return true if has next, false otherwise
233   * @throws ChangelogException
234   *           When database exception raised.
235   */
236  boolean next() throws ChangelogException;
237
238  /**
239   * Release the resources and locks used by this Iterator. This method must be
240   * called when the iterator is no longer used. Failure to do it could cause DB
241   * deadlock.
242   */
243  @Override
244  void close();
245}