001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2006-2008 Sun Microsystems, Inc.
015 * Portions Copyright 2014-2016 ForgeRock AS.
016 */
017package org.opends.server.plugins;
018
019import static org.forgerock.opendj.ldap.schema.CoreSchema.*;
020import static org.opends.messages.PluginMessages.*;
021
022import java.util.List;
023import java.util.Set;
024import java.util.UUID;
025
026import org.forgerock.i18n.LocalizableMessage;
027import org.forgerock.opendj.config.server.ConfigChangeResult;
028import org.forgerock.opendj.config.server.ConfigException;
029import org.forgerock.opendj.config.server.ConfigurationChangeListener;
030import org.forgerock.opendj.ldap.schema.AttributeType;
031import org.forgerock.opendj.server.config.meta.PluginCfgDefn;
032import org.forgerock.opendj.server.config.server.EntryUUIDPluginCfg;
033import org.forgerock.opendj.server.config.server.PluginCfg;
034import org.opends.server.api.plugin.DirectoryServerPlugin;
035import org.opends.server.api.plugin.PluginResult;
036import org.opends.server.api.plugin.PluginType;
037import org.opends.server.types.Attribute;
038import org.opends.server.types.Attributes;
039import org.opends.server.types.Entry;
040import org.opends.server.types.LDIFImportConfig;
041import org.opends.server.types.operation.PreOperationAddOperation;
042
043/**
044 * This class implements a Directory Server plugin that will add the entryUUID
045 * attribute to an entry whenever it is added or imported as per RFC 4530.  For
046 * entries added over LDAP, the entryUUID will be based on a semi-random UUID
047 * (which is still guaranteed to be unique).  For entries imported from LDIF,
048 * the UUID will be constructed from the entry DN using a repeatable algorithm.
049 * This will ensure that LDIF files imported in parallel across multiple systems
050 * will have identical entryUUID values.
051 */
052public final class EntryUUIDPlugin
053       extends DirectoryServerPlugin<EntryUUIDPluginCfg>
054       implements ConfigurationChangeListener<EntryUUIDPluginCfg>
055{
056  /** The attribute type for the "entryUUID" attribute. */
057  private static final AttributeType entryUUIDType = getEntryUUIDAttributeType();
058  /** The current configuration for this plugin. */
059  private EntryUUIDPluginCfg currentConfig;
060
061  /** Mandatory default constructor of this Directory Server plugin. */
062  public EntryUUIDPlugin()
063  {
064    super();
065  }
066
067  @Override
068  public final void initializePlugin(Set<PluginType> pluginTypes,
069                                     EntryUUIDPluginCfg configuration)
070         throws ConfigException
071  {
072    currentConfig = configuration;
073    configuration.addEntryUUIDChangeListener(this);
074
075    // Make sure that the plugin has been enabled for the appropriate types.
076    for (PluginType t : pluginTypes)
077    {
078      switch (t)
079      {
080        case LDIF_IMPORT:
081        case PRE_OPERATION_ADD:
082          // These are acceptable.
083          break;
084
085        default:
086          throw new ConfigException(ERR_PLUGIN_ENTRYUUID_INVALID_PLUGIN_TYPE.get(t));
087      }
088    }
089  }
090
091  @Override
092  public final void finalizePlugin()
093  {
094    currentConfig.removeEntryUUIDChangeListener(this);
095  }
096
097  @Override
098  public final PluginResult.ImportLDIF
099               doLDIFImport(LDIFImportConfig importConfig, Entry entry)
100  {
101    // See if the entry being imported already contains an entryUUID attribute.
102    // If so, then leave it alone.
103    List<Attribute> uuidList = entry.getAttribute(entryUUIDType);
104    if (!uuidList.isEmpty())
105    {
106      return PluginResult.ImportLDIF.continueEntryProcessing();
107    }
108
109    // Construct a new UUID.  In order to make sure that UUIDs are consistent
110    // when the same LDIF is generated on multiple servers, we'll base the UUID
111    // on the byte representation of the normalized DN.
112    entry.putAttribute(entryUUIDType, toAttributeList(entry.getName().toUUID()));
113
114    // We shouldn't ever need to return a non-success result.
115    return PluginResult.ImportLDIF.continueEntryProcessing();
116  }
117
118  @Override
119  public final PluginResult.PreOperation
120               doPreOperation(PreOperationAddOperation addOperation)
121  {
122    // See if the entry being added already contains an entryUUID attribute.
123    // It shouldn't, since it's NO-USER-MODIFICATION, but if it does then leave
124    // it alone.
125    List<Attribute> uuidList = addOperation.getOperationalAttributes().get(entryUUIDType);
126    if (uuidList != null)
127    {
128      return PluginResult.PreOperation.continueOperationProcessing();
129    }
130
131    // Construct a new random UUID.
132    addOperation.setAttribute(entryUUIDType, toAttributeList(UUID.randomUUID()));
133    return PluginResult.PreOperation.continueOperationProcessing();
134  }
135
136  private List<Attribute> toAttributeList(UUID uuid)
137  {
138    return Attributes.createAsList(entryUUIDType, uuid.toString());
139  }
140
141  @Override
142  public boolean isConfigurationAcceptable(PluginCfg configuration,
143                                           List<LocalizableMessage> unacceptableReasons)
144  {
145    EntryUUIDPluginCfg cfg = (EntryUUIDPluginCfg) configuration;
146    return isConfigurationChangeAcceptable(cfg, unacceptableReasons);
147  }
148
149  @Override
150  public boolean isConfigurationChangeAcceptable(
151                      EntryUUIDPluginCfg configuration,
152                      List<LocalizableMessage> unacceptableReasons)
153  {
154    boolean configAcceptable = true;
155
156    // Ensure that the set of plugin types contains only LDIF import and
157    // pre-operation add.
158    for (PluginCfgDefn.PluginType pluginType : configuration.getPluginType())
159    {
160      switch (pluginType)
161      {
162        case LDIFIMPORT:
163        case PREOPERATIONADD:
164          // These are acceptable.
165          break;
166
167        default:
168          unacceptableReasons.add(ERR_PLUGIN_ENTRYUUID_INVALID_PLUGIN_TYPE.get(pluginType));
169          configAcceptable = false;
170      }
171    }
172
173    return configAcceptable;
174  }
175
176  @Override
177  public ConfigChangeResult applyConfigurationChange(
178                                 EntryUUIDPluginCfg configuration)
179  {
180    currentConfig = configuration;
181    return new ConfigChangeResult();
182  }
183}