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 }