cn.calm.osgi.conter.FelixOsgiHost.java Source code

Java tutorial

Introduction

Here is the source code for cn.calm.osgi.conter.FelixOsgiHost.java

Source

/*
 * $Id$
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package cn.calm.osgi.conter;

import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.TreeMap;

import javax.servlet.ServletContext;

import org.apache.commons.lang3.StringUtils;
import org.apache.felix.framework.FrameworkFactory;
import org.apache.felix.framework.util.FelixConstants;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.launch.Framework;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import cn.calm.osgi.conter.util.AutoProcessor;
import cn.calm.osgi.conter.util.FelixPropertiesUtil;
import cn.calm.osgi.conter.util.Resource;

/**
 * Apache felix implementation of an OsgiHost See
 * http://felix.apache.org/site/apache
 * -felix-framework-launching-and-embedding.html <br/>
 * Servlet config params:
 * <p>
 * struts.osgi.clearBundleCache: Defaults to "true" delete installed bundles
 * when the comntainer starts
 * </p>
 * <p>
 * struts.osgi.logLevel: Defaults to "1". Felix log level. 1 = error, 2 =
 * warning, 3 = information, and 4 = debug
 * </p>
 * <p>
 * struts.osgi.runLevel: Defaults to "3". Run level to start the container.
 * </p>
 */
public class FelixOsgiHost implements OsgiHost {
    private static final Logger LOG = LoggerFactory.getLogger(FelixOsgiHost.class);
    Resource resource;
    Framework f;
    private ServletContext servletContext;

    protected void startFelix() {

        // load properties from felix embedded file
        Properties configProps = getProperties("default.properties");

        // Copy framework properties from the system properties.
        FelixPropertiesUtil.copySystemProperties(configProps);
        replaceSystemPackages(configProps);

        // struts, xwork and felix exported packages
        Properties strutsConfigProps = getProperties("osgi.properties");
        addExportedPackages(strutsConfigProps, configProps);

        // find bundles and adde em to autostart property
        addAutoStartBundles(configProps);

        // Bundle cache
        String storageDir = System.getProperty("java.io.tmpdir") + ".felix-cache";
        configProps.setProperty(Constants.FRAMEWORK_STORAGE, storageDir);
        if (LOG.isDebugEnabled())
            LOG.debug("Storing bundles at [#0]", storageDir);

        String cleanBundleCache = getServletContextParam("struts.osgi.clearBundleCache", "true");
        if ("true".equalsIgnoreCase(cleanBundleCache)) {
            if (LOG.isDebugEnabled())
                LOG.debug("Clearing bundle cache");
            configProps.put(FelixConstants.FRAMEWORK_STORAGE_CLEAN,
                    FelixConstants.FRAMEWORK_STORAGE_CLEAN_ONFIRSTINIT);
        }

        configProps.put(FelixConstants.SERVICE_URLHANDLERS_PROP, "false");
        configProps.put(FelixConstants.LOG_LEVEL_PROP, getServletContextParam("struts.osgi.logLevel", "3"));
        configProps.put(FelixConstants.BUNDLE_CLASSPATH, ".");

        configProps.put("org.osgi.framework.startlevel.beginning",
                getServletContextParam("struts.osgi.runLevel", "3"));
        //
        try {
            Map<String, String> map = new TreeMap<String, String>(new Comparator<String>() {
                public int compare(String obj1, String obj2) {
                    // ???
                    return obj1.compareTo(obj2);
                }
            });
            for (Object o : configProps.keySet()) {
                map.put(String.valueOf(o), configProps.getProperty(String.valueOf(o)));
            }
            FrameworkFactory ff = new FrameworkFactory();

            f = ff.newFramework(map);
            // f.start();
            f.init();

            AutoProcessor.process(map, f.getBundleContext());
            f.start();

        } catch (Exception ex) {
            throw new RuntimeException("Couldn't start Apache Felix", ex);
        }

        // add the bundle context to the ServletContext
        servletContext.setAttribute(OSGI_BUNDLE_CONTEXT, f.getBundleContext());
    }

    /**
     * Gets a param from the ServletContext, returning the default value if the
     * param is not set
     * 
     * @param paramName
     *            the name of the param to get from the ServletContext
     * @param defaultValue
     *            value to return if the param is not set
     * @return
     */
    private String getServletContextParam(String paramName, String defaultValue) {
        return StringUtils.defaultString(this.servletContext.getInitParameter(paramName), defaultValue);
    }

