001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements.  See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership.  The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the
007     * "License"); you may not use this file except in compliance
008     * with the License.  You may obtain a copy of the License at
009     *
010     *   http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing,
013     * software distributed under the License is distributed on an
014     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015     * KIND, either express or implied.  See the License for the
016     * specific language governing permissions and limitations
017     * under the License.
018     */
019    package org.apache.felix.main;
020    
021    import java.io.*;
022    import java.net.MalformedURLException;
023    import java.net.URL;
024    import java.util.*;
025    import org.apache.felix.framework.util.Util;
026    import org.osgi.framework.Constants;
027    import org.osgi.framework.launch.Framework;
028    import org.osgi.framework.launch.FrameworkFactory;
029    
030    /**
031     * <p>
032     * This class is the default way to instantiate and execute the framework. It is not
033     * intended to be the only way to instantiate and execute the framework; rather, it is
034     * one example of how to do so. When embedding the framework in a host application,
035     * this class can serve as a simple guide of how to do so. It may even be
036     * worthwhile to reuse some of its property handling capabilities.
037     * </p>
038    **/
039    public class Main
040    {
041        /**
042         * Switch for specifying bundle directory.
043        **/
044        public static final String BUNDLE_DIR_SWITCH = "-b";
045    
046        /**
047         * The property name used to specify whether the launcher should
048         * install a shutdown hook.
049        **/
050        public static final String SHUTDOWN_HOOK_PROP = "felix.shutdown.hook";
051        /**
052         * The property name used to specify an URL to the system
053         * property file.
054        **/
055        public static final String SYSTEM_PROPERTIES_PROP = "felix.system.properties";
056        /**
057         * The default name used for the system properties file.
058        **/
059        public static final String SYSTEM_PROPERTIES_FILE_VALUE = "system.properties";
060        /**
061         * The property name used to specify an URL to the configuration
062         * property file to be used for the created the framework instance.
063        **/
064        public static final String CONFIG_PROPERTIES_PROP = "felix.config.properties";
065        /**
066         * The default name used for the configuration properties file.
067        **/
068        public static final String CONFIG_PROPERTIES_FILE_VALUE = "config.properties";
069        /**
070         * Name of the configuration directory.
071         */
072        public static final String CONFIG_DIRECTORY = "conf";
073    
074        private static Framework m_fwk = null;
075    
076        /**
077         * <p>
078         * This method performs the main task of constructing an framework instance
079         * and starting its execution. The following functions are performed
080         * when invoked:
081         * </p>
082         * <ol>
083         *   <li><i><b>Examine and verify command-line arguments.</b></i> The launcher
084         *       accepts a "<tt>-b</tt>" command line switch to set the bundle auto-deploy
085         *       directory and a single argument to set the bundle cache directory.
086         *   </li>
087         *   <li><i><b>Read the system properties file.</b></i> This is a file
088         *       containing properties to be pushed into <tt>System.setProperty()</tt>
089         *       before starting the framework. This mechanism is mainly shorthand
090         *       for people starting the framework from the command line to avoid having
091         *       to specify a bunch of <tt>-D</tt> system property definitions.
092         *       The only properties defined in this file that will impact the framework's
093         *       behavior are the those concerning setting HTTP proxies, such as
094         *       <tt>http.proxyHost</tt>, <tt>http.proxyPort</tt>, and
095         *       <tt>http.proxyAuth</tt>. Generally speaking, the framework does
096         *       not use system properties at all.
097         *   </li>
098         *   <li><i><b>Read the framework's configuration property file.</b></i> This is
099         *       a file containing properties used to configure the framework
100         *       instance and to pass configuration information into
101         *       bundles installed into the framework instance. The configuration
102         *       property file is called <tt>config.properties</tt> by default
103         *       and is located in the <tt>conf/</tt> directory of the Felix
104         *       installation directory, which is the parent directory of the
105         *       directory containing the <tt>felix.jar</tt> file. It is possible
106         *       to use a different location for the property file by specifying
107         *       the desired URL using the <tt>felix.config.properties</tt>
108         *       system property; this should be set using the <tt>-D</tt> syntax
109         *       when executing the JVM. If the <tt>config.properties</tt> file
110         *       cannot be found, then default values are used for all configuration
111         *       properties. Refer to the
112         *       <a href="Felix.html#Felix(java.util.Map)"><tt>Felix</tt></a>
113         *       constructor documentation for more information on framework
114         *       configuration properties.
115         *   </li>
116         *   <li><i><b>Copy configuration properties specified as system properties
117         *       into the set of configuration properties.</b></i> Even though the
118         *       Felix framework does not consult system properties for configuration
119         *       information, sometimes it is convenient to specify them on the command
120         *       line when launching Felix. To make this possible, the Felix launcher
121         *       copies any configuration properties specified as system properties
122         *       into the set of configuration properties passed into Felix.
123         *   </li>
124         *   <li><i><b>Add shutdown hook.</b></i> To make sure the framework shutdowns
125         *       cleanly, the launcher installs a shutdown hook; this can be disabled
126         *       with the <tt>felix.shutdown.hook</tt> configuration property.
127         *   </li>
128         *   <li><i><b>Create and initialize a framework instance.</b></i> The OSGi standard
129         *       <tt>FrameworkFactory</tt> is retrieved from <tt>META-INF/services</tt>
130         *       and used to create a framework instance with the configuration properties.
131         *   </li>
132         *   <li><i><b>Auto-deploy bundles.</b></i> All bundles in the auto-deploy
133         *       directory are deployed into the framework instance.
134         *   </li>
135         *   <li><i><b>Start the framework.</b></i> The framework is started and
136         *       the launcher thread waits for the framework to shutdown.
137         *   </li>
138         * </ol>
139         * <p>
140         * It should be noted that simply starting an instance of the framework is not
141         * enough to create an interactive session with it. It is necessary to install
142         * and start bundles that provide a some means to interact with the framework;
143         * this is generally done by bundles in the auto-deploy directory or specifying
144         * an "auto-start" property in the configuration property file. If no bundles
145         * providing a means to interact with the framework are installed or if the
146         * configuration property file cannot be found, the framework will appear to
147         * be hung or deadlocked. This is not the case, it is executing correctly,
148         * there is just no way to interact with it.
149         * </p>
150         * <p>
151         * The launcher provides two ways to deploy bundles into a framework at
152         * startup, which have associated configuration properties:
153         * </p>
154         * <ul>
155         *   <li>Bundle auto-deploy - Automatically deploys all bundles from a
156         *       specified directory, controlled by the following configuration
157         *       properties:
158         *     <ul>
159         *       <li><tt>felix.auto.deploy.dir</tt> - Specifies the auto-deploy directory
160         *           from which bundles are automatically deploy at framework startup.
161         *           The default is the <tt>bundle/</tt> directory of the current directory.
162         *       </li>
163         *       <li><tt>felix.auto.deploy.action</tt> - Specifies the auto-deploy actions
164         *           to be found on bundle JAR files found in the auto-deploy directory.
165         *           The possible actions are <tt>install</tt>, <tt>update</tt>,
166         *           <tt>start</tt>, and <tt>uninstall</tt>. If no actions are specified,
167         *           then the auto-deploy directory is not processed. There is no default
168         *           value for this property.
169         *       </li>
170         *     </ul>
171         *   </li>
172         *   <li>Bundle auto-properties - Configuration properties which specify URLs
173         *       to bundles to install/start:
174         *     <ul>
175         *       <li><tt>felix.auto.install.N</tt> - Space-delimited list of bundle
176         *           URLs to automatically install when the framework is started,
177         *           where <tt>N</tt> is the start level into which the bundle will be
178         *           installed (e.g., felix.auto.install.2).
179         *       </li>
180         *       <li><tt>felix.auto.start.N</tt> - Space-delimited list of bundle URLs
181         *           to automatically install and start when the framework is started,
182         *           where <tt>N</tt> is the start level into which the bundle will be
183         *           installed (e.g., felix.auto.start.2).
184         *       </li>
185         *     </ul>
186         *   </li>
187         * </ul>
188         * <p>
189         * These properties should be specified in the <tt>config.properties</tt>
190         * so that they can be processed by the launcher during the framework
191         * startup process.
192         * </p>
193         * @param args Accepts arguments to set the auto-deploy directory and/or
194         *        the bundle cache directory.
195         * @throws Exception If an error occurs.
196        **/
197        public static void main(String[] args) throws Exception
198        {
199            // Look for bundle directory and/or cache directory.
200            // We support at most one argument, which is the bundle
201            // cache directory.
202            String bundleDir = null;
203            String cacheDir = null;
204            boolean expectBundleDir = false;
205            for (int i = 0; i < args.length; i++)
206            {
207                if (args[i].equals(BUNDLE_DIR_SWITCH))
208                {
209                    expectBundleDir = true;
210                }
211                else if (expectBundleDir)
212                {
213                    bundleDir = args[i];
214                    expectBundleDir = false;
215                }
216                else
217                {
218                    cacheDir = args[i];
219                }
220            }
221    
222            if ((args.length > 3) || (expectBundleDir && bundleDir == null))
223            {
224                System.out.println("Usage: [-b <bundle-deploy-dir>] [<bundle-cache-dir>]");
225                System.exit(0);
226            }
227    
228            // Load system properties.
229            Main.loadSystemProperties();
230    
231            // Read configuration properties.
232            Properties configProps = Main.loadConfigProperties();
233            // If no configuration properties were found, then create
234            // an empty properties object.
235            if (configProps == null)
236            {
237                System.err.println("No " + CONFIG_PROPERTIES_FILE_VALUE + " found.");
238                configProps = new Properties();
239            }
240    
241            // Copy framework properties from the system properties.
242            Main.copySystemProperties(configProps);
243    
244            // If there is a passed in bundle auto-deploy directory, then
245            // that overwrites anything in the config file.
246            if (bundleDir != null)
247            {
248                configProps.setProperty(AutoProcessor.AUTO_DEPLOY_DIR_PROPERY, bundleDir);
249            }
250    
251            // If there is a passed in bundle cache directory, then
252            // that overwrites anything in the config file.
253            if (cacheDir != null)
254            {
255                configProps.setProperty(Constants.FRAMEWORK_STORAGE, cacheDir);
256            }
257    
258            // If enabled, register a shutdown hook to make sure the framework is
259            // cleanly shutdown when the VM exits.
260            String enableHook = configProps.getProperty(SHUTDOWN_HOOK_PROP);
261            if ((enableHook == null) || !enableHook.equalsIgnoreCase("false"))
262            {
263                Runtime.getRuntime().addShutdownHook(new Thread("Felix Shutdown Hook") {
264                    public void run()
265                    {
266                        try
267                        {
268                            if (m_fwk != null)
269                            {
270                                m_fwk.stop();
271                                m_fwk.waitForStop(0);
272                            }
273                        }
274                        catch (Exception ex)
275                        {
276                            System.err.println("Error stopping framework: " + ex);
277                        }
278                    }
279                });
280            }
281    
282            // Print welcome banner.
283            System.out.println("\nWelcome to Felix");
284            System.out.println("================\n");
285    
286            try
287            {
288                // Create an instance of the framework.
289                FrameworkFactory factory = getFrameworkFactory();
290                m_fwk = factory.newFramework(configProps);
291                // Initialize the framework, but don't start it yet.
292                m_fwk.init();
293                // Use the system bundle context to process the auto-deploy
294                // and auto-install/auto-start properties.
295                AutoProcessor.process(configProps, m_fwk.getBundleContext());
296                // Start the framework.
297                m_fwk.start();
298                // Wait for framework to stop to exit the VM.
299                m_fwk.waitForStop(0);
300                System.exit(0);
301            }
302            catch (Exception ex)
303            {
304                System.err.println("Could not create framework: " + ex);
305                ex.printStackTrace();
306                System.exit(-1);
307            }
308        }
309    
310        /**
311         * Simple method to parse META-INF/services file for framework factory.
312         * Currently, it assumes the first non-commented line is the class name
313         * of the framework factory implementation.
314         * @return The created <tt>FrameworkFactory</tt> instance.
315         * @throws Exception if any errors occur.
316        **/
317        private static FrameworkFactory getFrameworkFactory() throws Exception
318        {
319            URL url = Main.class.getClassLoader().getResource(
320                "META-INF/services/org.osgi.framework.launch.FrameworkFactory");
321            if (url != null)
322            {
323                BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream()));
324                try
325                {
326                    for (String s = br.readLine(); s != null; s = br.readLine())
327                    {
328                        s = s.trim();
329                        // Try to load first non-empty, non-commented line.
330                        if ((s.length() > 0) && (s.charAt(0) != '#'))
331                        {
332                            return (FrameworkFactory) Class.forName(s).newInstance();
333                        }
334                    }
335                }
336                finally
337                {
338                    if (br != null) br.close();
339                }
340            }
341    
342            throw new Exception("Could not find framework factory.");
343        }
344    
345        /**
346         * <p>
347         * Loads the properties in the system property file associated with the
348         * framework installation into <tt>System.setProperty()</tt>. These properties
349         * are not directly used by the framework in anyway. By default, the system
350         * property file is located in the <tt>conf/</tt> directory of the Felix
351         * installation directory and is called "<tt>system.properties</tt>". The
352         * installation directory of Felix is assumed to be the parent directory of
353         * the <tt>felix.jar</tt> file as found on the system class path property.
354         * The precise file from which to load system properties can be set by
355         * initializing the "<tt>felix.system.properties</tt>" system property to an
356         * arbitrary URL.
357         * </p>
358        **/
359        public static void loadSystemProperties()
360        {
361            // The system properties file is either specified by a system
362            // property or it is in the same directory as the Felix JAR file.
363            // Try to load it from one of these places.
364    
365            // See if the property URL was specified as a property.
366            URL propURL = null;
367            String custom = System.getProperty(SYSTEM_PROPERTIES_PROP);
368            if (custom != null)
369            {
370                try
371                {
372                    propURL = new URL(custom);
373                }
374                catch (MalformedURLException ex)
375                {
376                    System.err.print("Main: " + ex);
377                    return;
378                }
379            }
380            else
381            {
382                // Determine where the configuration directory is by figuring
383                // out where felix.jar is located on the system class path.
384                File confDir = null;
385                String classpath = System.getProperty("java.class.path");
386                int index = classpath.toLowerCase().indexOf("felix.jar");
387                int start = classpath.lastIndexOf(File.pathSeparator, index) + 1;
388                if (index >= start)
389                {
390                    // Get the path of the felix.jar file.
391                    String jarLocation = classpath.substring(start, index);
392                    // Calculate the conf directory based on the parent
393                    // directory of the felix.jar directory.
394                    confDir = new File(
395                        new File(new File(jarLocation).getAbsolutePath()).getParent(),
396                        CONFIG_DIRECTORY);
397                }
398                else
399                {
400                    // Can't figure it out so use the current directory as default.
401                    confDir = new File(System.getProperty("user.dir"), CONFIG_DIRECTORY);
402                }
403    
404                try
405                {
406                    propURL = new File(confDir, SYSTEM_PROPERTIES_FILE_VALUE).toURL();
407                }
408                catch (MalformedURLException ex)
409                {
410                    System.err.print("Main: " + ex);
411                    return;
412                }
413            }
414    
415            // Read the properties file.
416            Properties props = new Properties();
417            InputStream is = null;
418            try
419            {
420                is = propURL.openConnection().getInputStream();
421                props.load(is);
422                is.close();
423            }
424            catch (FileNotFoundException ex)
425            {
426                // Ignore file not found.
427            }
428            catch (Exception ex)
429            {
430                System.err.println(
431                    "Main: Error loading system properties from " + propURL);
432                System.err.println("Main: " + ex);
433                try
434                {
435                    if (is != null) is.close();
436                }
437                catch (IOException ex2)
438                {
439                    // Nothing we can do.
440                }
441                return;
442            }
443    
444            // Perform variable substitution on specified properties.
445            for (Enumeration e = props.propertyNames(); e.hasMoreElements(); )
446            {
447                String name = (String) e.nextElement();
448                System.setProperty(name,
449                    Util.substVars(props.getProperty(name), name, null, null));
450            }
451        }
452    
453        /**
454         * <p>
455         * Loads the configuration properties in the configuration property file
456         * associated with the framework installation; these properties
457         * are accessible to the framework and to bundles and are intended
458         * for configuration purposes. By default, the configuration property
459         * file is located in the <tt>conf/</tt> directory of the Felix
460         * installation directory and is called "<tt>config.properties</tt>".
461         * The installation directory of Felix is assumed to be the parent
462         * directory of the <tt>felix.jar</tt> file as found on the system class
463         * path property. The precise file from which to load configuration
464         * properties can be set by initializing the "<tt>felix.config.properties</tt>"
465         * system property to an arbitrary URL.
466         * </p>
467         * @return A <tt>Properties</tt> instance or <tt>null</tt> if there was an error.
468        **/
469        public static Properties loadConfigProperties()
470        {
471            // The config properties file is either specified by a system
472            // property or it is in the conf/ directory of the Felix
473            // installation directory.  Try to load it from one of these
474            // places.
475    
476            // See if the property URL was specified as a property.
477            URL propURL = null;
478            String custom = System.getProperty(CONFIG_PROPERTIES_PROP);
479            if (custom != null)
480            {
481                try
482                {
483                    propURL = new URL(custom);
484                }
485                catch (MalformedURLException ex)
486                {
487                    System.err.print("Main: " + ex);
488                    return null;
489                }
490            }
491            else
492            {
493                // Determine where the configuration directory is by figuring
494                // out where felix.jar is located on the system class path.
495                File confDir = null;
496                String classpath = System.getProperty("java.class.path");
497                int index = classpath.toLowerCase().indexOf("felix.jar");
498                int start = classpath.lastIndexOf(File.pathSeparator, index) + 1;
499                if (index >= start)
500                {
501                    // Get the path of the felix.jar file.
502                    String jarLocation = classpath.substring(start, index);
503                    // Calculate the conf directory based on the parent
504                    // directory of the felix.jar directory.
505                    confDir = new File(
506                        new File(new File(jarLocation).getAbsolutePath()).getParent(),
507                        CONFIG_DIRECTORY);
508                }
509                else
510                {
511                    // Can't figure it out so use the current directory as default.
512                    confDir = new File(System.getProperty("user.dir"), CONFIG_DIRECTORY);
513                }
514    
515                try
516                {
517                    propURL = new File(confDir, CONFIG_PROPERTIES_FILE_VALUE).toURL();
518                }
519                catch (MalformedURLException ex)
520                {
521                    System.err.print("Main: " + ex);
522                    return null;
523                }
524            }
525    
526            // Read the properties file.
527            Properties props = new Properties();
528            InputStream is = null;
529            try
530            {
531                // Try to load config.properties.
532                is = propURL.openConnection().getInputStream();
533                props.load(is);
534                is.close();
535            }
536            catch (Exception ex)
537            {
538                // Try to close input stream if we have one.
539                try
540                {
541                    if (is != null) is.close();
542                }
543                catch (IOException ex2)
544                {
545                    // Nothing we can do.
546                }
547    
548                return null;
549            }
550    
551            // Perform variable substitution for system properties.
552            for (Enumeration e = props.propertyNames(); e.hasMoreElements(); )
553            {
554                String name = (String) e.nextElement();
555                props.setProperty(name,
556                    Util.substVars(props.getProperty(name), name, null, props));
557            }
558    
559            return props;
560        }
561    
562        public static void copySystemProperties(Properties configProps)
563        {
564            for (Enumeration e = System.getProperties().propertyNames();
565                 e.hasMoreElements(); )
566            {
567                String key = (String) e.nextElement();
568                if (key.startsWith("felix.") || key.startsWith("org.osgi.framework."))
569                {
570                    configProps.setProperty(key, System.getProperty(key));
571                }
572            }
573        }
574    }