org.eclipse.orion.internal.server.hosting.HostedSiteServlet.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.orion.internal.server.hosting.HostedSiteServlet.java

Source

/*******************************************************************************
 * Copyright (c) 2011, 2012 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.orion.internal.server.hosting;

import java.io.IOException;
import java.net.*;
import java.util.*;
import java.util.Map.Entry;
import javax.servlet.ServletException;
import javax.servlet.http.*;
import org.eclipse.core.filesystem.IFileStore;
import org.eclipse.core.runtime.*;
import org.eclipse.jetty.servlets.ProxyServlet;
import org.eclipse.orion.internal.server.servlets.Activator;
import org.eclipse.orion.internal.server.servlets.ServletResourceHandler;
import org.eclipse.orion.internal.server.servlets.file.ServletFileStoreHandler;
import org.eclipse.orion.internal.server.servlets.hosting.IHostedSite;
import org.eclipse.orion.internal.server.servlets.workspace.WebProject;
import org.eclipse.orion.internal.server.servlets.workspace.WebWorkspace;
import org.eclipse.orion.internal.server.servlets.workspace.authorization.AuthorizationService;
import org.eclipse.orion.server.core.LogHelper;
import org.eclipse.orion.server.core.ServerStatus;
import org.eclipse.orion.server.servlets.OrionServlet;
import org.eclipse.osgi.util.NLS;
import org.json.JSONException;

/**
 * Handles requests for URIs that are part of a running hosted site.
 */
public class HostedSiteServlet extends OrionServlet {

    static class LocationHeaderServletResponseWrapper extends HttpServletResponseWrapper {

        private static final String LOCATION = "Location";
        private HttpServletRequest request;
        private IHostedSite site;

        public LocationHeaderServletResponseWrapper(HttpServletRequest request, HttpServletResponse response,
                IHostedSite site) {
            super(response);
            this.request = request;
            this.site = site;
        }

        private String mapLocation(String location) {
            Map<String, List<String>> mappings = site.getMappings();
            String bestMatch = "";
            String prefix = null;
            for (Iterator<Entry<String, List<String>>> iterator = mappings.entrySet().iterator(); iterator
                    .hasNext();) {
                Entry<String, List<String>> entry = iterator.next();
                List<String> candidates = entry.getValue();
                for (Iterator<String> candidateIt = candidates.iterator(); candidateIt.hasNext();) {
                    String candidate = candidateIt.next();
                    if (location.startsWith(candidate) && candidate.length() > bestMatch.length()) {
                        bestMatch = candidate;
                        prefix = entry.getKey();
                    }
                }
            }
            if (prefix != null) {
                String suffix = location.substring(bestMatch.length());
                String separator = suffix.length() > 0 && !prefix.endsWith("/") && !suffix.startsWith("/") ? "/"
                        : "";
                String reverseMappedPath = prefix + separator + suffix;
                try {
                    URI pathlessRequestURI = new URI(request.getScheme(), null, request.getServerName(),
                            request.getServerPort(), null, null, null);
                    return pathlessRequestURI.toString() + reverseMappedPath;
                } catch (URISyntaxException t) {
                    // best effort
                    System.err.println(t);
                }
            }
            return location;
        }

        @Override
        public void addHeader(String name, String value) {
            if (name.equals(LOCATION)) {
                String newLocation = mapLocation(value.trim());
                super.addHeader(name, newLocation);
            } else {
                super.addHeader(name, value);
            }
        }

        @Override
        public void setHeader(String name, String value) {
            if (name.equals(LOCATION)) {
                String newLocation = mapLocation(value.trim());
                super.setHeader(name, newLocation);
            } else {
                super.setHeader(name, value);
            }
        }
    }

    private static final long serialVersionUID = 1L;

    private static final String WORKSPACE_SERVLET_ALIAS = "/workspace"; //$NON-NLS-1$
    private static final String FILE_SERVLET_ALIAS = "/file"; //$NON-NLS-1$

    // FIXME these variables are copied from fileservlet
    private ServletResourceHandler<IFileStore> fileSerializer;
    private final URI rootStoreURI;

    public HostedSiteServlet() {
        rootStoreURI = Activator.getDefault().getRootLocationURI();
    }

    @Override
    public void init() throws ServletException {
        super.init();
        fileSerializer = new ServletFileStoreHandler(rootStoreURI, getStatusHandler(), getServletContext());
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // Handled by service()
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // Handled by service()
    }

