All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.ow2.petals.microkernel.container.lifecycle.InstallerImpl Maven / Gradle / Ivy

There is a newer version: 4.3.0
Show newest version
/**
 * 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() { @Override public Object call() throws Exception { InstallerImpl.this.jbiBootstrap.cleanUp(); return null; } }, this.bootstrapClassLoader); } private void bootstrapOnUninstall() throws JBIException { this.separateThread.execute(new Callable() { @Override public Object call() throws Exception { InstallerImpl.this.jbiBootstrap.onUninstall(); return null; } }, this.bootstrapClassLoader); } private void bootstrapOnInstall() throws JBIException { this.separateThread.execute(new Callable() { @Override public Object call() throws Exception { InstallerImpl.this.jbiBootstrap.onInstall(); return null; } }, this.bootstrapClassLoader); } private void bootstrapInit() throws JBIException { this.separateThread.execute(new Callable() { @Override public Object call() throws Exception { InstallerImpl.this.jbiBootstrap.init(InstallerImpl.this.installContext); return null; } }, this.bootstrapClassLoader); this.bootstrapInitialised = true; } }