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 < K < 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}