    protected void addAutoStartBundles(Properties configProps) {
        List<String> bundleJarsLevel1 = new ArrayList<String>();
        configProps.put(AutoProcessor.AUTO_START_PROP + ".1", StringUtils.join(bundleJarsLevel1, " "));

        // get a list of directories under /bundles with numeric names (the
        // runlevel)
        Map<String, String> runLevels = getRunLevelDirs("bundles");
        if (runLevels.isEmpty()) {
            // there are no run level dirs, search for bundles in that dir
            List<String> bundles = getBundlesInDir("bundles");
            if (!bundles.isEmpty())
                configProps.put(AutoProcessor.AUTO_START_PROP + ".2", StringUtils.join(bundles, " "));
        } else {
            for (String runLevel : runLevels.keySet()) {
                if ("1".endsWith(runLevel))
                    throw new RuntimeException(
                            "Run level dirs must be greater than 1. Run level 1 is reserved for the Felix bundles");
                List<String> bundles = getBundlesInDir(runLevels.get(runLevel));
                configProps.put(AutoProcessor.AUTO_START_PROP + "." + runLevel, StringUtils.join(bundles, " "));
            }
        }
    }

    /**
     * Return a list of directories under a directory whose name is a number
     */
    protected Map<String, String> getRunLevelDirs(String dir) {
        Map<String, String> dirs = new HashMap<String, String>();
        try {

            URL url = resource.find("bundles");
            if (url != null) {
                if ("file".equals(url.getProtocol())) {
                    File bundlesDir = new File(url.toURI());
                    String[] runLevelDirs = bundlesDir.list(new FilenameFilter() {
                        public boolean accept(File file, String name) {
                            try {
                                return file.isDirectory() && Integer.valueOf(name) > 0;
                            } catch (NumberFormatException ex) {
                                // the name is not a number
                                return false;
                            }
                        }
                    });

                    if (runLevelDirs != null && runLevelDirs.length > 0) {
                        // add all the dirs to the list
                        for (String runLevel : runLevelDirs)
                            dirs.put(runLevel, StringUtils.removeEnd(dir, "/") + "/" + runLevel);

                    } else if (LOG.isDebugEnabled()) {
                        LOG.debug("No run level directories found under the [#0] directory", dir);
                    }
                } else if (LOG.isWarnEnabled())
                    LOG.warn("Unable to read [#0] directory", dir);
            } else if (LOG.isWarnEnabled())
                LOG.warn("The [#0] directory was not found", dir);
        } catch (Exception e) {
            if (LOG.isWarnEnabled())
                LOG.warn("Unable load bundles from the [#0] directory", e, dir);
        }
        return dirs;
    }

    protected List<String> getBundlesInDir(String dir) {
        List<String> bundleJars = new ArrayList<String>();
        try {
            URL url = resource.find(dir);
            if (url != null) {
                if ("file".equals(url.getProtocol())) {
                    File bundlesDir = new File(url.toURI());
                    File[] bundles = bundlesDir.listFiles(new FilenameFilter() {
                        public boolean accept(File file, String name) {
                            return StringUtils.endsWith(name, ".jar");
                        }
                    });

                    if (bundles != null && bundles.length > 0) {
                        // add all the bundles to the list
                        for (File bundle : bundles) {
                            String externalForm = bundle.toURI().toURL().toExternalForm();
                            if (LOG.isDebugEnabled()) {
                                LOG.debug("Adding bundle [#0]", externalForm);
                            }
                            bundleJars.add(externalForm);
                        }

                    } else if (LOG.isDebugEnabled()) {
                        LOG.debug("No bundles found under the [#0] directory", dir);
                    }
                } else if (LOG.isWarnEnabled())
                    LOG.warn("Unable to read [#0] directory", dir);
            } else if (LOG.isWarnEnabled())
                LOG.warn("The [#0] directory was not found", dir);
        } catch (Exception e) {
            if (LOG.isWarnEnabled())
                LOG.warn("Unable load bundles from the [#0] directory", e, dir);
        }
        return bundleJars;
    }

    //
    //
    protected void replaceSystemPackages(Properties properties) {
        // Felix has a way to load the config file and substitution expressions
        // but the method does not have a way to specify the file (other than in
        // an env variable)

        // ${jre-${java.specification.version}}
        String systemPackages = (String) properties.get(Constants.FRAMEWORK_SYSTEMPACKAGES);
        String jreVersion = "jre-" + System.getProperty("java.version").substring(0, 3);
        systemPackages = systemPackages.replace("${jre-${java.specification.version}}",
                (String) properties.get(jreVersion));
        properties.put(Constants.FRAMEWORK_SYSTEMPACKAGES, systemPackages);
    }

