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