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: 2007-06-24 02:01:56 -0400 (Sun, 24 Jun 2007) $
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 final File bootDirectory;
094    
095        static {
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            bootDirectory = directory;
114        }
115    
116        public static ConfigurationMarshaler createConfigurationMarshaler(String marshalerClass) throws Exception {
117            ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
118            Class clazz = null;
119            if (classLoader != null) {
120                try {
121                    clazz = ClassLoading.loadClass(marshalerClass, classLoader);
122                } catch (ClassNotFoundException ignored) {
123                    // doesn't matter
124                }
125            }
126            if (clazz == null) {
127                classLoader = ConfigurationUtil.class.getClassLoader();
128                try {
129                    clazz = ClassLoading.loadClass(marshalerClass, classLoader);
130                } catch (ClassNotFoundException ignored) {
131                    // doesn't matter
132                }
133            }
134    
135            if (clazz != null) {
136                Object object = clazz.newInstance();
137                if (object instanceof ConfigurationMarshaler) {
138                    return (ConfigurationMarshaler) object;
139                } else {
140                    log.warn("Configuration marshaler class is not an instance of ConfigurationMarshaler " + marshalerClass + ": using default configuration ");
141                }
142            }
143            return null;
144        }
145    
146        private ConfigurationUtil() {
147        }
148    
149        public static GBeanState newGBeanState(Collection gbeans) {
150            return configurationMarshaler.newGBeanState(gbeans);
151        }
152    
153        public static AbstractName loadBootstrapConfiguration(Kernel kernel, InputStream in, ClassLoader classLoader) throws Exception {
154            return loadBootstrapConfiguration(kernel, in, classLoader, false);
155        }
156    
157        public static AbstractName loadBootstrapConfiguration(Kernel kernel, InputStream in, ClassLoader classLoader, boolean enableBootRepo) throws Exception {
158            ConfigurationData configurationData = readConfigurationData(in);
159            return loadBootstrapConfiguration(kernel, configurationData, classLoader, enableBootRepo);
160        }
161    
162        public static AbstractName loadBootstrapConfiguration(Kernel kernel, ConfigurationData configurationData, ClassLoader classLoader) throws Exception {
163            return loadBootstrapConfiguration(kernel, configurationData, classLoader, false);
164        }
165    
166        public static AbstractName loadBootstrapConfiguration(Kernel kernel, ConfigurationData configurationData, ClassLoader classLoader, boolean enableBootRepo) throws Exception {
167            if (kernel == null) throw new NullPointerException("kernel is null");
168            if (configurationData == null) throw new NullPointerException("configurationData is null");
169            if (classLoader == null) throw new NullPointerException("classLoader is null");
170    
171            // build the gbean data
172            Artifact configId = configurationData.getId();
173            AbstractName abstractName = Configuration.getConfigurationAbstractName(configId);
174            GBeanData gbeanData = new GBeanData(abstractName, Configuration.GBEAN_INFO);
175            gbeanData.setAttribute("configurationData", configurationData);
176            
177            Collection repositories = null;
178            ArtifactResolver artifactResolver = null;
179            if (enableBootRepo) {
180                String repository = System.getProperty("Xorg.apache.geronimo.repository.boot.path", "repository");
181                Maven2Repository bootRepository = new Maven2Repository(new File(bootDirectory, repository));
182                repositories = Collections.singleton(bootRepository);
183                artifactResolver = new DefaultArtifactResolver(new DefaultArtifactManager(), bootRepository);
184            } else {
185                // a bootstrap configuration can not have any dependencies
186                List dependencies = configurationData.getEnvironment().getDependencies();
187                if (!dependencies.isEmpty()) {
188                    configurationData.getEnvironment().setDependencies(Collections.EMPTY_SET);
189                }
190            }
191            gbeanData.setAttribute("configurationResolver", new ConfigurationResolver(configurationData, repositories, artifactResolver));
192    
193            // load and start the gbean
194            kernel.loadGBean(gbeanData, classLoader);
195            kernel.startGBean(gbeanData.getAbstractName());
196    
197            Configuration configuration = (Configuration) kernel.getGBean(gbeanData.getAbstractName());
198    
199            // start the gbeans
200            startConfigurationGBeans(configuration.getAbstractName(), configuration, kernel);
201    
202            ConfigurationManager configurationManager = getConfigurationManager(kernel);
203            configurationManager.loadConfiguration(configId);
204            return gbeanData.getAbstractName();
205        }
206    
207        public static void writeConfigurationData(ConfigurationData configurationData, OutputStream out) throws IOException {
208            configurationMarshaler.writeConfigurationData(configurationData, out);
209        }
210    
211        public static ConfigurationData readConfigurationData(InputStream in) throws IOException, ClassNotFoundException {
212            return configurationMarshaler.readConfigurationData(in);
213        }
214    
215        public static void writeConfigInfo(PrintWriter writer, ConfigurationData configurationData) {
216            writeConfigInfo("", writer, configurationData);
217        }
218        private static void writeConfigInfo(String prefix, PrintWriter writer, ConfigurationData configurationData) {
219            writer.println(prefix+"id=" + configurationData.getId());
220            writer.println(prefix+"type=" + configurationData.getModuleType());
221            writer.println(prefix+"created=" + configurationData.getCreated());
222            Set ownedConfigurations = configurationData.getOwnedConfigurations();
223            int i = 0;
224            for (Iterator iterator = ownedConfigurations.iterator(); iterator.hasNext();) {
225                Artifact ownedConfiguration = (Artifact) iterator.next();
226                writer.println(prefix+"owned." + i++ + "=" + ownedConfiguration);
227            }
228            i = 0;
229            for (Iterator it = configurationData.getChildConfigurations().values().iterator(); it.hasNext(); i++) {
230                ConfigurationData data = (ConfigurationData) it.next();
231                writeConfigInfo("child."+i+".", writer, data);
232            }
233            writer.flush();
234        }
235    
236        public static ConfigurationInfo readConfigurationInfo(InputStream in, AbstractName storeName, File inPlaceLocation) throws IOException {
237            Properties properties = new Properties();
238            properties.load(in);
239            return readConfigurationInfo("", properties, storeName, inPlaceLocation);
240        }
241        private static ConfigurationInfo readConfigurationInfo(String prefix, Properties properties, AbstractName storeName, File inPlaceLocation) throws IOException {
242            String id = properties.getProperty(prefix+"id");
243            Artifact configId = Artifact.create(id);
244    
245            String type = properties.getProperty(prefix+"type");
246            ConfigurationModuleType moduleType = ConfigurationModuleType.getByName(type);
247            if (moduleType == null) {
248                throw new IllegalArgumentException("Unknown module type: " + type);
249            }
250    
251            String created = properties.getProperty(prefix+"created");
252            long time;
253            try {
254                time = Long.parseLong(created);
255            } catch (NumberFormatException e) {
256                throw new IllegalArgumentException("Invalid created time: " + created);
257            }
258    
259            LinkedHashSet ownedConfigurations = new LinkedHashSet();
260            for (Iterator iterator = properties.entrySet().iterator(); iterator.hasNext();) {
261                Map.Entry entry = (Map.Entry) iterator.next();
262                String name = (String) entry.getKey();
263                if (name.startsWith(prefix+"owned.")) {
264                    String value = (String) entry.getValue();
265                    Artifact ownedConfiguration = Artifact.create(value);
266                    ownedConfigurations.add(ownedConfiguration);
267                }
268            }
269            LinkedHashSet childConfigurations = new LinkedHashSet();
270            int test = 0;
271            while(true) {
272                String next = prefix+"child."+test+".";
273                String value = properties.getProperty(next+".id");
274                if(value == null) {
275                    break;
276                }
277                childConfigurations.add(readConfigurationInfo(next, properties, storeName, inPlaceLocation));
278                ++test;
279            }
280    
281            return new ConfigurationInfo(storeName, configId, moduleType, time, ownedConfigurations, childConfigurations, inPlaceLocation);
282        }
283    
284        /**
285         * Gets the name of the ConfigurationManager running in the specified kernel.
286         *
287         * @return Its AbstractName
288         * @throws IllegalStateException Occurs if a ConfigurationManager cannot be identified
289         */
290        public static AbstractName getConfigurationManagerName(Kernel kernel) {
291            Set names = kernel.listGBeans(new AbstractNameQuery(ConfigurationManager.class.getName()));
292            for (Iterator iterator = names.iterator(); iterator.hasNext();) {
293                AbstractName abstractName = (AbstractName) iterator.next();
294                if (!kernel.isRunning(abstractName)) {
295                    iterator.remove();
296                }
297            }
298            if (names.isEmpty()) {
299                throw new IllegalStateException("A Configuration Manager could not be found in the kernel");
300            }
301            if (names.size() > 1) {
302                String error = "More than one Configuration Manager was found in the kernel: ";
303                for (Object name : names) {
304                    AbstractName abName = (AbstractName)name;
305                    error = error + "\"" + abName.toString() + "\" ";
306                }
307                throw new IllegalStateException(error);
308            }
309            return (AbstractName) names.iterator().next();
310        }
311        
312        
313        /**
314         * Gets a reference or proxy to the ConfigurationManager running in the specified kernel.
315         *
316         * @return The ConfigurationManager
317         * @throws IllegalStateException Occurs if a ConfigurationManager cannot be identified
318         */
319        public static ConfigurationManager getConfigurationManager(Kernel kernel) {
320            AbstractName configurationManagerName = getConfigurationManagerName(kernel);
321            return (ConfigurationManager) kernel.getProxyManager().createProxy(configurationManagerName, ConfigurationManager.class);
322        }
323    
324        /**
325         * Gets a reference or proxy to an EditableConfigurationManager running in the specified kernel, if there is one.
326         *
327         * @return The EdtiableConfigurationManager, or none if there is not one available.
328         * @throws IllegalStateException Occurs if there are multiple EditableConfigurationManagers in the kernel.
329         */
330        public static EditableConfigurationManager getEditableConfigurationManager(Kernel kernel) {
331            Set names = kernel.listGBeans(new AbstractNameQuery(EditableConfigurationManager.class.getName()));
332            for (Iterator iterator = names.iterator(); iterator.hasNext();) {
333                AbstractName abstractName = (AbstractName) iterator.next();
334                if (!kernel.isRunning(abstractName)) {
335                    iterator.remove();
336                }
337            }
338            if (names.isEmpty()) {
339                return null; // may be one, just not editable
340            }
341            if (names.size() > 1) {
342                throw new IllegalStateException("More than one Configuration Manager was found in the kernel");
343            }
344            AbstractName configurationManagerName = (AbstractName) names.iterator().next();
345            return (EditableConfigurationManager) kernel.getProxyManager().createProxy(configurationManagerName, EditableConfigurationManager.class);
346        }
347    
348        public static void releaseConfigurationManager(Kernel kernel, ConfigurationManager configurationManager) {
349            kernel.getProxyManager().destroyProxy(configurationManager);
350        }
351    
352        static void preprocessGBeanData(AbstractName configurationName, Configuration configuration, GBeanData gbeanData) throws InvalidConfigException {
353            if (log.isDebugEnabled()) {
354                log.debug("resolving dependencies for " + gbeanData.getAbstractName());
355            }
356            for (Iterator references = gbeanData.getReferencesNames().iterator(); references.hasNext();) {
357                String referenceName = (String) references.next();
358                GReferenceInfo referenceInfo = gbeanData.getGBeanInfo().getReference(referenceName);
359                if (referenceInfo == null) {
360                    throw new InvalidConfigException("No reference named " + referenceName + " in gbean " + gbeanData.getAbstractName());
361                }
362                boolean isSingleValued = !referenceInfo.getProxyType().equals(Collection.class.getName());
363                if (isSingleValued) {
364                    ReferencePatterns referencePatterns = gbeanData.getReferencePatterns(referenceName);
365                    AbstractName abstractName;
366                    try {
367                        abstractName = configuration.findGBean(referencePatterns);
368                        if (log.isDebugEnabled()) {
369                            log.debug("referencePatterns: " + referencePatterns + " resolved to " + abstractName);
370                        }
371                    } catch (GBeanNotFoundException e) {
372                        throw new InvalidConfigException("Unable to resolve reference \"" + referenceName + "\" in gbean " + gbeanData.getAbstractName() + " to a gbean matching the pattern " + referencePatterns, e);
373                    }
374                    gbeanData.setReferencePatterns(referenceName, new ReferencePatterns(abstractName));
375                }
376            }
377    
378            Set newDependencies = new HashSet();
379            for (Iterator dependencyIterator = gbeanData.getDependencies().iterator(); dependencyIterator.hasNext();) {
380                ReferencePatterns referencePatterns = (ReferencePatterns) dependencyIterator.next();
381                AbstractName abstractName;
382                try {
383                    abstractName = configuration.findGBean(referencePatterns);
384                    if (log.isDebugEnabled()) {
385                        log.debug("referencePatterns: " + referencePatterns + " resolved to " + abstractName);
386                    }
387                } catch (GBeanNotFoundException e) {
388                    throw new InvalidConfigException("Unable to resolve dependency in gbean " + gbeanData.getAbstractName(), e);
389                }
390                newDependencies.add(new ReferencePatterns(abstractName));
391            }
392            gbeanData.setDependencies(newDependencies);
393    
394            // If the GBean has a configurationBaseUrl attribute, set it
395            // todo Even though this is not used by the classloader, web apps still need this.  WHY???
396            GAttributeInfo attribute = gbeanData.getGBeanInfo().getAttribute("configurationBaseUrl");
397            if (attribute != null && attribute.getType().equals("java.net.URL")) {
398                try {
399                    Set set = configuration.getConfigurationResolver().resolve("");
400                    if (set.size() != 1) {
401                        throw new AssertionError("Expected one match for pattern \".\", but got " + set.size() + " matches");
402                    }
403                    URL baseURL = (URL) set.iterator().next();
404                    gbeanData.setAttribute("configurationBaseUrl", baseURL);
405                } catch (Exception e) {
406                    throw new InvalidConfigException("Unable to set attribute named " + "configurationBaseUrl" + " in gbean " + gbeanData.getAbstractName(), e);
407                }
408            }
409    
410            // add a dependency from the gbean to the configuration
411            gbeanData.addDependency(configurationName);
412        }
413    
414        static void startConfigurationGBeans(AbstractName configurationName, Configuration configuration, Kernel kernel) throws InvalidConfigException {
415            List gbeans = new ArrayList(configuration.getGBeans().values());
416            Collections.sort(gbeans, new GBeanData.PriorityComparator());
417    
418            List loaded = new ArrayList(gbeans.size());
419            List started = new ArrayList(gbeans.size());
420    
421            try {
422                // register all the GBeans
423                for (Iterator iterator = gbeans.iterator(); iterator.hasNext();) {
424                    GBeanData gbeanData = (GBeanData) iterator.next();
425    
426                    // copy the gbeanData object as not to mutate the original
427                    gbeanData = new GBeanData(gbeanData);
428    
429                    // preprocess the gbeanData (resolve references, set base url, declare dependency, etc.)
430                    preprocessGBeanData(configurationName, configuration, gbeanData);
431    
432                    try {
433                        kernel.loadGBean(gbeanData, configuration.getConfigurationClassLoader());
434                        loaded.add(gbeanData.getAbstractName());
435                    } catch (GBeanAlreadyExistsException e) {
436                        throw new InvalidConfigException(e);
437                    } catch (Throwable e) {
438                        log.warn("Could not load gbean " + gbeanData.getAbstractName(), e);
439                        throw e;
440                    }
441                }
442    
443                try {
444                    // start the gbeans
445                    for (Iterator iterator = gbeans.iterator(); iterator.hasNext();) {
446                        GBeanData gbeanData = (GBeanData) iterator.next();
447                        AbstractName gbeanName = gbeanData.getAbstractName();
448                        kernel.startRecursiveGBean(gbeanName);
449                        started.add(gbeanName);
450                    }
451    
452                    // assure all of the gbeans are started
453                    List unstarted = new ArrayList();
454                    for (Iterator iterator = gbeans.iterator(); iterator.hasNext();) {
455                        GBeanData gbeanData = (GBeanData) iterator.next();
456                        AbstractName gbeanName = gbeanData.getAbstractName();
457                        if (State.RUNNING_INDEX != kernel.getGBeanState(gbeanName)) {
458                            String stateReason = null;
459                            if (kernel instanceof BasicKernel) {
460                                stateReason = ((BasicKernel) kernel).getStateReason(gbeanName);
461                            }
462                            String name = gbeanName.toURI().getQuery();
463                            if (stateReason != null) {
464                                unstarted.add("The service " + name + " did not start because " + stateReason);
465                            } else {
466                                unstarted.add("The service " + name + " did not start for an unknown reason");
467                            }
468                        }
469                    }
470                    if (!unstarted.isEmpty()) {
471                        StringBuffer message = new StringBuffer();
472                        message.append("Configuration ").append(configuration.getId()).append(" failed to start due to the following reasons:\n");
473                        for (Iterator iterator = unstarted.iterator(); iterator.hasNext();) {
474                            String reason = (String) iterator.next();
475                            message.append("  ").append(reason).append("\n");
476                        }
477                        throw new InvalidConfigurationException(message.toString());
478                    }
479                } catch (GBeanNotFoundException e) {
480                    throw new InvalidConfigException(e);
481                }
482    
483                for (Iterator iterator = configuration.getChildren().iterator(); iterator.hasNext();) {
484                    Configuration childConfiguration = (Configuration) iterator.next();
485                    ConfigurationUtil.startConfigurationGBeans(configurationName, childConfiguration, kernel);
486                }
487            } catch (Throwable e) {
488                for (Iterator iterator = started.iterator(); iterator.hasNext();) {
489                    AbstractName gbeanName = (AbstractName) iterator.next();
490                    try {
491                        kernel.stopGBean(gbeanName);
492                    } catch (GBeanNotFoundException ignored) {
493                    } catch (IllegalStateException ignored) {
494                    } catch (InternalKernelException kernelException) {
495                        log.debug("Error cleaning up after failed start of configuration " + configuration.getId() + " gbean " + gbeanName, kernelException);
496                    }
497                }
498                for (Iterator iterator = loaded.iterator(); iterator.hasNext();) {
499                    AbstractName gbeanName = (AbstractName) iterator.next();
500                    try {
501                        kernel.unloadGBean(gbeanName);
502                    } catch (GBeanNotFoundException ignored) {
503                    } catch (IllegalStateException ignored) {
504                    } catch (InternalKernelException kernelException) {
505                        log.debug("Error cleaning up after failed start of configuration " + configuration.getId() + " gbean " + gbeanName, kernelException);
506                    }
507                }
508                if (e instanceof Error) {
509                    throw (Error) e;
510                }                         
511                if (e instanceof InvalidConfigException) {
512                    throw (InvalidConfigException) e;
513                }
514                throw new InvalidConfigException("Unknown start exception", e);
515            }
516        }
517    }