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 2007-2009 Sun Microsystems, Inc. 025 * Portions Copyright 2011-2015 ForgeRock AS 026 */ 027package org.opends.server.core; 028 029import static org.forgerock.opendj.adapter.server3x.Converters.*; 030import static org.opends.messages.ConfigMessages.*; 031import static org.opends.server.util.StaticUtils.*; 032 033import java.util.*; 034import java.util.Map.Entry; 035import java.util.concurrent.ConcurrentHashMap; 036import java.util.concurrent.ConcurrentMap; 037 038import org.forgerock.i18n.LocalizableMessage; 039import org.forgerock.i18n.slf4j.LocalizedLogger; 040import org.forgerock.opendj.config.server.ConfigChangeResult; 041import org.forgerock.opendj.config.server.ConfigException; 042import org.forgerock.opendj.ldap.ResultCode; 043import org.forgerock.util.Utils; 044import org.opends.server.admin.ClassPropertyDefinition; 045import org.opends.server.admin.server.ConfigurationAddListener; 046import org.opends.server.admin.server.ConfigurationChangeListener; 047import org.opends.server.admin.server.ConfigurationDeleteListener; 048import org.opends.server.admin.server.ServerManagementContext; 049import org.opends.server.admin.std.meta.VirtualAttributeCfgDefn; 050import org.opends.server.admin.std.server.RootCfg; 051import org.opends.server.admin.std.server.VirtualAttributeCfg; 052import org.opends.server.api.VirtualAttributeProvider; 053import org.opends.server.types.*; 054 055/** 056 * This class defines a utility that will be used to manage the set of 057 * virtual attribute providers defined in the Directory Server. It will 058 * initialize the providers when the server starts, and then will manage any 059 * additions, removals, or modifications to any virtual attribute providers 060 * while the server is running. 061 */ 062public class VirtualAttributeConfigManager 063 implements ConfigurationChangeListener<VirtualAttributeCfg>, 064 ConfigurationAddListener<VirtualAttributeCfg>, 065 ConfigurationDeleteListener<VirtualAttributeCfg> 066{ 067 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 068 069 /** 070 * A mapping between the DNs of the config entries and the associated virtual 071 * attribute rules. 072 */ 073 private final ConcurrentMap<DN, VirtualAttributeRule> rules = new ConcurrentHashMap<>(); 074 075 private final ServerContext serverContext; 076 077 /** 078 * Creates a new instance of this virtual attribute config manager. 079 * 080 * @param serverContext 081 * The server context. 082 */ 083 public VirtualAttributeConfigManager(ServerContext serverContext) 084 { 085 this.serverContext = serverContext; 086 } 087 088 /** 089 * Initializes all virtual attribute providers currently defined in the 090 * Directory Server configuration. This should only be called at Directory 091 * Server startup. 092 * 093 * @throws ConfigException 094 * If a configuration problem causes the virtual attribute provider 095 * initialization process to fail. 096 * @throws InitializationException 097 * If a problem occurs while initializing the virtual attribute 098 * providers that is not related to the server configuration. 099 */ 100 public void initializeVirtualAttributes() 101 throws ConfigException, InitializationException 102 { 103 // Get the root configuration object. 104 ServerManagementContext managementContext = 105 ServerManagementContext.getInstance(); 106 RootCfg rootConfiguration = managementContext.getRootConfiguration(); 107 108 109 // Register as an add and delete listener with the root configuration so we 110 // can be notified if any virtual attribute provider entries are added or 111 // removed. 112 rootConfiguration.addVirtualAttributeAddListener(this); 113 rootConfiguration.addVirtualAttributeDeleteListener(this); 114 115 116 //Initialize the existing virtual attribute providers. 117 for (String providerName : rootConfiguration.listVirtualAttributes()) 118 { 119 VirtualAttributeCfg cfg = 120 rootConfiguration.getVirtualAttribute(providerName); 121 cfg.addChangeListener(this); 122 123 if (cfg.isEnabled()) 124 { 125 String className = cfg.getJavaClass(); 126 try 127 { 128 VirtualAttributeProvider<? extends VirtualAttributeCfg> provider = 129 loadProvider(className, cfg, true); 130 131 Map<LocalizableMessage, DirectoryException> reasons = 132 new LinkedHashMap<>(); 133 Set<SearchFilter> filters = buildFilters(cfg, reasons); 134 if (!reasons.isEmpty()) 135 { 136 Entry<LocalizableMessage, DirectoryException> entry = 137 reasons.entrySet().iterator().next(); 138 throw new ConfigException(entry.getKey(), entry.getValue()); 139 } 140 141 if (cfg.getAttributeType().isSingleValue()) 142 { 143 if (provider.isMultiValued()) 144 { 145 LocalizableMessage message = ERR_CONFIG_VATTR_SV_TYPE_WITH_MV_PROVIDER. 146 get(cfg.dn(), cfg.getAttributeType().getNameOrOID(), className); 147 throw new ConfigException(message); 148 } 149 else if (cfg.getConflictBehavior() == 150 VirtualAttributeCfgDefn.ConflictBehavior. 151 MERGE_REAL_AND_VIRTUAL) 152 { 153 LocalizableMessage message = ERR_CONFIG_VATTR_SV_TYPE_WITH_MERGE_VALUES. 154 get(cfg.dn(), cfg.getAttributeType().getNameOrOID()); 155 throw new ConfigException(message); 156 } 157 } 158 159 VirtualAttributeRule rule = createRule(cfg, provider, filters); 160 rules.put(cfg.dn(), rule); 161 } 162 catch (InitializationException ie) 163 { 164 logger.error(ie.getMessageObject()); 165 continue; 166 } 167 } 168 } 169 } 170 171 private VirtualAttributeRule createRule(VirtualAttributeCfg cfg, 172 VirtualAttributeProvider<? extends VirtualAttributeCfg> provider, 173 Set<SearchFilter> filters) 174 { 175 return new VirtualAttributeRule(cfg.getAttributeType(), provider, 176 cfg.getBaseDN(), 177 from(cfg.getScope()), 178 cfg.getGroupDN(), 179 filters, 180 cfg.getConflictBehavior()); 181 } 182 183 /** {@inheritDoc} */ 184 @Override 185 public boolean isConfigurationAddAcceptable( 186 VirtualAttributeCfg configuration, 187 List<LocalizableMessage> unacceptableReasons) 188 { 189 if (configuration.isEnabled()) 190 { 191 // Get the name of the class and make sure we can instantiate it as a 192 // virtual attribute provider. 193 String className = configuration.getJavaClass(); 194 try 195 { 196 loadProvider(className, configuration, false); 197 } 198 catch (InitializationException ie) 199 { 200 unacceptableReasons.add(ie.getMessageObject()); 201 return false; 202 } 203 } 204 205 // If there were any search filters provided, then make sure they are all 206 // valid. 207 return areFiltersAcceptable(configuration, unacceptableReasons); 208 } 209 210 private Set<SearchFilter> buildFilters(VirtualAttributeCfg cfg, 211 Map<LocalizableMessage, DirectoryException> unacceptableReasons) 212 { 213 Set<SearchFilter> filters = new LinkedHashSet<>(); 214 for (String filterString : cfg.getFilter()) 215 { 216 try 217 { 218 filters.add(SearchFilter.createFilterFromString(filterString)); 219 } 220 catch (DirectoryException de) 221 { 222 logger.traceException(de); 223 224 LocalizableMessage message = ERR_CONFIG_VATTR_INVALID_SEARCH_FILTER.get( 225 filterString, cfg.dn(), de.getMessageObject()); 226 unacceptableReasons.put(message, de); 227 } 228 } 229 return filters; 230 } 231 232 /** {@inheritDoc} */ 233 @Override 234 public ConfigChangeResult applyConfigurationAdd( 235 VirtualAttributeCfg configuration) 236 { 237 final ConfigChangeResult ccr = new ConfigChangeResult(); 238 239 configuration.addChangeListener(this); 240 241 if (! configuration.isEnabled()) 242 { 243 return ccr; 244 } 245 246 // Make sure that we can parse all of the search filters. 247 Map<LocalizableMessage, DirectoryException> reasons = 248 new LinkedHashMap<>(); 249 Set<SearchFilter> filters = buildFilters(configuration, reasons); 250 if (!reasons.isEmpty()) 251 { 252 ccr.getMessages().addAll(reasons.keySet()); 253 ccr.setResultCodeIfSuccess(ResultCode.INVALID_ATTRIBUTE_SYNTAX); 254 } 255 256 // Get the name of the class and make sure we can instantiate it as a 257 // certificate mapper. 258 VirtualAttributeProvider<? extends VirtualAttributeCfg> provider = null; 259 if (ccr.getResultCode() == ResultCode.SUCCESS) 260 { 261 String className = configuration.getJavaClass(); 262 try 263 { 264 provider = loadProvider(className, configuration, true); 265 } 266 catch (InitializationException ie) 267 { 268 ccr.setResultCode(DirectoryServer.getServerErrorResultCode()); 269 ccr.addMessage(ie.getMessageObject()); 270 } 271 } 272 273 if (ccr.getResultCode() == ResultCode.SUCCESS) 274 { 275 VirtualAttributeRule rule = createRule(configuration, provider, filters); 276 rules.put(configuration.dn(), rule); 277 } 278 279 return ccr; 280 } 281 282 /** {@inheritDoc} */ 283 @Override 284 public boolean isConfigurationDeleteAcceptable( 285 VirtualAttributeCfg configuration, 286 List<LocalizableMessage> unacceptableReasons) 287 { 288 // We will always allow getting rid of a virtual attribute rule. 289 return true; 290 } 291 292 /** {@inheritDoc} */ 293 @Override 294 public ConfigChangeResult applyConfigurationDelete( 295 VirtualAttributeCfg configuration) 296 { 297 final ConfigChangeResult ccr = new ConfigChangeResult(); 298 299 VirtualAttributeRule rule = rules.remove(configuration.dn()); 300 if (rule != null) 301 { 302 rule.getProvider().finalizeVirtualAttributeProvider(); 303 } 304 305 return ccr; 306 } 307 308 /** {@inheritDoc} */ 309 @Override 310 public boolean isConfigurationChangeAcceptable( 311 VirtualAttributeCfg configuration, 312 List<LocalizableMessage> unacceptableReasons) 313 { 314 if (configuration.isEnabled()) 315 { 316 // Get the name of the class and make sure we can instantiate it as a 317 // virtual attribute provider. 318 String className = configuration.getJavaClass(); 319 try 320 { 321 loadProvider(className, configuration, false); 322 } 323 catch (InitializationException ie) 324 { 325 unacceptableReasons.add(ie.getMessageObject()); 326 return false; 327 } 328 } 329 330 // If there were any search filters provided, then make sure they are all 331 // valid. 332 return areFiltersAcceptable(configuration, unacceptableReasons); 333 } 334 335 private boolean areFiltersAcceptable(VirtualAttributeCfg cfg, 336 List<LocalizableMessage> unacceptableReasons) 337 { 338 Map<LocalizableMessage, DirectoryException> reasons = 339 new LinkedHashMap<>(); 340 buildFilters(cfg, reasons); 341 if (!reasons.isEmpty()) 342 { 343 unacceptableReasons.addAll(reasons.keySet()); 344 return false; 345 } 346 return true; 347 } 348 349 /** {@inheritDoc} */ 350 @Override 351 public ConfigChangeResult applyConfigurationChange( 352 VirtualAttributeCfg configuration) 353 { 354 final ConfigChangeResult ccr = new ConfigChangeResult(); 355 356 357 // Get the existing rule if it's already enabled. 358 VirtualAttributeRule existingRule = rules.get(configuration.dn()); 359 360 361 // If the new configuration has the rule disabled, then disable it if it 362 // is enabled, or do nothing if it's already disabled. 363 if (! configuration.isEnabled()) 364 { 365 if (existingRule != null) 366 { 367 rules.remove(configuration.dn()); 368 existingRule.getProvider().finalizeVirtualAttributeProvider(); 369 } 370 371 return ccr; 372 } 373 374 375 // Make sure that we can parse all of the search filters. 376 Map<LocalizableMessage, DirectoryException> reasons = 377 new LinkedHashMap<>(); 378 Set<SearchFilter> filters = buildFilters(configuration, reasons); 379 if (!reasons.isEmpty()) 380 { 381 ccr.getMessages().addAll(reasons.keySet()); 382 ccr.setResultCodeIfSuccess(ResultCode.INVALID_ATTRIBUTE_SYNTAX); 383 } 384 385 // Get the name of the class and make sure we can instantiate it as a 386 // certificate mapper. 387 VirtualAttributeProvider<? extends VirtualAttributeCfg> provider = null; 388 if (ccr.getResultCode() == ResultCode.SUCCESS) 389 { 390 String className = configuration.getJavaClass(); 391 try 392 { 393 provider = loadProvider(className, configuration, true); 394 } 395 catch (InitializationException ie) 396 { 397 ccr.setResultCode(DirectoryServer.getServerErrorResultCode()); 398 ccr.addMessage(ie.getMessageObject()); 399 } 400 } 401 402 if (ccr.getResultCode() == ResultCode.SUCCESS) 403 { 404 VirtualAttributeRule rule = createRule(configuration, provider, filters); 405 rules.put(configuration.dn(), rule); 406 if (existingRule != null) 407 { 408 existingRule.getProvider().finalizeVirtualAttributeProvider(); 409 } 410 } 411 412 return ccr; 413 } 414 415 416 417 /** 418 * Loads the specified class, instantiates it as a certificate mapper, and 419 * optionally initializes that instance. 420 * 421 * @param className The fully-qualified name of the certificate mapper 422 * class to load, instantiate, and initialize. 423 * @param cfg The configuration to use to initialize the 424 * virtual attribute provider. It must not be 425 * {@code null}. 426 * @param initialize Indicates whether the virtual attribute provider 427 * instance should be initialized. 428 * 429 * @return The possibly initialized certificate mapper. 430 * 431 * @throws InitializationException If a problem occurred while attempting to 432 * initialize the certificate mapper. 433 */ 434 @SuppressWarnings({ "rawtypes", "unchecked" }) 435 private VirtualAttributeProvider<? extends VirtualAttributeCfg> 436 loadProvider(String className, VirtualAttributeCfg cfg, 437 boolean initialize) 438 throws InitializationException 439 { 440 try 441 { 442 VirtualAttributeCfgDefn definition = 443 VirtualAttributeCfgDefn.getInstance(); 444 ClassPropertyDefinition propertyDefinition = 445 definition.getJavaClassPropertyDefinition(); 446 Class<? extends VirtualAttributeProvider> providerClass = 447 propertyDefinition.loadClass(className, 448 VirtualAttributeProvider.class); 449 VirtualAttributeProvider provider = providerClass.newInstance(); 450 451 if (initialize) 452 { 453 provider.initializeVirtualAttributeProvider(cfg); 454 } 455 else 456 { 457 List<LocalizableMessage> unacceptableReasons = new ArrayList<>(); 458 if (!provider.isConfigurationAcceptable(cfg, unacceptableReasons)) 459 { 460 String reasons = Utils.joinAsString(". ", unacceptableReasons); 461 LocalizableMessage message = ERR_CONFIG_VATTR_CONFIG_NOT_ACCEPTABLE.get(cfg.dn(), reasons); 462 throw new InitializationException(message); 463 } 464 } 465 466 return provider; 467 } 468 catch (Exception e) 469 { 470 LocalizableMessage message = ERR_CONFIG_VATTR_INITIALIZATION_FAILED. 471 get(className, cfg.dn(), stackTraceToSingleLineString(e)); 472 throw new InitializationException(message, e); 473 } 474 } 475 476 /** 477 * Retrieves the collection of registered virtual attribute rules. 478 * 479 * @return The collection of registered virtual attribute rules. 480 */ 481 public Collection<VirtualAttributeRule> getVirtualAttributes() 482 { 483 return this.rules.values(); 484 } 485 486 /** 487 * Registers the provided virtual attribute rule. 488 * 489 * @param rule 490 * The virtual attribute rule to be registered. 491 */ 492 public void register(VirtualAttributeRule rule) 493 { 494 rules.put(getDummyDN(rule), rule); 495 } 496 497 /** 498 * Deregisters the provided virtual attribute rule. 499 * 500 * @param rule 501 * The virtual attribute rule to be deregistered. 502 */ 503 public void deregister(VirtualAttributeRule rule) 504 { 505 rules.remove(getDummyDN(rule)); 506 } 507 508 private DN getDummyDN(VirtualAttributeRule rule) 509 { 510 try 511 { 512 String name = rule.getAttributeType().getNameOrOID(); 513 return DN.valueOf("cn=" + name + ",cn=Virtual Attributes,cn=config"); 514 } 515 catch (DirectoryException e) 516 { 517 // should never happen 518 throw new RuntimeException(e); 519 } 520 } 521}