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    }