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.Map;
026    import java.util.concurrent.TimeUnit;
027    import java.util.concurrent.atomic.AtomicBoolean;
028    import net.jcip.annotations.ThreadSafe;
029    import org.jboss.dna.common.collection.Problems;
030    import org.jboss.dna.common.collection.SimpleProblems;
031    import org.jboss.dna.common.util.CheckArg;
032    import org.jboss.dna.common.util.Reflection;
033    import org.jboss.dna.connector.federation.FederationException;
034    import org.jboss.dna.graph.DnaLexicon;
035    import org.jboss.dna.graph.ExecutionContext;
036    import org.jboss.dna.graph.Graph;
037    import org.jboss.dna.graph.Location;
038    import org.jboss.dna.graph.Node;
039    import org.jboss.dna.graph.Subgraph;
040    import org.jboss.dna.graph.connectors.RepositorySource;
041    import org.jboss.dna.graph.properties.Name;
042    import org.jboss.dna.graph.properties.Path;
043    import org.jboss.dna.graph.properties.PathNotFoundException;
044    import org.jboss.dna.graph.properties.Property;
045    import org.jboss.dna.graph.properties.ValueFactories;
046    import org.jboss.dna.graph.properties.ValueFactory;
047    import org.jboss.dna.repository.services.AbstractServiceAdministrator;
048    import org.jboss.dna.repository.services.AdministeredService;
049    import org.jboss.dna.repository.services.ServiceAdministrator;
050    
051    /**
052     * @author Randall Hauch
053     */
054    @ThreadSafe
055    public class RepositoryService implements AdministeredService {
056    
057        /**
058         * The administrative component for this service.
059         * 
060         * @author Randall Hauch
061         */
062        protected class Administrator extends AbstractServiceAdministrator {
063    
064            protected Administrator() {
065                super(RepositoryI18n.federationServiceName, State.PAUSED);
066            }
067    
068            /**
069             * {@inheritDoc}
070             */
071            @Override
072            protected boolean doCheckIsTerminated() {
073                return true;
074            }
075    
076            /**
077             * {@inheritDoc}
078             */
079            @Override
080            protected void doStart( State fromState ) {
081                super.doStart(fromState);
082                startService();
083            }
084    
085            /**
086             * {@inheritDoc}
087             * 
088             * @see org.jboss.dna.repository.services.ServiceAdministrator#awaitTermination(long, java.util.concurrent.TimeUnit)
089             */
090            public boolean awaitTermination( long timeout,
091                                             TimeUnit unit ) {
092                return true;
093            }
094        }
095    
096        private final ExecutionContext context;
097        private final RepositoryLibrary sources;
098        private final String configurationSourceName;
099        private final Path pathToConfigurationRoot;
100        private final Administrator administrator = new Administrator();
101        private final AtomicBoolean started = new AtomicBoolean(false);
102    
103        /**
104         * Create a service instance, reading the configuration describing new {@link RepositorySource} instances from the source with
105         * the supplied name.
106         * 
107         * @param sources the source manager
108         * @param configurationSourceName the name of the {@link RepositorySource} that is the configuration repository
109         * @param context the execution context in which this service should run
110         * @throws IllegalArgumentException if the bootstrap source is null or the execution context is null
111         */
112        public RepositoryService( RepositoryLibrary sources,
113                                  String configurationSourceName,
114                                  ExecutionContext context ) {
115            this(sources, configurationSourceName, null, context);
116        }
117    
118        /**
119         * Create a service instance, reading the configuration describing new {@link RepositorySource} instances from the source with
120         * the supplied name and path within the repository.
121         * 
122         * @param sources the source manager
123         * @param configurationSourceName the name of the {@link RepositorySource} that is the configuration repository
124         * @param pathToConfigurationRoot the path of the node in the configuration source repository that should be treated by this
125         *        service as the root of the service's configuration; if null, then "/dna:system" is used
126         * @param context the execution context in which this service should run
127         * @throws IllegalArgumentException if the bootstrap source is null or the execution context is null
128         */
129        public RepositoryService( RepositoryLibrary sources,
130                                  String configurationSourceName,
131                                  Path pathToConfigurationRoot,
132                                  ExecutionContext context ) {
133            CheckArg.isNotNull(configurationSourceName, "configurationSourceName");
134            CheckArg.isNotNull(sources, "sources");
135            CheckArg.isNotNull(context, "context");
136            if (pathToConfigurationRoot == null) pathToConfigurationRoot = context.getValueFactories().getPathFactory().create("/dna:system");
137            this.sources = sources;
138            this.pathToConfigurationRoot = pathToConfigurationRoot;
139            this.configurationSourceName = configurationSourceName;
140            this.context = context;
141        }
142    
143        /**
144         * {@inheritDoc}
145         */
146        public ServiceAdministrator getAdministrator() {
147            return this.administrator;
148        }
149    
150        /**
151         * @return configurationSourceName
152         */
153        public String getConfigurationSourceName() {
154            return configurationSourceName;
155        }
156    
157        /**
158         * @return sources
159         */
160        public RepositoryLibrary getRepositorySourceManager() {
161            return sources;
162        }
163    
164        /**
165         * @return env
166         */
167        public ExecutionContext getExecutionEnvironment() {
168            return context;
169        }
170    
171        public String getJndiName() {
172            // TODO
173            return null;
174        }
175    
176        protected synchronized void startService() {
177            if (this.started.get() == false) {
178                Problems problems = new SimpleProblems();
179    
180                // ------------------------------------------------------------------------------------
181                // Read the configuration ...
182                // ------------------------------------------------------------------------------------
183    
184                // Read the configuration and repository source nodes (children under "/dna:sources") ...
185                Graph graph = Graph.create(getConfigurationSourceName(), sources, context);
186                Path pathToSourcesNode = context.getValueFactories().getPathFactory().create(pathToConfigurationRoot, "dna:sources");
187                try {
188                    Subgraph sourcesGraph = graph.getSubgraphOfDepth(3).at(pathToSourcesNode);
189    
190                    // Iterate over each of the children, and create the RepositorySource ...
191                    for (Location location : sourcesGraph.getRoot().getChildren()) {
192                        Node sourceNode = sourcesGraph.getNode(location);
193                        sources.addSource(createRepositorySource(location.getPath(), sourceNode.getPropertiesByName(), problems));
194                    }
195                } catch (PathNotFoundException e) {
196                    // No sources were found, and this is okay!
197                } catch (Throwable err) {
198                    throw new FederationException(RepositoryI18n.errorStartingRepositoryService.text(), err);
199                }
200                this.started.set(true);
201            }
202        }
203    
204        /**
205         * Instantiate the {@link RepositorySource} described by the supplied properties.
206         * 
207         * @param path the path to the node where these properties were found; never null
208         * @param properties the properties; never null
209         * @param problems the problems container in which any problems should be reported; never null
210         * @return the repository source instance, or null if it could not be created
211         */
212        @SuppressWarnings( "null" )
213        protected RepositorySource createRepositorySource( Path path,
214                                                           Map<Name, Property> properties,
215                                                           Problems problems ) {
216            ValueFactories valueFactories = context.getValueFactories();
217            ValueFactory<String> stringFactory = valueFactories.getStringFactory();
218    
219            // Get the classname and classpath ...
220            Property classnameProperty = properties.get(DnaLexicon.CLASSNAME);
221            Property classpathProperty = properties.get(DnaLexicon.CLASSPATH);
222            if (classnameProperty == null) {
223                problems.addError(RepositoryI18n.requiredPropertyIsMissingFromNode, DnaLexicon.CLASSNAME, path);
224            }
225            // If the classpath property is null or empty, the default classpath will be used
226            if (problems.hasErrors()) return null;
227    
228            // Create the instance ...
229            String classname = stringFactory.create(classnameProperty.getValues().next());
230            String[] classpath = classpathProperty == null ? new String[] {} : stringFactory.create(classpathProperty.getValuesAsArray());
231            ClassLoader classLoader = context.getClassLoader(classpath);
232            RepositorySource source = null;
233            try {
234                Class<?> sourceClass = classLoader.loadClass(classname);
235                source = (RepositorySource)sourceClass.newInstance();
236            } catch (ClassNotFoundException err) {
237                problems.addError(err, RepositoryI18n.unableToLoadClassUsingClasspath, classname, classpath);
238            } catch (IllegalAccessException err) {
239                problems.addError(err, RepositoryI18n.unableToAccessClassUsingClasspath, classname, classpath);
240            } catch (Throwable err) {
241                problems.addError(err, RepositoryI18n.unableToInstantiateClassUsingClasspath, classname, classpath);
242            }
243    
244            // Try to set the name property to the local name of the node...
245            Reflection reflection = new Reflection(source.getClass());
246            try {
247                reflection.invokeSetterMethodOnTarget("name", source, path.getLastSegment().getName().getLocalName());
248            } catch (SecurityException err) {
249                // Do nothing ... assume not a JavaBean property
250            } catch (NoSuchMethodException err) {
251                // Do nothing ... assume not a JavaBean property
252            } catch (IllegalArgumentException err) {
253                // Do nothing ... assume not a JavaBean property
254            } catch (IllegalAccessException err) {
255                // Do nothing ... assume not a JavaBean property
256            } catch (InvocationTargetException err) {
257                // Do nothing ... assume not a JavaBean property
258            }
259    
260            // Now set all the properties that we can, ignoring any property that doesn't fit pattern ...
261            for (Map.Entry<Name, Property> entry : properties.entrySet()) {
262                Name propertyName = entry.getKey();
263                Property property = entry.getValue();
264                String javaPropertyName = propertyName.getLocalName();
265                if (property.isEmpty()) continue;
266                Object value = null;
267                if (property.isSingle()) {
268                    value = property.getValues().next();
269                } else if (property.isMultiple()) {
270                    value = property.getValuesAsArray();
271                }
272                try {
273                    reflection.invokeSetterMethodOnTarget(javaPropertyName, source, value);
274                } catch (SecurityException err) {
275                    // Do nothing ... assume not a JavaBean property
276                } catch (NoSuchMethodException err) {
277                    // Do nothing ... assume not a JavaBean property
278                } catch (IllegalArgumentException err) {
279                    // Do nothing ... assume not a JavaBean property
280                } catch (IllegalAccessException err) {
281                    // Do nothing ... assume not a JavaBean property
282                } catch (InvocationTargetException err) {
283                    // Do nothing ... assume not a JavaBean property
284                }
285            }
286            return source;
287        }
288    
289        /**
290         * {@inheritDoc}
291         */
292        @Override
293        public boolean equals( Object obj ) {
294            if (obj == this) return true;
295            return false;
296        }
297    }