Package org.apache.ace.client.rest

Source Code of org.apache.ace.client.rest.RESTClientServlet

/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements.  See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership.  The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License.  You may obtain a copy of the License at
*
*   http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied.  See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.ace.client.rest;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Dictionary;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

import org.apache.ace.client.repository.RepositoryObject;
import org.apache.ace.client.repository.stateful.StatefulTargetObject;
import org.apache.ace.client.workspace.Workspace;
import org.apache.ace.client.workspace.WorkspaceManager;
import org.apache.ace.feedback.Event;
import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.cm.ManagedService;
import org.osgi.service.log.LogService;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;

/**
* Servlet that offers a REST client API.
*/
public class RESTClientServlet extends HttpServlet implements ManagedService, HttpSessionListener {
  private static final String SESSION_KEY_WORKSPACES = "workspaces";
  /** Timeout in seconds for REST sessions. */
    private static final String KEY_SESSION_TIMEOUT = "session.timeout";
    private static final int DEFAULT_SESSION_TIMEOUT = 300; // in seconds.
   
    /** Alias that redirects to the latest version automatically. */
    private static final String LATEST_FOLDER = "latest";
    /** Name of the folder where working copies are kept. */
    private static final String WORK_FOLDER = "work";

    /** The action name for approving targets. */
    private static final String ACTION_APPROVE = "approve";
    /** The action name for registering targets. */
    private static final String ACTION_REGISTER = "register";
    /** The action name for reading audit events. */
    private static final String ACTION_AUDITEVENTS = "auditEvents";

    private volatile LogService m_logger;

    private volatile WorkspaceManager m_workspaceManager;
   
    private volatile int m_sessionTimeout = DEFAULT_SESSION_TIMEOUT;

    private final Gson m_gson;

    /**
     * Creates a new {@link RESTClientServlet} instance.
     */
    public RESTClientServlet() {
        m_gson = (new GsonBuilder())
            .registerTypeHierarchyAdapter(RepositoryObject.class, new RepositoryObjectSerializer())
            .registerTypeHierarchyAdapter(Event.class, new LogEventSerializer())
            .create();       
    }

    /**
     * Builds a URL path from the supplied elements. Each individual element is URL encoded.
     *
     * @param elements the elements
     * @return the URL path
     */
    String buildPathFromElements(String... elements) {
        StringBuilder result = new StringBuilder();
        for (String element : elements) {
            if (result.length() > 0) {
                result.append('/');
            }
            result.append(urlEncode(element));
        }
        return result.toString();
    }

    /**
     * Returns the separate path parts from the request, and URL decodes them.
     *
     * @param req the request
     * @return the separate path parts
     */
    String[] getPathElements(HttpServletRequest req) {
        String path = req.getPathInfo();
        if (path == null) {
            return new String[0];
        }
        if (path.startsWith("/") && path.length() > 1) {
            path = path.substring(1);
        }
        if (path.endsWith("/") && path.length() > 1) {
            path = path.substring(0, path.length() - 1);
        }
       
        String[] pathElements = path.split("/");
        for (int i = 0; i < pathElements.length; i++) {
            pathElements[i] = urlDecode(pathElements[i]);
        }
       
        return pathElements;
    }

    /**
     * @see javax.servlet.http.HttpServlet#doDelete(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
     */
    @Override
    protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
      HttpSession session = getSession(req);
        String[] pathElements = getPathElements(req);
        if (pathElements == null || pathElements.length < 1 || !WORK_FOLDER.equals(pathElements[0])) {
            resp.sendError(HttpServletResponse.SC_NOT_FOUND);
            return;
        }

        final String id = pathElements[1];

        Workspace workspace = m_workspaceManager.getWorkspace(id);
        if (workspace == null) {
            resp.sendError(HttpServletResponse.SC_NOT_FOUND, "Could not find workspace: " + id);
            return;
        }

