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 }