    //
    /*
     * Find subpackages of the packages defined in the property file and export
     * them
     */
    protected void addExportedPackages(Properties strutsConfigProps, Properties configProps) {
        String[] rootPackages = StringUtils.split(strutsConfigProps.getProperty("scanning.package.includes"), ",");
        List<String> exportedPackages = new ArrayList<String>();
        // build a list of subpackages
        StringBuilder strB = new StringBuilder();
        //      org.osgi.framework;version="[1.4,2)"
        for (String rootPackage : rootPackages) {
            String sp[] = StringUtils.split(rootPackage, ";");
            strB.delete(0, strB.length());
            strB.append(sp[0]);
            strB.append(";version=\"");
            try {
                strB.append(sp[1]);
            } catch (Exception e) {
                strB.append("0.0.0");
            }
            strB.append("\"");
            exportedPackages.add(strB.toString());
        }
        //
        //      // make a string with the exported packages and add it to the system
        //      // properties
        if (!exportedPackages.isEmpty()) {
            String systemPackages = (String) configProps.get(Constants.FRAMEWORK_SYSTEMPACKAGES);
            systemPackages = StringUtils.removeEnd(systemPackages, ",") + ","
                    + StringUtils.join(exportedPackages, ",");
            configProps.put(Constants.FRAMEWORK_SYSTEMPACKAGES, systemPackages);
        }
        System.out.println(exportedPackages);
    }
    //
    //   /**
    //    * Gets the version used to export the packages. it tries to get it from
    //    * MANIFEST.MF, or the file name
    //    */
    //   protected String getVersion(URL url) {
    //      if ("jar".equals(url.getProtocol())) {
    //         try {
    ////            FileManager fileManager = ServletActionContext.getContext()
    ////                  .getInstance(FileManagerFactory.class).getFileManager();
    //            String file=url.toURI().toString();
    //            file=file.substring(10, file.lastIndexOf("!"));
    //            JarFile jarFile = new JarFile(file);
    //            Manifest manifest = jarFile.getManifest();
    //            if (manifest != null) {
    //               String version = manifest.getMainAttributes().getValue(
    //                     "Bundle-Version");
    //               if (StringUtils.isNotBlank(version)) {
    //                  return getVersionFromString(version);
    //               }
    //            } else {
    //               // try to get the version from the file name
    //               return getVersionFromString(jarFile.getName());
    //            }
    //         } catch (Exception e) {
    //            if (LOG.isErrorEnabled())
    //               LOG.error(
    //                     "Unable to extract version from [#0], defaulting to '1.0.0'",
    //                     url.toExternalForm());
    //
    //         }
    //      }
    //
    //      return "1.0.0";
    //   }

    //   /**
    //    * Extracts numbers followed by "." or "-" from the string and joins them
    //    * with "."
    //    */
    //   protected static String getVersionFromString(String str) {
    //      Matcher matcher = versionPattern.matcher(str);
    //      List<String> parts = new ArrayList<String>();
    //      while (matcher.find()) {
    //         parts.add(matcher.group(1));
    //      }
    //
    //      // default
    //      if (parts.size() == 0)
    //         return "1.0.0";
    //
    //      while (parts.size() < 3)
    //         parts.add("0");
    //
    //      return StringUtils.join(parts, ".");
    //   }

    protected Properties getProperties(String fileName) {

        try {
            return resource.findProperties(fileName);
        } catch (IOException e) {
            if (LOG.isErrorEnabled())
                LOG.error("Unable to read property file [#]", fileName);
            return new Properties();
        }
    }

    /**
     * This bundle map will not change, but the status of the bundles can change
     * over time. Use getActiveBundles() for active bundles
     */
    public Map<String, Bundle> getBundles() {
        Map<String, Bundle> bundles = new HashMap<String, Bundle>();
        for (Bundle bundle : f.getBundleContext().getBundles()) {
            bundles.put(bundle.getSymbolicName(), bundle);
        }

        return Collections.unmodifiableMap(bundles);
    }

    public Map<String, Bundle> getActiveBundles() {
        Map<String, Bundle> bundles = new HashMap<String, Bundle>();
        for (Bundle bundle : f.getBundleContext().getBundles()) {
            if (bundle.getState() == Bundle.ACTIVE)
                bundles.put(bundle.getSymbolicName(), bundle);
        }

        return Collections.unmodifiableMap(bundles);
    }

    public BundleContext getBundleContext() {
        return f.getBundleContext();
    }

    public void destroy() throws Exception {
        f.stop();
        if (LOG.isTraceEnabled())
            LOG.trace("Apache Felix has stopped");
    }

    public void init(ServletContext servletContext) {
        this.servletContext = servletContext;
        resource = Resource.getResource(servletContext);
        startFelix();
    }
}