001/*
002 * (C) Copyright 2006-2017 Nuxeo (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 *     Kevin Leturc <kleturc@nuxeo.com>
019 */
020package org.nuxeo.runtime.tomcat.dev;
021
022import java.io.File;
023import java.io.FileInputStream;
024import java.io.IOException;
025import java.io.InputStreamReader;
026import java.io.Reader;
027import java.io.Writer;
028import java.nio.file.Files;
029import java.nio.file.Paths;
030import java.nio.file.StandardCopyOption;
031
032import javax.servlet.ServletException;
033
034import org.apache.catalina.connector.Request;
035import org.apache.catalina.connector.Response;
036import org.apache.catalina.valves.ValveBase;
037import org.apache.commons.logging.Log;
038import org.apache.commons.logging.LogFactory;
039
040/**
041 * Enable remote hot deploy and getting configuration from remote Nuxeo SDK servers
042 * <p>
043 * This valve is enabled only in SDK profile (i.e. dev mode). It will intercept any call to '/sdk' under the context
044 * path (i.e. /nuxeo/sdk)
045 *
046 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
047 */
048public class DevValve extends ValveBase {
049
050    Log log = LogFactory.getLog(DevValve.class);
051
052    @Override
053    public void invoke(Request req, Response resp) throws IOException, ServletException {
054
055        String path = req.getServletPath();
056        if (path != null && path.startsWith("/sdk/")) {
057            path = path.substring("/sdk/".length());
058            if ("reload".equals(path)) {
059                if ("GET".equals(req.getMethod())) {
060                    getReload(req, resp);
061                } else if ("POST".equals(req.getMethod())) {
062                    postReload(req, resp);
063                }
064                return;
065            } else if (path.startsWith("files/")) {
066                path = path.substring("files/".length());
067                getFile(path, req, resp);
068                return;
069            }
070            resp.setStatus(404);
071            return;
072        }
073        getNext().invoke(req, resp);
074    }
075
076    private final File getHome() {
077        return new File(System.getProperty("catalina.base"));
078    }
079
080    private final File getSDKFile(String path) {
081        return new File(new File(getHome(), "sdk"), path);
082    }
083
084    private void getFile(String path, Request req, Response resp) {
085        File file = getSDKFile(path);
086        if (!file.isFile()) {
087            resp.setStatus(404);
088        } else {
089            resp.setContentType("text/plain");
090            resp.setCharacterEncoding("UTF-8");
091            resp.setStatus(200);
092            try {
093                @SuppressWarnings("resource") // not ours to close
094                Writer out = resp.getWriter();
095                sendFile(file, out);
096                out.flush();
097            } catch (IOException e) {
098                resp.setStatus(500);
099                log.error("Failed to send file: " + file, e);
100            }
101        }
102    }
103
104    private void sendFile(File file, Writer out) throws IOException {
105        Reader in = new InputStreamReader(new FileInputStream(file), "UTF-8");
106        try {
107            char cbuf[] = new char[64 * 1024];
108            int r = -1;
109            while ((r = in.read(cbuf)) != -1) {
110                if (r > 0) {
111                    out.write(cbuf, 0, r);
112                }
113            }
114        } finally {
115            in.close();
116        }
117    }
118
119    private void getReload(Request req, Response resp) {
120        ClassLoader webLoader = req.getContext().getLoader().getClassLoader();
121        if (webLoader instanceof NuxeoDevWebappClassLoader) {
122            NuxeoDevWebappClassLoader loader = (NuxeoDevWebappClassLoader) webLoader;
123            // only if dev.bundles was modified
124            loader.getBootstrap().loadDevBundles();
125            // log.error("###### reloaded dev bundles");
126        }
127        resp.setStatus(200);
128    }
129
130    private void postReload(Request req, Response resp) throws IOException {
131        ClassLoader webLoader = req.getContext().getLoader().getClassLoader();
132        if (webLoader instanceof NuxeoDevWebappClassLoader) {
133            NuxeoDevWebappClassLoader loader = (NuxeoDevWebappClassLoader) webLoader;
134            DevFrameworkBootstrap bootstrap = loader.getBootstrap();
135            String devBundlesLocation = bootstrap.getDevBundlesLocation();
136            try {
137                Files.copy(req.getStream(), Paths.get(devBundlesLocation), StandardCopyOption.REPLACE_EXISTING);
138                // only if dev.bundles was modified
139                bootstrap.loadDevBundles();
140                resp.setStatus(200);
141            } catch (IOException e) {
142                log.error("Unable to write to dev.bundles", e);
143                resp.sendError(500, "Unable to write to dev.bundles");
144            } catch (RuntimeException e) {
145                log.error("Unable to reload dev.bundles", e);
146                resp.sendError(500, "Unable to reload dev.bundles");
147            }
148        }
149    }
150
151}