/**
*
* 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.loader;
import org.apache.catalina.Container;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleEvent;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.Service;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardEngine;
import org.apache.catalina.core.StandardHost;
import org.apache.catalina.core.StandardServer;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.Properties;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* The sole purpose of this class is to call the {@link TomcatEmbedder#embed} method
* <p/>
* This is an alternate way to load the Tomcat integration
* This approach is mutually exclussive to the {@link LoaderServlet}
* <p/>
* This class does nothing more than scrape around in
* Tomcat and look for the tomee.war so it can call the embedder
* <p/>
* This class can be installed in the Tomcat server.xml as an alternate
* way to bootstrap OpenEJB into Tomcat. The benefit of this is that
* OpenEJB is guaranteed to start before all webapps.
*/
public class OpenEJBListener implements LifecycleListener {
private static final Logger LOGGER = Logger.getLogger(OpenEJBListener.class.getName());
private static boolean listenerInstalled;
private static boolean logWebappNotFound = true;
public static boolean isListenerInstalled() {
return listenerInstalled;
}
@Override
public void lifecycleEvent(final LifecycleEvent event) {
// only install once
if (listenerInstalled || !Lifecycle.AFTER_INIT_EVENT.equals(event.getType())) {
return;
}
try {
File webappDir = findOpenEjbWar();
if (webappDir == null && event.getSource() instanceof StandardServer) {
final StandardServer server = (StandardServer) event.getSource();
webappDir = tryToFindAndExtractWar(server);
if (webappDir != null) { // we are using webapp startup
final File exploded = extractDirectory(webappDir);
if (exploded != null) {
extract(webappDir, exploded);
}
webappDir = exploded;
TomcatHelper.setServer(server);
}
}
if (webappDir != null) {
LOGGER.info("found the tomee webapp on " + webappDir.getPath());
final Properties properties = new Properties();
properties.setProperty("tomee.war", webappDir.getAbsolutePath());
properties.setProperty("openejb.embedder.source", OpenEJBListener.class.getSimpleName());
TomcatEmbedder.embed(properties, StandardServer.class.getClassLoader());
listenerInstalled = true;
} else if (logWebappNotFound) {
LOGGER.info("tomee webapp not found from the listener, will try from the webapp if exists");
logWebappNotFound = false;
}
} catch (final Exception e) {
LOGGER.log(Level.SEVERE, "TomEE Listener can't start OpenEJB", e);
// e.printStackTrace(System.err);
}
}
private static File extractDirectory(final File webappDir) {
File exploded = new File(webappDir.getAbsolutePath().replace(".war", ""));
int i = 0;
while (exploded.exists()) {
exploded = new File(exploded.getAbsolutePath() + "_" + i++);
}
return exploded;
}
private static File tryToFindAndExtractWar(final StandardServer source) {
if (System.getProperties().containsKey("openejb.war")) {
return new File(System.getProperty("openejb.war"));
}
for (final Service service : source.findServices()) {
final Container container = service.getContainer();
if (container instanceof StandardEngine) {
final StandardEngine engine = (StandardEngine) container;
for (final Container child : engine.findChildren()) {
if (child instanceof StandardHost) {
final StandardHost host = (StandardHost) child;
final File base = hostDir(System.getProperty("catalina.base"), host.getAppBase());
final File[] files = base.listFiles();
if (files != null) {
for (final File file : files) {
if (isTomEEWar(file)) {
return file;
}
}
}
}
}
}
}
return null;
}
private static boolean isTomEEWar(final File file) {
final String name = file.getName();
try (final JarFile jarFile = new JarFile(file)) {
return jarFile.getEntry("lib") != null
&& (name.startsWith("tomee") || name.startsWith("openejb")
&& name.endsWith(".war"));
} catch (final IOException e) {
return false;
}
}
private static File findOpenEjbWar() {
// in Tomcat 5.5 the OpenEjb war is in the server/webapps director
final String catalinaBase = System.getProperty("catalina.base");
final File serverWebapps = new File(catalinaBase, "server/webapps");
File openEjbWar = findOpenEjbWar(serverWebapps);
if (openEjbWar != null) {
return openEjbWar;
}
try {
// in Tomcat 6 the OpenEjb war is normally in webapps, but we just
// scan all hosts directories
for (final Service service : TomcatHelper.getServer().findServices()) {
final Container container = service.getContainer();
if (container instanceof StandardEngine) {
final StandardEngine engine = (StandardEngine) container;
for (final Container child : engine.findChildren()) {
if (child instanceof StandardHost) {
final StandardHost host = (StandardHost) child;
final File hostDir = hostDir(catalinaBase, host.getAppBase());
openEjbWar = findOpenEjbWar(hostDir);
if (openEjbWar != null) {
return openEjbWar;
} else {
return findOpenEjbWar(host);
}
}
}
}
}
} catch (final Exception e) {
LOGGER.log(Level.WARNING, "OpenEJBListener.findOpenEjbWar: " + e.getMessage());
}
return null;
}
private static File hostDir(final String catalinaBase, final String appBase) {
File hostDir = new File(appBase);
if (!hostDir.isAbsolute()) {
hostDir = new File(catalinaBase, appBase);
}
return hostDir;
}
private static File findOpenEjbWar(final StandardHost standardHost) {
//look for openejb war in a Tomcat context
for (final Container container : standardHost.findChildren()) {
if (container instanceof StandardContext) {
final StandardContext standardContext = (StandardContext) container;
File contextDocBase = new File(standardContext.getDocBase());
if (!contextDocBase.isDirectory() && standardContext.getOriginalDocBase() != null) {
contextDocBase = new File(standardContext.getOriginalDocBase());
}
if (contextDocBase.isDirectory()) {
final File openEjbWar = findOpenEjbWarInContext(contextDocBase);
if (openEjbWar != null) {
return openEjbWar;
}
}
}
}
return null;
}
private static File findOpenEjbWar(final File hostDir) {
if (!hostDir.isDirectory()) {
return null;
}
// iterate over the contexts
final File[] files = hostDir.listFiles();
if (null != files) {
for (final File contextDir : files) {
final File foundContextDir = findOpenEjbWarInContext(contextDir);
if (foundContextDir != null) {
return foundContextDir;
}
}
}
return null;
}
private static File findOpenEjbWarInContext(final File contextDir) {
// this should be a webapp
if (!new File(contextDir, "WEB-INF").exists()) {
return null;
}
// this should be the openejb war...
// make sure it has a lib directory
final File webInfLib = new File(contextDir, "lib");
if (!webInfLib.isDirectory()) {
return null;
}
// iterate over the libs looking for the openejb-loader-*.jar
final File[] files = webInfLib.listFiles();
if (files != null) {
for (final File file : files) {
if (file.getName().startsWith("tomee-catalina-") && file.getName().endsWith(".jar")) {
return contextDir;
}
}
}
return null;
}
// copied for classloading reason
public static void extract(final File src, final File dest) throws IOException {
if (dest.exists()) {
return;
}
LOGGER.info("Extracting openejb webapp from " + src.getAbsolutePath() + " to " + dest.getAbsolutePath());
if (!dest.mkdirs()) {
throw new IOException("Failed to create: " + dest);
}
JarFile jarFile = null;
InputStream input = null;
try {
jarFile = new JarFile(src);
final Enumeration jarEntries = jarFile.entries();
while (jarEntries.hasMoreElements()) {
final JarEntry jarEntry = (JarEntry) jarEntries.nextElement();
final String name = jarEntry.getName();
final int last = name.lastIndexOf('/');
if (last >= 0) {
final File parent = new File(dest, name.substring(0, last));
if (!parent.mkdirs()) {
throw new IOException("Failed to create: " + parent);
}
}
if (name.endsWith("/")) {
continue;
}
input = jarFile.getInputStream(jarEntry);
final File file = new File(dest, name);
BufferedOutputStream output = null;
try {
output = new BufferedOutputStream(new FileOutputStream(file));
final byte[] buffer = new byte[2048];
while (true) {
final int n = input.read(buffer);
if (n <= 0) {
break;
}
output.write(buffer, 0, n);
}
} finally {
if (output != null) {
try {
output.close();
} catch (final IOException e) {
// Ignore
}
}
}
final long lastModified = jarEntry.getTime();
if (lastModified != -1 && lastModified != 0 && file != null) {
if (!file.setLastModified(lastModified)) {
LOGGER.log(Level.WARNING, "Failed to set last modified time on: " + file.getAbsolutePath());
}
}
input.close();
input = null;
}
} finally {
if (input != null) {
try {
input.close();
} catch (final Throwable t) {
// no-op
}
}
if (jarFile != null) {
try {
jarFile.close();
} catch (final Throwable t) {
// no-op
}
}
}
LOGGER.info("Extracted openejb webapp");
}
}