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}