/******************************************************************************* * Copyright (c) 2011, 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.internal.server.hosting; import org.eclipse.orion.server.core.ProtocolConstants; import org.eclipse.orion.internal.server.servlets.ServletResourceHandler; import org.eclipse.orion.server.servlets.*; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.core.runtime.*; import org.eclipse.orion.internal.server.servlets.*; import org.eclipse.orion.server.core.OrionConfiguration; import org.eclipse.orion.server.core.ServerStatus; import org.eclipse.orion.server.core.metastore.UserInfo; import org.eclipse.osgi.util.NLS; import org.json.*; /** * Processes an HTTP request for a site configuration resource. */ public class SiteConfigurationResourceHandler extends ServletResourceHandler<SiteInfo> { private final ServletResourceHandler<IStatus> statusHandler; public SiteConfigurationResourceHandler(ServletResourceHandler<IStatus> statusHandler) { this.statusHandler = statusHandler; } /** * Creates a new site configuration with the given name, a generated id, and the rest of its * properties given by <code>object</code>. * @param user User creating the SiteConfiguration * @param name Name for the SiteConfiguration * @param workspace Workspace the SiteConfiguration is associated to * @param object Object from which other properties will be drawn * @return The created SiteConfiguration. */ public static SiteInfo createFromJSON(UserInfo user, String name, String workspace, JSONObject object) throws CoreException { if (name == null || name.length() == 0) throw new CoreException(new ServerStatus(IStatus.ERROR, HttpServletResponse.SC_BAD_REQUEST, "Name is missing", null)); else if (workspace == null || name.length() == 0) throw new CoreException(new ServerStatus(IStatus.ERROR, HttpServletResponse.SC_BAD_REQUEST, "Workspace is missing", null)); SiteInfo site = SiteInfo.newSiteConfiguration(user, name, workspace); copyProperties(object, site, false); site.save(user); return site; } /** * Copies properties from a JSONObject representation of a site configuration to a SiteConfiguration * instance. * @param source JSON object to copy from. * @param target Site configuration instance to copy to. * @param copyName If <code>true</code>, the name property from <code>source</code> will overwrite * <code>target</code>'s name. * @throws CoreException if, after copying, <code>target</code> is missing a required property. */ private static void copyProperties(JSONObject source, SiteInfo target, boolean copyName) throws CoreException { if (copyName) { String name = source.optString(ProtocolConstants.KEY_NAME, null); if (name != null) target.setName(name); } String hostHint = source.optString(SiteConfigurationConstants.KEY_HOST_HINT, null); if (hostHint != null) target.setHostHint(hostHint); String workspace = source.optString(SiteConfigurationConstants.KEY_WORKSPACE, null); if (workspace != null) target.setWorkspace(workspace); JSONArray mappings = source.optJSONArray(SiteConfigurationConstants.KEY_MAPPINGS); if (mappings != null) target.setMappings(mappings); // Sanity check if (target.getName() == null || target.getName().length() == 0) throw new CoreException(new ServerStatus(IStatus.ERROR, HttpServletResponse.SC_BAD_REQUEST, "Name was not specified", null)); if (target.getWorkspace() == null || target.getWorkspace().length() == 0) throw new CoreException(new ServerStatus(IStatus.ERROR, HttpServletResponse.SC_BAD_REQUEST, "Workspace was not specified", null)); } /** * @param baseLocation The URI of the SiteConfigurationServlet. * @return Representation of <code>site</code> as a JSONObject. */ public static JSONObject toJSON(SiteInfo site, URI baseLocation) { JSONObject result = site.toJSON(); try { result.put(ProtocolConstants.KEY_LOCATION, URIUtil.append(baseLocation, site.getId())); // Note: The SiteConfigurationConstants.KEY_HOSTING_STATUS field will be contributed to the result // by the site-hosting bundle (if present) via an IWebResourceDecorator } catch (JSONException e) { // Can't happen } return result; } private boolean handleGet(HttpServletRequest req, HttpServletResponse resp, SiteInfo site) throws IOException { // Strip off the SiteConfig id from the request URI URI location = getURI(req); URI baseLocation = location.resolve(""); //$NON-NLS-1$ JSONObject result = toJSON(site, baseLocation); OrionServlet.writeJSONResponse(req, resp, result, JsonURIUnqualificationStrategy.LOCATION_ONLY); return true; } /** * Creates a new site configuration, and possibly starts it. * @param site <code>null</code> */ private boolean handlePost(HttpServletRequest req, HttpServletResponse resp, SiteInfo site) throws CoreException, IOException, JSONException { if (site != null) throw new IllegalArgumentException("Can't POST to an existing site"); UserInfo user = OrionConfiguration.getMetaStore().readUser(getUserName(req)); JSONObject requestJson = getRequestJson(req); try { site = doCreateSiteConfiguration(req, requestJson, user); changeHostingStatus(req, resp, requestJson, user, site); } catch (CoreException e) { // If starting it failed, try to clean up if (site != null) { // Remove site config site.delete(user); } throw e; } URI baseLocation = getURI(req); JSONObject result = toJSON(site, baseLocation); OrionServlet.writeJSONResponse(req, resp, result, JsonURIUnqualificationStrategy.LOCATION_ONLY); resp.setStatus(HttpServletResponse.SC_CREATED); resp.addHeader(ProtocolConstants.HEADER_LOCATION, result.getString(ProtocolConstants.KEY_LOCATION)); return true; } private boolean handlePut(HttpServletRequest req, HttpServletResponse resp, SiteInfo site) throws IOException, CoreException, JSONException { UserInfo user = OrionConfiguration.getMetaStore().readUser(getUserName(req)); JSONObject requestJson = OrionServlet.readJSONRequest(req); copyProperties(requestJson, site, true); // Start/stop the site if necessary changeHostingStatus(req, resp, requestJson, user, site); // Everything succeeded, save the changed site site.save(user); // Strip off the SiteConfig id from the request URI URI location = getURI(req); URI baseLocation = location.resolve(""); //$NON-NLS-1$ JSONObject result = toJSON(site, baseLocation); OrionServlet.writeJSONResponse(req, resp, result, JsonURIUnqualificationStrategy.LOCATION_ONLY); return true; } private boolean handleDelete(HttpServletRequest req, HttpServletResponse resp, SiteInfo site) throws CoreException { UserInfo user = OrionConfiguration.getMetaStore().readUser(getUserName(req)); ISiteHostingService hostingService = getHostingService(); IHostedSite runningSite = hostingService.get(site, user); if (runningSite != null) { String msg = NLS.bind("Site configuration is running at {0}. Must be stopped before it can be deleted", runningSite.getHost()); throw new CoreException(new ServerStatus(IStatus.ERROR, HttpServletResponse.SC_CONFLICT, msg, null)); } site.delete(user); return true; } @Override public boolean handleRequest(HttpServletRequest req, HttpServletResponse resp, SiteInfo site) throws ServletException { if (site == null && getMethod(req) != Method.POST) { return statusHandler.handleRequest(req, resp, new ServerStatus(IStatus.ERROR, HttpServletResponse.SC_NOT_FOUND, "Site configuration not found", null)); //$NON-NLS-1$ } try { switch (getMethod(req)) { case GET : return handleGet(req, resp, site); case PUT : return handlePut(req, resp, site); case POST : return handlePost(req, resp, site); case DELETE : return handleDelete(req, resp, site); default : return false; } } catch (IOException e) { return statusHandler.handleRequest(req, resp, new ServerStatus(IStatus.ERROR, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage(), e)); } catch (JSONException e) { return statusHandler.handleRequest(req, resp, new ServerStatus(IStatus.ERROR, HttpServletResponse.SC_BAD_REQUEST, "Syntax error in request", e)); } catch (CoreException e) { return statusHandler.handleRequest(req, resp, e.getStatus()); } } private static JSONObject getRequestJson(HttpServletRequest req) throws IOException, JSONException { // WebUser user = WebUser.fromUserName(getUserName(req)); return OrionServlet.readJSONRequest(req); } /** * Creates a new site configuration from the request */ private SiteInfo doCreateSiteConfiguration(HttpServletRequest req, JSONObject requestJson, UserInfo user) throws CoreException { String name = computeName(req, requestJson); String workspace = requestJson.optString(SiteConfigurationConstants.KEY_WORKSPACE, null); SiteInfo site = createFromJSON(user, name, workspace, requestJson); return site; } /** * Changes <code>site</code>'s hosting status to the desired status. The desired status * is given by the <code>HostingStatus.Status</code> field of the <code>requestJson</code>. * @param site The site configuration to act on. * @param requestJson The request body, which may or may not have a HostingStatus field. * @throws CoreException If a bogus value was found for <code>HostingStatus.Status</code>, * or if the hosting service threw an exception when trying to change the status. */ private void changeHostingStatus(HttpServletRequest req, HttpServletResponse resp, JSONObject requestJson, UserInfo user, SiteInfo site) throws CoreException { JSONObject hostingStatus = requestJson.optJSONObject(SiteConfigurationConstants.KEY_HOSTING_STATUS); if (hostingStatus == null) return; String status = hostingStatus.optString(SiteConfigurationConstants.KEY_HOSTING_STATUS_STATUS); try { if ("started".equalsIgnoreCase(status)) { //$NON-NLS-1$ String editServer = req.getScheme() + "://" + req.getHeader("Host"); //$NON-NLS-1$ //$NON-NLS-2$ getHostingService().start(site, user, editServer, new URI(req.getRequestURL().toString())); } else if ("stopped".equalsIgnoreCase(status)) { //$NON-NLS-1$ getHostingService().stop(site, user); } else if (status == null) { // No Status; ignore it } else { // Status has a bogus value throw new CoreException(new ServerStatus(IStatus.ERROR, HttpServletResponse.SC_BAD_REQUEST, NLS.bind("Status not understood: {0}", status), null)); } } catch (SiteHostingException e) { // Give a JSON response object instead of stack trace throw new CoreException(new ServerStatus(IStatus.ERROR, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage(), e)); } catch (URISyntaxException e) { // Should not happen throw new CoreException(new ServerStatus(IStatus.ERROR, HttpServletResponse.SC_BAD_REQUEST, NLS.bind("Error parsing request URL \"{0}\"", status), null)); } } /** * @throws CoreException If the site hosting service is not present. */ private static ISiteHostingService getHostingService() throws CoreException { ISiteHostingService service = HostingActivator.getDefault().getHostingService(); if (service == null) { throw new CoreException(new Status(IStatus.ERROR, Activator.PI_SERVER_SERVLETS, "Site hosting service unavailable")); } return service; } /** * Computes the name for the resource to be created by a POST operation. * @return The name, or the empty string if no name was given. */ private static String computeName(HttpServletRequest req, JSONObject requestBody) { // Try Slug first String name = req.getHeader(ProtocolConstants.HEADER_SLUG); if (name == null || name.length() == 0) { name = requestBody.optString(ProtocolConstants.KEY_NAME); } return name; } /** * Obtain and return the user name from the request headers. */ private static String getUserName(HttpServletRequest req) { return req.getRemoteUser(); } }