/**
* <a href="http://servicemix.org">ServiceMix: The open source ESB</a>
*
* Copyright 2005 RAJD Consultancy Ltd
*
* Licensed 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.servicemix.jbi.framework;
import edu.emory.mathcs.backport.java.util.concurrent.ConcurrentHashMap;
import edu.emory.mathcs.backport.java.util.concurrent.CopyOnWriteArraySet;
import edu.emory.mathcs.backport.java.util.concurrent.atomic.AtomicBoolean;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.servicemix.jbi.config.spring.XBeanProcessor;
import org.servicemix.jbi.container.EnvironmentContext;
import org.servicemix.jbi.container.JBIContainer;
import org.servicemix.jbi.deployment.Descriptor;
import org.servicemix.jbi.deployment.ServiceAssembly;
import org.servicemix.jbi.management.AttributeInfoHelper;
import org.servicemix.jbi.management.BaseLifeCycle;
import org.servicemix.jbi.util.FileUtil;
import org.xbean.spring.context.FileSystemXmlApplicationContext;
import javax.jbi.JBIException;
import javax.jbi.management.DeploymentException;
import javax.jbi.management.LifeCycleMBean;
import javax.management.JMException;
import javax.management.MBeanAttributeInfo;
import javax.resource.spi.work.Work;
import javax.resource.spi.work.WorkException;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
/**
* Monitors install and deploy directories to auto install/deploy archives
*
* @version $Revision: 765 $
*/
public class AutoDeploymentService extends BaseLifeCycle {
private static final Log log = LogFactory.getLog(AutoDeploymentService.class);
private JBIContainer container;
private EnvironmentContext environmentContext;
protected static final String DESCRIPTOR_FILE = "META-INF/jbi.xml";
private DeploymentService deploymentService;
private InstallationService installationService;
private boolean monitorInstallationDirectory = true;
private int monitorInterval = 10;
private long lastMonitorTime = 0;
private AtomicBoolean started = new AtomicBoolean(false);
private Timer statsTimer;
private TimerTask timerTask;
private Map descriptorMap = new ConcurrentHashMap();
private Set installFileSet = new CopyOnWriteArraySet();
private Set deployFileSet = new CopyOnWriteArraySet();
private Map pendingSAs = new ConcurrentHashMap();
public String getDescription() {
return "automatically installs and deploys JBI Archives";
}
/**
* @return Returns the monitorInstallationDirectory.
*/
public boolean isMonitorInstallationDirectory() {
return monitorInstallationDirectory;
}
/**
* @param monitorInstallationDirectory The monitorInstallationDirectory to set.
*/
public void setMonitorInstallationDirectory(boolean monitorInstallationDirectory) {
this.monitorInstallationDirectory = monitorInstallationDirectory;
}
/**
* @return Returns the monitorInterval (number in secs)
*/
public int getMonitorInterval() {
return monitorInterval;
}
/**
* @param monitorInterval The monitorInterval to set (in secs)
*/
public void setMonitorInterval(int monitorInterval) {
this.monitorInterval = monitorInterval;
}
public void start() throws javax.jbi.JBIException {
super.start();
if (started.compareAndSet(false, true)) {
scheduleDirectoryTimer();
}
}
/**
* Stop the item. This suspends current messaging activities.
*
* @exception javax.jbi.JBIException if the item fails to stop.
*/
public void stop() throws javax.jbi.JBIException {
if (started.compareAndSet(true, false)) {
super.stop();
if (timerTask != null) {
timerTask.cancel();
}
}
}
/**
* Initialize the Service
*
* @param container
* @throws JBIException
*/
public void init(JBIContainer container) throws JBIException {
this.container = container;
this.environmentContext = container.getEnvironmentContext();
this.installationService = container.getInstallationService();
this.deploymentService = container.getDeploymentService();
// clean-up tmp directory
FileUtil.deleteFile(environmentContext.getTmpDir());
this.buildState(environmentContext.getInstallationDir());
this.buildState(environmentContext.getDeploymentDir());
container.getManagementContext().registerSystemService(this, LifeCycleMBean.class);
}
/**
* Shut down the item. The releases resources, preparatory to uninstallation.
*
* @exception javax.jbi.JBIException if the item fails to shut down.
*/
public void shutDown() throws javax.jbi.JBIException {
super.shutDown();
stop();
if (statsTimer != null) {
statsTimer.cancel();
}
container.getManagementContext().unregisterMBean(this);
}
/**
* Update an archive
*
* @param location
* @param autoStart
* @throws DeploymentException
*/
public void updateArchive(String location, boolean autoStart) throws DeploymentException {
File tmp = AutoDeploymentService.unpackLocation(environmentContext.getTmpDir(), location);
Descriptor root = AutoDeploymentService.buildDescriptor(tmp);
if (root != null) {
try {
container.getBroker().suspend();
if (root.getComponent() != null) {
String componentName = root.getComponent().getIdentification().getName();
log.info("Uninstalling Component: " + componentName);
if (installationService.unloadInstaller(componentName, true)) {
}
installationService.install(tmp, root, autoStart);
autoDeployServiceSA(componentName);
}
else if (root.getSharedLibrary() != null) {
installationService.doInstallSharedLibrary(tmp, root.getSharedLibrary());
}
else if (root.getServiceAssembly() != null) {
ServiceAssembly sa = root.getServiceAssembly();
String name = sa.getIdentification().getName();
try {
if (deploymentService.isSaDeployed(name)) {
deploymentService.shutDown(name);
deploymentService.undeploy(name);
deploymentService.deploy(tmp, sa);
if (autoStart) {
deploymentService.start(name);
}
}
else {
// see if Component is installed
String componentName = deploymentService.getComponentName(sa);
if (container.getRegistry().isLocalComponentRegistered(componentName)) {
deploymentService.deploy(tmp, sa);
if (autoStart) {
deploymentService.start(name);
}
}
else {
log.info("No Component named " + componentName + " exists yet - adding ServiceAssembly " + name + " to pending list");
pendingSAs.put(componentName, tmp);
}
}
}
catch (Exception e) {
String errStr = "Failed to update Service Assembly: " + name;
log.error(errStr, e);
throw new DeploymentException(errStr, e);
}
}
}
finally {
container.getBroker().resume();
descriptorMap.put(location, root);
}
}
}
/**
* Auto deploy an SA
*
* @param componentName
*/
private void autoDeployServiceSA(String componentName) {
File tmpDir = (File) pendingSAs.remove(componentName);
if (tmpDir != null) {
try {
Descriptor root = AutoDeploymentService.buildDescriptor(tmpDir);
if (root != null) {
ServiceAssembly sa = root.getServiceAssembly();
if (sa != null && sa.getIdentification() != null) {
String name = sa.getIdentification().getName();
log.info("auto deploying Service Assembly: " + name);
if (!deploymentService.isSaDeployed(name)) {
deploymentService.deploy(tmpDir, sa);
deploymentService.start(name);
}
}
else {
log.warn("Could not find ServiceAssembly in descriptor from " + tmpDir);
}
}
else {
log.warn("Failed to build descriptor from " + tmpDir);
}
}
catch (Exception e) {
log.error("Failed to deploy service assembly to " + componentName, e);
}
}
}
private void autoRemoveSAs(String componentName) {
for (Iterator i = descriptorMap.entrySet().iterator();i.hasNext();) {
Map.Entry entry = (Map.Entry) i.next();
Descriptor root = (Descriptor) entry.getValue();
if (root != null && root.getServiceAssembly() != null) {
ServiceAssembly sa = root.getServiceAssembly();
String name = sa.getIdentification().getName();
if (deploymentService.isSaDeployed(name)) {
// remove
try {
deploymentService.shutDown(name);
deploymentService.undeploy(name);
// remove file from descriptorMap
descriptorMap.remove(entry.getKey());
// remove SA from deloyment
deployFileSet.remove(entry.getKey());
installFileSet.remove(entry.getKey());
log.info("Removing ServiceAssembly " + name + " associated with Component " + componentName);
}
catch (Exception e) {
log.error("failed to remove ServiceAssembly " + name + " for Component: " + componentName);
}
}
}
}
}
/**
* Remove an archive location
*
* @param location
* @throws DeploymentException
*/
public void removeArchive(String location) throws DeploymentException {
log.info("Attempting to remove archive at: " + location);
Descriptor root = (Descriptor) descriptorMap.remove(location);
if (root != null) {
try {
container.getBroker().suspend();
if (root.getComponent() != null) {
String componentName = root.getComponent().getIdentification().getName();
log.info("Uninstalling Component: " + componentName);
autoRemoveSAs(componentName);
installationService.unloadInstaller(componentName, true);
}
if (root.getSharedLibrary() != null) {
String name = root.getSharedLibrary().getIdentification().getName();
log.info("removing shared library: " + name);
installationService.uninstallSharedLibrary(name);
}
if (root.getServiceAssembly() != null) {
ServiceAssembly sa = root.getServiceAssembly();
String name = sa.getIdentification().getName();
log.info("removing service assembly " + name);
try {
if (deploymentService.isSaDeployed(name)) {
deploymentService.shutDown(name);
deploymentService.undeploy(name);
}
}
catch (Exception e) {
String errStr = "Failed to update Service Assembly: " + name;
log.error(errStr, e);
throw new DeploymentException(errStr, e);
}
}
}
finally {
container.getBroker().resume();
}
}
else {
throw new DeploymentException("remove archive cannot find descriptor for " + location);
}
}
/**
* Check to see if an archive is installed
*
* @param location
* @return the Descriptor if installed
*/
protected Descriptor getInstalledDescriptor(String location) throws JBIException{
Descriptor result = null;
boolean exists = false;
try {
File tmp = AutoDeploymentService.unpackLocation(environmentContext.getTmpDir(), location);
Descriptor root = AutoDeploymentService.buildDescriptor(tmp);
if (root.getComponent() != null) {
String componentName = root.getComponent().getIdentification().getName();
exists = container.getRegistry().isLocalComponentRegistered(componentName);
}
else if (root.getServiceAssembly() != null) {
ServiceAssembly sa = root.getServiceAssembly();
String name = sa.getIdentification().getName();
exists = deploymentService.isSaDeployed(name);
}
else if (root.getSharedLibrary() != null) {
String name = root.getSharedLibrary().getIdentification().getName();
exists = installationService.containsSharedLibrary(name);
}
if (exists) {
result = root;
}
else {
FileUtil.deleteFile(tmp);
}
}
catch (DeploymentException e) {
result = null;
log.error("Could not process " + location, e);
}
return result;
}
/**
* Get an array of MBeanAttributeInfo
*
* @return array of AttributeInfos
* @throws JMException
*/
public MBeanAttributeInfo[] getAttributeInfos() throws JMException {
AttributeInfoHelper helper = new AttributeInfoHelper();
helper.addAttribute(getObjectToManage(), "monitorInstallationDirectory",
"Periodically monitor the Installation directory");
helper.addAttribute(getObjectToManage(), "monitorInterval", "Interval (secs) before monitoring");
return AttributeInfoHelper.join(super.getAttributeInfos(), helper.getAttributeInfos());
}
/**
* Build a Descriptor from a file archieve
*
* @param tmpDir
* @return the Descriptor object
*/
protected static Descriptor buildDescriptor(File tmpDir) {
Descriptor root = null;
File descriptorFile = new File(tmpDir, DESCRIPTOR_FILE);
if (descriptorFile.exists()) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(AutoDeploymentService.class.getClassLoader());
FileSystemXmlApplicationContext context = new FileSystemXmlApplicationContext("file:///"
+ descriptorFile.getAbsolutePath(),
Arrays.asList(new Object[] { new XBeanProcessor()}));
root = (Descriptor) context.getBean("jbi");
} finally {
Thread.currentThread().setContextClassLoader(cl);
}
}
return root;
}
/**
* Unpack a location into a temp file directory
*
* @param location
* @return tmp directory
* @throws DeploymentException
*/
protected static File unpackLocation(File tmpRoot, String location) throws DeploymentException {
File tmpDir = null;
try {
File file = new File(location);
if (file.isDirectory()) {
log.info("Deploying an exploded jar/zip, we will create a temporary jar for it.");
// If we have a directory then we should move it over
File newFile = new File(tmpRoot.getAbsolutePath() + "/exploded.jar");
newFile.delete();
FileUtil.zipDir(file.getAbsolutePath(), newFile.getAbsolutePath());
file = newFile;
log.info("Deployment will now work from " + file.getAbsolutePath());
}
if (!file.exists()) {
// assume it's a URL
try {
URL url = new URL(location);
String fileName = url.getFile();
if (fileName == null || (!fileName.endsWith(".zip") && !fileName.endsWith(".jar"))) {
throw new DeploymentException("Location: " + location + " is not an archive");
}
file = FileUtil.unpackArchive(url, tmpRoot);
}
catch (MalformedURLException e) {
throw new DeploymentException(e);
}
}
if (FileUtil.archiveContainsEntry(file, AutoDeploymentService.DESCRIPTOR_FILE)) {
tmpDir = FileUtil.createUniqueDirectory(tmpRoot, file.getName());
FileUtil.unpackArchive(file, tmpDir);
log.info("Unpacked archive " + location + " to " + tmpDir);
}
}
catch (IOException e) {
log.error("I/O error installing archive", e);
throw new DeploymentException(e);
}
return tmpDir;
}
private void scheduleDirectoryTimer() {
if (isMonitorInstallationDirectory()) {
if (statsTimer == null) {
statsTimer = new Timer(true);
}
if (timerTask != null) {
timerTask.cancel();
}
timerTask = new TimerTask() {
public void run() {
monitorDirectory(environmentContext.getInstallationDir(), installFileSet);
monitorDirectory(environmentContext.getDeploymentDir(), deployFileSet);
}
};
long interval = monitorInterval * 1000;
statsTimer.scheduleAtFixedRate(timerTask, 0, interval);
}
}
private void buildState(File dir) throws JBIException {
if (dir != null && dir.exists() && dir.isDirectory()) {
File[] files = dir.listFiles();
if (files != null) {
for (int i = 0;i < files.length;i++) {
File file = files[i];
if (file.getPath().endsWith(".jar") || file.getPath().endsWith(".zip")) {
Descriptor root = getInstalledDescriptor(file.getAbsolutePath());
if (root != null) {
descriptorMap.put(file.getAbsolutePath(), root);
}
}
}
}
}
}
private void monitorDirectory(final File root, final Set fileSet) {
log.debug("Monitoring directory " + root.getAbsolutePath() + " for new or modified archives");
List tmpList = new ArrayList();
if (root != null && root.exists() && root.isDirectory()) {
File[] files = root.listFiles();
if (files != null) {
for (int i = 0;i < files.length;i++) {
final File file = files[i];
tmpList.add(file.getAbsolutePath());
if ((file.getPath().endsWith(".jar") || file.getPath().endsWith(".zip"))
&& (!descriptorMap.containsKey(file.getAbsolutePath()) || file.lastModified() > lastMonitorTime)) {
try {
container.getWorkManager().doWork(new Work() {
public void run() {
log.info("Directory: " + root.getName() + ": Archive changed: processing "
+ file.getName() + " ...");
try {
updateArchive(file.getAbsolutePath(), true);
fileSet.add(file.getAbsolutePath());
}
catch (Exception e) {
log.warn("Directory: " + root.getName() + ": Automatic install of " + file
+ " failed", e);
}
log.info("Directory: " + root.getName() + ": Finished installation of archive: "
+ file.getName());
}
public void release() {
}
});
}
catch (WorkException e) {
log.warn("Automatic install of " + file + " failed", e);
}
}
}
}
// now remove any locations no longer here
for (Iterator i = fileSet.iterator();i.hasNext();) {
String location = i.next().toString();
if (!tmpList.contains(location)) {
fileSet.remove(location);
try {
log.info("Location " + location + " no longer exists - removing ...");
removeArchive(location);
}
catch (DeploymentException e) {
log.error("Failed to removeArchive: " + location, e);
}
}
}
}
lastMonitorTime = System.currentTimeMillis();
}
}