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.scripting;
028    
029    import java.util.List;
030    
031    import com.unboundid.directory.sdk.common.internal.Configurable;
032    import com.unboundid.directory.sdk.sync.config.SyncDestinationConfig;
033    import com.unboundid.directory.sdk.sync.internal.SynchronizationServerExtension;
034    import com.unboundid.directory.sdk.sync.types.EndpointException;
035    import com.unboundid.directory.sdk.sync.types.SyncOperation;
036    import com.unboundid.directory.sdk.sync.types.SyncServerContext;
037    import com.unboundid.ldap.sdk.Entry;
038    import com.unboundid.ldap.sdk.Modification;
039    import com.unboundid.util.Extensible;
040    import com.unboundid.util.ThreadSafety;
041    import com.unboundid.util.ThreadSafetyLevel;
042    import com.unboundid.util.args.ArgumentException;
043    import com.unboundid.util.args.ArgumentParser;
044    
045    /**
046     * This class defines an API that must be implemented by scripted extensions
047     * that wish to push changes processed by the Synchronization Server to an
048     * arbitrary destination. This type of sync destination is generic and can
049     * support a wide range of endpoints. In addition, this type of sync destination
050     * supports one-way notifications, where the source and destination entries are
051     * never compared but instead changes are pushed straight through.
052     *
053     * <H2>Configuring Scripted Sync Destinations</H2>
054     * In order to configure a sync destination created using this API, use
055     * a command like:
056     * <PRE>
057     *      dsconfig create-sync-destination \
058     *           --sync-destination-name "<I>{name}</I>" \
059     *           --type groovy-scripted \
060     *           --set "script-class:<I>{class-name}</I>" \
061     *           --set "script-argument:<I>{name=value}</I>"
062     * </PRE>
063     * where "<I>{name}</I>" is the name to use for the sync destination
064     * instance, "<I>{script-name}</I>" is the fully-qualified name of the Java
065     * class that extends
066     * {@code com.unboundid.directory.sdk.sync.scripting.ScriptedSyncDestination},
067     * and "<I>{name=value}</I>" represents name-value pairs for any arguments to
068     * provide to the sync destination. If multiple arguments should be
069     * provided to extension, then the
070     * "<CODE>--set script-argument:<I>{name=value}</I></CODE>" option should be
071     * provided multiple times.
072     *
073     * @see
074     *    com.unboundid.directory.sdk.sync.api.SyncDestination
075     */
076    @Extensible()
077    @SynchronizationServerExtension(appliesToLocalContent=false,
078                                    appliesToSynchronizedContent=true)
079    @ThreadSafety(level = ThreadSafetyLevel.MOSTLY_THREADSAFE)
080    public abstract class ScriptedSyncDestination implements Configurable
081    {
082    
083      /**
084       * {@inheritDoc}
085       */
086      public void defineConfigArguments(final ArgumentParser parser)
087             throws ArgumentException
088      {
089        // No arguments will be allowed by default.
090      }
091    
092    
093    
094      /**
095       * Initializes this sync destination. This is called when a Sync Pipe
096       * first starts up, or when the <i>resync</i> process first starts up. Any
097       * initialization should be performed here. This method should generally store
098       * the {@link SyncServerContext} in a class member so that it can be used
099       * elsewhere in the implementation.
100       * <p>
101       * The default implementation is empty.
102       *
103       * @param  serverContext  A handle to the server context for the server in
104       *                        which this extension is running. Extensions should
105       *                        typically store this in a class member.
106       * @param  config         The general configuration for this object.
107       * @param  parser         The argument parser which has been initialized from
108       *                        the configuration for this sync destination.
109       * @throws  EndpointException
110       *                        if a problem occurs while initializing this
111       *                        sync destination.
112       */
113      public void initializeSyncDestination(
114                                         final SyncServerContext serverContext,
115                                         final SyncDestinationConfig config,
116                                         final ArgumentParser parser)
117                                                 throws EndpointException
118      {
119        // No initialization will be performed by default.
120      }
121    
122    
123    
124      /**
125       * This hook is called when a Sync Pipe shuts down, or when the <i>resync</i>
126       * process shuts down. Any clean-up of this sync destination should be
127       * performed here.
128       * <p>
129       * The default implementation is empty.
130       */
131      public void finalizeSyncDestination()
132      {
133        // No implementation is performed by default.
134      }
135    
136    
137    
138      /**
139       * Return the URL or path identifying the destination endpoint
140       * to which this extension is transmitting data. This is used for logging
141       * purposes only, so it could just be a server name or hostname and port, etc.
142       *
143       * @return the path to the destination endpoint
144       */
145      public abstract String getCurrentEndpointURL();
146    
147    
148    
149      /**
150       * Return a full destination entry (in LDAP form) from the destination
151       * endpoint, corresponding to the the source {@link Entry} that is passed in.
152       * This method should perform any queries necessary to gather the latest
153       * values for all the attributes to be synchronized and return them in an
154       * Entry.
155       * <p>
156       * This method only needs to be implemented if the 'synchronization-mode' on
157       * the Sync Pipe is set to 'standard'. If it is set to 'notification', this
158       * method will never be called, and the pipe will pass changes straight
159       * through to one of {@link #createEntry}, {@link #modifyEntry}, or
160       * {@link #deleteEntry}.
161       * <p>
162       * Note that the if the source entry was renamed (see
163       * {@link SyncOperation#isModifyDN}), the
164       * <code>destEntryMappedFromSrc</code> will have the new DN; the old DN can
165       * be obtained by calling
166       * {@link SyncOperation#getDestinationEntryBeforeChange()} and getting the DN
167       * from there. This method should return the entry in its existing form
168       * (i.e. with the old DN, before it is changed).
169       * <p>
170       * This method <b>must be thread safe</b>, as it will be called repeatedly and
171       * concurrently by each of the Sync Pipe worker threads as they process
172       * entries.
173       * @param destEntryMappedFromSrc
174       *          the LDAP entry which corresponds to the destination "entry" to
175       *          fetch
176       * @param  operation
177       *          the sync operation for this change
178       * @return a list containing the full LDAP entries that matched this search
179       *          (there may be more than one), or an empty list if no such entry
180       *          exists
181       * @throws EndpointException
182       *           if there is an error fetching the entry
183       */
184      public List<Entry> fetchEntry(final Entry destEntryMappedFromSrc,
185                                    final SyncOperation operation)
186                                        throws EndpointException
187      {
188        throw new UnsupportedOperationException(
189                     "The fetchEntry() method must be implemented in the " +
190                     "ScriptedSyncDestination if the Sync Pipe is " +
191                     "running in standard mode (see 'synchronization-mode' " +
192                     "in the Sync Pipe configuration).");
193      }
194    
195    
196    
197      /**
198       * Creates a full destination "entry", corresponding to the the LDAP
199       * {@link Entry} that is passed in. This method is responsible for
200       * transforming the contents of the entry into the desired format and
201       * transmitting it to the target destination. It should perform any inserts or
202       * updates necessary to make sure the entry is fully created on the
203       * destination endpoint.
204       * <p>
205       * This method <b>must be thread safe</b>, as it will be called repeatedly and
206       * concurrently by the Sync Pipe worker threads as they process CREATE
207       * operations.
208       * @param entryToCreate
209       *          the LDAP entry which corresponds to the destination
210       *          "entry" to create
211       * @param  operation
212       *          the sync operation for this change
213       * @throws EndpointException
214       *           if there is an error creating the entry
215       */
216      public abstract void createEntry(final Entry entryToCreate,
217                                       final SyncOperation operation)
218                                          throws EndpointException;
219    
220    
221    
222      /**
223       * Modify an "entry" on the destination, corresponding to the the LDAP
224       * {@link Entry} that is passed in. This method is responsible for
225       * transforming the contents of the entry into the desired format and
226       * transmitting it to the target destination. It may perform multiple updates
227       * (including inserting or deleting other attributes) in order to fully
228       * synchronize the entire entry on the destination endpoint.
229       * <p>
230       * Note that the if the source entry was renamed (see
231       * {@link SyncOperation#isModifyDN}), the
232       * <code>fetchedDestEntry</code> will have the old DN; the new DN can
233       * be obtained by calling
234       * {@link SyncOperation#getDestinationEntryAfterChange()} and getting the DN
235       * from there.
236       * <p>
237       * This method <b>must be thread safe</b>, as it will be called repeatedly and
238       * concurrently by the Sync Pipe worker threads as they process MODIFY
239       * operations.
240       * @param entryToModify
241       *          the LDAP entry which corresponds to the destination
242       *          "entry" to modify. If the synchronization mode is 'standard',
243       *          this will be the entry that was returned by {@link #fetchEntry};
244       *          otherwise if the synchronization mode is 'notification', this
245       *          will be the mapped destination entry.
246       * @param modsToApply
247       *          a list of Modification objects which should be applied
248       * @param  operation
249       *          the sync operation for this change
250       * @throws EndpointException
251       *           if there is an error modifying the entry
252       */
253      public abstract void modifyEntry(final Entry entryToModify,
254                                       final List<Modification> modsToApply,
255                                       final SyncOperation operation)
256                                              throws EndpointException;
257    
258    
259    
260      /**
261       * Delete a full "entry" from the destination, corresponding to the the LDAP
262       * {@link Entry} that is passed in. This method may perform multiple deletes
263       * or updates if necessary to fully delete the entry from the destination
264       * endpoint.
265       * <p>
266       * This method <b>must be thread safe</b>, as it will be called repeatedly and
267       * concurrently by the Sync Pipe worker threads as they process DELETE
268       * operations.
269       * @param entryToDelete
270       *          the LDAP entry which corresponds to the destination
271       *          "entry" to delete. If the synchronization mode is 'standard',
272       *          this will be the entry that was returned by {@link #fetchEntry};
273       *          otherwise if the synchronization mode is 'notification', this
274       *          will be the mapped destination entry.
275       * @param  operation
276       *          the sync operation for this change
277       * @throws EndpointException
278       *           if there is an error deleting the entry
279       */
280      public abstract void deleteEntry(final Entry entryToDelete,
281                                       final SyncOperation operation)
282                                            throws EndpointException;
283    
284    }