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