/**
* 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
}
}
}