You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ace.apache.org by ja...@apache.org on 2012/03/29 16:25:37 UTC

svn commit: r1306879 - in /ace/trunk/ace-client-rest/src/main/java/org/apache/ace/client/rest: RESTClientServlet.java Workspace.java

Author: jawi
Date: Thu Mar 29 14:25:37 2012
New Revision: 1306879

URL: http://svn.apache.org/viewvc?rev=1306879&view=rev
Log:
ACE-238, ACE-239: make REST API more robust wrt unregistered targets. Also cleaned up the REST implementation a bit.

Modified:
    ace/trunk/ace-client-rest/src/main/java/org/apache/ace/client/rest/RESTClientServlet.java
    ace/trunk/ace-client-rest/src/main/java/org/apache/ace/client/rest/Workspace.java

Modified: ace/trunk/ace-client-rest/src/main/java/org/apache/ace/client/rest/RESTClientServlet.java
URL: http://svn.apache.org/viewvc/ace/trunk/ace-client-rest/src/main/java/org/apache/ace/client/rest/RESTClientServlet.java?rev=1306879&r1=1306878&r2=1306879&view=diff
==============================================================================
--- ace/trunk/ace-client-rest/src/main/java/org/apache/ace/client/rest/RESTClientServlet.java (original)
+++ ace/trunk/ace-client-rest/src/main/java/org/apache/ace/client/rest/RESTClientServlet.java Thu Mar 29 14:25:37 2012
@@ -40,17 +40,19 @@ import org.apache.felix.dm.Component;
 import org.apache.felix.dm.DependencyManager;
 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 {
-	private static final long serialVersionUID = 5210711248294238039L;
+    
     /** Alias that redirects to the latest version automatically. */
     private static final String LATEST_FOLDER = "latest";
     /** Name of the folder where working copies are kept. */
@@ -78,27 +80,145 @@ public class RESTClientServlet extends H
 
     private static long m_sessionID = 1;
 
+    private volatile LogService m_logger;
     private volatile DependencyManager m_dm;
     private volatile SessionFactory m_sessionFactory;
 
-    private final Map<String, Workspace> m_workspaces = new HashMap<String, Workspace>();
-    private final Map<String, Component> m_workspaceComponents = new HashMap<String, Component>();
-    private Gson m_gson;
+    private final Map<String, Workspace> m_workspaces;
+    private final Map<String, Component> m_workspaceComponents;
+    private final Gson m_gson;
+    
     private String m_repositoryURL;
     private String m_obrURL;
     private String m_customerName;
     private String m_storeRepositoryName;
-    private String m_distributionRepositoryName;
+    private String m_targetRepositoryName;
     private String m_deploymentRepositoryName;
     private String m_serverUser;
-    
+
+    /**
+     * Creates a new {@link RESTClientServlet} instance.
+     */
     public RESTClientServlet() {
         m_gson = (new GsonBuilder())
             .registerTypeHierarchyAdapter(RepositoryObject.class, new RepositoryObjectSerializer())
             .registerTypeHierarchyAdapter(LogEvent.class, new LogEventSerializer())
             .create();
+        
+        m_workspaces = new HashMap<String, Workspace>();
+        m_workspaceComponents = new HashMap<String, Component>();
     }
-    
+
+    public void updated(Dictionary properties) throws ConfigurationException {
+        // Note that configuration changes are only applied to new work areas, started after the
+        // configuration was changed. No attempt is done to "fix" existing work areas, although we
+        // might consider flushing/invalidating them.
+        synchronized (new Object()) {
+            m_repositoryURL = getProperty(properties, KEY_REPOSITORY_URL, "http://localhost:8080/repository");
+            m_obrURL = getProperty(properties, KEY_OBR_URL, "http://localhost:8080/obr");
+            m_customerName = getProperty(properties, KEY_CUSTOMER_NAME, "apache");
+            m_storeRepositoryName = getProperty(properties, KEY_STORE_REPOSITORY_NAME, "shop");
+            m_targetRepositoryName = getProperty(properties, KEY_DISTRIBUTION_REPOSITORY_NAME, "target");
+            m_deploymentRepositoryName = getProperty(properties, KEY_DEPLOYMENT_REPOSITORY_NAME, "deployment");
+            m_serverUser = getProperty(properties, KEY_USER_NAME, "d");
+        }
+    }
+
+    /**
+     * 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;
+    }
+
+    /**
+     * Helper method to safely obtain a property value from the given dictionary.
+     * 
+     * @param properties the dictionary to retrieve the value from, can be <code>null</code>;
+     * @param key the name of the property to retrieve, cannot be <code>null</code>;
+     * @param defaultValue the default value to return in case the property does not exist, or the given dictionary was <code>null</code>.
+     * @return a property value, can be <code>null</code>.
+     */
+    String getProperty(Dictionary properties, String key, String defaultValue) {
+        if (properties != null) {
+            Object value = properties.get(key);
+            if (value != null && value instanceof String) {
+                return (String) value;
+            }
+        }
+        return defaultValue;
+    }
+
+    /**
+     * @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 {
+        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 = getWorkspace(id);
+        if (workspace == null) {
+            resp.sendError(HttpServletResponse.SC_NOT_FOUND, "Could not find workspace: " + id);
+            return;
+        }
+
+        if (pathElements.length == 2) {
+            removeWorkspace(id, resp);
+        }
+        else if (pathElements.length == 4) {
+            deleteRepositoryObject(workspace, pathElements[2], pathElements[3], resp);
+        }
+        else {
+            // All other path lengths...
+            resp.sendError(HttpServletResponse.SC_NOT_FOUND);
+        }
+    }
+
+    /**
+     * @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 {
         String[] pathElements = getPathElements(req);
@@ -107,305 +227,224 @@ public class RESTClientServlet extends H
             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 {
-            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 if (pathElements.length == 2) {
-            	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;
+            // path elements of length > 1...
+            final String id = pathElements[1];
+
+            Workspace workspace = 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) {
-                if (WORK_FOLDER.equals(pathElements[0])) {
-                    Workspace workspace = getWorkspace(pathElements[1]);
-                    if (workspace != null) {
-                        // TODO add a feature to filter the list that is returned (query, paging, ...)
-                        List<RepositoryObject> objects = workspace.getRepositoryObjects(pathElements[2]);
-                        JsonArray result = new JsonArray();
-                        for (RepositoryObject ro : objects) {
-                            String identity = ro.getDefinition();
-                            if (identity != null) {
-                                result.add(new JsonPrimitive(URLEncoder.encode(identity, "UTF-8")));
-                            }
-                        }
-                        resp.getWriter().println(m_gson.toJson(result));
-                        return;
-                    }
-                    resp.sendError(HttpServletResponse.SC_NOT_FOUND, "Could not find workspace: " + pathElements[1]);
-                    return;
-                }
+                listRepositoryObjects(workspace, pathElements[2], resp);
             }
             else if (pathElements.length == 4) {
-                if (WORK_FOLDER.equals(pathElements[0])) {
-                    Workspace workspace = getWorkspace(pathElements[1]);
-                    if (workspace != null) {
-                        String entityType = pathElements[2];
-                        String entityId = pathElements[3];
-                        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;
-                        }
-                        
-                        resp.getWriter().println(m_gson.toJson(repositoryObject));
-                        return;
-                    }
-                    resp.sendError(HttpServletResponse.SC_NOT_FOUND, "Could not find workspace: " + pathElements[1]);
-                    return;
-                }
+                readRepositoryObject(workspace, pathElements[2], pathElements[3], resp);
             }
             else if (pathElements.length == 5) {
-                if (WORK_FOLDER.equals(pathElements[0])) {
-                    Workspace workspace = getWorkspace(pathElements[1]);
-                    if (workspace != null) {
-                        String entityType = pathElements[2];
-                        String entityId = pathElements[3];
-                        String action = pathElements[4];
-                        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;
-                        }
-                        
-                        boolean isTarget = Workspace.TARGET.equals(entityType);
-						if (isTarget && ACTION_APPROVE.equals(action)) {
-                            resp.getWriter().println(m_gson.toJson(((StatefulTargetObject) repositoryObject).getStoreState()));
-                            return;
-                        }
-						else if (isTarget && ACTION_REGISTER.equals(action)) {
-                            resp.getWriter().println(m_gson.toJson(((StatefulTargetObject) repositoryObject).getRegistrationState()));
-                            return;
-                        }
-						else if (isTarget && ACTION_AUDITEVENTS.equals(action)) {
-							StatefulTargetObject target = (StatefulTargetObject) repositoryObject;
-							List<LogEvent> 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<LogEvent> selection = events.subList(start, end);
-                        	resp.getWriter().println(m_gson.toJson(selection));
-                        	return;
-                        }
-						else {
-                            resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Unknown action '" + action + "' for " + entityType + "/" + entityId);
-                            return;
-                        }
-                    }
-                    resp.sendError(HttpServletResponse.SC_NOT_FOUND, "Could not find workspace: " + pathElements[1]);
-                    return;
-                }
+                handleWorkspaceAction(workspace, pathElements[2], pathElements[3], pathElements[4], req, resp);
+            }
+            else {
+                // All other path lengths...
+                resp.sendError(HttpServletResponse.SC_NOT_FOUND);
             }
-            resp.sendError(HttpServletResponse.SC_NOT_FOUND);
-            return;
         }
     }
 
+    /**
+     * @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 {
         String[] pathElements = getPathElements(req);
-        if (pathElements != null) {
-            if (pathElements.length == 1) {
-                if (WORK_FOLDER.equals(pathElements[0])) {
-                    // TODO get data from post body (if no data, assume latest??) -> for now always assume latest
-                    String sessionID;
-                    Workspace workspace;
-                    Component component;
-                    synchronized (m_workspaces) {
-                        sessionID = "rest-" + m_sessionID++;
-                        workspace = new Workspace(sessionID, m_repositoryURL, m_obrURL, m_customerName, m_storeRepositoryName, m_distributionRepositoryName, m_deploymentRepositoryName, m_serverUser);
-                        m_workspaces.put(sessionID, workspace);
-                        component = m_dm.createComponent().setImplementation(workspace);
-                        m_workspaceComponents.put(sessionID, component);
-                    }
-                    m_sessionFactory.createSession(sessionID);
-                    m_dm.add(component);
-                    resp.sendRedirect(buildPathFromElements(WORK_FOLDER, sessionID));
-                    return;
-                }
-            }
-            else if (pathElements.length == 2) {
-                if (WORK_FOLDER.equals(pathElements[0])) {
-                    Workspace workspace = getWorkspace(pathElements[1]);
-                    if (workspace != null) {
-                        try {
-                            workspace.commit();
-                            return;
-                        }
-                        catch (Exception e) {
-                            e.printStackTrace();
-                            resp.sendError(HttpServletResponse.SC_CONFLICT, "Commit failed: " + e.getMessage());
-                            return;
-                        }
-                    }
-                    else {
-                        // return error
-                        System.out.println("Failed...");
-                    }
-                }
+        if (pathElements == null || pathElements.length < 1 || !WORK_FOLDER.equals(pathElements[0])) {
+            resp.sendError(HttpServletResponse.SC_NOT_FOUND);
+            return;
+        }
+
+        if (pathElements.length == 1) {
+            createWorkspace(resp);
+        }
+        else {
+            // more than one path elements...
+            Workspace workspace = 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) {
-                if (WORK_FOLDER.equals(pathElements[0])) {
-                    Workspace workspace = getWorkspace(pathElements[1]);
-                    if (workspace != null) {
-                        try {
-                            RepositoryValueObject data = m_gson.fromJson(req.getReader(), RepositoryValueObject.class);
-                            RepositoryObject object = workspace.addRepositoryObject(pathElements[2], data.attributes, data.tags);
-                            String identity = object.getDefinition();
-                            if (identity != null) {
-                                resp.sendRedirect(buildPathFromElements(WORK_FOLDER, pathElements[1], pathElements[2], identity));
-                            }
-                            else {
-                                // TODO decide what to do here, if this can happen at all
-                            }
-                            return;
-                        }
-                        catch (IllegalArgumentException e) {
-                            e.printStackTrace();
-                        }
-                    }
-                    resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Could not add entity of type " + pathElements[2]);
-                    return;
-                }
+                // Possible repository object creation...
+                RepositoryValueObject data = getRepositoryValueObject(req);
+                createRepositoryObject(workspace, pathElements[2], data, resp);
             }
             else if (pathElements.length == 5) {
-                if (WORK_FOLDER.equals(pathElements[0])) {
-                    Workspace workspace = getWorkspace(pathElements[1]);
-                    if (workspace != null) {
-                        String entityType = pathElements[2];
-                        String entityId = pathElements[3];
-                        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;
-                        }
-
-                        // the last element is the "command" to apply...
-                        String action = pathElements[4];
-
-                        if (Workspace.TARGET.equals(entityType) && ACTION_APPROVE.equals(action)) {
-                            StatefulTargetObject sto = workspace.approveTarget(repositoryObject);
-                            // Respond with the current store state...
-                            resp.getWriter().println(m_gson.toJson(sto.getStoreState()));
-                            return;
-                        } else if (Workspace.TARGET.equals(entityType) && ACTION_REGISTER.equals(action)) {
-                            StatefulTargetObject sto = workspace.registerTarget(repositoryObject);
-                            // Respond with the current registration state...
-                            resp.getWriter().println(m_gson.toJson(sto.getRegistrationState()));
-                            return;
-                        }
-                        
-                        resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Unknown action for " + pathElements[2]);
-                    }
-                    resp.sendError(HttpServletResponse.SC_NOT_FOUND, "Could not find workspace: " + pathElements[1]);
-                    return;
-                }
+                // Possible workspace action...
+                performWorkspaceAction(workspace, pathElements[2], pathElements[3], pathElements[4], resp);
+            }
+            else {
+                // All other path lengths...
+                resp.sendError(HttpServletResponse.SC_NOT_FOUND);
             }
         }
-        resp.sendError(HttpServletResponse.SC_NOT_FOUND);
-        return;
     }
 
     @Override
     protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
         String[] pathElements = getPathElements(req);
-        if (pathElements != null) {
-            if (pathElements.length == 4) {
-                if (pathElements[0].equals(WORK_FOLDER)) {
-                    Workspace workspace = getWorkspace(pathElements[1]);
-                    if (workspace != null) {
-                        try {
-                            RepositoryValueObject data = m_gson.fromJson(req.getReader(), RepositoryValueObject.class);
-                            workspace.updateObjectWithData(pathElements[2], pathElements[2], data);
-                            resp.sendRedirect(buildPathFromElements(WORK_FOLDER, pathElements[1], pathElements[2], pathElements[3]));
-                            return;
-                        }
-                        catch (IllegalArgumentException e) {
-                            e.printStackTrace();
-                        }
-                    }
-                    resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Could not add entity of type " + pathElements[2]);
-                    return;
-                }
-            }
+        if (pathElements == null || pathElements.length != 4 || !WORK_FOLDER.equals(pathElements[0])) {
+            resp.sendError(HttpServletResponse.SC_NOT_FOUND);
+            return;
+        }
+
+        Workspace workspace = getWorkspace(pathElements[1]);
+        if (workspace == null) {
+            resp.sendError(HttpServletResponse.SC_NOT_FOUND, "Could not find workspace: " + pathElements[1]);
+            return;
         }
-        resp.sendError(HttpServletResponse.SC_NOT_FOUND);
-        return;
+
+        RepositoryValueObject data = getRepositoryValueObject(req);
+        updateRepositoryObject(workspace, pathElements[2], pathElements[3], data, resp);
     }
 
-    @Override
-    protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
-        String[] pathElements = getPathElements(req);
-        if (pathElements != null) {
-            if (pathElements.length == 2) {
-                if (WORK_FOLDER.equals(pathElements[0])) {
-                    String id = pathElements[1];
-                    Workspace workspace;
-                    Component component;
-                    synchronized (m_workspaces) {
-                        workspace = m_workspaces.remove(id);
-                        component = m_workspaceComponents.remove(id);
-                    }
-                    if (workspace != null && component != null) {
-                        // TODO delete the work area
-                        m_dm.remove(component);
-                        m_sessionFactory.destroySession(id);
-                        return;
-                    }
-                    else {
-                        resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Could not delete work area.");
-                        return;
-                    }
-                }
-            }
-            else if (pathElements.length == 4) {
-                if (WORK_FOLDER.equals(pathElements[0])) {
-                    String id = pathElements[1];
-                    String entityType = pathElements[2];
-                    String entityId = pathElements[3];
-                    
-                    Workspace workspace;
-                    synchronized (m_workspaces) {
-                        workspace = m_workspaces.get(id);
-                    }
-                    if (workspace != null) {
-                        workspace.deleteRepositoryObject(entityType, entityId);
-                        return;
-                    }
-                    else {
-                        resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Could not find work area.");
-                        return;
-                    }
-                }
-            }
+    /**
+     * 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());
         }
-        resp.sendError(HttpServletResponse.SC_NOT_FOUND);
-        return;
     }
 
+    /**
+     * 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, HttpServletResponse resp) throws IOException {
+        try {
+            RepositoryObject object = workspace.addRepositoryObject(entityType, data.attributes, data.tags);
+
+            resp.sendRedirect(buildPathFromElements(WORK_FOLDER, workspace.getSessionID(), entityType, object.getDefinition()));
+        }
+        catch (IllegalArgumentException e) {
+            m_logger.log(LogService.LOG_WARNING, "Failed to add entity of type: " + entityType, e);
+            resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Could not add entity of type " + entityType);
+        }
+    }
+
+    /**
+     * Creates a new workspace.
+     * 
+     * @param resp the servlet repsonse to write the response data to.
+     * @throws IOException in case of I/O errors.
+     */
+    private void createWorkspace(HttpServletResponse resp) throws IOException {
+        // TODO get data from post body (if no data, assume latest??) -> for now always assume latest
+        final String sessionID;
+        final Workspace workspace;
+        final Component component;
+
+        synchronized (m_workspaces) {
+            sessionID = "rest-" + m_sessionID++;
+            workspace = new Workspace(sessionID, m_repositoryURL, m_obrURL, m_customerName, m_storeRepositoryName, m_targetRepositoryName, m_deploymentRepositoryName, m_serverUser);
+            m_workspaces.put(sessionID, workspace);
+
+            component = m_dm.createComponent().setImplementation(workspace);
+            m_workspaceComponents.put(sessionID, component);
+        }
+        m_sessionFactory.createSession(sessionID);
+        m_dm.add(component);
+
+        resp.sendRedirect(buildPathFromElements(WORK_FOLDER, sessionID));
+    }
+
+    /**
+     * 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);
+        }
+    }
+
+    /**
+     * Returns a workspace by its identification.
+     * 
+     * @param id the (session) identifier of the workspace to return.
+     * @return the workspace with requested ID, or <code>null</code> if no such workspace exists.
+     */
     private Workspace getWorkspace(String id) {
         Workspace workspace;
         synchronized (m_workspaces) {
@@ -413,74 +452,214 @@ public class RESTClientServlet extends H
         }
         return workspace;
     }
-    
+
     /**
-     * Builds a URL path from the supplied elements. Each individual element is URL encoded.
+     * Performs an idempotent action on an repository object for the given workspace.
      * 
-     * @param elements the elements
-     * @return the URL path
+     * @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
      */
-    String buildPathFromElements(String... elements) {
-        StringBuilder result = new StringBuilder();
-        for (String element : elements) {
-            if (result.length() > 0) {
-                result.append('/');
+    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<LogEvent> 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<LogEvent> 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)));
             }
