001 /*
002 * JBoss, Home of Professional Open Source.
003 * Copyright 2008, Red Hat Middleware LLC, and individual contributors
004 * as indicated by the @author tags. See the copyright.txt file in the
005 * distribution for a full listing of individual contributors.
006 *
007 * This is free software; you can redistribute it and/or modify it
008 * under the terms of the GNU Lesser General Public License as
009 * published by the Free Software Foundation; either version 2.1 of
010 * the License, or (at your option) any later version.
011 *
012 * This software is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * You should have received a copy of the GNU Lesser General Public
018 * License along with this software; if not, write to the Free
019 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
021 */
022 package org.jboss.dna.repository;
023
024 import java.lang.reflect.InvocationTargetException;
025 import java.util.List;
026 import java.util.Map;
027 import java.util.concurrent.TimeUnit;
028 import java.util.concurrent.atomic.AtomicBoolean;
029 import net.jcip.annotations.ThreadSafe;
030 import org.jboss.dna.common.collection.Problems;
031 import org.jboss.dna.common.collection.SimpleProblems;
032 import org.jboss.dna.common.util.CheckArg;
033 import org.jboss.dna.common.util.Reflection;
034 import org.jboss.dna.connector.federation.FederationException;
035 import org.jboss.dna.graph.DnaLexicon;
036 import org.jboss.dna.graph.ExecutionContext;
037 import org.jboss.dna.graph.commands.GraphCommand;
038 import org.jboss.dna.graph.commands.basic.BasicCompositeCommand;
039 import org.jboss.dna.graph.commands.basic.BasicGetChildrenCommand;
040 import org.jboss.dna.graph.commands.basic.BasicGetNodeCommand;
041 import org.jboss.dna.graph.commands.executor.CommandExecutor;
042 import org.jboss.dna.graph.commands.executor.SingleSourceCommandExecutor;
043 import org.jboss.dna.graph.connectors.RepositorySource;
044 import org.jboss.dna.graph.properties.Name;
045 import org.jboss.dna.graph.properties.NameFactory;
046 import org.jboss.dna.graph.properties.Path;
047 import org.jboss.dna.graph.properties.PathFactory;
048 import org.jboss.dna.graph.properties.Property;
049 import org.jboss.dna.graph.properties.ValueFactories;
050 import org.jboss.dna.graph.properties.ValueFactory;
051 import org.jboss.dna.repository.services.AbstractServiceAdministrator;
052 import org.jboss.dna.repository.services.AdministeredService;
053 import org.jboss.dna.repository.services.ServiceAdministrator;
054
055 /**
056 * @author Randall Hauch
057 */
058 @ThreadSafe
059 public class RepositoryService implements AdministeredService {
060
061 /**
062 * The administrative component for this service.
063 *
064 * @author Randall Hauch
065 */
066 protected class Administrator extends AbstractServiceAdministrator {
067
068 protected Administrator() {
069 super(RepositoryI18n.federationServiceName, State.PAUSED);
070 }
071
072 /**
073 * {@inheritDoc}
074 */
075 @Override
076 protected boolean doCheckIsTerminated() {
077 return true;
078 }
079
080 /**
081 * {@inheritDoc}
082 */
083 @Override
084 protected void doStart( State fromState ) {
085 super.doStart(fromState);
086 startService();
087 }
088
089 /**
090 * {@inheritDoc}
091 *
092 * @see org.jboss.dna.repository.services.ServiceAdministrator#awaitTermination(long, java.util.concurrent.TimeUnit)
093 */
094 public boolean awaitTermination( long timeout,
095 TimeUnit unit ) {
096 return true;
097 }
098 }
099
100 private final ExecutionContext context;
101 private final RepositoryLibrary sources;
102 private final String configurationSourceName;
103 private final Path pathToConfigurationRoot;
104 private final Administrator administrator = new Administrator();
105 private final AtomicBoolean started = new AtomicBoolean(false);
106
107 /**
108 * Create a service instance, reading the configuration describing new {@link RepositorySource} instances from the source with
109 * the supplied name.
110 *
111 * @param sources the source manager
112 * @param configurationSourceName the name of the {@link RepositorySource} that is the configuration repository
113 * @param context the execution context in which this service should run
114 * @throws IllegalArgumentException if the bootstrap source is null or the execution context is null
115 */
116 public RepositoryService( RepositoryLibrary sources,
117 String configurationSourceName,
118 ExecutionContext context ) {
119 this(sources, configurationSourceName, null, context);
120 }
121
122 /**
123 * Create a service instance, reading the configuration describing new {@link RepositorySource} instances from the source with
124 * the supplied name and path within the repository.
125 *
126 * @param sources the source manager
127 * @param configurationSourceName the name of the {@link RepositorySource} that is the configuration repository
128 * @param pathToConfigurationRoot the path of the node in the configuration source repository that should be treated by this
129 * service as the root of the service's configuration; if null, then "/dna:system" is used
130 * @param context the execution context in which this service should run
131 * @throws IllegalArgumentException if the bootstrap source is null or the execution context is null
132 */
133 public RepositoryService( RepositoryLibrary sources,
134 String configurationSourceName,
135 Path pathToConfigurationRoot,
136 ExecutionContext context ) {
137 CheckArg.isNotNull(configurationSourceName, "configurationSourceName");
138 CheckArg.isNotNull(sources, "sources");
139 CheckArg.isNotNull(context, "context");
140 if (pathToConfigurationRoot == null) pathToConfigurationRoot = context.getValueFactories().getPathFactory().create("/dna:system");
141 this.sources = sources;
142 this.pathToConfigurationRoot = pathToConfigurationRoot;
143 this.configurationSourceName = configurationSourceName;
144 this.context = context;
145 }
146
147 /**
148 * {@inheritDoc}
149 */
150 public ServiceAdministrator getAdministrator() {
151 return this.administrator;
152 }
153
154 /**
155 * @return configurationSourceName
156 */
157 public String getConfigurationSourceName() {
158 return configurationSourceName;
159 }
160
161 /**
162 * @return sources
163 */
164 public RepositoryLibrary getRepositorySourceManager() {
165 return sources;
166 }
167
168 /**
169 * @return env
170 */
171 public ExecutionContext getExecutionEnvironment() {
172 return context;
173 }
174
175 public String getJndiName() {
176 // TODO
177 return null;
178 }
179
180 protected synchronized void startService() {
181 if (this.started.get() == false) {
182 Problems problems = new SimpleProblems();
183
184 // ------------------------------------------------------------------------------------
185 // Read the configuration ...
186 // ------------------------------------------------------------------------------------
187 ValueFactories valueFactories = context.getValueFactories();
188 PathFactory pathFactory = valueFactories.getPathFactory();
189 NameFactory nameFactory = valueFactories.getNameFactory();
190
191 // Create a command executor to execute the commands.
192 CommandExecutor executor = new SingleSourceCommandExecutor(context, configurationSourceName, sources);
193
194 // Read the configuration and the repository sources, located as child nodes/branches under "/dna:sources",
195 // and then instantiate and register each in the "sources" manager
196 Path configurationRoot = this.pathToConfigurationRoot;
197 try {
198 Path sourcesNode = pathFactory.create(configurationRoot, nameFactory.create("dna:sources"));
199 BasicGetChildrenCommand getSources = new BasicGetChildrenCommand(sourcesNode);
200 executor.execute(getSources);
201 if (getSources.hasNoError()) {
202
203 // Build the commands to get each of the children ...
204 List<Path.Segment> children = getSources.getChildren();
205 if (!children.isEmpty()) {
206 BasicCompositeCommand commands = new BasicCompositeCommand();
207 for (Path.Segment child : getSources.getChildren()) {
208 final Path pathToSource = pathFactory.create(sourcesNode, child);
209 commands.add(new BasicGetNodeCommand(pathToSource));
210 }
211 executor.execute(commands);
212
213 // Iterate over each source node obtained ...
214 for (GraphCommand command : commands) {
215 BasicGetNodeCommand getSourceCommand = (BasicGetNodeCommand)command;
216 if (getSourceCommand.hasNoError()) {
217 RepositorySource source = createRepositorySource(getSourceCommand.getPath(),
218 getSourceCommand.getPropertiesByName(),
219 problems);
220 if (source != null) sources.addSource(source);
221 }
222 }
223 }
224 }
225 } catch (Throwable err) {
226 throw new FederationException(RepositoryI18n.errorStartingRepositoryService.text());
227 } finally {
228 executor.close();
229 }
230 this.started.set(true);
231 }
232 }
233
234 /**
235 * Instantiate the {@link RepositorySource} described by the supplied properties.
236 *
237 * @param path the path to the node where these properties were found; never null
238 * @param properties the properties; never null
239 * @param problems the problems container in which any problems should be reported; never null
240 * @return the repository source instance, or null if it could not be created
241 */
242 @SuppressWarnings( "null" )
243 protected RepositorySource createRepositorySource( Path path,
244 Map<Name, Property> properties,
245 Problems problems ) {
246 ValueFactories valueFactories = context.getValueFactories();
247 ValueFactory<String> stringFactory = valueFactories.getStringFactory();
248
249 // Get the classname and classpath ...
250 Property classnameProperty = properties.get(DnaLexicon.CLASSNAME);
251 Property classpathProperty = properties.get(DnaLexicon.CLASSPATH);
252 if (classnameProperty == null) {
253 problems.addError(RepositoryI18n.requiredPropertyIsMissingFromNode, DnaLexicon.CLASSNAME, path);
254 }
255 // If the classpath property is null or empty, the default classpath will be used
256 if (problems.hasErrors()) return null;
257
258 // Create the instance ...
259 String classname = stringFactory.create(classnameProperty.getValues().next());
260 String[] classpath = classpathProperty == null ? new String[] {} : stringFactory.create(classpathProperty.getValuesAsArray());
261 ClassLoader classLoader = context.getClassLoader(classpath);
262 RepositorySource source = null;
263 try {
264 Class<?> sourceClass = classLoader.loadClass(classname);
265 source = (RepositorySource)sourceClass.newInstance();
266 } catch (ClassNotFoundException err) {
267 problems.addError(err, RepositoryI18n.unableToLoadClassUsingClasspath, classname, classpath);
268 } catch (IllegalAccessException err) {
269 problems.addError(err, RepositoryI18n.unableToAccessClassUsingClasspath, classname, classpath);
270 } catch (Throwable err) {
271 problems.addError(err, RepositoryI18n.unableToInstantiateClassUsingClasspath, classname, classpath);
272 }
273
274 // Try to set the name property to the local name of the node...
275 Reflection reflection = new Reflection(source.getClass());
276 try {
277 reflection.invokeSetterMethodOnTarget("name", source, path.getLastSegment().getName().getLocalName());
278 } catch (SecurityException err) {
279 // Do nothing ... assume not a JavaBean property
280 } catch (NoSuchMethodException err) {
281 // Do nothing ... assume not a JavaBean property
282 } catch (IllegalArgumentException err) {
283 // Do nothing ... assume not a JavaBean property
284 } catch (IllegalAccessException err) {
285 // Do nothing ... assume not a JavaBean property
286 } catch (InvocationTargetException err) {
287 // Do nothing ... assume not a JavaBean property
288 }
289
290 // Now set all the properties that we can, ignoring any property that doesn't fit pattern ...
291 for (Map.Entry<Name, Property> entry : properties.entrySet()) {
292 Name propertyName = entry.getKey();
293 Property property = entry.getValue();
294 String javaPropertyName = propertyName.getLocalName();
295 if (property.isEmpty()) continue;
296 Object value = null;
297 if (property.isSingle()) {
298 value = property.getValues().next();
299 } else if (property.isMultiple()) {
300 value = property.getValuesAsArray();
301 }
302 try {
303 reflection.invokeSetterMethodOnTarget(javaPropertyName, source, value);
304 } catch (SecurityException err) {
305 // Do nothing ... assume not a JavaBean property
306 } catch (NoSuchMethodException err) {
307 // Do nothing ... assume not a JavaBean property
308 } catch (IllegalArgumentException err) {
309 // Do nothing ... assume not a JavaBean property
310 } catch (IllegalAccessException err) {
311 // Do nothing ... assume not a JavaBean property
312 } catch (InvocationTargetException err) {
313 // Do nothing ... assume not a JavaBean property
314 }
315 }
316 return source;
317 }
318
319 /**
320 * {@inheritDoc}
321 */
322 @Override
323 public boolean equals( Object obj ) {
324 if (obj == this) return true;
325 return false;
326 }
327 }