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    }