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