/*
* 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.tomee.catalina.cluster;
import org.apache.catalina.ha.ClusterListener;
import org.apache.catalina.ha.ClusterMessage;
import org.apache.openejb.NoSuchApplicationException;
import org.apache.openejb.OpenEJBException;
import org.apache.openejb.UndeployException;
import org.apache.openejb.assembler.Deployer;
import org.apache.openejb.assembler.classic.Assembler;
import org.apache.openejb.core.LocalInitialContextFactory;
import org.apache.openejb.loader.Files;
import org.apache.openejb.loader.IO;
import org.apache.openejb.loader.SystemInstance;
import org.apache.openejb.util.DaemonThreadFactory;
import org.apache.openejb.util.LogCategory;
import org.apache.openejb.util.Logger;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import java.io.File;
import java.util.Properties;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class TomEEClusterListener extends ClusterListener {
@Override
public void messageReceived(final ClusterMessage clusterMessage) {
final Class<?> type = clusterMessage.getClass();
if (DeployMessage.class.equals(type)) {
final DeployMessage msg = (DeployMessage) clusterMessage;
String file = msg.getFile();
final boolean alreadyDeployed = isDeployed(file);
File ioFile = new File(file);
if (!alreadyDeployed) {
if (msg.getArchive() != null) {
final File deployed = deployedDir();
try {
if (!deployed.exists()) {
Files.mkdirs(deployed); // can throw runtime exceptions
}
final File dump = new File(deployed, ioFile.getName());
IO.copy(msg.getArchive(), dump);
file = dump.getAbsolutePath();
ioFile = new File(file);
Static.LOGGER.info("dumped archive: " + msg.getFile() + " to " + file);
} catch (final Exception e) {
Static.LOGGER.error("can't dump archive: "+ file, e);
}
}
if (ioFile.exists()) {
Static.SERVICE.submit(new DeployTask(file));
} else {
Static.LOGGER.warning("can't find '" + file);
}
} else {
Static.LOGGER.info("application already deployed: " + file);
}
} else if (UndeployMessage.class.equals(type)) {
final String file = ((UndeployMessage) clusterMessage).getFile();
if (isDeployed(file)) {
Static.SERVICE.submit(new UndeployTask(file));
} else {
final File alternativeFile = new File(deployedDir(), new File(file).getName());
if (isDeployed(alternativeFile.getAbsolutePath())) {
Static.SERVICE.submit(new UndeployTask(alternativeFile.getAbsolutePath()));
}
Static.LOGGER.info("app '" + file + "' was not deployed");
}
} else {
Static.LOGGER.warning("message type not supported: " + type);
}
}
private File deployedDir() {
return new File(SystemInstance.get().getHome().getDirectory(), "deployed");
}
private static boolean isDeployed(final String file) {
return SystemInstance.get().getComponent(Assembler.class).isDeployed(file);
}
private static Deployer deployer() throws NamingException {
return (Deployer) new InitialContext(Static.IC_PROPS).lookup("openejb/DeployerBusinessRemote");
}
@Override
public boolean accept(final ClusterMessage clusterMessage) {
return clusterMessage != null
&& (DeployMessage.class.equals(clusterMessage.getClass())
|| UndeployMessage.class.equals(clusterMessage.getClass()));
}
public static void stop() {
Static.SERVICE.shutdown();
try {
Static.SERVICE.awaitTermination(1, TimeUnit.MINUTES);
} catch (final InterruptedException e) {
Static.SERVICE.shutdownNow();
}
}
private static class DeployTask implements Runnable {
private final String app;
private static final Properties REMOTE_DEPLOY_PROPERTIES = new Properties();
static {
REMOTE_DEPLOY_PROPERTIES.setProperty("openejb.app.autodeploy", "true"); // avoid to send deployment again
}
public DeployTask(final String ioFile) {
app = ioFile;
}
@Override
public void run() {
if (!isDeployed(app)) {
try {
deployer().deploy(app, REMOTE_DEPLOY_PROPERTIES);
} catch (final OpenEJBException e) {
Static.LOGGER.warning("can't deploy: " + app, e);
} catch (final NamingException e) {
Static.LOGGER.warning("can't find deployer", e);
}
}
}
}
private static class UndeployTask implements Runnable {
private final String app;
public UndeployTask(final String ioFile) {
app = ioFile;
}
@Override
public void run() {
if (isDeployed(app)) {
try {
deployer().undeploy(app);
} catch (final UndeployException e) {
Static.LOGGER.error("can't undeploy app", e);
} catch (final NoSuchApplicationException e) {
Static.LOGGER.warning("no app toi deploy", e);
} catch (final NamingException e) {
Static.LOGGER.warning("can't find deployer", e);
}
}
}
}
// lazy init of logger (can fail with shutdown hooks to kill the container) and executor
private static final class Static {
private static final Logger LOGGER = Logger.getInstance(LogCategory.OPENEJB, TomEEClusterListener.class);
private static final Properties IC_PROPS = new Properties();
// async processing to avoid to make the cluster hanging
private static final ExecutorService SERVICE = Executors.newSingleThreadExecutor(new DaemonThreadFactory("TomEE-Cluster-Listener-thread-"));
static {
IC_PROPS.setProperty(Context.INITIAL_CONTEXT_FACTORY, LocalInitialContextFactory.class.getName());
}
private Static() {
// no-op
}
}
}