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 2009 Sun Microsystems, Inc. 025 * Portions Copyright 2011-2015 ForgeRock AS 026 */ 027package org.opends.server.replication.plugin; 028 029import java.util.*; 030 031import org.forgerock.i18n.LocalizableMessage; 032import org.forgerock.util.Utils; 033import org.opends.server.admin.server.ConfigurationChangeListener; 034import org.opends.server.admin.server.ServerManagementContext; 035import org.opends.server.admin.std.server.*; 036import org.opends.server.api.plugin.DirectoryServerPlugin; 037import org.opends.server.api.plugin.PluginResult; 038import org.opends.server.api.plugin.PluginType; 039import org.forgerock.opendj.config.server.ConfigChangeResult; 040import org.forgerock.opendj.config.server.ConfigException; 041import org.opends.server.core.DirectoryServer; 042import org.opends.server.replication.plugin.LDAPReplicationDomain.*; 043import org.opends.server.types.*; 044import org.forgerock.opendj.ldap.ByteString; 045import static org.opends.messages.ReplicationMessages.*; 046import static org.opends.server.replication.plugin.LDAPReplicationDomain.*; 047 048/** 049 * This class implements a Directory Server plugin that is used in fractional 050 * replication to initialize a just configured fractional domain (when an online 051 * full update occurs or offline/online ldif import). 052 * The following tasks are done: 053 * - check that the fractional configuration (if any) stored in the (incoming) 054 * root entry of the domain is compliant with the fractional configuration of 055 * the domain (if not make online update stop) 056 * - perform filtering according to fractional configuration of the domain 057 * - flush the fractional configuration of the domain in the root entry 058 * (if no one already present) 059 */ 060public final class FractionalLDIFImportPlugin 061 extends DirectoryServerPlugin<FractionalLDIFImportPluginCfg> 062 implements ConfigurationChangeListener<FractionalLDIFImportPluginCfg> 063{ 064 /** 065 * Holds the fractional configuration and if available the replication domain 066 * matching this import session (they form the import fractional context). 067 * Domain is available if the server is online (import-ldif, online full 068 * update..) otherwise, this is an import-ldif with server off. The key is the 069 * ImportConfig object of the session which acts as a cookie for the whole 070 * session. This allows to potentially run man imports at the same time. 071 */ 072 private final Map<LDIFImportConfig, ImportFractionalContext> 073 importSessionContexts = new Hashtable<>(); 074 075 /** 076 * Holds an import session fractional context. 077 */ 078 private static class ImportFractionalContext 079 { 080 /** 081 * Fractional configuration of the local domain (may be null if import on a 082 * not replicated domain). 083 */ 084 private FractionalConfig fractionalConfig; 085 /** The local domain object (may stay null if server is offline). */ 086 private LDAPReplicationDomain domain; 087 088 /** 089 * Constructor. 090 * @param fractionalConfig The fractional configuration. 091 * @param domain The replication domain. 092 */ 093 public ImportFractionalContext(FractionalConfig fractionalConfig, 094 LDAPReplicationDomain domain) 095 { 096 this.fractionalConfig = fractionalConfig; 097 this.domain = domain; 098 } 099 100 /** 101 * Getter for the fractional configuration. 102 * @return the fractionalConfig 103 */ 104 public FractionalConfig getFractionalConfig() 105 { 106 return fractionalConfig; 107 } 108 109 /** 110 * Getter for the domain.. 111 * @return the domain 112 */ 113 public LDAPReplicationDomain getDomain() 114 { 115 return domain; 116 } 117 } 118 119 /** 120 * Creates a new instance of this Directory Server plugin. Every plugin must 121 * implement a default constructor (it is the only one that will be used to 122 * create plugins defined in the configuration), and every plugin constructor 123 * must call {@code super()} as its first element. 124 */ 125 public FractionalLDIFImportPlugin() 126 { 127 super(); 128 } 129 130 /** {@inheritDoc} */ 131 @Override 132 public final void initializePlugin(Set<PluginType> pluginTypes, 133 FractionalLDIFImportPluginCfg configuration) 134 throws ConfigException 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 LDIF_IMPORT_END: 143 // This is acceptable. 144 break; 145 146 default: 147 throw new ConfigException(ERR_PLUGIN_FRACTIONAL_LDIF_IMPORT_INVALID_PLUGIN_TYPE.get(t)); 148 } 149 } 150 } 151 152 /** {@inheritDoc} */ 153 @Override 154 public final void finalizePlugin() 155 { 156 // Nothing to do 157 } 158 159 /** 160 * Attempts to retrieve the fractional configuration of the domain being 161 * imported. 162 * @param entry An imported entry of the imported domain 163 * @return The parsed fractional configuration for the domain matching the 164 * passed entry. Null if no configuration is found for the domain 165 * (not a replicated domain). 166 */ 167 private static FractionalConfig getStaticReplicationDomainFractionalConfig( 168 Entry entry) throws Exception { 169 170 // Retrieve the configuration 171 ServerManagementContext context = ServerManagementContext.getInstance(); 172 RootCfg root = context.getRootConfiguration(); 173 174 175 ReplicationSynchronizationProviderCfg sync = 176 (ReplicationSynchronizationProviderCfg) 177 root.getSynchronizationProvider("Multimaster Synchronization"); 178 179 String[] domainNames = sync.listReplicationDomains(); 180 if (domainNames == null) 181 { 182 // No domain in replication 183 return null; 184 } 185 186 // Find the configuration for domain the entry is part of 187 ReplicationDomainCfg matchingReplicatedDomainCfg = null; 188 for (String domainName : domainNames) 189 { 190 ReplicationDomainCfg replicationDomainCfg = 191 sync.getReplicationDomain(domainName); 192 // Is the entry a sub entry of the replicated domain main entry ? 193 DN replicatedDn = replicationDomainCfg.getBaseDN(); 194 DN entryDn = entry.getName(); 195 if (entryDn.isDescendantOf(replicatedDn)) 196 { 197 // Found the matching replicated domain configuration object 198 matchingReplicatedDomainCfg = replicationDomainCfg; 199 break; 200 } 201 } 202 203 if (matchingReplicatedDomainCfg == null) 204 { 205 // No matching replicated domain found 206 return null; 207 } 208 209 // Extract the fractional configuration from the domain configuration object 210 // and return it. 211 return FractionalConfig.toFractionalConfig(matchingReplicatedDomainCfg); 212 } 213 214 /** {@inheritDoc} */ 215 @Override 216 public final void doLDIFImportEnd(LDIFImportConfig importConfig) 217 { 218 // Remove the cookie of this import session 219 synchronized(importSessionContexts) 220 { 221 importSessionContexts.remove(importConfig); 222 } 223 } 224 225 /** 226 * See class comment for what we achieve here... 227 * {@inheritDoc} 228 */ 229 @Override 230 public final PluginResult.ImportLDIF doLDIFImport( 231 LDIFImportConfig importConfig, Entry entry) 232 { 233 /** 234 * try to get the import fractional context for this entry. If not found, 235 * create and initialize it. The mechanism here is done to take a lock only 236 * once for the whole import session (except the necessary lock of the 237 * doLDIFImportEnd method) 238 */ 239 ImportFractionalContext importFractionalContext = 240 importSessionContexts.get(importConfig); 241 242 DN entryDn = entry.getName(); 243 FractionalConfig localFractionalConfig = null; 244 245 // If no context, create it 246 if (importFractionalContext == null) 247 { 248 synchronized(importSessionContexts) 249 { 250 // Insure another thread was not creating the context at the same time 251 // (we would create it for the second time which is useless) 252 importFractionalContext = importSessionContexts.get(importConfig); 253 if (importFractionalContext == null) 254 { 255 /* 256 * Create context 257 */ 258 259 /** 260 * Retrieve the replicated domain this entry belongs to. Try to 261 * retrieve replication domain instance first. If we are in an online 262 * server, we should get it (if we are treating an entry that belongs 263 * to a replicated domain), otherwise the domain is not replicated or 264 * we are in an offline server context (import-ldif command run with 265 * offline server) and we must retrieve the fractional configuration 266 * directly from the configuration management system. 267 */ 268 LDAPReplicationDomain domain = 269 MultimasterReplication.findDomain(entryDn, null); 270 271 // Get the fractional configuration extracted from the local server 272 // configuration for the currently imported domain 273 if (domain == null) 274 { 275 // Server may be offline, attempt to find fractional configuration 276 // from config sub-system 277 try 278 { 279 localFractionalConfig = 280 getStaticReplicationDomainFractionalConfig(entry); 281 } catch (Exception ex) 282 { 283 return PluginResult.ImportLDIF.stopEntryProcessing( 284 ERR_FRACTIONAL_COULD_NOT_RETRIEVE_CONFIG.get(entry)); 285 } 286 } else 287 { 288 // Found a live domain, retrieve the fractional configuration from 289 // it. 290 localFractionalConfig = domain.getFractionalConfig(); 291 } 292 // Create context and store it 293 importFractionalContext = 294 new ImportFractionalContext(localFractionalConfig, domain); 295 importSessionContexts.put(importConfig, importFractionalContext); 296 } 297 } 298 } 299 300 // Extract the fractional configuration from the context 301 localFractionalConfig = importFractionalContext.getFractionalConfig(); 302 if (localFractionalConfig == null) 303 { 304 // Not part of a replicated domain : nothing to do 305 return PluginResult.ImportLDIF.continueEntryProcessing(); 306 } 307 308 /** 309 * At this point, either the domain instance has been found and we use its 310 * fractional configuration, or the server is offline and we use the parsed 311 * fractional configuration. We differentiate both cases testing if domain 312 * is null. We are also for sure handling an entry of a replicated suffix. 313 */ 314 315 // Is the entry to handle the root entry of the domain ? If yes, analyze the 316 // fractional configuration in it and compare with local fractional 317 // configuration. Stop the import if some inconsistency is detected 318 DN replicatedDomainBaseDn = localFractionalConfig.getBaseDn(); 319 if (replicatedDomainBaseDn.equals(entryDn)) 320 { 321 // This is the root entry, try to read a fractional configuration from it 322 Attribute exclAttr = getAttribute(REPLICATION_FRACTIONAL_EXCLUDE, entry); 323 Iterator<String> exclIt = null; 324 if (exclAttr != null) 325 { 326 exclIt = new AttributeValueStringIterator(exclAttr.iterator()); 327 } 328 329 Attribute inclAttr = getAttribute(REPLICATION_FRACTIONAL_INCLUDE, entry); 330 Iterator<String> inclIt = null; 331 if (inclAttr != null) 332 { 333 inclIt = new AttributeValueStringIterator(inclAttr.iterator()); 334 } 335 336 // Compare backend and local fractional configuration 337 if (isFractionalConfigConsistent(localFractionalConfig, exclIt, inclIt)) 338 { 339 // local and remote non/fractional config are equivalent : 340 // follow import, no need to go with filtering as remote backend 341 // should be ok 342 // let import finish 343 return PluginResult.ImportLDIF.continueEntryProcessing(); 344 } 345 346 if (localFractionalConfig.isFractional()) 347 { 348 // Local domain is fractional, remote domain has not same config 349 boolean remoteDomainHasSomeConfig = 350 isNotEmpty(exclAttr) || isNotEmpty(inclAttr); 351 if (remoteDomainHasSomeConfig) 352 { 353 LDAPReplicationDomain domain = importFractionalContext.getDomain(); 354 if (domain != null) 355 { 356 // Local domain is fractional, remote domain has some config which 357 // is different : stop import (error will be logged when import is 358 // stopped) 359 domain.setImportErrorMessageId(IMPORT_ERROR_MESSAGE_BAD_REMOTE); 360 return PluginResult.ImportLDIF.stopEntryProcessing(null); 361 } 362 363 return PluginResult.ImportLDIF.stopEntryProcessing( 364 NOTE_ERR_LDIF_IMPORT_FRACTIONAL_BAD_DATA_SET.get(replicatedDomainBaseDn)); 365 } 366 367 // Local domain is fractional but remote domain has no config : 368 // flush local config into root entry and follow import with filtering 369 flushFractionalConfigIntoEntry(localFractionalConfig, entry); 370 } 371 else 372 { 373 // Local domain is not fractional 374 LDAPReplicationDomain domain = importFractionalContext.getDomain(); 375 if (domain != null) 376 { 377 // Local domain is not fractional but remote one is : stop import : 378 //local domain should be configured with the same config as remote one 379 domain.setImportErrorMessageId( 380 IMPORT_ERROR_MESSAGE_REMOTE_IS_FRACTIONAL); 381 return PluginResult.ImportLDIF.stopEntryProcessing(null); 382 } 383 384 return PluginResult.ImportLDIF.stopEntryProcessing( 385 NOTE_ERR_LDIF_IMPORT_FRACTIONAL_DATA_SET_IS_FRACTIONAL.get(replicatedDomainBaseDn)); 386 } 387 } 388 389 // If we get here, local domain fractional configuration is enabled. 390 // Now filter for potential attributes to be removed. 391 LDAPReplicationDomain.fractionalRemoveAttributesFromEntry( 392 localFractionalConfig, entry.getName().rdn(), 393 entry.getObjectClasses(), entry.getUserAttributes(), true); 394 395 return PluginResult.ImportLDIF.continueEntryProcessing(); 396 } 397 398 private boolean isNotEmpty(Attribute attr) 399 { 400 return attr != null && attr.size() > 0; 401 } 402 403 private Attribute getAttribute(String attributeName, Entry entry) 404 { 405 AttributeType attrType = DirectoryServer.getAttributeTypeOrNull(attributeName); 406 List<Attribute> inclAttrs = entry.getAttribute(attrType); 407 if (inclAttrs != null) 408 { 409 return inclAttrs.get(0); 410 } 411 return null; 412 } 413 414 /** 415 * Write the fractional configuration in the passed domain into the passed 416 * entry. WARNING: assumption is that no fractional attributes at all is 417 * already present in the passed entry. Also assumption is that domain 418 * fractional configuration is on. 419 * 420 * @param localFractionalConfig 421 * The local domain fractional configuration 422 * @param entry 423 * The entry to modify 424 */ 425 private static void flushFractionalConfigIntoEntry(FractionalConfig 426 localFractionalConfig, Entry entry) 427 { 428 if (localFractionalConfig.isFractional()) // Paranoia check 429 { 430 // Get the fractional configuration of the domain 431 boolean fractionalExclusive = 432 localFractionalConfig.isFractionalExclusive(); 433 Map<String, Set<String>> fractionalSpecificClassesAttributes = 434 localFractionalConfig.getFractionalSpecificClassesAttributes(); 435 Set<String> fractionalAllClassesAttributes = 436 localFractionalConfig.getFractionalAllClassesAttributes(); 437 438 // Create attribute builder for the right fractional mode 439 String fractAttribute = fractionalExclusive ? 440 REPLICATION_FRACTIONAL_EXCLUDE : REPLICATION_FRACTIONAL_INCLUDE; 441 AttributeBuilder attrBuilder = new AttributeBuilder(fractAttribute); 442 // Add attribute values for all classes 443 boolean somethingToFlush = 444 add(attrBuilder, "*", fractionalAllClassesAttributes); 445 446 // Add attribute values for specific classes 447 if (!fractionalSpecificClassesAttributes.isEmpty()) 448 { 449 for (Map.Entry<String, Set<String>> specific 450 : fractionalSpecificClassesAttributes.entrySet()) 451 { 452 if (add(attrBuilder, specific.getKey(), specific.getValue())) 453 { 454 somethingToFlush = true; 455 } 456 } 457 } 458 459 // Now flush attribute values into entry 460 if (somethingToFlush) 461 { 462 List<ByteString> duplicateValues = new ArrayList<>(); 463 entry.addAttribute(attrBuilder.toAttribute(), duplicateValues); 464 } 465 } 466 } 467 468 private static boolean add(AttributeBuilder attrBuilder, String className, 469 Set<String> values) 470 { 471 if (!values.isEmpty()) 472 { 473 attrBuilder.add(className + ":" + Utils.joinAsString(",", values)); 474 return true; 475 } 476 return false; 477 } 478 479 /** {@inheritDoc} */ 480 @Override 481 public boolean isConfigurationAcceptable(PluginCfg configuration, 482 List<LocalizableMessage> unacceptableReasons) 483 { 484 return true; 485 } 486 487 /** {@inheritDoc} */ 488 @Override 489 public boolean isConfigurationChangeAcceptable( 490 FractionalLDIFImportPluginCfg configuration, 491 List<LocalizableMessage> unacceptableReasons) 492 { 493 return true; 494 } 495 496 /** {@inheritDoc} */ 497 @Override 498 public ConfigChangeResult applyConfigurationChange( 499 FractionalLDIFImportPluginCfg configuration) 500 { 501 return new ConfigChangeResult(); 502 } 503}