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