-            try {
-                result.append(URLEncoder.encode(element, "UTF-8").replaceAll("\\+", "%20"));
+        }
+
+        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()));
             }
-            catch (UnsupportedEncodingException e) {} // ignored on purpose, any JVM must support UTF-8
         }
-        return result.toString();
+        else {
+            resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Unknown action for " + entityType);
+        }
     }
 
     /**
-     * Returns the separate path parts from the request, and URL decodes them.
+     * Reads a single repository object and returns a JSON representation of it.
      * 
-     * @param req the request
-     * @return the separate path parts
+     * @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.
      */
-    String[] getPathElements(HttpServletRequest req) {
-        String path = req.getPathInfo();
-        if (path == null) {
-            return new String[0];
+    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.");
         }
-        if (path.startsWith("/") && path.length() > 1) {
-            path = path.substring(1);
+        else {
+            resp.getWriter().println(m_gson.toJson(repositoryObject));
         }
-        if (path.endsWith("/") && path.length() > 1) {
-            path = path.substring(0, path.length() - 1);
+    }
+
+    /**
+     * Removes the workspace with the given identifier.
+     * 
+     * @param id the identifier of the workspace to remove; 
+     * @param resp the servlet response to write the response data to.
+     * @throws IOException in case of I/O problems.
+     */
+    private void removeWorkspace(final String id, HttpServletResponse resp) throws IOException {
+        final Workspace workspace;
+        final Component component;
+
+        synchronized (m_workspaces) {
+            workspace = m_workspaces.remove(id);
+            component = m_workspaceComponents.remove(id);
         }
-        String[] pathElements = path.split("/");
+
+        if ((workspace != null) && (component != null)) {
+            workspace.destroy();
+
+            m_dm.remove(component);
+            m_sessionFactory.destroySession(id);
+        }
+        else {
+            resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Could not delete work area.");
+        }
+    }
+
+    /**
+     * 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, HttpServletResponse resp) throws IOException {
         try {
-            for (int i = 0; i < pathElements.length; i++) {
-                pathElements[i] = URLDecoder.decode(pathElements[i].replaceAll("%20", "\\+"), "UTF-8");
-            }
+            workspace.updateObjectWithData(entityType, entityId, data);
+
+            resp.sendRedirect(buildPathFromElements(WORK_FOLDER, workspace.getSessionID(), entityType, entityId));
+        }
+        catch (IllegalArgumentException e) {
+            m_logger.log(LogService.LOG_WARNING, "Failed to update entity of type: " + entityType, e);
+            resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Could not update entity of type " + entityType);
         }
-        catch (UnsupportedEncodingException e) {}
-        return pathElements;
     }
 
-    public void updated(Dictionary properties) throws ConfigurationException {
-        // Note that configuration changes are only applied to new work areas, started after the
-        // configuration was changed. No attempt is done to "fix" existing work areas, although we
-        // might consider flushing/invalidating them.
-        m_repositoryURL = getProperty(properties, KEY_REPOSITORY_URL, "http://localhost:8080/repository");
-        m_obrURL = getProperty(properties, KEY_OBR_URL, "http://localhost:8080/obr");
-        m_customerName = getProperty(properties, KEY_CUSTOMER_NAME, "apache");
-        m_storeRepositoryName = getProperty(properties, KEY_STORE_REPOSITORY_NAME, "shop");
-        m_distributionRepositoryName = getProperty(properties, KEY_DISTRIBUTION_REPOSITORY_NAME, "target"); // TODO default was: gateway, shouldn't this be distribution?
-        m_deploymentRepositoryName = getProperty(properties, KEY_DEPLOYMENT_REPOSITORY_NAME, "deployment");
-        m_serverUser = getProperty(properties, KEY_USER_NAME, "d");
+    /**
+     * 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
+        }
     }
-    
-    String getProperty(Dictionary properties, String key, String defaultValue) {
-        if (properties != null) {
-            Object value = properties.get(key);
-            if (value != null && value instanceof String) {
-                return (String) value;
-            }
+
+    /**
+     * 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
         }
-        return defaultValue;
     }
 }

Modified: ace/trunk/ace-client-rest/src/main/java/org/apache/ace/client/rest/Workspace.java
URL: http://svn.apache.org/viewvc/ace/trunk/ace-client-rest/src/main/java/org/apache/ace/client/rest/Workspace.java?rev=1306879&r1=1306878&r2=1306879&view=diff
==============================================================================
--- ace/trunk/ace-client-rest/src/main/java/org/apache/ace/client/rest/Workspace.java (original)
+++ ace/trunk/ace-client-rest/src/main/java/org/apache/ace/client/rest/Workspace.java Thu Mar 29 14:25:37 2012
@@ -85,6 +85,13 @@ public class Workspace {
         m_serverUser = serverUser;
     }
     
+    /**
+     * @return the session ID of this workspace, never <code>null</code>.
+     */
+    public String getSessionID() {
+        return m_sessionID;
+    }
+    
     private void addSessionDependency(Component component, Class service, boolean isRequired) {
         component.add(m_manager.createServiceDependency()
             .setService(service, "(" + SessionFactory.SERVICE_SID + "=" + m_sessionID + ")")
@@ -205,32 +212,25 @@ public class Workspace {
     
     /**
      * Approves a given stateful target object.
-     * <p>If the given repository object does <em>not</em> represent a stateful target object, this method will do nothing.</p>
      * 
-     * @param repositoryObject the repository object to approve.
-     * @return the approved stateful target object, can be <code>null</code> only if the given repository object does not represent a {@link StatefulTargetObject}.
+     * @param targetObject the target object to approve, cannot be <code>null</code>.
+     * @return the approved stateful target object, cannot be <code>null</code>.
      */
-    public StatefulTargetObject approveTarget(RepositoryObject repositoryObject) {
-        if (!(repositoryObject instanceof StatefulTargetObject)) {
-            return null;
-        }
-        StatefulTargetObject targetObject = (StatefulTargetObject) repositoryObject;
+    public StatefulTargetObject approveTarget(StatefulTargetObject targetObject) {
         targetObject.approve();
         return targetObject;
     }
 
     /**
      * Registers a given stateful target object.
-     * <p>If the given repository object does <em>not</em> represent a stateful target object, this method will do nothing.</p>
      * 
-     * @param repositoryObject the repository object to register.
-     * @return the registered stateful target object, can be <code>null</code> only if the given repository object does not represent a {@link StatefulTargetObject}.
+     * @param targetObject the target object to register, cannot be <code>null</code>.
+     * @return the registered stateful target object, can be <code>null</code> only if the given target object is already registered.
      */
-    public StatefulTargetObject registerTarget(RepositoryObject repositoryObject) {
-        if (!(repositoryObject instanceof StatefulTargetObject)) {
+    public StatefulTargetObject registerTarget(StatefulTargetObject targetObject) {
+        if (targetObject.isRegistered()) {
             return null;
         }
-        StatefulTargetObject targetObject = (StatefulTargetObject) repositoryObject;
         targetObject.register();
         return targetObject;
     }
@@ -318,38 +318,48 @@ public class Workspace {
     }
 
     public RepositoryObject getLeft(String entityType, String entityId) {
-        ObjectRepository repo = getObjectRepository(entityType);
         if (ARTIFACT2FEATURE.equals(entityType)) {
             return getObjectRepository(ARTIFACT).get(entityId);
         }
-        if (FEATURE2DISTRIBUTION.equals(entityType)) {
+        else if (FEATURE2DISTRIBUTION.equals(entityType)) {
             return getObjectRepository(FEATURE).get(entityId);
         }
-        if (DISTRIBUTION2TARGET.equals(entityType)) {
+        else if (DISTRIBUTION2TARGET.equals(entityType)) {
             return getObjectRepository(DISTRIBUTION).get(entityId);
         }
+        else {
+            // throws an exception in case of an illegal type!
+            getObjectRepository(entityType);
+        }
         return null;
     }
 
     public RepositoryObject getRight(String entityType, String entityId) {
-        ObjectRepository repo = getObjectRepository(entityType);
         if (ARTIFACT2FEATURE.equals(entityType)) {
             return getObjectRepository(FEATURE).get(entityId);
         }
-        if (FEATURE2DISTRIBUTION.equals(entityType)) {
+        else if (FEATURE2DISTRIBUTION.equals(entityType)) {
             return getObjectRepository(DISTRIBUTION).get(entityId);
         }
-        if (DISTRIBUTION2TARGET.equals(entityType)) {
+        else if (DISTRIBUTION2TARGET.equals(entityType)) {
             return getObjectRepository(TARGET).get(entityId);
         }
+        else {
+            // throws an exception in case of an illegal type!
+            getObjectRepository(entityType);
+        }
         return null;
     }
     
     public void deleteRepositoryObject(String entityType, String entityId) {
-        RepositoryObject result = null;
-            ObjectRepository objectRepository = getObjectRepository(entityType);
-            RepositoryObject repositoryObject = objectRepository.get(entityId);
-            objectRepository.remove(repositoryObject);
+        ObjectRepository objectRepository = getObjectRepository(entityType);
+        RepositoryObject repositoryObject = objectRepository.get(entityId);
+        // ACE-239: avoid null entities being passed in...
+        if (repositoryObject == null) {
+            throw new IllegalArgumentException("Could not find repository object!");
+        }
+
+        objectRepository.remove(repositoryObject);
     }
 
     private ObjectRepository getObjectRepository(String entityType) {