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 010 * docs/licenses/cddl.txt 011 * or http://www.opensource.org/licenses/cddl1.php. 012 * See the License for the specific language governing permissions 013 * and limitations under the License. 014 * 015 * When distributing Covered Code, include this CDDL HEADER in each 016 * file and include the License file at 017 * docs/licenses/cddl.txt. If applicable, 018 * add the following below this CDDL HEADER, with the fields enclosed 019 * by brackets "[]" replaced with your own identifying information: 020 * Portions Copyright [yyyy] [name of copyright owner] 021 * 022 * CDDL HEADER END 023 * 024 * 025 * Copyright 2010-2012 UnboundID Corp. 026 */ 027 package com.unboundid.directory.sdk.sync.types; 028 029 import java.util.Arrays; 030 import java.util.Date; 031 import java.util.Map; 032 import java.util.concurrent.ConcurrentHashMap; 033 import java.util.concurrent.atomic.AtomicLong; 034 035 import com.unboundid.ldap.sdk.DN; 036 import com.unboundid.util.InternalUseOnly; 037 import com.unboundid.util.ThreadSafety; 038 import com.unboundid.util.ThreadSafetyLevel; 039 040 import com.unboundid.directory.sdk.sync.util.ScriptUtils; 041 042 /** 043 * This class represents the basis for a single database change record. A 044 * ScriptedJDBCSyncSource implementation should create instances of this 045 * class based on changes detected in the database (either from a changelog 046 * table or some other change tracking mechanism). The resync process will also 047 * use instances of this class to identify database entries, which can then be 048 * fetched. 049 */ 050 @ThreadSafety(level = ThreadSafetyLevel.COMPLETELY_THREADSAFE) 051 public final class DatabaseChangeRecord 052 { 053 054 /** Potential types of database changes. */ 055 public static enum ChangeType 056 { 057 /** 058 * Represents a database insert. 059 */ 060 insert, 061 /** 062 * Represents a database delete. 063 */ 064 delete, 065 /** 066 * Represents a database update. 067 */ 068 update, 069 /** 070 * Represents a resync operation. This is a special type used by the resync 071 * command-line tool. See 072 * <code>ScriptedJDBCSyncSource#listAllEntries()</code> for more 073 * information. 074 */ 075 resync 076 } 077 078 // the basic, common attributes of a DatabaseChangeRecord. 079 private final long changeNumber; 080 private final ChangeType changeType; 081 private final String tableName; 082 private final DN identifiableInfo; 083 private final String entryType; 084 private final String[] changedColumns; 085 private final String modifier; 086 private final long changeTime; 087 private final Map<Object, Object> properties; 088 private CompletionStatus completionStatus; 089 090 private static final AtomicLong uniqueIdSequence = new AtomicLong(0); 091 092 /** 093 * Private constructor, uses Builder to construct immutable instance. 094 * @param bldr the Builder from which to create this change record. 095 */ 096 private DatabaseChangeRecord(final Builder bldr) 097 { 098 // if changeNumber is not set, assign it an auto-incrementing value. we need 099 // this to have a way to identify a specific change and the order in which 100 // changes are processed 101 changeNumber = (bldr.changeNumber == -1) ? 102 uniqueIdSequence.getAndIncrement() : bldr.changeNumber; 103 changeType = bldr.changeType; 104 identifiableInfo = bldr.identifiableInfo; 105 tableName = bldr.tableName; 106 entryType = bldr.entryType; 107 changedColumns = bldr.changedColumns; 108 modifier = bldr.modifier; 109 changeTime = bldr.changeTime; 110 properties = bldr.properties; 111 } 112 113 /** 114 * Get the change number that identifies this particular change. If a change 115 * number 116 * is not used by the database for change detection, this method will return 117 * a monotonically increasing sequence number for this change record, so that 118 * you can 119 * still identify the order in which changes were detected. 120 * @return the changeNumber 121 */ 122 public long getChangeNumber() 123 { 124 return changeNumber; 125 } 126 127 /** 128 * Get the change type (insert/update/delete/resync). 129 * @return the changeType 130 */ 131 public ChangeType getChangeType() 132 { 133 return changeType; 134 } 135 136 /** 137 * Get the database table on which the change occurred. 138 * @return the table name 139 */ 140 public String getTableName() 141 { 142 return tableName; 143 } 144 145 /** 146 * Get the DN that identifies the row that changed (for example 147 * "account_id=123"). If multiple attributes are part of the identifier, 148 * they will be represented as different 149 * RDN components of the DN in the order they were originally specified 150 * (for example "account_id=123,group_id=5"). 151 * @return an identifier string 152 */ 153 public DN getIdentifiableInfo() 154 { 155 return identifiableInfo; 156 } 157 158 /** 159 * Get the database entry type that this change corresponds to (for example 160 * "account" or "subscriber"). 161 * @return the type of database entry 162 */ 163 public String getEntryType() 164 { 165 return entryType; 166 } 167 168 /** 169 * Get the set of changed columns for this change entry. 170 * @return an array of column names that were modified as part of the change 171 */ 172 public String[] getChangedColumns() 173 { 174 return changedColumns; 175 } 176 177 /** 178 * Get the database user that made the change. 179 * @return the database user account 180 */ 181 public String getModifier() 182 { 183 return modifier; 184 } 185 186 /** 187 * Get the time at which the change occurred. This is based on the clock on 188 * the database server. 189 * @return the change time (in milliseconds since epoch) 190 */ 191 public long getChangeTime() 192 { 193 return changeTime; 194 } 195 196 /** 197 * Get the property value (if one exists) for the given key. 198 * @param key 199 * the property key whose value to return 200 * @return the property value 201 */ 202 public Object getProperty(final Object key) 203 { 204 return properties.get(key); 205 } 206 207 /** 208 * This method is used by the Sync Pipe to indicate if the completion status 209 * of a synchronization operation. This is for internal use only 210 * and should not be called by clients. 211 * @param status the completion status for this DatabaseChangeRecord 212 */ 213 @InternalUseOnly 214 public synchronized void setCompletionStatus(final CompletionStatus status) 215 { 216 this.completionStatus = status; 217 } 218 219 /** 220 * Gets the completion status for this change. This will be null if the change 221 * has not finished processing yet. 222 * @return the CompletionStatus indicating whether this change completed 223 * successfully or else a reason why it failed 224 */ 225 public synchronized CompletionStatus getCompletionStatus() 226 { 227 return completionStatus; 228 } 229 230 /** 231 * This class is used to construct DatabaseChangeRecord instances. At least a 232 * {@link ChangeType} and an identifiableInfo string are required; the rest of 233 * the parameters are optional. 234 * Arbitrary properties can also be added to the object by calling 235 * {@link #addProperty(Object, Object)}. The setter methods return the Builder 236 * instance itself, so that these calls can be chained. When finished setting 237 * up parameters, call the {@link #build()} method to create a new 238 * {@link DatabaseChangeRecord}. 239 */ 240 public static class Builder 241 { 242 // required parameters 243 private final ChangeType changeType; 244 private final DN identifiableInfo; 245 246 // optional parameters 247 private long changeNumber; 248 private String tableName; 249 private String entryType; 250 private String[] changedColumns; 251 private String modifier; 252 private long changeTime; 253 254 // various other values that are attached to the change record 255 private final Map<Object, Object> properties = 256 new ConcurrentHashMap<Object, Object>(); 257 258 // flag to indicate whether this builder has been built 259 private boolean built; 260 261 /** 262 * Creates a Builder which can be used to construct a DatabaseChangeRecord. 263 * @param type 264 * the ChangeType (insert/update/delete/resync) 265 * @param identifiableInfo 266 * a unique identifier for the row that changed 267 * (i.e. "account_id=123"). If multiple attributes are part of 268 * the identifier, they should be separate RDN components of the DN 269 * (i.e. "account_id=123,group_id=5"). The order of the RDN 270 * components does not matter here. 271 */ 272 public Builder(final ChangeType type, final DN identifiableInfo) 273 { 274 if(type == null || identifiableInfo == null) 275 { 276 throw new IllegalArgumentException( 277 "The 'type' and 'identifiableInfo' parameters can not be null."); 278 } 279 this.changeType = type; 280 this.identifiableInfo = identifiableInfo; 281 this.changeNumber = -1; 282 this.built = false; 283 } 284 285 /** 286 * Creates a Builder which can be used to construct a DatabaseChangeRecord. 287 * @param type 288 * the ChangeType (insert/update/delete/resync) 289 * @param identifiableInfo 290 * a unique identifier for the row that changed 291 * (i.e. "account_id=123"). If multiple attributes are part of 292 * the identifier, they should be delimited with the default 293 * delimiter of "%%" (i.e. "account_id=123%%group_id=5"). 294 * The order of the components does not matter here. 295 */ 296 public Builder(final ChangeType type, final String identifiableInfo) 297 { 298 this(type, ScriptUtils.idStringToDN(identifiableInfo, null)); 299 } 300 301 /** 302 * Creates a Builder which can be used to construct a DatabaseChangeRecord. 303 * @param type 304 * the ChangeType (insert/update/delete/resync) 305 * @param identifiableInfo 306 * a unique identifier for the row that changed 307 * (i.e. "account_id=123"). If multiple attributes are part of 308 * the identifier, they should be delimited with a unique string 309 * (i.e. "account_id=123%%group_id=5") which is specified by the 310 * <i>delimiter</i> parameter. The order of the components does 311 * not matter here. 312 * @param delimiter 313 * The delimiter used to split separate components of the 314 * identifiable info. If this is null, the default of "%%" will be 315 * used. 316 */ 317 public Builder(final ChangeType type, final String identifiableInfo, 318 final String delimiter) 319 { 320 this(type, ScriptUtils.idStringToDN(identifiableInfo, delimiter)); 321 } 322 323 /** 324 * Set the change number that identifies this particular change (if 325 * applicable). 326 * @param changeNumber 327 * the change number 328 * @return the Builder instance 329 */ 330 public Builder changeNumber(final long changeNumber) 331 { 332 this.changeNumber = changeNumber; 333 return this; 334 } 335 336 /** 337 * Set the database table on which the change occurred. 338 * @param tableName 339 * the table name 340 * @return the Builder instance 341 */ 342 public Builder tableName(final String tableName) 343 { 344 this.tableName = tableName; 345 return this; 346 } 347 348 /** 349 * Set the database entry type that this change corresponds to (for example 350 * "account" 351 * or "subscriber"). 352 * @param entryType 353 * the type of database entry 354 * @return the Builder instance 355 */ 356 public Builder entryType(final String entryType) 357 { 358 this.entryType = entryType; 359 return this; 360 } 361 362 /** 363 * Set the set of changed columns for this change entry. 364 * @param changedColumns 365 * an array of column names that were modified as part of the 366 * change 367 * @return the Builder instance 368 */ 369 public Builder changedColumns(final String[] changedColumns) 370 { 371 this.changedColumns = changedColumns; 372 return this; 373 } 374 375 /** 376 * Set the database user that made the change. 377 * @param modifier 378 * the database account name 379 * @return the Builder instance 380 */ 381 public Builder modifier(final String modifier) 382 { 383 this.modifier = modifier; 384 return this; 385 } 386 387 /** 388 * Set the time at which the change occurred. This should be based on the 389 * database server clock. 390 * @param changeTime 391 * the time of the change (in milliseconds since epoch) 392 * @return the Builder instance 393 */ 394 public Builder changeTime(final long changeTime) 395 { 396 this.changeTime = changeTime; 397 return this; 398 } 399 400 /** 401 * Add an arbitrary attachment or property to the DatabaseChangeRecord being 402 * built. 403 * @param key 404 * the key for the property 405 * @param value 406 * the value of the property 407 * @return the Builder instance 408 */ 409 public Builder addProperty(final Object key, final Object value) 410 { 411 this.properties.put(key, value); 412 return this; 413 } 414 415 /** 416 * Construct the DatabaseChangeRecord. This method may only be called once. 417 * @return a DatabaseChangeRecord instance 418 */ 419 public synchronized DatabaseChangeRecord build() 420 { 421 if(built) 422 { 423 throw new IllegalStateException("This Builder has already been built."); 424 } 425 built = true; 426 return new DatabaseChangeRecord(this); 427 } 428 } 429 430 /** 431 * {@inheritDoc} 432 */ 433 @Override 434 public String toString() 435 { 436 return "DatabaseChangeRecord [changeNumber=" + changeNumber + 437 ", changeType=" + 438 changeType + ", tableName=" + tableName + ", identifiableInfo=" + 439 identifiableInfo + 440 ", entryType=" + entryType + 441 ", changedColumns=" + Arrays.toString(changedColumns) + 442 ", modifier=" + modifier + ", changeTime=" + 443 (new Date(changeTime)).toString() + 444 ", properties=" + properties + "]"; 445 } 446 }