/** * Copyright (C) 2008 Progress Software, Inc. All rights reserved. * http://fusesource.com * * The software in this package is published under the terms of the AGPL license * a copy of which has been included with this distribution in the license.txt file. */ package org.fusesource.cloudmix.agent.jbi; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.StringReader; import java.net.URI; import java.net.URL; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import javax.management.InstanceNotFoundException; import javax.management.MBeanException; import javax.management.MBeanServer; import javax.management.ObjectName; import javax.management.ReflectionException; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.fusesource.cloudmix.agent.Bundle; import org.fusesource.cloudmix.agent.Feature; import org.fusesource.cloudmix.agent.InstallerAgent; import org.fusesource.cloudmix.agent.security.SecurityUtils; import org.fusesource.cloudmix.common.util.FileUtils; import org.apache.servicemix.jbi.deployment.Descriptor; import org.apache.servicemix.jbi.deployment.DescriptorFactory; import org.apache.servicemix.jbi.deployment.ServiceAssembly; import org.apache.servicemix.jbi.deployment.ServiceUnit; import org.apache.servicemix.jbi.deployment.Target; import org.apache.servicemix.jbi.util.DOMUtil; public class JBIInstallerAgent extends InstallerAgent { private static final String SA_NAME_KEY = JBIInstallerAgent.class.getName() + ".saName"; private static final Log LOGGER = LogFactory.getLog(JBIInstallerAgent.class); private static final String JBI_NS = "http://java.sun.com/xml/ns/jbi/management-message"; private static final String JBI_DESC_FILE_NAME = "META-INF/jbi.xml"; private static final int DEFAULT_MAX_DEPLOY_ATTEMPTS = 18; private static final int DEFAULT_DEPLOY_ATTEMPT_DELAY = 10; private MBeanServer mbeanServer; private ObjectName mbeanName; private int maxDeployAttempts = DEFAULT_MAX_DEPLOY_ATTEMPTS; private int deployAttemptDelay = DEFAULT_DEPLOY_ATTEMPT_DELAY; public void setMBeanServer(MBeanServer anMbeanServer) { mbeanServer = anMbeanServer; } public void setMBeanName(ObjectName name) { this.mbeanName = name; } public void setMaxDeployAttempts(int t) { maxDeployAttempts = t; } public int getMaxDeployAttempts() { return maxDeployAttempts; } public void setDeployAttemptDelay(int t) { deployAttemptDelay = t; } public int getDeployAttemptDelay() { return deployAttemptDelay; } /** * should be careful when using this: do not use hard coded value as the ID should be somewhat guaranteed * to be unique. Can be used when reloading the agent settings after a restart * @param anId */ public void setAgentId(String anId) { agentId = anId; } @Override public boolean validateAgent() { if (mbeanServer == null || mbeanName == null) { LOGGER.error("Invalid mbean server or name"); return false; } return true; } @Override protected boolean installBundle(Feature feature, Bundle bundle) { LOGGER.info("installing bundle " + bundle.getUri()); // To start and undeploy a SA we need the deployed name of the SA. // However the deploy() operation does not return this information so // we use the getDeployedServiceAssemblies() before and after the // deployment and look for a new entry. There is a risk that another // SA could be deployed by some other mechanism while this current // deployment is happening. // TODO: Now that we are reading the JBI descriptor (see targetComponentsAvailable()) // we can easily get the SA name. Set<String> before = getDeployedSAs(); try { if (!targetComponentsAvailable(bundle)) { return false; } String[] sig = {"java.lang.String" }; String[] deployParams = {bundle.getUri() }; LOGGER.debug("Calling deploy() mbean operation"); Object ret = mbeanServer.invoke(mbeanName, "deploy", deployParams, sig); if (!statusOK("deploy", ret)) { LOGGER.error("Failed to deploy service assembly " + bundle.getUri() + " for feature " + feature.getName()); LOGGER.info("result: " + ret); return false; } } catch (Exception e) { LOGGER.error("Exception invoking deploy(" + bundle.getUri() + ") operation"); LOGGER.error("exception: " + e); return false; } Set<String> after = getDeployedSAs(); List<String> newSAs = getDiffs(before, after); if (newSAs.size() == 0) { LOGGER.error("No new service assemblies deployed"); return false; } if (newSAs.size() > 1) { LOGGER.error("Too many new service assemblies deployed"); // Assume its the first in the list. } String saName = newSAs.get(0); try { String[] sig = {"java.lang.String" }; LOGGER.debug("Calling start() mbean operation"); String[] startParams = {saName }; Object ret = mbeanServer.invoke(mbeanName, "start", startParams, sig); if (!statusOK("start", (String) ret)) { LOGGER.error("Failed to start service assembly " + saName + ", bundle " + bundle.getUri() + " for feature " + feature.getName()); LOGGER.info("result: " + ret); // TODO: should we undeploy the SA now? return false; } } catch (Exception e) { LOGGER.error("Exception invoking start(" + saName + ") operation"); LOGGER.error("exception: " + e); return false; } addSA(bundle, saName); return true; } @Override protected boolean uninstallBundle(Feature feature, Bundle bundle) { LOGGER.info("uninstalling bundle " + bundle.getUri()); String featureName = feature.getName(); String saName = getSAName(bundle); if (saName == null) { LOGGER.error("Cannot find SA name for bundle " + bundle.getUri()); return false; } String[] sig = {"java.lang.String" }; String[] params = {saName }; try { LOGGER.debug("Calling shutDown() mbean operation"); Object ret = mbeanServer.invoke(mbeanName, "shutDown", params, sig); if (!statusOK("shutDown", (String) ret)) { LOGGER.error("Failed to shutdown service assembly " + saName + ", bundle " + bundle.getUri() + " for feature " + featureName); LOGGER.info("result: " + ret); return false; } } catch (Exception e) { LOGGER.error("Exception invoking shutDown(" + params[0] + ") operation"); LOGGER.info("exception: " + e); return false; } try { LOGGER.debug("Calling undeploy() mbean operation"); Object ret = mbeanServer.invoke(mbeanName, "undeploy", params, sig); if (ret != null && !statusOK("undeploy", ret)) { LOGGER.error("Failed to undeploy service assembly " + saName + ", bundle " + bundle.getUri() + " for feature " + featureName); LOGGER.info("result: " + ret); return false; } } catch (Exception e) { LOGGER.error("Exception invoking undeploy(" + params[0] + ") operation"); LOGGER.info("exception: " + e); return false; } removeSA(bundle); return true; } /* * Extracts the JBI descriptor from the ZIP and makes sure all target components are available. */ private boolean targetComponentsAvailable(Bundle bundle) { File jbiDescFile = null; ZipInputStream zip = null; try { URL saUrl = new URL(bundle.getUri()); LOGGER.info("checking target components for SA bundle " + saUrl); zip = new ZipInputStream(SecurityUtils.getInputStream(saUrl)); boolean foundDescriptor = false; ZipEntry ze = zip.getNextEntry(); while (ze != null) { LOGGER.debug("Zip entry " + ze.getName()); if (!ze.isDirectory() && ze.getName().equals(JBI_DESC_FILE_NAME)) { foundDescriptor = true; break; } ze = zip.getNextEntry(); } if (!foundDescriptor) { LOGGER.error("cannot find JBI descriptor (" + JBI_DESC_FILE_NAME + ") in bundle " + bundle); return false; } jbiDescFile = File.createTempFile("jbi-agent", ".xml"); FileOutputStream os = new FileOutputStream(jbiDescFile); extractZipEntry(zip, os); Descriptor jbiDesc = DescriptorFactory.buildDescriptor(jbiDescFile); ServiceAssembly sa = jbiDesc.getServiceAssembly(); ServiceUnit[] sus = sa.getServiceUnits(); boolean canDeploy = true; int max = getMaxDeployAttempts(); for (int i = 0; i < max; i++) { canDeploy = true; for (ServiceUnit su : sus) { Target target = su.getTarget(); if (!checkComponentAvailability(target.getComponentName())) { LOGGER.info("cannot deploy SU " + su.getIdentification().getName() + " to component " + target.getComponentName()); canDeploy = false; break; } else { LOGGER.info("can deploy SU " + su.getIdentification().getName() + " to component " + target.getComponentName()); } } if (!canDeploy) { LOGGER.warn("cannot deploy JBI bundle " + bundle + " as all target components are not available"); int r = max - i - 1; if (r != 0) { int t = getDeployAttemptDelay(); LOGGER.info(r + " attempts remaining, sleeping for " + t + " seconds"); sleep(t); } } else { break; } } return canDeploy; } catch (Exception e) { LOGGER.error("Got exception checking availability of target components for bundle " + bundle, e); return false; } finally { if (jbiDescFile != null) { LOGGER.info("deleting " + jbiDescFile); jbiDescFile.delete(); } if (zip != null) { try { zip.close(); } catch (IOException e) { // Complete. } } } } private boolean checkComponentAvailability(String componentName) { LOGGER.info("checking component " + componentName); String[] sig = {"java.lang.String" }; String[] canDeployParams = { componentName }; Object ret; try { ret = mbeanServer.invoke(mbeanName, "canDeployToComponent", canDeployParams, sig); } catch (Exception e) { LOGGER.error("Exception inovking mbean canDeployToCompoent(" + componentName + ")", e); return false; } if (ret instanceof Boolean) { return ((Boolean) ret).booleanValue(); } else { LOGGER.error("unexpected result from canDeployToComponent: " + ret); return false; } } private Set<String> getDeployedSAs() { try { String[] sig = {}; String[] params = {}; Object ret = mbeanServer.invoke(mbeanName, "getDeployedServiceAssemblies", params, sig); if (ret instanceof String[]) { Set<String> set = new HashSet<String>(); for (String s : (String[]) ret) { set.add(s); } return set; } } catch (Exception e) { LOGGER.error("Exception invoking getDeployedServiceAssemblies() operation"); LOGGER.error("exception: " + e); } return null; } private List<String> getDiffs(Set<String> before, Set<String> after) { List<String> diffs = new ArrayList<String>(); for (String s : after) { if (!before.contains(s)) { diffs.add(s); } } return diffs; } private String getSAName(Bundle bundle) { return (String) bundle.getAgentProperties().get(SA_NAME_KEY); } private void addSA(Bundle bundle, String saName) { String oldName = getSAName(bundle); if (oldName != null) { LOGGER.warn("Bundle " + bundle.getUri() + " is already deployed as SA " + oldName); } LOGGER.info("SA name for bundle " + bundle.getUri() + " is " + saName); bundle.getAgentProperties().put(SA_NAME_KEY, saName); } private void removeSA(Bundle bundle) { bundle.getAgentProperties().remove(SA_NAME_KEY); } private boolean statusOK(String opName, Object result) { if (!(result instanceof String)) { LOGGER.error("result is not a string; class is " + result.getClass().getName()); return false; } try { Document doc = parse((String) result); Element e = getElement(doc, "jbi-task"); e = getChildElement(e, "jbi-task-result"); e = getChildElement(e, "frmwk-task-result"); e = getChildElement(e, "frmwk-task-result-details"); Element details = getChildElement(e, "task-result-details"); e = getChildElement(details, "task-result"); String status = DOMUtil.getElementText(e); e = getChildElement(details, "task-id"); String taskId = DOMUtil.getElementText(e); if (!opName.equals(taskId)) { LOGGER.warn("Mismatch between operation name (" + opName + ") and task-id (" + taskId + ")"); } return "SUCCESS".equals(status); } catch (Exception e) { LOGGER.error("Error parsing XML document; exception " + e); LOGGER.error("XML Document: " + result); } return false; } private Document parse(String result) throws ParserConfigurationException, SAXException, IOException { LOGGER.debug("Parsing XML Document:\n" + result); DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(true); factory.setIgnoringElementContentWhitespace(true); factory.setIgnoringComments(true); DocumentBuilder builder = factory.newDocumentBuilder(); return builder.parse(new InputSource(new StringReader(result))); } private Element getElement(Document doc, String name) { NodeList nl = doc.getElementsByTagNameNS(JBI_NS, name); return (Element) nl.item(0); } private Element getChildElement(Element e, String name) { NodeList nl = e.getElementsByTagNameNS(JBI_NS, name); return (Element) nl.item(0); } private void extractZipEntry(ZipInputStream zip, OutputStream os) throws IOException { final byte[] buffer = new byte[FileUtils.BUFFER_SIZE]; try { long total = 0; int n = zip.read(buffer); while (n != -1) { total += n; os.write(buffer, 0, n); n = zip.read(buffer); } LOGGER.info("Copied " + total + " bytes"); } finally { zip.closeEntry(); os.close(); } } private void sleep(int sleepTime) { try { Thread.sleep(sleepTime * 1000); } catch (InterruptedException e) { // Complete } } }