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.xbean.kernel;
018    
019    import java.io.BufferedReader;
020    import java.io.IOException;
021    import java.io.InputStream;
022    import java.io.InputStreamReader;
023    import java.util.HashMap;
024    import java.util.Map;
025    
026    import java.util.concurrent.ConcurrentHashMap;
027    import org.apache.xbean.kernel.standard.StandardKernelFactory;
028    
029    /**
030     * The Kernel factory is used to construct and locate Kernels.  This class is loosly based on the SAXParserFactory and
031     * the JMX MBeanServerFactory.  To constuct a kernel use the following:
032     * <p><blockquote><pre>
033     * Kernel kernel = KernelFactory.newInstance().createKernel(name);
034     * </pre></blockquote>
035     *
036     * @org.apache.xbean.XBean namespace="http://xbean.apache.org/schemas/kernel" element="load-all-main"
037     *     description="Creates kernels"
038     *
039     * @author Dain Sundstrom
040     * @version $Id$
041     * @since 2.0
042     */
043    public abstract class KernelFactory {
044        /**
045         * The name of the system property and META-INF/services used to locate the kernel factory class.
046         */
047        public static final String KERNEL_FACTORY_KEY = KernelFactory.class.getName();
048    
049        private static final ConcurrentHashMap kernels = new ConcurrentHashMap(1);
050    
051        /**
052         * Gets the kernel registered under the specified name.  If no kernel is registered with the specified name, null
053         * is returned.
054         *
055         * @param name the name of the kernel to return
056         * @return the kernel or null if no kernel is registered under the specified name
057         */
058        public static Kernel getKernel(String name) {
059            if (name == null) throw new NullPointerException("name is null");
060            return (Kernel) kernels.get(name);
061        }
062    
063        /**
064         * Gets a map of the existing kernels by kernel name.
065         *
066         * @return the existing kernels by kernel name.
067         */
068        public static Map getKernels() {
069            return new HashMap(kernels);
070        }
071        
072        /**
073         * Creates a kernel with the specified name.  This method will attempt to locate a KernelFactory implementation
074         * using the following procedure
075         * <ul> <li>
076         * The org.apache.xbean.kernel.KernelFactory system property.
077         * </li> <li>
078         * Use the <a href="http://java.sun.com/j2se/1.3/docs/guide/jar/jar.html">Jar Service Specification</a>
079         * This method will attempt to get the factory name from the file
080         * META-INF/services/org.apache.xbean.kernel.KernelFactory loaded using the thread context class loader.
081         * </li>
082         * <li>
083         * The StandardKernel implementation.
084         * </li>
085         * </ul>
086         * The factory class is loaded and constucted using the thread context class loader, if present, or the class
087         * loader of this class.
088         *
089         * @return the kernel factory implementation
090         * @throws KernelFactoryError if the specified kernel factory can not be created
091         */
092        public static KernelFactory newInstance() throws KernelFactoryError {
093            ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
094            if (classLoader == null) {
095                classLoader = KernelFactory.class.getClassLoader();
096            }
097    
098            // System property
099            try {
100                String kernelFactoryName = System.getProperty(KERNEL_FACTORY_KEY);
101                if (kernelFactoryName != null) {
102                    return createKernelFactory(kernelFactoryName, classLoader);
103                }
104            } catch (SecurityException se) {
105            }
106    
107            // Jar Service Specification - http://java.sun.com/j2se/1.3/docs/guide/jar/jar.html
108            String serviceId = "META-INF/services/" + KERNEL_FACTORY_KEY;
109            InputStream inputStream = null;
110            try {
111                inputStream = classLoader.getResourceAsStream(serviceId);
112                if (inputStream != null) {
113                    BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
114                    String kernelFactoryName = reader.readLine();
115                    reader.close();
116    
117                    if (kernelFactoryName != null && kernelFactoryName.length() > 0) {
118                        return createKernelFactory(kernelFactoryName, classLoader);
119                    }
120                }
121            } catch (Exception ignored) {
122            } finally {
123                if (inputStream != null) {
124                    try {
125                        inputStream.close();
126                    } catch (IOException ignored) {
127                    }
128                    inputStream = null;
129                }
130            }
131    
132            // Default is the standard kernel
133            return new StandardKernelFactory();
134        }
135    
136        /**
137         * Removes the kernel instance from the internal kernel registry.  This method should only be called by the kernel
138         * instance itself during destruction.
139         * @param kernel the kernel to destroy
140         * @throws KernelFactoryError if the kernel is still running
141         */
142        public static void destroyInstance(Kernel kernel) throws KernelFactoryError {
143            if (kernel.isRunning()) {
144                throw new KernelFactoryError("Kernel is running: name" + kernel.getKernelName());
145            }
146    
147            kernels.remove(kernel.getKernelName(), kernel);
148        }
149    
150        private static KernelFactory createKernelFactory(String className, ClassLoader classLoader) throws KernelFactoryError {
151            try {
152                return (KernelFactory) classLoader.loadClass(className).newInstance();
153            } catch (ClassCastException e) {
154                throw new KernelFactoryError("Kernel factory class does not implement KernelFactory: " + className);
155            } catch (ClassNotFoundException e) {
156                throw new KernelFactoryError("Kernel factory class not found: " + className);
157            } catch (Exception e) {
158                throw new KernelFactoryError("Unable to instantiate kernel factory class: " + className, e);
159            }
160        }
161    
162        /**
163         * Creates a new kernel instance and registers it with the static KernelFactory registry.  This allows the kernel
164         * to be retrieved from the {@link KernelFactory#getKernel(String)} method.
165         *
166         * @param name the name of the kernel to create
167         * @return the new kernel
168         * @throws KernelAlreadyExistsException is a kernel already exists with the specified name
169         */
170        public final Kernel createKernel(String name) throws KernelAlreadyExistsException {
171            if (name == null) throw new NullPointerException("name is null");
172    
173            // quick check to see if a kernel already registerd wit the name
174            if (kernels.containsKey(name)) {
175                throw new KernelAlreadyExistsException(name);
176            }
177    
178            // create the kernel -- this may be an unnecessary construction, but it shouldn't be a big deal
179            Kernel kernel = createKernelInternal(name);
180    
181            // register the kernel, checking if someone snuck in an registered a kernel while we were creating ours
182            if (kernels.putIfAbsent(name, kernel) != null) {
183                throw new KernelAlreadyExistsException(name);
184            }
185    
186            return kernel;
187        }
188    
189        /**
190         * Creates the actual kernel instance which will be registerd in the KernelFactory.
191         *
192         * @param name the kernel name
193         * @return a new kernel instance
194         */
195        protected abstract Kernel createKernelInternal(String name);
196    }