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 }