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.util.Utils; 032import org.opends.server.admin.server.ConfigurationChangeListener; 033import org.opends.server.admin.server.ServerManagementContext; 034import org.opends.server.admin.std.server.FractionalLDIFImportPluginCfg; 035import org.opends.server.admin.std.server.PluginCfg; 036import org.opends.server.admin.std.server.ReplicationDomainCfg; 037import org.opends.server.admin.std.server.ReplicationSynchronizationProviderCfg; 038import org.opends.server.admin.std.server.RootCfg; 039import org.opends.server.api.plugin.DirectoryServerPlugin; 040import org.opends.server.api.plugin.PluginResult; 041import org.opends.server.api.plugin.PluginType; 042import org.opends.server.core.DirectoryServer; 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( 172 Entry entry) throws Exception { 173 174 // Retrieve the configuration 175 ServerManagementContext context = ServerManagementContext.getInstance(); 176 RootCfg root = context.getRootConfiguration(); 177 178 179 ReplicationSynchronizationProviderCfg sync = 180 (ReplicationSynchronizationProviderCfg) 181 root.getSynchronizationProvider("Multimaster Synchronization"); 182 183 String[] domainNames = sync.listReplicationDomains(); 184 if (domainNames == null) 185 { 186 // No domain in replication 187 return null; 188 } 189 190 // Find the configuration for domain the entry is part of 191 ReplicationDomainCfg matchingReplicatedDomainCfg = null; 192 for (String domainName : domainNames) 193 { 194 ReplicationDomainCfg replicationDomainCfg = 195 sync.getReplicationDomain(domainName); 196 // Is the entry a sub entry of the replicated domain main entry ? 197 DN replicatedDn = replicationDomainCfg.getBaseDN(); 198 DN entryDn = entry.getName(); 199 if (entryDn.isSubordinateOrEqualTo(replicatedDn)) 200 { 201 // Found the matching replicated domain configuration object 202 matchingReplicatedDomainCfg = replicationDomainCfg; 203 break; 204 } 205 } 206 207 if (matchingReplicatedDomainCfg == null) 208 { 209 // No matching replicated domain found 210 return null; 211 } 212 213 // Extract the fractional configuration from the domain configuration object 214 // and return it. 215 return FractionalConfig.toFractionalConfig(matchingReplicatedDomainCfg); 216 } 217 218 /** {@inheritDoc} */ 219 @Override 220 public final void doLDIFImportEnd(LDIFImportConfig importConfig) 221 { 222 // Remove the cookie of this import session 223 synchronized(importSessionContexts) 224 { 225 importSessionContexts.remove(importConfig); 226 } 227 } 228 229 /** 230 * See class comment for what we achieve here... 231 * {@inheritDoc} 232 */ 233 @Override 234 public final PluginResult.ImportLDIF doLDIFImport( 235 LDIFImportConfig importConfig, Entry entry) 236 { 237 /** 238 * try to get the import fractional context for this entry. If not found, 239 * create and initialize it. The mechanism here is done to take a lock only 240 * once for the whole import session (except the necessary lock of the 241 * doLDIFImportEnd method) 242 */ 243 ImportFractionalContext importFractionalContext = 244 importSessionContexts.get(importConfig); 245 246 DN entryDn = entry.getName(); 247 FractionalConfig localFractionalConfig = null; 248 249 // If no context, create it 250 if (importFractionalContext == null) 251 { 252 synchronized(importSessionContexts) 253 { 254 // Insure another thread was not creating the context at the same time 255 // (we would create it for the second time which is useless) 256 importFractionalContext = importSessionContexts.get(importConfig); 257 if (importFractionalContext == null) 258 { 259 /* 260 * Create context 261 */ 262 263 /** 264 * Retrieve the replicated domain this entry belongs to. Try to 265 * retrieve replication domain instance first. If we are in an online 266 * server, we should get it (if we are treating an entry that belongs 267 * to a replicated domain), otherwise the domain is not replicated or 268 * we are in an offline server context (import-ldif command run with 269 * offline server) and we must retrieve the fractional configuration 270 * directly from the configuration management system. 271 */ 272 LDAPReplicationDomain domain = 273 MultimasterReplication.findDomain(entryDn, null); 274 275 // Get the fractional configuration extracted from the local server 276 // configuration for the currently imported domain 277 if (domain == null) 278 { 279 // Server may be offline, attempt to find fractional configuration 280 // from config sub-system 281 try 282 { 283 localFractionalConfig = 284 getStaticReplicationDomainFractionalConfig(entry); 285 } catch (Exception ex) 286 { 287 return PluginResult.ImportLDIF.stopEntryProcessing( 288 ERR_FRACTIONAL_COULD_NOT_RETRIEVE_CONFIG.get(entry)); 289 } 290 } else 291 { 292 // Found a live domain, retrieve the fractional configuration from 293 // it. 294 localFractionalConfig = domain.getFractionalConfig(); 295 } 296 // Create context and store it 297 importFractionalContext = 298 new ImportFractionalContext(localFractionalConfig, domain); 299 importSessionContexts.put(importConfig, importFractionalContext); 300 } 301 } 302 } 303 304 // Extract the fractional configuration from the context 305 localFractionalConfig = importFractionalContext.getFractionalConfig(); 306 if (localFractionalConfig == null) 307 { 308 // Not part of a replicated domain : nothing to do 309 return PluginResult.ImportLDIF.continueEntryProcessing(); 310 } 311 312 /** 313 * At this point, either the domain instance has been found and we use its 314 * fractional configuration, or the server is offline and we use the parsed 315 * fractional configuration. We differentiate both cases testing if domain 316 * is null. We are also for sure handling an entry of a replicated suffix. 317 */ 318 319 // Is the entry to handle the root entry of the domain ? If yes, analyze the 320 // fractional configuration in it and compare with local fractional 321 // configuration. Stop the import if some inconsistency is detected 322 DN replicatedDomainBaseDn = localFractionalConfig.getBaseDn(); 323 if (replicatedDomainBaseDn.equals(entryDn)) 324 { 325 // This is the root entry, try to read a fractional configuration from it 326 Attribute exclAttr = getAttribute(REPLICATION_FRACTIONAL_EXCLUDE, entry); 327 Iterator<ByteString> exclIt = null; 328 if (exclAttr != null) 329 { 330 exclIt = exclAttr.iterator(); 331 } 332 333 Attribute inclAttr = getAttribute(REPLICATION_FRACTIONAL_INCLUDE, entry); 334 Iterator<ByteString> inclIt = null; 335 if (inclAttr != null) 336 { 337 inclIt = inclAttr.iterator(); 338 } 339 340 // Compare backend and local fractional configuration 341 if (isFractionalConfigConsistent(localFractionalConfig, exclIt, inclIt)) 342 { 343 // local and remote non/fractional config are equivalent : 344 // follow import, no need to go with filtering as remote backend 345 // should be ok 346 // let import finish 347 return PluginResult.ImportLDIF.continueEntryProcessing(); 348 } 349 350 if (localFractionalConfig.isFractional()) 351 { 352 // Local domain is fractional, remote domain has not same config 353 boolean remoteDomainHasSomeConfig = 354 isNotEmpty(exclAttr) || isNotEmpty(inclAttr); 355 if (remoteDomainHasSomeConfig) 356 { 357 LDAPReplicationDomain domain = importFractionalContext.getDomain(); 358 if (domain != null) 359 { 360 // Local domain is fractional, remote domain has some config which 361 // is different : stop import (error will be logged when import is 362 // stopped) 363 domain.setImportErrorMessageId(IMPORT_ERROR_MESSAGE_BAD_REMOTE); 364 return PluginResult.ImportLDIF.stopEntryProcessing(null); 365 } 366 367 return PluginResult.ImportLDIF.stopEntryProcessing( 368 NOTE_ERR_LDIF_IMPORT_FRACTIONAL_BAD_DATA_SET.get(replicatedDomainBaseDn)); 369 } 370 371 // Local domain is fractional but remote domain has no config : 372 // flush local config into root entry and follow import with filtering 373 flushFractionalConfigIntoEntry(localFractionalConfig, entry); 374 } 375 else 376 { 377 // Local domain is not fractional 378 LDAPReplicationDomain domain = importFractionalContext.getDomain(); 379 if (domain != null) 380 { 381 // Local domain is not fractional but remote one is : stop import : 382 //local domain should be configured with the same config as remote one 383 domain.setImportErrorMessageId( 384 IMPORT_ERROR_MESSAGE_REMOTE_IS_FRACTIONAL); 385 return PluginResult.ImportLDIF.stopEntryProcessing(null); 386 } 387 388 return PluginResult.ImportLDIF.stopEntryProcessing( 389 NOTE_ERR_LDIF_IMPORT_FRACTIONAL_DATA_SET_IS_FRACTIONAL.get(replicatedDomainBaseDn)); 390 } 391 } 392 393 // If we get here, local domain fractional configuration is enabled. 394 // Now filter for potential attributes to be removed. 395 LDAPReplicationDomain.fractionalRemoveAttributesFromEntry( 396 localFractionalConfig, entry.getName().rdn(), 397 entry.getObjectClasses(), entry.getUserAttributes(), true); 398 399 return PluginResult.ImportLDIF.continueEntryProcessing(); 400 } 401 402 private boolean isNotEmpty(Attribute attr) 403 { 404 return attr != null && attr.size() > 0; 405 } 406 407 private Attribute getAttribute(String attributeName, Entry entry) 408 { 409 List<Attribute> attrs = entry.getAttribute(DirectoryServer.getAttributeType(attributeName)); 410 return !attrs.isEmpty() ? attrs.get(0) : null; 411 } 412 413 /** 414 * Write the fractional configuration in the passed domain into the passed 415 * entry. WARNING: assumption is that no fractional attributes at all is 416 * already present in the passed entry. Also assumption is that domain 417 * fractional configuration is on. 418 * 419 * @param localFractionalConfig 420 * The local domain fractional configuration 421 * @param entry 422 * The entry to modify 423 */ 424 private static void flushFractionalConfigIntoEntry(FractionalConfig 425 localFractionalConfig, Entry entry) 426 { 427 if (localFractionalConfig.isFractional()) // Paranoia check 428 { 429 // Get the fractional configuration of the domain 430 boolean fractionalExclusive = 431 localFractionalConfig.isFractionalExclusive(); 432 Map<String, Set<String>> fractionalSpecificClassesAttributes = 433 localFractionalConfig.getFractionalSpecificClassesAttributes(); 434 Set<String> fractionalAllClassesAttributes = 435 localFractionalConfig.getFractionalAllClassesAttributes(); 436 437 // Create attribute builder for the right fractional mode 438 String fractAttribute = fractionalExclusive ? 439 REPLICATION_FRACTIONAL_EXCLUDE : REPLICATION_FRACTIONAL_INCLUDE; 440 AttributeBuilder attrBuilder = new AttributeBuilder(fractAttribute); 441 // Add attribute values for all classes 442 boolean somethingToFlush = 443 add(attrBuilder, "*", fractionalAllClassesAttributes); 444 445 // Add attribute values for specific classes 446 if (!fractionalSpecificClassesAttributes.isEmpty()) 447 { 448 for (Map.Entry<String, Set<String>> specific 449 : fractionalSpecificClassesAttributes.entrySet()) 450 { 451 if (add(attrBuilder, specific.getKey(), specific.getValue())) 452 { 453 somethingToFlush = true; 454 } 455 } 456 } 457 458 // Now flush attribute values into entry 459 if (somethingToFlush) 460 { 461 List<ByteString> duplicateValues = new ArrayList<>(); 462 entry.addAttribute(attrBuilder.toAttribute(), duplicateValues); 463 } 464 } 465 } 466 467 private static boolean add(AttributeBuilder attrBuilder, String className, 468 Set<String> values) 469 { 470 if (!values.isEmpty()) 471 { 472 attrBuilder.add(className + ":" + Utils.joinAsString(",", values)); 473 return true; 474 } 475 return false; 476 } 477 478 /** {@inheritDoc} */ 479 @Override 480 public boolean isConfigurationAcceptable(PluginCfg configuration, 481 List<LocalizableMessage> unacceptableReasons) 482 { 483 return true; 484 } 485 486 /** {@inheritDoc} */ 487 @Override 488 public boolean isConfigurationChangeAcceptable( 489 FractionalLDIFImportPluginCfg configuration, 490 List<LocalizableMessage> unacceptableReasons) 491 { 492 return true; 493 } 494 495 /** {@inheritDoc} */ 496 @Override 497 public ConfigChangeResult applyConfigurationChange( 498 FractionalLDIFImportPluginCfg configuration) 499 { 500 return new ConfigChangeResult(); 501 } 502}