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