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