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 }