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