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.repository; 025 026 import java.lang.reflect.InvocationTargetException; 027 import java.lang.reflect.Method; 028 import java.util.Map; 029 import java.util.concurrent.TimeUnit; 030 import java.util.concurrent.atomic.AtomicBoolean; 031 import net.jcip.annotations.ThreadSafe; 032 import org.jboss.dna.common.collection.Problems; 033 import org.jboss.dna.common.collection.SimpleProblems; 034 import org.jboss.dna.common.util.CheckArg; 035 import org.jboss.dna.common.util.Logger; 036 import org.jboss.dna.common.util.Reflection; 037 import org.jboss.dna.connector.federation.FederationException; 038 import org.jboss.dna.graph.ExecutionContext; 039 import org.jboss.dna.graph.Graph; 040 import org.jboss.dna.graph.JcrLexicon; 041 import org.jboss.dna.graph.Location; 042 import org.jboss.dna.graph.Node; 043 import org.jboss.dna.graph.Subgraph; 044 import org.jboss.dna.graph.connector.RepositorySource; 045 import org.jboss.dna.graph.property.Name; 046 import org.jboss.dna.graph.property.Path; 047 import org.jboss.dna.graph.property.PathNotFoundException; 048 import org.jboss.dna.graph.property.Property; 049 import org.jboss.dna.graph.property.PropertyType; 050 import org.jboss.dna.graph.property.ValueFactories; 051 import org.jboss.dna.graph.property.ValueFactory; 052 import org.jboss.dna.repository.service.AbstractServiceAdministrator; 053 import org.jboss.dna.repository.service.AdministeredService; 054 import org.jboss.dna.repository.service.ServiceAdministrator; 055 056 /** 057 * @author Randall Hauch 058 */ 059 @ThreadSafe 060 public class RepositoryService implements AdministeredService { 061 062 /** 063 * The administrative component for this service. 064 * 065 * @author Randall Hauch 066 */ 067 protected class Administrator extends AbstractServiceAdministrator { 068 069 protected Administrator() { 070 super(RepositoryI18n.federationServiceName, State.PAUSED); 071 } 072 073 /** 074 * {@inheritDoc} 075 */ 076 @Override 077 protected boolean doCheckIsTerminated() { 078 return true; 079 } 080 081 /** 082 * {@inheritDoc} 083 */ 084 @Override 085 protected void doStart( State fromState ) { 086 super.doStart(fromState); 087 startService(); 088 } 089 090 /** 091 * {@inheritDoc} 092 * 093 * @see org.jboss.dna.repository.service.ServiceAdministrator#awaitTermination(long, java.util.concurrent.TimeUnit) 094 */ 095 public boolean awaitTermination( long timeout, 096 TimeUnit unit ) { 097 return true; 098 } 099 } 100 101 private final ExecutionContext context; 102 private final RepositoryLibrary sources; 103 private final String configurationSourceName; 104 private final String configurationWorkspaceName; 105 private final Path pathToConfigurationRoot; 106 private final Administrator administrator = new Administrator(); 107 private final AtomicBoolean started = new AtomicBoolean(false); 108 109 /** 110 * Create a service instance, reading the configuration describing new {@link RepositorySource} instances from the source with 111 * the supplied name. 112 * 113 * @param sources the source manager 114 * @param configurationSourceName the name of the {@link RepositorySource} that is the configuration repository 115 * @param configurationWorkspaceName the name of the workspace in the {@link RepositorySource} that is the configuration 116 * repository 117 * @param context the execution context in which this service should run 118 * @throws IllegalArgumentException if the bootstrap source is null or the execution context is null 119 */ 120 public RepositoryService( RepositoryLibrary sources, 121 String configurationSourceName, 122 String configurationWorkspaceName, 123 ExecutionContext context ) { 124 this(sources, configurationSourceName, configurationWorkspaceName, null, context); 125 } 126 127 /** 128 * Create a service instance, reading the configuration describing new {@link RepositorySource} instances from the source with 129 * the supplied name and path within the repository. 130 * 131 * @param sources the source manager 132 * @param configurationSourceName the name of the {@link RepositorySource} that is the configuration repository 133 * @param configurationWorkspaceName the name of the workspace in the {@link RepositorySource} that is the configuration 134 * repository, or null if the default workspace of the source should be used (if there is one) 135 * @param pathToConfigurationRoot the path of the node in the configuration source repository that should be treated by this 136 * service as the root of the service's configuration; if null, then "/dna:system" is used 137 * @param context the execution context in which this service should run 138 * @throws IllegalArgumentException if the bootstrap source is null or the execution context is null 139 */ 140 public RepositoryService( RepositoryLibrary sources, 141 String configurationSourceName, 142 String configurationWorkspaceName, 143 Path pathToConfigurationRoot, 144 ExecutionContext context ) { 145 CheckArg.isNotNull(configurationSourceName, "configurationSourceName"); 146 CheckArg.isNotNull(sources, "sources"); 147 CheckArg.isNotNull(context, "context"); 148 if (pathToConfigurationRoot == null) pathToConfigurationRoot = context.getValueFactories() 149 .getPathFactory() 150 .create("/dna:system"); 151 this.sources = sources; 152 this.pathToConfigurationRoot = pathToConfigurationRoot; 153 this.configurationSourceName = configurationSourceName; 154 this.configurationWorkspaceName = configurationWorkspaceName; 155 this.context = context; 156 } 157 158 /** 159 * {@inheritDoc} 160 */ 161 public ServiceAdministrator getAdministrator() { 162 return this.administrator; 163 } 164 165 /** 166 * @return configurationSourceName 167 */ 168 public String getConfigurationSourceName() { 169 return configurationSourceName; 170 } 171 172 /** 173 * @return configurationWorkspaceName 174 */ 175 public String getConfigurationWorkspaceName() { 176 return configurationWorkspaceName; 177 } 178 179 /** 180 * @return sources 181 */ 182 public RepositoryLibrary getRepositorySourceManager() { 183 return sources; 184 } 185 186 /** 187 * @return env 188 */ 189 public ExecutionContext getExecutionEnvironment() { 190 return context; 191 } 192 193 public String getJndiName() { 194 // TODO 195 return null; 196 } 197 198 protected synchronized void startService() { 199 if (this.started.get() == false) { 200 Problems problems = new SimpleProblems(); 201 202 // ------------------------------------------------------------------------------------ 203 // Read the configuration ... 204 // ------------------------------------------------------------------------------------ 205 206 // Read the configuration and repository source nodes (children under "/dna:sources") ... 207 Graph graph = Graph.create(getConfigurationSourceName(), sources, context); 208 Path pathToSourcesNode = context.getValueFactories().getPathFactory().create(pathToConfigurationRoot, 209 DnaLexicon.SOURCES); 210 try { 211 String workspaceName = getConfigurationWorkspaceName(); 212 if (workspaceName != null) graph.useWorkspace(workspaceName); 213 214 Subgraph sourcesGraph = graph.getSubgraphOfDepth(3).at(pathToSourcesNode); 215 216 // Iterate over each of the children, and create the RepositorySource ... 217 for (Location location : sourcesGraph.getRoot().getChildren()) { 218 Node sourceNode = sourcesGraph.getNode(location); 219 sources.addSource(createRepositorySource(location.getPath(), sourceNode.getPropertiesByName(), problems)); 220 } 221 } catch (PathNotFoundException e) { 222 // No sources were found, and this is okay! 223 } catch (Throwable err) { 224 throw new FederationException(RepositoryI18n.errorStartingRepositoryService.text(), err); 225 } 226 this.started.set(true); 227 } 228 } 229 230 /** 231 * Instantiate the {@link RepositorySource} described by the supplied properties. 232 * 233 * @param path the path to the node where these properties were found; never null 234 * @param properties the properties; never null 235 * @param problems the problems container in which any problems should be reported; never null 236 * @return the repository source instance, or null if it could not be created 237 */ 238 @SuppressWarnings( "null" ) 239 protected RepositorySource createRepositorySource( Path path, 240 Map<Name, Property> properties, 241 Problems problems ) { 242 ValueFactories valueFactories = context.getValueFactories(); 243 ValueFactory<String> stringFactory = valueFactories.getStringFactory(); 244 245 // Get the classname and classpath ... 246 Property classnameProperty = properties.get(DnaLexicon.CLASSNAME); 247 Property classpathProperty = properties.get(DnaLexicon.CLASSPATH); 248 if (classnameProperty == null) { 249 problems.addError(RepositoryI18n.requiredPropertyIsMissingFromNode, DnaLexicon.CLASSNAME, path); 250 } 251 // If the classpath property is null or empty, the default classpath will be used 252 if (problems.hasErrors()) return null; 253 254 // Create the instance ... 255 String classname = stringFactory.create(classnameProperty.getValues().next()); 256 String[] classpath = classpathProperty == null ? new String[] {} : stringFactory.create(classpathProperty.getValuesAsArray()); 257 ClassLoader classLoader = context.getClassLoader(classpath); 258 RepositorySource source = null; 259 try { 260 Class<?> sourceClass = classLoader.loadClass(classname); 261 source = (RepositorySource)sourceClass.newInstance(); 262 } catch (ClassNotFoundException err) { 263 problems.addError(err, RepositoryI18n.unableToLoadClassUsingClasspath, classname, classpath); 264 } catch (IllegalAccessException err) { 265 problems.addError(err, RepositoryI18n.unableToAccessClassUsingClasspath, classname, classpath); 266 } catch (Throwable err) { 267 problems.addError(err, RepositoryI18n.unableToInstantiateClassUsingClasspath, classname, classpath); 268 } 269 270 // We need to set the name using the local name of the node... 271 Property nameProperty = context.getPropertyFactory().create(JcrLexicon.NAME, 272 path.getLastSegment().getName().getLocalName()); 273 properties.put(JcrLexicon.NAME, nameProperty); 274 275 // Now set all the properties that we can, ignoring any property that doesn't fit the pattern ... 276 Reflection reflection = new Reflection(source.getClass()); 277 for (Map.Entry<Name, Property> entry : properties.entrySet()) { 278 Name propertyName = entry.getKey(); 279 Property property = entry.getValue(); 280 String javaPropertyName = propertyName.getLocalName(); 281 if (property.isEmpty()) continue; 282 283 Object value = null; 284 Method setter = null; 285 try { 286 setter = reflection.findFirstMethod("set" + javaPropertyName, false); 287 if (setter == null) continue; 288 // Determine the type of the one parameter ... 289 Class<?>[] parameterTypes = setter.getParameterTypes(); 290 if (parameterTypes.length != 1) continue; // not a valid JavaBean property 291 Class<?> paramType = parameterTypes[0]; 292 PropertyType allowedType = PropertyType.discoverType(paramType); 293 if (allowedType == null) continue; // assume not a JavaBean property with usable type 294 ValueFactory<?> factory = context.getValueFactories().getValueFactory(allowedType); 295 if (paramType.isArray()) { 296 if (paramType.getComponentType().isArray()) continue; // array of array, which we don't do 297 Object[] values = factory.create(property.getValuesAsArray()); 298 // Convert to an array of primitives if that's what the signature requires ... 299 Class<?> componentType = paramType.getComponentType(); 300 if (Integer.TYPE.equals(componentType)) { 301 int[] primitiveValues = new int[values.length]; 302 for (int i = 0; i != values.length; ++i) { 303 primitiveValues[i] = ((Long)values[i]).intValue(); 304 } 305 value = primitiveValues; 306 } else if (Short.TYPE.equals(componentType)) { 307 short[] primitiveValues = new short[values.length]; 308 for (int i = 0; i != values.length; ++i) { 309 primitiveValues[i] = ((Long)values[i]).shortValue(); 310 } 311 value = primitiveValues; 312 } else if (Long.TYPE.equals(componentType)) { 313 long[] primitiveValues = new long[values.length]; 314 for (int i = 0; i != values.length; ++i) { 315 primitiveValues[i] = ((Long)values[i]).longValue(); 316 } 317 value = primitiveValues; 318 } else if (Double.TYPE.equals(componentType)) { 319 double[] primitiveValues = new double[values.length]; 320 for (int i = 0; i != values.length; ++i) { 321 primitiveValues[i] = ((Double)values[i]).doubleValue(); 322 } 323 value = primitiveValues; 324 } else if (Float.TYPE.equals(componentType)) { 325 float[] primitiveValues = new float[values.length]; 326 for (int i = 0; i != values.length; ++i) { 327 primitiveValues[i] = ((Double)values[i]).floatValue(); 328 } 329 value = primitiveValues; 330 } else if (Boolean.TYPE.equals(componentType)) { 331 boolean[] primitiveValues = new boolean[values.length]; 332 for (int i = 0; i != values.length; ++i) { 333 primitiveValues[i] = ((Boolean)values[i]).booleanValue(); 334 } 335 value = primitiveValues; 336 } else { 337 value = values; 338 } 339 } else { 340 value = factory.create(property.getFirstValue()); 341 // Convert to the correct primitive, if needed ... 342 if (Integer.TYPE.equals(paramType)) { 343 value = new Integer(((Long)value).intValue()); 344 } else if (Short.TYPE.equals(paramType)) { 345 value = new Short(((Long)value).shortValue()); 346 } else if (Float.TYPE.equals(paramType)) { 347 value = new Float(((Double)value).floatValue()); 348 } 349 } 350 // Invoke the method ... 351 String msg = "Setting property {0} to {1} on source at {2} in configuration repository {3} in workspace {4}"; 352 Logger.getLogger(getClass()).trace(msg, 353 javaPropertyName, 354 value, 355 path, 356 configurationSourceName, 357 configurationWorkspaceName); 358 setter.invoke(source, value); 359 } catch (SecurityException err) { 360 Logger.getLogger(getClass()).debug(err, "Error invoking {0}.{1}", source.getClass(), setter); 361 } catch (IllegalArgumentException err) { 362 // Do nothing ... assume not a JavaBean property (but log) 363 String msg = "Invalid argument invoking {0} with parameter {1} on source at {2} in configuration repository {3} in workspace {4}"; 364 Logger.getLogger(getClass()).debug(err, 365 msg, 366 setter, 367 value, 368 path, 369 configurationSourceName, 370 configurationWorkspaceName); 371 } catch (IllegalAccessException err) { 372 Logger.getLogger(getClass()).debug(err, "Error invoking {0}.{1}", source.getClass(), setter); 373 } catch (InvocationTargetException err) { 374 // Do nothing ... assume not a JavaBean property (but log) 375 String msg = "Error invoking {0} with parameter {1} on source at {2} in configuration repository {3} in workspace {4}"; 376 Logger.getLogger(getClass()).debug(err.getTargetException(), 377 msg, 378 setter, 379 value, 380 path, 381 configurationSourceName, 382 configurationWorkspaceName); 383 } 384 } 385 return source; 386 } 387 388 /** 389 * {@inheritDoc} 390 */ 391 @Override 392 public boolean equals( Object obj ) { 393 if (obj == this) return true; 394 return false; 395 } 396 }