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