/******************************************************************************* * Copyright (c) 2013 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.server.cf.servlets; import java.io.InputStreamReader; import java.net.URLDecoder; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IStatus; import org.eclipse.orion.internal.server.servlets.ServletResourceHandler; import org.eclipse.orion.internal.server.servlets.task.TaskJobHandler; import org.eclipse.orion.server.cf.jobs.CFJob; import org.eclipse.orion.server.cf.objects.CFObject; import org.eclipse.orion.server.core.ServerStatus; import org.eclipse.orion.server.servlets.JsonURIUnqualificationStrategy; import org.eclipse.osgi.util.NLS; import org.json.JSONObject; import org.json.JSONTokener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * An abstract REST object handler. The class contains a set of default helper methods * for common functions, e. g. resource extraction, error handing, etc. */ public abstract class AbstractRESTHandler<T extends CFObject> extends ServletResourceHandler<String> { private final Logger logger = LoggerFactory.getLogger("org.eclipse.orion.server.cf"); //$NON-NLS-1$ protected ServletResourceHandler<IStatus> statusHandler; public AbstractRESTHandler(ServletResourceHandler<IStatus> statusHandler) { this.statusHandler = statusHandler; } /** * Builds the handled resource according to custom criteria. * @param request The request object used to build the resource. * @param path Path suffix required to handle the request. * @throws CoreException if the resource cannot be constructed due to an internal server error. * @return <code>null</code> if the resource cannot be constructed due to invalid request, the resource object otherwise. */ protected abstract T buildResource(HttpServletRequest request, String path) throws CoreException; /** * Helper method for PUT data extraction. * @param request The PUT requested to be processed. * @return The extracted data JSON or null if none provided or invalid JSON format. */ protected JSONObject extractJSONData(final HttpServletRequest request) { try { JSONTokener tokener = new JSONTokener(new InputStreamReader(request.getInputStream())); return new JSONObject(tokener); } catch (Exception ex) { return null; } } protected JSONObject extractJSONData(final String param) { try { return param != null ? new JSONObject(URLDecoder.decode(param, "UTF8")) : null; } catch (Exception e) { return null; } } /** * Handles a GET request. Note this method is meant to be overridden in descendant classes. * @param request The GET request being handled. * @param response The response associated with the request. * @param path Path suffix required to handle the request. * @return A {@link JazzJob} which returns the requested resource on completion. */ protected CFJob handleGet(T resource, final HttpServletRequest request, final HttpServletResponse response, final String path) { return new CFJob(request, false) { @Override protected IStatus performJob() { String msg = NLS.bind("Failed to handle request for {0}", path); //$NON-NLS-1$ return new ServerStatus(IStatus.ERROR, HttpServletResponse.SC_NOT_IMPLEMENTED, msg, null); } }; } /** * Handles an idempotent PUT request. Note this method is meant to be overridden in descendant classes. * @param request The PUT request being handled. * @param response The response associated with the request. * @param path Path suffix required to handle the request. * @return A {@link JazzJob} which returns the PUT request result on completion. */ protected CFJob handlePut(T resource, final HttpServletRequest request, final HttpServletResponse response, final String path) { return new CFJob(request, false) { @Override protected IStatus performJob() { String msg = NLS.bind("Failed to handle request for {0}", path); //$NON-NLS-1$ return new ServerStatus(IStatus.ERROR, HttpServletResponse.SC_NOT_IMPLEMENTED, msg, null); } }; } /** * Handles a POST request. Note this method is meant to be overridden in descendant classes. * The POST request is not idempotent as PUT, although it may handle similar operations. * @param request The POST request being handled. * @param response The response associated with the request. * @param path Path suffix required to handle the request. * @return A {@link JazzJob} which returns the POST request result on completion. */ protected CFJob handlePost(final T resource, final HttpServletRequest request, final HttpServletResponse response, final String path) { return new CFJob(request, false) { @Override protected IStatus performJob() { String msg = NLS.bind("Failed to handle request for {0}", path); //$NON-NLS-1$ return new ServerStatus(IStatus.ERROR, HttpServletResponse.SC_NOT_IMPLEMENTED, msg, null); } }; } /** * Handles a DELETE request. Note this method is meant to be overridden in descendant classes. * @param request The DELETE request being handled. * @param response The response associated with the request. * @param path Path suffix required to handle the request. * @return A {@link JazzJob} which returns the DELETE request result on completion. */ protected CFJob handleDelete(T resource, final HttpServletRequest request, final HttpServletResponse response, final String path) { return new CFJob(request, false) { @Override protected IStatus performJob() { String msg = NLS.bind("Failed to handle request for {0}", path); //$NON-NLS-1$ return new ServerStatus(IStatus.ERROR, HttpServletResponse.SC_NOT_IMPLEMENTED, msg, null); } }; } /** * A helper method which handles returning a 404 HTTP error status. * @param request The request being handled. * @param response The response associated with the request. * @param path Path mapped to the handled request. Used to display the error message. * @return <code>true</code> iff the 404 has been sent, <code>false</code> otherwise. */ protected boolean handleNotExistingResource(HttpServletRequest request, HttpServletResponse response, String path) throws ServletException { String msg = NLS.bind("Failed to handle request for {0}", path); //$NON-NLS-1$ ServerStatus status = new ServerStatus(IStatus.ERROR, HttpServletResponse.SC_NOT_FOUND, msg, null); return statusHandler.handleRequest(request, response, status); } /** * A helper method which handles returning a 409 HTTP error status. * @param request The request being handled. * @param response The response associated with the request. * @param path Path mapped to the handled request. Used to display the error message. * @return <code>true</code> iff the 409 has been sent, <code>false</code> otherwise. */ protected boolean handleConflictingResource(HttpServletRequest request, HttpServletResponse response, String path) throws ServletException { String msg = NLS.bind("Failed to handle request for {0}", path); //$NON-NLS-1$ ServerStatus status = new ServerStatus(IStatus.ERROR, HttpServletResponse.SC_CONFLICT, msg, null); return statusHandler.handleRequest(request, response, status); } @Override public boolean handleRequest(HttpServletRequest request, HttpServletResponse response, String path) throws ServletException { try { /* build the request resource */ T resource = buildResource(request, path); switch (getMethod(request)) { case GET : CFJob getJob = handleGet(resource, request, response, path); return TaskJobHandler.handleTaskJob(request, response, getJob, statusHandler, JsonURIUnqualificationStrategy.LOCATION_ONLY); case PUT : CFJob putJob = handlePut(resource, request, response, path); return TaskJobHandler.handleTaskJob(request, response, putJob, statusHandler, JsonURIUnqualificationStrategy.LOCATION_ONLY); case POST : CFJob postJob = handlePost(resource, request, response, path); return TaskJobHandler.handleTaskJob(request, response, postJob, statusHandler, JsonURIUnqualificationStrategy.LOCATION_ONLY); case DELETE : CFJob deleteJob = handleDelete(resource, request, response, path); return TaskJobHandler.handleTaskJob(request, response, deleteJob, statusHandler, JsonURIUnqualificationStrategy.LOCATION_ONLY); default : /* we don't know how to handle this request */ return false; } } catch (Exception e) { String msg = NLS.bind("Failed to handle request for {0}", path); //$NON-NLS-1$ ServerStatus status = new ServerStatus(IStatus.ERROR, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, msg, e); logger.error(msg, e); return statusHandler.handleRequest(request, response, status); } } }