/******************************************************************************* * Copyright (c) 2013 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * and Eclipse Distribution License v1.0 which accompany this distribution. * The Eclipse Public License is available at * http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: * Jan S. Rellermeyer, IBM Research - initial API and implementation *******************************************************************************/ package org.eclipse.concierge.compat.service; import java.io.BufferedReader; import java.io.File; import java.io.FileFilter; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.PrintStream; import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.concierge.BundleImpl.Revision; import org.eclipse.concierge.Concierge; import org.eclipse.concierge.Factory; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.BundleException; import org.osgi.framework.Constants; import org.osgi.framework.startlevel.BundleStartLevel; import org.osgi.framework.wiring.BundleRevision; public class XargsFileLauncher { protected static final boolean WIN = System.getProperty("os.name") .toLowerCase().startsWith("win"); private PrintStream streamErr; public XargsFileLauncher() { this(System.err); } public XargsFileLauncher(PrintStream err) { streamErr = err; } /** * process an init.xargs-style file. * * @param file * the file. * @return the startlevel. * @throws BundleException * @throws FileNotFoundException * @throws Throwable * if something goes wrong. For example, if strict startup is * set and the installation of a bundle fails. */ public Concierge processXargsFile(final File file) throws BundleException, FileNotFoundException { InputStream inputStream = new FileInputStream(file); // we have to preserve the properties for later variable and wildcard // replacement final Map<String, String> passedProperties = getPropertiesFromXargsInputStream( inputStream); // now process again for install/start options with given properties inputStream = new FileInputStream(file); return processXargsInputStream(passedProperties, inputStream); } public Concierge processXargsInputStream( final Map<String, String> passedProperties, final InputStream inputStream) throws BundleException, FileNotFoundException { // create framework with given properties final Concierge concierge = (Concierge) new Factory() .newFramework(passedProperties); concierge.init(); // we will start Concierge immediately BEFORE installing // any bundles into it. // This will result in a natural order of installed bundles. concierge.start(); if (concierge.restart) { return concierge; } final BundleContext context = concierge.getBundleContext(); final BufferedReader reader = new BufferedReader( new InputStreamReader(inputStream)); try { final HashMap<String, Bundle> memory = new HashMap<String, Bundle>( 0); String token; int initLevel = 1; boolean skipProcessing = false; while (!skipProcessing && ((token = reader.readLine()) != null)) { token = token.trim(); if (token.equals("")) { continue; } else if (token.charAt(0) == '#') { continue; } else if (token.startsWith("-initlevel")) { token = getArg(token, 10); initLevel = Integer.parseInt(token); continue; } else if (token.startsWith("-all")) { token = getArg(token, 4); final File jardir; if (token.isEmpty()) { jardir = new File( new URL(concierge.BUNDLE_LOCATION).getFile()); } else { jardir = new File(token); } final File files[]; files = jardir.listFiles(new FilenameFilter() { public boolean accept(File dir, String name) { return name.toLowerCase().endsWith(".jar") || name.toLowerCase().endsWith(".zip"); } }); if (files == null) { printErr("NO FILES FOUND IN " + jardir.getPath()); break; } // first install all bundles final List<Bundle> bundlesToStart = new ArrayList<Bundle>(); for (int i = 0; i < files.length; i++) { if (files[i].isDirectory()) { continue; } final Bundle b = (Bundle) context .installBundle(files[i].getPath()); // adapt to BundleStartLevel final BundleStartLevel bundleStartLevel = b .adapt(BundleStartLevel.class); bundleStartLevel.setStartLevel(initLevel); bundlesToStart.add(b); } // then start all bundles (if not a fragment) for (Iterator<Bundle> iter = bundlesToStart.iterator(); iter .hasNext();) { Bundle b = iter.next(); // is it a fragment? final Revision rev = (Revision) b .adapt(BundleRevision.class); if (!rev.isFragment()) { b.start(); } } continue; } else if (token.startsWith("-istart")) { String bundleLocation = getArg(token, 7); bundleLocation = replaceVariable(bundleLocation, passedProperties); bundleLocation = resolveWildcardName(bundleLocation); final Bundle bundle = context.installBundle(bundleLocation); // adapt to BundleStartLevel final BundleStartLevel bundleStartLevel = bundle .adapt(BundleStartLevel.class); bundleStartLevel.setStartLevel(initLevel); bundle.start(); } else if (token.startsWith("-install")) { String bundleLocation = getArg(token, 8); bundleLocation = replaceVariable(bundleLocation, passedProperties); bundleLocation = resolveWildcardName(bundleLocation); final Bundle bundle = context.installBundle(bundleLocation); // adapt to BundleStartLevel final BundleStartLevel bundleStartLevel = bundle .adapt(BundleStartLevel.class); bundleStartLevel.setStartLevel(initLevel); memory.put(bundleLocation, bundle); } else if (token.startsWith("-start")) { String bundleLocation = getArg(token, 6); bundleLocation = replaceVariable(bundleLocation, passedProperties); bundleLocation = resolveWildcardName(bundleLocation); final Bundle bundle = memory.remove(bundleLocation); if (bundle == null) { printErr("Bundle " + bundleLocation + " is marked to be started but has not been " + "installed before. Ignoring the command !"); } else { // set start level again in case it has been changed // meanwhile final BundleStartLevel bundleStartLevel = bundle .adapt(BundleStartLevel.class); bundleStartLevel.setStartLevel(initLevel); bundle.start(); } } else if (token.startsWith("-skip")) { // skip the remaining part of the xargs file skipProcessing = true; } } } catch (IOException ioe) { ioe.printStackTrace(); } finally { try { reader.close(); } catch (IOException ioe) { } // formerly Concierge was started here. // we moved that to start Concierge at beginning } return concierge; } public Map<String, String> getPropertiesFromXargsInputStream( final InputStream inputStream) { final Map<String, String> properties = new HashMap<String, String>(); final BufferedReader reader = new BufferedReader( new InputStreamReader(inputStream)); try { String line; String token; while ((line = reader.readLine()) != null) { token = line.trim(); if (token.equals("")) { continue; } else if (token.charAt(0) == '#') { continue; } else if (token.startsWith("-D")) { token = getArg(token, 2); // get key and value int pos = token.indexOf("="); if (pos < 0) { printErr("WRONG PROPERTY DEFINITION: " + "EQUALS for -Dname=value IS MISSING, IGNORE '" + line + "'"); } else if (pos == 0) { printErr("WRONG PROPERTY DEFINITION: " + "NAME for -Dname=value IS MISSING, IGNORE '" + line + "'"); } else if (pos > 0) { // do we have "+=" syntax? boolean doAdd = token.charAt(pos - 1) == '+'; String key = token.substring(0, doAdd ? pos - 1 : pos); if (key.length() == 0) { printErr("WRONG PROPERTY DEFINITION: " + "NAME for -Dname+=value IS MISSING, IGNORE '" + line + "'"); continue; } String value = token.substring(pos + 1); // handle multi line properties while (value.endsWith("\\")) { token = reader.readLine(); // filter out comment and trim string token = getArg(token, 0); // append trimmed value without backslash plus next // line value = value.substring(0, value.length() - 1) .trim() + token.trim(); } value = replaceVariable(value, properties); if (doAdd) { String oldValue = properties.get(key); properties.put(key, (oldValue == null ? "" : oldValue) + value); } else { properties.put(key, value); } } continue; } else if (token.startsWith("-profile")) { token = getArg(token, 8); properties.put("ch.ethz.systems.concierge.profile", token); continue; } else if (token.equals("-init")) { properties.put(Constants.FRAMEWORK_STORAGE_CLEAN, Constants.FRAMEWORK_STORAGE_CLEAN_ONFIRSTINIT); } else if (token.startsWith("-startlevel")) { token = getArg(token, 11); properties.put(Constants.FRAMEWORK_BEGINNING_STARTLEVEL, token); } } } catch (IOException ioe) { ioe.printStackTrace(); } finally { try { reader.close(); } catch (IOException ioe) { } } return properties; } /** * get the argument from a start list entry. * * @param entry * the entry. * @param cmdLength * length of command. * @return the argument. */ private static String getArg(final String entry, final int cmdLength) { // strip command final String str = entry.substring(cmdLength); // strip comments int pos = str.indexOf("#"); return pos > -1 ? str.substring(0, pos).trim() : str.trim(); } // package scope for testing support /** Regex pattern for finding ${property} variable. */ static final String regex = "\\$\\{([^}]*)\\}"; /** Precompiler pattern for regex. */ static final Pattern pattern = Pattern.compile(regex); /** * Replace all ${propertyName} entries via its property value. The * implementation has been optimized to use regex pattern matcher. */ String replaceVariable(final String line, final Map<String, String> properties) { final Matcher matcher = pattern.matcher(line); String replacedLine = line; int pos = 0; while (matcher.find(pos)) { pos = matcher.end(); String variable = matcher.group(); String propertyName = variable.substring(2, variable.length() - 1); String propertyValue = properties.get(propertyName); if (propertyValue != null) { replacedLine = replacedLine.replace(variable, propertyValue); } } return replacedLine; } /** * Resolve bundle names with wildcards included. */ String resolveWildcardName(final String bundleName) { if (!bundleName.contains("*")) { return bundleName; } // TODO how to check http protocol? final File dir = new File( bundleName.substring(0, bundleName.lastIndexOf("/"))); // try to use a file filter final FileFilter filter = new FileFilter() { public boolean accept(final File pathname) { final String preStar = bundleName.substring(0, bundleName.lastIndexOf("*")); final String postStar = bundleName .substring(bundleName.lastIndexOf("*") + 1); final String path = WIN ? pathname.getPath().replace('\\', '/') : pathname.getPath(); return path.startsWith(preStar) && path.endsWith(postStar); } }; final File foundFiles[] = dir.listFiles(filter); if ((foundFiles == null) || foundFiles.length == 0) { return bundleName; // use default name in case nothing found } else if (foundFiles.length == 1) { return foundFiles[0].getPath(); // exact match } else if (foundFiles.length > 1) { // sort the list of found files, takes the "newest" one final ArrayList<String> sortedFiles = new ArrayList<String>(); for (int i = 0; i < foundFiles.length; i++) { sortedFiles.add(foundFiles[i].getPath()); } Collections.sort(sortedFiles, Collections.reverseOrder()); return sortedFiles.get(0); } return bundleName; } private void printErr(String msg) { streamErr.println("[XargsFileLauncher] " + msg); } }