org.ow2.petals.microkernel.container.lifecycle.InstallerImpl Maven / Gradle / Ivy
/**
* Copyright (c) 2006-2012 EBM WebSourcing, 2012-2016 Linagora
*
* This program/library is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 2.1 of the License, or (at your
* option) any later version.
*
* This program/library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program/library; If not, see http://www.gnu.org/licenses/
* for the GNU Lesser General Public License version 2.1.
*/
package org.ow2.petals.microkernel.container.lifecycle;
import java.io.IOException;
import java.net.URL;
import java.util.AbstractMap.SimpleImmutableEntry;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.logging.Logger;
import javax.jbi.JBIException;
import javax.jbi.component.Bootstrap;
import javax.jbi.component.Component;
import javax.jbi.management.ComponentLifeCycleMBean;
import javax.jbi.management.LifeCycleMBean;
import javax.jbi.messaging.MessagingException;
import javax.management.ObjectName;
import javax.naming.NamingException;
import org.objectweb.fractal.api.NoSuchInterfaceException;
import org.objectweb.fractal.fraclet.annotations.Interface;
import org.objectweb.fractal.fraclet.annotations.Lifecycle;
import org.objectweb.fractal.fraclet.annotations.Requires;
import org.objectweb.fractal.fraclet.extensions.Controller;
import org.objectweb.fractal.fraclet.types.Step;
import org.ow2.petals.basisapi.exception.PetalsException;
import org.ow2.petals.jbi.descriptor.original.generated.ClassLoaderDelegationType;
import org.ow2.petals.jbi.descriptor.original.generated.Component.SharedLibrary;
import org.ow2.petals.jbi.descriptor.original.generated.Jbi;
import org.ow2.petals.jbi.messaging.PetalsDeliveryChannel;
import org.ow2.petals.microkernel.api.communication.jndi.client.JNDIService;
import org.ow2.petals.microkernel.api.configuration.ConfigurationService;
import org.ow2.petals.microkernel.api.container.ComponentContextCommunication;
import org.ow2.petals.microkernel.api.container.ComponentLifeCycle;
import org.ow2.petals.microkernel.api.container.ContainerService;
import org.ow2.petals.microkernel.api.container.Installer;
import org.ow2.petals.microkernel.api.container.IsolatingThread;
import org.ow2.petals.microkernel.api.container.exception.ContainerServiceException;
import org.ow2.petals.microkernel.api.jbi.component.PetalsComponentContext;
import org.ow2.petals.microkernel.api.jbi.management.AdminService;
import org.ow2.petals.microkernel.api.jbi.management.Context;
import org.ow2.petals.microkernel.api.jbi.messaging.RouterService;
import org.ow2.petals.microkernel.api.jbi.messaging.endpoint.EndpointDirectoryService;
import org.ow2.petals.microkernel.api.jbi.messaging.exception.RoutingException;
import org.ow2.petals.microkernel.api.system.SystemStateService;
import org.ow2.petals.microkernel.api.system.repository.RepositoryService;
import org.ow2.petals.microkernel.container.lifecycle.util.SeparateIsolatingThread;
import org.ow2.petals.microkernel.jbi.component.context.ComponentContextImpl;
import org.ow2.petals.microkernel.jbi.component.context.InstallationContextImpl;
import org.ow2.petals.microkernel.server.exception.InvalidCompBasedArchiException;
import org.ow2.petals.microkernel.system.classloader.ClassLoaderService;
import org.ow2.petals.microkernel.system.classloader.PetalsClassLoader;
import com.ebmwebsourcing.easycommons.log.LoggingUtil;
/**
*
* The synchonisation and ordering of lifecycle actions is managed using synchronization on this and all the actual
* actions on the component implementation rely on the component {@link IsolatingThread}.
*
* @author Adrien Louis - EBM WebSourcing
* @author Olivier Fabre - EBM WebSourcing
* @author wjoseph - EBM WebSourcing
* @author vnoel
*/
@org.objectweb.fractal.fraclet.annotations.Component(provides = @Interface(name = Installer.FRACTAL_SRV_ITF_NAME, signature = Installer.class))
public class InstallerImpl implements Installer, ComponentContextCommunication {
/**
* Bootstrap classloader id prefix.
*/
public static final String BS_PREFIX = "BS";
/**
* Component classloader id prefix.
*/
public static final String COMP_PREFIX = "COMP";
private final LoggingUtil log = new LoggingUtil(Logger.getLogger(Installer.COMPONENT_LOGGER_NAME));
/**
* JBI-Management :: Admin Service
*/
@Requires(name = "admin")
private AdminService adminService;
/**
* Platform :: Loader Service
*/
@Requires(name = "classloader")
private ClassLoaderService classloaderService;
/**
* JBI-Messaging :: Router Service
*/
@Requires(name = "router")
private RouterService router;
/**
* Platform :: Repository Service
*/
@Requires(name = "repository")
private RepositoryService repositoryService;
/**
* JBI-Messaging :: EndpointRegistry
*/
@Requires(name = "endpointDirectory")
private EndpointDirectoryService endpointDirectory;
/**
* Communication :: JNDIService
*/
@Requires(name = "jndi")
private JNDIService jndiService;
/**
* Container :: Container Service
*/
@Requires(name = "container")
private ContainerService container;
/**
* Platform component :: SystemState Service
*/
@Requires(name = "systemstate")
private SystemStateService recoverySrv;
/**
* The Configuration service fractal component
*/
@Requires(name = "configuration")
private ConfigurationService configurationService;
/**
* The Fractal component
*/
@Controller
private org.objectweb.fractal.api.Component fractalComponent;
private Context context;
private String state;
private Jbi jbiDescriptor;
private URL installationRootURL;
private String installationRootPath;
private String workingRootPath;
private InstallationContextImpl installContext;
private PetalsClassLoader bootstrapClassLoader;
private PetalsComponentContext componentContext;
private org.ow2.petals.jbi.descriptor.original.generated.Component componentDescription;
private String componentName;
private Bootstrap jbiBootstrap;
private boolean bootstrapInitialised = false;
private SeparateIsolatingThread separateThread;
private boolean firstStart = true;
/**
* Create the Installer that will manage installation / uninstallation of
* the JBI component. The related bootstrap is created and initialize here.
*/
public InstallerImpl() {
super();
this.state = UNINSTALLED;
}
@Override
public void init(final Context ctxt) throws IOException, JBIException, NamingException {
this.context = ctxt;
this.state = UNINSTALLED;
this.jbiDescriptor = this.context.getDescriptor();
this.componentDescription = this.jbiDescriptor.getComponent();
this.componentName = this.componentDescription.getIdentification().getName();
this.installationRootPath = this.context.getInstallFile().getPath();
this.installationRootURL = this.context.getInstallFile().toURI().toURL();
this.workingRootPath = this.repositoryService.getComponentWorkDirectory(this.componentName)
.getAbsolutePath();
// 1. create the component context
try {
this.componentContext = this.createComponentContext();
} catch (final InvalidCompBasedArchiException e) {
throw new JBIException(e);
}
// 2. create the bootstrap classloader
this.bootstrapClassLoader = this.createBootstrapClassloader();
// 3. create the bootstrap object
this.jbiBootstrap = this.loadBootstrap(this.bootstrapClassLoader);
// 4. create the component executor
this.separateThread = new SeparateIsolatingThread("Component Lifecycle Thread (" + this.componentName + ")",
this.configurationService.getContainerConfiguration().getTaskTimeout(), this.log);
// 5. create installation context
this.installContext = new InstallationContextImpl(this.componentDescription,
this.componentContext, true);
// 7. init bootstrap
this.initBootstrap(true);
}
@Override
public synchronized ObjectName getInstallerConfigurationMBean() throws JBIException {
if (!bootstrapInitialised) {
throw new JBIException("Can't get the Extension MBean for an unloaded installer.");
}
return this.separateThread.execute(new Callable() {
@Override
public ObjectName call() throws Exception {
return jbiBootstrap.getExtensionMBeanName();
}
}, this.bootstrapClassLoader);
}
@Override
public String getInstallRoot() {
return this.componentContext.getInstallRoot();
}
@Override
public synchronized ObjectName install() throws JBIException {
this.log.start();
try {
// create the component life cycle
return this.doInstall().getKey();
} catch (final Exception e) {
this.log.error("Can't install component", e);
if (e instanceof JBIException) {
throw (JBIException) e;
} else {
throw new JBIException(e);
}
} finally {
this.log.end();
}
}
@Override
public synchronized ComponentLifeCycle installInternal() throws JBIException {
return this.doInstall().getValue();
}
/**
* Uninstall the JBI component. Call the Bootstrap onUninstall() and
* cleanup() methods, unregister the jbiComponent object,
*
* @throws JBIException
* the component is already uninstalled
*/
@Override
public synchronized void uninstall() throws JBIException {
this.log.start();
try {
// If the component has been forced shutdown, then it may have had some SUs left!
// TODO is that a situation where we will never be able to uninstall the component then?!?!
if (!this.container.getServiceUnitsLifeCyclesByComponent(this.componentName).isEmpty()) {
throw new JBIException("Cannot uninstall a component which holds deployed Service Unit(s)");
}
final ComponentLifeCycleMBean componentLifeCycle = this.container
.getComponentLifecycleByName(this.componentName);
if (componentLifeCycle == null) {
throw new JBIException("The component '" + this.componentName
+ "' can not be uninstalled as it is not installed!");
}
String componentState = componentLifeCycle.getCurrentState();
if ((INSTALLED.equals(this.state)) && (LifeCycleMBean.SHUTDOWN.equals(componentState)
|| LifeCycleMBean.UNKNOWN.equals(componentState))) {
this.doUninstall();
this.setState(UNINSTALLED);
} else {
throw new JBIException("The component '" + this.componentName
+ "' can not be uninstalled in the Installer state: " + this.state
+ " and Component state: " + componentState);
}
} catch (JBIException e) {
this.log.error(e.getMessage(), e);
throw e;
}
this.log.info("Component '" + this.componentName + "' uninstalled");
this.log.end();
}
@Override
public synchronized boolean isInstalled() {
return INSTALLED.equals(this.state);
}
@Override
public String getComponentName() {
return this.componentName;
}
@Override
public PetalsComponentContext getComponentContext() {
return this.componentContext;
}
@Override
public IsolatingThread getComponentThread() {
return this.separateThread;
}
@Override
public EndpointDirectoryService getEndpointDirectory() {
return this.endpointDirectory;
}
@Override
public String getInstallationRoot() {
return this.installationRootPath;
}
@Override
public Jbi getJBIDescriptor() {
return this.jbiDescriptor;
}
@Override
public AdminService getAdminService() {
return this.adminService;
}
@Override
public ContainerService getContainerService() {
return this.container;
}
@Override
public RouterService getRouterService() {
return this.router;
}
@Override
public ConfigurationService getConfigurationService() {
return this.configurationService;
}
@Override
public JNDIService getJNDIService() {
return this.jndiService;
}
@Override
public String getWorkspaceRoot() {
return this.workingRootPath;
}
@Override
public Component getComponent() {
return this.componentContext.getComponent();
}
@Override
public void initBootstrap(boolean install) throws JBIException {
this.installContext.setInstall(install);
try {
this.bootstrapInit();
} catch (final Exception e) {
try {
// for init we execute it only in case of problems!
this.bootstrapCleanUp();
} catch (final Exception ex) {
e.addSuppressed(ex);
}
throw e;
}
}
/**
* Start of the petals component
*
* @throws PetalsException
* @throws JBIException
*
*/
@Lifecycle(step = Step.START)
public void startFractalComponent() throws JBIException {
this.log.start();
if (!firstStart) {
// the first start is handled by init
// Note: this method is never really called after stopFractalComponent has been called,
// but the following is present for coherence
this.separateThread.initialize();
this.bootstrapClassLoader = createBootstrapClassloader();
this.bootstrapInit();
} else {
firstStart = false;
}
this.log.end();
}
/**
* Stop of the petals component
*
*/
@Lifecycle(step = Step.STOP)
public void stopFractalComponent() {
this.log.start();
try {
this.bootstrapCleanUp();
} catch (final Exception e) {
log.warning("Can't cleanup the installer on shutdown", e);
}
// unload bootstrap classloader
this.classloaderService.deleteClassLoader(InstallerImpl.BS_PREFIX + this.componentName);
this.bootstrapClassLoader = null;
this.separateThread.shutdown();
this.log.end();
}
/**
* Set the State
reference of the current state. An
* LifeCycleStateEventAbstract
is thrown.
*
* @param state
* @throws JBIException
*/
private void setState(String state) throws JBIException {
this.state = state;
try {
this.recoverySrv.updateComponentInstallationState(this.getComponentName(), state);
} catch (Exception e) {
throw new JBIException("Installer state can't be persisted for recovery", e);
}
}
/**
*
* Create the classLoader for Bootstrap
*
*
* The bootstrap classloader depends on share library classloader to be able to check configuration parameter as
* JDBC Driver, JMS Driver provided through SL.
*
*
* @return The bootstrap classloader.
*/
private PetalsClassLoader createBootstrapClassloader() {
this.log.call();
final List classpath = this.componentDescription.getBootstrapClassPath().getPathElement();
// Shared libraries used by this component
final List slList = this.componentDescription.getSharedLibraryList();
final List slNameList = new ArrayList();
if (slList != null) {
for (final SharedLibrary sl : slList) {
slNameList.add(sl.getContent().trim() + "-" + sl.getVersion());
}
}
final boolean parentFirst = !ClassLoaderDelegationType.SELF_FIRST
.equals(this.componentDescription.getBootstrapClassLoaderDelegation());
return this.createClassLoader(BS_PREFIX, classpath, parentFirst, slNameList);
}
/**
* Create a classloader with the specified parameters.
*
* @param prefix
* bootstrap or component prefix
* @param classpath
* classpath of the bootstrap/component
* @param parentFirst
* delegation mode
* @param slNameList
* list of the name of the required sharedLibraries
* @return a classloader, or null
if an error occurs.
*/
private PetalsClassLoader createClassLoader(final String prefix, final List classpath,
final boolean parentFirst, final List slNameList) {
this.log.start();
// prepare bootstrap and component classloader
final URL[] baseUrls = { this.installationRootURL };
try {
return (PetalsClassLoader) this.classloaderService.createComponentClassLoader(
prefix + this.componentName, baseUrls, classpath, parentFirst, slNameList);
} catch (final PetalsException pe) {
// just log when some error occur
this.log.error("Failed to create bootstrap class loader" + pe.getMessage(), pe);
return null;
} finally {
this.log.end();
}
}
/**
* Create the classloader for the component. Be careful, we use the
* classpath set in the InstallationContext. (during the installation phase,
* the Bootstrap can modify the component's classpath by setting it in the
* InstallationContext object.)
*
* @return classloader for the component
*/
private PetalsClassLoader createComponentClassloader() {
this.log.start();
final List classpath = this.installContext.getClassPathElements();
// Delegation model
final boolean parentFirst = !ClassLoaderDelegationType.SELF_FIRST
.equals(this.componentDescription.getComponentClassLoaderDelegation());
// Shared libraries used by this component
final List slList = this.componentDescription.getSharedLibraryList();
final List slNameList = new ArrayList();
if (slList != null) {
for (final SharedLibrary sl : slList) {
slNameList.add(sl.getContent().trim() + "-" + sl.getVersion());
}
}
final PetalsClassLoader componentClassloader = this.createClassLoader(COMP_PREFIX, classpath,
parentFirst, slNameList);
this.log.end();
return componentClassloader;
}
/**
* Register the installer engine/binding in the life cycle manager service
*
* @return
* @throws JBIException
* @throws PetalsException
*/
private Map.Entry doInstall() throws JBIException {
this.log.start();
try {
if (UNINSTALLED.equals(this.state)) {
try {
// install the boostrap
this.bootstrapOnInstall();
} finally {
try {
this.bootstrapCleanUp();
} catch (final Exception e) {
log.warning("Can't cleanup the installer on install", e);
}
}
// 1. create component classloader
final PetalsClassLoader cl = this.createComponentClassloader();
// 2. create the component object
final Component jbiComponent = this.loadComponent(cl);
// 3. set the jbiComponent reference to the contextImpl
this.componentContext.setComponent(jbiComponent);
// 4. Creation of the life cycle
final ComponentLifeCycle clc = this.container.createComponentLifeCycle(this.componentDescription);
// 5. inform the router about the component installation
this.router.addComponent(this.componentContext);
final ObjectName mbean = this.container.registerComponentLifeCycleMBean(this.componentName);
this.setState(INSTALLED);
this.log.info("Component '" + this.componentName + "' installed");
return new SimpleImmutableEntry<>(mbean, clc);
} else {
throw new JBIException("The Installer for '" + this.componentName
+ "' can not be installed in this state: " + this.state);
}
} catch (RoutingException | ContainerServiceException e) {
throw new JBIException(e);
} finally {
this.log.end();
}
}
private PetalsComponentContext createComponentContext() throws InvalidCompBasedArchiException, NamingException {
try {
final PetalsComponentContext componentContextImpl = new ComponentContextImpl(
(ComponentContextCommunication) this.fractalComponent.getFcInterface("/content"));
this.context.setComponentContext(componentContextImpl);
return componentContextImpl;
} catch (final NoSuchInterfaceException e) {
throw new InvalidCompBasedArchiException(e);
}
}
/**
* load the bootstrap, with its own classloader
*
* @return
*/
private Bootstrap loadBootstrap(PetalsClassLoader classloader) throws JBIException {
this.log.call();
Class bootstrapClass;
Bootstrap jbiBootstrapTmp = null;
try {
bootstrapClass = classloader.loadClass(this.componentDescription
.getBootstrapClassName().trim());
// if it is null there is a bug somewhere: loadClass should throw an exception
assert bootstrapClass != null;
jbiBootstrapTmp = (Bootstrap) bootstrapClass.newInstance();
} catch (final ClassNotFoundException e) {
throw new JBIException("Class not found: "
+ this.componentDescription.getBootstrapClassName(), e);
} catch (final InstantiationException e) {
throw new JBIException("Could not instantiate : "
+ this.componentDescription.getBootstrapClassName(), e);
} catch (final IllegalAccessException e) {
throw new JBIException("Illegal access on: "
+ this.componentDescription.getBootstrapClassName(), e);
} catch (final SecurityException e) {
// this can happen because we use URLClassLoaders
throw new JBIException("Security exception on: "
+ this.componentDescription.getComponentClassName(), e);
}
return jbiBootstrapTmp;
}
/**
* Load the component, within its own classloader
*
* @return the component
*/
private Component loadComponent(PetalsClassLoader classloader) throws JBIException {
this.log.call();
Component jbiComponent;
Class componentClass;
try {
componentClass = classloader.loadClass(this.componentDescription
.getComponentClassName().getContent().trim());
// if it is null there is a bug somewhere: loadClass should throw an exception
assert componentClass != null;
jbiComponent = (Component) componentClass.newInstance();
} catch (final ClassNotFoundException e) {
throw new JBIException("Class not found: "
+ this.componentDescription.getComponentClassName(), e);
} catch (final InstantiationException e) {
throw new JBIException("Could not instantiate : "
+ this.componentDescription.getComponentClassName(), e);
} catch (final IllegalAccessException e) {
throw new JBIException("Illegal access on: "
+ this.componentDescription.getComponentClassName(), e);
} catch (final SecurityException e) {
// this can happen because we use URLClassLoaders
throw new JBIException("Security exception on: "
+ this.componentDescription.getComponentClassName(), e);
}
return jbiComponent;
}
/**
* Do the bootstrap uninstallation. All the exceptions occurred are logged.
* If errors occurs, only the first exception will be thrown.
*
* @throws JBIException
* : the first error generated
*/
private void doUninstall() {
this.log.start();
// Ensure that the DeliveryChannel of the component is closed
// (if the component is in an unknown state, for instance)
// This will also deactivate all endpoints and reinject left-over exchanges back into the NMR.
try {
final PetalsDeliveryChannel channel = this.componentContext.getDeliveryChannelInstance();
if ((channel != null) && channel.isOpened()) {
this.log.debug("close the component DeliveryChannel");
channel.close();
}
} catch (MessagingException e) {
this.log.error("Unable to close the delivery channel: ", e);
}
// call uninstall on bootstrap
try {
this.bootstrapOnUninstall();
} catch (final Exception e) {
this.log.error("Unable to uninstall the bootstrap: ", e);
} finally {
try {
// clean the bootstrap thread
this.bootstrapCleanUp();
} catch (final Exception e) {
this.log.error("Unable to cleanup the bootstrap: ", e);
}
}
// unregister the component life-cycle mbean
try {
this.container.unregisterComponentLifeCycleMBean(this.componentName);
} catch (final ContainerServiceException e) {
this.log.error("Unable to unregister the component lifecycle: ", e);
}
// unregister the optional extension mbean
try {
if (this.componentContext.getComponent().getLifeCycle() != null) {
ObjectName extName = this.componentContext.getComponent().getLifeCycle()
.getExtensionMBeanName();
if (extName != null) {
this.adminService.getMBeanServer().unregisterMBean(extName);
}
}
} catch (Exception e) {
this.log.error("Unable to unregister the extension of the component: ", e);
}
// inform the router about the component uninstallation
try {
this.router.removeComponent(this.componentContext);
} catch (RoutingException e) {
this.log.error("Unable to inform the router about the component uninstallation: ", e);
}
// remove the fractal component
try {
this.container.removeComponentLifeCycle(this.componentName);
} catch (PetalsException e) {
this.log.error("Unable to remove the fractal component: ", e);
}
// clean the component context
this.componentContext.clear();
// unload component classloader
this.classloaderService.deleteClassLoader(InstallerImpl.COMP_PREFIX + this.componentName);
this.log.end();
}
private void bootstrapCleanUp() throws JBIException {
this.bootstrapInitialised = false;
this.separateThread.execute(new Callable