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.server.spring.main;
018    
019    import java.beans.PropertyEditorManager;
020    import java.io.File;
021    import java.net.JarURLConnection;
022    import java.net.MalformedURLException;
023    import java.net.URI;
024    import java.net.URL;
025    import java.util.Arrays;
026    import java.util.Collections;
027    import java.util.LinkedList;
028    import java.util.List;
029    import java.util.jar.Attributes;
030    import java.util.jar.Manifest;
031    
032    import org.apache.xbean.server.main.FatalStartupError;
033    import org.apache.xbean.server.main.Main;
034    import org.apache.xbean.spring.context.ClassPathXmlApplicationContext;
035    import org.apache.xbean.spring.context.FileSystemXmlApplicationContext;
036    import org.apache.xbean.spring.context.SpringApplicationContext;
037    
038    /**
039     * SpringBootstrap is the main class used by a Spring based server.  This class uses the following strategies to determine
040     * the configuration file to load:
041     *
042     * Command line parameter --bootstrap FILE
043     * Manifest entry XBean-Bootstrap in the startup jar
044     * META-INF/xbean-bootstrap.xml
045     *
046     * This class atempts to first load the configuration file from the local file system and if that fails it attempts to
047     * load it from the classpath.
048     *
049     * SpringBootstrap expects the configuration to contain a service with the id "main" which is an implementation of
050     * org.apache.xbean.server.main.Main.
051     *
052     * This class will set the system property xbean.base.dir to the directory containing the startup jar if the property
053     * has not alredy been set (on the command line).
054     *
055     * @author Dain Sundstrom
056     * @version $Id$
057     * @since 2.0
058     */
059    public class SpringBootstrap {
060        private static final String XBEAN_BOOTSTRAP_MANIFEST = "XBean-Bootstrap";
061        private static final String BOOTSTRAP_FLAG = "--bootstrap";
062        private static final String DEFAULT_BOOTSTRAP = "META-INF/xbean-bootstrap.xml";
063        private static final List DEFAULT_PROPERTY_EDITOR_PATHS = Collections.singletonList("org.apache.xbean.server.propertyeditor");
064    
065        private String configurationFile;
066        private String[] mainArguments;
067        private List propertyEditorPaths = DEFAULT_PROPERTY_EDITOR_PATHS;
068        private String serverBaseDirectory;
069    
070        /**
071         * Initializes and boots the server using the supplied arguments.  If an error is thrown from the boot method,
072         * this method will pring the error to standard error along with the stack trace and exit with the exit specified
073         * in the FatalStartupError or exit code 9 if the error was not a FatalStartupError.
074         * @param args the arguments used to start the server
075         */
076        public static void main(String[] args) {
077            SpringBootstrap springBootstrap = new SpringBootstrap();
078            main(args, springBootstrap);
079        }
080    
081        /**
082         * Like the main(args) method but allows a configured bootstrap instance to be passed in.
083         * 
084         * @see #main(String[])
085         */
086        public static void main(String[] args, SpringBootstrap springBootstrap) {
087            springBootstrap.initialize(args);
088    
089            try {
090                springBootstrap.boot();
091            } catch (FatalStartupError e) {
092                System.err.println(e.getMessage());
093                if (e.getCause() != null) {
094                    e.getCause().printStackTrace();
095                }
096                System.exit(e.getExitCode());
097            } catch (Throwable e) {
098                System.err.println("Unknown error");
099                e.printStackTrace();
100                System.exit(9);
101            }
102        }
103    
104        /**
105         * Gets the configuration file from which the main instance is loaded.
106         * @return the configuration file from which the main instance is loaded
107         */
108        public String getConfigurationFile() {
109            return configurationFile;
110        }
111    
112        /**
113         * Sets the configuration file from which the main instance is loaded.
114         * @param configurationFile the configuration file from which the main instance is loaded
115         */
116        public void setConfigurationFile(String configurationFile) {
117            this.configurationFile = configurationFile;
118        }
119    
120        /**
121         * Gets the arguments passed to the main instance.
122         * @return the arguments passed to the main instance
123         */
124        public String[] getMainArguments() {
125            return mainArguments;
126        }
127    
128        /**
129         * Sets the arguments passed to the main instance.
130         * @param mainArguments the arguments passed to the main instance
131         */
132        public void setMainArguments(String[] mainArguments) {
133            this.mainArguments = mainArguments;
134        }
135    
136        /**
137         * Gets the paths that are appended to the system property editors search path.
138         * @return the paths that are appended to the system property editors search path
139         */
140        public List getPropertyEditorPaths() {
141            return propertyEditorPaths;
142        }
143    
144        /**
145         * Sets the paths that are appended to the system property editors search path.
146         * @param propertyEditorPaths the paths that are appended to the system property editors search path
147         */
148        public void setPropertyEditorPaths(List propertyEditorPaths) {
149            this.propertyEditorPaths = propertyEditorPaths;
150        }
151    
152        /**
153         * Gets the base directory of the server.
154         * @return the base directory of the server
155         */
156        public String getServerBaseDirectory() {
157            return serverBaseDirectory;
158        }
159    
160        /**
161         * Sets the base directory of the server.
162         * @param serverBaseDirectory the base directory of the server
163         */
164        public void setServerBaseDirectory(String serverBaseDirectory) {
165            this.serverBaseDirectory = serverBaseDirectory;
166        }
167    
168        /**
169         * Determines the configuration file and server base directory.
170         * @param args the arguments passed to main
171         */
172        public void initialize(String[] args) {
173            // check if bootstrap configuration was specified on the command line
174            if (args.length > 1 && BOOTSTRAP_FLAG.equals(args[0])) {
175                configurationFile = args[1];
176                this.mainArguments = new String[args.length - 2];
177                System.arraycopy(args, 2, this.mainArguments, 0, args.length);
178            } else {
179                if (configurationFile == null) {
180                    configurationFile = DEFAULT_BOOTSTRAP;
181                }
182                this.mainArguments = args;
183            }
184    
185            // Determine the xbean installation directory
186            // guess from the location of the jar
187            URL url = SpringBootstrap.class.getClassLoader().getResource("META-INF/startup-jar");
188            if (url != null) {
189                try {
190                    JarURLConnection jarConnection = (JarURLConnection) url.openConnection();
191                    url = jarConnection.getJarFileURL();
192    
193                    if (serverBaseDirectory == null) {
194                        URI baseURI = new URI(url.toString()).resolve("..");
195                        serverBaseDirectory = new File(baseURI).getAbsolutePath();
196                    }
197    
198                    Manifest manifest;
199                    manifest = jarConnection.getManifest();
200                    Attributes mainAttributes = manifest.getMainAttributes();
201                    if (configurationFile == null) {
202                        configurationFile = mainAttributes.getValue(XBEAN_BOOTSTRAP_MANIFEST);
203                    }
204                } catch (Exception e) {
205                    System.err.println("Could not determine xbean installation directory");
206                    e.printStackTrace();
207                    System.exit(9);
208                    return;
209                }
210            } else {
211                if (serverBaseDirectory == null) {
212                    String dir = System.getProperty("xbean.base.dir", System.getProperty("user.dir"));
213                    serverBaseDirectory = new File(dir).getAbsolutePath();
214                }
215            }
216        }
217    
218        /**
219         * Loads the main instance from the configuration file.
220         * @return the main instance
221         */
222        public Main loadMain() {
223            if (serverBaseDirectory == null) {
224                throw new NullPointerException("serverBaseDirectory is null");
225    
226            }
227            File baseDirectory = new File(serverBaseDirectory);
228            if (!baseDirectory.isDirectory()) {
229                throw new IllegalArgumentException("serverBaseDirectory is not a directory: " + serverBaseDirectory);
230    
231            }
232            if (configurationFile == null) {
233                throw new NullPointerException("configurationFile is null");
234    
235            }
236    
237            ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();
238            Thread.currentThread().setContextClassLoader(SpringBootstrap.class.getClassLoader());
239            try {
240                // add our property editors into the system
241                if (propertyEditorPaths != null && !propertyEditorPaths.isEmpty()) {
242                    List editorSearchPath = new LinkedList(Arrays.asList(PropertyEditorManager.getEditorSearchPath()));
243                    editorSearchPath.addAll(propertyEditorPaths);
244                    PropertyEditorManager.setEditorSearchPath((String[]) editorSearchPath.toArray(new String[editorSearchPath.size()]));
245                }
246    
247                // set the server base directory system property
248                System.setProperty("xbean.base.dir", baseDirectory.getAbsolutePath());
249    
250                // load the configuration file
251                SpringApplicationContext factory;
252                File file = new File(configurationFile);
253                if (!file.isAbsolute()) {
254                    file = new File(baseDirectory, configurationFile);
255                }
256                if (file.canRead()) {
257                    try {
258                        // configuration file is on the local file system
259                        factory = new FileSystemXmlApplicationContext(file.toURL().toString());
260                    } catch (MalformedURLException e) {
261                        throw new FatalStartupError("Error creating url for bootstrap file", e);
262                    }
263                } else {
264                    // assume it is a classpath resource
265                    factory = new ClassPathXmlApplicationContext(configurationFile);
266                }
267    
268                // get the main service from the configuration file
269                String[] names = factory.getBeanNamesForType(Main.class);
270                Main main = null;
271                if (names.length == 0) {
272                    throw new FatalStartupError("No bean of type: " + Main.class.getName() + " found in the bootstrap file: " + configurationFile, 10);
273                }
274                main = (Main) factory.getBean(names[0]);
275                return main;
276            }
277            finally {
278                Thread.currentThread().setContextClassLoader(oldClassLoader);
279            }
280        }
281    
282        /**
283         * Loads the main instance from the Spring configuration file and executes it.
284         */
285        public void boot() {
286            // load the main instance
287            Main main = loadMain();
288    
289            // start it up
290            main.main(mainArguments);
291    
292        }
293    }