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    }