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-2016 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     *          When creating a replica DB Cursor, this is the default CSN to
142     *          use for replicas which do not have an associated CSN
143     */
144    public CursorOptions(KeyMatchingStrategy keyMatchingStrategy, PositionStrategy positionStrategy, CSN defaultCSN)
145    {
146      this.keyMatchingStrategy = keyMatchingStrategy;
147      this.positionStrategy = positionStrategy;
148      this.defaultCSN = defaultCSN;
149    }
150
151    /**
152     * Returns the key matching strategy.
153     *
154     * @return the key matching strategy
155     */
156    public KeyMatchingStrategy getKeyMatchingStrategy()
157    {
158      return keyMatchingStrategy;
159    }
160
161    /**
162     * Returns the position strategy.
163     *
164     * @return the position strategy
165     */
166    public PositionStrategy getPositionStrategy()
167    {
168      return positionStrategy;
169    }
170
171    /**
172     * Returns the default CSN.
173     *
174     * @return the default CSN
175     */
176    public CSN getDefaultCSN()
177    {
178      return defaultCSN;
179    }
180
181    @Override
182    public boolean equals(Object obj)
183    {
184      if (this == obj) {
185        return true;
186      }
187      if (obj instanceof CursorOptions) {
188        CursorOptions other = (CursorOptions) obj;
189        return keyMatchingStrategy == other.keyMatchingStrategy
190            && positionStrategy == other.positionStrategy
191            && Objects.equals(defaultCSN, other.defaultCSN);
192      }
193      return false;
194    }
195
196    @Override
197    public int hashCode()
198    {
199      final int prime = 31;
200      int result = 1;
201      result = prime * result + ((keyMatchingStrategy == null) ? 0 : keyMatchingStrategy.hashCode());
202      result = prime * result + ((positionStrategy == null) ? 0 : positionStrategy.hashCode());
203      result = prime * result + ((defaultCSN == null) ? 0 : defaultCSN.hashCode());
204      return result;
205    }
206
207    @Override
208    public String toString()
209    {
210      return getClass().getSimpleName()
211          + " [keyMatchingStrategy=" + keyMatchingStrategy
212          + ", positionStrategy=" + positionStrategy
213          + ", defaultCSN=" + defaultCSN + "]";
214    }
215  }
216
217  /**
218   * Getter for the current record.
219   *
220   * @return The current record.
221   */
222  T getRecord();
223
224  /**
225   * Skip to the next record of the database.
226   *
227   * @return true if has next, false otherwise
228   * @throws ChangelogException
229   *           When database exception raised.
230   */
231  boolean next() throws ChangelogException;
232
233  /**
234   * Release the resources and locks used by this Iterator. This method must be
235   * called when the iterator is no longer used. Failure to do it could cause DB
236   * deadlock.
237   */
238  @Override
239  void close();
240}