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