        if (pathElements.length == 2) {
          try {
              Set<String> workspaces = (Set<String>) session.getAttribute(SESSION_KEY_WORKSPACES);
              if (workspaces != null) {
                workspaces.remove(workspace.getSessionID());
                  session.setAttribute(SESSION_KEY_WORKSPACES, workspaces);
              }
            m_workspaceManager.removeWorkspace(id);
          }
          catch (IOException ioe) {
                resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Could not delete work area: " + ioe.getMessage());
          }
        }
        else if (pathElements.length == 4) {
            deleteRepositoryObject(workspace, pathElements[2], pathElements[3], resp);
        }
        else {
            // All other path lengths...
            resp.sendError(HttpServletResponse.SC_NOT_FOUND);
        }
    }

  private HttpSession getSession(HttpServletRequest req) {
    HttpSession session = req.getSession(false);
    if (session == null) {
      session = req.getSession(true);
      session.setMaxInactiveInterval(m_sessionTimeout); // seconds
    }
    return session;
  }

    /**
     * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
     */
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
      HttpSession session = getSession(req);
        String[] pathElements = getPathElements(req);
        if (pathElements == null || pathElements.length == 0) {
            // TODO return a list of versions
            resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, "Not implemented: list of versions");
            return;
        }

        if (pathElements.length == 1) {
            if (LATEST_FOLDER.equals(pathElements[0])) {
                // TODO redirect to latest version
                // resp.sendRedirect("notImplemented" /* to latest version */);
                resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, "Not implemented: redirect to latest version");
                return;
            }
            else {
                // All other paths...
                resp.sendError(HttpServletResponse.SC_NOT_FOUND);
            }
        }
        else {
            // path elements of length > 1...
            final String id = pathElements[1];

            Workspace workspace = m_workspaceManager.getWorkspace(id);
            if (workspace == null) {
                resp.sendError(HttpServletResponse.SC_NOT_FOUND, "Could not find workspace: " + id);
                return;
            }

            if (pathElements.length == 2) {
                // TODO this should be the current set of repository objects?!
                JsonArray result = new JsonArray();
                result.add(new JsonPrimitive(Workspace.ARTIFACT));
                result.add(new JsonPrimitive(Workspace.ARTIFACT2FEATURE));
                result.add(new JsonPrimitive(Workspace.FEATURE));
                result.add(new JsonPrimitive(Workspace.FEATURE2DISTRIBUTION));
                result.add(new JsonPrimitive(Workspace.DISTRIBUTION));
                result.add(new JsonPrimitive(Workspace.DISTRIBUTION2TARGET));
                result.add(new JsonPrimitive(Workspace.TARGET));
                resp.getWriter().println(m_gson.toJson(result));
                return;
            }
            else if (pathElements.length == 3) {
                listRepositoryObjects(workspace, pathElements[2], resp);
            }
            else if (pathElements.length == 4) {
                readRepositoryObject(workspace, pathElements[2], pathElements[3], resp);
            }
            else if (pathElements.length == 5) {
                handleWorkspaceAction(workspace, pathElements[2], pathElements[3], pathElements[4], req, resp);
            }
            else {
                // All other path lengths...
                resp.sendError(HttpServletResponse.SC_NOT_FOUND);
            }
        }
    }

    /**
     * @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
     */
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
      HttpSession session = getSession(req);
        String[] pathElements = getPathElements(req);
        if (pathElements == null || pathElements.length < 1 || !WORK_FOLDER.equals(pathElements[0])) {
            resp.sendError(HttpServletResponse.SC_NOT_FOUND);
            return;
        }

        if (pathElements.length == 1) {
            Workspace workspace = m_workspaceManager.createWorkspace(req.getParameterMap(), req);
            if (workspace == null) {
                resp.sendError(HttpServletResponse.SC_UNAUTHORIZED);
            }
            else {
              Set<String> workspaces = (Set<String>) session.getAttribute(SESSION_KEY_WORKSPACES);
              if (workspaces == null) {
                workspaces = new HashSet<>();
              }
              workspaces.add(workspace.getSessionID());
              session.setAttribute(SESSION_KEY_WORKSPACES, workspaces);
                resp.sendRedirect(req.getServletPath() + "/" + buildPathFromElements(WORK_FOLDER, workspace.getSessionID()));
            }
        }
        else {
            // more than one path elements...
            Workspace workspace = m_workspaceManager.getWorkspace(pathElements[1]);
            if (workspace == null) {
                resp.sendError(HttpServletResponse.SC_NOT_FOUND, "Could not find workspace: " + pathElements[1]);
                return;
            }

            if (pathElements.length == 2) {
                // Possible commit of workspace...
                commitWorkspace(workspace, resp);
            }
            else if (pathElements.length == 3) {
                // Possible repository object creation...
                RepositoryValueObject data = getRepositoryValueObject(req);
                createRepositoryObject(workspace, pathElements[2], data, req, resp);
            }
            else if (pathElements.length == 5) {
                // Possible workspace action...
                performWorkspaceAction(workspace, pathElements[2], pathElements[3], pathElements[4], resp);
            }
            else {
                // All other path lengths...
                resp.sendError(HttpServletResponse.SC_NOT_FOUND);
            }
        }
    }

    @Override
    protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
      HttpSession session = getSession(req);
        String[] pathElements = getPathElements(req);
        if (pathElements == null || pathElements.length != 4 || !WORK_FOLDER.equals(pathElements[0])) {
            resp.sendError(HttpServletResponse.SC_NOT_FOUND);
            return;
        }

        Workspace workspace = m_workspaceManager.getWorkspace(pathElements[1]);
        if (workspace == null) {
            resp.sendError(HttpServletResponse.SC_NOT_FOUND, "Could not find workspace: " + pathElements[1]);
            return;
        }

        RepositoryValueObject data = getRepositoryValueObject(req);
        updateRepositoryObject(workspace, pathElements[2], pathElements[3], data, req, resp);
    }

    /**
     * Commits the given workspace.
     *
     * @param workspace the workspace to commit;
     * @param resp the servlet repsonse to write the response data to.
     * @throws IOException in case of I/O errors.
     */
    private void commitWorkspace(Workspace workspace, HttpServletResponse resp) throws IOException {
        try {
            workspace.commit();
        }
        catch (Exception e) {
            m_logger.log(LogService.LOG_WARNING, "Failed to commit workspace!", e);
            resp.sendError(HttpServletResponse.SC_CONFLICT, "Commit failed: " + e.getMessage());
        }
    }

    /**
     * Creates a new repository object.
     *
     * @param workspace the workspace to create the new repository object in;
     * @param entityType the type of repository object to create;
     * @param data the repository value object to use as content for the to-be-created repository object;
     * @param resp the servlet response to write the response data to.
     * @throws IOException in case of I/O errors.
     */
    private void createRepositoryObject(Workspace workspace, String entityType, RepositoryValueObject data, HttpServletRequest req, HttpServletResponse resp) throws IOException {
        try {
            RepositoryObject object = workspace.createRepositoryObject(entityType, data.attributes, data.tags);

            resp.sendRedirect(req.getServletPath() + "/" + buildPathFromElements(WORK_FOLDER, workspace.getSessionID(), entityType, object.getDefinition()));
        }
        catch (IllegalArgumentException e) {
            m_logger.log(LogService.LOG_WARNING, "Failed to add entity of type: " + entityType + " with data: " + data);
            resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Could not add entity of type " + entityType + " with data: " + data);
        }
    }
   

    /**
     * Deletes a repository object from the current workspace.
     *
     * @param workspace the workspace to perform the action for;
     * @param entityType the type of entity to apply the action to;
     * @param entityId the identification of the entity to apply the action to;
     * @param resp the servlet response to write the response data to.
     * @throws IOException in case of I/O errors.
     */
    private void deleteRepositoryObject(Workspace workspace, String entityType, String entityId, HttpServletResponse resp) throws IOException {
        try {
            workspace.deleteRepositoryObject(entityType, entityId);
        }
        catch (IllegalArgumentException e) {
            m_logger.log(LogService.LOG_WARNING, "Failed to delete repository object!", e);
            resp.sendError(HttpServletResponse.SC_NOT_FOUND, "Repository object of type " + entityType + " and identity " + entityId + " not found.");
        }
    }

    /**
     * Interprets the given request-data as JSON-data and converts it to a {@link RepositoryValueObject} instance.
     *
     * @param request the servlet request data to interpret;
     * @return a {@link RepositoryValueObject} representation of the given request-data, never <code>null</code>.
     * @throws IOException in case of I/O errors, or in case the JSON parsing failed.
     */
    private RepositoryValueObject getRepositoryValueObject(HttpServletRequest request) throws IOException {
        try {
            return m_gson.fromJson(request.getReader(), RepositoryValueObject.class);
        }
        catch (JsonParseException e) {
            m_logger.log(LogService.LOG_WARNING, "Invalid repository object data!", e);
            throw new IOException("Unable to parse repository object!", e);
        }
    }


    /**
     * Performs an idempotent action on an repository object for the given workspace.
     *
     * @param workspace the workspace to perform the action for;
     * @param entityType the type of entity to apply the action to;
     * @param entityId the identification of the entity to apply the action to;
     * @param action the (name of the) action to apply;
     * @param req the servlet request to read the request data from;
     * @param resp the servlet response to write the response data to.
     * @throws IOException
     */
    private void handleWorkspaceAction(Workspace workspace, String entityType, String entityId, String action, HttpServletRequest req, HttpServletResponse resp) throws IOException {
        RepositoryObject repositoryObject = workspace.getRepositoryObject(entityType, entityId);
        if (repositoryObject == null) {
            resp.sendError(HttpServletResponse.SC_NOT_FOUND, "Repository object of type " + entityType + " and identity " + entityId + " not found.");
            return;
        }

        if (Workspace.TARGET.equals(entityType) && ACTION_APPROVE.equals(action)) {
            resp.getWriter().println(m_gson.toJson(((StatefulTargetObject) repositoryObject).getStoreState()));
        }
        else if (Workspace.TARGET.equals(entityType) && ACTION_REGISTER.equals(action)) {
            resp.getWriter().println(m_gson.toJson(((StatefulTargetObject) repositoryObject).getRegistrationState()));
        }
        else if (Workspace.TARGET.equals(entityType) && ACTION_AUDITEVENTS.equals(action)) {
            StatefulTargetObject target = (StatefulTargetObject) repositoryObject;
            List<Event> events = target.getAuditEvents();

            String startValue = req.getParameter("start");
            String maxValue = req.getParameter("max");

            int start = (startValue == null) ? 0 : Integer.parseInt(startValue);
            // ACE-237: ensure the start-value is a correctly bounded positive integer...
            start = Math.max(0, Math.min(events.size() - 1, start));

            int max = (maxValue == null) ? 100 : Integer.parseInt(maxValue);
            // ACE-237: ensure the max- & end-values are correctly bounded...
            max = Math.max(1, max);

            int end = Math.min(events.size(), start + max);

            List<Event> selection = events.subList(start, end);
            resp.getWriter().println(m_gson.toJson(selection));
        }
        else {
            resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Unknown action for " + entityType);
        }
    }

    /**
     * Returns the identifiers of all repository objects of a given type.
     *
     * @param workspace the workspace to read the repository objects from;
     * @param entityType the type of repository objects to read;
     * @param resp the servlet response to write the response data to.
     * @throws IOException in case of I/O problems.
     */
    private void listRepositoryObjects(Workspace workspace, String entityType, HttpServletResponse resp) throws IOException {
        // TODO add a feature to filter the list that is returned (query, paging, ...)
        List<RepositoryObject> objects = workspace.getRepositoryObjects(entityType);

        JsonArray result = new JsonArray();
        for (RepositoryObject ro : objects) {
            String identity = ro.getDefinition();
            if (identity != null) {
                result.add(new JsonPrimitive(urlEncode(identity)));
            }
        }

        resp.getWriter().println(m_gson.toJson(result));
    }

    /**
     * Performs a non-idempotent action on an repository object for the given workspace.
     *
     * @param workspace the workspace to perform the action for;
     * @param entityType the type of entity to apply the action to;
     * @param entityId the identification of the entity to apply the action to;
     * @param action the (name of the) action to apply;
     * @param resp the servlet response to write the response data to.
     * @throws IOException in case of I/O errors.
     */
    private void performWorkspaceAction(Workspace workspace, String entityType, String entityId, String action, HttpServletResponse resp) throws IOException {
        RepositoryObject repositoryObject = workspace.getRepositoryObject(entityType, entityId);
        if (repositoryObject == null) {
            resp.sendError(HttpServletResponse.SC_NOT_FOUND, "Repository object of type " + entityType + " and identity " + entityId + " not found.");
            return;
        }

        if (Workspace.TARGET.equals(entityType) && ACTION_APPROVE.equals(action)) {
            StatefulTargetObject sto = workspace.approveTarget((StatefulTargetObject) repositoryObject);

            // Respond with the current store state...
            resp.getWriter().println(m_gson.toJson(sto.getStoreState()));
        }
        else if (Workspace.TARGET.equals(entityType) && ACTION_REGISTER.equals(action)) {
            StatefulTargetObject sto = workspace.registerTarget((StatefulTargetObject) repositoryObject);
            if (sto == null) {
                resp.sendError(HttpServletResponse.SC_CONFLICT, "Target already registered: " + entityId);
            }
            else {
                // Respond with the current registration state...
                resp.getWriter().println(m_gson.toJson(sto.getRegistrationState()));
            }
        }
        else {
            resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Unknown action for " + entityType);
        }
    }

    /**
     * Reads a single repository object and returns a JSON representation of it.
     *
     * @param workspace the workspace to read the repository object from;
     * @param entityType the type of repository object to read;
     * @param entityId the identifier of the repository object to read;
     * @param resp the servlet response to write the response data to.
     * @throws IOException in case of I/O problems.
     */
    private void readRepositoryObject(Workspace workspace, String entityType, String entityId, HttpServletResponse resp) throws IOException {
        RepositoryObject repositoryObject = workspace.getRepositoryObject(entityType, entityId);
        if (repositoryObject == null) {
            resp.sendError(HttpServletResponse.SC_NOT_FOUND, "Repository object of type " + entityType + " and identity " + entityId + " not found.");
        }
        else {
            resp.getWriter().println(m_gson.toJson(repositoryObject));
        }
    }



    /**
     * Updates an existing repository object.
     *
     * @param workspace the workspace to update the repository object in;
     * @param entityType the type of repository object to update;
     * @param entityId the identifier of the repository object to update;
     * @param data the repository value object to use as content for the to-be-updated repository object;
     * @param resp the servlet response to write the response data to.
     * @throws IOException in case of I/O errors.
     */
    private void updateRepositoryObject(Workspace workspace, String entityType, String entityId, RepositoryValueObject data, HttpServletRequest req, HttpServletResponse resp) throws IOException {
        try {
            workspace.updateRepositoryObject(entityType, entityId, data.attributes, data.tags);

            resp.sendRedirect(req.getServletPath() + "/" + buildPathFromElements(WORK_FOLDER, workspace.getSessionID(), entityType, entityId));
        }
        catch (IllegalArgumentException e) {
            m_logger.log(LogService.LOG_WARNING, "Failed to update entity of type: " + entityType + " with data: " + data, e);
            resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Could not update entity of type " + entityType + " with data: " + data);
        }
    }

    /**
     * URL decodes a given element.
     *
     * @param element the element to decode, cannot be <code>null</code>.
     * @return the decoded element, never <code>null</code>.
     */
    private String urlDecode(String element) {
        try {
            return URLDecoder.decode(element.replaceAll("%20", "\\+"), "UTF-8");
        }
        catch (UnsupportedEncodingException e) {
            // ignored on purpose, any JVM must support UTF-8
            return null; // should never occur
        }
    }

    /**
     * URL encodes a given element.
     *
     * @param element the element to encode, cannot be <code>null</code>.
     * @return the encoded element, never <code>null</code>.
     */
    private String urlEncode(String element) {
        try {
            return URLEncoder.encode(element, "UTF-8").replaceAll("\\+", "%20");
        }
        catch (UnsupportedEncodingException e) {
            // ignored on purpose, any JVM must support UTF-8
            return null; // should never occur
        }
    }

    @Override
    public void updated(Dictionary properties) throws ConfigurationException {
      if (properties == null) {
        // defaults
        m_sessionTimeout = DEFAULT_SESSION_TIMEOUT;
      }
      else {
        try {
          Object timeoutObject = properties.get(KEY_SESSION_TIMEOUT);
          if (timeoutObject != null) {
            if (timeoutObject instanceof Integer) {
              m_sessionTimeout = (Integer) timeoutObject;
            }
            else {
              m_sessionTimeout = Integer.parseInt(timeoutObject.toString());
            }
            if (m_sessionTimeout < 1) {
              m_sessionTimeout = DEFAULT_SESSION_TIMEOUT;
              throw new ConfigurationException(KEY_SESSION_TIMEOUT, "Session timeout should be at least 1 second");
            }
          }
        }
        catch (Exception e) {
          throw new ConfigurationException(KEY_SESSION_TIMEOUT, "Could not parse timeout, it should either be a string or integer");
        }
      }
    }

  @Override
  public void sessionCreated(HttpSessionEvent e) {
  }

  @Override
  public void sessionDestroyed(HttpSessionEvent e) {
    HttpSession session = e.getSession();
    if (session != null) {
      Set<String> workspaces = (Set<String>) session.getAttribute(SESSION_KEY_WORKSPACES);
      if (workspaces != null) {
        for (String id : workspaces) {
          try {
            m_workspaceManager.removeWorkspace(id);
          }
          catch (IOException ioe) {
            m_logger.log(LogService.LOG_WARNING, "Error while removing workspace after session timeout", ioe);
          }
        }
      }
    }
  }
}
TOP

Related Classes of org.apache.ace.client.rest.RESTClientServlet

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.