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 }