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