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    }