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.opends.messages.PluginMessages.*;
020
021import java.util.List;
022import java.util.Map;
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.ldap.schema.AttributeType;
030import org.opends.server.admin.server.ConfigurationChangeListener;
031import org.opends.server.admin.std.meta.PluginCfgDefn;
032import org.opends.server.admin.std.server.EntryUUIDPluginCfg;
033import org.opends.server.admin.std.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.core.DirectoryServer;
038import org.opends.server.types.Attribute;
039import org.opends.server.types.Attributes;
040import org.opends.server.types.Entry;
041import org.opends.server.types.LDIFImportConfig;
042import org.opends.server.types.operation.PreOperationAddOperation;
043
044/**
045 * This class implements a Directory Server plugin that will add the entryUUID
046 * attribute to an entry whenever it is added or imported as per RFC 4530.  For
047 * entries added over LDAP, the entryUUID will be based on a semi-random UUID
048 * (which is still guaranteed to be unique).  For entries imported from LDIF,
049 * the UUID will be constructed from the entry DN using a repeatable algorithm.
050 * This will ensure that LDIF files imported in parallel across multiple systems
051 * will have identical entryUUID values.
052 */
053public final class EntryUUIDPlugin
054       extends DirectoryServerPlugin<EntryUUIDPluginCfg>
055       implements ConfigurationChangeListener<EntryUUIDPluginCfg>
056{
057  /** The name of the entryUUID attribute type. */
058  private static final String ENTRYUUID = "entryuuid";
059
060  /** The attribute type for the "entryUUID" attribute. */
061  private final AttributeType entryUUIDType;
062  /** The current configuration for this plugin. */
063  private EntryUUIDPluginCfg currentConfig;
064
065  /** Mandatory default constructor of this Directory Server plugin. */
066  public EntryUUIDPlugin()
067  {
068    entryUUIDType = DirectoryServer.getAttributeType(ENTRYUUID);
069  }
070
071  @Override
072  public final void initializePlugin(Set<PluginType> pluginTypes,
073                                     EntryUUIDPluginCfg configuration)
074         throws ConfigException
075  {
076    currentConfig = configuration;
077    configuration.addEntryUUIDChangeListener(this);
078
079    // Make sure that the plugin has been enabled for the appropriate types.
080    for (PluginType t : pluginTypes)
081    {
082      switch (t)
083      {
084        case LDIF_IMPORT:
085        case PRE_OPERATION_ADD:
086          // These are acceptable.
087          break;
088
089        default:
090          throw new ConfigException(ERR_PLUGIN_ENTRYUUID_INVALID_PLUGIN_TYPE.get(t));
091      }
092    }
093  }
094
095  @Override
096  public final void finalizePlugin()
097  {
098    currentConfig.removeEntryUUIDChangeListener(this);
099  }
100
101  @Override
102  public final PluginResult.ImportLDIF
103               doLDIFImport(LDIFImportConfig importConfig, Entry entry)
104  {
105    // See if the entry being imported already contains an entryUUID attribute.
106    // If so, then leave it alone.
107    List<Attribute> uuidList = entry.getAttribute(entryUUIDType);
108    if (!uuidList.isEmpty())
109    {
110      return PluginResult.ImportLDIF.continueEntryProcessing();
111    }
112
113    // Construct a new UUID.  In order to make sure that UUIDs are consistent
114    // when the same LDIF is generated on multiple servers, we'll base the UUID
115    // on the byte representation of the normalized DN.
116    UUID uuid = entry.getName().toUUID();
117    uuidList = Attributes.createAsList(entryUUIDType, uuid.toString());
118    entry.putAttribute(entryUUIDType, uuidList);
119
120    // We shouldn't ever need to return a non-success result.
121    return PluginResult.ImportLDIF.continueEntryProcessing();
122  }
123
124  @Override
125  public final PluginResult.PreOperation
126               doPreOperation(PreOperationAddOperation addOperation)
127  {
128    // See if the entry being added already contains an entryUUID attribute.
129    // It shouldn't, since it's NO-USER-MODIFICATION, but if it does then leave
130    // it alone.
131    Map<AttributeType,List<Attribute>> operationalAttributes =
132         addOperation.getOperationalAttributes();
133    List<Attribute> uuidList = operationalAttributes.get(entryUUIDType);
134    if (uuidList != null)
135    {
136      return PluginResult.PreOperation.continueOperationProcessing();
137    }
138
139    // Construct a new random UUID.
140    UUID uuid = UUID.randomUUID();
141    uuidList = Attributes.createAsList(entryUUIDType, uuid.toString());
142
143    // Add the attribute to the entry and return.
144    addOperation.setAttribute(entryUUIDType, uuidList);
145    return PluginResult.PreOperation.continueOperationProcessing();
146  }
147
148  @Override
149  public boolean isConfigurationAcceptable(PluginCfg configuration,
150                                           List<LocalizableMessage> unacceptableReasons)
151  {
152    EntryUUIDPluginCfg cfg = (EntryUUIDPluginCfg) configuration;
153    return isConfigurationChangeAcceptable(cfg, unacceptableReasons);
154  }
155
156  @Override
157  public boolean isConfigurationChangeAcceptable(
158                      EntryUUIDPluginCfg configuration,
159                      List<LocalizableMessage> unacceptableReasons)
160  {
161    boolean configAcceptable = true;
162
163    // Ensure that the set of plugin types contains only LDIF import and
164    // pre-operation add.
165    for (PluginCfgDefn.PluginType pluginType : configuration.getPluginType())
166    {
167      switch (pluginType)
168      {
169        case LDIFIMPORT:
170        case PREOPERATIONADD:
171          // These are acceptable.
172          break;
173
174        default:
175          unacceptableReasons.add(ERR_PLUGIN_ENTRYUUID_INVALID_PLUGIN_TYPE.get(pluginType));
176          configAcceptable = false;
177      }
178    }
179
180    return configAcceptable;
181  }
182
183  @Override
184  public ConfigChangeResult applyConfigurationChange(
185                                 EntryUUIDPluginCfg configuration)
186  {
187    currentConfig = configuration;
188    return new ConfigChangeResult();
189  }
190}