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