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.File;
022    import java.util.*;
023    import org.osgi.framework.*;
024    import org.osgi.service.startlevel.*;
025    
026    public class AutoProcessor
027    {
028        /**
029         * The property name used for the bundle directory.
030        **/
031        public static final String AUTO_DEPLOY_DIR_PROPERY = "felix.auto.deploy.dir";
032        /**
033         * The default name used for the bundle directory.
034        **/
035        public static final String AUTO_DEPLOY_DIR_VALUE = "bundle";
036        /**
037         * The property name used to specify auto-deploy actions.
038        **/
039        public static final String AUTO_DEPLOY_ACTION_PROPERY = "felix.auto.deploy.action";
040        /**
041         * The name used for the auto-deploy install action.
042        **/
043        public static final String AUTO_DEPLOY_INSTALL_VALUE = "install";
044        /**
045         * The name used for the auto-deploy start action.
046        **/
047        public static final String AUTO_DEPLOY_START_VALUE = "start";
048        /**
049         * The name used for the auto-deploy update action.
050        **/
051        public static final String AUTO_DEPLOY_UPDATE_VALUE = "update";
052        /**
053         * The name used for the auto-deploy uninstall action.
054        **/
055        public static final String AUTO_DEPLOY_UNINSTALL_VALUE = "uninstall";
056        /**
057         * The property name prefix for the launcher's auto-install property.
058        **/
059        public static final String AUTO_INSTALL_PROP = "felix.auto.install";
060        /**
061         * The property name prefix for the launcher's auto-start property.
062        **/
063        public static final String AUTO_START_PROP = "felix.auto.start";
064    
065        /**
066         * Used to instigate auto-deploy directory process and auto-install/auto-start
067         * configuration property processing during.
068         * @param configMap Map of configuration properties.
069         * @param context The system bundle context.
070        **/
071        public static void process(Map configMap, BundleContext context)
072        {
073            configMap = (configMap == null) ? new HashMap() : configMap;
074            processAutoDeploy(configMap, context);
075            processAutoProperties(configMap, context);
076        }
077    
078        /**
079         * <p>
080         * Processes bundles in the auto-deploy directory, installing and then
081         * starting each one.
082         * </p>
083         */
084        private static void processAutoDeploy(Map configMap, BundleContext context)
085        {
086            // Determine if auto deploy actions to perform.
087            String action = (String) configMap.get(AUTO_DEPLOY_ACTION_PROPERY);
088            action = (action == null) ? "" : action;
089            List actionList = new ArrayList();
090            StringTokenizer st = new StringTokenizer(action, ",");
091            while (st.hasMoreTokens())
092            {
093                String s = st.nextToken().trim().toLowerCase();
094                if (s.equals(AUTO_DEPLOY_INSTALL_VALUE)
095                    || s.equals(AUTO_DEPLOY_START_VALUE)
096                    || s.equals(AUTO_DEPLOY_UPDATE_VALUE)
097                    || s.equals(AUTO_DEPLOY_UNINSTALL_VALUE))
098                {
099                    actionList.add(s);
100                }
101            }
102    
103            // Perform auto-deploy actions.
104            if (actionList.size() > 0)
105            {
106                // Get list of already installed bundles as a map.
107                Map installedBundleMap = new HashMap();
108                Bundle[] bundles = context.getBundles();
109                for (int i = 0; i < bundles.length; i++)
110                {
111                    installedBundleMap.put(bundles[i].getLocation(), bundles[i]);
112                }
113    
114                // Get the auto deploy directory.
115                String autoDir = (String) configMap.get(AUTO_DEPLOY_DIR_PROPERY);
116                autoDir = (autoDir == null) ? AUTO_DEPLOY_DIR_VALUE : autoDir;
117                // Look in the specified bundle directory to create a list
118                // of all JAR files to install.
119                File[] files = new File(autoDir).listFiles();
120                List jarList = new ArrayList();
121                if (files != null)
122                {
123                    Arrays.sort(files);
124                    for (int i = 0; i < files.length; i++)
125                    {
126                        if (files[i].getName().endsWith(".jar"))
127                        {
128                            jarList.add(files[i]);
129                        }
130                    }
131                }
132    
133                // Install bundle JAR files and remember the bundle objects.
134                final List startBundleList = new ArrayList();
135                for (int i = 0; i < jarList.size(); i++)
136                {
137                    // Look up the bundle by location, removing it from
138                    // the map of installed bundles so the remaining bundles
139                    // indicate which bundles may need to be uninstalled.
140                    Bundle b = (Bundle) installedBundleMap.remove(
141                        ((File) jarList.get(i)).toURI().toString());
142                    try
143                    {
144                        // If the bundle is not already installed, then install it
145                        // if the 'install' action is present.
146                        if ((b == null) && actionList.contains(AUTO_DEPLOY_INSTALL_VALUE))
147                        {
148                            b = context.installBundle(
149                                ((File) jarList.get(i)).toURI().toString());
150                        }
151                        // If the bundle is already installed, then update it
152                        // if the 'update' action is present.
153                        else if (actionList.contains(AUTO_DEPLOY_UPDATE_VALUE))
154                        {
155                            b.update();
156                        }
157    
158                        // If we have found and/or successfully installed a bundle,
159                        // then add it to the list of bundles to potentially start.
160                        if (b != null)
161                        {
162                            startBundleList.add(b);
163                        }
164                    }
165                    catch (BundleException ex)
166                    {
167                        System.err.println("Auto-deploy install: "
168                            + ex + ((ex.getCause() != null) ? " - " + ex.getCause() : ""));
169                    }
170                }
171    
172                // Uninstall all bundles not in the auto-deploy directory if
173                // the 'uninstall' action is present.
174                if (actionList.contains(AUTO_DEPLOY_UNINSTALL_VALUE))
175                {
176                    for (Iterator it = installedBundleMap.entrySet().iterator(); it.hasNext(); )
177                    {
178                        Map.Entry entry = (Map.Entry) it.next();
179                        Bundle b = (Bundle) entry.getValue();
180                        if (b.getBundleId() != 0)
181                        {
182                            try
183                            {
184                                b.uninstall();
185                            }
186                            catch (BundleException ex)
187                            {
188                            System.err.println("Auto-deploy uninstall: "
189                                + ex + ((ex.getCause() != null) ? " - " + ex.getCause() : ""));
190                            }
191                        }
192                    }
193                }
194    
195                // Start all installed and/or updated bundles if the 'start'
196                // action is present.
197                if (actionList.contains(AUTO_DEPLOY_START_VALUE))
198                {
199                    for (int i = 0; i < startBundleList.size(); i++)
200                    {
201                        try
202                        {
203                            if (!isFragment((Bundle) startBundleList.get(i)))
204                            {
205                                ((Bundle) startBundleList.get(i)).start();
206                            }
207                        }
208                        catch (BundleException ex)
209                        {
210                            System.err.println("Auto-deploy start: "
211                                + ex + ((ex.getCause() != null) ? " - " + ex.getCause() : ""));
212                        }
213                    }
214                }
215            }
216        }
217    
218        /**
219         * <p>
220         * Processes the auto-install and auto-start properties from the
221         * specified configuration properties.
222         * </p>
223         */
224        private static void processAutoProperties(Map configMap, BundleContext context)
225        {
226            // Retrieve the Start Level service, since it will be needed
227            // to set the start level of the installed bundles.
228            StartLevel sl = (StartLevel) context.getService(
229                context.getServiceReference(org.osgi.service.startlevel.StartLevel.class.getName()));
230    
231            // Retrieve all auto-install and auto-start properties and install
232            // their associated bundles. The auto-install property specifies a
233            // space-delimited list of bundle URLs to be automatically installed
234            // into each new profile, while the auto-start property specifies
235            // bundles to be installed and started. The start level to which the
236            // bundles are assigned is specified by appending a ".n" to the
237            // property name, where "n" is the desired start level for the list
238            // of bundles. If no start level is specified, the default start
239            // level is assumed.
240            for (Iterator i = configMap.keySet().iterator(); i.hasNext(); )
241            {
242                String key = ((String) i.next()).toLowerCase();
243    
244                // Ignore all keys that are not an auto property.
245                if (!key.startsWith(AUTO_INSTALL_PROP) && !key.startsWith(AUTO_START_PROP))
246                {
247                    continue;
248                }
249    
250                // If the auto property does not have a start level,
251                // then assume it is the default bundle start level, otherwise
252                // parse the specified start level.
253                int startLevel = sl.getInitialBundleStartLevel();
254                if (!key.equals(AUTO_INSTALL_PROP) && !key.equals(AUTO_START_PROP))
255                {
256                    try
257                    {
258                        startLevel = Integer.parseInt(key.substring(key.lastIndexOf('.') + 1));
259                    }
260                    catch (NumberFormatException ex)
261                    {
262                        System.err.println("Invalid property: " + key);
263                    }
264                }
265    
266                // Parse and install the bundles associated with the key.
267                StringTokenizer st = new StringTokenizer((String) configMap.get(key), "\" ", true);
268                for (String location = nextLocation(st); location != null; location = nextLocation(st))
269                {
270                    try
271                    {
272                        Bundle b = context.installBundle(location, null);
273                        sl.setBundleStartLevel(b, startLevel);
274                    }
275                    catch (Exception ex)
276                    {
277                        System.err.println("Auto-properties install: " + location + " ("
278                            + ex + ((ex.getCause() != null) ? " - " + ex.getCause() : "") + ")");
279    if (ex.getCause() != null)
280        ex.printStackTrace();
281                    }
282                }
283            }
284    
285            // Now loop through the auto-start bundles and start them.
286            for (Iterator i = configMap.keySet().iterator(); i.hasNext(); )
287            {
288                String key = ((String) i.next()).toLowerCase();
289                if (key.startsWith(AUTO_START_PROP))
290                {
291                    StringTokenizer st = new StringTokenizer((String) configMap.get(key), "\" ", true);
292                    for (String location = nextLocation(st); location != null; location = nextLocation(st))
293                    {
294                        // Installing twice just returns the same bundle.
295                        try
296                        {
297                            Bundle b = context.installBundle(location, null);
298                            if (b != null)
299                            {
300                                b.start();
301                            }
302                        }
303                        catch (Exception ex)
304                        {
305                            System.err.println("Auto-properties start: " + location + " ("
306                                + ex + ((ex.getCause() != null) ? " - " + ex.getCause() : "") + ")");
307                        }
308                    }
309                }
310            }
311        }
312    
313        private static String nextLocation(StringTokenizer st)
314        {
315            String retVal = null;
316    
317            if (st.countTokens() > 0)
318            {
319                String tokenList = "\" ";
320                StringBuffer tokBuf = new StringBuffer(10);
321                String tok = null;
322                boolean inQuote = false;
323                boolean tokStarted = false;
324                boolean exit = false;
325                while ((st.hasMoreTokens()) && (!exit))
326                {
327                    tok = st.nextToken(tokenList);
328                    if (tok.equals("\""))
329                    {
330                        inQuote = ! inQuote;
331                        if (inQuote)
332                        {
333                            tokenList = "\"";
334                        }
335                        else
336                        {
337                            tokenList = "\" ";
338                        }
339    
340                    }
341                    else if (tok.equals(" "))
342                    {
343                        if (tokStarted)
344                        {
345                            retVal = tokBuf.toString();
346                            tokStarted=false;
347                            tokBuf = new StringBuffer(10);
348                            exit = true;
349                        }
350                    }
351                    else
352                    {
353                        tokStarted = true;
354                        tokBuf.append(tok.trim());
355                    }
356                }
357    
358                // Handle case where end of token stream and
359                // still got data
360                if ((!exit) && (tokStarted))
361                {
362                    retVal = tokBuf.toString();
363                }
364            }
365    
366            return retVal;
367        }
368    
369        private static boolean isFragment(Bundle bundle)
370        {
371            return bundle.getHeaders().get(Constants.FRAGMENT_HOST) != null;
372        }
373    }