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 legal-notices/CDDLv1_0.txt
010 * or http://forgerock.org/license/CDDLv1.0.html.
011 * See the License for the specific language governing permissions
012 * and limitations under the License.
013 *
014 * When distributing Covered Code, include this CDDL HEADER in each
015 * file and include the License file at legal-notices/CDDLv1_0.txt.
016 * If applicable, add the following below this CDDL HEADER, with the
017 * fields enclosed by brackets "[]" replaced with your own identifying
018 * information:
019 *      Portions Copyright [yyyy] [name of copyright owner]
020 *
021 * CDDL HEADER END
022 *
023 *
024 *      Copyright 2006-2008 Sun Microsystems, Inc.
025 *      Portions Copyright 2014-2015 ForgeRock AS
026 */
027package org.opends.server.plugins;
028
029import static org.opends.messages.PluginMessages.*;
030
031import java.util.Collections;
032import java.util.List;
033import java.util.Map;
034import java.util.Set;
035import java.util.UUID;
036
037import org.forgerock.i18n.LocalizableMessage;
038import org.forgerock.opendj.config.server.ConfigChangeResult;
039import org.forgerock.opendj.config.server.ConfigException;
040import org.forgerock.opendj.ldap.schema.AttributeUsage;
041import org.opends.server.admin.server.ConfigurationChangeListener;
042import org.opends.server.admin.std.meta.PluginCfgDefn;
043import org.opends.server.admin.std.server.EntryUUIDPluginCfg;
044import org.opends.server.admin.std.server.PluginCfg;
045import org.opends.server.api.plugin.DirectoryServerPlugin;
046import org.opends.server.api.plugin.PluginResult;
047import org.opends.server.api.plugin.PluginType;
048import org.opends.server.core.DirectoryServer;
049import org.opends.server.types.*;
050import org.opends.server.types.operation.PreOperationAddOperation;
051
052/**
053 * This class implements a Directory Server plugin that will add the entryUUID
054 * attribute to an entry whenever it is added or imported as per RFC 4530.  For
055 * entries added over LDAP, the entryUUID will be based on a semi-random UUID
056 * (which is still guaranteed to be unique).  For entries imported from LDIF,
057 * the UUID will be constructed from the entry DN using a repeatable algorithm.
058 * This will ensure that LDIF files imported in parallel across multiple systems
059 * will have identical entryUUID values.
060 */
061public final class EntryUUIDPlugin
062       extends DirectoryServerPlugin<EntryUUIDPluginCfg>
063       implements ConfigurationChangeListener<EntryUUIDPluginCfg>
064{
065  /**
066   * The name of the entryUUID attribute type.
067   */
068  private static final String ENTRYUUID = "entryuuid";
069
070
071
072  /** The attribute type for the "entryUUID" attribute. */
073  private final AttributeType entryUUIDType;
074
075  /** The current configuration for this plugin. */
076  private EntryUUIDPluginCfg currentConfig;
077
078
079
080  /**
081   * Creates a new instance of this Directory Server plugin.  Every plugin must
082   * implement a default constructor (it is the only one that will be used to
083   * create plugins defined in the configuration), and every plugin constructor
084   * must call <CODE>super()</CODE> as its first element.
085   */
086  public EntryUUIDPlugin()
087  {
088    super();
089
090
091    // Get the entryUUID attribute type.  This needs to be done in the
092    // constructor in order to make the associated variables "final".
093    AttributeType at = DirectoryServer.getAttributeTypeOrNull(ENTRYUUID);
094    if (at == null)
095    {
096      String definition =
097           "( 1.3.6.1.1.16.4 NAME 'entryUUID' DESC 'UUID of the entry' " +
098           "EQUALITY uuidMatch ORDERING uuidOrderingMatch " +
099           "SYNTAX 1.3.6.1.1.16.1 SINGLE-VALUE NO-USER-MODIFICATION " +
100           "USAGE directoryOperation X-ORIGIN 'RFC 4530' )";
101
102      at = new AttributeType(definition, ENTRYUUID,
103                             Collections.singleton(ENTRYUUID), ENTRYUUID, null,
104                             null, DirectoryConfig.getDefaultAttributeSyntax(),
105                             AttributeUsage.DIRECTORY_OPERATION, false, true,
106                             false, true);
107    }
108
109    entryUUIDType = at;
110  }
111
112
113
114  /** {@inheritDoc} */
115  @Override
116  public final void initializePlugin(Set<PluginType> pluginTypes,
117                                     EntryUUIDPluginCfg configuration)
118         throws ConfigException
119  {
120    currentConfig = configuration;
121    configuration.addEntryUUIDChangeListener(this);
122
123    // Make sure that the plugin has been enabled for the appropriate types.
124    for (PluginType t : pluginTypes)
125    {
126      switch (t)
127      {
128        case LDIF_IMPORT:
129        case PRE_OPERATION_ADD:
130          // These are acceptable.
131          break;
132
133        default:
134          throw new ConfigException(ERR_PLUGIN_ENTRYUUID_INVALID_PLUGIN_TYPE.get(t));
135      }
136    }
137  }
138
139
140
141  /** {@inheritDoc} */
142  @Override
143  public final void finalizePlugin()
144  {
145    currentConfig.removeEntryUUIDChangeListener(this);
146  }
147
148
149
150  /** {@inheritDoc} */
151  @Override
152  public final PluginResult.ImportLDIF
153               doLDIFImport(LDIFImportConfig importConfig, Entry entry)
154  {
155    // See if the entry being imported already contains an entryUUID attribute.
156    // If so, then leave it alone.
157    List<Attribute> uuidList = entry.getAttribute(entryUUIDType);
158    if (uuidList != null)
159    {
160      return PluginResult.ImportLDIF.continueEntryProcessing();
161    }
162
163
164    // Construct a new UUID.  In order to make sure that UUIDs are consistent
165    // when the same LDIF is generated on multiple servers, we'll base the UUID
166    // on the byte representation of the normalized DN.
167    byte[] dnBytes = entry.getName().toNormalizedByteString().toByteArray();
168    UUID uuid = UUID.nameUUIDFromBytes(dnBytes);
169
170    uuidList = Attributes.createAsList(entryUUIDType, uuid.toString());
171    entry.putAttribute(entryUUIDType, uuidList);
172
173    // We shouldn't ever need to return a non-success result.
174    return PluginResult.ImportLDIF.continueEntryProcessing();
175  }
176
177
178
179  /** {@inheritDoc} */
180  @Override
181  public final PluginResult.PreOperation
182               doPreOperation(PreOperationAddOperation addOperation)
183  {
184    // See if the entry being added already contains an entryUUID attribute.
185    // It shouldn't, since it's NO-USER-MODIFICATION, but if it does then leave
186    // it alone.
187    Map<AttributeType,List<Attribute>> operationalAttributes =
188         addOperation.getOperationalAttributes();
189    List<Attribute> uuidList = operationalAttributes.get(entryUUIDType);
190    if (uuidList != null)
191    {
192      return PluginResult.PreOperation.continueOperationProcessing();
193    }
194
195
196    // Construct a new random UUID.
197    UUID uuid = UUID.randomUUID();
198    uuidList = Attributes.createAsList(entryUUIDType, uuid.toString());
199
200    // Add the attribute to the entry and return.
201    addOperation.setAttribute(entryUUIDType, uuidList);
202    return PluginResult.PreOperation.continueOperationProcessing();
203  }
204
205
206
207  /** {@inheritDoc} */
208  @Override
209  public boolean isConfigurationAcceptable(PluginCfg configuration,
210                                           List<LocalizableMessage> unacceptableReasons)
211  {
212    EntryUUIDPluginCfg cfg = (EntryUUIDPluginCfg) configuration;
213    return isConfigurationChangeAcceptable(cfg, unacceptableReasons);
214  }
215
216
217
218  /** {@inheritDoc} */
219  @Override
220  public boolean isConfigurationChangeAcceptable(
221                      EntryUUIDPluginCfg configuration,
222                      List<LocalizableMessage> unacceptableReasons)
223  {
224    boolean configAcceptable = true;
225
226    // Ensure that the set of plugin types contains only LDIF import and
227    // pre-operation add.
228    for (PluginCfgDefn.PluginType pluginType : configuration.getPluginType())
229    {
230      switch (pluginType)
231      {
232        case LDIFIMPORT:
233        case PREOPERATIONADD:
234          // These are acceptable.
235          break;
236
237
238        default:
239          unacceptableReasons.add(ERR_PLUGIN_ENTRYUUID_INVALID_PLUGIN_TYPE.get(pluginType));
240          configAcceptable = false;
241      }
242    }
243
244    return configAcceptable;
245  }
246
247
248
249  /** {@inheritDoc} */
250  @Override
251  public ConfigChangeResult applyConfigurationChange(
252                                 EntryUUIDPluginCfg configuration)
253  {
254    currentConfig = configuration;
255    return new ConfigChangeResult();
256  }
257}
258