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    }