org.eclipse.orion.internal.server.servlets.file.DirectoryHandlerV1.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.orion.internal.server.servlets.file.DirectoryHandlerV1.java

Source

/*******************************************************************************
 * Copyright (c) 2010, 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.servlets.file;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.*;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.core.filesystem.*;
import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.URIUtil;
import org.eclipse.orion.internal.server.servlets.ProtocolConstants;
import org.eclipse.orion.internal.server.servlets.ServletResourceHandler;
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.*;

/**
 * Handles HTTP requests against directories for eclipse web protocol version
 * 1.0.
 */
public class DirectoryHandlerV1 extends ServletResourceHandler<IFileStore> {
    static final int CREATE_COPY = 0x1;
    static final int CREATE_MOVE = 0x2;
    static final int CREATE_NO_OVERWRITE = 0x4;

    private final ServletResourceHandler<IStatus> statusHandler;

    public DirectoryHandlerV1(ServletResourceHandler<IStatus> statusHandler) {
        this.statusHandler = statusHandler;
    }

    private boolean handleGet(HttpServletRequest request, HttpServletResponse response, IFileStore dir)
            throws IOException, CoreException {
        URI location = getURI(request);
        JSONObject result = ServletFileStoreHandler.toJSON(dir, dir.fetchInfo(EFS.NONE, null), location);
        String depthString = request.getParameter(ProtocolConstants.PARM_DEPTH);
        int depth = 0;
        if (depthString != null) {
            try {
                depth = Integer.parseInt(depthString);
            } catch (NumberFormatException e) {
                // ignore
            }
        }
        encodeChildren(dir, location, result, depth);
        OrionServlet.writeJSONResponse(request, response, result);
        return true;
    }

    private void encodeChildren(IFileStore dir, URI location, JSONObject result, int depth) throws CoreException {
        if (depth <= 0)
            return;
        JSONArray children = new JSONArray();
        //more efficient to ask for child information in bulk for certain file systems
        IFileInfo[] childInfos = dir.childInfos(EFS.NONE, null);
        for (IFileInfo childInfo : childInfos) {
            IFileStore childStore = dir.getChild(childInfo.getName());
            String name = childInfo.getName();
            if (childInfo.isDirectory())
                name += "/"; //$NON-NLS-1$
            URI childLocation = URIUtil.append(location, name);
            JSONObject childResult = ServletFileStoreHandler.toJSON(childStore, childInfo, childLocation);
            if (childInfo.isDirectory())
                encodeChildren(childStore, childLocation, childResult, depth - 1);
            children.put(childResult);
        }
        try {
            result.put(ProtocolConstants.KEY_CHILDREN, children);
        } catch (JSONException e) {
            // cannot happen
            throw new RuntimeException(e);
        }
    }

    private boolean handlePost(HttpServletRequest request, HttpServletResponse response, IFileStore dir)
            throws JSONException, CoreException, ServletException, IOException {
        //setup and precondition checks
        JSONObject requestObject = OrionServlet.readJSONRequest(request);
        String name = computeName(request, requestObject);
        if (name.length() == 0)
            return statusHandler.handleRequest(request, response, new ServerStatus(IStatus.ERROR,
                    HttpServletResponse.SC_BAD_REQUEST, "File name not specified.", null));
        IFileStore toCreate = dir.getChild(name);
        if (!name.equals(toCreate.getName()))
            return statusHandler.handleRequest(request, response, new ServerStatus(IStatus.ERROR,
                    HttpServletResponse.SC_BAD_REQUEST, "Bad file name: " + name, null));
        int options = getCreateOptions(request);
        boolean destinationExists = toCreate.fetchInfo(EFS.NONE, null).exists();
        if (!validateOptions(request, response, toCreate, destinationExists, options))
            return true;
        //perform the operation
        if (performPost(request, response, requestObject, toCreate, options)) {
            //write the response
            URI location = URIUtil.append(getURI(request), name);
            JSONObject result = ServletFileStoreHandler.toJSON(toCreate, toCreate.fetchInfo(EFS.NONE, null),
                    location);
            OrionServlet.writeJSONResponse(request, response, result);
            response.setHeader(ProtocolConstants.HEADER_LOCATION,
                    ServletResourceHandler.resovleOrionURI(request, location).toString());
            //response code should indicate if a new resource was actually created or not
            response.setStatus(destinationExists ? HttpServletResponse.SC_OK : HttpServletResponse.SC_CREATED);
        }
        return true;
    }

