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 * Unless otherwise indicated, all code in JBoss DNA is licensed
010 * 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.graph.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.ExecutorService;
033 import java.util.concurrent.Executors;
034 import java.util.concurrent.TimeUnit;
035 import javax.naming.Context;
036 import javax.naming.Name;
037 import javax.naming.RefAddr;
038 import javax.naming.Reference;
039 import javax.naming.StringRefAddr;
040 import javax.naming.spi.ObjectFactory;
041 import net.jcip.annotations.GuardedBy;
042 import org.jboss.dna.common.i18n.I18n;
043 import org.jboss.dna.common.util.HashCode;
044 import org.jboss.dna.graph.DnaLexicon;
045 import org.jboss.dna.graph.ExecutionContext;
046 import org.jboss.dna.graph.GraphI18n;
047 import org.jboss.dna.graph.Location;
048 import org.jboss.dna.graph.Node;
049 import org.jboss.dna.graph.Subgraph;
050 import org.jboss.dna.graph.SubgraphNode;
051 import org.jboss.dna.graph.cache.BasicCachePolicy;
052 import org.jboss.dna.graph.cache.CachePolicy;
053 import org.jboss.dna.graph.connector.RepositoryConnection;
054 import org.jboss.dna.graph.connector.RepositoryConnectionFactory;
055 import org.jboss.dna.graph.connector.RepositoryContext;
056 import org.jboss.dna.graph.connector.RepositorySource;
057 import org.jboss.dna.graph.connector.RepositorySourceCapabilities;
058 import org.jboss.dna.graph.connector.RepositorySourceException;
059 import org.jboss.dna.graph.observe.Observer;
060 import org.jboss.dna.graph.property.NamespaceRegistry;
061 import org.jboss.dna.graph.property.Path;
062 import org.jboss.dna.graph.property.Property;
063 import org.jboss.dna.graph.property.ValueFactories;
064 import org.jboss.dna.graph.property.ValueFactory;
065
066 /**
067 *
068 */
069 public class FederatedRepositorySource implements RepositorySource, ObjectFactory {
070
071 /**
072 * The default limit is {@value} for retrying {@link RepositoryConnection connection} calls to the underlying source.
073 */
074 public static final int DEFAULT_RETRY_LIMIT = 0;
075
076 protected static final String SOURCE_NAME = "sourceName";
077 protected static final String RETRY_LIMIT = "retryLimit";
078
079 private static final long serialVersionUID = 1L;
080
081 private volatile String name;
082 private volatile int retryLimit;
083 private volatile RepositorySourceCapabilities capabilities = new RepositorySourceCapabilities(true, true, false, false, true);
084 private volatile transient FederatedRepository configuration;
085 private volatile transient RepositoryContext context;
086
087 /**
088 * Construct a new instance of a {@link RepositorySource} for a federated repository.
089 */
090 public FederatedRepositorySource() {
091 }
092
093 /**
094 * {@inheritDoc}
095 *
096 * @see org.jboss.dna.graph.connector.RepositorySource#getName()
097 */
098 public String getName() {
099 return name;
100 }
101
102 /**
103 * @param name Sets name to the specified value.
104 */
105 public synchronized void setName( String name ) {
106 if (this.name == name || this.name != null && this.name.equals(name)) return; // unchanged
107 this.name = name;
108 changeConfiguration();
109 }
110
111 /**
112 * {@inheritDoc}
113 *
114 * @see org.jboss.dna.graph.connector.RepositorySource#getRetryLimit()
115 */
116 public int getRetryLimit() {
117 return retryLimit;
118 }
119
120 /**
121 * {@inheritDoc}
122 *
123 * @see org.jboss.dna.graph.connector.RepositorySource#setRetryLimit(int)
124 */
125 public synchronized void setRetryLimit( int limit ) {
126 retryLimit = limit < 0 ? 0 : limit;
127 changeConfiguration();
128 }
129
130 /**
131 * {@inheritDoc}
132 *
133 * @see org.jboss.dna.graph.connector.RepositorySource#getCapabilities()
134 */
135 public RepositorySourceCapabilities getCapabilities() {
136 return capabilities;
137 }
138
139 /**
140 * {@inheritDoc}
141 *
142 * @see org.jboss.dna.graph.connector.RepositorySource#initialize(org.jboss.dna.graph.connector.RepositoryContext)
143 */
144 public synchronized void initialize( RepositoryContext context ) throws RepositorySourceException {
145 this.context = context;
146 changeConfiguration();
147 }
148
149 /**
150 * Get the repository context that was used to {@link #initialize(RepositoryContext) initialize} this source.
151 *
152 * @return the context, or null if the source was not yet {@link #initialize(RepositoryContext) initialized}
153 */
154 /*package*/RepositoryContext getRepositoryContext() {
155 return context;
156 }
157
158 /**
159 * {@inheritDoc}
160 *
161 * @see org.jboss.dna.graph.connector.RepositorySource#getConnection()
162 */
163 public RepositoryConnection getConnection() throws RepositorySourceException {
164 FederatedRepository config = this.configuration;
165 if (config == null) {
166 synchronized (this) {
167 if (this.configuration == null) {
168 // Check all the properties of this source ...
169 String name = getName();
170 if (name == null) {
171 I18n msg = GraphI18n.namePropertyIsRequiredForFederatedRepositorySource;
172 throw new RepositorySourceException(getName(), msg.text("name"));
173 }
174 RepositoryContext repositoryContext = getRepositoryContext();
175 if (repositoryContext == null) {
176 I18n msg = GraphI18n.federatedRepositorySourceMustBeInitialized;
177 throw new RepositorySourceException(getName(), msg.text("name", name));
178 }
179
180 // Load the configuration ...
181 this.configuration = loadRepository(name, repositoryContext);
182 }
183 config = this.configuration;
184 }
185 }
186 Observer observer = this.context != null ? this.context.getObserver() : null;
187 return new FederatedRepositoryConnection(config, observer);
188 }
189
190 /**
191 * {@inheritDoc}
192 *
193 * @see javax.naming.Referenceable#getReference()
194 */
195 public Reference getReference() {
196 String className = getClass().getName();
197 String factoryClassName = this.getClass().getName();
198 Reference ref = new Reference(className, factoryClassName, null);
199
200 ref.add(new StringRefAddr(SOURCE_NAME, getName()));
201 ref.add(new StringRefAddr(RETRY_LIMIT, Integer.toString(getRetryLimit())));
202 return ref;
203 }
204
205 /**
206 * {@inheritDoc}
207 *
208 * @see javax.naming.spi.ObjectFactory#getObjectInstance(java.lang.Object, javax.naming.Name, javax.naming.Context,
209 * java.util.Hashtable)
210 */
211 public Object getObjectInstance( Object obj,
212 Name name,
213 Context nameCtx,
214 Hashtable<?, ?> environment ) throws Exception {
215 if (obj instanceof Reference) {
216 Map<String, String> values = new HashMap<String, String>();
217 Reference ref = (Reference)obj;
218 Enumeration<?> en = ref.getAll();
219 while (en.hasMoreElements()) {
220 RefAddr subref = (RefAddr)en.nextElement();
221 if (subref instanceof StringRefAddr) {
222 String key = subref.getType();
223 Object value = subref.getContent();
224 if (value != null) values.put(key, value.toString());
225 }
226 }
227 String sourceName = values.get(SOURCE_NAME);
228 String retryLimit = values.get(RETRY_LIMIT);
229
230 // Create the source instance ...
231 FederatedRepositorySource source = new FederatedRepositorySource();
232 if (sourceName != null) source.setName(sourceName);
233 if (retryLimit != null) source.setRetryLimit(Integer.parseInt(retryLimit));
234 return source;
235 }
236 return null;
237 }
238
239 /**
240 * {@inheritDoc}
241 */
242 @Override
243 public int hashCode() {
244 return HashCode.compute(getName());
245 }
246
247 /**
248 * {@inheritDoc}
249 */
250 @Override
251 public boolean equals( Object obj ) {
252 if (obj == this) return true;
253 if (obj instanceof FederatedRepositorySource) {
254 FederatedRepositorySource that = (FederatedRepositorySource)obj;
255 // The source name must match
256 if (this.getName() == null) {
257 if (that.getName() != null) return false;
258 } else {
259 if (!this.getName().equals(that.getName())) return false;
260 }
261 return true;
262 }
263 return false;
264 }
265
266 /**
267 * Mark the current configuration (if there is one) as being invalid.
268 */
269 @GuardedBy( "this" )
270 protected void changeConfiguration() {
271 this.configuration = null;
272 }
273
274 /**
275 * Utility to load the current configuration for this source from the {@link RepositoryContext#getConfiguration(int)
276 * configuration repository}. This method may only be called after the source is {@link #initialize(RepositoryContext)
277 * initialized}.
278 *
279 * @param name the name of the source; may not be null
280 * @param repositoryContext the repository context; may not be null
281 * @return the configuration; never null
282 * @throws RepositorySourceException if there is a problem with the configuration
283 */
284 protected FederatedRepository loadRepository( String name,
285 RepositoryContext repositoryContext ) throws RepositorySourceException {
286 // All the required properties have been set ...
287 ExecutionContext executionContext = repositoryContext.getExecutionContext();
288 RepositoryConnectionFactory connectionFactory = repositoryContext.getRepositoryConnectionFactory();
289 ValueFactories valueFactories = executionContext.getValueFactories();
290 ValueFactory<String> strings = valueFactories.getStringFactory();
291 ValueFactory<Long> longs = valueFactories.getLongFactory();
292 ProjectionParser projectionParser = ProjectionParser.getInstance();
293 NamespaceRegistry registry = executionContext.getNamespaceRegistry();
294
295 try {
296 // Read the configuration for the federated repository:
297 // Level 1: the node representing the federated repository
298 // Level 2: the "dna:workspaces" node
299 // Level 3: a node for each workspace in the federated repository
300 // Level 4: the "dna:projections" nodes
301 // Level 5: a node below "dna:projections" for each projection, with properties for the source name,
302 // workspace name, cache expiration time, and projection rules
303 Subgraph repositories = repositoryContext.getConfiguration(5);
304
305 // Get the name of the default workspace ...
306 String defaultWorkspaceName = null;
307 Property defaultWorkspaceNameProperty = repositories.getRoot().getProperty(DnaLexicon.DEFAULT_WORKSPACE_NAME);
308 if (defaultWorkspaceNameProperty != null) {
309 // Set the name using the property if there is one ...
310 defaultWorkspaceName = strings.create(defaultWorkspaceNameProperty.getFirstValue());
311 }
312
313 // Get the default expiration time for the repository ...
314 CachePolicy defaultCachePolicy = null;
315 Property timeToExpire = repositories.getRoot().getProperty(DnaLexicon.TIME_TO_EXPIRE);
316 if (timeToExpire != null && !timeToExpire.isEmpty()) {
317 long timeToCacheInMillis = longs.create(timeToExpire.getFirstValue());
318 defaultCachePolicy = new BasicCachePolicy(timeToCacheInMillis, TimeUnit.MILLISECONDS).getUnmodifiable();
319 }
320
321 // Level 2: The "dna:workspaces" node ...
322 Node workspacesNode = repositories.getNode(DnaLexicon.WORKSPACES);
323 if (workspacesNode == null) {
324 I18n msg = GraphI18n.requiredNodeDoesNotExistRelativeToNode;
325 throw new RepositorySourceException(msg.text(DnaLexicon.WORKSPACES.getString(registry),
326 repositories.getLocation().getPath().getString(registry),
327 repositories.getGraph().getCurrentWorkspaceName(),
328 repositories.getGraph().getSourceName()));
329 }
330
331 // Level 3: The workspace nodes ...
332 LinkedList<FederatedWorkspace> workspaces = new LinkedList<FederatedWorkspace>();
333 for (Location workspace : workspacesNode) {
334
335 // Get the name of the workspace ...
336 String workspaceName = null;
337 SubgraphNode workspaceNode = repositories.getNode(workspace);
338 Property workspaceNameProperty = workspaceNode.getProperty(DnaLexicon.WORKSPACE_NAME);
339 if (workspaceNameProperty != null) {
340 // Set the name using the property if there is one ...
341 workspaceName = strings.create(workspaceNameProperty.getFirstValue());
342 }
343 if (workspaceName == null) {
344 // Otherwise, set the name using the local name of the workspace node ...
345 workspaceName = workspace.getPath().getLastSegment().getName().getLocalName();
346 }
347
348 // Level 4: the "dna:projections" node ...
349 Node projectionsNode = workspaceNode.getNode(DnaLexicon.PROJECTIONS);
350 if (projectionsNode == null) {
351 I18n msg = GraphI18n.requiredNodeDoesNotExistRelativeToNode;
352 throw new RepositorySourceException(getName(), msg.text(DnaLexicon.PROJECTIONS.getString(registry),
353 workspaceNode.getLocation()
354 .getPath()
355 .getString(registry),
356 repositories.getGraph().getCurrentWorkspaceName(),
357 repositories.getGraph().getSourceName()));
358 }
359
360 // Level 5: the projection nodes ...
361 List<Projection> sourceProjections = new LinkedList<Projection>();
362 for (Location projection : projectionsNode) {
363 Node projectionNode = repositories.getNode(projection);
364 sourceProjections.add(createProjection(executionContext, projectionParser, projectionNode));
365 }
366
367 // Create the federated workspace configuration ...
368 FederatedWorkspace space = new FederatedWorkspace(repositoryContext, name, workspaceName, sourceProjections,
369 defaultCachePolicy);
370 if (workspaceName.equals(defaultWorkspaceName)) {
371 workspaces.addFirst(space);
372 } else {
373 workspaces.add(space);
374 }
375 }
376
377 // Create the ExecutorService ...
378 ExecutorService executor = Executors.newCachedThreadPool();
379
380 return new FederatedRepository(name, connectionFactory, workspaces, defaultCachePolicy, executor);
381 } catch (RepositorySourceException t) {
382 throw t; // rethrow
383 } catch (Throwable t) {
384 I18n msg = GraphI18n.errorReadingConfigurationForFederatedRepositorySource;
385 throw new RepositorySourceException(getName(), msg.text(name), t);
386 }
387 }
388
389 /**
390 * Instantiate the {@link Projection} described by the supplied properties.
391 *
392 * @param context the execution context that should be used to read the configuration; may not be null
393 * @param projectionParser the projection rule parser that should be used; may not be null
394 * @param node the node where these properties were found; never null
395 * @return the region instance, or null if it could not be created
396 */
397 protected Projection createProjection( ExecutionContext context,
398 ProjectionParser projectionParser,
399 Node node ) {
400 ValueFactory<String> strings = context.getValueFactories().getStringFactory();
401
402 Path path = node.getLocation().getPath();
403
404 // Get the source name from the local name of the node ...
405 String sourceName = path.getLastSegment().getName().getLocalName();
406 Property sourceNameProperty = node.getProperty(DnaLexicon.SOURCE_NAME);
407 if (sourceNameProperty != null && !sourceNameProperty.isEmpty()) {
408 // There is a "dna:sourceName" property, so use this instead ...
409 sourceName = strings.create(sourceNameProperty.getFirstValue());
410 }
411 assert sourceName != null;
412
413 // Get the workspace name ...
414 String workspaceName = null;
415 Property workspaceNameProperty = node.getProperty(DnaLexicon.WORKSPACE_NAME);
416 if (workspaceNameProperty != null && !workspaceNameProperty.isEmpty()) {
417 // There is a "dna:workspaceName" property, so use this instead ...
418 workspaceName = strings.create(workspaceNameProperty.getFirstValue());
419 }
420
421 // Get the projection rules ...
422 Projection.Rule[] projectionRules = null;
423 Property projectionRulesProperty = node.getProperty(DnaLexicon.PROJECTION_RULES);
424 if (projectionRulesProperty != null && !projectionRulesProperty.isEmpty()) {
425 String[] projectionRuleStrs = strings.create(projectionRulesProperty.getValuesAsArray());
426 if (projectionRuleStrs != null && projectionRuleStrs.length != 0) {
427 projectionRules = projectionParser.rulesFromStrings(context, projectionRuleStrs);
428 }
429 }
430
431 // Is this projection read-only?
432 boolean readOnly = false;
433 Property readOnlyProperty = node.getProperty(DnaLexicon.READ_ONLY);
434 if (readOnlyProperty != null && !readOnlyProperty.isEmpty()) {
435 readOnly = context.getValueFactories().getBooleanFactory().create(readOnlyProperty.getFirstValue());
436 }
437
438 return new Projection(sourceName, workspaceName, readOnly, projectionRules);
439 }
440
441 }