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