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