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     * trunk/opends/resource/legal-notices/OpenDS.LICENSE
011     * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
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     * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  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 2006-2008 Sun Microsystems, Inc.
026     */
027    package org.opends.server.plugins;
028    
029    
030    
031    import java.util.ArrayList;
032    import java.util.Collections;
033    import java.util.LinkedHashSet;
034    import java.util.List;
035    import java.util.Map;
036    import java.util.Set;
037    import java.util.UUID;
038    
039    import org.opends.messages.Message;
040    import org.opends.server.admin.server.ConfigurationChangeListener;
041    import org.opends.server.admin.std.meta.PluginCfgDefn;
042    import org.opends.server.admin.std.server.EntryUUIDPluginCfg;
043    import org.opends.server.admin.std.server.PluginCfg;
044    import org.opends.server.api.plugin.*;
045    import org.opends.server.config.ConfigException;
046    import org.opends.server.types.Attribute;
047    import org.opends.server.types.AttributeType;
048    import org.opends.server.types.AttributeUsage;
049    import org.opends.server.types.AttributeValue;
050    import org.opends.server.types.ByteStringFactory;
051    import org.opends.server.types.ConfigChangeResult;
052    import org.opends.server.types.DirectoryConfig;
053    import org.opends.server.types.Entry;
054    import org.opends.server.types.LDIFImportConfig;
055    import org.opends.server.types.ResultCode;
056    import org.opends.server.types.operation.PreOperationAddOperation;
057    
058    import static org.opends.messages.PluginMessages.*;
059    import static org.opends.server.util.StaticUtils.*;
060    
061    
062    
063    /**
064     * This class implements a Directory Server plugin that will add the entryUUID
065     * attribute to an entry whenever it is added or imported as per RFC 4530.  For
066     * entries added over LDAP, the entryUUID will be based on a semi-random UUID
067     * (which is still guaranteed to be unique).  For entries imported from LDIF,
068     * the UUID will be constructed from the entry DN using a repeatable algorithm.
069     * This will ensure that LDIF files imported in parallel across multiple systems
070     * will have identical entryUUID values.
071     */
072    public final class EntryUUIDPlugin
073           extends DirectoryServerPlugin<EntryUUIDPluginCfg>
074           implements ConfigurationChangeListener<EntryUUIDPluginCfg>
075    {
076      /**
077       * The name of the entryUUID attribute type.
078       */
079      private static final String ENTRYUUID = "entryuuid";
080    
081    
082    
083      // The attribute type for the "entryUUID" attribute.
084      private final AttributeType entryUUIDType;
085    
086      // The current configuration for this plugin.
087      private EntryUUIDPluginCfg currentConfig;
088    
089    
090    
091      /**
092       * Creates a new instance of this Directory Server plugin.  Every plugin must
093       * implement a default constructor (it is the only one that will be used to
094       * create plugins defined in the configuration), and every plugin constructor
095       * must call <CODE>super()</CODE> as its first element.
096       */
097      public EntryUUIDPlugin()
098      {
099        super();
100    
101    
102        // Get the entryUUID attribute type.  This needs to be done in the
103        // constructor in order to make the associated variables "final".
104        AttributeType at = DirectoryConfig.getAttributeType(ENTRYUUID, false);
105        if (at == null)
106        {
107          String definition =
108               "( 1.3.6.1.1.16.4 NAME 'entryUUID' DESC 'UUID of the entry' " +
109               "EQUALITY uuidMatch ORDERING uuidOrderingMatch " +
110               "SYNTAX 1.3.6.1.1.16.1 SINGLE-VALUE NO-USER-MODIFICATION " +
111               "USAGE directoryOperation X-ORIGIN 'RFC 4530' )";
112    
113          at = new AttributeType(definition, ENTRYUUID,
114                                 Collections.singleton(ENTRYUUID), ENTRYUUID, null,
115                                 null, DirectoryConfig.getDefaultAttributeSyntax(),
116                                 AttributeUsage.DIRECTORY_OPERATION, false, true,
117                                 false, true);
118        }
119    
120        entryUUIDType = at;
121      }
122    
123    
124    
125      /**
126       * {@inheritDoc}
127       */
128      @Override()
129      public final void initializePlugin(Set<PluginType> pluginTypes,
130                                         EntryUUIDPluginCfg configuration)
131             throws ConfigException
132      {
133        currentConfig = configuration;
134        configuration.addEntryUUIDChangeListener(this);
135    
136        // Make sure that the plugin has been enabled for the appropriate types.
137        for (PluginType t : pluginTypes)
138        {
139          switch (t)
140          {
141            case LDIF_IMPORT:
142            case PRE_OPERATION_ADD:
143              // These are acceptable.
144              break;
145    
146    
147            default:
148              Message message =
149                  ERR_PLUGIN_ENTRYUUID_INVALID_PLUGIN_TYPE.get(t.toString());
150              throw new ConfigException(message);
151          }
152        }
153      }
154    
155    
156    
157      /**
158       * {@inheritDoc}
159       */
160      @Override()
161      public final void finalizePlugin()
162      {
163        currentConfig.removeEntryUUIDChangeListener(this);
164      }
165    
166    
167    
168      /**
169       * {@inheritDoc}
170       */
171      @Override()
172      public final PluginResult.ImportLDIF
173                   doLDIFImport(LDIFImportConfig importConfig, Entry entry)
174      {
175        // See if the entry being imported already contains an entryUUID attribute.
176        // If so, then leave it alone.
177        List<Attribute> uuidList = entry.getAttribute(entryUUIDType);
178        if (uuidList != null)
179        {
180          return PluginResult.ImportLDIF.continueEntryProcessing();
181        }
182    
183    
184        // Construct a new UUID.  In order to make sure that UUIDs are consistent
185        // when the same LDIF is generated on multiple servers, we'll base the UUID
186        // on the byte representation of the normalized DN.
187        byte[] dnBytes = getBytes(entry.getDN().toNormalizedString());
188        UUID uuid = UUID.nameUUIDFromBytes(dnBytes);
189    
190        LinkedHashSet<AttributeValue> values = new LinkedHashSet<AttributeValue>(1);
191        values.add(new AttributeValue(entryUUIDType,
192                                      ByteStringFactory.create(uuid.toString())));
193    
194        uuidList = new ArrayList<Attribute>(1);
195        Attribute uuidAttr = new Attribute(entryUUIDType, "entryUUID", values);
196        uuidList.add(uuidAttr);
197        entry.putAttribute(entryUUIDType, uuidList);
198    
199    
200        // We shouldn't ever need to return a non-success result.
201        return PluginResult.ImportLDIF.continueEntryProcessing();
202      }
203    
204    
205    
206      /**
207       * {@inheritDoc}
208       */
209      @Override()
210      public final PluginResult.PreOperation
211                   doPreOperation(PreOperationAddOperation addOperation)
212      {
213        // See if the entry being added already contains an entryUUID attribute.
214        // It shouldn't, since it's NO-USER-MODIFICATION, but if it does then leave
215        // it alone.
216        Map<AttributeType,List<Attribute>> operationalAttributes =
217             addOperation.getOperationalAttributes();
218        List<Attribute> uuidList = operationalAttributes.get(entryUUIDType);
219        if (uuidList != null)
220        {
221          return PluginResult.PreOperation.continueOperationProcessing();
222        }
223    
224    
225        // Construct a new random UUID.
226        UUID uuid = UUID.randomUUID();
227    
228        LinkedHashSet<AttributeValue> values = new LinkedHashSet<AttributeValue>(1);
229        values.add(new AttributeValue(entryUUIDType,
230                                      ByteStringFactory.create(uuid.toString())));
231    
232        uuidList = new ArrayList<Attribute>(1);
233        Attribute uuidAttr = new Attribute(entryUUIDType, "entryUUID", values);
234        uuidList.add(uuidAttr);
235    
236    
237        // Add the attribute to the entry and return.
238        addOperation.setAttribute(entryUUIDType, uuidList);
239        return PluginResult.PreOperation.continueOperationProcessing();
240      }
241    
242    
243    
244      /**
245       * {@inheritDoc}
246       */
247      @Override()
248      public boolean isConfigurationAcceptable(PluginCfg configuration,
249                                               List<Message> unacceptableReasons)
250      {
251        EntryUUIDPluginCfg cfg = (EntryUUIDPluginCfg) configuration;
252        return isConfigurationChangeAcceptable(cfg, unacceptableReasons);
253      }
254    
255    
256    
257      /**
258       * {@inheritDoc}
259       */
260      public boolean isConfigurationChangeAcceptable(
261                          EntryUUIDPluginCfg configuration,
262                          List<Message> unacceptableReasons)
263      {
264        boolean configAcceptable = true;
265    
266        // Ensure that the set of plugin types contains only LDIF import and
267        // pre-operation add.
268        for (PluginCfgDefn.PluginType pluginType : configuration.getPluginType())
269        {
270          switch (pluginType)
271          {
272            case LDIFIMPORT:
273            case PREOPERATIONADD:
274              // These are acceptable.
275              break;
276    
277    
278            default:
279              Message message = ERR_PLUGIN_ENTRYUUID_INVALID_PLUGIN_TYPE.get(
280                      pluginType.toString());
281              unacceptableReasons.add(message);
282              configAcceptable = false;
283          }
284        }
285    
286        return configAcceptable;
287      }
288    
289    
290    
291      /**
292       * {@inheritDoc}
293       */
294      public ConfigChangeResult applyConfigurationChange(
295                                     EntryUUIDPluginCfg configuration)
296      {
297        currentConfig = configuration;
298        return new ConfigChangeResult(ResultCode.SUCCESS, false);
299      }
300    }
301