package ddth.dasp.servlet.osgi; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Comparator; import java.util.Date; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import javax.servlet.ServletContext; import org.apache.commons.io.IOUtils; import org.apache.felix.framework.util.Util; import org.apache.felix.main.AutoProcessor; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.BundleException; import org.osgi.framework.Constants; import org.osgi.framework.ServiceReference; import org.osgi.framework.launch.Framework; import org.osgi.framework.launch.FrameworkFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.Marker; import org.slf4j.MarkerFactory; import ddth.dasp.common.DaspGlobal; import ddth.dasp.common.config.IConfigDao; import ddth.dasp.common.id.IdGenerator; import ddth.dasp.common.osgi.IOsgiBootstrap; import ddth.dasp.common.utils.OsgiUtils; public class FelixOsgiBootstrap implements IOsgiBootstrap { private final static String OSGI_CONFIG_FILE = "osgi-felix.properties"; private final static String SYSPROP_SPRING_PROFILE = "spring.profiles.active"; private ServletContext servletContext; private String osgiContainerLocation = "/WEB-INF/osgi-container"; private Logger LOGGER = LoggerFactory.getLogger(FelixOsgiBootstrap.class); private Marker FATAL = MarkerFactory.getMarker("FATAL"); private Framework framework; private String remoteShellListenIp = "127.0.0.1"; private int remoteShellListenPort = 6666; protected ServletContext getServletContext() { return servletContext; } public void setServletContext(ServletContext servletContext) { this.servletContext = servletContext; } /** * Gets the osgi container folder. * * @return String */ protected String getOsgiContainerLocation() { return osgiContainerLocation; } /** * Sets the osgi container folder. * * @param osgiContainerLocation * String */ public void setOsgiContainerLocation(String osgiContainerLocation) { this.osgiContainerLocation = osgiContainerLocation; } /** * Set the Felix's Remote Shell's listen IP. * * @param remoteShellListenIp * String */ public void setRemoteShellListenIp(String remoteShellListenIp) { this.remoteShellListenIp = remoteShellListenIp; } /** * Set the Felix's Remote Shell's listen port. * * @param remoteShellListenPort * int */ public void setRemoteShellListenPort(int remoteShellListenPort) { this.remoteShellListenPort = remoteShellListenPort; } /** * {@inheritDoc} */ @Override public BundleContext getBundleContext() { return framework.getBundleContext(); } /** * {@inheritDoc} */ @Override public Bundle deployBundle(String bundleId, File file) throws FileNotFoundException, BundleException { if (file.isFile() & file.canRead()) { return deployBundle(bundleId != null ? bundleId : file.getName(), new FileInputStream( file)); } return null; } /** * {@inheritDoc} */ @Override public Bundle deployBundle(String bundleId, URL url) throws BundleException, IOException { if (url != null) { return deployBundle(bundleId, url.openStream()); } return null; } /** * Starts an installed bundle. * * @param bundle * Bundle * @throws BundleException */ public void startBundle(Bundle bundle) throws BundleException { if (bundle == null) { LOGGER.warn("Null argument!"); return; } int state = bundle.getState(); if ((state == Bundle.RESOLVED || state == Bundle.INSTALLED) && bundle.getHeaders().get(Constants.FRAGMENT_HOST) == null) { bundle.start(); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Bundle [" + bundle + "] has been started."); } } } /** * Deploys a bundle from an {@link InputStream}. The input stream will be * automatically closed by this method. * * @param bundleId * String * @param is * InputStream * @return Bundle the deployed bundle, or <code>null</code> if not * successful * @throws BundleException */ protected Bundle deployBundle(String bundleId, InputStream is) throws BundleException { return deployBundle(bundleId, is, false); } /** * Deploys a bundle from an {@link InputStream}. The input stream will be * automatically closed by this method. * * @param bundleId * String * @param is * InputStream * @param start * boolean specify if the bundle should be automatically started * after deployment * @return Bundle the deployed bundle, or <code>null</code> if not * successful * @throws BundleException */ protected Bundle deployBundle(String bundleId, InputStream is, boolean start) throws BundleException { try { Bundle bundle = framework.getBundleContext().installBundle(bundleId, is); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Bundle [" + bundle + "] has been deployed."); } if (start) { startBundle(bundle); } return bundle; } finally { IOUtils.closeQuietly(is); } } /** * {@inheritDoc} */ @Override public ServiceReference<?> getServiceReference(String clazz) { return OsgiUtils.getServiceReference(getBundleContext(), clazz); } /** * {@inheritDoc} */ @Override public ServiceReference<?> getServiceReference(String clazz, String query) { return OsgiUtils.getServiceReference(getBundleContext(), clazz, query); } /** * {@inheritDoc} */ @Override public ServiceReference<?> getServiceReference(String clazz, Map<String, String> filter) { return OsgiUtils.getServiceReference(getBundleContext(), clazz, filter); } /** * {@inheritDoc} */ @Override public ServiceReference<?>[] getServiceReferences(String clazz, String query) { return OsgiUtils.getServiceReferences(getBundleContext(), clazz, query); } /** * {@inheritDoc} */ @Override public ServiceReference<?>[] getServiceReferences(String clazz, Map<String, String> filter) { return OsgiUtils.getServiceReferences(getBundleContext(), clazz, filter); } /** * {@inheritDoc} */ @Override public void ungetServiceReference(ServiceReference<?> sref) { OsgiUtils.ungetServiceReference(getBundleContext(), sref); } /** * {@inheritDoc} */ @Override public Object getService(ServiceReference<?> sref) { return OsgiUtils.getService(getBundleContext(), sref); } /** * {@inheritDoc} */ @Override public <T> T getService(ServiceReference<T> sref, Class<T> clazz) { return OsgiUtils.getService(getBundleContext(), sref, clazz); } /** * {@inheritDoc} */ @Override public <T> T getService(Class<T> clazz) { return OsgiUtils.getService(getBundleContext(), clazz); } /** * {@inheritDoc} */ @Override public <T> T getService(Class<T> clazz, Map<String, String> filter) { return OsgiUtils.getService(getBundleContext(), clazz, filter); } /** * {@inheritDoc} */ @Override public <T> T getService(Class<T> clazz, String query) { return OsgiUtils.getService(getBundleContext(), clazz, query); } private File renderOsgiContainerLocation() { String root = servletContext.getRealPath(""); return new File(root, osgiContainerLocation); } private Properties loadConfigProperties() { File configFile = new File(renderOsgiContainerLocation(), OSGI_CONFIG_FILE); Properties configProps = new Properties(); FileInputStream fis = null; try { fis = new FileInputStream(configFile); configProps.load(fis); } catch (IOException e) { LOGGER.error(FATAL, e.getMessage()); throw new RuntimeException(e); } finally { IOUtils.closeQuietly(fis); } IdGenerator idGen = IdGenerator.getInstance(IdGenerator.getMacAddr()); DateFormat df = new SimpleDateFormat("yyyyMMddHHmmss"); Date date = new Date(); String instanceRandomStr = df.format(date) + "_" + System.getProperty(SYSPROP_SPRING_PROFILE, "") + "_" + idGen.generateId48Hex(); // perform variables substitution for system properties. for (Enumeration<?> e = configProps.propertyNames(); e.hasMoreElements();) { String name = (String) e.nextElement(); String value = configProps.getProperty(name); if (value != null) { value = value.replace("${random}", instanceRandomStr); } value = Util.substVars(value, name, null, configProps); configProps.setProperty(name, value); } // configure Felix auto-deploy directory String sAutoDeployDir = configProps.getProperty(AutoProcessor.AUTO_DEPLOY_DIR_PROPERY); if (sAutoDeployDir == null) { throw new RuntimeException("Can not find configuration [" + AutoProcessor.AUTO_DEPLOY_DIR_PROPERY + "] in file " + configFile.getAbsolutePath()); } File fAutoDeployDir = new File(renderOsgiContainerLocation(), sAutoDeployDir); if (LOGGER.isDebugEnabled()) { LOGGER.debug(AutoProcessor.AUTO_DEPLOY_DIR_PROPERY + ": " + fAutoDeployDir.getAbsolutePath()); } configProps.setProperty(AutoProcessor.AUTO_DEPLOY_DIR_PROPERY, fAutoDeployDir.getAbsolutePath()); // configure Felix temp (storage) directory String sCacheDir = configProps.getProperty(Constants.FRAMEWORK_STORAGE); if (sCacheDir == null) { throw new RuntimeException("Can not find configuration [" + Constants.FRAMEWORK_STORAGE + "] in file " + configFile.getAbsolutePath()); } else if (LOGGER.isDebugEnabled()) { LOGGER.debug(Constants.FRAMEWORK_STORAGE + ": " + sCacheDir); } File fCacheDir = new File(sCacheDir); String contextPath = DaspGlobal.getServletContext().getContextPath(); if (contextPath.equals("")) { contextPath = "_"; } fCacheDir = new File(fCacheDir, contextPath); configProps.setProperty(Constants.FRAMEWORK_STORAGE, fCacheDir.getAbsolutePath()); // configure Felix's File Install watch directory String sMonitorDir = configProps.getProperty("felix.fileinstall.dir"); if (sMonitorDir != null) { File fMonitorDir = new File(renderOsgiContainerLocation(), sMonitorDir); configProps.setProperty("felix.fileinstall.dir", fMonitorDir.getAbsolutePath()); if (LOGGER.isDebugEnabled()) { LOGGER.debug("felix.fileinstall.dir: " + fMonitorDir.getAbsolutePath()); } } // configure Felix's Remote Shell listen IP & Port if (remoteShellListenIp != null) { configProps.setProperty("osgi.shell.telnet.ip", remoteShellListenIp); } if (remoteShellListenPort > 0) { configProps .setProperty("osgi.shell.telnet.port", String.valueOf(remoteShellListenPort)); } if (LOGGER.isDebugEnabled()) { LOGGER.debug("Remote Shell: " + remoteShellListenIp + ":" + remoteShellListenPort); } return configProps; } private void autoDeployBundles(Properties configProps, Framework framework) { // make sure bundles are deployed and started in order! File dir = new File(configProps.getProperty(AutoProcessor.AUTO_DEPLOY_DIR_PROPERY)); File[] files = dir.listFiles(); Arrays.sort(files, new Comparator<File>() { @Override public int compare(File file1, File file2) { return file1.compareTo(file2); } }); for (File file : files) { if (file.isDirectory() && !file.getName().startsWith(".")) { autoDeployBundles(file, framework); } } } private void autoDeployBundles(File dir, Framework framework) { // make sure bundles are deployed and started in order! File[] files = dir.listFiles(); Arrays.sort(files, new Comparator<File>() { @Override public int compare(File file1, File file2) { return file1.compareTo(file2); } }); for (File file : files) { if (file.isFile() && file.getAbsolutePath().endsWith(".jar")) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Auto deploying bundle [" + file + "]..."); } try { // Bundle bundle = deployBundle(file.getName(), file); } catch (Exception e) { LOGGER.error(e.getMessage(), e); } } } } private void initFelixFramework() throws BundleException { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Initialzing Apache Felix Framework, OSGi container [" + osgiContainerLocation + "]..."); } class _DaspGlobal extends DaspGlobal { public _DaspGlobal(IOsgiBootstrap osgiBootstrap) { this.setOsgiBootstrap(osgiBootstrap); } } new _DaspGlobal(this); Properties configProps = loadConfigProperties(); Map<String, String> config = new HashMap<String, String>(); for (Entry<Object, Object> entry : configProps.entrySet()) { config.put(entry.getKey().toString(), entry.getValue().toString()); } FrameworkFactory factory = new org.apache.felix.framework.FrameworkFactory(); framework = factory.newFramework(config); framework.init(); AutoProcessor.process(configProps, framework.getBundleContext()); autoDeployBundles(configProps, framework); framework.start(); } private void startAllBundles() { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Starting all bundles..."); } Bundle[] bundles = framework.getBundleContext().getBundles(); Arrays.sort(bundles, new Comparator<Bundle>() { @Override public int compare(Bundle o1, Bundle o2) { long result = o1.getBundleId() - o2.getBundleId(); return result < 0 ? -1 : (result > 0 ? 1 : 0); } }); for (Bundle bundle : bundles) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Before Start: " + bundle.getBundleId() + ":" + bundle.getState() + "/" + bundle.getSymbolicName()); } try { startBundle(bundle); } catch (Exception e) { LOGGER.error(e.getMessage(), e); } if (LOGGER.isDebugEnabled()) { LOGGER.debug("After Start: " + bundle.getBundleId() + ":" + bundle.getState() + "/" + bundle.getSymbolicName()); } } } private IConfigDao configDao; @SuppressWarnings("unchecked") protected void initBundleConfigDao() throws Exception { BundleContext bundleContext = getBundleContext(); String daoClass = bundleContext.getProperty("osgi.dasp.config.dao.class"); Class<IConfigDao> clazz = (Class<IConfigDao>) Class.forName(daoClass); configDao = clazz.newInstance(); configDao.init(bundleContext); bundleContext.registerService(IConfigDao.class, configDao, null); } protected void destroyBundleConfigDao() throws Exception { configDao.destroy(getBundleContext()); } public void init() throws Exception { initFelixFramework(); initBundleConfigDao(); startAllBundles(); } public void destroy() { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Destroying Apache Felix Framework [" + osgiContainerLocation + "]..."); } try { destroyBundleConfigDao(); } catch (Exception e) { LOGGER.error(FATAL, e.getMessage(), e); } try { if (framework != null) { framework.stop(); framework.waitForStop(0); } } catch (Exception e) { LOGGER.error(FATAL, e.getMessage(), e); } } }