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