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 }