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