    @Override
    protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // Handled by service()
    }

    @Override
    protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // Handled by service()
    }

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        traceRequest(req);
        String pathInfoString = req.getPathInfo();
        IPath pathInfo = new Path(null /*don't parse host:port as device*/,
                pathInfoString == null ? "" : pathInfoString); //$NON-NLS-1$
        if (pathInfo.segmentCount() > 0) {
            String hostedHost = pathInfo.segment(0);
            IHostedSite site = HostingActivator.getDefault().getHostingService().get(hostedHost);
            if (site != null) {
                IPath path = pathInfo.removeFirstSegments(1);
                IPath contextPath = new Path(req.getContextPath());
                IPath contextlessPath = path.makeRelativeTo(contextPath).makeAbsolute();
                URI[] mappedPaths;
                try {
                    mappedPaths = getMapped(site, contextlessPath, req.getQueryString());
                } catch (URISyntaxException e) {
                    handleException(resp, new ServerStatus(IStatus.ERROR, HttpServletResponse.SC_BAD_REQUEST,
                            "Could not create target URI   ", e));
                    return;
                }
                if (mappedPaths != null) {
                    serve(req, resp, site, mappedPaths);
                } else {
                    handleException(resp, new ServerStatus(IStatus.ERROR, HttpServletResponse.SC_NOT_FOUND,
                            NLS.bind("No mappings matched {0}", path), null));
                }
            } else {
                resp.setHeader("Cache-Control", "no-cache"); //$NON-NLS-1$ //$NON-NLS-2$
                String msg = NLS.bind("Hosted site {0} is stopped", hostedHost);
                handleException(resp, new ServerStatus(IStatus.ERROR, HttpServletResponse.SC_NOT_FOUND, msg, null));
            }
        } else {
            super.doGet(req, resp);
        }
    }

    /**
     * Returns paths constructed by rewriting pathInfo using rules from the hosted site's mappings.
     * Paths are ordered from most to least specific match.
     * @param site The hosted site.
     * @param pathInfo Path to be rewritten.
     * @param queryString 
     * @return The rewritten path. May be either:<ul>
     * <li>A path to a file in the Orion workspace, eg. <code>/ProjectA/foo/bar.txt</code></li>
     * <li>An absolute URL pointing to another site, eg. <code>http://foo.com/bar.txt</code></li>
     * </ul>
     * @return The rewritten paths. 
     * @throws URISyntaxException 
     */
    private URI[] getMapped(IHostedSite site, IPath pathInfo, String queryString) throws URISyntaxException {
        final Map<String, List<String>> map = site.getMappings();
        final IPath originalPath = pathInfo;
        IPath path = originalPath.removeTrailingSeparator();

        List<URI> uris = new ArrayList<URI>();
        String rest = null;
        final int count = path.segmentCount();
        for (int i = 0; i <= count; i++) {
            List<String> base = map.get(path.toString());
            if (base != null) {
                rest = originalPath.removeFirstSegments(count - i).toString();
                for (int j = 0; j < base.size(); j++) {
                    URI uri = (rest.equals("") || rest.equals("/")) ? new URI(base.get(j))
                            : URIUtil.append(new URI(base.get(j)), rest);
                    uris.add(createUri(uri, queryString));
                }
            }
            path = path.removeLastSegments(1);
        }
        if (uris.size() == 0)
            // No mapping for /
            return null;
        else
            return uris.toArray(new URI[uris.size()]);
    }

    // Returns a copy of uri with queryString giving the query component. The query is not decoded.
    private URI createUri(URI uri, String queryString) throws URISyntaxException {
        String queryPart = queryString == null ? "" : "?" + queryString; //$NON-NLS-1$ //$NON-NLS-2$
        String fragmentPart = uri.getFragment() == null ? "" : "#" + uri.getRawFragment();//$NON-NLS-1$ //$NON-NLS-2$
        URI pathonly = new URI(uri.getScheme(), uri.getUserInfo(), uri.getHost(), uri.getPort(), uri.getPath(),
                null, null);
        StringBuilder buf = new StringBuilder();
        buf.append(pathonly.toString()).append(queryPart).append(fragmentPart);
        return new URI(buf.toString());
    }

    private void serve(HttpServletRequest req, HttpServletResponse resp, IHostedSite site, URI[] mappedURIs)
            throws ServletException, IOException {
        for (int i = 0; i < mappedURIs.length; i++) {
            URI uri = mappedURIs[i];
            // Bypass a 404 if any workspace or remote paths remain to be checked.
            boolean failEarlyOn404 = i + 1 < mappedURIs.length;
            if (uri.getScheme() == null) {
                if ("GET".equals(req.getMethod())) { //$NON-NLS-1$
                    if (serveOrionFile(req, resp, site, new Path(uri.getPath()), failEarlyOn404))
                        return;
                } else {
                    String message = "Only GET method is supported for workspace paths";
                    handleException(resp, new ServerStatus(IStatus.ERROR, HttpServletResponse.SC_METHOD_NOT_ALLOWED,
                            NLS.bind(message, mappedURIs), null));
                    return;
                }
            } else {
                if (proxyRemotePath(req, new LocationHeaderServletResponseWrapper(req, resp, site), uri,
                        failEarlyOn404))
                    return;
            }
        }
    }

    // returns true if the request has been served, false if not (only if failEarlyOn404 is true)
    private boolean serveOrionFile(HttpServletRequest req, HttpServletResponse resp, IHostedSite site, IPath path,
            boolean failEarlyOn404) throws ServletException {
        String userId = site.getUserId();
        String workspaceId = site.getWorkspaceId();
        String workspaceUri = WORKSPACE_SERVLET_ALIAS + "/" + workspaceId; //$NON-NLS-1$
        String fileURI = FILE_SERVLET_ALIAS + path.toString();
        boolean allow = false;
        // Check that user who launched the hosted site really has access to the workspace
        try {
            if (AuthorizationService.checkRights(userId, workspaceUri, "GET")) { //$NON-NLS-1$
                boolean fileMatch = AuthorizationService.checkRights(userId, fileURI, "GET"); //$NON-NLS-1$
                boolean dirMatch = fileURI.endsWith("/") //$NON-NLS-1$
                        && AuthorizationService.checkRights(userId, fileURI, "GET"); //$NON-NLS-1$
                if (fileMatch || dirMatch) {
                    allow = true;
                } else {
                    handleException(resp, new ServerStatus(IStatus.ERROR, HttpServletResponse.SC_FORBIDDEN,
                            NLS.bind("No rights to access {0}", fileURI), null));
                }
            } else {
                handleException(resp, new ServerStatus(IStatus.ERROR, HttpServletResponse.SC_FORBIDDEN,
                        NLS.bind("No rights to access {0}", workspaceUri), null));
            }
        } catch (JSONException e) {
            throw new ServletException(e);
        }

        if (allow) {
            // FIXME: this code is copied from NewFileServlet, fix it
            // start copied
            String pathInfo = path.toString();
            IPath filePath = pathInfo == null ? Path.ROOT : new Path(pathInfo);
            IFileStore file = tempGetFileStore(filePath);
            if (file == null || !file.fetchInfo().exists()) {
                if (failEarlyOn404) {
                    return false;
                }
                handleException(resp,
                        new ServerStatus(IStatus.ERROR, 404, NLS.bind("File not found: {0}", filePath), null));
                return true;
            }
            if (fileSerializer.handleRequest(req, resp, file)) {
                //return;
            }
            // end copied

            if (file != null) {
                addEditHeaders(resp, site, path);
                addContentTypeHeader(resp, file.getName());
            }
        }
        return true;
    }

    private void addEditHeaders(HttpServletResponse resp, IHostedSite site, IPath path) {
        resp.addHeader("X-Edit-Server", site.getEditServerUrl() + "/edit/edit.html#"); //$NON-NLS-1$ //$NON-NLS-2$
        resp.addHeader("X-Edit-Token", FILE_SERVLET_ALIAS + path.toString()); //$NON-NLS-1$
    }

    private void addContentTypeHeader(HttpServletResponse resp, String filename) {
        if (filename != null) {
            String mimeType = getServletContext().getMimeType(filename);
            if (mimeType != null)
                resp.addHeader("Content-Type", mimeType);
        }
    }

    // temp code for grabbing files from filesystem
    protected IFileStore tempGetFileStore(IPath path) {
        //path format is /workspaceId/projectName/[suffix]
        if (path.segmentCount() <= 1)
            return null;
        WebWorkspace workspace = WebWorkspace.fromId(path.segment(0));
        WebProject project = workspace.getProjectByName(path.segment(1));
        if (project == null)
            return null;
        try {
            return project.getProjectStore().getFileStore(path.removeFirstSegments(2));
        } catch (CoreException e) {
            LogHelper.log(new Status(IStatus.WARNING, Activator.PI_SERVER_SERVLETS, 1,
                    NLS.bind("An error occurred when getting file store for path {0}", path), e));
            // fallback and return null
        }
        return null;
    }

    /**
     * @return true if the request was served.
     */
    private boolean proxyRemotePath(HttpServletRequest req, HttpServletResponse resp, URI remoteURI,
            boolean failEarlyOn404) throws IOException, ServletException, UnknownHostException {
        try {
            return proxyRemoteUrl(req, resp, new URL(remoteURI.toString()), failEarlyOn404);
        } catch (MalformedURLException e) {
            String message = NLS.bind("Malformed remote URL: {0}", remoteURI.toString());
            handleException(resp, new ServerStatus(IStatus.ERROR, HttpServletResponse.SC_NOT_FOUND, message, e));
        } catch (UnknownHostException e) {
            String message = NLS.bind("Unknown host {0}", e.getMessage());
            handleException(resp, new ServerStatus(IStatus.ERROR, HttpServletResponse.SC_NOT_FOUND, message, e));
        } catch (Exception e) {
            String message = NLS.bind("An error occurred while retrieving {0}", remoteURI.toString());
            handleException(resp,
                    new ServerStatus(IStatus.ERROR, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, message, e));
        }
        return true;
    }

    /**
     * @return true if the request was served.
     */
    private boolean proxyRemoteUrl(HttpServletRequest req, HttpServletResponse resp, final URL mappedURL,
            boolean failEarlyOn404) throws IOException, ServletException, UnknownHostException {
        ProxyServlet proxy = new RemoteURLProxyServlet(mappedURL, failEarlyOn404);
        proxy.init(getServletConfig());
        try {
            // TODO: May want to avoid console noise from 4xx response codes?
            traceRequest(req);
            try {
                proxy.service(req, resp);
            } catch (NotFoundException ex) {
                // This exception is only thrown in the "fail early on 404" case, in which case
                // no output was written and we didn't serve the request.
                return false;
            }
        } finally {
            proxy.destroy();
        }
        // We served this request
        return true;
    }

}