package ddth.dasp.framework.osgi;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.List;
import java.util.Map.Entry;
import java.util.Properties;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.ServiceRegistration;
import org.osgi.framework.Version;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.ViewResolver;
import ddth.dasp.common.osgi.IBundleAwareService;
import ddth.dasp.common.osgi.IRequireCleanupService;
/**
* This {@link BundleActivator} does the following:
* <ul>
* <li>On bundle start:
* <ul>
* <li>Saves instance of bundle context and bundle for latter use. See
* {@link #setBundleContext(BundleContext)} and {@link #setBundle(Bundle)}.</li>
* <li>Builds bundle's properties. See {@link #setProperties(Properties)}.</li>
* <li>Calls {@link #registerSpringMvcHandlerMapping()} and
* {@link #registerSpringMvcViewResolver()}.</li>
* <li>Calls {@link #registerServices()}.</li>
* </ul>
* </li>
* <li>On bundle stop:
* <ul>
* <li>Calls {@link #unregisterServices()}.</li>
* </ul>
* </li>
* </ul>
*
* @author NBThanh <btnguyen2k@gmail.com>
*/
public abstract class BaseBundleActivator implements BundleActivator {
private final Logger LOGGER = LoggerFactory.getLogger(BaseBundleActivator.class);
private BundleContext bundleContext;
private Bundle bundle;
private Properties properties;
private List<ServiceRegistration<?>> registeredServices = new LinkedList<ServiceRegistration<?>>();
private File bundleExtractDir;
/**
* Gets name of the associated bundle.
*
* This method returns the bundle's symbolic name.
*
* @return
*/
public String getBundleName() {
return bundle.getSymbolicName();
}
protected BundleContext getBundleContext() {
return bundleContext;
}
protected void setBundleContext(BundleContext bundeContext) {
this.bundleContext = bundeContext;
}
protected Bundle getBundle() {
return bundle;
}
protected void setBundle(Bundle bundle) {
this.bundle = bundle;
}
protected Properties getProperties() {
return properties;
}
protected void setProperties(Properties properties) {
this.properties = properties;
}
/**
* Extracts content from the bundle to a directory.
*
* @param bundleRootPath
* @param toDirRoot
* @throws IOException
*/
protected void extractBundleContent(String bundleRootPath, String toDirRoot) throws IOException {
File toDir = new File(toDirRoot);
if (!toDir.isDirectory()) {
throw new RuntimeException("[" + toDir.getAbsolutePath()
+ "] is not a valid directory or does not exist!");
}
// toDir = new File(toDir, String.valueOf(bundle.getBundleId()));
toDir = new File(toDir, getModuleName());
toDir = new File(toDir, bundle.getVersion().toString());
FileUtils.forceMkdir(toDir);
bundleExtractDir = toDir;
Enumeration<String> entryPaths = bundle.getEntryPaths(bundleRootPath);
if (entryPaths != null) {
while (entryPaths.hasMoreElements()) {
extractContent(bundleRootPath, entryPaths.nextElement(), bundleExtractDir);
}
}
}
private void extractContent(String pathPrefix, String path, File rootDir) throws IOException {
if (path.endsWith("/")) {
extractContentDir(pathPrefix, path, rootDir);
} else {
extractContentFile(pathPrefix, path, rootDir);
}
}
private void extractContentDir(String pathPrefix, String path, File rootDir) throws IOException {
File dir = new File(rootDir, path.substring(pathPrefix.length()));
FileUtils.forceMkdir(dir);
Enumeration<String> entryPaths = bundle.getEntryPaths(path);
if (entryPaths != null) {
while (entryPaths.hasMoreElements()) {
extractContent(pathPrefix, entryPaths.nextElement(), bundleExtractDir);
}
}
}
private void extractContentFile(String pathPrefix, String path, File rootDir)
throws IOException {
URL source = bundle.getResource(path);
File destination = new File(rootDir, path.substring(pathPrefix.length()));
FileUtils.copyURLToFile(source, destination);
}
/**
* {@inheritDoc}
*/
@Override
public void start(BundleContext bundleContext) throws Exception {
try {
internalBundleStart(bundleContext);
} catch (Exception e) {
stop(bundleContext);
throw e;
}
}
/**
* Called by {@link #start(BundleContext)}. Sub-classes should only override
* this method instead of {@link #start(BundleContext)}.
*
* @param bundleContext
* @throws Exception
*/
protected void internalBundleStart(BundleContext bundleContext) throws Exception {
setBundleContext(bundleContext);
setBundle(bundleContext.getBundle());
long myId = this.bundle.getBundleId();
String myName = this.bundle.getSymbolicName();
Bundle[] currentBundles = bundleContext.getBundles();
for (Bundle bundle : currentBundles) {
if (myId != bundle.getBundleId() && myName.equals(bundle.getSymbolicName())) {
// found another version of me
handlerAnotherVersionAtStartup(bundle);
}
}
Properties props = new Properties();
props.put("Version", bundle.getVersion().toString());
String moduleName = getModuleName();
if (!StringUtils.isEmpty(moduleName)) {
props.put("Module", moduleName);
}
setProperties(props);
registerSpringMvcHandlerMapping();
registerSpringMvcViewResolver();
registerServices();
}
/**
* Called when another version of this bundle is found in the bundle
* context.
*
* This method stops the other bundle if it's an old version of the current
* bundle. Sub-class may override this method to implement its own business
* logic.
*
* @param bundle
* @throws BundleException
*/
protected void handlerAnotherVersionAtStartup(Bundle bundle) throws BundleException {
Version myVersion = this.bundle.getVersion();
Version otherVersion = bundle.getVersion();
if (myVersion.compareTo(otherVersion) > 0) {
String msg = "Found an older version of me [" + bundle + "], stopping it!";
LOGGER.info(msg);
bundle.stop();
}
}
/**
* {@inheritDoc}
*/
@Override
public void stop(BundleContext bundleContext) throws Exception {
if (bundleExtractDir != null) {
try {
FileUtils.deleteQuietly(bundleExtractDir);
} catch (Exception e) {
LOGGER.warn(e.getMessage(), e);
}
}
try {
unregisterServices();
} catch (Exception e) {
LOGGER.warn(e.getMessage(), e);
}
try {
internalBundleStop(bundleContext);
} catch (Exception e) {
LOGGER.warn(e.getMessage(), e);
}
}
/**
* Called by {@link #stop(BundleContext)}. Sub-classes should only override
* this method instead of {@link #stop(BundleContext)}.
*
* @param bundleContext
* @throws Exception
*/
protected void internalBundleStop(BundleContext bundleContext) throws Exception {
}
/**
* Registers module's Spring's {@link HandlerMapping}.
*/
protected void registerSpringMvcHandlerMapping() {
String moduleName = getModuleName();
HandlerMapping handlerMapping = getSpringMvcHandlerMapping();
if (StringUtils.isEmpty(moduleName) || handlerMapping == null) {
return;
}
registerService(HandlerMapping.class.getName(), handlerMapping, getProperties());
}
/**
* Registers module's Spring's {@link ViewResolver}.
*/
protected void registerSpringMvcViewResolver() {
String moduleName = getModuleName();
ViewResolver viewResolver = getSpringMvcViewResolver();
if (StringUtils.isEmpty(moduleName) || viewResolver == null) {
return;
}
registerService(ViewResolver.class.getName(), viewResolver, getProperties());
}
/**
* Gets the module's name.
*
* This method simply returns <code>null</code>.
*
* @return
*/
protected String getModuleName() {
return null;
}
/**
* Gets the module's SpringMVC's {@link HandlerMapping}.
*
* This method simply returns <code>null</code>.
*
* @return
*/
protected HandlerMapping getSpringMvcHandlerMapping() {
return null;
}
/**
* Gets the module's SpringMVC's {@link ViewResolver}.
*
* This method simply returns <code>null</code>.
*
* @return
*/
protected ViewResolver getSpringMvcViewResolver() {
return null;
}
protected List<ServiceInfo> getServiceInfoList() {
return null;
}
/**
* Registers OSGi services provided by this module.
*/
protected void registerServices() {
List<ServiceInfo> serviceInfoList = getServiceInfoList();
if (serviceInfoList == null || serviceInfoList.size() == 0) {
return;
}
for (ServiceInfo serviceInfo : serviceInfoList) {
Properties props = new Properties();
props.putAll(getProperties());
props.putAll(serviceInfo.getProperties());
registerService(serviceInfo.getClassName(), serviceInfo.getService(), props);
}
}
/**
* Unregisters registered services. Services registered via
* {@link #registerService(String, Object, Properties)} will be
* unregistered.
*
* This method is automatically called by {@link #stop(BundleContext)}.
*
*/
protected void unregisterServices() {
for (ServiceRegistration<?> sr : registeredServices) {
try {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Unregistering service [" + sr + "]...");
}
Object service = bundleContext.getService(sr.getReference());
if (service instanceof IRequireCleanupService) {
((IRequireCleanupService) service).destroy();
}
} catch (Exception e) {
LOGGER.warn(e.getMessage(), e);
} finally {
sr.unregister();
}
}
}
/**
* Convenient method for sub-class to register one service.
*
* @param className
* String
* @param service
* Object
* @param props
* properties
* @return ServiceRegistration
*/
protected ServiceRegistration<?> registerService(String className, Object service,
Properties props) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Registering service [" + className + "] with properties " + props);
}
if (service instanceof IBundleAwareService) {
((IBundleAwareService) service).setBundle(bundle);
}
// ServiceRegistration sr = bundleContext.registerService(name, service,
// props);
Dictionary<String, Object> dProps = new Hashtable<String, Object>();
for (Entry<Object, Object> entry : props.entrySet()) {
dProps.put(entry.getKey().toString(), entry.getValue());
}
ServiceRegistration<?> sr = bundleContext.registerService(className, service, dProps);
if (sr != null) {
registeredServices.add(sr);
}
return sr;
}
}