/* * 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 org.apache.ode.jbi.osgi.deployer; import java.io.File; import java.io.FileWriter; import java.io.FilenameFilter; import java.io.InputStream; import java.lang.reflect.Field; import java.net.URL; import java.util.Enumeration; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import javax.jbi.component.Component; import javax.jbi.component.ComponentLifeCycle; import javax.jbi.component.ServiceUnitManager; import javax.management.MBeanServer; import javax.management.ObjectName; import javax.xml.namespace.QName; import org.apache.commons.io.IOUtils; import org.apache.ode.bpel.iapi.ProcessStore; import org.apache.ode.jbi.OdeContext; import org.osgi.framework.Bundle; public class OdeDeployedBundle { private static final Logger LOG = Logger.getLogger(OdeDeployedBundle.class.getName()); private boolean updated; private Bundle bundle; private OdeExtenderImpl odeRegistrationService; private File rootDir; private String duName; private String name; OdeDeployedBundle(Bundle bundle, OdeExtenderImpl odeRegistrationService) { if (LOG.isLoggable(Level.FINE)) LOG.fine("Initialized ODE service unit deployer for bundle: " + bundle.getSymbolicName()); this.bundle = bundle; this.odeRegistrationService = odeRegistrationService; } String getName() { if (this.name == null) this.name = this.bundle.getSymbolicName(); return this.name; } private String getDUName() { if (this.duName == null) this.duName = getName() + "-" + bundle.getVersion().getMicro(); return this.duName; } private File getRootDir() { if (this.rootDir == null && this.bundle.getBundleContext() != null) this.rootDir = this.bundle.getBundleContext().getDataFile("bpelData/" + getDUName()); return this.rootDir; } void doStart() throws Exception { // If we are already started, don't bother starting again. LOG.info("Starting ODE service unit: " + getName()); // Wait until ODE is available before starting. waitAvailable(); // Do we need to undeploy first? boolean needUpdate = updated; boolean forceDeploy = needUpdate; this.updated = false; // Do deploy. this.deploy(this.bundle, this.odeRegistrationService.getOdeComponent().getServiceUnitManager(), forceDeploy, needUpdate); } void doStop() throws Exception { LOG.info("Stopping ODE service unit: " + getName()); this.shutdown(this.bundle, this.odeRegistrationService.getOdeComponent().getServiceUnitManager()); } void doInstall() throws Exception { LOG.info("Installing ODE service unit: " + getName()); } void doUninstall() throws Exception { LOG.info("Uninstalling ODE service unit: " + getName()); this.undeploy(this.bundle, this.odeRegistrationService.getOdeComponent().getServiceUnitManager()); } void doUpdate() throws Exception { LOG.info("Updating ODE service unit: " + getName()); // We simply mark the update state of this bundle so that on next start, we do a redeploy updated = true; } private void deploy(Bundle bundle, ServiceUnitManager suM, boolean forceDeploy, boolean undeploy) throws Exception { // Do deployment. File rootDir = getRootDir(); String duName = getDUName(); boolean needDeploy = rootDir.mkdirs() || forceDeploy; if (LOG.isLoggable(Level.FINE)) LOG.fine("Exploding content to " + rootDir + " for " + duName); Enumeration<?> en = bundle.findEntries("/", "*", false); while (en.hasMoreElements()) needDeploy |= copyOne(rootDir, (URL) en.nextElement()); // Now start it. ClassLoader l = Thread.currentThread().getContextClassLoader(); try { ClassLoader suL = suM.getClass().getClassLoader(); Thread.currentThread().setContextClassLoader(new BundleClassLoader(suL, bundle)); try { // Try first an init/start, which will fail if the process isn't // here. if (needDeploy) { // If deployed, undeploy first. if (undeploy && isDeployed(duName)) { // Do the undeploy to service unit manager. LOG.info("Performing undeploy " + duName + " from dir " + rootDir); suM.undeploy(duName, rootDir.getAbsolutePath()); // Now, remove any .cbp files. File[] cbpFiles = rootDir.listFiles(new FilenameFilter() { public boolean accept(File dir, String name) { return name.endsWith(".cbp"); } }); for (File cbpFile : cbpFiles) { LOG.info("Removing compiled bpel process: " + cbpFile); cbpFile.delete(); } } LOG.info("Deploying " + duName + " to dir " + rootDir); suM.deploy(duName, rootDir.getAbsolutePath()); } suM.init(duName, rootDir.getAbsolutePath()); suM.start(duName); } catch (javax.jbi.management.DeploymentException e) { LOG.log(Level.WARNING, "Deploy failed; could not deploy/start this bundle: " + this.getName(), e); throw e; } } finally { Thread.currentThread().setContextClassLoader(l); } } private boolean isDeployed(String processName) { boolean Result = true; try { // Get the "ProcessStore" interface by grabbing the internal field // of OdeContext and querying for the processes. Could also use PMAPI here, // but in any case we just need to know if the process exists in a deployed state. // // TODO: add a OdeContext.getStore() method to clean this up. OdeContext inst = OdeContext.getInstance(); Field _store = inst.getClass().getDeclaredField("_store"); _store.setAccessible(true); ProcessStore ps = (ProcessStore) _store.get(inst); List<QName> processes = ps.listProcesses(processName); Result = processes != null && !processes.isEmpty(); } catch (Exception e) { LOG.log(Level.WARNING, "Could not determine deployment state for process: " + processName, e); } return Result; } private boolean copyOne(File dest, URL url) throws Exception { File d = new File(dest, url.getPath()); boolean needDeploy = !d.exists(); long length = d.exists() ? d.length() : 0L; InputStream str = url.openStream(); if (str != null) { FileWriter wr = new FileWriter(d); try { IOUtils.copy(str, wr); } finally { wr.flush(); wr.close(); } // If file is zero-length (which is the case handling a directory), // just remove it. if (d.exists() && d.length() == 0) { d.delete(); needDeploy = false; } else needDeploy |= length != d.length(); } return needDeploy; } private void shutdown(Bundle bundle, ServiceUnitManager suM) throws Exception { String duName = getDUName(); if (suM != null) { suM.stop(duName); suM.shutDown(duName); } else { LOG.warning("Could not shutdown this process (" + duName + ") because ode component was never located"); } } private void undeploy(Bundle bundle, ServiceUnitManager suM) throws Exception { String duName = getDUName(); if (suM != null) { if (isDeployed(duName)) { // Use ODE's classloader to avoid class loading from the bundle // being undeployed. ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); try { Thread.currentThread().setContextClassLoader(suM.getClass().getClassLoader()); File rootDir = getRootDir(); LOG.info("Performing undeploy " + duName + " from dir " + rootDir); suM.undeploy(duName, rootDir.getAbsolutePath()); } finally { Thread.currentThread().setContextClassLoader(classLoader); } } } else { LOG.warning("Could not shutdown this process (" + duName + ") because ode component was never located"); } } public class BundleClassLoader extends ClassLoader { private final Bundle delegate; public BundleClassLoader(ClassLoader parent, Bundle delegate) { super(parent); this.delegate = delegate; } @Override public Class<?> loadClass(String name) throws ClassNotFoundException { try { return getParent().loadClass(name); } catch (Exception e) { return delegate.loadClass(name); } } } private void waitAvailable() throws InterruptedException { /** * We need to wait until the service unit manager is available before * proceeding. Also, since the ode component itself does an asynchronous * start with respect to this bundle, we need to wait until it is done * initializing. This would be much cleaner if we could simply * call "isStarted" on OdeLifeCycle, which maintains a started state. * * If we do not wait until the ode component is started, deployments * will fail sporadically because of asynchronous start race conditions. */ boolean showedWait = false; while (this.odeRegistrationService.getOdeComponent().getServiceUnitManager() == null || !isStarted(this.odeRegistrationService.getOdeComponent())) { // Do a wait. if (!showedWait) { LOG.info("Waiting for ODE to arrive (" + getName() + ")..."); showedWait = true; } Thread.sleep(500L); } } private boolean isStarted(Component odeComponent) { boolean Result = true; try { // Get the ODE component started state by grabbing the internal field // of OdeLifeCycle. We cannot rely on the started state of the ODE // component bundle. // // TODO: add OdeLifeCycle.isStarted() and do a cast of odeComponent.getLifeCycle() accordingly. ComponentLifeCycle inst = odeComponent.getLifeCycle(); Field _started = inst.getClass().getDeclaredField("_started"); _started.setAccessible(true); Result = Boolean.TRUE.equals(_started.get(inst)); } catch (Exception e) { LOG.log(Level.WARNING, "Could not determine started state for ode component", e); } return Result; } }