    /**
     * Performs the actual modification corresponding to a POST request. All preconditions
     * are assumed to be satisfied.
     * @return <code>true</code> if the operation was successful, and <code>false</code> otherwise.
     */
    private boolean performPost(HttpServletRequest request, HttpServletResponse response, JSONObject requestObject,
            IFileStore toCreate, int options) throws CoreException, IOException, ServletException {
        boolean isCopy = (options & CREATE_COPY) != 0;
        boolean isMove = (options & CREATE_MOVE) != 0;
        try {
            if (isCopy || isMove)
                return performCopyMove(request, response, requestObject, toCreate, isCopy, options);
            if (requestObject.optBoolean(ProtocolConstants.KEY_DIRECTORY))
                toCreate.mkdir(EFS.NONE, null);
            else
                toCreate.openOutputStream(EFS.NONE, null).close();
        } catch (CoreException e) {
            IStatus status = e.getStatus();
            if (status != null && status.getCode() == EFS.ERROR_WRITE) {
                // Sanitize message, as it might contain the filepath.
                statusHandler.handleRequest(request, response,
                        new ServerStatus(IStatus.ERROR, HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
                                "Failed to create: " + toCreate.getName(), null));
                return false;
            }
            throw e;
        }
        return true;
    }

    /**
     * Perform a copy or move as specified by the request.
     * @return <code>true</code> if the operation was successful, and <code>false</code> otherwise.
     */
    private boolean performCopyMove(HttpServletRequest request, HttpServletResponse response,
            JSONObject requestObject, IFileStore toCreate, boolean isCopy, int options)
            throws ServletException, CoreException {
        String locationString = requestObject.optString(ProtocolConstants.KEY_LOCATION, null);
        if (locationString == null) {
            statusHandler.handleRequest(request, response, new ServerStatus(IStatus.ERROR,
                    HttpServletResponse.SC_BAD_REQUEST, "Copy or move request must specify source location", null));
            return false;
        }
        try {
            IFileStore source = resolveSourceLocation(request, locationString);
            if (source == null) {
                statusHandler.handleRequest(request, response,
                        new ServerStatus(IStatus.ERROR, HttpServletResponse.SC_NOT_FOUND,
                                NLS.bind("Source does not exist: ", locationString), null));
                return false;
            }
            boolean allowOverwrite = (options & CREATE_NO_OVERWRITE) == 0;
            int efsOptions = allowOverwrite ? EFS.OVERWRITE : EFS.NONE;
            try {
                if (isCopy)
                    source.copy(toCreate, efsOptions, null);
                else
                    source.move(toCreate, efsOptions, null);
            } catch (CoreException e) {
                if (!source.fetchInfo(EFS.NONE, null).exists()) {
                    statusHandler.handleRequest(request, response,
                            new ServerStatus(IStatus.ERROR, HttpServletResponse.SC_NOT_FOUND,
                                    NLS.bind("Source does not exist: ", locationString), e));
                    return false;
                }
                if (e.getStatus().getCode() == EFS.ERROR_EXISTS) {
                    statusHandler.handleRequest(request, response,
                            new ServerStatus(IStatus.ERROR, HttpServletResponse.SC_PRECONDITION_FAILED,
                                    "A file or folder with the same name already exists at this location", null));
                    return false;
                }
                //just rethrow if we can't do something more specific
                throw e;
            }
        } catch (URISyntaxException e) {
            statusHandler.handleRequest(request, response,
                    new ServerStatus(IStatus.ERROR, HttpServletResponse.SC_BAD_REQUEST,
                            NLS.bind("Bad source location in request: ", locationString), e));
            return false;
        }
        return true;
    }

    /**
     * Maps the client-facing location URL of a file or directory back to the local
     * file system path on the server. Returns <code>null</code> if the
     * location could not be resolved to a local file system location.
     */
    private IFileStore resolveSourceLocation(HttpServletRequest request, String locationString)
            throws URISyntaxException, CoreException {
        URI sourceLocation = new URI(locationString);
        //resolve relative URI against request URI
        String sourcePath = sourceLocation.getPath().substring(request.getContextPath().length());
        //first segment is the servlet path
        IPath path = new Path(sourcePath).removeFirstSegments(1);
        return NewFileServlet.getFileStore(request, path);
    }

    private static String decodeSlug(String slug) {
        if (slug == null)
            return null;
        try {
            return URLDecoder.decode(slug.replace("+", "%2B"), "UTF-8");
        } catch (IllegalArgumentException e) {
            // Malformed Slug
        } catch (UnsupportedEncodingException e) {
            // Should not happen
        }
        return null;
    }

