001/*
002 * (C) Copyright 2006-2018 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     bstefanescu
018 */
019package org.nuxeo.runtime.tomcat;
020
021import java.io.File;
022import java.io.IOException;
023import java.lang.reflect.Method;
024import java.net.MalformedURLException;
025import java.net.URL;
026import java.net.URLClassLoader;
027import java.nio.file.FileSystems;
028import java.nio.file.Files;
029import java.nio.file.Path;
030import java.nio.file.PathMatcher;
031import java.util.ArrayList;
032import java.util.List;
033import java.util.stream.Stream;
034
035import org.apache.catalina.Container;
036import org.apache.catalina.Lifecycle;
037import org.apache.catalina.LifecycleEvent;
038import org.apache.catalina.LifecycleListener;
039import org.apache.catalina.LifecycleState;
040import org.apache.catalina.core.ContainerBase;
041import org.apache.catalina.core.StandardHost;
042import org.nuxeo.osgi.application.FrameworkBootstrap;
043
044/**
045 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
046 */
047public class NuxeoDeployer implements LifecycleListener {
048
049    protected String home = "nxserver";
050
051    protected FrameworkBootstrap bootstrap;
052
053    public void setHome(String home) {
054        this.home = home;
055    }
056
057    public String getHome() {
058        return home;
059    }
060
061    @Override
062    public void lifecycleEvent(LifecycleEvent event) {
063        Lifecycle lifecycle = event.getLifecycle();
064        String type = event.getType();
065
066        if (lifecycle instanceof Container && Lifecycle.BEFORE_START_EVENT.equals(type)) {
067            Container container = (Container) lifecycle;
068            preprocess(container);
069        } else if (lifecycle instanceof StandardHost && Lifecycle.AFTER_START_EVENT.equals(type)) {
070            StandardHost container = (StandardHost) lifecycle;
071            checkFailures(container);
072        }
073    }
074
075    protected void checkFailures(StandardHost host) {
076        boolean ok = Stream.of(host.findChildren()).map(Lifecycle::getState).allMatch(s -> s == LifecycleState.STARTED);
077        boolean strict = Boolean.getBoolean("nuxeo.start.strict");
078
079        if (!ok && strict) {
080            // Throws an exception and let Tomcat to handle it via Catalina's shutdown hook. Otherwise, we could call
081            // `#stop()` and `#destroy()` by ourselves, but that means we're making a shutdown in AFTER_STARTED
082            // lifecycle event. It would be misleading.
083            throw new IllegalStateException("Some contexts failed to start.");
084        }
085    }
086    protected void preprocess(Container container) {
087        try {
088            ClassLoader parentCl = container.getParentClassLoader();
089            File homeDir = resolveHomeDirectory();
090            File bundles = new File(homeDir, "bundles");
091            File lib = new File(homeDir, "lib");
092            File deployerJar = FrameworkBootstrap.findFileStartingWidth(bundles, "nuxeo-runtime-deploy");
093            File commonJar = FrameworkBootstrap.findFileStartingWidth(bundles, "nuxeo-common");
094            if (deployerJar == null || commonJar == null) {
095                System.out.println("Deployer and/or common JAR (nuxeo-runtime-deploy* | nuxeo-common*) not found in "
096                        + bundles);
097                return;
098            }
099            List<URL> urls = new ArrayList<>();
100            PathMatcher jar = FileSystems.getDefault().getPathMatcher("glob:**.jar");
101            try (Stream<Path> stream = Files.list(lib.toPath())) {
102                stream.filter(jar::matches).map(this::toURL).forEach(urls::add);
103            }
104            try (Stream<Path> stream = Files.list(bundles.toPath())) {
105                stream.filter(jar::matches).map(this::toURL).forEach(urls::add);
106            }
107            urls.add(homeDir.toURI().toURL());
108            urls.add(new File(homeDir, "config").toURI().toURL());
109            try (URLClassLoader cl = new URLClassLoader(urls.toArray(new URL[0]), parentCl)) {
110                System.out.println("# Running Nuxeo Preprocessor ...");
111                Class<?> klass = cl.loadClass("org.nuxeo.runtime.deployment.preprocessor.DeploymentPreprocessor");
112                Method main = klass.getMethod("main", String[].class);
113                main.invoke(null, new Object[] { new String[] { homeDir.getAbsolutePath() } });
114                System.out.println("# Preprocessing done.");
115            }
116        } catch (IOException | ReflectiveOperationException | IllegalStateException e) {
117            throw new RuntimeException("Failed to handle event", e);
118        }
119    }
120
121    protected URL toURL(Path p) {
122        try {
123            return p.toUri().toURL();
124        } catch (MalformedURLException e) {
125            throw new IllegalStateException(e);
126        }
127    }
128
129    protected File resolveHomeDirectory() {
130        String path;
131        if (home.startsWith("/")) {
132            path = home;
133        } else {
134            path = getTomcatHome() + '/' + home;
135        }
136        return new File(path);
137    }
138
139    public String getTomcatHome() {
140        String tomcatHome = System.getProperty("catalina.base");
141        if (tomcatHome == null) {
142            tomcatHome = System.getProperty("catalina.home");
143        }
144        return tomcatHome;
145    }
146
147    /**
148     * @deprecated Since 10.1, use {@link #preprocess(Container)} instead.
149     */
150    @Deprecated
151    protected void handleEvent(ContainerBase container, LifecycleEvent event) {
152        if (Lifecycle.BEFORE_START_EVENT.equals(event.getType())) {
153            preprocess(container);
154        }
155    }
156
157}