You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ace.apache.org by ma...@apache.org on 2012/07/05 14:10:06 UTC
svn commit: r1357570 [22/34] - in /ace/sandbox/marrs: cnf/ cnf/ext/ cnf/lib/
cnf/releaserepo/ cnf/repo/ cnf/repo/.obrcache/
cnf/repo/.obrcache/http%3A%2F%2Fbundles.bndtools.org.s3.amazonaws.com%2Fcom.jcraft.jsch/
cnf/repo/.obrcache/http%3A%2F%2Fbundles...
Added: ace/sandbox/marrs/org.apache.ace.client.rest/src/org/apache/ace/client/rest/RESTClientServlet.java
URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.client.rest/src/org/apache/ace/client/rest/RESTClientServlet.java?rev=1357570&view=auto
==============================================================================
--- ace/sandbox/marrs/org.apache.ace.client.rest/src/org/apache/ace/client/rest/RESTClientServlet.java (added)
+++ ace/sandbox/marrs/org.apache.ace.client.rest/src/org/apache/ace/client/rest/RESTClientServlet.java Thu Jul 5 12:09:30 2012
@@ -0,0 +1,691 @@
+/*
+ * 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.Dictionary;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.ace.client.repository.RepositoryObject;
+import org.apache.ace.client.repository.SessionFactory;
+import org.apache.ace.client.repository.stateful.StatefulTargetObject;
+import org.apache.ace.log.LogEvent;
+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 {
+
+ /** 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";
+ /** A boolean denoting whether or not authentication is enabled. */
+ private static final String KEY_USE_AUTHENTICATION = "authentication.enabled";
+ /** URL of the repository to talk to. */
+ private static final String KEY_REPOSITORY_URL = "repository.url";
+ /** URL of the OBR to talk to. */
+ private static final String KEY_OBR_URL = "obr.url";
+ /** Name of the customer. */
+ private static final String KEY_CUSTOMER_NAME = "customer.name";
+ /** Name of the store repository. */
+ private static final String KEY_STORE_REPOSITORY_NAME = "store.repository.name";
+ /** Name of the distribution repository. */
+ private static final String KEY_DISTRIBUTION_REPOSITORY_NAME = "distribution.repository.name";
+ /** Name of the deployment repository. */
+ private static final String KEY_DEPLOYMENT_REPOSITORY_NAME = "deployment.repository.name";
+ /** Name of the user to log in as, in case no actual authentication is used. */
+ private static final String KEY_USER_NAME = "user.name";
+ /** 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 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;
+ private final Map<String, Component> m_workspaceComponents;
+ private final Gson m_gson;
+
+ private boolean m_useAuthentication;
+ private String m_repositoryURL;
+ private String m_obrURL;
+ private String m_customerName;
+ private String m_storeRepositoryName;
+ 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 {
+ // First check whether all mandatory configuration keys are available...
+ String useAuth = getProperty(properties, KEY_USE_AUTHENTICATION);
+ if (useAuth == null || !("true".equalsIgnoreCase(useAuth) || "false".equalsIgnoreCase(useAuth))) {
+ throw new ConfigurationException(KEY_USE_AUTHENTICATION, "Missing or invalid value!");
+ }
+
+ // 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 (m_workspaces) {
+ m_useAuthentication = Boolean.valueOf(useAuth);
+ 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) {
+ String value = getProperty(properties, key);
+ return (value == null) ? defaultValue : value;
+ }
+
+ /**
+ * 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>.
+ * @return a property value, can be <code>null</code>.
+ */
+ String getProperty(Dictionary properties, String key) {
+ if (properties != null) {
+ Object value = properties.get(key);
+ if (value != null && value instanceof String) {
+ return (String) value;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * @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);
+ 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 = 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 {
+ 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) {
+ createWorkspace(req, 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) {
+ // Possible repository object creation...
+ RepositoryValueObject data = getRepositoryValueObject(req);
+ createRepositoryObject(workspace, pathElements[2], data, 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 {
+ String[] pathElements = getPathElements(req);
+ 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;
+ }
+
+ RepositoryValueObject data = getRepositoryValueObject(req);
+ updateRepositoryObject(workspace, pathElements[2], pathElements[3], data, 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, 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 response to write the response data to.
+ * @throws IOException in case of I/O errors.
+ */
+ private void createWorkspace(final HttpServletRequest req, final 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_useAuthentication, 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);
+
+ if (!workspace.login(req)) {
+ resp.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+ } else {
+ 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) {
+ workspace = m_workspaces.get(id);
+ }
+ return workspace;
+ }
+
+ /**
+ * 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<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)));
+ }
+ }
+
+ 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));
+ }
+ }
+
+ /**
+ * 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);
+ }
+
+ 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 {
+ 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);
+ }
+ }
+
+ /**
+ * 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
+ }
+ }
+}
Added: ace/sandbox/marrs/org.apache.ace.client.rest/src/org/apache/ace/client/rest/RepositoryObjectSerializer.java
URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.client.rest/src/org/apache/ace/client/rest/RepositoryObjectSerializer.java?rev=1357570&view=auto
==============================================================================
--- ace/sandbox/marrs/org.apache.ace.client.rest/src/org/apache/ace/client/rest/RepositoryObjectSerializer.java (added)
+++ ace/sandbox/marrs/org.apache.ace.client.rest/src/org/apache/ace/client/rest/RepositoryObjectSerializer.java Thu Jul 5 12:09:30 2012
@@ -0,0 +1,184 @@
+/*
+ * 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.lang.reflect.Type;
+import java.util.Enumeration;
+
+import org.apache.ace.client.repository.RepositoryObject;
+import org.apache.ace.client.repository.object.ArtifactObject;
+import org.apache.ace.client.repository.object.DeploymentArtifact;
+import org.apache.ace.client.repository.stateful.StatefulTargetObject;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+
+/**
+ * Provides an object serializer for the entire type hierarchy of {@link RepositoryObject}s.
+ */
+public class RepositoryObjectSerializer implements JsonSerializer<RepositoryObject> {
+
+ /** used in all repository objects. */
+ private static final String DEFINITION = "definition";
+ /** used in all repository objects. */
+ private static final String TAGS = "tags";
+ /** used in all repository objects. */
+ private static final String ATTRIBUTES = "attributes";
+ /** used in stateful target objects only. */
+ private static final String STATE = "state";
+
+ private static final String REGISTRATION_STATE = "registrationState";
+ private static final String CURRENT_VERSION = "currentVersion";
+ private static final String STORE_STATE = "storeState";
+ private static final String PROVISIONING_STATE = "provisioningState";
+ private static final String IS_REGISTERED = "isRegistered";
+ private static final String NEEDS_APPROVAL = "needsApproval";
+ private static final String AUTO_APPROVE = "autoApprove";
+ private static final String ARTIFACTS_FROM_SHOP = "artifactsFromShop";
+ private static final String ARTIFACTS_FROM_DEPLOYMENT = "artifactsFromDeployment";
+ private static final String LAST_INSTALL_VERSION = "lastInstallVersion";
+ private static final String LAST_INSTALL_SUCCESS = "lastInstallSuccess";
+
+ /**
+ * @see com.google.gson.JsonSerializer#serialize(java.lang.Object, java.lang.reflect.Type, com.google.gson.JsonSerializationContext)
+ */
+ public JsonElement serialize(RepositoryObject repositoryObject, Type featureType, JsonSerializationContext context) {
+ // ACE-164: for stateful target objects we need some special measures to serialize it...
+ if (repositoryObject instanceof StatefulTargetObject) {
+ return serializeStatefulTargetObject((StatefulTargetObject) repositoryObject);
+ }
+
+ // All other repository objects can be simply serialized...
+ return serializeRepositoryObject(repositoryObject);
+ }
+
+ /**
+ * Custom serializer method for {@link StatefulTargetObject}s, as they have state and cannot be accessed
+ * always in the same way as other repository objects. For example, when dealing with unregistered targets,
+ * we cannot ask for the attributes and/or tags of a target.
+ *
+ * @param targetObject the target object to serialize, cannot be <code>null</code>.
+ * @return a JSON representation of the given target object, never <code>null</code>.
+ */
+ private JsonElement serializeStatefulTargetObject(StatefulTargetObject targetObject) {
+ JsonObject result = new JsonObject();
+ // ACE-243: first all the definition...
+ result.addProperty(DEFINITION, targetObject.getDefinition());
+
+ // then add all attributes
+ JsonObject attr = new JsonObject();
+
+ if (targetObject.isRegistered()) {
+ Enumeration<String> keys = targetObject.getAttributeKeys();
+ while (keys.hasMoreElements()) {
+ String key = keys.nextElement();
+ attr.addProperty(key, targetObject.getAttribute(key));
+ }
+ }
+ else {
+ // Ensure that the ID of the target is always present as attribute...
+ attr.addProperty(StatefulTargetObject.KEY_ID, targetObject.getID());
+ }
+
+ result.add(ATTRIBUTES, attr);
+
+ // then add all tags
+ JsonObject tags = new JsonObject();
+
+ if (targetObject.isRegistered()) {
+ Enumeration<String> keys = targetObject.getTagKeys();
+ while (keys.hasMoreElements()) {
+ String key = keys.nextElement();
+ tags.addProperty(key, targetObject.getTag(key));
+ }
+ }
+
+ result.add(TAGS, tags);
+
+ // finally, if it's a target with state, add that as well
+ JsonObject state = new JsonObject();
+ state.addProperty(REGISTRATION_STATE, targetObject.getRegistrationState().name());
+ state.addProperty(PROVISIONING_STATE, targetObject.getProvisioningState().name());
+ state.addProperty(STORE_STATE, targetObject.getStoreState().name());
+ state.addProperty(CURRENT_VERSION, targetObject.getCurrentVersion());
+ state.addProperty(IS_REGISTERED, Boolean.toString(targetObject.isRegistered()));
+ state.addProperty(NEEDS_APPROVAL, Boolean.toString(targetObject.needsApprove()));
+ state.addProperty(AUTO_APPROVE, Boolean.toString(targetObject.getAutoApprove()));
+
+ JsonArray artifactsFromShop = new JsonArray();
+ for (ArtifactObject a : targetObject.getArtifactsFromShop()) {
+ artifactsFromShop.add(new JsonPrimitive(a.getDefinition()));
+ }
+ state.add(ARTIFACTS_FROM_SHOP, artifactsFromShop);
+
+ JsonArray artifactsFromDeployment = new JsonArray();
+ for (DeploymentArtifact a : targetObject.getArtifactsFromDeployment()) {
+ artifactsFromDeployment.add(new JsonPrimitive(a.getUrl()));
+ }
+ state.add(ARTIFACTS_FROM_DEPLOYMENT, artifactsFromDeployment);
+
+ state.addProperty(LAST_INSTALL_VERSION, targetObject.getLastInstallVersion());
+ state.addProperty(LAST_INSTALL_SUCCESS, targetObject.getLastInstallSuccess());
+
+ /* TODO getLicenses/AssocationsWith might not be that helpful since the data is also available in a different way */
+ /* TODO some of this tends to show up as attributes as well, so we will need to do some filtering there */
+ /* TODO some aspects of the state can be manipulated as well, we need to supply methods for that */
+ result.add(STATE, state);
+
+ return result;
+ }
+
+ /**
+ * Serializes a (non stateful target object) repository object to a JSON representation.
+ *
+ * @param repositoryObject the repository object to serialize, cannot be <code>null</code>.
+ * @return a JSON representation of the given repository object, never <code>null</code>.
+ */
+ private JsonElement serializeRepositoryObject(RepositoryObject repositoryObject) {
+ JsonObject result = new JsonObject();
+ // ACE-243: first all the definition...
+ result.addProperty(DEFINITION, repositoryObject.getDefinition());
+
+ // then add all attributes
+ JsonObject attr = new JsonObject();
+
+ Enumeration<String> keys = repositoryObject.getAttributeKeys();
+ while (keys.hasMoreElements()) {
+ String key = keys.nextElement();
+ attr.addProperty(key, repositoryObject.getAttribute(key));
+ }
+ result.add(ATTRIBUTES, attr);
+
+ // then add all tags
+ JsonObject tags = new JsonObject();
+
+ keys = repositoryObject.getTagKeys();
+ while (keys.hasMoreElements()) {
+ String key = keys.nextElement();
+ tags.addProperty(key, repositoryObject.getTag(key));
+ }
+ result.add(TAGS, tags);
+
+ return result;
+ }
+}
\ No newline at end of file
Added: ace/sandbox/marrs/org.apache.ace.client.rest/src/org/apache/ace/client/rest/RepositoryValueObject.java
URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.client.rest/src/org/apache/ace/client/rest/RepositoryValueObject.java?rev=1357570&view=auto
==============================================================================
--- ace/sandbox/marrs/org.apache.ace.client.rest/src/org/apache/ace/client/rest/RepositoryValueObject.java (added)
+++ ace/sandbox/marrs/org.apache.ace.client.rest/src/org/apache/ace/client/rest/RepositoryValueObject.java Thu Jul 5 12:09:30 2012
@@ -0,0 +1,29 @@
+/*
+ * 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.util.Map;
+
+/**
+ * A value object that is used to store incoming JSON data.
+ */
+public class RepositoryValueObject {
+ public Map<String, String> attributes;
+ public Map<String, String> tags;
+}
\ No newline at end of file
Added: ace/sandbox/marrs/org.apache.ace.client.rest/src/org/apache/ace/client/rest/Workspace.java
URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.client.rest/src/org/apache/ace/client/rest/Workspace.java?rev=1357570&view=auto
==============================================================================
--- ace/sandbox/marrs/org.apache.ace.client.rest/src/org/apache/ace/client/rest/Workspace.java (added)
+++ ace/sandbox/marrs/org.apache.ace.client.rest/src/org/apache/ace/client/rest/Workspace.java Thu Jul 5 12:09:30 2012
@@ -0,0 +1,432 @@
+/*
+ * 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.net.MalformedURLException;
+import java.net.URL;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.ace.authentication.api.AuthenticationService;
+import org.apache.ace.client.repository.ObjectRepository;
+import org.apache.ace.client.repository.RepositoryAdmin;
+import org.apache.ace.client.repository.RepositoryAdminLoginContext;
+import org.apache.ace.client.repository.RepositoryObject;
+import org.apache.ace.client.repository.SessionFactory;
+import org.apache.ace.client.repository.repository.Artifact2FeatureAssociationRepository;
+import org.apache.ace.client.repository.repository.ArtifactRepository;
+import org.apache.ace.client.repository.repository.Distribution2TargetAssociationRepository;
+import org.apache.ace.client.repository.repository.DistributionRepository;
+import org.apache.ace.client.repository.repository.Feature2DistributionAssociationRepository;
+import org.apache.ace.client.repository.repository.FeatureRepository;
+import org.apache.ace.client.repository.repository.TargetRepository;
+import org.apache.ace.client.repository.stateful.StatefulTargetObject;
+import org.apache.ace.client.repository.stateful.StatefulTargetRepository;
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.DependencyManager;
+import org.osgi.service.log.LogService;
+import org.osgi.service.useradmin.User;
+import org.osgi.service.useradmin.UserAdmin;
+
+public class Workspace {
+ static final String ARTIFACT = "artifact";
+ static final String ARTIFACT2FEATURE = "artifact2feature";
+ static final String FEATURE = "feature";
+ static final String FEATURE2DISTRIBUTION = "feature2distribution";
+ static final String DISTRIBUTION = "distribution";
+ static final String DISTRIBUTION2TARGET = "distribution2target";
+ static final String TARGET = "target";
+
+ private final String m_sessionID;
+ private final URL m_repositoryURL;
+ private final URL m_obrURL;
+ private final String m_customerName;
+ private final String m_storeRepositoryName;
+ private final String m_distributionRepositoryName;
+ private final String m_deploymentRepositoryName;
+ private final String m_serverUser;
+ private final boolean m_useAuthentication;
+
+ private volatile AuthenticationService m_authenticationService;
+ private volatile DependencyManager m_manager;
+ private volatile RepositoryAdmin m_repositoryAdmin;
+ private volatile ArtifactRepository m_artifactRepository;
+ private volatile FeatureRepository m_featureRepository;
+ private volatile DistributionRepository m_distributionRepository;
+ private volatile StatefulTargetRepository m_statefulTargetRepository;
+ private volatile Artifact2FeatureAssociationRepository m_artifact2FeatureAssociationRepository;
+ private volatile Feature2DistributionAssociationRepository m_feature2DistributionAssociationRepository;
+ private volatile Distribution2TargetAssociationRepository m_distribution2TargetAssociationRepository;
+ private volatile UserAdmin m_userAdmin;
+ private volatile LogService m_log;
+
+ public Workspace(String sessionID, String repositoryURL, String obrURL, String customerName, String storeRepositoryName, String distributionRepositoryName, String deploymentRepositoryName, boolean useAuthentication, String serverUser) throws MalformedURLException {
+ m_sessionID = sessionID;
+ m_repositoryURL = new URL(repositoryURL);
+ m_obrURL = new URL(obrURL);
+ m_customerName = customerName;
+ m_storeRepositoryName = storeRepositoryName;
+ m_distributionRepositoryName = distributionRepositoryName;
+ m_deploymentRepositoryName = deploymentRepositoryName;
+ m_useAuthentication = useAuthentication;
+ 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 + ")")
+ .setRequired(isRequired)
+ .setInstanceBound(true)
+ );
+ }
+
+ private void addDependency(Component component, Class service, boolean isRequired) {
+ component.add(m_manager.createServiceDependency()
+ .setService(service)
+ .setRequired(isRequired)
+ .setInstanceBound(true)
+ );
+ }
+
+ public void init(Component component) {
+ addSessionDependency(component, RepositoryAdmin.class, true);
+ addSessionDependency(component, ArtifactRepository.class, true);
+ addSessionDependency(component, FeatureRepository.class, true);
+ addSessionDependency(component, DistributionRepository.class, true);
+ addSessionDependency(component, TargetRepository.class, true);
+ addSessionDependency(component, StatefulTargetRepository.class, true);
+ addSessionDependency(component, Artifact2FeatureAssociationRepository.class, true);
+ addSessionDependency(component, Feature2DistributionAssociationRepository.class, true);
+ addSessionDependency(component, Distribution2TargetAssociationRepository.class, true);
+ addDependency(component, AuthenticationService.class, m_useAuthentication);
+ addDependency(component, UserAdmin.class, true);
+ addDependency(component, LogService.class, false);
+ }
+
+ public void start() {
+ }
+
+ public void destroy() {
+ }
+
+ public boolean login(HttpServletRequest request) {
+ try {
+ final User user;
+ if (m_useAuthentication) {
+ // Use the authentication service to authenticate the given request...
+ user = m_authenticationService.authenticate(request);
+ } else {
+ // Use the "hardcoded" user to login with...
+ user = m_userAdmin.getUser("username", m_serverUser);
+ }
+
+ if (user == null) {
+ // No user obtained through request/fallback scenario; login failed...
+ return false;
+ }
+
+ RepositoryAdminLoginContext context = m_repositoryAdmin.createLoginContext(user);
+
+ context.setObrBase(m_obrURL)
+ .add(context.createShopRepositoryContext()
+ .setLocation(m_repositoryURL)
+ .setCustomer(m_customerName)
+ .setName(m_storeRepositoryName)
+ .setWriteable())
+ .add(context.createTargetRepositoryContext()
+ .setLocation(m_repositoryURL)
+ .setCustomer(m_customerName)
+ .setName(m_distributionRepositoryName)
+ .setWriteable())
+ .add(context.createDeploymentRepositoryContext()
+ .setLocation(m_repositoryURL)
+ .setCustomer(m_customerName)
+ .setName(m_deploymentRepositoryName)
+ .setWriteable());
+
+ m_repositoryAdmin.login(context);
+ m_repositoryAdmin.checkout();
+ }
+ catch (IOException e) {
+ e.printStackTrace();
+ m_log.log(LogService.LOG_ERROR, "Could not login and checkout. Workspace will probably not work correctly.", e);
+ }
+
+ return true;
+ }
+
+ public void checkout() throws IOException {
+ m_repositoryAdmin.checkout();
+ }
+
+ public void commit() throws IOException {
+ m_repositoryAdmin.commit();
+ }
+
+ public RepositoryObject getRepositoryObject(String entityType, String entityId) {
+ return getObjectRepository(entityType).get(entityId);
+ }
+
+ public List<RepositoryObject> getRepositoryObjects(String entityType) {
+ List list = getObjectRepository(entityType).get();
+ if (list != null) {
+ return list;
+ }
+ else {
+ return Collections.EMPTY_LIST;
+ }
+ }
+
+ public RepositoryObject addRepositoryObject(String entityType, Map<String, String> attributes, Map<String, String> tags) throws IllegalArgumentException {
+ if (TARGET.equals(entityType)) {
+ return ((StatefulTargetRepository) getObjectRepository(TARGET)).preregister(attributes, tags);
+ }
+ else {
+ if (ARTIFACT2FEATURE.equals(entityType) || FEATURE2DISTRIBUTION.equals(entityType) || DISTRIBUTION2TARGET.equals(entityType)) {
+
+ String leftAttribute = attributes.get("left");
+ String rightAttribute = attributes.get("right");
+
+ RepositoryObject left = null;
+ if(leftAttribute != null) {
+ left = getLeft(entityType, leftAttribute);
+ }
+
+ RepositoryObject right = null;
+ if(rightAttribute != null) {
+ right = getRight(entityType, rightAttribute);
+ }
+
+
+ if (left != null) {
+ if (left instanceof StatefulTargetObject) {
+ if (((StatefulTargetObject) left).isRegistered()) {
+ attributes.put("leftEndpoint", ((StatefulTargetObject) left).getTargetObject().getAssociationFilter(attributes));
+ }
+ }
+ else {
+ attributes.put("leftEndpoint", left.getAssociationFilter(attributes));
+ }
+ }
+ if (right != null) {
+ if (right instanceof StatefulTargetObject) {
+ if (((StatefulTargetObject) right).isRegistered()) {
+ attributes.put("rightEndpoint", ((StatefulTargetObject) right).getTargetObject().getAssociationFilter(attributes));
+ }
+ }
+ else {
+ attributes.put("rightEndpoint", right.getAssociationFilter(attributes));
+ }
+ }
+ }
+ return getObjectRepository(entityType).create(attributes, tags);
+ }
+ }
+
+ /**
+ * Approves a given stateful target object.
+ *
+ * @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(StatefulTargetObject targetObject) {
+ targetObject.approve();
+ return targetObject;
+ }
+
+ /**
+ * Registers a given stateful target object.
+ *
+ * @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(StatefulTargetObject targetObject) {
+ if (targetObject.isRegistered()) {
+ return null;
+ }
+ targetObject.register();
+ return targetObject;
+ }
+
+ public void updateObjectWithData(String entityType, String entityId, RepositoryValueObject valueObject) {
+ RepositoryObject repositoryObject = getRepositoryObject(entityType, entityId);
+ // first handle the attributes
+ for (Entry<String, String> attribute : valueObject.attributes.entrySet()) {
+ String key = attribute.getKey();
+ String value = attribute.getValue();
+ // only add/update the attribute if it actually changed
+ if (!value.equals(repositoryObject.getAttribute(key))) {
+ repositoryObject.addAttribute(key, value);
+ }
+ }
+ Enumeration<String> keys = repositoryObject.getAttributeKeys();
+ while (keys.hasMoreElements()) {
+ String key = keys.nextElement();
+ if (!valueObject.attributes.containsKey(key)) {
+ // TODO since we cannot remove keys right now, we null them
+ repositoryObject.addAttribute(key, null);
+ }
+ }
+ if (ARTIFACT2FEATURE.equals(entityType) || FEATURE2DISTRIBUTION.equals(entityType) || DISTRIBUTION2TARGET.equals(entityType)) {
+ String leftAttribute = repositoryObject.getAttribute("left");
+ String rightAttribute = repositoryObject.getAttribute("right");
+
+ RepositoryObject left = null;
+ if (leftAttribute != null) {
+ left = getLeft(entityType, leftAttribute);
+ }
+
+ RepositoryObject right = null;
+ if (rightAttribute != null) {
+ right = getRight(entityType, rightAttribute);
+ }
+
+ if (left != null) {
+ if (left instanceof StatefulTargetObject) {
+ if (((StatefulTargetObject) left).isRegistered()) {
+ repositoryObject.addAttribute("leftEndpoint", ((StatefulTargetObject) left).getTargetObject().getAssociationFilter(getAttributes(((StatefulTargetObject) left).getTargetObject())));
+ }
+ }
+ else {
+ repositoryObject.addAttribute("leftEndpoint", left.getAssociationFilter(getAttributes(left)));
+ }
+ }
+ if (right != null) {
+ if (right instanceof StatefulTargetObject) {
+ if (((StatefulTargetObject) right).isRegistered()) {
+ repositoryObject.addAttribute("rightEndpoint", ((StatefulTargetObject) right).getTargetObject().getAssociationFilter(getAttributes(((StatefulTargetObject) right).getTargetObject())));
+ }
+ }
+ else {
+ repositoryObject.addAttribute("rightEndpoint", right.getAssociationFilter(getAttributes(right)));
+ }
+ }
+ }
+ // now handle the tags in a similar way
+ for (Entry<String, String> attribute : valueObject.tags.entrySet()) {
+ String key = attribute.getKey();
+ String value = attribute.getValue();
+ // only add/update the tag if it actually changed
+ if (!value.equals(repositoryObject.getTag(key))) {
+ repositoryObject.addTag(key, value);
+ }
+ }
+ keys = repositoryObject.getTagKeys();
+ while (keys.hasMoreElements()) {
+ String key = keys.nextElement();
+ if (!valueObject.tags.containsKey(key)) {
+ // TODO since we cannot remove keys right now, we null them
+ repositoryObject.addTag(key, null);
+ }
+ }
+ }
+
+ private Map getAttributes(RepositoryObject object) {
+ Map result = new HashMap();
+ for (Enumeration<String> keys = object.getAttributeKeys(); keys.hasMoreElements();) {
+ String key = keys.nextElement();
+ result.put(key, object.getAttribute(key));
+ }
+ return result;
+ }
+
+ public RepositoryObject getLeft(String entityType, String entityId) {
+ if (ARTIFACT2FEATURE.equals(entityType)) {
+ return getObjectRepository(ARTIFACT).get(entityId);
+ }
+ else if (FEATURE2DISTRIBUTION.equals(entityType)) {
+ return getObjectRepository(FEATURE).get(entityId);
+ }
+ 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) {
+ if (ARTIFACT2FEATURE.equals(entityType)) {
+ return getObjectRepository(FEATURE).get(entityId);
+ }
+ else if (FEATURE2DISTRIBUTION.equals(entityType)) {
+ return getObjectRepository(DISTRIBUTION).get(entityId);
+ }
+ 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) {
+ 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) {
+ if (ARTIFACT.equals(entityType)) {
+ return m_artifactRepository;
+ }
+ if (ARTIFACT2FEATURE.equals(entityType)) {
+ return m_artifact2FeatureAssociationRepository;
+ }
+ if (FEATURE.equals(entityType)) {
+ return m_featureRepository;
+ }
+ if (FEATURE2DISTRIBUTION.equals(entityType)) {
+ return m_feature2DistributionAssociationRepository;
+ }
+ if (DISTRIBUTION.equals(entityType)) {
+ return m_distributionRepository;
+ }
+ if (DISTRIBUTION2TARGET.equals(entityType)) {
+ return m_distribution2TargetAssociationRepository;
+ }
+ if (TARGET.equals(entityType)) {
+ return m_statefulTargetRepository;
+ }
+ throw new IllegalArgumentException("Unknown entity type: " + entityType);
+ }
+}
Added: ace/sandbox/marrs/org.apache.ace.client.rest/test-output/Default suite/Default test.html
URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.client.rest/test-output/Default%20suite/Default%20test.html?rev=1357570&view=auto
==============================================================================
--- ace/sandbox/marrs/org.apache.ace.client.rest/test-output/Default suite/Default test.html (added)
+++ ace/sandbox/marrs/org.apache.ace.client.rest/test-output/Default suite/Default test.html Thu Jul 5 12:09:30 2012
@@ -0,0 +1,89 @@
+<html>
+<head>
+<title>TestNG: Default test</title>
+<link href="../testng.css" rel="stylesheet" type="text/css" />
+<link href="../my-testng.css" rel="stylesheet" type="text/css" />
+
+<style type="text/css">
+.log { display: none;}
+.stack-trace { display: none;}
+</style>
+<script type="text/javascript">
+<!--
+function flip(e) {
+ current = e.style.display;
+ if (current == 'block') {
+ e.style.display = 'none';
+ return 0;
+ }
+ else {
+ e.style.display = 'block';
+ return 1;
+ }
+}
+
+function toggleBox(szDivId, elem, msg1, msg2)
+{
+ var res = -1; if (document.getElementById) {
+ res = flip(document.getElementById(szDivId));
+ }
+ else if (document.all) {
+ // this is the way old msie versions work
+ res = flip(document.all[szDivId]);
+ }
+ if(elem) {
+ if(res == 0) elem.innerHTML = msg1; else elem.innerHTML = msg2;
+ }
+
+}
+
+function toggleAllBoxes() {
+ if (document.getElementsByTagName) {
+ d = document.getElementsByTagName('div');
+ for (i = 0; i < d.length; i++) {
+ if (d[i].className == 'log') {
+ flip(d[i]);
+ }
+ }
+ }
+}
+
+// -->
+</script>
+
+</head>
+<body>
+<h2 align='center'>Default test</h2><table border='1' align="center">
+<tr>
+<td>Tests passed/Failed/Skipped:</td><td>2/0/0</td>
+</tr><tr>
+<td>Started on:</td><td>Wed Jul 04 12:17:19 CEST 2012</td>
+</tr>
+<tr><td>Total time:</td><td>0 seconds (429 ms)</td>
+</tr><tr>
+<td>Included groups:</td><td></td>
+</tr><tr>
+<td>Excluded groups:</td><td></td>
+</tr>
+</table><p/>
+<small><i>(Hover the method name to see the test class name)</i></small><p/>
+<table width='100%' border='1' class='invocation-passed'>
+<tr><td colspan='4' align='center'><b>PASSED TESTS</b></td></tr>
+<tr><td><b>Test method</b></td>
+<td width="30%"><b>Exception</b></td>
+<td width="10%"><b>Time (seconds)</b></td>
+<td><b>Instance</b></td>
+</tr>
+<tr>
+<td title='org.apache.ace.client.rest.RESTClientTest.testPathTransforms()'><b>testPathTransforms</b><br>Test class: org.apache.ace.client.rest.RESTClientTest</td>
+<td></td>
+<td>0</td>
+<td>org.apache.ace.client.rest.RESTClientTest@44050988</td></tr>
+<tr>
+<td title='org.apache.ace.client.rest.RESTClientTest.testPropertyGetter()'><b>testPropertyGetter</b><br>Test class: org.apache.ace.client.rest.RESTClientTest</td>
+<td></td>
+<td>0</td>
+<td>org.apache.ace.client.rest.RESTClientTest@44050988</td></tr>
+</table><p>
+</body>
+</html>
\ No newline at end of file
Added: ace/sandbox/marrs/org.apache.ace.client.rest/test-output/Default suite/Default test.xml
URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.client.rest/test-output/Default%20suite/Default%20test.xml?rev=1357570&view=auto
==============================================================================
--- ace/sandbox/marrs/org.apache.ace.client.rest/test-output/Default suite/Default test.xml (added)
+++ ace/sandbox/marrs/org.apache.ace.client.rest/test-output/Default suite/Default test.xml Thu Jul 5 12:09:30 2012
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated by org.testng.reporters.JUnitXMLReporter -->
+<testsuite hostname="Marcels-MacBook-Pro.local" name="Default test" tests="2" failures="0" timestamp="4 Jul 2012 10:17:20 GMT" time="0.429" errors="0">
+ <testcase name="testPathTransforms" time="0.414" classname="org.apache.ace.client.rest.RESTClientTest"/>
+ <testcase name="testPropertyGetter" time="0.0010" classname="org.apache.ace.client.rest.RESTClientTest"/>
+</testsuite> <!-- Default test -->
Added: ace/sandbox/marrs/org.apache.ace.client.rest/test-output/bullet_point.png
URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.client.rest/test-output/bullet_point.png?rev=1357570&view=auto
==============================================================================
Binary file - no diff available.
Propchange: ace/sandbox/marrs/org.apache.ace.client.rest/test-output/bullet_point.png
------------------------------------------------------------------------------
svn:mime-type = application/octet-stream
Added: ace/sandbox/marrs/org.apache.ace.client.rest/test-output/collapseall.gif
URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.client.rest/test-output/collapseall.gif?rev=1357570&view=auto
==============================================================================
Binary file - no diff available.
Propchange: ace/sandbox/marrs/org.apache.ace.client.rest/test-output/collapseall.gif
------------------------------------------------------------------------------
svn:mime-type = application/octet-stream
Added: ace/sandbox/marrs/org.apache.ace.client.rest/test-output/emailable-report.html
URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.client.rest/test-output/emailable-report.html?rev=1357570&view=auto
==============================================================================
--- ace/sandbox/marrs/org.apache.ace.client.rest/test-output/emailable-report.html (added)
+++ ace/sandbox/marrs/org.apache.ace.client.rest/test-output/emailable-report.html Thu Jul 5 12:09:30 2012
@@ -0,0 +1,45 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>TestNG: Unit Test</title>
+<style type="text/css">
+table caption,table.info_table,table.param,table.passed,table.failed {margin-bottom:10px;border:1px solid #000099;border-collapse:collapse;empty-cells:show;}
+table.info_table td,table.info_table th,table.param td,table.param th,table.passed td,table.passed th,table.failed td,table.failed th {
+border:1px solid #000099;padding:.25em .5em .25em .5em
+}
+table.param th {vertical-align:bottom}
+td.numi,th.numi,td.numi_attn {
+text-align:right
+}
+tr.total td {font-weight:bold}
+table caption {
+text-align:center;font-weight:bold;
+}
+table.passed tr.stripe td,table tr.passedodd td {background-color: #00AA00;}
+table.passed td,table tr.passedeven td {background-color: #33FF33;}
+table.passed tr.stripe td,table tr.skippedodd td {background-color: #cccccc;}
+table.passed td,table tr.skippedodd td {background-color: #dddddd;}
+table.failed tr.stripe td,table tr.failedodd td,table.param td.numi_attn {background-color: #FF3333;}
+table.failed td,table tr.failedeven td,table.param tr.stripe td.numi_attn {background-color: #DD0000;}
+tr.stripe td,tr.stripe th {background-color: #E6EBF9;}
+p.totop {font-size:85%;text-align:center;border-bottom:2px black solid}
+div.shootout {padding:2em;border:3px #4854A8 solid}
+</style>
+</head>
+<body>
+<table cellspacing=0 cellpadding=0 class="param">
+<tr><th>Test</th><th class="numi">Methods<br/>Passed</th><th class="numi">Scenarios<br/>Passed</th><th class="numi"># skipped</th><th class="numi"># failed</th><th class="numi">Total<br/>Time</th><th class="numi">Included<br/>Groups</th><th class="numi">Excluded<br/>Groups</th></tr>
+<tr><td style="text-align:left;padding-right:2em">Default test</td><td class="numi">2</td><td class="numi">2</td><td class="numi">0</td><td class="numi">0</td><td class="numi">0.4 seconds</td><td class="numi"></td><td class="numi"></td></tr>
+</table>
+<a id="summary"></a>
+<table cellspacing=0 cellpadding=0 class="passed">
+<tr><th>Class</th><th>Method</th><th># of<br/>Scenarios</th><th>Start</th><th>Time<br/>(ms)</th></tr>
+<tr><th colspan="4">Default test — passed</th></tr>
+<tr class="passedodd"><td rowspan="2">org.apache.ace.client.rest.RESTClientTest<td><a href="#m1"><b>testPathTransforms</b> (unit) </a></td><td class="numi">1</td><td>1341397039609</td><td class="numi">414</td></tr><tr class="passedodd"><td><a href="#m2"><b>testPropertyGetter</b> (unit) </a></td><td class="numi">1</td><td>1341397040024</td><td class="numi">1</td></tr>
+</table>
+<h1>Default test</h1>
+<a id="m1"></a><h2>org.apache.ace.client.rest.RESTClientTest:testPathTransforms</h2>
+<p class="totop"><a href="#summary">back to summary</a></p>
+<a id="m2"></a><h2>org.apache.ace.client.rest.RESTClientTest:testPropertyGetter</h2>
+<p class="totop"><a href="#summary">back to summary</a></p>
+</body></html>
Added: ace/sandbox/marrs/org.apache.ace.client.rest/test-output/failed.png
URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.client.rest/test-output/failed.png?rev=1357570&view=auto
==============================================================================
Binary file - no diff available.
Propchange: ace/sandbox/marrs/org.apache.ace.client.rest/test-output/failed.png
------------------------------------------------------------------------------
svn:mime-type = application/octet-stream
Added: ace/sandbox/marrs/org.apache.ace.client.rest/test-output/index.html
URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.client.rest/test-output/index.html?rev=1357570&view=auto
==============================================================================
--- ace/sandbox/marrs/org.apache.ace.client.rest/test-output/index.html (added)
+++ ace/sandbox/marrs/org.apache.ace.client.rest/test-output/index.html Thu Jul 5 12:09:30 2012
@@ -0,0 +1,263 @@
+<!DOCTYPE html>
+
+<html>
+ <head>
+ <title>TestNG new reports</title>
+
+ <link type="text/css" href="testng-reports.css" rel="stylesheet" />
+ <script type="text/javascript" src="jquery-1.7.1.min.js"></script>
+ <script type="text/javascript" src="testng-reports.js"></script>
+ <script type="text/javascript" src="https://www.google.com/jsapi"></script>
+ <script type='text/javascript'>
+ google.load('visualization', '1', {packages:['table']});
+ google.setOnLoadCallback(drawTable);
+ var suiteTableInitFunctions = new Array();
+ var suiteTableData = new Array();
+ </script>
+ <!--
+ <script type="text/javascript" src="jquery-ui/js/jquery-ui-1.8.16.custom.min.js"></script>
+ -->
+ </head>
+
+ <body>
+ <div class="top-banner-root">
+ <span class="top-banner-title-font">Test results</span>
+ <br/>
+ <span class="top-banner-font-1">1 suite</span>
+ </div> <!-- top-banner-root -->
+ <div class="navigator-root">
+ <div class="navigator-suite-header">
+ <span>All suites</span>
+ <a href="#" class="collapse-all-link" title="Collapse/expand all the suites">
+ <img class="collapse-all-icon" src="collapseall.gif">
+ </img> <!-- collapse-all-icon -->
+ </a> <!-- collapse-all-link -->
+ </div> <!-- navigator-suite-header -->
+ <div class="suite">
+ <div class="rounded-window">
+ <div class="suite-header light-rounded-window-top">
+ <a href="#" class="navigator-link" panel-name="suite-Default_suite">
+ <span class="suite-name border-passed">Default suite</span>
+ </a> <!-- navigator-link -->
+ </div> <!-- suite-header light-rounded-window-top -->
+ <div class="navigator-suite-content">
+ <div class="suite-section-title">
+ <span>Info</span>
+ </div> <!-- suite-section-title -->
+ <div class="suite-section-content">
+ <ul>
+ <li>
+ <a href="#" class="navigator-link " panel-name="test-xml-Default_suite">
+ <span>testng-customsuite.xml</span>
+ </a> <!-- navigator-link -->
+ </li>
+ <li>
+ <a href="#" class="navigator-link " panel-name="testlist-Default_suite">
+ <span class="test-stats">1 test</span>
+ </a> <!-- navigator-link -->
+ </li>
+ <li>
+ <a href="#" class="navigator-link " panel-name="group-Default_suite">
+ <span>1 group</span>
+ </a> <!-- navigator-link -->
+ </li>
+ <li>
+ <a href="#" class="navigator-link " panel-name="times-Default_suite">
+ <span>Times</span>
+ </a> <!-- navigator-link -->
+ </li>
+ <li>
+ <a href="#" class="navigator-link " panel-name="reporter-Default_suite">
+ <span>Reporter output</span>
+ </a> <!-- navigator-link -->
+ </li>
+ <li>
+ <a href="#" class="navigator-link " panel-name="ignored-methods-Default_suite">
+ <span>Ignored methods</span>
+ </a> <!-- navigator-link -->
+ </li>
+ <li>
+ <a href="#" class="navigator-link " panel-name="chronological-Default_suite">
+ <span>Chronological view</span>
+ </a> <!-- navigator-link -->
+ </li>
+ </ul>
+ </div> <!-- suite-section-content -->
+ <div class="result-section">
+ <div class="suite-section-title">
+ <span>Results</span>
+ </div> <!-- suite-section-title -->
+ <div class="suite-section-content">
+ <ul>
+ <li>
+ <span class="method-stats">2 methods, 2 passed</span>
+ </li>
+ <li>
+ <span class="method-list-title passed">Passed methods</span>
+ <span class="show-or-hide-methods passed">
+ <a href="#" panel-name="suite-Default_suite" class="hide-methods passed suite-Default_suite"> (hide)</a> <!-- hide-methods passed suite-Default_suite -->
+ <a href="#" panel-name="suite-Default_suite" class="show-methods passed suite-Default_suite"> (show)</a> <!-- show-methods passed suite-Default_suite -->
+ </span>
+ <div class="method-list-content passed suite-Default_suite">
+ <span>
+ <img width="3%" src="passed.png"/>
+ <a href="#" class="method navigator-link" panel-name="suite-Default_suite" title="org.apache.ace.client.rest.RESTClientTest" hash-for-method="testPathTransforms">testPathTransforms</a> <!-- method navigator-link -->
+ </span>
+ <br/>
+ <span>
+ <img width="3%" src="passed.png"/>
+ <a href="#" class="method navigator-link" panel-name="suite-Default_suite" title="org.apache.ace.client.rest.RESTClientTest" hash-for-method="testPropertyGetter">testPropertyGetter</a> <!-- method navigator-link -->
+ </span>
+ <br/>
+ </div> <!-- method-list-content passed suite-Default_suite -->
+ </li>
+ </ul>
+ </div> <!-- suite-section-content -->
+ </div> <!-- result-section -->
+ </div> <!-- navigator-suite-content -->
+ </div> <!-- rounded-window -->
+ </div> <!-- suite -->
+ </div> <!-- navigator-root -->
+ <div class="wrapper">
+ <div class="main-panel-root">
+ <div panel-name="suite-Default_suite" class="panel Default_suite">
+ <div class="suite-Default_suite-class-passed">
+ <div class="main-panel-header rounded-window-top">
+ <img src="passed.png"/>
+ <span class="class-name">org.apache.ace.client.rest.RESTClientTest</span>
+ </div> <!-- main-panel-header rounded-window-top -->
+ <div class="main-panel-content rounded-window-bottom">
+ <div class="method">
+ <div class="method-content">
+ <a name="testPathTransforms">
+ </a> <!-- testPathTransforms -->
+ <span class="method-name">testPathTransforms</span>
+ </div> <!-- method-content -->
+ </div> <!-- method -->
+ <div class="method">
+ <div class="method-content">
+ <a name="testPropertyGetter">
+ </a> <!-- testPropertyGetter -->
+ <span class="method-name">testPropertyGetter</span>
+ </div> <!-- method-content -->
+ </div> <!-- method -->
+ </div> <!-- main-panel-content rounded-window-bottom -->
+ </div> <!-- suite-Default_suite-class-passed -->
+ </div> <!-- panel Default_suite -->
+ <div panel-name="test-xml-Default_suite" class="panel">
+ <div class="main-panel-header rounded-window-top">
+ <span class="header-content">/private/var/folders/4k/58plh09n605ffjkhv0kpcx540000gn/T/testng-eclipse-599445437/testng-customsuite.xml</span>
+ </div> <!-- main-panel-header rounded-window-top -->
+ <div class="main-panel-content rounded-window-bottom">
+ <pre>
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
+<suite name="Default suite">
+ <test verbose="2" name="Default test" preserve-order="true">
+ <classes>
+ <class name="org.apache.ace.client.rest.RESTClientTest"/>
+ </classes>
+ </test> <!-- Default test -->
+</suite> <!-- Default suite -->
+ </pre>
+ </div> <!-- main-panel-content rounded-window-bottom -->
+ </div> <!-- panel -->
+ <div panel-name="testlist-Default_suite" class="panel">
+ <div class="main-panel-header rounded-window-top">
+ <span class="header-content">Tests for Default suite</span>
+ </div> <!-- main-panel-header rounded-window-top -->
+ <div class="main-panel-content rounded-window-bottom">
+ <ul>
+ <li>
+ <span class="test-name">Default test (1 class)</span>
+ </li>
+ </ul>
+ </div> <!-- main-panel-content rounded-window-bottom -->
+ </div> <!-- panel -->
+ <div panel-name="group-Default_suite" class="panel">
+ <div class="main-panel-header rounded-window-top">
+ <span class="header-content">Groups for Default suite</span>
+ </div> <!-- main-panel-header rounded-window-top -->
+ <div class="main-panel-content rounded-window-bottom">
+ <div class="test-group">
+ <span class="test-group-name">unit</span>
+ <br/>
+ <div class="method-in-group">
+ <span class="method-in-group-name">testPathTransforms</span>
+ <br/>
+ </div> <!-- method-in-group -->
+ <div class="method-in-group">
+ <span class="method-in-group-name">testPropertyGetter</span>
+ <br/>
+ </div> <!-- method-in-group -->
+ </div> <!-- test-group -->
+ </div> <!-- main-panel-content rounded-window-bottom -->
+ </div> <!-- panel -->
+ <div panel-name="times-Default_suite" class="panel">
+ <div class="main-panel-header rounded-window-top">
+ <span class="header-content">Times for Default suite</span>
+ </div> <!-- main-panel-header rounded-window-top -->
+ <div class="main-panel-content rounded-window-bottom">
+ <div class="times-div">
+ <script type="text/javascript">
+suiteTableInitFunctions.push('tableData_Default_suite');
+function tableData_Default_suite() {
+var data = new google.visualization.DataTable();
+data.addColumn('number', 'Number');
+data.addColumn('string', 'Method');
+data.addColumn('string', 'Class');
+data.addColumn('number', 'Time (ms)');
+data.addRows(2);
+data.setCell(0, 0, 0)
+data.setCell(0, 1, 'testPathTransforms')
+data.setCell(0, 2, 'org.apache.ace.client.rest.RESTClientTest')
+data.setCell(0, 3, 414);
+data.setCell(1, 0, 1)
+data.setCell(1, 1, 'testPropertyGetter')
+data.setCell(1, 2, 'org.apache.ace.client.rest.RESTClientTest')
+data.setCell(1, 3, 1);
+window.suiteTableData['Default_suite']= { tableData: data, tableDiv: 'times-div-Default_suite'}
+return data;
+}
+ </script>
+ <span class="suite-total-time">Total running time: 415 ms</span>
+ <div id="times-div-Default_suite">
+ </div> <!-- times-div-Default_suite -->
+ </div> <!-- times-div -->
+ </div> <!-- main-panel-content rounded-window-bottom -->
+ </div> <!-- panel -->
+ <div panel-name="reporter-Default_suite" class="panel">
+ <div class="main-panel-header rounded-window-top">
+ <span class="header-content">Reporter output for Default suite</span>
+ </div> <!-- main-panel-header rounded-window-top -->
+ <div class="main-panel-content rounded-window-bottom">
+ </div> <!-- main-panel-content rounded-window-bottom -->
+ </div> <!-- panel -->
+ <div panel-name="ignored-methods-Default_suite" class="panel">
+ <div class="main-panel-header rounded-window-top">
+ <span class="header-content">0 ignored methods</span>
+ </div> <!-- main-panel-header rounded-window-top -->
+ <div class="main-panel-content rounded-window-bottom">
+ </div> <!-- main-panel-content rounded-window-bottom -->
+ </div> <!-- panel -->
+ <div panel-name="chronological-Default_suite" class="panel">
+ <div class="main-panel-header rounded-window-top">
+ <span class="header-content">Methods in chronological order</span>
+ </div> <!-- main-panel-header rounded-window-top -->
+ <div class="main-panel-content rounded-window-bottom">
+ <div class="chronological-class">
+ <div class="chronological-class-name">org.apache.ace.client.rest.RESTClientTest</div> <!-- chronological-class-name -->
+ <div class="test-method">
+ <span class="method-name">testPathTransforms</span>
+ <span class="method-start">0 ms</span>
+ </div> <!-- test-method -->
+ <div class="test-method">
+ <span class="method-name">testPropertyGetter</span>
+ <span class="method-start">415 ms</span>
+ </div> <!-- test-method -->
+ </div> <!-- main-panel-content rounded-window-bottom -->
+ </div> <!-- panel -->
+ </div> <!-- main-panel-root -->
+ </div> <!-- wrapper -->
+ </body>
+</html>