    /**
     * Computes the name of the resource to be created by a POST operation. Returns
     * an empty string if the name was not specified.
     */
    private String computeName(HttpServletRequest request, JSONObject requestObject) {
        String name = decodeSlug(request.getHeader(ProtocolConstants.HEADER_SLUG));

        //next comes the source location for a copy/move
        if (name == null || name.length() == 0) {
            String location = requestObject.optString(ProtocolConstants.KEY_LOCATION);
            int lastSlash = location.lastIndexOf('/');
            if (lastSlash >= 0)
                name = location.substring(lastSlash + 1);
        }
        //finally use the name attribute from the request body
        if (name == null || name.length() == 0)
            name = requestObject.optString(ProtocolConstants.KEY_NAME);
        return name;
    }

    /**
     * Asserts that request options are valid. If options are not valid then this method handles the request response and return false. If the options
     * are valid this method return true.
     */
    private boolean validateOptions(HttpServletRequest request, HttpServletResponse response, IFileStore toCreate,
            boolean destinationExists, int options) throws ServletException {
        //operation cannot be both copy and move
        int copyMove = CREATE_COPY | CREATE_MOVE;
        if ((options & copyMove) == copyMove) {
            statusHandler.handleRequest(request, response, new ServerStatus(IStatus.ERROR,
                    HttpServletResponse.SC_BAD_REQUEST, "Syntax error in request", null));
            return false;
        }
        //if overwrite is disallowed make sure destination does not exist yet
        boolean noOverwrite = (options & CREATE_NO_OVERWRITE) != 0;
        //for copy/move case, let the implementation check for overwrite because pre-validating is complicated
        if ((options & copyMove) == 0 && noOverwrite && destinationExists) {
            statusHandler.handleRequest(request, response,
                    new ServerStatus(IStatus.ERROR, HttpServletResponse.SC_PRECONDITION_FAILED,
                            "A file or folder with the same name already exists at this location", null));
            return false;
        }
        return true;
    }

    /**
     * Returns a bit-mask of create options as specified by the request.
     */
    private int getCreateOptions(HttpServletRequest request) {
        int result = 0;
        String optionString = request.getHeader(ProtocolConstants.HEADER_CREATE_OPTIONS);
        if (optionString != null) {
            for (String option : optionString.split(",")) { //$NON-NLS-1$
                if (ProtocolConstants.OPTION_COPY.equalsIgnoreCase(option))
                    result |= CREATE_COPY;
                else if (ProtocolConstants.OPTION_MOVE.equalsIgnoreCase(option))
                    result |= CREATE_MOVE;
                else if (ProtocolConstants.OPTION_NO_OVERWRITE.equalsIgnoreCase(option))
                    result |= CREATE_NO_OVERWRITE;
            }
        }
        return result;
    }

    private boolean handleDelete(HttpServletRequest request, HttpServletResponse response, IFileStore dir)
            throws JSONException, CoreException, ServletException, IOException {
        dir.delete(EFS.NONE, null);
        return true;
    }

    private boolean handlePut(HttpServletRequest request, HttpServletResponse response, IFileStore dir)
            throws JSONException, IOException, CoreException {
        IFileInfo info = ServletFileStoreHandler.fromJSON(request);
        dir.putInfo(info, EFS.NONE, null);
        return true;
    }

    @Override
    public boolean handleRequest(HttpServletRequest request, HttpServletResponse response, IFileStore dir)
            throws ServletException {
        try {
            switch (getMethod(request)) {
            case GET:
                return handleGet(request, response, dir);
            case PUT:
                return handlePut(request, response, dir);
            case POST:
                return handlePost(request, response, dir);
            case DELETE:
                return handleDelete(request, response, dir);
            }
        } catch (JSONException e) {
            return statusHandler.handleRequest(request, response, new ServerStatus(IStatus.ERROR,
                    HttpServletResponse.SC_BAD_REQUEST, "Syntax error in request", e));
        } catch (CoreException e) {
            //core exception messages are designed for end user consumption, so use message directly
            return statusHandler.handleRequest(request, response, new ServerStatus(IStatus.ERROR,
                    HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage(), e));
        } catch (Exception e) {
            if (handleAuthFailure(request, response, e))
                return true;
            //the exception message is probably not appropriate for end user consumption
            LogHelper.log(e);
            return statusHandler.handleRequest(request, response, new ServerStatus(IStatus.ERROR,
                    HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
                    "An unknown failure occurred. Consult your server log or contact your system administrator.",
                    e));
        }
        return false;
    }
}