001 /* 002 * JBoss, Home of Professional Open Source. 003 * Copyright 2008, Red Hat Middleware LLC, and individual contributors 004 * as indicated by the @author tags. See the copyright.txt file in the 005 * distribution for a full listing of individual contributors. 006 * 007 * This is free software; you can redistribute it and/or modify it 008 * under the terms of the GNU Lesser General Public License as 009 * published by the Free Software Foundation; either version 2.1 of 010 * the License, or (at your option) any later version. 011 * 012 * This software is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * You should have received a copy of the GNU Lesser General Public 018 * License along with this software; if not, write to the Free 019 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 020 * 02110-1301 USA, or see the FSF site: http://www.fsf.org. 021 */ 022 package org.jboss.dna.connector.federation; 023 024 import java.util.Collections; 025 import java.util.Enumeration; 026 import java.util.HashMap; 027 import java.util.Hashtable; 028 import java.util.LinkedList; 029 import java.util.List; 030 import java.util.Map; 031 import java.util.concurrent.TimeUnit; 032 import java.util.concurrent.atomic.AtomicInteger; 033 import javax.naming.Context; 034 import javax.naming.RefAddr; 035 import javax.naming.Reference; 036 import javax.naming.StringRefAddr; 037 import javax.naming.spi.ObjectFactory; 038 import javax.security.auth.callback.Callback; 039 import javax.security.auth.callback.CallbackHandler; 040 import javax.security.auth.callback.NameCallback; 041 import javax.security.auth.callback.PasswordCallback; 042 import javax.security.auth.login.LoginException; 043 import net.jcip.annotations.ThreadSafe; 044 import org.jboss.dna.common.collection.Problems; 045 import org.jboss.dna.common.collection.SimpleProblems; 046 import org.jboss.dna.common.i18n.I18n; 047 import org.jboss.dna.common.util.CheckArg; 048 import org.jboss.dna.common.util.Logger; 049 import org.jboss.dna.connector.federation.executor.FederatingCommandExecutor; 050 import org.jboss.dna.connector.federation.executor.SingleProjectionCommandExecutor; 051 import org.jboss.dna.graph.ExecutionContext; 052 import org.jboss.dna.graph.ExecutionContextFactory; 053 import org.jboss.dna.graph.cache.BasicCachePolicy; 054 import org.jboss.dna.graph.cache.CachePolicy; 055 import org.jboss.dna.graph.commands.GraphCommand; 056 import org.jboss.dna.graph.commands.basic.BasicCompositeCommand; 057 import org.jboss.dna.graph.commands.basic.BasicGetChildrenCommand; 058 import org.jboss.dna.graph.commands.basic.BasicGetNodeCommand; 059 import org.jboss.dna.graph.commands.executor.CommandExecutor; 060 import org.jboss.dna.graph.commands.executor.LoggingCommandExecutor; 061 import org.jboss.dna.graph.commands.executor.NoOpCommandExecutor; 062 import org.jboss.dna.graph.connectors.RepositoryConnection; 063 import org.jboss.dna.graph.connectors.RepositoryConnectionFactory; 064 import org.jboss.dna.graph.connectors.RepositoryContext; 065 import org.jboss.dna.graph.connectors.RepositorySource; 066 import org.jboss.dna.graph.connectors.RepositorySourceCapabilities; 067 import org.jboss.dna.graph.connectors.RepositorySourceException; 068 import org.jboss.dna.graph.properties.InvalidPathException; 069 import org.jboss.dna.graph.properties.Name; 070 import org.jboss.dna.graph.properties.NameFactory; 071 import org.jboss.dna.graph.properties.Path; 072 import org.jboss.dna.graph.properties.PathFactory; 073 import org.jboss.dna.graph.properties.Property; 074 import org.jboss.dna.graph.properties.ValueFactories; 075 import org.jboss.dna.graph.properties.ValueFactory; 076 077 /** 078 * @author Randall Hauch 079 */ 080 @ThreadSafe 081 public class FederatedRepositorySource implements RepositorySource, ObjectFactory { 082 083 /** 084 */ 085 private static final long serialVersionUID = 7587346948013486977L; 086 087 /** 088 * The default limit is {@value} for retrying {@link RepositoryConnection connection} calls to the underlying source. 089 */ 090 public static final int DEFAULT_RETRY_LIMIT = 0; 091 092 public static final String DEFAULT_CONFIGURATION_SOURCE_PATH = "/"; 093 094 protected static final String REPOSITORY_NAME = "repositoryName"; 095 protected static final String SOURCE_NAME = "sourceName"; 096 protected static final String USERNAME = "username"; 097 protected static final String PASSWORD = "password"; 098 protected static final String CONFIGURATION_SOURCE_NAME = "configurationSourceName"; 099 protected static final String CONFIGURATION_SOURCE_PATH = "configurationSourcePath"; 100 protected static final String SECURITY_DOMAIN = "securityDomain"; 101 protected static final String RETRY_LIMIT = "retryLimit"; 102 103 public static final String PATH_TO_CONFIGURATION_INFORMATION = "/dna:system/dna:federation"; 104 public static final String DNA_CACHE_SEGMENT = "dna:cache"; 105 public static final String DNA_PROJECTIONS_SEGMENT = "dna:projections"; 106 public static final String PROJECTION_RULES_CONFIG_PROPERTY_NAME = "dna:projectionRules"; 107 public static final String CACHE_POLICY_TIME_TO_LIVE_CONFIG_PROPERTY_NAME = "dna:timeToCache"; 108 109 private String repositoryName; 110 private String sourceName; 111 private String username; 112 private String password; 113 private String configurationSourceName; 114 private String configurationSourcePath = DEFAULT_CONFIGURATION_SOURCE_PATH; 115 private String securityDomain; 116 private final AtomicInteger retryLimit = new AtomicInteger(DEFAULT_RETRY_LIMIT); 117 private transient FederatedRepository repository; 118 private transient RepositoryContext repositoryContext; 119 120 /** 121 * Create a new instance of the source, which must still be properly initialized with a {@link #setRepositoryName(String) 122 * repository name}. 123 */ 124 public FederatedRepositorySource() { 125 super(); 126 } 127 128 /** 129 * Create a new instance of the source with the required repository name and federation service. 130 * 131 * @param repositoryName the repository name 132 * @throws IllegalArgumentException if the federation service is null or the repository name is null or blank 133 */ 134 public FederatedRepositorySource( String repositoryName ) { 135 super(); 136 CheckArg.isNotNull(repositoryName, "repositoryName"); 137 this.repositoryName = repositoryName; 138 } 139 140 /** 141 * {@inheritDoc} 142 * 143 * @see org.jboss.dna.graph.connectors.RepositorySource#initialize(org.jboss.dna.graph.connectors.RepositoryContext) 144 */ 145 public void initialize( RepositoryContext context ) throws RepositorySourceException { 146 this.repositoryContext = context; 147 } 148 149 /** 150 * @return repositoryContext 151 */ 152 public RepositoryContext getRepositoryContext() { 153 return repositoryContext; 154 } 155 156 /** 157 * {@inheritDoc} 158 */ 159 public synchronized String getName() { 160 return sourceName; 161 } 162 163 /** 164 * Set the name of this source. 165 * <p> 166 * This is a required property. 167 * </p> 168 * 169 * @param sourceName the name of this repository source 170 * @see #setConfigurationSourceName(String) 171 * @see #setConfigurationSourcePath(String) 172 * @see #setPassword(String) 173 * @see #setUsername(String) 174 * @see #setRepositoryName(String) 175 * @see #setPassword(String) 176 * @see #setUsername(String) 177 * @see #setName(String) 178 */ 179 public synchronized void setName( String sourceName ) { 180 if (this.sourceName == sourceName || this.sourceName != null && this.sourceName.equals(sourceName)) return; // unchanged 181 this.sourceName = sourceName; 182 changeRepositoryConfig(); 183 } 184 185 /** 186 * {@inheritDoc} 187 * 188 * @see org.jboss.dna.graph.connectors.RepositorySource#getRetryLimit() 189 */ 190 public int getRetryLimit() { 191 return retryLimit.get(); 192 } 193 194 /** 195 * {@inheritDoc} 196 * 197 * @see org.jboss.dna.graph.connectors.RepositorySource#setRetryLimit(int) 198 */ 199 public void setRetryLimit( int limit ) { 200 retryLimit.set(limit < 0 ? 0 : limit); 201 } 202 203 /** 204 * Get the name in JNDI of a {@link RepositorySource} instance that should be used by the {@link FederatedRepository federated 205 * repository} as the configuration repository. 206 * <p> 207 * This is a required property. 208 * </p> 209 * 210 * @return the JNDI name of the {@link RepositorySource} instance that should be used for the configuration, or null if the 211 * federated repository instance is to be found in JNDI 212 * @see #setConfigurationSourceName(String) 213 */ 214 public String getConfigurationSourceName() { 215 return configurationSourceName; 216 } 217 218 /** 219 * Get the name of a {@link RepositorySource} instance that should be used by the {@link FederatedRepository federated 220 * repository} as the configuration repository. The instance will be retrieved from the {@link RepositoryConnectionFactory} 221 * instance from the {@link RepositoryContext#getRepositoryConnectionFactory() repository context} supplied during 222 * {@link RepositorySource#initialize(RepositoryContext) initialization}. 223 * <p> 224 * This is a required property. 225 * </p> 226 * 227 * @param sourceName the name of the {@link RepositorySource} instance that should be used for the configuration, or null if 228 * the federated repository instance is to be found in JNDI 229 * @see #getConfigurationSourceName() 230 * @see #setConfigurationSourcePath(String) 231 * @see #setPassword(String) 232 * @see #setUsername(String) 233 * @see #setRepositoryName(String) 234 * @see #setName(String) 235 */ 236 public void setConfigurationSourceName( String sourceName ) { 237 if (this.configurationSourceName == sourceName || this.configurationSourceName != null 238 && this.configurationSourceName.equals(sourceName)) return; // unchanged 239 this.configurationSourceName = sourceName; 240 changeRepositoryConfig(); 241 } 242 243 /** 244 * Get the path in the source that will be subgraph below the <code>/dna:system</code> branch of the repository. 245 * <p> 246 * This is a required property. 247 * </p> 248 * 249 * @return the string array of projection rules, or null if the projection rules haven't yet been set or if the federated 250 * repository instance is to be found in JNDI 251 * @see #setConfigurationSourcePath(String) 252 */ 253 public String getConfigurationSourcePath() { 254 return configurationSourcePath; 255 } 256 257 /** 258 * Set the path in the source that will be subgraph below the <code>/dna:system</code> branch of the repository. 259 * <p> 260 * This is a required property. 261 * </p> 262 * 263 * @param pathInSourceToConfigurationRoot the path within the configuration source to the node that should be the root of the 264 * configuration information, or null if the path hasn't yet been set or if the federated repository instance is to be 265 * found in JNDI 266 * @see #setConfigurationSourcePath(String) 267 * @see #setConfigurationSourceName(String) 268 * @see #setPassword(String) 269 * @see #setUsername(String) 270 * @see #setRepositoryName(String) 271 * @see #setName(String) 272 */ 273 public void setConfigurationSourcePath( String pathInSourceToConfigurationRoot ) { 274 if (this.configurationSourcePath == pathInSourceToConfigurationRoot || this.configurationSourcePath != null 275 && this.configurationSourcePath.equals(pathInSourceToConfigurationRoot)) return; 276 this.configurationSourcePath = pathInSourceToConfigurationRoot != null ? pathInSourceToConfigurationRoot : DEFAULT_CONFIGURATION_SOURCE_PATH; 277 changeRepositoryConfig(); 278 } 279 280 /** 281 * Get the name of the security domain that should be used by JAAS to identify the application or security context. This 282 * should correspond to the JAAS login configuration located within the JAAS login configuration file. 283 * 284 * @return securityDomain 285 */ 286 public String getSecurityDomain() { 287 return securityDomain; 288 } 289 290 /** 291 * Set the name of the security domain that should be used by JAAS to identify the application or security context. This 292 * should correspond to the JAAS login configuration located within the JAAS login configuration file. 293 * 294 * @param securityDomain Sets securityDomain to the specified value. 295 */ 296 public void setSecurityDomain( String securityDomain ) { 297 if (this.securityDomain != null && this.securityDomain.equals(securityDomain)) return; // unchanged 298 this.securityDomain = securityDomain; 299 changeRepositoryConfig(); 300 } 301 302 /** 303 * Get the name of the federated repository. 304 * <p> 305 * This is a required property. 306 * </p> 307 * 308 * @return the name of the repository 309 * @see #setRepositoryName(String) 310 */ 311 public synchronized String getRepositoryName() { 312 return this.repositoryName; 313 } 314 315 /** 316 * Get the name of the federated repository. 317 * <p> 318 * This is a required property. 319 * </p> 320 * 321 * @param repositoryName the new name of the repository 322 * @throws IllegalArgumentException if the repository name is null, empty or blank 323 * @see #getRepositoryName() 324 * @see #setConfigurationSourceName(String) 325 * @see #setConfigurationSourcePath(String) 326 * @see #setPassword(String) 327 * @see #setUsername(String) 328 * @see #setName(String) 329 */ 330 public synchronized void setRepositoryName( String repositoryName ) { 331 CheckArg.isNotEmpty(repositoryName, "repositoryName"); 332 if (this.repositoryName != null && this.repositoryName.equals(repositoryName)) return; // unchanged 333 this.repositoryName = repositoryName; 334 changeRepositoryConfig(); 335 } 336 337 /** 338 * Get the username that should be used when authenticating and {@link #getConnection() creating connections}. 339 * <p> 340 * This is an optional property, required only when authentication is to be used. 341 * </p> 342 * 343 * @return the username, or null if no username has been set or are not to be used 344 * @see #setUsername(String) 345 */ 346 public String getUsername() { 347 return this.username; 348 } 349 350 /** 351 * Set the username that should be used when authenticating and {@link #getConnection() creating connections}. 352 * <p> 353 * This is an optional property, required only when authentication is to be used. 354 * </p> 355 * 356 * @param username the username, or null if no username has been set or are not to be used 357 * @see #getUsername() 358 * @see #setPassword(String) 359 * @see #setConfigurationSourceName(String) 360 * @see #setConfigurationSourcePath(String) 361 * @see #setPassword(String) 362 * @see #setRepositoryName(String) 363 * @see #setName(String) 364 */ 365 public void setUsername( String username ) { 366 if (this.username != null && this.username.equals(username)) return; // unchanged 367 this.username = username; 368 changeRepositoryConfig(); 369 } 370 371 /** 372 * Get the password that should be used when authenticating and {@link #getConnection() creating connections}. 373 * <p> 374 * This is an optional property, required only when authentication is to be used. 375 * </p> 376 * 377 * @return the password, or null if no password have been set or are not to be used 378 * @see #setPassword(String) 379 */ 380 public String getPassword() { 381 return this.password; 382 } 383 384 /** 385 * Get the password that should be used when authenticating and {@link #getConnection() creating connections}. 386 * <p> 387 * This is an optional property, required only when authentication is to be used. 388 * </p> 389 * 390 * @param password the password, or null if no password have been set or are not to be used 391 * @see #getPassword() 392 * @see #setConfigurationSourceName(String) 393 * @see #setConfigurationSourcePath(String) 394 * @see #setUsername(String) 395 * @see #setRepositoryName(String) 396 * @see #setName(String) 397 */ 398 public void setPassword( String password ) { 399 if (this.password != null && this.password.equals(password)) return; // unchanged 400 this.password = password; 401 changeRepositoryConfig(); 402 } 403 404 /** 405 * This method is called to signal that some aspect of the configuration has changed. If a {@link #getRepository() repository} 406 * instance has been created, it's configuration is 407 * {@link #getRepositoryConfiguration(ExecutionContext, RepositoryConnectionFactory) rebuilt} and updated. Nothing is done, 408 * however, if there is currently no {@link #getRepository() repository}. 409 */ 410 protected synchronized void changeRepositoryConfig() { 411 if (this.repository != null) { 412 RepositoryContext repositoryContext = getRepositoryContext(); 413 if (repositoryContext != null) { 414 // Find in JNDI the repository source registry and the environment ... 415 ExecutionContext context = getExecutionContext(); 416 RepositoryConnectionFactory factory = getRepositoryContext().getRepositoryConnectionFactory(); 417 // Compute a new repository config and set it on the repository ... 418 FederatedRepositoryConfig newConfig = getRepositoryConfiguration(context, factory); 419 this.repository.setConfiguration(newConfig); 420 } 421 } 422 } 423 424 /** 425 * {@inheritDoc} 426 * 427 * @see org.jboss.dna.graph.connectors.RepositorySource#getConnection() 428 */ 429 public RepositoryConnection getConnection() throws RepositorySourceException { 430 if (getName() == null) { 431 I18n msg = FederationI18n.propertyIsRequired; 432 throw new RepositorySourceException(getName(), msg.text("name")); 433 } 434 if (getRepositoryContext() == null) { 435 I18n msg = FederationI18n.propertyIsRequired; 436 throw new RepositorySourceException(getName(), msg.text("repository context")); 437 } 438 if (getUsername() != null && getSecurityDomain() == null) { 439 I18n msg = FederationI18n.propertyIsRequired; 440 throw new RepositorySourceException(getName(), msg.text("security domain")); 441 } 442 // Find the repository ... 443 FederatedRepository repository = getRepository(); 444 // Authenticate the user ... 445 String username = this.username; 446 Object credentials = this.password; 447 RepositoryConnection connection = repository.createConnection(this, username, credentials); 448 if (connection == null) { 449 I18n msg = FederationI18n.unableToAuthenticateConnectionToFederatedRepository; 450 throw new RepositorySourceException(msg.text(this.repositoryName, username)); 451 } 452 // Return the new connection ... 453 return connection; 454 } 455 456 /** 457 * Get the {@link FederatedRepository} instance that this source is using. This method uses the following logic: 458 * <ol> 459 * <li>If a {@link FederatedRepository} already was obtained from a prior call, the same instance is returned.</li> 460 * <li>A {@link FederatedRepository} is created using a {@link FederatedRepositoryConfig} is created from this instance's 461 * properties and {@link ExecutionContext} and {@link RepositoryConnectionFactory} instances obtained from JNDI.</li> 462 * <li></li> 463 * <li></li> 464 * </ol> 465 * 466 * @return the federated repository instance 467 * @throws RepositorySourceException 468 */ 469 protected synchronized FederatedRepository getRepository() throws RepositorySourceException { 470 if (repository == null) { 471 ExecutionContext context = getExecutionContext(); 472 RepositoryConnectionFactory connectionFactory = getRepositoryContext().getRepositoryConnectionFactory(); 473 // And create the configuration and the repository ... 474 FederatedRepositoryConfig config = getRepositoryConfiguration(context, connectionFactory); 475 repository = new FederatedRepository(context, connectionFactory, config); 476 } 477 return repository; 478 } 479 480 protected ExecutionContext getExecutionContext() { 481 ExecutionContextFactory factory = getRepositoryContext().getExecutionContextFactory(); 482 CallbackHandler handler = createCallbackHandler(); 483 try { 484 String securityDomain = getSecurityDomain(); 485 if (securityDomain != null || getUsername() != null) { 486 return factory.create(securityDomain, handler); 487 } 488 return factory.create(); 489 } catch (LoginException e) { 490 I18n msg = FederationI18n.unableToCreateExecutionContext; 491 throw new RepositorySourceException(getName(), msg.text(this.sourceName, securityDomain), e); 492 } 493 } 494 495 protected CallbackHandler createCallbackHandler() { 496 return new CallbackHandler() { 497 public void handle( Callback[] callbacks ) { 498 for (Callback callback : callbacks) { 499 if (callback instanceof NameCallback) { 500 NameCallback nameCallback = (NameCallback)callback; 501 nameCallback.setName(FederatedRepositorySource.this.getUsername()); 502 } 503 if (callback instanceof PasswordCallback) { 504 PasswordCallback passwordCallback = (PasswordCallback)callback; 505 passwordCallback.setPassword(FederatedRepositorySource.this.getPassword().toCharArray()); 506 } 507 } 508 } 509 }; 510 } 511 512 /** 513 * Create a {@link FederatedRepositoryConfig} instance from the current properties of this instance. This method does 514 * <i>not</i> modify the state of this instance. 515 * 516 * @param context the execution context that should be used to read the configuration; may not be null 517 * @param connectionFactory the factory for {@link RepositoryConnection}s can be obtained; may not be null 518 * @return a configuration reflecting the current state of this instance 519 */ 520 protected synchronized FederatedRepositoryConfig getRepositoryConfiguration( ExecutionContext context, 521 RepositoryConnectionFactory connectionFactory ) { 522 Problems problems = new SimpleProblems(); 523 ValueFactories valueFactories = context.getValueFactories(); 524 PathFactory pathFactory = valueFactories.getPathFactory(); 525 NameFactory nameFactory = valueFactories.getNameFactory(); 526 ValueFactory<Long> longFactory = valueFactories.getLongFactory(); 527 528 // Create the configuration projection ... 529 ProjectionParser projectionParser = ProjectionParser.getInstance(); 530 String ruleStr = "/dna:system => " + this.getConfigurationSourcePath(); 531 Projection.Rule[] rules = projectionParser.rulesFromStrings(context, ruleStr); 532 Projection configurationProjection = new Projection(this.getConfigurationSourceName(), rules); 533 534 // Create a federating command executor to execute the commands and merge the results into a single set of 535 // commands. 536 final String configurationSourceName = configurationProjection.getSourceName(); 537 List<Projection> projections = Collections.singletonList(configurationProjection); 538 CommandExecutor executor = null; 539 if (configurationProjection.getRules().size() == 0) { 540 // There is no projection for the configuration repository, so just use a no-op executor 541 executor = new NoOpCommandExecutor(context, configurationSourceName); 542 } else if (configurationProjection.isSimple()) { 543 // There is just a single projection for the configuration repository, so just use an executor that 544 // translates the paths using the projection 545 executor = new SingleProjectionCommandExecutor(context, configurationSourceName, configurationProjection, 546 connectionFactory); 547 } else { 548 // The configuration repository has more than one projection, so we need to merge the results 549 executor = new FederatingCommandExecutor(context, configurationSourceName, projections, connectionFactory); 550 } 551 // Wrap the executor with a logging executor ... 552 executor = new LoggingCommandExecutor(executor, context.getLogger(getClass()), Logger.Level.DEBUG); 553 554 // The configuration projection (via "executor") will convert this path into a path that exists in the configuration 555 // repository 556 Path configNode = pathFactory.create(PATH_TO_CONFIGURATION_INFORMATION); 557 558 try { 559 // Get the repository node ... 560 BasicGetNodeCommand getRepository = new BasicGetNodeCommand(configNode); 561 executor.execute(getRepository); 562 if (getRepository.hasError()) { 563 throw new FederationException(FederationI18n.federatedRepositoryCannotBeFound.text(repositoryName)); 564 } 565 566 // Get the first child node of the "dna:cache" node, since this represents the source used as the cache ... 567 Path cacheNode = pathFactory.create(configNode, nameFactory.create(DNA_CACHE_SEGMENT)); 568 BasicGetChildrenCommand getCacheSource = new BasicGetChildrenCommand(cacheNode); 569 570 executor.execute(getCacheSource); 571 if (getCacheSource.hasError() || getCacheSource.getChildren().size() < 1) { 572 I18n msg = FederationI18n.requiredNodeDoesNotExistRelativeToNode; 573 throw new FederationException(msg.text(DNA_CACHE_SEGMENT, configNode)); 574 } 575 576 // Add a command to get the projection defining the cache ... 577 Path pathToCacheRegion = pathFactory.create(cacheNode, getCacheSource.getChildren().get(0)); 578 BasicGetNodeCommand getCacheRegion = new BasicGetNodeCommand(pathToCacheRegion); 579 executor.execute(getCacheRegion); 580 Projection cacheProjection = createProjection(context, 581 projectionParser, 582 getCacheRegion.getPath(), 583 getCacheRegion.getPropertiesByName(), 584 problems); 585 586 if (getCacheRegion.hasError()) { 587 I18n msg = FederationI18n.requiredNodeDoesNotExistRelativeToNode; 588 throw new FederationException(msg.text(DNA_CACHE_SEGMENT, configNode)); 589 } 590 591 // Get the source projections for the repository ... 592 Path projectionsNode = pathFactory.create(configNode, nameFactory.create(DNA_PROJECTIONS_SEGMENT)); 593 BasicGetChildrenCommand getProjections = new BasicGetChildrenCommand(projectionsNode); 594 595 executor.execute(getProjections); 596 if (getProjections.hasError()) { 597 I18n msg = FederationI18n.requiredNodeDoesNotExistRelativeToNode; 598 throw new FederationException(msg.text(DNA_PROJECTIONS_SEGMENT, configNode)); 599 } 600 601 // Build the commands to get each of the projections (children of the "dna:projections" node) ... 602 List<Projection> sourceProjections = new LinkedList<Projection>(); 603 if (getProjections.hasNoError() && !getProjections.getChildren().isEmpty()) { 604 BasicCompositeCommand commands = new BasicCompositeCommand(); 605 for (Path.Segment child : getProjections.getChildren()) { 606 final Path pathToSource = pathFactory.create(projectionsNode, child); 607 commands.add(new BasicGetNodeCommand(pathToSource)); 608 } 609 // Now execute these commands ... 610 executor.execute(commands); 611 612 // Iterate over each region node obtained ... 613 for (GraphCommand command : commands) { 614 BasicGetNodeCommand getProjectionCommand = (BasicGetNodeCommand)command; 615 if (getProjectionCommand.hasNoError()) { 616 Projection projection = createProjection(context, 617 projectionParser, 618 getProjectionCommand.getPath(), 619 getProjectionCommand.getPropertiesByName(), 620 problems); 621 if (projection != null) { 622 Logger logger = context.getLogger(getClass()); 623 if (logger.isTraceEnabled()) { 624 logger.trace("Adding projection to federated repository {0}: {1}", 625 getRepositoryName(), 626 projection); 627 } 628 sourceProjections.add(projection); 629 } 630 } 631 } 632 } 633 634 // Look for the default cache policy ... 635 BasicCachePolicy cachePolicy = new BasicCachePolicy(); 636 Property timeToLiveProperty = getRepository.getPropertiesByName().get(nameFactory.create(CACHE_POLICY_TIME_TO_LIVE_CONFIG_PROPERTY_NAME)); 637 if (timeToLiveProperty != null && !timeToLiveProperty.isEmpty()) { 638 cachePolicy.setTimeToLive(longFactory.create(timeToLiveProperty.getValues().next()), TimeUnit.MILLISECONDS); 639 } 640 CachePolicy defaultCachePolicy = cachePolicy.isEmpty() ? null : cachePolicy.getUnmodifiable(); 641 return new FederatedRepositoryConfig(repositoryName, cacheProjection, sourceProjections, defaultCachePolicy); 642 } catch (InvalidPathException err) { 643 I18n msg = FederationI18n.federatedRepositoryCannotBeFound; 644 throw new FederationException(msg.text(repositoryName)); 645 } finally { 646 executor.close(); 647 } 648 649 } 650 651 /** 652 * Instantiate the {@link Projection} described by the supplied properties. 653 * 654 * @param context the execution context that should be used to read the configuration; may not be null 655 * @param projectionParser the projection rule parser that should be used; may not be null 656 * @param path the path to the node where these properties were found; never null 657 * @param properties the properties; never null 658 * @param problems the problems container in which any problems should be reported; never null 659 * @return the region instance, or null if it could not be created 660 */ 661 protected Projection createProjection( ExecutionContext context, 662 ProjectionParser projectionParser, 663 Path path, 664 Map<Name, Property> properties, 665 Problems problems ) { 666 ValueFactories valueFactories = context.getValueFactories(); 667 NameFactory nameFactory = valueFactories.getNameFactory(); 668 ValueFactory<String> stringFactory = valueFactories.getStringFactory(); 669 670 String sourceName = path.getLastSegment().getName().getLocalName(); 671 672 // Get the rules ... 673 Projection.Rule[] projectionRules = null; 674 Property projectionRulesProperty = properties.get(nameFactory.create(PROJECTION_RULES_CONFIG_PROPERTY_NAME)); 675 if (projectionRulesProperty != null && !projectionRulesProperty.isEmpty()) { 676 String[] projectionRuleStrs = stringFactory.create(projectionRulesProperty.getValuesAsArray()); 677 if (projectionRuleStrs != null && projectionRuleStrs.length != 0) { 678 projectionRules = projectionParser.rulesFromStrings(context, projectionRuleStrs); 679 } 680 } 681 if (problems.hasErrors()) return null; 682 683 Projection region = new Projection(sourceName, projectionRules); 684 return region; 685 } 686 687 /** 688 * {@inheritDoc} 689 */ 690 public synchronized Reference getReference() { 691 String className = getClass().getName(); 692 String factoryClassName = this.getClass().getName(); 693 Reference ref = new Reference(className, factoryClassName, null); 694 695 if (getRepositoryName() != null) { 696 ref.add(new StringRefAddr(REPOSITORY_NAME, getRepositoryName())); 697 } 698 if (getName() != null) { 699 ref.add(new StringRefAddr(SOURCE_NAME, getName())); 700 } 701 if (getUsername() != null) { 702 ref.add(new StringRefAddr(USERNAME, getUsername())); 703 } 704 if (getPassword() != null) { 705 ref.add(new StringRefAddr(PASSWORD, getPassword())); 706 } 707 if (getConfigurationSourceName() != null) { 708 ref.add(new StringRefAddr(CONFIGURATION_SOURCE_NAME, getConfigurationSourceName())); 709 } 710 if (getConfigurationSourcePath() != null) { 711 ref.add(new StringRefAddr(CONFIGURATION_SOURCE_PATH, getConfigurationSourcePath())); 712 } 713 if (getSecurityDomain() != null) { 714 ref.add(new StringRefAddr(SECURITY_DOMAIN, getSecurityDomain())); 715 } 716 ref.add(new StringRefAddr(RETRY_LIMIT, Integer.toString(getRetryLimit()))); 717 return ref; 718 } 719 720 /** 721 * {@inheritDoc} 722 */ 723 public Object getObjectInstance( Object obj, 724 javax.naming.Name name, 725 Context nameCtx, 726 Hashtable<?, ?> environment ) throws Exception { 727 if (obj instanceof Reference) { 728 Map<String, String> values = new HashMap<String, String>(); 729 Reference ref = (Reference)obj; 730 Enumeration<?> en = ref.getAll(); 731 while (en.hasMoreElements()) { 732 RefAddr subref = (RefAddr)en.nextElement(); 733 if (subref instanceof StringRefAddr) { 734 String key = subref.getType(); 735 Object value = subref.getContent(); 736 if (value != null) values.put(key, value.toString()); 737 } 738 } 739 String repositoryName = values.get(FederatedRepositorySource.REPOSITORY_NAME); 740 String sourceName = values.get(FederatedRepositorySource.SOURCE_NAME); 741 String username = values.get(FederatedRepositorySource.USERNAME); 742 String password = values.get(FederatedRepositorySource.PASSWORD); 743 String configurationSourceName = values.get(FederatedRepositorySource.CONFIGURATION_SOURCE_NAME); 744 String configurationSourcePath = values.get(FederatedRepositorySource.CONFIGURATION_SOURCE_PATH); 745 String securityDomain = values.get(FederatedRepositorySource.SECURITY_DOMAIN); 746 String retryLimit = values.get(FederatedRepositorySource.RETRY_LIMIT); 747 748 // Create the source instance ... 749 FederatedRepositorySource source = new FederatedRepositorySource(); 750 if (repositoryName != null) source.setRepositoryName(repositoryName); 751 if (sourceName != null) source.setName(sourceName); 752 if (username != null) source.setUsername(username); 753 if (password != null) source.setPassword(password); 754 if (configurationSourceName != null) source.setConfigurationSourceName(configurationSourceName); 755 if (configurationSourcePath != null) source.setConfigurationSourcePath(configurationSourcePath); 756 if (securityDomain != null) source.setSecurityDomain(securityDomain); 757 if (retryLimit != null) source.setRetryLimit(Integer.parseInt(retryLimit)); 758 return source; 759 } 760 return null; 761 } 762 763 /** 764 * {@inheritDoc} 765 */ 766 @Override 767 public int hashCode() { 768 return repositoryName.hashCode(); 769 } 770 771 /** 772 * {@inheritDoc} 773 */ 774 @Override 775 public boolean equals( Object obj ) { 776 if (obj == this) return true; 777 if (obj instanceof FederatedRepositorySource) { 778 FederatedRepositorySource that = (FederatedRepositorySource)obj; 779 // The repository name, source name, and federation service must all match 780 if (!this.getRepositoryName().equals(that.getRepositoryName())) return false; 781 if (this.getName() == null) { 782 if (that.getName() != null) return false; 783 } else { 784 if (!this.getName().equals(that.getName())) return false; 785 } 786 return true; 787 } 788 return false; 789 } 790 791 /** 792 * {@inheritDoc} 793 * 794 * @see org.jboss.dna.graph.connectors.RepositorySource#getCapabilities() 795 */ 796 public RepositorySourceCapabilities getCapabilities() { 797 return new Capabilities(); 798 } 799 800 protected class Capabilities implements RepositorySourceCapabilities { 801 public boolean supportsSameNameSiblings() { 802 return true; 803 } 804 805 public boolean supportsUpdates() { 806 return true; 807 } 808 } 809 810 }