001    /**
002     *  Licensed to the Apache Software Foundation (ASF) under one or more
003     *  contributor license agreements.  See the NOTICE file distributed with
004     *  this work for additional information regarding copyright ownership.
005     *  The ASF licenses this file to You under the Apache License, Version 2.0
006     *  (the "License"); you may not use this file except in compliance with
007     *  the License.  You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     *  Unless required by applicable law or agreed to in writing, software
012     *  distributed under the License is distributed on an "AS IS" BASIS,
013     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     *  See the License for the specific language governing permissions and
015     *  limitations under the License.
016     */
017    package org.apache.geronimo.kernel.config;
018    
019    import java.io.File;
020    import java.io.IOException;
021    import java.io.InputStream;
022    import java.io.OutputStream;
023    import java.io.PrintWriter;
024    import java.net.JarURLConnection;
025    import java.net.URI;
026    import java.net.URL;
027    import java.util.ArrayList;
028    import java.util.Collection;
029    import java.util.Collections;
030    import java.util.HashSet;
031    import java.util.Iterator;
032    import java.util.LinkedHashSet;
033    import java.util.List;
034    import java.util.Map;
035    import java.util.Properties;
036    import java.util.Set;
037    
038    import org.apache.commons.logging.Log;
039    import org.apache.commons.logging.LogFactory;
040    import org.apache.geronimo.gbean.AbstractName;
041    import org.apache.geronimo.gbean.AbstractNameQuery;
042    import org.apache.geronimo.gbean.GAttributeInfo;
043    import org.apache.geronimo.gbean.GBeanData;
044    import org.apache.geronimo.gbean.GReferenceInfo;
045    import org.apache.geronimo.gbean.InvalidConfigurationException;
046    import org.apache.geronimo.gbean.ReferencePatterns;
047    import org.apache.geronimo.kernel.ClassLoading;
048    import org.apache.geronimo.kernel.GBeanAlreadyExistsException;
049    import org.apache.geronimo.kernel.GBeanNotFoundException;
050    import org.apache.geronimo.kernel.InternalKernelException;
051    import org.apache.geronimo.kernel.Kernel;
052    import org.apache.geronimo.kernel.basic.BasicKernel;
053    import org.apache.geronimo.kernel.management.State;
054    import org.apache.geronimo.kernel.repository.Artifact;
055    import org.apache.geronimo.kernel.repository.ArtifactResolver;
056    import org.apache.geronimo.kernel.repository.DefaultArtifactManager;
057    import org.apache.geronimo.kernel.repository.DefaultArtifactResolver;
058    import org.apache.geronimo.kernel.repository.Maven2Repository;
059    
060    /**
061     * @version $Rev:386276 $ $Date: 2008-10-21 14:44:05 +0000 (Tue, 21 Oct 2008) $
062     */
063    public final class ConfigurationUtil {
064        private static final Log log = LogFactory.getLog(ConfigurationUtil.class);
065        private static final ConfigurationMarshaler configurationMarshaler;
066    
067        static {
068            ConfigurationMarshaler marshaler = null;
069            String marshalerClass = System.getProperty("Xorg.apache.geronimo.kernel.config.Marshaler");
070            if (marshalerClass != null) {
071                try {
072                    marshaler = createConfigurationMarshaler(marshalerClass);
073                } catch (Exception e) {
074                    log.error("Error creating configuration marshaler class " + marshalerClass , e);
075                }
076            }
077    
078            // todo this code effectively makes the default format xstream
079            //if (marshaler == null) {
080            //    try {
081            //        marshaler = createConfigurationMarshaler("org.apache.geronimo.kernel.config.xstream.XStreamConfigurationMarshaler");
082            //    } catch (Throwable ignored) {
083            //    }
084            //}
085    
086            if (marshaler == null) {
087                marshaler = new SerializedConfigurationMarshaler();
088            }
089    
090            configurationMarshaler = marshaler;
091        }
092        
093        private static File bootDirectory;
094    
095        private static File getStartupDirectory() {
096            // guess from the location of the jar
097            URL url = ConfigurationUtil.class.getClassLoader().getResource("META-INF/startup-jar");
098    
099            File directory = null;
100            if (url != null) {
101                try {
102                    JarURLConnection jarConnection = (JarURLConnection) url.openConnection();
103                    url = jarConnection.getJarFileURL();
104    
105                    URI baseURI = new URI(url.toString()).resolve("..");
106                    directory = new File(baseURI);
107                } catch (Exception ignored) {
108                    log.error("Error while determining the installation directory of Apache Geronimo", ignored);
109                }
110            } else {
111                log.error("Cound not determine the installation directory of Apache Geronimo, because the startup jar could not be found in the current class loader.");
112            }
113            
114            return directory;
115        }
116        
117        private static File getBootDirectory() {
118            if (bootDirectory == null) {
119                bootDirectory = getStartupDirectory();
120            }        
121            return bootDirectory;
122        }
123        
124        public static ConfigurationMarshaler createConfigurationMarshaler(String marshalerClass) throws Exception {
125            ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
126            Class clazz = null;
127            if (classLoader != null) {
128                try {
129                    clazz = ClassLoading.loadClass(marshalerClass, classLoader);
130                } catch (ClassNotFoundException ignored) {
131                    // doesn't matter
132                }
133            }
134            if (clazz == null) {
135                classLoader = ConfigurationUtil.class.getClassLoader();
136                try {
137                    clazz = ClassLoading.loadClass(marshalerClass, classLoader);
138                } catch (ClassNotFoundException ignored) {
139                    // doesn't matter
140                }
141            }
142    
143            if (clazz != null) {
144                Object object = clazz.newInstance();
145                if (object instanceof ConfigurationMarshaler) {
146                    return (ConfigurationMarshaler) object;
147                } else {
148                    log.warn("Configuration marshaler class is not an instance of ConfigurationMarshaler " + marshalerClass + ": using default configuration ");
149                }
150            }
151            return null;
152        }
153    
154        private ConfigurationUtil() {
155        }
156    
157        public static GBeanState newGBeanState(Collection gbeans) {
158            return configurationMarshaler.newGBeanState(gbeans);
159        }
160    
161        public static AbstractName loadBootstrapConfiguration(Kernel kernel, InputStream in, ClassLoader classLoader) throws Exception {
162            return loadBootstrapConfiguration(kernel, in, classLoader, false);
163        }
164    
165        public static AbstractName loadBootstrapConfiguration(Kernel kernel, InputStream in, ClassLoader classLoader, boolean enableBootRepo) throws Exception {
166            ConfigurationData configurationData = readConfigurationData(in);
167            return loadBootstrapConfiguration(kernel, configurationData, classLoader, enableBootRepo);
168        }
169    
170        public static AbstractName loadBootstrapConfiguration(Kernel kernel, ConfigurationData configurationData, ClassLoader classLoader) throws Exception {
171            return loadBootstrapConfiguration(kernel, configurationData, classLoader, false);
172        }
173    
174        public static AbstractName loadBootstrapConfiguration(Kernel kernel, ConfigurationData configurationData, ClassLoader classLoader, boolean enableBootRepo) throws Exception {
175            if (kernel == null) throw new NullPointerException("kernel is null");
176            if (configurationData == null) throw new NullPointerException("configurationData is null");
177            if (classLoader == null) throw new NullPointerException("classLoader is null");
178    
179            // build the gbean data
180            Artifact configId = configurationData.getId();
181            AbstractName abstractName = Configuration.getConfigurationAbstractName(configId);
182            GBeanData gbeanData = new GBeanData(abstractName, Configuration.GBEAN_INFO);
183            gbeanData.setAttribute("configurationData", configurationData);
184            
185            Collection repositories = null;
186            ArtifactResolver artifactResolver = null;
187            if (enableBootRepo) {
188                String repository = System.getProperty("Xorg.apache.geronimo.repository.boot.path", "repository");
189                Maven2Repository bootRepository = new Maven2Repository(new File(getBootDirectory(), repository));
190                repositories = Collections.singleton(bootRepository);
191                artifactResolver = new DefaultArtifactResolver(new DefaultArtifactManager(), bootRepository);
192            } else {
193                // a bootstrap configuration can not have any dependencies
194                List dependencies = configurationData.getEnvironment().getDependencies();
195                if (!dependencies.isEmpty()) {
196                    configurationData.getEnvironment().setDependencies(Collections.EMPTY_SET);
197                }
198            }
199            gbeanData.setAttribute("configurationResolver", new ConfigurationResolver(configurationData, repositories, artifactResolver));
200    
201            // load and start the gbean
202            kernel.loadGBean(gbeanData, classLoader);
203            kernel.startGBean(gbeanData.getAbstractName());
204    
205            Configuration configuration = (Configuration) kernel.getGBean(gbeanData.getAbstractName());
206    
207            // start the gbeans
208            startConfigurationGBeans(configuration.getAbstractName(), configuration, kernel);
209    
210            ConfigurationManager configurationManager = getConfigurationManager(kernel);
211            configurationManager.loadConfiguration(configId);
212            return gbeanData.getAbstractName();
213        }
214    
215        public static void writeConfigurationData(ConfigurationData configurationData, OutputStream out) throws IOException {
216            configurationMarshaler.writeConfigurationData(configurationData, out);
217        }
218    
219        public static ConfigurationData readConfigurationData(InputStream in) throws IOException, ClassNotFoundException {
220            return configurationMarshaler.readConfigurationData(in);
221        }
222    
223        public static void writeConfigInfo(PrintWriter writer, ConfigurationData configurationData) {
224            writeConfigInfo("", writer, configurationData);
225        }
226        
227        private static void writeConfigInfo(String prefix, PrintWriter writer, ConfigurationData configurationData) {
228            writer.println(prefix+"id=" + configurationData.getId());
229            writer.println(prefix+"type=" + configurationData.getModuleType());
230            writer.println(prefix+"created=" + configurationData.getCreated());
231            Set<Artifact> ownedConfigurations = configurationData.getOwnedConfigurations();
232            int i = 0;
233            for (Artifact ownedConfiguration : ownedConfigurations) {
234                writer.println(prefix + "owned." + i++ + "=" + ownedConfiguration);
235            }
236            i = 0;
237            for (ConfigurationData data : configurationData.getChildConfigurations().values()) {
238                writeConfigInfo("child." + i++ + ".", writer, data);
239            }
240            writer.flush();
241        }
242    
243        public static ConfigurationInfo readConfigurationInfo(InputStream in, AbstractName storeName, File inPlaceLocation) throws IOException {
244            Properties properties = new Properties();
245            properties.load(in);
246            return readConfigurationInfo("", properties, storeName, inPlaceLocation);
247        }
248    
249        private static ConfigurationInfo readConfigurationInfo(String prefix, Properties properties, AbstractName storeName, File inPlaceLocation) throws IOException {
250            String id = properties.getProperty(prefix+"id");
251            Artifact configId = Artifact.create(id);
252    
253            String type = properties.getProperty(prefix+"type");
254            ConfigurationModuleType moduleType = ConfigurationModuleType.getByName(type);
255            if (moduleType == null) {
256                throw new IllegalArgumentException("Unknown module type: " + type);
257            }
258    
259            String created = properties.getProperty(prefix+"created");
260            long time;
261            try {
262                time = Long.parseLong(created);
263            } catch (NumberFormatException e) {
264                throw new IllegalArgumentException("Invalid created time: " + created);
265            }
266    
267            LinkedHashSet ownedConfigurations = new LinkedHashSet();
268            for (Iterator iterator = properties.entrySet().iterator(); iterator.hasNext();) {
269                Map.Entry entry = (Map.Entry) iterator.next();
270                String name = (String) entry.getKey();
271                if (name.startsWith(prefix+"owned.")) {
272                    String value = (String) entry.getValue();
273                    Artifact ownedConfiguration = Artifact.create(value);
274                    ownedConfigurations.add(ownedConfiguration);
275                }
276            }
277            LinkedHashSet childConfigurations = new LinkedHashSet();
278            int test = 0;
279            while(true) {
280                String next = prefix+"child."+test+".";
281                String value = properties.getProperty(next+".id");
282                if(value == null) {
283                    break;
284                }
285                childConfigurations.add(readConfigurationInfo(next, properties, storeName, inPlaceLocation));
286                ++test;
287            }
288    
289            return new ConfigurationInfo(storeName, configId, moduleType, time, ownedConfigurations, childConfigurations, inPlaceLocation);
290        }
291    
292        /**
293         * Gets the name of the ConfigurationManager running in the specified kernel.
294         *
295         * @return Its AbstractName
296         * @throws IllegalStateException Occurs if a ConfigurationManager cannot be identified
297         */
298        public static AbstractName getConfigurationManagerName(Kernel kernel) {
299            Set names = kernel.listGBeans(new AbstractNameQuery(ConfigurationManager.class.getName()));
300            for (Iterator iterator = names.iterator(); iterator.hasNext();) {
301                AbstractName abstractName = (AbstractName) iterator.next();
302                if (!kernel.isRunning(abstractName)) {
303                    iterator.remove();
304                }
305            }
306            if (names.isEmpty()) {
307                throw new IllegalStateException("A Configuration Manager could not be found in the kernel");
308            }
309            if (names.size() > 1) {
310                String error = "More than one Configuration Manager was found in the kernel: ";
311                for (Object name : names) {
312                    AbstractName abName = (AbstractName)name;
313                    error = error + "\"" + abName.toString() + "\" ";
314                }
315                throw new IllegalStateException(error);
316            }
317            return (AbstractName) names.iterator().next();
318        }
319        
320        
321        /**
322         * Gets a reference or proxy to the ConfigurationManager running in the specified kernel.
323         *
324         * @return The ConfigurationManager
325         * @throws IllegalStateException Occurs if a ConfigurationManager cannot be identified
326         */
327        public static ConfigurationManager getConfigurationManager(Kernel kernel) {
328            AbstractName configurationManagerName = getConfigurationManagerName(kernel);
329            return (ConfigurationManager) kernel.getProxyManager().createProxy(configurationManagerName, ConfigurationManager.class);
330        }
331    
332        /**
333         * Gets a reference or proxy to an EditableConfigurationManager running in the specified kernel, if there is one.
334         *
335         * @return The EdtiableConfigurationManager, or none if there is not one available.
336         * @throws IllegalStateException Occurs if there are multiple EditableConfigurationManagers in the kernel.
337         */
338        public static EditableConfigurationManager getEditableConfigurationManager(Kernel kernel) {
339            Set names = kernel.listGBeans(new AbstractNameQuery(EditableConfigurationManager.class.getName()));
340            for (Iterator iterator = names.iterator(); iterator.hasNext();) {
341                AbstractName abstractName = (AbstractName) iterator.next();
342                if (!kernel.isRunning(abstractName)) {
343                    iterator.remove();
344                }
345            }
346            if (names.isEmpty()) {
347                return null; // may be one, just not editable
348            }
349            if (names.size() > 1) {
350                throw new IllegalStateException("More than one Configuration Manager was found in the kernel");
351            }
352            AbstractName configurationManagerName = (AbstractName) names.iterator().next();
353            return (EditableConfigurationManager) kernel.getProxyManager().createProxy(configurationManagerName, EditableConfigurationManager.class);
354        }
355    
356        public static void releaseConfigurationManager(Kernel kernel, ConfigurationManager configurationManager) {
357            kernel.getProxyManager().destroyProxy(configurationManager);
358        }
359    
360        static void preprocessGBeanData(AbstractName configurationName, Configuration configuration, GBeanData gbeanData) throws InvalidConfigException {
361            if (log.isDebugEnabled()) {
362                log.debug("resolving dependencies for " + gbeanData.getAbstractName());
363            }
364            for (Iterator references = gbeanData.getReferencesNames().iterator(); references.hasNext();) {
365                String referenceName = (String) references.next();
366                GReferenceInfo referenceInfo = gbeanData.getGBeanInfo().getReference(referenceName);
367                if (referenceInfo == null) {
368                    throw new InvalidConfigException("No reference named " + referenceName + " in gbean " + gbeanData.getAbstractName());
369                }
370                boolean isSingleValued = !referenceInfo.getProxyType().equals(Collection.class.getName());
371                if (isSingleValued) {
372                    ReferencePatterns referencePatterns = gbeanData.getReferencePatterns(referenceName);
373                    AbstractName abstractName;
374                    try {
375                        abstractName = configuration.findGBean(referencePatterns);
376                        if (log.isDebugEnabled()) {
377                            log.debug("referencePatterns: " + referencePatterns + " resolved to " + abstractName);
378                        }
379                    } catch (GBeanNotFoundException e) {
380                        throw new InvalidConfigException("Unable to resolve reference \"" + referenceName + "\" in gbean " + gbeanData.getAbstractName() + " to a gbean matching the pattern " + referencePatterns, e);
381                    }
382                    gbeanData.setReferencePatterns(referenceName, new ReferencePatterns(abstractName));
383                }
384            }
385    
386            Set newDependencies = new HashSet();
387            for (Iterator dependencyIterator = gbeanData.getDependencies().iterator(); dependencyIterator.hasNext();) {
388                ReferencePatterns referencePatterns = (ReferencePatterns) dependencyIterator.next();
389                AbstractName abstractName;
390                try {
391                    abstractName = configuration.findGBean(referencePatterns);
392                    if (log.isDebugEnabled()) {
393                        log.debug("referencePatterns: " + referencePatterns + " resolved to " + abstractName);
394                    }
395                } catch (GBeanNotFoundException e) {
396                    throw new InvalidConfigException("Unable to resolve dependency in gbean " + gbeanData.getAbstractName(), e);
397                }
398                newDependencies.add(new ReferencePatterns(abstractName));
399            }
400            gbeanData.setDependencies(newDependencies);
401    
402            // If the GBean has a configurationBaseUrl attribute, set it
403            // todo Even though this is not used by the classloader, web apps still need this.  WHY???
404            GAttributeInfo attribute = gbeanData.getGBeanInfo().getAttribute("configurationBaseUrl");
405            if (attribute != null && attribute.getType().equals("java.net.URL")) {
406                try {
407                    Set set = configuration.getConfigurationResolver().resolve("");
408                    if (set.size() != 1) {
409                        throw new AssertionError("Expected one match for pattern \".\", but got " + set.size() + " matches");
410                    }
411                    URL baseURL = (URL) set.iterator().next();
412                    gbeanData.setAttribute("configurationBaseUrl", baseURL);
413                } catch (Exception e) {
414                    throw new InvalidConfigException("Unable to set attribute named " + "configurationBaseUrl" + " in gbean " + gbeanData.getAbstractName(), e);
415                }
416            }
417    
418            // add a dependency from the gbean to the configuration
419            gbeanData.addDependency(configurationName);
420        }
421    
422        static void startConfigurationGBeans(AbstractName configurationName, Configuration configuration, Kernel kernel) throws InvalidConfigException {
423            List gbeans = new ArrayList(configuration.getGBeans().values());
424            Collections.sort(gbeans, new GBeanData.PriorityComparator());
425    
426            List loaded = new ArrayList(gbeans.size());
427            List started = new ArrayList(gbeans.size());
428    
429            try {
430                // register all the GBeans
431                for (Iterator iterator = gbeans.iterator(); iterator.hasNext();) {
432                    GBeanData gbeanData = (GBeanData) iterator.next();
433    
434                    // copy the gbeanData object as not to mutate the original
435                    gbeanData = new GBeanData(gbeanData);
436    
437                    // preprocess the gbeanData (resolve references, set base url, declare dependency, etc.)
438                    preprocessGBeanData(configurationName, configuration, gbeanData);
439    
440                    try {
441                        kernel.loadGBean(gbeanData, configuration.getConfigurationClassLoader());
442                        loaded.add(gbeanData.getAbstractName());
443                    } catch (GBeanAlreadyExistsException e) {
444                        throw new InvalidConfigException(e);
445                    } catch (Throwable e) {
446                        log.warn("Could not load gbean " + gbeanData.getAbstractName(), e);
447                        throw e;
448                    }
449                }
450    
451                try {
452                    // start the gbeans
453                    for (Iterator iterator = gbeans.iterator(); iterator.hasNext();) {
454                        GBeanData gbeanData = (GBeanData) iterator.next();
455                        AbstractName gbeanName = gbeanData.getAbstractName();
456                        kernel.startRecursiveGBean(gbeanName);
457                        started.add(gbeanName);
458                    }
459    
460                    // assure all of the gbeans are started
461                    List unstarted = new ArrayList();
462                    for (Iterator iterator = gbeans.iterator(); iterator.hasNext();) {
463                        GBeanData gbeanData = (GBeanData) iterator.next();
464                        AbstractName gbeanName = gbeanData.getAbstractName();
465                        if (State.RUNNING_INDEX != kernel.getGBeanState(gbeanName)) {
466                            String stateReason = null;
467                            if (kernel instanceof BasicKernel) {
468                                stateReason = ((BasicKernel) kernel).getStateReason(gbeanName);
469                            }
470                            String name = gbeanName.toURI().getQuery();
471                            if (stateReason != null) {
472                                unstarted.add("The service " + name + " did not start because " + stateReason);
473                            } else {
474                                unstarted.add("The service " + name + " did not start for an unknown reason");
475                            }
476                        }
477                    }
478                    if (!unstarted.isEmpty()) {
479                        StringBuffer message = new StringBuffer();
480                        message.append("Configuration ").append(configuration.getId()).append(" failed to start due to the following reasons:\n");
481                        for (Iterator iterator = unstarted.iterator(); iterator.hasNext();) {
482                            String reason = (String) iterator.next();
483                            message.append("  ").append(reason).append("\n");
484                        }
485                        throw new InvalidConfigurationException(message.toString());
486                    }
487                } catch (GBeanNotFoundException e) {
488                    throw new InvalidConfigException(e);
489                }
490    
491                for (Iterator iterator = configuration.getChildren().iterator(); iterator.hasNext();) {
492                    Configuration childConfiguration = (Configuration) iterator.next();
493                    ConfigurationUtil.startConfigurationGBeans(configurationName, childConfiguration, kernel);
494                }
495            } catch (Throwable e) {
496                for (Iterator iterator = started.iterator(); iterator.hasNext();) {
497                    AbstractName gbeanName = (AbstractName) iterator.next();
498                    try {
499                        kernel.stopGBean(gbeanName);
500                    } catch (GBeanNotFoundException ignored) {
501                    } catch (IllegalStateException ignored) {
502                    } catch (InternalKernelException kernelException) {
503                        log.debug("Error cleaning up after failed start of configuration " + configuration.getId() + " gbean " + gbeanName, kernelException);
504                    }
505                }
506                for (Iterator iterator = loaded.iterator(); iterator.hasNext();) {
507                    AbstractName gbeanName = (AbstractName) iterator.next();
508                    try {
509                        kernel.unloadGBean(gbeanName);
510                    } catch (GBeanNotFoundException ignored) {
511                    } catch (IllegalStateException ignored) {
512                    } catch (InternalKernelException kernelException) {
513                        log.debug("Error cleaning up after failed start of configuration " + configuration.getId() + " gbean " + gbeanName, kernelException);
514                    }
515                }
516                if (e instanceof Error) {
517                    throw (Error) e;
518                }                         
519                if (e instanceof InvalidConfigException) {
520                    throw (InvalidConfigException) e;
521                }
522                throw new InvalidConfigException("Unknown start exception", e);
523            }
524        }
525    }