diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/Content.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/Content.java index f04e619013b8f615c6451c20595e0937acfd4569..90a9d4656788ef5b0a6d2850e15c0957f7cb00d7 100644 --- a/modules/activiti-cycle/src/main/java/org/activiti/cycle/Content.java +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/Content.java @@ -24,10 +24,11 @@ public class Content { private InputStream contentAsInputStream; - private byte[] loadBytes(InputStream inputStream) { - byte[] bytes = IoUtil.readInputStream(inputStream, "Repository Artifact Content"); - IoUtil.closeSilently(inputStream); - return bytes; + private byte[] loadBytes() { + contentAsByteArray = IoUtil.readInputStream(contentAsInputStream, "Repository Artifact Content"); + IoUtil.closeSilently(contentAsInputStream); + contentAsInputStream = null; + return contentAsByteArray; } public byte[] asByteArray() { @@ -36,7 +37,7 @@ public class Content { } else if (contentAsString != null) { return contentAsString.getBytes(); } else if (contentAsInputStream != null) { - return loadBytes(contentAsInputStream); + return loadBytes(); } else { throw new RuntimeException("Not yet implemented"); } @@ -48,7 +49,7 @@ public class Content { } else if (contentAsByteArray != null) { return new String(contentAsByteArray); } else if (contentAsInputStream != null) { - return new String(loadBytes(contentAsInputStream)); + return new String(loadBytes()); } else { throw new RuntimeException("Not yet implemented"); } diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/CycleComponentFactory.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/CycleComponentFactory.java index 60c7c0c9f901e2e8213a79f5e0b37c42a399f6bb..e79dccb8b2da5e003ea53a8ada2a55ec84342d8f 100644 --- a/modules/activiti-cycle/src/main/java/org/activiti/cycle/CycleComponentFactory.java +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/CycleComponentFactory.java @@ -27,6 +27,7 @@ import org.activiti.cycle.context.CycleContext; import org.activiti.cycle.context.CycleContextType; import org.activiti.cycle.context.CycleRequestContext; import org.activiti.cycle.context.CycleSessionContext; +import org.activiti.cycle.event.CycleEventListener; import org.activiti.cycle.impl.CycleComponentInvocationHandler; import org.activiti.cycle.impl.Interceptor; import org.activiti.cycle.transform.ContentArtifactTypeTransformation; @@ -48,13 +49,13 @@ public abstract class CycleComponentFactory { public final String name; public final CycleContextType contextType; public final Class< ? > clazz; - public final Set> types; + public final Set> types; private CycleComponentDescriptor(String name, CycleContextType type, Class< ? > clazz) { this.name = name; this.contextType = type; this.clazz = clazz; - this.types = new HashSet>(); + this.types = new HashSet>(); } @Override public String toString() { @@ -216,7 +217,6 @@ public abstract class CycleComponentFactory { } } - private Object decorateInstance(Object instanceToDecorate, Class< ? extends Interceptor>[] interceptorClasses) throws Exception { Set interfaces = new HashSet(); Class< ? > superClass = instanceToDecorate.getClass(); @@ -256,6 +256,7 @@ public abstract class CycleComponentFactory { componentTypes.add(ContentMimeTypeTransformation.class); componentTypes.add(MimeType.class); componentTypes.add(RepositoryArtifactType.class); + componentTypes.add(CycleEventListener.class); // scan for components scanForComponents(); @@ -334,7 +335,7 @@ public abstract class CycleComponentFactory { componentDescriptor.types.add(componentType); } } - logger.log(Level.INFO, "Registered component "+componentDescriptor); + logger.log(Level.INFO, "Registered component " + componentDescriptor); return name; } @@ -363,7 +364,7 @@ public abstract class CycleComponentFactory { } public static T getCycleComponentInstance(Class< ? > clazz, Class castTo) { - Object instance = getInstance().getComponentInstance(clazz); + Object instance = getInstance().getComponentInstance(clazz.getCanonicalName()); return castSafely(instance, castTo); } @@ -379,8 +380,8 @@ public abstract class CycleComponentFactory { return castSafely(instance, castTo); } - public static Object getCycleComponentInstance(Class< ? > clazz) { - return getInstance().getComponentInstance(clazz); + public static T getCycleComponentInstance(Class clazz) { + return getCycleComponentInstance(clazz, clazz); } public static Object getCycleComponentInstance(String name) { diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/RepositoryConnector.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/RepositoryConnector.java index 495a20540e4ac439a78211e966d43ed78a127b2c..6aec8b772210955e923c385dc325113487c7fce9 100644 --- a/modules/activiti-cycle/src/main/java/org/activiti/cycle/RepositoryConnector.java +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/RepositoryConnector.java @@ -19,7 +19,7 @@ import java.util.Map; * @author bernd.ruecker@camunda.com */ public interface RepositoryConnector { - + /** * log in given user and return true, if login was successful and false, if * the user couldn't be logged in @@ -228,6 +228,8 @@ public interface RepositoryConnector { */ public void setName(String name); + public String concatenateNodeId(String prefix, String suffix); + // public String getGlobalId(RepositoryNode node); } diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/action/CreateUrlAction.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/action/CreateUrlAction.java index 2c6aee8561a869ba140cd1182da4b6802d1716f1..740ff6b319308230ef6e3a098edbb6bac6ccf6bd 100644 --- a/modules/activiti-cycle/src/main/java/org/activiti/cycle/action/CreateUrlAction.java +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/action/CreateUrlAction.java @@ -15,5 +15,7 @@ import org.activiti.cycle.RepositoryConnector; public interface CreateUrlAction extends Action { public URL getUrl(RepositoryConnector connector, RepositoryArtifact repositoryArtifact); - + + public String getWarning(RepositoryConnector connector, RepositoryArtifact repositoryArtifact); + } diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/action/RepositoryArtifactOpenLinkAction.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/action/RepositoryArtifactOpenLinkAction.java index 4496634afe2d42bbc83da5f7509a4b217aeff8c7..c93238de1fa7edbad3797bcdae517ab4d88bafc9 100644 --- a/modules/activiti-cycle/src/main/java/org/activiti/cycle/action/RepositoryArtifactOpenLinkAction.java +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/action/RepositoryArtifactOpenLinkAction.java @@ -12,6 +12,7 @@ import org.activiti.cycle.RepositoryArtifact; */ public class RepositoryArtifactOpenLinkAction { + private String warning; private String id; private URL url; @@ -28,4 +29,12 @@ public class RepositoryArtifactOpenLinkAction { return url; } + public String getWarning() { + return warning; + } + + public void setWarning(String warning) { + this.warning = warning; + } + } diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/annotations/CycleComponent.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/annotations/CycleComponent.java index 283b13a6c56d5a0acdf288957176c88a0f8d6aaa..8bd5d9f0ff90e7166cf32c8c6b77f4362c5dea1e 100644 --- a/modules/activiti-cycle/src/main/java/org/activiti/cycle/annotations/CycleComponent.java +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/annotations/CycleComponent.java @@ -17,6 +17,7 @@ import org.activiti.cycle.context.CycleContext; import org.activiti.cycle.context.CycleContextType; import org.activiti.cycle.context.CycleRequestContext; import org.activiti.cycle.context.CycleSessionContext; +import org.activiti.cycle.event.CycleEventListener; import org.activiti.cycle.transform.ContentArtifactTypeTransformation; import org.activiti.cycle.transform.ContentMimeTypeTransformation; @@ -45,6 +46,7 @@ import org.activiti.cycle.transform.ContentMimeTypeTransformation; *
  • {@link ContentMimeTypeTransformation}
  • *
  • {@link MimeType}
  • *
  • {@link RepositoryArtifactType}
  • + *
  • {@link CycleEventListener}
  • * *

    *

    diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/event/CycleCompensatingEventListener.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/event/CycleCompensatingEventListener.java new file mode 100644 index 0000000000000000000000000000000000000000..102549dc8b72b36fa439f81465edd6d1376bf4c7 --- /dev/null +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/event/CycleCompensatingEventListener.java @@ -0,0 +1,11 @@ +package org.activiti.cycle.event; + +/** + * + * @author daniel.meyer@camunda.com + */ +public interface CycleCompensatingEventListener extends CycleEventListener { + + public void compensateEvent(T event); + +} diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/event/CycleEventListener.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/event/CycleEventListener.java new file mode 100644 index 0000000000000000000000000000000000000000..158c07128956d85294ba140e2130de834bd48256 --- /dev/null +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/event/CycleEventListener.java @@ -0,0 +1,15 @@ +package org.activiti.cycle.event; + +import org.activiti.cycle.annotations.CycleComponent; + +/** + * An interface for EventListeners. Can be implemented by a + * {@link CycleComponent} to be notified of Cycle Events. + * + * @author daniel.meyer@camunda.com + */ +public interface CycleEventListener { + + void onEvent(T event); + +} diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/action/CreateUrlActionImpl.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/action/CreateUrlActionImpl.java index e9a620073f9a423b7593c4aacb4d2aef9a605e99..3ae1ee36014e1a49af4446d70186120e2e810009 100644 --- a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/action/CreateUrlActionImpl.java +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/action/CreateUrlActionImpl.java @@ -2,6 +2,8 @@ package org.activiti.cycle.impl.action; import java.net.URL; +import org.activiti.cycle.RepositoryArtifact; +import org.activiti.cycle.RepositoryConnector; import org.activiti.cycle.action.CreateUrlAction; import org.activiti.cycle.impl.AbstractArtifactActionImpl; @@ -21,5 +23,9 @@ public abstract class CreateUrlActionImpl extends AbstractArtifactActionImpl imp public CreateUrlActionImpl(String actionId) { super(actionId); } + + public String getWarning(RepositoryConnector connector, RepositoryArtifact repositoryArtifact) { + return null; +} } diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/action/fom/FormHandler.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/action/fom/FormHandler.java index fecc6383a9f3c74c3b50f4d840a47632d0308483..60df79a0c20beb19ec4f1d148a59fc6d96e4ca25 100644 --- a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/action/fom/FormHandler.java +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/action/fom/FormHandler.java @@ -64,6 +64,8 @@ public class FormHandler { getExpressions(inputElements, expressionMap); NodeList textAreaElements = document.getElementsByTagName("textarea"); getExpressions(textAreaElements, expressionMap); + textAreaElements = document.getElementsByTagName("output"); + getExpressions(textAreaElements, expressionMap); return expressionMap; } @@ -86,8 +88,15 @@ public class FormHandler { } else if ("checkbox".equals(currentElement.getAttribute("type"))) { nameAttribute = currentElement.getAttribute("name"); valueAttribute = currentElement.getAttribute("property"); + } else if ("hidden".equals(currentElement.getAttribute("type"))) { + nameAttribute = currentElement.getAttribute("name"); + valueAttribute = currentElement.getAttribute("value"); } } + if ("output".equals(currentElement.getTagName())) { + nameAttribute = currentElement.getAttribute("value"); + valueAttribute = currentElement.getAttribute("value"); + } if ("checkbox".equals(currentElement.getTagName())) { nameAttribute = currentElement.getAttribute("name"); valueAttribute = currentElement.getAttribute("property"); diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/components/CycleEmailDispatcher.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/components/CycleEmailDispatcher.java new file mode 100644 index 0000000000000000000000000000000000000000..1f9cf053813c81d9f98c7cf9281af89faeee8195 --- /dev/null +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/components/CycleEmailDispatcher.java @@ -0,0 +1,114 @@ +package org.activiti.cycle.impl.components; + +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.activiti.cycle.annotations.CycleComponent; +import org.activiti.cycle.context.CycleContextType; +import org.activiti.engine.ProcessEngines; +import org.activiti.engine.impl.ProcessEngineImpl; +import org.activiti.engine.impl.cfg.ProcessEngineConfigurationImpl; +import org.apache.commons.mail.Email; +import org.apache.commons.mail.HtmlEmail; + +/** + * {@link CycleComponent} for asynchronous email-dispatching (extremely basic + * implementation) + * + * @author daniel.meyer@camunda.com + */ +@CycleComponent(context = CycleContextType.APPLICATION) +public class CycleEmailDispatcher { + + private static int QUEUE_SIZE = 100; + + Logger logger = Logger.getLogger(CycleEmailDispatcher.class.getName()); + + private final BlockingQueue emailQueue = new ArrayBlockingQueue(QUEUE_SIZE); + + private class EmailDto { + private String from, address, subject, message; + public String toString() { + return "Email from '"+from+"' to '" + address + "' with subject '" + subject + "'"; + } + } + + public void sendEmail(String from, String address, String subject, String message) { + EmailDto dto = new EmailDto(); + dto.address = address; + dto.from = from; + dto.subject = subject; + dto.message = message; + sendEmail(dto); + } + + private void sendEmail(EmailDto email) { + // if the queue is full, the email is simply not added. + emailQueue.offer(email); + startDispatchment(); + } + + public synchronized void startDispatchment() { + if (!emailDispatcherThread.isAlive()) { + try { + emailDispatcherThread.start(); + } catch (Exception e) { + emailDispatcherThread = new EmailDispatcherThread(); + emailDispatcherThread.start(); + } + } + } + + public synchronized void stopDispatchment() { + if (!emailDispatcherThread.isInterrupted()) + emailDispatcherThread.interrupt(); + } + + private Thread emailDispatcherThread = new EmailDispatcherThread(); + + private class EmailDispatcherThread extends Thread { + + public void run() { + while (!isInterrupted()) { + EmailDto mailDto = null; + try { + mailDto = emailQueue.take(); + Email email = new HtmlEmail(); + email.setFrom(mailDto.from); + email.setMsg(mailDto.message); + email.setSubject(mailDto.subject); + email.addTo(mailDto.address); + setMailServerProperties(email); + email.send(); + } catch (InterruptedException e) { + // just terminate + return; + } catch (Exception e) { + logger.log(Level.SEVERE, "Could not send " + mailDto, e); + // TODO: retry? + } + } + } + } + + /* copied from MailActivityBehaviour in engine */ + protected void setMailServerProperties(Email email) { + // for the moment, simply reuse activiti-engine mailconfiguration + ProcessEngineConfigurationImpl processEngineConfiguration = ((ProcessEngineImpl) ProcessEngines.getDefaultProcessEngine()).getProcessEngineConfiguration(); + + String host = processEngineConfiguration.getMailServerHost(); + email.setHostName(host); + + int port = processEngineConfiguration.getMailServerPort(); + email.setSmtpPort(port); + + String user = processEngineConfiguration.getMailServerUsername(); + String password = processEngineConfiguration.getMailServerPassword(); + if (user != null && password != null) { + email.setAuthentication(user, password); + } + }/* end copied from MailActivityBehaviour in engine */ + +} diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/components/RuntimeConnectorList.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/components/RuntimeConnectorList.java index 5b00ad660a7f486215d9c0f859b1f930acc2260a..65a6c9a7e52481fd288cd5bf5b03fecf0d1cf764 100644 --- a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/components/RuntimeConnectorList.java +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/components/RuntimeConnectorList.java @@ -1,17 +1,22 @@ package org.activiti.cycle.impl.components; import java.io.Serializable; +import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; +import org.activiti.cycle.CycleComponentFactory; import org.activiti.cycle.RepositoryConnector; import org.activiti.cycle.annotations.CycleComponent; import org.activiti.cycle.context.CycleApplicationContext; import org.activiti.cycle.context.CycleContextType; import org.activiti.cycle.impl.connector.view.TagConnector; +import org.activiti.cycle.impl.processsolution.connector.ProcessSolutionConnector; +import org.activiti.cycle.processsolution.ProcessSolution; +import org.activiti.cycle.service.CycleServiceFactory; @CycleComponent(context = CycleContextType.SESSION) public class RuntimeConnectorList implements Serializable { @@ -32,12 +37,24 @@ public class RuntimeConnectorList implements Serializable { public synchronized RepositoryConnector getConnectorById(String id) { init(); + + if (id.startsWith("ps-")) { + return new ProcessSolutionConnector(id.substring(3)); + } + return connectors.get(id); + } public synchronized List getConnectors() { init(); - return connectorList; + List resultList = new ArrayList(connectorList); + // add virtual connectors for process solutions: + for (ProcessSolution processSolution : CycleServiceFactory.getProcessSolutionService().getProcessSolutions()) { + ProcessSolutionConnector psConnector = new ProcessSolutionConnector(processSolution.getId()); + resultList.add(psConnector); + } + return resultList; } protected synchronized void init() { @@ -56,6 +73,7 @@ public class RuntimeConnectorList implements Serializable { // sort connector-list Collections.sort(connectorList, new Comparator() { + public int compare(RepositoryConnector o1, RepositoryConnector o2) { String name1 = o1.getName(); String name2 = o2.getName(); @@ -68,7 +86,6 @@ public class RuntimeConnectorList implements Serializable { RepositoryConnector tagConnector = new TagConnector(); connectors.put(tagConnector.getId(), tagConnector); connectorList.add(0, tagConnector); - } public synchronized void discardConnectors() { @@ -76,4 +93,12 @@ public class RuntimeConnectorList implements Serializable { connectorList = null; } + public void registerConnector(RepositoryConnector connector) { + connectors.put(connector.getId(), connector); + connectorList.add(connector); + } + + public static RepositoryConnector getMyConnectorById(String id) { + return CycleComponentFactory.getCycleComponentInstance(RuntimeConnectorList.class, RuntimeConnectorList.class).getConnectorById(id); + } } diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/AbstractFileSystemBasedRepositoryConnector.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/AbstractFileSystemBasedRepositoryConnector.java index b8cb41b19750df95e3bd3ac0fa6e46e7d4450dc9..b5813c13e9d5a36851f9b69d01133510b79ac53b 100644 --- a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/AbstractFileSystemBasedRepositoryConnector.java +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/AbstractFileSystemBasedRepositoryConnector.java @@ -85,4 +85,12 @@ public abstract class AbstractFileSystemBasedRepositoryConnector extends Abstrac RepositoryArtifact artifact = getRepositoryArtifact(artifactId); return BasicRepositoryArtifactType.getDefaultContentRepresentation((BasicRepositoryArtifactType) artifact.getArtifactType()); } + + public String concatenateNodeId(String prefix, String suffix) { + String concatChar = "/"; + if(prefix.endsWith(concatChar) || suffix.startsWith("/")) { + concatChar = ""; + } + return prefix + concatChar + suffix; + } } diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/AbstractRestClientConnector.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/AbstractRestClientConnector.java index 6d2a6ba9f5798316168c1e31b7cceaaf46187dc7..4252156448cdd0ad6f9cc1c8c1697476b82a4ac5 100644 --- a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/AbstractRestClientConnector.java +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/AbstractRestClientConnector.java @@ -27,165 +27,165 @@ import org.restlet.representation.Representation; */ public abstract class AbstractRestClientConnector extends AbstractRepositoryConnector implements PasswordEnabledRepositoryConnector { -// protected Map properties; -// protected transient Client restletClient; -// protected Context context; -// -// public AbstractRestClientConnector() { -// } -// -// public Client initClient() { -// // TODO: Check timeout on client and re-create it -// -// if (restletClient == null) { -// // TODO: check for additional options -// // Create and initialize HTTP client for HTTP REST API calls -// context.getParameters().set("maxConnectionsPerHost", "20"); -// restletClient = new Client(context, Protocol.HTTP); -// } -// return restletClient; -// } -// -// public Response sendRequest(Request request) throws IOException { -// injectCustomSecurityMechanism(request); -// -// if (log.isLoggable(Level.FINE)) { -// RestClientLogHelper.logHttpRequest(log, Level.FINE, request); -// } -// -// Client client = initClient(); -// Response response = client.handle(request); -// -// if (log.isLoggable(Level.FINE)) { -// RestClientLogHelper.logHttpResponse(log, Level.FINE, response); -// } -// -// if (response.getStatus().isSuccess()) { -// return response; -// } else if (response.getStatus().equals(Status.CLIENT_ERROR_UNAUTHORIZED)) { -// // retry request with additional authentication -// Request retryRequest = createChallengeResponse(response); -// return sendRequest(retryRequest); -// } -// -// throw new RepositoryException("Encountered error while retrieving http response (HttpStatus: " + response.getStatus() + ", Body: " -// + response.getEntity().getText() + ")"); -// } -// -// /** -// * Overwrite this method to add required security mechanisms to requests. -// * For example: add a token to request headers -// * @param request -// * @return modified request enhanced with custom security mechanism -// */ -// protected abstract Request injectCustomSecurityMechanism(Request request); -// -// private Request createChallengeResponse(Response resourceNeedsAuth) { -// ChallengeResponse cResponse = null; -// -// for (ChallengeRequest challengeRequest : resourceNeedsAuth.getChallengeRequests()) { -// ChallengeScheme cs = challengeRequest.getScheme(); -// -// if (ChallengeScheme.HTTP_BASIC.equals(cs)) { -// if (log.isLoggable(Level.INFO)) { -// log.info("Received 401 Error -> retrying request with HTTP_BASIC AUTH now!"); -// } -// cResponse = new ChallengeResponse(ChallengeScheme.HTTP_BASIC, getUsername(), getPassword()); -// break; -// } -// } -// -// if (cResponse != null) { -// Request authenticatedRequest = resourceNeedsAuth.getRequest(); -// authenticatedRequest.setChallengeResponse(cResponse); -// return authenticatedRequest; -// } -// -// throw new IllegalStateException(new AuthenticationException("Unable to determine required authentication scheme!")); -// } -// -// private Request createRequest(Reference reference, Representation representation) { -// if (reference != null) { -// Request request = new Request(); -// request.setResourceRef(reference); -// if (representation != null) { -// request.setEntity(representation); -// } -// -// return request; -// } -// -// throw new RepositoryException("Reference object is null!"); -// } -// -// private Request createJsonRequest(Reference reference, Representation representation) { -// Request jsonRequest = createRequest(reference, representation); -// jsonRequest.getClientInfo().getAcceptedMediaTypes().add(new Preference(MediaType.APPLICATION_JSON)); -// -// return jsonRequest; -// } -// -// private Request createXmlRequest(Reference reference, Representation representation) { -// Request xmlRequest = createRequest(reference, representation); -// xmlRequest.getClientInfo().getAcceptedMediaTypes().add(new Preference(MediaType.APPLICATION_XML)); -// -// return xmlRequest; -// } -// -// public Response getJson(Reference reference, Representation representation) throws IOException { -// Request getRequest = createJsonRequest(reference, representation); -// getRequest.setMethod(Method.GET); -// -// return sendRequest(getRequest); -// } -// -// public Response postJson(Reference reference, Representation representation) throws IOException { -// Request postRequest = createJsonRequest(reference, representation); -// postRequest.setMethod(Method.POST); -// -// return sendRequest(postRequest); -// } -// -// public Response putJson(Reference reference, Representation representation) throws IOException { -// Request putRequest = createJsonRequest(reference, representation); -// putRequest.setMethod(Method.PUT); -// -// return sendRequest(putRequest); -// } -// -// public Response deleteJson(Reference reference, Representation representation) throws IOException { -// Request deleteRequest = createJsonRequest(reference, representation); -// deleteRequest.setMethod(Method.DELETE); -// -// return sendRequest(deleteRequest); -// } -// -// public Response getXml(Reference reference, Representation representation) throws IOException { -// Request getRequest = createXmlRequest(reference, representation); -// getRequest.setMethod(Method.GET); -// -// return sendRequest(getRequest); -// } -// -// public Response postXml(Reference reference, Representation representation) throws IOException { -// Request postRequest = createXmlRequest(reference, representation); -// postRequest.setMethod(Method.POST); -// -// return sendRequest(postRequest); -// } -// -// public Response putXml(Reference reference, Representation representation) throws IOException { -// Request putRequest = createXmlRequest(reference, representation); -// putRequest.setMethod(Method.PUT); -// -// return sendRequest(putRequest); -// } -// -// public Response deleteXml(Reference reference, Representation representation) throws IOException { -// Request deleteRequest = createXmlRequest(reference, representation); -// deleteRequest.setMethod(Method.DELETE); -// -// return sendRequest(deleteRequest); -// } + protected Map properties; + protected transient Client restletClient; + protected Context context; + + public AbstractRestClientConnector() { + } + + public Client initClient() { + // TODO: Check timeout on client and re-create it + + if (restletClient == null) { + // TODO: check for additional options + // Create and initialize HTTP client for HTTP REST API calls + context.getParameters().set("maxConnectionsPerHost", "20"); + restletClient = new Client(context, Protocol.HTTP); + } + return restletClient; + } + + public Response sendRequest(Request request) throws IOException { + injectCustomSecurityMechanism(request); + + if (log.isLoggable(Level.FINE)) { + RestClientLogHelper.logHttpRequest(log, Level.FINE, request); + } + + Client client = initClient(); + Response response = client.handle(request); + + if (log.isLoggable(Level.FINE)) { + RestClientLogHelper.logHttpResponse(log, Level.FINE, response); + } + + if (response.getStatus().isSuccess()) { + return response; + } else if (response.getStatus().equals(Status.CLIENT_ERROR_UNAUTHORIZED)) { + // retry request with additional authentication + Request retryRequest = createChallengeResponse(response); + return sendRequest(retryRequest); + } + + throw new RepositoryException("Encountered error while retrieving http response (HttpStatus: " + response.getStatus() + ", Body: " + + response.getEntity().getText() + ")"); + } + + /** + * Overwrite this method to add required security mechanisms to requests. + * For example: add a token to request headers + * @param request + * @return modified request enhanced with custom security mechanism + */ + protected abstract Request injectCustomSecurityMechanism(Request request); + + private Request createChallengeResponse(Response resourceNeedsAuth) { + ChallengeResponse cResponse = null; + + for (ChallengeRequest challengeRequest : resourceNeedsAuth.getChallengeRequests()) { + ChallengeScheme cs = challengeRequest.getScheme(); + + if (ChallengeScheme.HTTP_BASIC.equals(cs)) { + if (log.isLoggable(Level.INFO)) { + log.info("Received 401 Error -> retrying request with HTTP_BASIC AUTH now!"); + } + cResponse = new ChallengeResponse(ChallengeScheme.HTTP_BASIC, getUsername(), getPassword()); + break; + } + } + + if (cResponse != null) { + Request authenticatedRequest = resourceNeedsAuth.getRequest(); + authenticatedRequest.setChallengeResponse(cResponse); + return authenticatedRequest; + } + + throw new IllegalStateException(new AuthenticationException("Unable to determine required authentication scheme!")); + } + + private Request createRequest(Reference reference, Representation representation) { + if (reference != null) { + Request request = new Request(); + request.setResourceRef(reference); + if (representation != null) { + request.setEntity(representation); + } + + return request; + } + + throw new RepositoryException("Reference object is null!"); + } + + private Request createJsonRequest(Reference reference, Representation representation) { + Request jsonRequest = createRequest(reference, representation); + jsonRequest.getClientInfo().getAcceptedMediaTypes().add(new Preference(MediaType.APPLICATION_JSON)); + + return jsonRequest; + } + + private Request createXmlRequest(Reference reference, Representation representation) { + Request xmlRequest = createRequest(reference, representation); + xmlRequest.getClientInfo().getAcceptedMediaTypes().add(new Preference(MediaType.APPLICATION_XML)); + + return xmlRequest; + } + + public Response getJson(Reference reference, Representation representation) throws IOException { + Request getRequest = createJsonRequest(reference, representation); + getRequest.setMethod(Method.GET); + + return sendRequest(getRequest); + } + + public Response postJson(Reference reference, Representation representation) throws IOException { + Request postRequest = createJsonRequest(reference, representation); + postRequest.setMethod(Method.POST); + + return sendRequest(postRequest); + } + + public Response putJson(Reference reference, Representation representation) throws IOException { + Request putRequest = createJsonRequest(reference, representation); + putRequest.setMethod(Method.PUT); + + return sendRequest(putRequest); + } + + public Response deleteJson(Reference reference, Representation representation) throws IOException { + Request deleteRequest = createJsonRequest(reference, representation); + deleteRequest.setMethod(Method.DELETE); + + return sendRequest(deleteRequest); + } + + public Response getXml(Reference reference, Representation representation) throws IOException { + Request getRequest = createXmlRequest(reference, representation); + getRequest.setMethod(Method.GET); + + return sendRequest(getRequest); + } + + public Response postXml(Reference reference, Representation representation) throws IOException { + Request postRequest = createXmlRequest(reference, representation); + postRequest.setMethod(Method.POST); + + return sendRequest(postRequest); + } + + public Response putXml(Reference reference, Representation representation) throws IOException { + Request putRequest = createXmlRequest(reference, representation); + putRequest.setMethod(Method.PUT); + + return sendRequest(putRequest); + } + + public Response deleteXml(Reference reference, Representation representation) throws IOException { + Request deleteRequest = createXmlRequest(reference, representation); + deleteRequest.setMethod(Method.DELETE); + + return sendRequest(deleteRequest); + } } diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/ConnectorLoginInterceptor.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/ConnectorLoginInterceptor.java index 644ddb0bc7a40f7d31175722ad256e5e1eccc2bd..3957cdf75cf09df5ef7489a79de5f140527f3786 100644 --- a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/ConnectorLoginInterceptor.java +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/ConnectorLoginInterceptor.java @@ -3,6 +3,8 @@ package org.activiti.cycle.impl.connector; import java.lang.reflect.Method; import java.util.SortedSet; import java.util.TreeSet; +import java.util.logging.Level; +import java.util.logging.Logger; import org.activiti.cycle.RepositoryAuthenticationException; import org.activiti.cycle.RepositoryConnector; @@ -15,6 +17,8 @@ import org.activiti.cycle.impl.Interceptor; */ public class ConnectorLoginInterceptor implements Interceptor { + private Logger log = Logger.getLogger(ConnectorLoginInterceptor.class.getName()); + // set of method-names we do not want to intercept calls to (check whether the // connector is loggedin) private static final SortedSet ignoredMethods = new TreeSet(); @@ -42,10 +46,26 @@ public class ConnectorLoginInterceptor implements Interceptor { } // for all other methods, check whether the connector is logged in. if (!connector.isLoggedIn()) { - // the connector is not logged in, block invocation - throw new RepositoryAuthenticationException("Connector '" + connector.getName() + "' is not logged in ", connector.getId()); + // the connector is not logged in, try to login: + boolean loginSuccessful = false; + Exception exception = null; + try { + PasswordEnabledRepositoryConnector pwEnabledRepositoryConnector = (PasswordEnabledRepositoryConnector) connector; + String username = pwEnabledRepositoryConnector.getUsername(); + String password = pwEnabledRepositoryConnector.getPassword(); + loginSuccessful = pwEnabledRepositoryConnector.login(username, password); + } catch (Exception e) { + log.log(Level.WARNING, "Could not login connector '" + connector.getName() + "' with provided credentials.", e); + // we cannot login with the provided credentials + exception = e; + } + if (!loginSuccessful) { + if (exception != null) { + throw new RepositoryAuthenticationException("Connector '" + connector.getName() + "' is not logged in ", connector.getId(), exception); + } + throw new RepositoryAuthenticationException("Connector '" + connector.getName() + "' is not logged in ", connector.getId()); + } } - } public void afterInvoke(Method m, Object object, Object invocationResult, Object... args) { diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/ci/hudson/action/CreateHudsonJob.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/ci/hudson/action/CreateHudsonJob.java new file mode 100644 index 0000000000000000000000000000000000000000..eb6254544e2338d544899f6934db83670c548cb8 --- /dev/null +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/ci/hudson/action/CreateHudsonJob.java @@ -0,0 +1,139 @@ +package org.activiti.cycle.impl.connector.ci.hudson.action; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.List; + +import org.activiti.cycle.RepositoryException; +import org.activiti.engine.impl.util.IoUtil; +import org.restlet.data.MediaType; +import org.restlet.data.Reference; +import org.restlet.engine.Engine; +import org.restlet.representation.Representation; +import org.restlet.representation.StringRepresentation; +import org.restlet.resource.ClientResource; + +/** + * Allows to create a basic Maven2/3 continuous integration job in + * Hudson/Jenkins. + * + * @author christian.lipphardt@camunda.com + */ +public class CreateHudsonJob { + + public static final String CONFIG_XML_TEMPLATE = "template.config.xml"; + + public static final String PARAM_HUDSON_CREATE_JOB = "createItem"; + public static final String PATTERN_JOB_DESCRIPTION = "@JOB_DESCRIPTION@"; + public static final String PATTERN_SVN_LOCATION = "@SVN_LOCATION@"; + public static final String PATTERN_EMAIL_ADDRESSES = "@EMAIL_ADDRESSES@"; + + public static final String DEFAULT_JOB_NAME = "CreatedByActivitiCycle"; + + public boolean createCIJobForProject(String hudsonUrl, String projectSVNUrl, String jobName, String jobDescription, List emailAddresses) { + // TODO: check supplied values or not ;) + if (hudsonUrl == null || hudsonUrl.length() == 0 || projectSVNUrl == null || projectSVNUrl.length() == 0) { + throw new RepositoryException("HudsonUrl and ProjectSVNUrl must not be null or empty"); + } + + // retrieve template.config.xml and enrich it with given data + String configXml = getTemplate(); + + // convert emails to single string with whitespace between them + String emailString = transformEmailAddress(emailAddresses); + + configXml = replaceVarsInTemplate(configXml, jobDescription, projectSVNUrl, emailString); + + hudsonUrl = transformUrl(hudsonUrl); + Reference reference = new Reference(hudsonUrl + PARAM_HUDSON_CREATE_JOB); + + if (jobName == null || jobName.length() == 0) { + // create a default name + jobName = DEFAULT_JOB_NAME + "-" + projectSVNUrl; + } + reference.addQueryParameter("name", jobName); + ClientResource clientResource = new ClientResource(reference); + + StringRepresentation configFileRep = new StringRepresentation(configXml, MediaType.APPLICATION_XML); + try { + Representation postResult = clientResource.post(configFileRep); + clientResource.release(); + return Boolean.TRUE; + } catch (Exception e) { + throw new RepositoryException("Encountered an error while creating Hudson CI Job for Project '" + jobName + "'", e); + } finally { + Engine.clearThreadLocalVariables(); + } + } + + private String getTemplate() { + InputStream is = null; + try { + is = this.getClass().getResourceAsStream(CONFIG_XML_TEMPLATE); + if (is == null) { + throw new RepositoryException("Template '" + CONFIG_XML_TEMPLATE + "' doesn't exist in classpath"); + } + BufferedReader reader = new BufferedReader(new InputStreamReader(is)); + StringBuilder sb = new StringBuilder(); + String line = null; + while ((line = reader.readLine()) != null) { + sb.append(line + "\n"); + } + is.close(); + + return sb.toString(); + } catch (Exception ex) { + throw new RepositoryException("Exception while creating template '" + CONFIG_XML_TEMPLATE + "'", ex); + } finally { + IoUtil.closeSilently(is); + } + } + + private String transformEmailAddress(List emailAddresses) { + StringBuffer emailString = new StringBuffer(); + + for (String emailAddress : emailAddresses) { + emailString.append(emailAddress + " "); + } + + return emailString.toString(); + } + + private String transformUrl(String url) { + if (url != null && !url.endsWith("/")) { + url = url + "/"; + } + + return url; + } + + private String replaceVarsInTemplate(String template, String jobDescription, String projectSVNUrl, String emailAddresses) { + template = template.replaceFirst(PATTERN_JOB_DESCRIPTION, ""); + template = template.replaceFirst(PATTERN_SVN_LOCATION, projectSVNUrl); + template = template.replaceFirst(PATTERN_EMAIL_ADDRESSES, emailAddresses); + + return template; + } + + public static void main(String[] args) { + String hudsonUrl = "http://localhost:8080/hudson/"; + String projectSVNUrl = "projectLocationinSVN"; + String jobName = "JavaGenerated-" + System.currentTimeMillis(); + List emailAddresses = new ArrayList(); + + emailAddresses.add("christian.lipphardt@camunda.com"); + emailAddresses.add("bernd.ruecker@camunda.com"); + + CreateHudsonJob createHudsonJob = new CreateHudsonJob(); + String jobDescription = "see test"; + Boolean successful = createHudsonJob.createCIJobForProject(hudsonUrl, projectSVNUrl, jobName, jobDescription , emailAddresses); + if (successful) { + System.out.println("Successfully created Hudson CI Job for Project '" + jobName + "'"); + } else { + System.out.println("Unable to create Hudson CI Job for Project '" + jobName + "'"); + } + } + +} diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/fs/FileSystemConnector.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/fs/FileSystemConnector.java index 89453d5985fb0a7385b386e8318bfe63420b97eb..2d52fed892b9d443c177d3971f032bcc6070a01f 100644 --- a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/fs/FileSystemConnector.java +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/fs/FileSystemConnector.java @@ -231,7 +231,7 @@ public class FileSystemConnector extends AbstractFileSystemBasedRepositoryConnec return getRepositoryArtifact(getRepositoryNodeId(parentFolderId, artifactName)); } - + public RepositoryArtifact createArtifactFromContentRepresentation(String parentFolderId, String artifactName, String artifactType, String contentRepresentationName, Content artifactContent) throws RepositoryNodeNotFoundException { return createArtifact(parentFolderId, artifactName, artifactType, artifactContent); @@ -281,17 +281,16 @@ public class FileSystemConnector extends AbstractFileSystemBasedRepositoryConnec } private String getLocalPath(String path) { - String basePath = getBasePath(); - if ("".equals(basePath)) { + if ("".equals(getBasePath())) { // if root is configured in Unix ("/" without trailing slash = "") return path; - } else if (path.startsWith(basePath)) { - path = path.replace(basePath, ""); + } else if (path.startsWith(getBasePath())) { + path = path.replace(getBasePath(), ""); // replace windows style slashes path = path.replace("\\", "/"); return path; } - throw new RepositoryException("Unable to determine local path! (path: '" + path + "', base path: '" + basePath + "')"); + throw new RepositoryException("Unable to determine local path! ('" + path + "')"); } public String[] getConfigurationKeys() { @@ -323,5 +322,6 @@ public class FileSystemConnector extends AbstractFileSystemBasedRepositoryConnec } return path; } + } diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/signavio/SignavioConnector.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/signavio/SignavioConnector.java index 2b616ece6a1b99f93ce3ec514ba8db278a1ffc72..e40e7dcadc853cc75f61e7e714687630dd5e6cc3 100644 --- a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/signavio/SignavioConnector.java +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/signavio/SignavioConnector.java @@ -12,8 +12,15 @@ */ package org.activiti.cycle.impl.connector.signavio; +import java.io.BufferedOutputStream; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.net.URLEncoder; +import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -46,6 +53,18 @@ import org.activiti.cycle.impl.connector.signavio.repositoryartifacttype.Signavi import org.activiti.cycle.impl.connector.signavio.repositoryartifacttype.SignavioDefaultArtifactType; import org.activiti.cycle.impl.connector.signavio.util.SignavioJsonHelper; import org.activiti.cycle.impl.connector.util.RestClientLogHelper; +import org.activiti.cycle.impl.util.IoUtils; +import org.apache.http.HttpVersion; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.mime.HttpMultipartMode; +import org.apache.http.entity.mime.MultipartEntity; +import org.apache.http.entity.mime.content.FileBody; +import org.apache.http.entity.mime.content.StringBody; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.params.CoreProtocolPNames; +import org.apache.http.util.EntityUtils; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -60,7 +79,6 @@ import org.restlet.data.Preference; import org.restlet.data.Protocol; import org.restlet.data.Reference; import org.restlet.data.Status; -import org.restlet.engine.Engine; import org.restlet.ext.json.JsonRepresentation; import org.restlet.representation.Representation; @@ -130,13 +148,7 @@ public class SignavioConnector extends AbstractRepositoryConnector implements Si return restletClient; } - public void sendRequest(Request request) throws IOException { - Response response = sendRequestGetResponse(request); - response.release(); - Engine.clearThreadLocalVariables(); - } - - public Response sendRequestGetResponse(Request request) throws IOException { + public Response sendRequest(Request request) throws IOException { injectSecurityToken(request); if (log.isLoggable(Level.FINE)) { @@ -145,7 +157,6 @@ public class SignavioConnector extends AbstractRepositoryConnector implements Si Client client = initClient(); Response response = client.handle(request); - request.release(); if (log.isLoggable(Level.FINE)) { RestClientLogHelper.logHttpResponse(log, Level.FINE, response); @@ -166,25 +177,12 @@ public class SignavioConnector extends AbstractRepositoryConnector implements Si return request; } - - public static void main(String[] args) throws IOException { - SignavioConnector connector = new SignavioConnector(); - System.out.println( - connector.getJsonResponse("http://localhost:8080/activiti-modeler/p/model/C:;Activiti;releases;activiti-5.2;apps;apache-tomcat-6.0.29;bin;..;..;..;workspace;activiti-modeler-examples;TwitterDemoProcess.signavio.xml/info") - ); - } - public String getJsonResponse(String url) throws IOException { + public Response getJsonResponse(String url) throws IOException { Request jsonRequest = new Request(Method.GET, new Reference(url)); jsonRequest.getClientInfo().getAcceptedMediaTypes().add(new Preference(MediaType.APPLICATION_JSON)); - Response response = sendRequestGetResponse(jsonRequest); - String responseString = response.getEntityAsText(); - response.release(); - - Engine.clearThreadLocalVariables(); - - return responseString; + return sendRequest(jsonRequest); } protected boolean registerUserWithSignavio(String firstname, String lastname, String email, String password) throws IOException { @@ -199,12 +197,13 @@ public class SignavioConnector extends AbstractRepositoryConnector implements Si Representation registrationRep = registrationForm.getWebRepresentation(); Request registrationRequest = new Request(Method.POST, getConfiguration().getRegistrationUrl(), registrationRep); - Response registrationResponse = sendRequestGetResponse(registrationRequest); - boolean success = registrationResponse.getStatus().equals(Status.SUCCESS_CREATED); - registrationResponse.release(); - Engine.clearThreadLocalVariables(); - - return success; + Response registrationResponse = sendRequest(registrationRequest); + + if (registrationResponse.getStatus().equals(Status.SUCCESS_CREATED)) { + return true; + } else { + return false; + } } public boolean login(String username, String password) { @@ -224,15 +223,13 @@ public class SignavioConnector extends AbstractRepositoryConnector implements Si Representation loginRep = loginForm.getWebRepresentation(); Request loginRequest = new Request(Method.POST, getConfiguration().getLoginUrl(), loginRep); - Response loginResponse = sendRequestGetResponse(loginRequest); - token = loginResponse.getEntity().getText(); - loginResponse.release(); + Response loginResponse = sendRequest(loginRequest); + + Representation representation = loginResponse.getEntity(); + token = representation.getText(); } catch (Exception ex) { throw new RepositoryException("Error during login to connector '" + getName() + "'", ex); } - finally { - Engine.clearThreadLocalVariables(); - } if (token.matches("[a-f0-9]{32}")) { setSecurityToken(token); log.fine("SecurityToken: " + getSecurityToken()); @@ -370,8 +367,8 @@ public class SignavioConnector extends AbstractRepositoryConnector implements Si if (getConfiguration() instanceof OryxConnectorConfiguration) { nodes = getChildrenFromOryxBackend(id); } else { - String directoryResponse = getJsonResponse(getConfiguration().getDirectoryUrl(id)); - JsonRepresentation jsonData = new JsonRepresentation(directoryResponse); + Response directoryResponse = getJsonResponse(getConfiguration().getDirectoryUrl(id)); + JsonRepresentation jsonData = new JsonRepresentation(directoryResponse.getEntity()); JSONArray relJsonArray = jsonData.getJsonArray(); if (log.isLoggable(Level.FINEST)) { @@ -433,8 +430,8 @@ public class SignavioConnector extends AbstractRepositoryConnector implements Si for (int i = modelRefs.length() - 1; (i >= 0 && i > modelRefs.length() - pageSize); i--) { String modelRef = modelRefs.getString(i); String modelId = connectorConfiguration.getModelIdFromUrl(modelRef); - String infoResponse = getJsonResponse(connectorConfiguration.getModelInfoUrl(modelId)); - JsonRepresentation jsonRepresentation = new JsonRepresentation(infoResponse); + Response infoResponse = getJsonResponse(connectorConfiguration.getModelInfoUrl(modelId)); + JsonRepresentation jsonRepresentation = new JsonRepresentation(infoResponse.getEntity()); RepositoryArtifact fileInfo = getArtifactInfoFromFile(modelId, jsonRepresentation.getJsonObject()); nodes.add(fileInfo); } @@ -458,8 +455,8 @@ public class SignavioConnector extends AbstractRepositoryConnector implements Si for (int i = modelRefs.length() - 1 - pageNumber * pageSize; (i >= 0 && i > modelRefs.length() - (pageNumber + 1) * pageSize); i--) { String modelRef = modelRefs.getString(i); String modelId = connectorConfiguration.getModelIdFromUrl(modelRef); - String infoResponse = getJsonResponse(connectorConfiguration.getModelInfoUrl(modelId)); - JsonRepresentation jsonRepresentation = new JsonRepresentation(infoResponse); + Response infoResponse = getJsonResponse(connectorConfiguration.getModelInfoUrl(modelId)); + JsonRepresentation jsonRepresentation = new JsonRepresentation(infoResponse.getEntity()); RepositoryArtifact fileInfo = getArtifactInfoFromFile(modelId, jsonRepresentation.getJsonObject()); nodes.add(fileInfo); } @@ -470,24 +467,24 @@ public class SignavioConnector extends AbstractRepositoryConnector implements Si private JSONArray getModelIdsFromOryxBackend(String stencilsetNamespace) throws IOException, JSONException { String type = URLEncoder.encode(stencilsetNamespace, "UTF-8"); - String filterResponse = getJsonResponse(getConfiguration().getRepositoryBackendUrl() + "filter?type=" + type + "&sort=rating"); - JsonRepresentation jsonRepresentation = new JsonRepresentation(filterResponse); + Response filterResponse = getJsonResponse(getConfiguration().getRepositoryBackendUrl() + "filter?type=" + type + "&sort=rating"); + JsonRepresentation jsonRepresentation = new JsonRepresentation(filterResponse.getEntity()); JSONArray modelRefs = jsonRepresentation.getJsonArray(); return modelRefs; } private JSONArray getStencilsets() throws IOException, JSONException { JSONArray stencilsets; - String stencilsetsResponse = getJsonResponse(getConfiguration().getStencilsetsUrl()); - JsonRepresentation stencilsetsJsonRepresentation = new JsonRepresentation(stencilsetsResponse); + Response stencilsetsResponse = getJsonResponse(getConfiguration().getStencilsetsUrl()); + JsonRepresentation stencilsetsJsonRepresentation = new JsonRepresentation(stencilsetsResponse.getEntity()); stencilsets = stencilsetsJsonRepresentation.getJsonArray(); return stencilsets; } public RepositoryFolder getRepositoryFolder(String id) { try { - String directoryResponse = getJsonResponse(getConfiguration().getDirectoryUrl(id)); - JsonRepresentation jsonData = new JsonRepresentation(directoryResponse); + Response directoryResponse = getJsonResponse(getConfiguration().getDirectoryUrl(id)); + JsonRepresentation jsonData = new JsonRepresentation(directoryResponse.getEntity()); JSONArray jsonArray = jsonData.getJsonArray(); // search for rel: "info" in jsonArray to get directory specified by id; @@ -521,8 +518,8 @@ public class SignavioConnector extends AbstractRepositoryConnector implements Si JSONObject jsonObject = null; try { - String modelResponse = getJsonResponse(getConfiguration().getModelUrl(id) + "/info"); - jsonData = new JsonRepresentation(modelResponse); + Response modelResponse = getJsonResponse(getConfiguration().getModelUrl(id) + "/info"); + jsonData = new JsonRepresentation(modelResponse.getEntity()); jsonObject = jsonData.getJsonObject(); return getArtifactInfoFromFile(id, jsonObject); } catch (Exception ex) { @@ -552,6 +549,19 @@ public class SignavioConnector extends AbstractRepositoryConnector implements Si // TODO: create response object which contains the ID or maybe skip the // whole artifact returning? + + // HACK: getting the created folder through iteration of the child + // nodes of the parent folder and comparing the names... + RepositoryNodeCollection nodes = getChildren(parentFolderId); + for (RepositoryNode node : nodes.asList()) { + if (!(node instanceof RepositoryFolder)) { + continue; + } + if (node.getMetadata().getName().equals(name)) { + return (RepositoryFolder) node; + } + } + return null; } catch (Exception ex) { // throw new RepositoryNodeNotFoundException(getConfiguration().getName(), @@ -624,7 +634,7 @@ public class SignavioConnector extends AbstractRepositoryConnector implements Si } public RepositoryArtifact createEmptyArtifact(String parentFolderId, String artifactName, String artifactType) throws RepositoryNodeNotFoundException { - return createArtifactFromJSON(parentFolderId, artifactName, artifactType, "{}"); + return createArtifactFromJSON(parentFolderId, artifactName, artifactType, SignavioJsonHelper.getEmptypModelTemplate()); } public RepositoryArtifact createArtifact(String parentFolderId, String artifactName, String artifactType, Content artifactContent) @@ -733,29 +743,64 @@ public class SignavioConnector extends AbstractRepositoryConnector implements Si } /** - * FIXME: unfinished but maybe it works... method accepts a xml String and - * returns the json representation + * FIXME: This implementation uses the bpmn2_0-import servlet which returns a + * temporary(?) nodeid for which we then retrieve JSon Content. + * + * NOTE: I could not make this work using Restlet. Maybe I just dont get it... + * (daniel) */ - public String transformBpmn20XmltoJson(String xmlData) { + public String transformBpmn20XmltoJson(String xml) { + File tmpfile = null; try { - Form dataForm = new Form(); - dataForm.add("data", xmlData); - Representation xmlDataRep = dataForm.getWebRepresentation(); - - Request request = new Request(Method.POST, new Reference(getConfiguration().getBpmn20XmlImportServletUrl()), xmlDataRep); - request.getClientInfo().getAcceptedMediaTypes().add(new Preference(MediaType.APPLICATION_JSON)); - Response jsonResponse = sendRequestGetResponse(request); - String xml = jsonResponse.getEntity().getText(); - jsonResponse.release(); - Engine.clearThreadLocalVariables(); - - return new JSONObject(xml).toString(); - + HttpClient client = new DefaultHttpClient(); + client.getParams().setParameter(CoreProtocolPNames.PROTOCOL_VERSION, HttpVersion.HTTP_1_1); + String postUrl = getConfiguration().getBpmn20XmlImportServletUrl(); + HttpPost post = new HttpPost(postUrl); + post.addHeader("token", getSecurityToken()); + MultipartEntity entity = new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE); + + // creating a temporary file + tmpfile = File.createTempFile(UUID.randomUUID().toString(), ".xml"); + OutputStream os = new BufferedOutputStream(new FileOutputStream(tmpfile)); + InputStream is = new ByteArrayInputStream(xml.getBytes(Charset.forName("utf-8"))); + IoUtils.copyBytes(is, os); + os.flush(); + os.close(); + entity.addPart("bpmn2_0file", new FileBody(tmpfile)); + // apparently this works: + entity.addPart("directory", new StringBody("/directory", Charset.forName("UTF-8"))); + post.setEntity(entity); + + // get the response (id of the temporary model) + String response = EntityUtils.toString(client.execute(post).getEntity(), "UTF-8"); + + // the returned response is of the form ["..."], I suggest this is the + // case because an xml-file can contain multiple process definitions. For + // the moment we just cut the json-list brackets. + String artifactId = response.substring(2); + artifactId = artifactId.substring(0, artifactId.length() - 2); + + HttpGet get = new HttpGet(getConfiguration().getEditorUrl(artifactId) + "&data"); + get.addHeader("token", getSecurityToken()); + // let's pretend we're Firefox on Windows + get.addHeader("User-Agent", "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.13) Gecko/20101203 Firefox/3.6.13"); + + // get the json Representation for the temporary model + response = EntityUtils.toString(client.execute(get).getEntity(), "UTF-8"); + + client.getConnectionManager().shutdown(); + + JSONObject jsonObj = new JSONObject(response); + return jsonObj.getString("model"); + } catch (Exception ex) { throw new RepositoryException("Error while transforming BPMN2_0_XML to BPMN2_0_JSON", ex); + } finally { + if (tmpfile != null) { + tmpfile.delete(); + } } } - public void updateContent(String artifactId, Content content) throws RepositoryNodeNotFoundException { throw new RepositoryException("Moving artifacts is not (yet) supported by the Signavio Connector"); } @@ -765,7 +810,11 @@ public class SignavioConnector extends AbstractRepositoryConnector implements Si } public boolean isLoggedIn() { - return loggedIn || !getConfigValue(CONFIG_KEY_LOGIN_REQUIRED, Boolean.class); + Boolean loginRequired = getConfigValue(CONFIG_KEY_LOGIN_REQUIRED, Boolean.class); + if (loginRequired==null) { + throw new RepositoryException("Configuration of Signavio Connector must contain the configuration attribute '"+CONFIG_KEY_LOGIN_REQUIRED+"'"); + } + return loggedIn || !loginRequired; } public Content getContent(String artifactId) throws RepositoryNodeNotFoundException { @@ -814,4 +863,8 @@ public class SignavioConnector extends AbstractRepositoryConnector implements Si return configuration; } + public String concatenateNodeId(String prefix, String suffix) { + return prefix + ";" + suffix; + } + } diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/signavio/SignavioConnectorConfiguration.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/signavio/SignavioConnectorConfiguration.java index fbe6ac69fec20c3902401cd18852dc6e924e519a..61e8c568221c1f166a14ce4b6f7bdc452346537a 100644 --- a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/signavio/SignavioConnectorConfiguration.java +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/signavio/SignavioConnectorConfiguration.java @@ -25,7 +25,7 @@ import org.activiti.cycle.RepositoryException; * * @author bernd.ruecker@camunda.com */ -public class SignavioConnectorConfiguration { +public class SignavioConnectorConfiguration { // these values differ between Oryx and Signavio protected static String REPOSITORY_BACKEND_URL_SUFFIX = "p/"; @@ -42,14 +42,15 @@ public class SignavioConnectorConfiguration { public static String STENCILSETS_URL_SUFFIX = "stencilsets/stencilsets.json"; public static String BPMN_20_EXPORT_SERVLET = "bpmn2_0serialization"; - public static String BPMN_20_IMPORT_SERVLET = "bpmn2_0deserialization"; + // public static String BPMN_20_IMPORT_SERVLET = "bpmn2_0deserialization"; + public static String BPMN_20_IMPORT_SERVLET = "bpmn2_0-import"; protected SignavioConnector connector; - + public SignavioConnectorConfiguration(SignavioConnector connector) { this.connector = connector; } - + public String getSignavioUrl() { return connector.getSignavioUrl(); } @@ -172,7 +173,7 @@ public class SignavioConnectorConfiguration { } public String getBpmn20XmlImportServletUrl() { - return getEditorBackendUrl() + BPMN_20_IMPORT_SERVLET; + return getSignavioUrl() + REPOSITORY_BACKEND_URL_SUFFIX + BPMN_20_IMPORT_SERVLET; } /** diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/signavio/SignavioConnectorInterface.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/signavio/SignavioConnectorInterface.java index b45f04fd9433f97ca95f3edabbaddfddde1fbf5a..3a40f6fa4205907a2299dfefde4fa88f3e329515 100644 --- a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/signavio/SignavioConnectorInterface.java +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/signavio/SignavioConnectorInterface.java @@ -5,6 +5,8 @@ import java.io.IOException; import org.activiti.cycle.RepositoryArtifact; import org.activiti.cycle.RepositoryConnector; import org.restlet.Client; +import org.restlet.Request; +import org.restlet.Response; /** * Public interface for the Signavio Connector. @@ -26,8 +28,10 @@ public interface SignavioConnectorInterface extends RepositoryConnector { public String getSecurityToken(); - public String getJsonResponse(String url) throws IOException; + public Response getJsonResponse(String url) throws IOException; - public SignavioConnectorConfiguration getConfiguration(); + public Response sendRequest(Request request) throws IOException; + + public abstract SignavioConnectorConfiguration getConfiguration(); } diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/signavio/action/CreateMavenProjectAction.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/signavio/action/CreateMavenProjectAction.java index 9ec2581d95b5a2ab9c7fe873d3c0eba737898f9b..98fdec592383dd3badb74a075e36f2a4f44003ed 100644 --- a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/signavio/action/CreateMavenProjectAction.java +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/signavio/action/CreateMavenProjectAction.java @@ -3,21 +3,26 @@ package org.activiti.cycle.impl.connector.signavio.action; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.logging.Level; import java.util.logging.Logger; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import org.activiti.cycle.Content; +import org.activiti.cycle.CycleComponentFactory; import org.activiti.cycle.RepositoryArtifact; import org.activiti.cycle.RepositoryArtifactLink; import org.activiti.cycle.RepositoryConnector; import org.activiti.cycle.RepositoryException; +import org.activiti.cycle.RepositoryFolder; import org.activiti.cycle.annotations.CycleComponent; -import org.activiti.cycle.annotations.ExcludesCycleComponents; import org.activiti.cycle.context.CycleContextType; -import org.activiti.cycle.impl.action.DefaultCopyArtifactAction; +import org.activiti.cycle.impl.components.RuntimeConnectorList; import org.activiti.cycle.impl.connector.signavio.provider.ActivitiCompliantBpmn20Provider; import org.activiti.cycle.impl.connector.util.TransactionalConnectorUtils; import org.activiti.cycle.impl.db.entity.RepositoryArtifactLinkEntity; @@ -47,7 +52,7 @@ public class CreateMavenProjectAction extends CreateTechnicalBpmnXmlAction { super("Create default maven project"); try { ACTIVITI_HOME_PATH = new File("../../../").getCanonicalPath(); - } catch (IOException e) { + } catch (IOException e) { ACTIVITI_HOME_PATH = new File(".").getAbsolutePath(); log.log(Level.WARNING, "Couldn't set Activiti Home path (see nested exception for the cause), using '" + ACTIVITI_HOME_PATH + "' instead.", e); } @@ -62,34 +67,41 @@ public class CreateMavenProjectAction extends CreateTechnicalBpmnXmlAction { RepositoryConnector targetConnector = (RepositoryConnector) getParameter(parameters, PARAM_TARGET_CONNECTOR, true, null, RepositoryConnector.class); boolean createLink = (Boolean) getParameter(parameters, CREATE_LINK_NAME, true, Boolean.TRUE, Boolean.class); - String bpmnXml = ActivitiCompliantBpmn20Provider.createBpmnXml(connector, artifact); + List processes = new ArrayList(); + processes.add(artifact); + RepositoryFolder targetFolder = targetConnector.createFolder(targetFolderId, targetName); + createMavenProject(targetFolder.getNodeId(), targetName, comment, targetConnector, createLink, processes); - RepositoryArtifact bpmnArtifact = null; + } + public Map createMavenProject(String targetFolderId, String targetName, String comment, + RepositoryConnector targetConnector, boolean createLink, List processes) { + Map bpmnArtifacts = null; TransactionalConnectorUtils.beginTransaction(targetConnector); try { // create maven project - bpmnArtifact = createProject(targetConnector, targetFolderId, targetName, bpmnXml); + bpmnArtifacts = createProject(targetConnector, targetFolderId, targetName, processes); TransactionalConnectorUtils.commitTransaction(targetConnector, comment); } catch (RepositoryException e) { TransactionalConnectorUtils.rollbackTransaction(targetConnector); throw e; } - if (createLink && bpmnArtifact != null) { + if (createLink && bpmnArtifacts != null) { // TODO: We cannot link to a folder at the moment! // RepositoryFolder targetFolder = // targetConnector.getRepositoryFolder(targetFolderId); - - RepositoryArtifactLink link = new RepositoryArtifactLinkEntity(); - link.setSourceArtifact(artifact); - link.setTargetArtifact(bpmnArtifact); - link.setComment(comment); - link.setLinkType(getLinkType()); - CycleRepositoryService repositoryService = CycleServiceFactory.getRepositoryService(); - repositoryService.addArtifactLink(link); + for (Entry processMappedToXml : bpmnArtifacts.entrySet()) { + RepositoryArtifactLink link = new RepositoryArtifactLinkEntity(); + link.setSourceArtifact(processMappedToXml.getKey()); + link.setTargetArtifact(processMappedToXml.getValue()); + link.setComment(comment); + link.setLinkType(getLinkType()); + CycleRepositoryService repositoryService = CycleServiceFactory.getRepositoryService(); + repositoryService.addArtifactLink(link); + } } + return bpmnArtifacts; } - public String getProcessName(RepositoryArtifact artifact) { return artifact.getMetadata().getName(); } @@ -98,16 +110,16 @@ public class CreateMavenProjectAction extends CreateTechnicalBpmnXmlAction { * create a project from the Maven template and return the RepositoryArtifact * representing the bpmn process model */ - public RepositoryArtifact createProject(RepositoryConnector connector, String rootFolderId, String projectName, String processDefinitionXml) { - RepositoryArtifact result = null; + public Map createProject(RepositoryConnector connector, String rootFolderId, String projectName, + List processes) { + Map resultList = null; try { ZipInputStream projectTemplateInputStream = new ZipInputStream(getProjectTemplate()); ZipEntry zipEntry = null; - String rootSubstitution = null; - while ((zipEntry = projectTemplateInputStream.getNextEntry()) != null) { String zipName = zipEntry.getName(); + zipName = zipName.replaceAll("activiti-cycle-maven-template/", ""); if (zipName.endsWith("/")) { zipName = zipName.substring(0, zipName.length() - 1); } @@ -117,39 +129,21 @@ public class CreateMavenProjectAction extends CreateTechnicalBpmnXmlAction { path = zipName.substring(0, zipName.lastIndexOf("/")); name = zipName.substring(zipName.lastIndexOf("/") + 1); } - if ("".equals(path)) { - // root folder is named after the project, not like the - // template - // folder name - rootSubstitution = name; - name = projectName; - } else { - // rename the root folder in all other paths as well - path = path.replace(rootSubstitution, projectName); - } String absolutePath = rootFolderId + "/" + path; - boolean isBpmnModel = false; if (zipEntry.isDirectory()) { - connector.createFolder(absolutePath, name); + if (!zipEntry.getName().equals("activiti-cycle-maven-template/")) { + connector.createFolder(absolutePath, name); + } } else { - Content content = new Content(); - if ("template.bpmn20.xml".equals(name)) { - // This file shall be replaced with the process - // definition - content.setValue(processDefinitionXml); - name = projectName + ".bpmn20.xml"; - isBpmnModel = true; - log.log(Level.INFO, "Create processdefinition from Signavio process model " + projectName); + resultList = generateProcesses(processes, absolutePath, connector); } else { + Content content = new Content(); byte[] bytes = IoUtil.readInputStream(projectTemplateInputStream, "ZIP entry '" + zipName + "'"); String txtContent = new String(bytes).replaceAll(REPLACE_STRING, projectName).replaceAll("@@ACTIVITI.HOME@@", ACTIVITI_HOME_PATH); content.setValue(txtContent); - } - log.log(Level.INFO, "Create new artifact from zip entry '" + zipEntry.getName() + "' in folder '" + absolutePath + "' with name '" + name + "'"); - RepositoryArtifact artifact = connector.createArtifact(absolutePath, name, null, content); - if (isBpmnModel) { - result = artifact; + log.log(Level.INFO, "Create new artifact from zip entry '" + zipEntry.getName() + "' in folder '" + absolutePath + "' with name '" + name + "'"); + connector.createArtifact(absolutePath, name, null, content); } } projectTemplateInputStream.closeEntry(); @@ -158,9 +152,26 @@ public class CreateMavenProjectAction extends CreateTechnicalBpmnXmlAction { } catch (IOException ex) { throw new RepositoryException("Couldn't create maven project due to IO errors", ex); } - return result; + return resultList; } + /** + * generates bpmn20.xml artifacts for the provided process models + */ + protected Map generateProcesses(List processes, String path, RepositoryConnector targetConnector) { + Map resultList = new HashMap(); + RuntimeConnectorList runtimeConnectorList = CycleComponentFactory.getCycleComponentInstance(RuntimeConnectorList.class, RuntimeConnectorList.class); + for (RepositoryArtifact process : processes) { + log.log(Level.INFO, "Create processdefinition from Signavio process model " + process.getMetadata().getName()); + String name = getProcessName(process) + ".bpmn20.xml"; + Content content = new Content(); + RepositoryConnector connector = runtimeConnectorList.getConnectorById(process.getConnectorId()); + content.setValue(ActivitiCompliantBpmn20Provider.createBpmnXml(connector, process)); + resultList.put(process, targetConnector.createArtifact(path, name, null, content)); + } + return resultList; + + } protected InputStream getProjectTemplate() { return this.getClass().getResourceAsStream("activiti-cycle-maven-template.zip"); } diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/signavio/action/OpenModelerAction.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/signavio/action/OpenModelerAction.java index d0010373ac8d3bf707e394f2ef33f09dc095737a..5516786a5d936299d1827e2b9d58395462ac504c 100644 --- a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/signavio/action/OpenModelerAction.java +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/signavio/action/OpenModelerAction.java @@ -28,6 +28,9 @@ import org.activiti.cycle.impl.action.CreateUrlActionImpl; import org.activiti.cycle.impl.connector.signavio.SignavioConnectorConfiguration; import org.activiti.cycle.impl.connector.signavio.SignavioConnectorInterface; import org.activiti.cycle.impl.connector.signavio.repositoryartifacttype.SignavioBpmn20ArtifactType; +import org.activiti.cycle.impl.processsolution.ProcessSolutionAction; +import org.activiti.cycle.impl.processsolution.connector.ProcessSolutionArtifact; +import org.activiti.cycle.processsolution.ProcessSolutionState; /** * Action to open the signavio modeler @@ -35,7 +38,7 @@ import org.activiti.cycle.impl.connector.signavio.repositoryartifacttype.Signavi * @author bernd.ruecker@camunda.com */ @CycleComponent(context = CycleContextType.APPLICATION) -public class OpenModelerAction extends CreateUrlActionImpl { +public class OpenModelerAction extends CreateUrlActionImpl implements ProcessSolutionAction { private static final long serialVersionUID = 1L; @@ -58,6 +61,21 @@ public class OpenModelerAction extends CreateUrlActionImpl { } } + public String getWarning(RepositoryConnector connector, RepositoryArtifact repositoryArtifact) { + if (repositoryArtifact instanceof ProcessSolutionArtifact) { + ProcessSolutionArtifact psArtifact = (ProcessSolutionArtifact) repositoryArtifact; + if (psArtifact.getWrappedNode() != null && psArtifact.getVirtualRepositoryFolder() != null) { + if ("Processes".equals(psArtifact.getVirtualRepositoryFolder().getType())) { + if (psArtifact.getProcessSolution().getState().equals(ProcessSolutionState.IN_IMPLEMENTATION)) { + return "This project is currently in implementation. Changes to the process
    models " + + "could potentially be overwritten in the next iteration.

    Are you shure you want to continue?"; + } + } + } + } + return null; + + } public Set getArtifactTypes() { return types; } diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/signavio/action/ValidateActivitiDeployment.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/signavio/action/ValidateActivitiDeployment.java index 35c2c7486a67922dc0fef9a9a8c23c70b16a4a9a..c4e07b8367de3692a34c3fe24bd4a95fd61cd1d6 100644 --- a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/signavio/action/ValidateActivitiDeployment.java +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/signavio/action/ValidateActivitiDeployment.java @@ -4,13 +4,18 @@ import java.util.Map; import org.activiti.cycle.RepositoryArtifact; import org.activiti.cycle.RepositoryConnector; +import org.activiti.cycle.RepositoryNode; import org.activiti.cycle.impl.connector.signavio.SignavioConnector; +import org.activiti.cycle.impl.connector.signavio.SignavioConnectorInterface; import org.activiti.cycle.impl.connector.signavio.provider.ActivitiCompliantBpmn20Provider; import org.activiti.engine.ProcessEngines; import org.activiti.engine.impl.ProcessEngineImpl; +import org.activiti.engine.impl.bpmn.parser.BpmnParse; import org.activiti.engine.impl.bpmn.parser.BpmnParser; +import org.activiti.engine.impl.context.Context; import org.activiti.engine.impl.el.ExpressionManager; import org.activiti.engine.impl.repository.DeploymentEntity; +import org.activiti.engine.impl.repository.ProcessDefinitionEntity; public class ValidateActivitiDeployment extends CreateTechnicalBpmnXmlAction { @@ -28,13 +33,20 @@ public class ValidateActivitiDeployment extends CreateTechnicalBpmnXmlAction { } public void execute(RepositoryConnector connector, RepositoryArtifact artifact, Map parameters) throws Exception { + BpmnParse parse = createParseObject(connector, artifact); + // parse to validate + parse.execute(); + // That's it, now we get an exception if the file is invalid + } + + public static BpmnParse createParseObject(RepositoryConnector connector, RepositoryArtifact artifact) { + String bpmnXml = ActivitiCompliantBpmn20Provider.createBpmnXml((SignavioConnectorInterface) connector, artifact); + // TODO: Okay, this needs more serious thinking where we get the engine // from! ExpressionManager expressionManager = ((ProcessEngineImpl) ProcessEngines.getDefaultProcessEngine()).getProcessEngineConfiguration().getExpressionManager(); - - String bpmnXml = ActivitiCompliantBpmn20Provider.createBpmnXml((SignavioConnector) connector, artifact); - BpmnParser bpmnParser = new BpmnParser(expressionManager); + Context.setProcessEngineConfiguration( ((ProcessEngineImpl)ProcessEngines.getDefaultProcessEngine()).getProcessEngineConfiguration()); // Unfortunately the deployment id is requested while parsing, so we have to // set a DeploymentEntity to avoid an NPE @@ -42,8 +54,8 @@ public class ValidateActivitiDeployment extends CreateTechnicalBpmnXmlAction { deployment.setId("VALIDATION_DEPLOYMENT"); // parse to validate - bpmnParser.createParse().deployment(deployment).sourceString(bpmnXml).name(artifact.getNodeId()).execute(); - // That's it, now we get an exception if the file is invalid + BpmnParse parse = bpmnParser.createParse().deployment(deployment).sourceString(bpmnXml).name(artifact.getNodeId()); + return parse; } diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/signavio/provider/AbstractPngProvider.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/signavio/provider/AbstractPngProvider.java index 2c70929b3093397e7612da26909af7d9aa73499b..cdddf1efda829303cf9ba917ec09bb0e73009f6d 100644 --- a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/signavio/provider/AbstractPngProvider.java +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/signavio/provider/AbstractPngProvider.java @@ -14,6 +14,8 @@ package org.activiti.cycle.impl.connector.signavio.provider; import java.io.InputStream; import java.net.URL; +import java.util.logging.Level; +import java.util.logging.Logger; import org.activiti.cycle.Content; import org.activiti.cycle.MimeType; @@ -25,11 +27,18 @@ import org.activiti.cycle.context.CycleSessionContext; import org.activiti.cycle.impl.components.RuntimeConnectorList; import org.activiti.cycle.impl.connector.signavio.SignavioConnectorConfiguration; import org.activiti.cycle.impl.connector.signavio.SignavioConnectorInterface; +import org.activiti.cycle.impl.connector.signavio.action.ValidateActivitiDeployment; import org.activiti.cycle.impl.mimetype.PngMimeType; +import org.activiti.engine.ActivitiException; +import org.activiti.engine.impl.bpmn.diagram.ProcessDiagramGenerator; +import org.activiti.engine.impl.bpmn.parser.BpmnParse; +import org.activiti.engine.impl.repository.ProcessDefinitionEntity; public abstract class AbstractPngProvider extends SignavioContentRepresentationProvider { private static final long serialVersionUID = 1L; + + private static Logger log = Logger.getLogger(AbstractPngProvider.class.getName()); public Content getContent(RepositoryArtifact artifact) { try { @@ -40,6 +49,30 @@ public abstract class AbstractPngProvider extends SignavioContentRepresentationP String modelAsPngUrl = configuration.getPngUrl(artifact.getNodeId(), signavioConnector.getSecurityToken()); InputStream is = new URL(modelAsPngUrl).openStream(); content.setValue(is); + + if (is.available() <= 201) { // 201 bytes is the missing PNG + // The Signavio PNG is very often missing if the model was not yet saved + // in the Modeler + // so we use the Activiti PNG for the moment to have anything + try { + BpmnParse parse = ValidateActivitiDeployment.createParseObject(signavioConnector, artifact); + try { + parse.execute(); + } + catch (ActivitiException ex) { + // ignore parsing erros + // TODO: Think about it + } + if (parse.getProcessDefinitions().size()>0) { + // TODO: Only get the first pool (breaks for multiple pools!!) + is = ProcessDiagramGenerator.generatePngDiagram(parse.getProcessDefinitions().get(0)); + content.setValue(is); + } + } catch (Exception ex) { + log.log(Level.SEVERE, "Couldn't create PNG from BPMN 2.0 XML. Ignoring.", ex); + } + } + return content; } catch (Exception ex) { throw new RepositoryException("Exception while accessing Signavio repository", ex); @@ -57,7 +90,7 @@ public abstract class AbstractPngProvider extends SignavioContentRepresentationP public RenderInfo getRenderInfo() { return RenderInfo.IMAGE; } - + public boolean isForDownload() { return true; } diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/signavio/provider/Bpmn20Provider.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/signavio/provider/Bpmn20Provider.java index e76a398d45b8e88f222c3cb0cf02af2bfecd55a1..a2b4bbaa72f0fff42c8752743d299405e547e41d 100644 --- a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/signavio/provider/Bpmn20Provider.java +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/signavio/provider/Bpmn20Provider.java @@ -43,8 +43,8 @@ public class Bpmn20Provider extends SignavioContentRepresentationProvider { // use the bpmn2_0_serialization export servlet to provide bpmn20 xml // by doing this, we can support different signavio versions instead of // the commercial Signavio only - String jsonResponse = getJsonResponse(signavioConnector, artifact, "/json"); - JSONObject jsonData = new JSONObject(jsonResponse); + Response jsonResponse = getJsonResponse(signavioConnector, artifact, "/json"); + JSONObject jsonData = new JSONObject(jsonResponse.getEntity().getText()); String result = signavioConnector.transformJsonToBpmn20Xml(jsonData.toString()); // This would have been the alternative that works only for signavio but diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/signavio/provider/EmbeddableModelProvider.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/signavio/provider/EmbeddableModelProvider.java index fd679854d8e64ed04293f8aa0543194679aacdf1..8f8e537b2397f92b0dffd482e3dc125d3c050448 100644 --- a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/signavio/provider/EmbeddableModelProvider.java +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/signavio/provider/EmbeddableModelProvider.java @@ -39,7 +39,6 @@ import org.restlet.data.MediaType; import org.restlet.data.Method; import org.restlet.data.Preference; import org.restlet.data.Reference; -import org.restlet.engine.Engine; import org.restlet.ext.json.JsonRepresentation; import org.restlet.representation.Representation; @@ -116,16 +115,12 @@ public class EmbeddableModelProvider extends SignavioContentRepresentationProvid // Send the request and retrieve the response Response embeddedBOResponse = client.handle(embeddedBORequest); - embeddedBORequest.release(); // Get the ResponseBody as JSON JsonRepresentation jsonData = new JsonRepresentation(embeddedBOResponse.getEntity()); - + // Transform to JSONArray JSONArray jsonArray = jsonData.getJsonArray(); - - embeddedBOResponse.release(); - Engine.clearThreadLocalVariables(); // Content of jsonArray above with modelID // /model/6fd6be02c610475c9daab28a046282e2 diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/signavio/provider/Jpdl4Provider.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/signavio/provider/Jpdl4Provider.java index 7ccf1b78868c6d2aa1075264bfeebc5d904501b8..d40f50e0aab424cf8ef626d73a9e4c09b9e69bee 100644 --- a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/signavio/provider/Jpdl4Provider.java +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/signavio/provider/Jpdl4Provider.java @@ -27,7 +27,7 @@ import org.activiti.cycle.impl.connector.signavio.SignavioConnectorInterface; import org.activiti.cycle.impl.connector.signavio.repositoryartifacttype.SignavioJpdl4ArtifactType; import org.activiti.cycle.impl.mimetype.XmlMimeType; import org.activiti.cycle.impl.transform.XmlToTextTransformation; -import org.restlet.ext.json.JsonRepresentation; +import org.restlet.Response; import org.restlet.ext.xml.DomRepresentation; @CycleComponent(context = CycleContextType.APPLICATION) @@ -42,8 +42,8 @@ public class Jpdl4Provider extends SignavioContentRepresentationProvider { SignavioConnectorInterface signavioConnector = (SignavioConnectorInterface) CycleSessionContext.get(RuntimeConnectorList.class).getConnectorById(artifact.getConnectorId()); Content content = new Content(); - String jpdlResponse = getJsonResponse(signavioConnector, artifact, "/jpdl4"); - DomRepresentation xmlData = new DomRepresentation(new JsonRepresentation( jpdlResponse )); + Response jpdlResponse = getJsonResponse(signavioConnector, artifact, "/jpdl4"); + DomRepresentation xmlData = new DomRepresentation(jpdlResponse.getEntity()); XmlToTextTransformation transformation = CycleApplicationContext.get(XmlToTextTransformation.class); String jpdl4AsString = transformation.getXmlAsString(xmlData.getDomSource()); // log.finest("JPDL4 String: " + jpdl4AsString); diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/signavio/provider/JsonProvider.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/signavio/provider/JsonProvider.java index 2b29c7b020a6c829866038c8f34e84ab8fa32c35..442413495821200a1739b8a8d28d99ffbd99bed4 100644 --- a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/signavio/provider/JsonProvider.java +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/signavio/provider/JsonProvider.java @@ -38,7 +38,9 @@ public class JsonProvider extends SignavioContentRepresentationProvider { try { SignavioConnectorInterface signavioConnector = (SignavioConnectorInterface) CycleSessionContext.get(RuntimeConnectorList.class).getConnectorById(artifact.getConnectorId()); Content content = new Content(); - String jsonString = getJsonResponse(signavioConnector, artifact, "/json"); + Response jsonResponse = getJsonResponse(signavioConnector, artifact, "/json"); + + String jsonString = jsonResponse.getEntity().getText(); JSONObject jsonObj = new JSONObject(jsonString); content.setValue(jsonObj.toString(2)); return content; diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/signavio/provider/SignavioContentRepresentationProvider.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/signavio/provider/SignavioContentRepresentationProvider.java index f1c616adf853e4d9fa3a4c0d00045c69b0c5b1da..3b2862a94e80e5b3cb7eab7417376393c2ed1cd3 100644 --- a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/signavio/provider/SignavioContentRepresentationProvider.java +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/signavio/provider/SignavioContentRepresentationProvider.java @@ -17,12 +17,13 @@ import java.io.IOException; import org.activiti.cycle.ContentRepresentation; import org.activiti.cycle.RepositoryArtifact; import org.activiti.cycle.impl.connector.signavio.SignavioConnectorInterface; +import org.restlet.Response; public abstract class SignavioContentRepresentationProvider implements ContentRepresentation { private static final long serialVersionUID = 1L; - public static String getJsonResponse(SignavioConnectorInterface connector, RepositoryArtifact artifact, String urlSuffix) throws IOException { + public static Response getJsonResponse(SignavioConnectorInterface connector, RepositoryArtifact artifact, String urlSuffix) throws IOException { String url = connector.getModelUrl(artifact) + urlSuffix; return connector.getJsonResponse(url); } diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/signavio/transform/pattern/RemedyTemporarySignavioIncompatibility.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/signavio/transform/pattern/RemedyTemporarySignavioIncompatibility.java index 42eff7bef81f9f6dacea81bab9a07d9092c9f769..86f75e37d1036a5c49ce4fab06e23556dfd90f7a 100644 --- a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/signavio/transform/pattern/RemedyTemporarySignavioIncompatibility.java +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/signavio/transform/pattern/RemedyTemporarySignavioIncompatibility.java @@ -9,6 +9,8 @@ package org.activiti.cycle.impl.connector.signavio.transform.pattern; * @author ruecker */ public class RemedyTemporarySignavioIncompatibility { + + // name="SequenceFlow" --> name="" public String transformBpmn20Xml(String xml, String processName) { // set process id and name @@ -35,6 +37,9 @@ public class RemedyTemporarySignavioIncompatibility { xml = exchangeAttributeText(xml, "gatewayDirection", "converging", "Converging"); xml = exchangeAttributeText(xml, "gatewayDirection", "mixed", "Mixed"); + // Signavio sets the default name to "SequenceFlow" in the BPMN 2.0 Export, which is a bit annoying in the diagram, so remove it + xml = exchangeAttributeText(xml, "sequenceFlow", "SequenceFlow", ""); + xml = removeAttribute(xml, "processType"); // add namespace (yeah, pretty hacky, I know) diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/signavio/util/SignavioJsonHelper.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/signavio/util/SignavioJsonHelper.java index 2593f539ad91d92fdc7d566ec529b532aed853e3..db0173ab1b4e5f1ef356d8408add1105cef645d7 100644 --- a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/signavio/util/SignavioJsonHelper.java +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/signavio/util/SignavioJsonHelper.java @@ -1,7 +1,14 @@ package org.activiti.cycle.impl.connector.signavio.util; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.StringWriter; +import java.nio.charset.Charset; import java.util.Date; +import org.activiti.cycle.impl.connector.signavio.SignavioConnector; import org.json.JSONException; import org.json.JSONObject; @@ -28,4 +35,28 @@ public class SignavioJsonHelper { return null; } } + + public static String getEmptypModelTemplate() { + BufferedReader reader = null; + try { + InputStream is = SignavioConnector.class.getResourceAsStream("emptyProcessModelTemplate.json"); + reader = new BufferedReader(new InputStreamReader(is, Charset.forName("UTF-8"))); + StringWriter resultWriter = new StringWriter(); + String line; + while ((line = reader.readLine()) != null) { + resultWriter.append(line); + } + reader.close(); + return resultWriter.toString(); + } catch (IOException e) { + if (reader == null) + return null; + try { + reader.close(); + } catch (IOException ex) { + + } + return null; + } + } } diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/view/TagConnector.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/view/TagConnector.java index 0f005168cc74974119f742a722f0ccdf261c2735..fd923fc08f3033d3606089e23573ffa6c0689c4d 100644 --- a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/view/TagConnector.java +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/connector/view/TagConnector.java @@ -69,7 +69,7 @@ public class TagConnector implements RepositoryConnector { return tagFolderList; } - + public RepositoryArtifact createEmptyArtifact(String parentFolderId, String artifactName, String artifactType) throws RepositoryNodeNotFoundException { throw new UnsupportedOperationException("Cannot create artifact in TagConnector, use real RepositoryConnector istead."); } @@ -173,4 +173,8 @@ public class TagConnector implements RepositoryConnector { public void addConfigurationEntry(String key, Object value) { // this connector is not configured } + + public String concatenateNodeId(String prefix, String suffix) { + return null; + } } diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/db/CycleProcessSolutionDao.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/db/CycleProcessSolutionDao.java new file mode 100644 index 0000000000000000000000000000000000000000..f805004460209beccd542b3ea3139d23a2d8afde --- /dev/null +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/db/CycleProcessSolutionDao.java @@ -0,0 +1,32 @@ +package org.activiti.cycle.impl.db; + +import java.util.List; + +import org.activiti.cycle.impl.db.entity.ProcessSolutionEntity; +import org.activiti.cycle.impl.db.entity.VirtualRepositoryFolderEntity; +import org.activiti.cycle.processsolution.VirtualRepositoryFolder; + +/** + * DAO for process solutions + * + * @author daniel.meyer@camunda.com + */ +public interface CycleProcessSolutionDao { + + public ProcessSolutionEntity getProcessSolutionById(String id); + + public void deleteProcessSolutionById(String id); + + public List getProcessSolutionList(); + + public VirtualRepositoryFolder getVirtualRepositoryFolderById(String id); + + public List getVirtualForldersByProcessSolutionId(String id); + + public ProcessSolutionEntity saveProcessSolution(ProcessSolutionEntity processSolution); + + public List addVirtualFoldersToSolution(String id, List folders); + + public void deleteVirtualRepositoryFolderById(String id); + +} diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/db/entity/ProcessSolutionEntity.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/db/entity/ProcessSolutionEntity.java new file mode 100644 index 0000000000000000000000000000000000000000..63fd12a8c0f606c12138e5b63c0029ccb33caa0c --- /dev/null +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/db/entity/ProcessSolutionEntity.java @@ -0,0 +1,64 @@ +package org.activiti.cycle.impl.db.entity; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +import org.activiti.cycle.processsolution.ProcessSolution; +import org.activiti.cycle.processsolution.ProcessSolutionState; +import org.activiti.engine.impl.db.PersistentObject; + +/** + * Represents a {@link ProcessSolution} + * + * @author daniel.meyer@camunda.com + */ +public class ProcessSolutionEntity implements Serializable, PersistentObject, ProcessSolution { + + private static final long serialVersionUID = 1L; + + protected String id; + protected String label; + protected ProcessSolutionState state; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getLabel() { + return label; + } + + public ProcessSolutionState getState() { + return state; + } + + public String getStateName() { + return state.name(); + } + + public void setStateName(String stateName) { + this.state = ProcessSolutionState.valueOf(stateName); + } + + public void setLabel(String label) { + this.label = label; + } + + public void setState(ProcessSolutionState state) { + this.state = state; + } + + public Object getPersistentState() { + Map persistentState = new HashMap(); + persistentState.put("id", id); + persistentState.put("label", label); + persistentState.put("state", state.name()); + return persistentState; + } + +} diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/db/entity/RepositoryArtifactLinkEntity.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/db/entity/RepositoryArtifactLinkEntity.java index 2f54a6f7ff702019e817e16cd52f8431dd95cd55..d51dd6c50c0bb026aa1bdffc8f3bfb38cc40d2db 100644 --- a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/db/entity/RepositoryArtifactLinkEntity.java +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/db/entity/RepositoryArtifactLinkEntity.java @@ -29,6 +29,7 @@ public class RepositoryArtifactLinkEntity implements PersistentObject, Repositor public static final String TYPE_REFINES = "refines"; public static final String TYPE_UNSPECIFIED = "unspecified link"; public static final String TYPE_COPY = "copy"; + public static final String TYPE_REQUIREMENT = "defines requirement"; /** * artificial id used as primary key to identify this link auto generated diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/db/entity/VirtualRepositoryFolderEntity.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/db/entity/VirtualRepositoryFolderEntity.java new file mode 100644 index 0000000000000000000000000000000000000000..89922bc45b0c051d43f4428fbaf15ed53444d55b --- /dev/null +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/db/entity/VirtualRepositoryFolderEntity.java @@ -0,0 +1,90 @@ +package org.activiti.cycle.impl.db.entity; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +import org.activiti.cycle.processsolution.VirtualRepositoryFolder; +import org.activiti.engine.impl.db.PersistentObject; + +/** + * Represents a {@link VirtualRepositoryFolder}. + * + * @author daniel.meyer@camunda.com + */ +public class VirtualRepositoryFolderEntity implements VirtualRepositoryFolder, Serializable, PersistentObject { + + private static final long serialVersionUID = 1L; + + private String id; + + private String label; + + private String connectorId; + + private String referencedNodeId; + + private String processSolutionId; + + private String type; + + public Object getPersistentState() { + Map persistentState = new HashMap(); + persistentState.put("id", id); + persistentState.put("label", label); + persistentState.put("connectorId", connectorId); + persistentState.put("processSolutionId", processSolutionId); + persistentState.put("type", type); + persistentState.put("referencedNodeId", referencedNodeId); + return persistentState; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } + + public String getConnectorId() { + return connectorId; + } + + public void setConnectorId(String connectorId) { + this.connectorId = connectorId; + } + + public String getReferencedNodeId() { + return referencedNodeId; + } + + public void setReferencedNodeId(String referencedNodeId) { + this.referencedNodeId = referencedNodeId; + } + + public String getProcessSolutionId() { + return processSolutionId; + } + + public void setProcessSolutionId(String processSolutionId) { + this.processSolutionId = processSolutionId; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + +} diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/db/impl/CycleDaoMyBatisImpl.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/db/impl/CycleDaoMyBatisImpl.java index 0f5fad2215e5b58eabcdfc45badfabb50c8a7841..91971744bfa94f098863b63d2921a1ebd411acd6 100644 --- a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/db/impl/CycleDaoMyBatisImpl.java +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/db/impl/CycleDaoMyBatisImpl.java @@ -14,22 +14,26 @@ import org.activiti.cycle.impl.CycleTagContentImpl; import org.activiti.cycle.impl.conf.RepositoryConnectorConfigurationImpl; import org.activiti.cycle.impl.db.CycleCommentDao; import org.activiti.cycle.impl.db.CycleConfigurationDao; +import org.activiti.cycle.impl.db.CycleProcessSolutionDao; import org.activiti.cycle.impl.db.CycleRepositoryConnectorConfigurationDao; import org.activiti.cycle.impl.db.CycleLinkDao; import org.activiti.cycle.impl.db.CyclePeopleLinkDao; import org.activiti.cycle.impl.db.CycleTagDao; import org.activiti.cycle.impl.db.entity.CycleConfigEntity; import org.activiti.cycle.impl.db.entity.CycleRepositoryConnectorConfigurationEntity; +import org.activiti.cycle.impl.db.entity.ProcessSolutionEntity; import org.activiti.cycle.impl.db.entity.RepositoryArtifactLinkEntity; import org.activiti.cycle.impl.db.entity.RepositoryNodeCommentEntity; import org.activiti.cycle.impl.db.entity.RepositoryNodePeopleLinkEntity; import org.activiti.cycle.impl.db.entity.RepositoryNodeTagEntity; +import org.activiti.cycle.impl.db.entity.VirtualRepositoryFolderEntity; +import org.activiti.cycle.processsolution.VirtualRepositoryFolder; import org.activiti.engine.ProcessEngines; import org.activiti.engine.identity.Group; import org.apache.ibatis.session.SqlSession; public class CycleDaoMyBatisImpl extends AbstractCycleDaoMyBatisImpl implements CycleCommentDao, CycleRepositoryConnectorConfigurationDao, CycleLinkDao, - CyclePeopleLinkDao, CycleTagDao, CycleConfigurationDao { + CyclePeopleLinkDao, CycleTagDao, CycleConfigurationDao, CycleProcessSolutionDao { private static Logger log = Logger.getLogger(CycleDaoMyBatisImpl.class.getName()); @@ -406,4 +410,105 @@ public class CycleDaoMyBatisImpl extends AbstractCycleDaoMyBatisImpl implements } } + public ProcessSolutionEntity getProcessSolutionById(String id) { + SqlSession sqlSession = openSession(); + try { + return (ProcessSolutionEntity) sqlSession.selectOne("selectProcessSolutionById", id); + } finally { + sqlSession.close(); + } + } + + @SuppressWarnings("unchecked") + public List getProcessSolutionList() { + SqlSession sqlSession = openSession(); + try { + return (List) sqlSession.selectList("selectProcessSolutions"); + } finally { + sqlSession.close(); + } + } + + public VirtualRepositoryFolderEntity getVirtualRepositoryFolderById(String id) { + SqlSession sqlSession = openSession(); + try { + return (VirtualRepositoryFolderEntity) sqlSession.selectOne("selectVirtualRepositoryFolderById", id); + } finally { + sqlSession.close(); + } + } + + @SuppressWarnings("unchecked") + public List getVirtualForldersByProcessSolutionId(String id) { + SqlSession sqlSession = openSession(); + try { + return (List) sqlSession.selectList("selectVirtualRepositoryFolderByProcessSolutionId", id); + } finally { + sqlSession.close(); + } + } + + public List addVirtualFoldersToSolution(String id, List folders) { + SqlSession sqlSession = openSession(); + try { + for (VirtualRepositoryFolderEntity virtualRepositoryFolderEntity : folders) { + virtualRepositoryFolderEntity.setProcessSolutionId(id); + virtualRepositoryFolderEntity.setId(UUID.randomUUID().toString()); + sqlSession.insert("insertVirtualRepositoryFolder", virtualRepositoryFolderEntity); + } + sqlSession.commit(); + return folders; + } catch (Exception e) { + sqlSession.rollback(); + throw new RuntimeException(e); + } finally { + sqlSession.close(); + } + } + + public ProcessSolutionEntity saveProcessSolution(ProcessSolutionEntity processSolution) { + SqlSession sqlSession = openSession(); + try { + if (processSolution.getId() == null) { + processSolution.setId(UUID.randomUUID().toString()); + sqlSession.insert("insertProcessSolution", processSolution); + } else { + sqlSession.update("updateProcessSolution", processSolution); + } + sqlSession.commit(); + return processSolution; + } catch (Exception e) { + sqlSession.rollback(); + throw new RuntimeException(e); + } finally { + sqlSession.close(); + } + } + + public void deleteProcessSolutionById(String id) { + SqlSession sqlSession = openSession(); + try { + sqlSession.delete("deleteProcessSolution", id); + sqlSession.commit(); + } catch (Exception e) { + sqlSession.rollback(); + throw new RuntimeException(e); + } finally { + sqlSession.close(); + } + } + + public void deleteVirtualRepositoryFolderById(String id) { + SqlSession sqlSession = openSession(); + try { + sqlSession.delete("deleteVirtualRepositoryFolder", id); + sqlSession.commit(); + } catch (Exception e) { + sqlSession.rollback(); + throw new RuntimeException(e); + } finally { + sqlSession.close(); + } + } + } diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/event/CycleEvents.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/event/CycleEvents.java new file mode 100644 index 0000000000000000000000000000000000000000..35b760a865a24c716a898f2e954fcb8fd9c4294f --- /dev/null +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/event/CycleEvents.java @@ -0,0 +1,71 @@ +package org.activiti.cycle.impl.event; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.activiti.cycle.CycleComponentFactory; +import org.activiti.cycle.annotations.CycleComponent; +import org.activiti.cycle.context.CycleApplicationContext; +import org.activiti.cycle.context.CycleContextType; +import org.activiti.cycle.event.CycleEventListener; + +/** + * {@link CycleContextType#APPLICATION}-scoped component for managing events. + * + * @author daniel.meyer@camunda.com + */ +@SuppressWarnings({ "unchecked", "rawtypes" }) +@CycleComponent(context = CycleContextType.APPLICATION) +public class CycleEvents { + + protected Map, Set>> eventListenerMap = null; + + public Set> getEventListeners(Class forEvent) { + Set> resultSet = new HashSet>(); + init(); + if (eventListenerMap.get(forEvent) != null) { + for (CycleEventListener< ? > cycleEventListener : eventListenerMap.get(forEvent)) { + resultSet.add((CycleEventListener) cycleEventListener); + } + } + return resultSet; + } + + protected void init() { + if (eventListenerMap == null) { + synchronized (this) { + if (eventListenerMap == null) { + eventListenerMap = new HashMap, Set>>(); + Set> allImplementations = CycleComponentFactory.getAllImplementations(CycleEventListener.class); + for (Class class1 : allImplementations) { + Type[] parameterizedTypes = class1.getGenericInterfaces(); + for (Type type : parameterizedTypes) { + if (!(type instanceof ParameterizedType)) { + continue; + } + ParameterizedType parameterizedType = (ParameterizedType) type; + for (Type eventType : parameterizedType.getActualTypeArguments()) { + if (!(eventType instanceof Class)) { + continue; + } + // found the event type + Class< ? > eventClass = (Class< ? >) eventType; + Set> listenersForThisType = eventListenerMap.get(eventClass); + if (listenersForThisType == null) { + listenersForThisType = new HashSet>(); + eventListenerMap.put(eventClass, listenersForThisType); + } + listenersForThisType.add(CycleApplicationContext.get(class1)); + + } + } + } + } + } + } + } +} diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/CurrentProcessesFolder.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/CurrentProcessesFolder.java new file mode 100644 index 0000000000000000000000000000000000000000..bdec333108ac3e3251b5c5c54f24532602a10880 --- /dev/null +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/CurrentProcessesFolder.java @@ -0,0 +1,34 @@ +package org.activiti.cycle.impl.processsolution; + +import java.io.Serializable; + +import org.activiti.cycle.annotations.CycleComponent; +import org.activiti.cycle.context.CycleContextType; + +/** + * + * @author daniel.meyer@camunda.com + */ +@CycleComponent(name = "currentProcessesFolder", context = CycleContextType.SESSION) // TODO: make REQUEST-scoped +public class CurrentProcessesFolder implements Serializable { + + private String connectorId; + private String folderId; + + public String getConnectorId() { + return connectorId; + } + + public void setConnectorId(String connectorId) { + this.connectorId = connectorId; + } + + public String getFolderId() { + return folderId; + } + + public void setFolderId(String folderId) { + this.folderId = folderId; + } + +} diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/DefaultProcessSolutionTemplate.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/DefaultProcessSolutionTemplate.java new file mode 100644 index 0000000000000000000000000000000000000000..99150dbf21a08ca28f5a783d19b9405da0b69148 --- /dev/null +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/DefaultProcessSolutionTemplate.java @@ -0,0 +1,83 @@ +package org.activiti.cycle.impl.processsolution; + +import java.io.ByteArrayInputStream; +import java.util.ArrayList; +import java.util.List; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; + +import org.activiti.cycle.annotations.CycleComponent; +import org.activiti.cycle.context.CycleContextType; +import org.activiti.cycle.impl.db.entity.VirtualRepositoryFolderEntity; +import org.activiti.cycle.processsolution.ProcessSolutionState; +import org.activiti.cycle.processsolution.ProcessSolutionTemplate; +import org.activiti.cycle.processsolution.VirtualRepositoryFolder; +import org.activiti.cycle.service.CycleConfigurationService; +import org.activiti.cycle.service.CycleServiceFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +/** + * Application-scoped {@link CycleComponent} representing the default + * {@link ProcessSolutionTemplate} + * + * @author daniel.meyer@camunda.com + */ +@CycleComponent(context = CycleContextType.APPLICATION) +public class DefaultProcessSolutionTemplate implements ProcessSolutionTemplate { + + private CycleConfigurationService configurationService = CycleServiceFactory.getConfigurationService(); + + public List getVirtualRepositoryFolders() { + try { + return getVirtualRepositoryFoldersFromConfiguration(); + } catch (Exception e) { + throw new RuntimeException("Default ProcessSolutionTemplate coult not be loaded: " + e.getMessage(), e); + } + } + + private List getVirtualRepositoryFoldersFromConfiguration() throws Exception { + // load template using the configurationService + String configurationString = configurationService.getConfigurationValue("processSolutionTemplates", "default"); + if (configurationString == null) { + throw new RuntimeException("No ProcessSolutionTemplate found using group='processSolutionTemplates' and key='default'"); + } + + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + DocumentBuilder db = dbf.newDocumentBuilder(); + + Document configurationDocument = db.parse(new ByteArrayInputStream(configurationString.getBytes())); + + List resultList = new ArrayList(); + + NodeList vFolderElementList = configurationDocument.getElementsByTagName("vFolder"); + for (int i = 0; i < vFolderElementList.getLength(); i++) { + Node node = vFolderElementList.item(i); + if (!(node instanceof Element)) { + continue; + } + Element element = (Element) node; + String type = element.getAttribute("type"); + String name = element.getAttribute("name"); + String connectorId = element.getAttribute("connectorId"); + String referencedNodeId = element.getAttribute("referencedNodeId"); + VirtualRepositoryFolderEntity virtualRepositoryFolderEntity = new VirtualRepositoryFolderEntity(); + virtualRepositoryFolderEntity.setType(type); + virtualRepositoryFolderEntity.setLabel(name); + virtualRepositoryFolderEntity.setConnectorId(connectorId); + virtualRepositoryFolderEntity.setReferencedNodeId(referencedNodeId); + resultList.add(virtualRepositoryFolderEntity); + } + + return resultList; + + } + + public ProcessSolutionState getInitialState() { + return ProcessSolutionState.IN_SPECIFICATION; + } + +} diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/LinkRequirementWithProcessDiagramAction.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/LinkRequirementWithProcessDiagramAction.java new file mode 100644 index 0000000000000000000000000000000000000000..5aa77cf40b864706df36bdaa72fc8feeb6d20e23 --- /dev/null +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/LinkRequirementWithProcessDiagramAction.java @@ -0,0 +1,87 @@ +package org.activiti.cycle.impl.processsolution; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.activiti.cycle.CycleComponentFactory; +import org.activiti.cycle.RepositoryArtifact; +import org.activiti.cycle.RepositoryArtifactLink; +import org.activiti.cycle.RepositoryArtifactType; +import org.activiti.cycle.RepositoryConnector; +import org.activiti.cycle.action.ArtifactAwareParameterizedAction; +import org.activiti.cycle.annotations.CycleComponent; +import org.activiti.cycle.context.CycleContextType; +import org.activiti.cycle.impl.ParameterizedHtmlFormTemplateAction; +import org.activiti.cycle.impl.db.entity.RepositoryArtifactLinkEntity; +import org.activiti.cycle.impl.processsolution.connector.ProcessSolutionArtifact; +import org.activiti.cycle.processsolution.VirtualRepositoryFolder; +import org.activiti.cycle.service.CycleProcessSolutionService; +import org.activiti.cycle.service.CycleRepositoryService; +import org.activiti.cycle.service.CycleServiceFactory; + +/** + * Action for linking Requirements with ProcessDiagrams + * + * @author daniel.meyer@camunda.com + */ +@CycleComponent(context = CycleContextType.APPLICATION) +public class LinkRequirementWithProcessDiagramAction extends ParameterizedHtmlFormTemplateAction implements ArtifactAwareParameterizedAction, + ProcessSolutionAction { + + public LinkRequirementWithProcessDiagramAction() { + super("Link Requirement with Process Model"); + } + + public void execute(RepositoryConnector connector, RepositoryArtifact artifact, Map parameters) throws Exception { + CycleRepositoryService repositoryService = CycleServiceFactory.getRepositoryService(); + + String comment = (String) parameters.get("comment"); + String targetArtifactId = (String) parameters.get("targetArtifactId"); + RepositoryConnector targetConnector = (RepositoryConnector) parameters.get("targetConnectorId"); + RepositoryArtifact requirementsArtifact = artifact; + RepositoryArtifact processModelArtifact = repositoryService.getRepositoryArtifact(targetConnector.getId(), targetArtifactId); + + RepositoryArtifactLink link = new RepositoryArtifactLinkEntity(); + link.setComment(comment); + link.setSourceArtifact(requirementsArtifact); + link.setTargetArtifact(processModelArtifact); + link.setLinkType(RepositoryArtifactLinkEntity.TYPE_REQUIREMENT); + repositoryService.addArtifactLink(link); + + } + public Set getArtifactTypes() { + // "null" means for all. + return null; + } + + public boolean isApplicable(RepositoryArtifact toArtifact) { + if (toArtifact instanceof ProcessSolutionArtifact) { + ProcessSolutionArtifact processSolutionArtifact = (ProcessSolutionArtifact) toArtifact; + if (processSolutionArtifact.getVirtualRepositoryFolder() == null) { + return false; + } + if ("Requirements".equals(processSolutionArtifact.getVirtualRepositoryFolder().getType())) { + // set values for the 'currentProcessesFolder' in the request context + CurrentProcessesFolder currentProcessesFolder = CycleComponentFactory.getCycleComponentInstance("currentProcessesFolder", CurrentProcessesFolder.class); + + CycleProcessSolutionService processService = CycleServiceFactory.getProcessSolutionService(); + List folders = processService.getFoldersForProcessSolution(processSolutionArtifact.getVirtualRepositoryFolder() + .getProcessSolutionId()); + for (VirtualRepositoryFolder virtualRepositoryFolder : folders) { + if (virtualRepositoryFolder.getType().equals("Processes")) { + currentProcessesFolder.setFolderId(virtualRepositoryFolder.getProcessSolutionId() + "/" + virtualRepositoryFolder.getId()); + currentProcessesFolder.setConnectorId("ps-" + virtualRepositoryFolder.getProcessSolutionId()); + } + } + + return true; + } + } + return false; + } + public String getFormResourceName() { + return getDefaultFormName(); + } + +} diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/ProcessSolutionAction.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/ProcessSolutionAction.java new file mode 100644 index 0000000000000000000000000000000000000000..9abbcf6f2af5d1e7d8c6199c09986401c5c59dc8 --- /dev/null +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/ProcessSolutionAction.java @@ -0,0 +1,14 @@ +package org.activiti.cycle.impl.processsolution; + + +import org.activiti.cycle.action.Action; +import org.activiti.cycle.impl.processsolution.connector.ProcessSolutionArtifact; + +/** + * Marker-interface for actions on {@link ProcessSolutionArtifact}s. + * + * @author daniel.meyer@camunda.com + */ +public interface ProcessSolutionAction extends Action { + +} diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/ProcessSolutionCreate.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/ProcessSolutionCreate.java new file mode 100644 index 0000000000000000000000000000000000000000..e55597ebf79c6cdfb8e5c54d186c26f79b95b44e --- /dev/null +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/ProcessSolutionCreate.java @@ -0,0 +1,154 @@ +package org.activiti.cycle.impl.processsolution; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.activiti.cycle.CycleComponentFactory; +import org.activiti.cycle.RepositoryConnector; +import org.activiti.cycle.RepositoryFolder; +import org.activiti.cycle.RepositoryNodeCollection; +import org.activiti.cycle.annotations.CycleComponent; +import org.activiti.cycle.context.CycleContextType; +import org.activiti.cycle.impl.components.RuntimeConnectorList; +import org.activiti.cycle.impl.connector.util.TransactionalConnectorUtils; +import org.activiti.cycle.impl.db.entity.ProcessSolutionEntity; +import org.activiti.cycle.impl.db.entity.VirtualRepositoryFolderEntity; +import org.activiti.cycle.impl.service.CycleProcessSolutionServiceImpl; +import org.activiti.cycle.processsolution.ProcessSolution; +import org.activiti.cycle.processsolution.ProcessSolutionTemplate; +import org.activiti.cycle.processsolution.VirtualRepositoryFolder; +import org.activiti.cycle.service.CycleServiceFactory; + +/** + * Cycle Component for creating new {@link ProcessSolution}s + * + * @author daniel.meyer@camunda.com + */ +@CycleComponent(name = "processSolutionCreate", context = CycleContextType.REQUEST) +public class ProcessSolutionCreate { + + private CycleProcessSolutionServiceImpl processSolutionService = (CycleProcessSolutionServiceImpl) CycleServiceFactory.getProcessSolutionService(); + private RuntimeConnectorList connectorList = CycleComponentFactory.getCycleComponentInstance(RuntimeConnectorList.class, RuntimeConnectorList.class); + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + /** + * returns a name for the {@link ProcessSolution} to be used as folder name in + * connectors. + */ + protected String getNameForFs() { + String name = ""; + for (char c : this.name.toCharArray()) { + if (Character.isLetter(c) || Character.isDigit(c)) { // only use letters + // and digits + name += c; + } + } + return name; + } + + public String createNewProcessSolution() { + ProcessSolutionTemplate template = processSolutionService.getDefaultProcessSolutionTemplate(); + + ProcessSolutionEntity processSolutionEntity = createProcessSolutionEntity(template); + + List createdVirtualFolders = new ArrayList(); + + // get participating connectors + Set participatingConnectors = getParticipatingConnectors(template); + // begin transactions: + for (RepositoryConnector participatingConnector : participatingConnectors) { + TransactionalConnectorUtils.beginTransaction(participatingConnector); + } + + try { + // create folders in repositories + for (VirtualRepositoryFolder folderTemplate : template.getVirtualRepositoryFolders()) { + createdVirtualFolders.add(createVirtualFolder(folderTemplate, processSolutionEntity)); + } + + processSolutionService.getDao().addVirtualFoldersToSolution(processSolutionEntity.getId(), createdVirtualFolders); + + // commit repository transactions: + for (RepositoryConnector participatingConnector : participatingConnectors) { + TransactionalConnectorUtils.commitTransaction(participatingConnector, "created new process solution " + name); + } + + return processSolutionEntity.getId(); + + } catch (Exception e) { + + // rollback repository transactions: + for (RepositoryConnector participatingConnector : participatingConnectors) { + TransactionalConnectorUtils.rollbackTransaction(participatingConnector); + } + + // delete virtual folders: + for (VirtualRepositoryFolder virtualFolder : createdVirtualFolders) { + processSolutionService.getDao().deleteVirtualRepositoryFolderById(virtualFolder.getId()); + } + + // delete process solution: + processSolutionService.getDao().deleteProcessSolutionById(processSolutionEntity.getId()); + + throw new RuntimeException("Could not create ProcessSolution " + e.getMessage(), e); + } + } + + private ProcessSolutionEntity createProcessSolutionEntity(ProcessSolutionTemplate template) { + ProcessSolutionEntity processSolutionEntity = new ProcessSolutionEntity(); + processSolutionEntity.setLabel(name); + processSolutionEntity.setState(template.getInitialState()); + return processSolutionService.getDao().saveProcessSolution(processSolutionEntity); + } + + private VirtualRepositoryFolderEntity createVirtualFolder(VirtualRepositoryFolder folderTemplate, ProcessSolutionEntity processSolutionEntity) { + String nodeId = folderTemplate.getReferencedNodeId(); + String connectorId = folderTemplate.getConnectorId(); + RepositoryConnector connector = connectorList.getConnectorById(connectorId); + RepositoryFolder parentFolder = connector.getRepositoryFolder(nodeId); + + // create or get folder for this processSolution using connector + RepositoryNodeCollection childNodeCollection = connector.getChildren(parentFolder.getNodeId()); + RepositoryFolder folderForThisProcessSolution = null; + for (RepositoryFolder folder : childNodeCollection.getFolderList()) { + if (folder.getMetadata().getName().equals(getNameForFs())) { + // found folder for this processSolution + folderForThisProcessSolution = folder; + } + } + if (folderForThisProcessSolution == null) { + // create new folder for this processSolution + folderForThisProcessSolution = connector.createFolder(parentFolder.getNodeId(), getNameForFs()); + } + + // create new folder under folder for this ProcessSolution: + RepositoryFolder actualFolder = connector.createFolder(folderForThisProcessSolution.getNodeId(), folderTemplate.getLabel()); + + // create entity + VirtualRepositoryFolderEntity entity = new VirtualRepositoryFolderEntity(); + entity.setLabel(folderTemplate.getLabel()); + entity.setConnectorId(connectorId); + entity.setProcessSolutionId(processSolutionEntity.getId()); + entity.setReferencedNodeId(actualFolder.getNodeId()); + entity.setType(folderTemplate.getType()); + return entity; + } + + private Set getParticipatingConnectors(ProcessSolutionTemplate template) { + Set resultList = new HashSet(); + for (VirtualRepositoryFolder virtualFolder : template.getVirtualRepositoryFolders()) { + resultList.add(connectorList.getConnectorById(virtualFolder.getConnectorId())); + } + return resultList; + } +} diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/ProcessSolutionUtils.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/ProcessSolutionUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..09e43558f21c66063cf300fa0169929f1764905b --- /dev/null +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/ProcessSolutionUtils.java @@ -0,0 +1,73 @@ +package org.activiti.cycle.impl.processsolution; + +import java.util.ArrayList; +import java.util.List; + +import org.activiti.cycle.CycleComponentFactory; +import org.activiti.cycle.RepositoryArtifact; +import org.activiti.cycle.RepositoryConnector; +import org.activiti.cycle.RepositoryFolder; +import org.activiti.cycle.RepositoryNodeCollection; +import org.activiti.cycle.annotations.CycleComponent; +import org.activiti.cycle.context.CycleContextType; +import org.activiti.cycle.impl.components.RuntimeConnectorList; +import org.activiti.cycle.impl.connector.signavio.repositoryartifacttype.SignavioBpmn20ArtifactType; +import org.activiti.cycle.processsolution.ProcessSolution; +import org.activiti.cycle.processsolution.VirtualRepositoryFolder; +import org.activiti.cycle.service.CycleProcessSolutionService; +import org.activiti.cycle.service.CycleServiceFactory; + +/** + * Utils for handling process solutions + * + * TODO: expose some of these methods in the {@link CycleProcessSolutionService} + * + * @author daniel.meyer@camunda.com + */ +@CycleComponent(context = CycleContextType.NONE) +public class ProcessSolutionUtils { + + private CycleProcessSolutionService processSolutionService = CycleServiceFactory.getProcessSolutionService(); + + public List getProcessModels(VirtualRepositoryFolder processes) { + RepositoryConnector connector = RuntimeConnectorList.getMyConnectorById(processes.getConnectorId()); + List resultList = new ArrayList(); + String currentFolder = processes.getReferencedNodeId(); + getProcessModelsRec(connector, currentFolder, resultList); + return resultList; + } + + public VirtualRepositoryFolder getImplementationFolder(ProcessSolution processSolution) { + // TODO: add dedicate query for this + List virtualFolders = processSolutionService.getFoldersForProcessSolution(processSolution.getId()); + for (VirtualRepositoryFolder virtualRepositoryFolder : virtualFolders) { + if ("Implementation".equals(virtualRepositoryFolder.getType())) { + return virtualRepositoryFolder; + } + } + return null; + } + + public VirtualRepositoryFolder getProcessesFolder(ProcessSolution processSolution) { + // TODO: add dedicate query for this + List virtualFolders = processSolutionService.getFoldersForProcessSolution(processSolution.getId()); + for (VirtualRepositoryFolder virtualRepositoryFolder : virtualFolders) { + if ("Processes".equals(virtualRepositoryFolder.getType())) { + return virtualRepositoryFolder; + } + } + return null; + } + + private void getProcessModelsRec(RepositoryConnector connector, String currentFolder, List resultList) { + RepositoryNodeCollection childNodes = connector.getChildren(currentFolder); + for (RepositoryArtifact repositoryArtifact : childNodes.getArtifactList()) { + if (repositoryArtifact.getArtifactType().equals(CycleComponentFactory.getCycleComponentInstance(SignavioBpmn20ArtifactType.class))) { + resultList.add(repositoryArtifact); + } + } + for (RepositoryFolder folder : childNodes.getFolderList()) { + getProcessModelsRec(connector, folder.getNodeId(), resultList); + } + } +} diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/artifacttype/ProcessSolutionHomeArtifactType.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/artifacttype/ProcessSolutionHomeArtifactType.java new file mode 100644 index 0000000000000000000000000000000000000000..48bdb84d8a6920987fd36ecbdb2b601104eb3151 --- /dev/null +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/artifacttype/ProcessSolutionHomeArtifactType.java @@ -0,0 +1,21 @@ +package org.activiti.cycle.impl.processsolution.artifacttype; + +import org.activiti.cycle.RepositoryArtifactType; +import org.activiti.cycle.annotations.CycleComponent; +import org.activiti.cycle.context.CycleContextType; +import org.activiti.cycle.impl.artifacttype.AbstractRepositoryArtifactType; +import org.activiti.cycle.processsolution.ProcessSolution; + +/** + * {@link RepositoryArtifactType} for {@link ProcessSolution}-Home folders + * + * @author daniel.meyer@camunda.com + */ +@CycleComponent(context = CycleContextType.APPLICATION) +public class ProcessSolutionHomeArtifactType extends AbstractRepositoryArtifactType implements RepositoryArtifactType { + + public String getName() { + return "Process Solution Home"; + } + +} diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/connector/ProcessSolutionArtifact.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/connector/ProcessSolutionArtifact.java new file mode 100644 index 0000000000000000000000000000000000000000..54086d5ecb77a50e4fa25110b5334660bfd86519 --- /dev/null +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/connector/ProcessSolutionArtifact.java @@ -0,0 +1,29 @@ +package org.activiti.cycle.impl.processsolution.connector; + +import org.activiti.cycle.CycleComponentFactory; +import org.activiti.cycle.RepositoryArtifact; +import org.activiti.cycle.RepositoryArtifactType; +import org.activiti.cycle.impl.processsolution.artifacttype.ProcessSolutionHomeArtifactType; +import org.activiti.cycle.processsolution.ProcessSolution; +import org.activiti.cycle.processsolution.VirtualRepositoryFolder; + +public class ProcessSolutionArtifact extends ProcessSolutionRepositoryNode implements RepositoryArtifact { + + public ProcessSolutionArtifact(String connectorId, String nodeId, VirtualRepositoryFolder virtualFolder, ProcessSolution processSolution, + RepositoryArtifact wrappedArtifact) { + super(connectorId, nodeId, virtualFolder, processSolution, wrappedArtifact); + } + + public RepositoryArtifactType getArtifactType() { + if (wrappedNode != null) { + return ((RepositoryArtifact) wrappedNode).getArtifactType(); + } else if (getNodeId().endsWith(ProcessSolutionConnector.PS_HOME_NAME)) { + return CycleComponentFactory.getCycleComponentInstance(ProcessSolutionHomeArtifactType.class, ProcessSolutionHomeArtifactType.class); + } + return null; + } + + public RepositoryArtifact getWrappedNode() { + return (RepositoryArtifact) super.getWrappedNode(); + } +} diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/connector/ProcessSolutionConnector.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/connector/ProcessSolutionConnector.java new file mode 100644 index 0000000000000000000000000000000000000000..c16c13e3cfaa8b64ffdbbcad976aaf0cc5943a08 --- /dev/null +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/connector/ProcessSolutionConnector.java @@ -0,0 +1,350 @@ +package org.activiti.cycle.impl.processsolution.connector; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.activiti.cycle.Content; +import org.activiti.cycle.ContentRepresentation; +import org.activiti.cycle.CycleComponentFactory; +import org.activiti.cycle.RepositoryArtifact; +import org.activiti.cycle.RepositoryConnector; +import org.activiti.cycle.RepositoryException; +import org.activiti.cycle.RepositoryFolder; +import org.activiti.cycle.RepositoryNode; +import org.activiti.cycle.RepositoryNodeCollection; +import org.activiti.cycle.RepositoryNodeNotFoundException; +import org.activiti.cycle.action.ParameterizedAction; +import org.activiti.cycle.context.CycleRequestContext; +import org.activiti.cycle.impl.RepositoryNodeCollectionImpl; +import org.activiti.cycle.impl.components.RuntimeConnectorList; +import org.activiti.cycle.impl.processsolution.ProcessSolutionAction; +import org.activiti.cycle.impl.processsolution.representation.ProcessSolutionHomeContentRepresentation; +import org.activiti.cycle.processsolution.ProcessSolution; +import org.activiti.cycle.processsolution.VirtualRepositoryFolder; +import org.activiti.cycle.service.CyclePluginService; +import org.activiti.cycle.service.CycleProcessSolutionService; +import org.activiti.cycle.service.CycleRepositoryService; +import org.activiti.cycle.service.CycleServiceFactory; + +/** + * Virtual {@link RepositoryConnector} for {@link ProcessSolution}s + * + * @author daniel.meyer@camunda.com + */ +public class ProcessSolutionConnector implements RepositoryConnector { + + protected CycleProcessSolutionService processSolutionService = CycleServiceFactory.getProcessSolutionService(); + + protected CycleRepositoryService repositoryService = CycleServiceFactory.getRepositoryService(); + + public static String PS_HOME_NAME = "/PSHOME"; + + protected final String processSolutionId; + + public ProcessSolutionConnector(String id) { + processSolutionId = id; + } + + public boolean login(String username, String password) { + // does not require login + return true; + } + + public static String getProcessSolutionId(String nodeId) { + if (nodeId == null) { + return null; + } + if (!nodeId.contains("/")) { + return nodeId; + } + return nodeId.substring(0, nodeId.indexOf("/")); + } + + public static String getVirtualFolderId(String nodeId) { + if (nodeId == null) { + return null; + } + String[] parts = nodeId.split("/"); + if (parts.length >= 2) { + return parts[1]; + } + return null; + } + + public RepositoryNode getRepositoryNode(String id) throws RepositoryNodeNotFoundException { + + String processSolutionId = getProcessSolutionId(id); + String virtualFolderId = getVirtualFolderId(id); + VirtualRepositoryFolder virtualFolder = null; + if (virtualFolderId != null) { + virtualFolder = processSolutionService.getVirtualRepositoryFolderById(virtualFolderId); + if (virtualFolder == null) { + virtualFolderId = null; + } + } + if ("".equals(id) || id == null) { + throw new RepositoryNodeNotFoundException(id); + } + + if ("/".equals(id)) { + processSolutionId = this.processSolutionId; + } + + if (!processSolutionId.equals(this.processSolutionId)) { + processSolutionId = null; + } + ProcessSolution processSolution; + RepositoryConnector connector; + // get the vFolderId from the request: + String vFolderId = CycleRequestContext.get("vFolderId", String.class); + if (vFolderId != null && virtualFolderId == null && processSolutionId == null) { + virtualFolderId = vFolderId; + virtualFolder = processSolutionService.getVirtualRepositoryFolderById(vFolderId); + processSolutionId = virtualFolder.getProcessSolutionId(); + connector = CycleComponentFactory.getCycleComponentInstance(RuntimeConnectorList.class, RuntimeConnectorList.class).getConnectorById( + virtualFolder.getConnectorId()); + processSolution = processSolutionService.getProcessSolutionById(processSolutionId); + if (virtualFolder.getReferencedNodeId().equals(id)) { + return new ProcessSolutionFolder(getId(), "/" + processSolutionId + "/" + virtualFolderId, null, processSolution, null); + } + } else { + + if (processSolutionId == null) { + throw new RepositoryNodeNotFoundException(id); + } + + if (processSolutionId.equals(this.processSolutionId) == false) { + throw new RepositoryNodeNotFoundException(id); + } + // id=id of a processSolution + processSolution = processSolutionService.getProcessSolutionById(processSolutionId); + + if (id.endsWith(PS_HOME_NAME)) { + return new ProcessSolutionArtifact(getId(), id, null, processSolution, null); + } + + if (virtualFolderId == null) { + return new ProcessSolutionFolder(getId(), id, null, processSolution, null); + } + + virtualFolder = processSolutionService.getVirtualRepositoryFolderById(virtualFolderId); + String relativePath = id.replace(processSolutionId + "/" + virtualFolderId, ""); + if (relativePath.length() == 0) { + // id == processsolution/virtualFolderId + return new ProcessSolutionFolder(getId(), id, virtualFolder, processSolution, null); + } + relativePath = id.replace(processSolutionId + "/" + virtualFolderId + "/", ""); + + // id == processsolution/virtualFolderId/... + connector = CycleComponentFactory.getCycleComponentInstance(RuntimeConnectorList.class, RuntimeConnectorList.class).getConnectorById( + virtualFolder.getConnectorId()); + id = connector.concatenateNodeId(virtualFolder.getReferencedNodeId(), relativePath); + } + + try { + RepositoryFolder folder = connector.getRepositoryFolder(id); + return new ProcessSolutionFolder(getId(), id, virtualFolder, processSolution, folder); + } catch (Exception e) { + RepositoryArtifact artifact = connector.getRepositoryArtifact(id); + return new ProcessSolutionArtifact(getId(), id, virtualFolder, processSolution, artifact); + } + + } + public RepositoryArtifact getRepositoryArtifact(String id) throws RepositoryNodeNotFoundException { + return (RepositoryArtifact) getRepositoryNode(id); + } + + public Content getRepositoryArtifactPreview(String artifactId) throws RepositoryNodeNotFoundException { + return null; + } + + public RepositoryFolder getRepositoryFolder(String id) throws RepositoryNodeNotFoundException { + return (RepositoryFolder) getRepositoryNode(id); + } + + public RepositoryNodeCollection getChildren(String id) throws RepositoryNodeNotFoundException { + List resultList = new ArrayList(); + ProcessSolutionFolder processSolutionFolder = (ProcessSolutionFolder) getRepositoryNode(id); + VirtualRepositoryFolder virtualFolder = processSolutionFolder.getVirtualRepositoryFolder(); + RepositoryFolder wrappedFolder = (RepositoryFolder) processSolutionFolder.getWrappedNode(); + RepositoryNodeCollection childNodes = null; + if (wrappedFolder != null) { + // get child nodes of wrapped folder + childNodes = repositoryService.getChildren(wrappedFolder.getConnectorId(), wrappedFolder.getNodeId()); + } else if (virtualFolder != null) { + // get child nodes of virtual folder + childNodes = repositoryService.getChildren(virtualFolder.getConnectorId(), virtualFolder.getReferencedNodeId()); + } + if (childNodes != null) { + for (RepositoryNode childNode : childNodes.asList()) { + String childNodeId = childNode.getNodeId(); + childNodeId = childNodeId.replace(processSolutionFolder.getVirtualRepositoryFolder().getReferencedNodeId(), ""); + childNodeId = processSolutionId + "/" + processSolutionFolder.getVirtualRepositoryFolder().getId() + "/" + childNodeId; + if (childNode instanceof RepositoryArtifact) { + resultList.add(new ProcessSolutionArtifact(getId(), childNodeId, processSolutionFolder.getVirtualRepositoryFolder(), processSolutionFolder + .getProcessSolution(), (RepositoryArtifact) childNode)); + } else { + resultList.add(new ProcessSolutionFolder(getId(), childNodeId, processSolutionFolder.getVirtualRepositoryFolder(), processSolutionFolder + .getProcessSolution(), (RepositoryFolder) childNode)); + } + } + } else { + // add Home folder: +// resultList.add(new ProcessSolutionArtifact(getId(), processSolutionId + PS_HOME_NAME, null, processSolutionFolder.processSolution, null)); + + // get children of process solution: + for (VirtualRepositoryFolder virtualChildfolder : processSolutionService.getFoldersForProcessSolution(processSolutionId)) { + String childNodeId = processSolutionId + "/" + virtualChildfolder.getId(); + resultList.add(new ProcessSolutionFolder(getId(), childNodeId, virtualChildfolder, processSolutionFolder.getProcessSolution(), null)); + } + } + return new RepositoryNodeCollectionImpl(resultList); + } + + public RepositoryArtifact createArtifact(String parentFolderId, String artifactName, String artifactType, Content artifactContent) + throws RepositoryNodeNotFoundException { + ProcessSolutionFolder folder = (ProcessSolutionFolder) getRepositoryNode(parentFolderId); + if (folder.getVirtualRepositoryFolder() == null) { + throw new RepositoryException("Cannot create artifact in the top-level folder. "); + } + RepositoryArtifact newArtifact = null; + if (folder.getWrappedNode() == null) { + newArtifact = repositoryService.createArtifact(folder.getVirtualRepositoryFolder().getConnectorId(), folder.getVirtualRepositoryFolder() + .getReferencedNodeId(), artifactName, artifactType, artifactContent); + } else { + newArtifact = repositoryService.createArtifact(folder.getWrappedNode().getConnectorId(), folder.getWrappedNode().getNodeId(), artifactName, artifactType, + artifactContent); + } + String relativePath = newArtifact.getNodeId().replace(folder.getVirtualRepositoryFolder().getReferencedNodeId(), ""); + String virtualPath = folder.getNodeId() + "/" + relativePath; + return new ProcessSolutionArtifact(getId(), virtualPath, folder.getVirtualRepositoryFolder(), folder.getProcessSolution(), newArtifact); + } + + public RepositoryArtifact createArtifactFromContentRepresentation(String parentFolderId, String artifactName, String artifactType, + String contentRepresentationName, Content artifactContent) throws RepositoryNodeNotFoundException { + return createArtifact(parentFolderId, artifactName, artifactType, artifactContent); + } + + public RepositoryArtifact createEmptyArtifact(String parentFolderId, String artifactName, String artifactType) throws RepositoryNodeNotFoundException { + ProcessSolutionFolder folder = (ProcessSolutionFolder) getRepositoryNode(parentFolderId); + if (folder.getVirtualRepositoryFolder() == null) { + throw new RepositoryException("Cannot create artifact in the top-level folder. "); + } + RepositoryArtifact newArtifact = null; + if (folder.getWrappedNode() == null) { + newArtifact = repositoryService.createEmptyArtifact(folder.getVirtualRepositoryFolder().getConnectorId(), folder.getVirtualRepositoryFolder() + .getReferencedNodeId(), artifactName, artifactType); + } else { + newArtifact = repositoryService.createEmptyArtifact(folder.getWrappedNode().getConnectorId(), folder.getWrappedNode().getNodeId(), artifactName, + artifactType); + } + String relativePath = newArtifact.getNodeId().replace(folder.getVirtualRepositoryFolder().getReferencedNodeId(), ""); + String virtualPath = folder.getNodeId() + "/" + relativePath; + return new ProcessSolutionArtifact(getId(), virtualPath, folder.getVirtualRepositoryFolder(), folder.getProcessSolution(), newArtifact); + } + + public RepositoryFolder createFolder(String parentFolderId, String name) throws RepositoryNodeNotFoundException { + ProcessSolutionFolder folder = (ProcessSolutionFolder) getRepositoryNode(parentFolderId); + if (folder.getVirtualRepositoryFolder() == null) { + throw new RepositoryException("Cannot create artifact in the top-level folder. "); + } + if (folder.getWrappedNode() == null) { + return repositoryService.createFolder(folder.getVirtualRepositoryFolder().getConnectorId(), folder.getVirtualRepositoryFolder().getReferencedNodeId(), + name); + } + return repositoryService.createFolder(folder.getWrappedNode().getConnectorId(), folder.getWrappedNode().getNodeId(), name); + } + + public Content getContent(String artifactId) throws RepositoryNodeNotFoundException { + ProcessSolutionArtifact artifact = (ProcessSolutionArtifact) getRepositoryNode(artifactId); + if (artifactId.endsWith(PS_HOME_NAME)) { + return CycleComponentFactory.getCycleComponentInstance(ProcessSolutionHomeContentRepresentation.class, ProcessSolutionHomeContentRepresentation.class) + .getContent(artifact); + } + return repositoryService.getContent(artifact.wrappedNode.getConnectorId(), artifact.wrappedNode.getNodeId()); + } + + public void updateContent(String artifactId, Content content) throws RepositoryNodeNotFoundException { + ProcessSolutionArtifact artifact = (ProcessSolutionArtifact) getRepositoryNode(artifactId); + repositoryService.updateContent(artifact.wrappedNode.getConnectorId(), artifact.wrappedNode.getNodeId(), content); + } + + public void updateContent(String artifactId, String contentRepresentationName, Content content) throws RepositoryNodeNotFoundException { + updateContent(artifactId, content); + } + + public void deleteArtifact(String artifactId) throws RepositoryNodeNotFoundException { + ProcessSolutionArtifact artifact = (ProcessSolutionArtifact) getRepositoryNode(artifactId); + repositoryService.deleteArtifact(artifact.wrappedNode.getConnectorId(), artifact.wrappedNode.getNodeId()); + } + + public void deleteFolder(String folderId) throws RepositoryNodeNotFoundException { + ProcessSolutionFolder folder = (ProcessSolutionFolder) getRepositoryNode(folderId); + repositoryService.deleteFolder(folder.wrappedNode.getConnectorId(), folder.wrappedNode.getNodeId()); + } + + public void executeParameterizedAction(String artifactId, String actionId, Map parameters) throws Exception { + ProcessSolutionArtifact artifact = (ProcessSolutionArtifact) getRepositoryNode(artifactId); + CyclePluginService pluginService = CycleServiceFactory.getCyclePluginService(); + ParameterizedAction action = pluginService.getParameterizedActionById(actionId); + if (action instanceof ProcessSolutionAction) { + // execute ProcessSolutionActions using the ProcessSolutionArtifact and + // the ProcessSolutionConnector + action.execute(this, artifact, parameters); + } else { + // execute non-ProcessSolutionActions using the underlying connector + repositoryService.executeParameterizedAction(artifact.wrappedNode.getConnectorId(), artifact.wrappedNode.getNodeId(), actionId, parameters); + } + } + + public boolean isLoggedIn() { + return false; + } + + public ContentRepresentation getDefaultContentRepresentation(String artifactId) throws RepositoryNodeNotFoundException { + ProcessSolutionArtifact artifact = (ProcessSolutionArtifact) getRepositoryNode(artifactId); + RuntimeConnectorList connectorList = CycleComponentFactory.getCycleComponentInstance(RuntimeConnectorList.class, RuntimeConnectorList.class); + RepositoryConnector connector = connectorList.getConnectorById(artifact.connectorId); + return connector.getDefaultContentRepresentation(artifact.wrappedNode.getNodeId()); + } + + public void startConfiguration() { + } + + public void addConfiguration(Map configurationValues) { + } + + public void addConfigurationEntry(String key, Object value) { + } + + public void configurationFinished() { + } + + public String[] getConfigurationKeys() { + return null; + } + + public void setId(String connectorId) { + } + + public String getId() { + return "ps-" + processSolutionId; + } + + public String getName() { + try { + return processSolutionService.getProcessSolutionById(processSolutionId).getLabel(); + } catch (Exception e) { + return "Deleted processSolution - " + processSolutionId; + } + } + + public void setName(String name) { + } + + public String concatenateNodeId(String prefix, String suffix) { + return null; + } + +} diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/connector/ProcessSolutionFolder.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/connector/ProcessSolutionFolder.java new file mode 100644 index 0000000000000000000000000000000000000000..af5ac07e7a53a2cbf675e9a51a7610fc26ad2693 --- /dev/null +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/connector/ProcessSolutionFolder.java @@ -0,0 +1,19 @@ +package org.activiti.cycle.impl.processsolution.connector; + +import org.activiti.cycle.RepositoryFolder; +import org.activiti.cycle.processsolution.ProcessSolution; +import org.activiti.cycle.processsolution.VirtualRepositoryFolder; + +/** + * A {@link RepositoryFolder}-implementation for virtual folders + * + * @author Daniel Meyer + */ +public class ProcessSolutionFolder extends ProcessSolutionRepositoryNode implements RepositoryFolder { + + public ProcessSolutionFolder(String connectorId, String nodeId, VirtualRepositoryFolder virtualFolder, ProcessSolution processSolution, + RepositoryFolder wrappedFolder) { + super(connectorId, nodeId, virtualFolder, processSolution, wrappedFolder); + } + +} diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/connector/ProcessSolutionRepositoryNode.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/connector/ProcessSolutionRepositoryNode.java new file mode 100644 index 0000000000000000000000000000000000000000..ce4f74885cdc56eb1c22d30388044ae69bb648a0 --- /dev/null +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/connector/ProcessSolutionRepositoryNode.java @@ -0,0 +1,119 @@ +package org.activiti.cycle.impl.processsolution.connector; + +import org.activiti.cycle.RepositoryFolder; +import org.activiti.cycle.RepositoryNode; +import org.activiti.cycle.RepositoryNodeMetadata; +import org.activiti.cycle.impl.RepositoryNodeMetadataImpl; +import org.activiti.cycle.processsolution.ProcessSolution; +import org.activiti.cycle.processsolution.VirtualRepositoryFolder; + +/** + * A {@link RepositoryFolder}-implementation for virtual folders + * + * @author daniel.meyer@camunda.com + */ +public class ProcessSolutionRepositoryNode implements RepositoryNode { + + protected VirtualRepositoryFolder virtualRepositoryFolder; + + protected ProcessSolution processSolution; + + protected RepositoryNode wrappedNode; + + protected String connectorId; + + protected String nodeId; + + public ProcessSolutionRepositoryNode(String connectorId, String nodeId, VirtualRepositoryFolder virtualFolder, ProcessSolution processSolution, + RepositoryNode wrappedNode) { + this.virtualRepositoryFolder = virtualFolder; + this.connectorId = connectorId; + this.nodeId = nodeId; + this.processSolution = processSolution; + this.wrappedNode = wrappedNode; + } + + public String getConnectorId() { + if (wrappedNode != null) { + return wrappedNode.getConnectorId(); + } + return connectorId; + } + + public String getNodeId() { + if (wrappedNode != null) { + return wrappedNode.getNodeId(); + } + return nodeId; + } + + public String getGlobalUniqueId() { + if (wrappedNode != null) { + return wrappedNode.getGlobalUniqueId(); + } + return nodeId; + } + + public RepositoryNodeMetadata getMetadata() { + if (wrappedNode != null) { + if (!wrappedNode.getMetadata().getParentFolderId().equals(virtualRepositoryFolder.getReferencedNodeId())) { + return wrappedNode.getMetadata(); + } + } + + return new RepositoryNodeMetadataImpl() { + + public String getName() { + if (wrappedNode != null) { + return wrappedNode.getMetadata().getName(); + } + if (virtualRepositoryFolder != null) { + return virtualRepositoryFolder.getLabel(); + } + if (nodeId.endsWith(ProcessSolutionConnector.PS_HOME_NAME)) { + return "Home"; + } + return processSolution.getLabel(); + } + public String getParentFolderId() { + if (wrappedNode != null) { + return processSolution.getId() + "/" + virtualRepositoryFolder.getId(); + } + return "/"; + } + }; + } + + public VirtualRepositoryFolder getVirtualRepositoryFolder() { + return virtualRepositoryFolder; + } + + public void setVirtualRepositoryFolder(VirtualRepositoryFolder virtualRepositoryFolder) { + this.virtualRepositoryFolder = virtualRepositoryFolder; + } + + public ProcessSolution getProcessSolution() { + return processSolution; + } + + public void setProcessSolution(ProcessSolution processSolution) { + this.processSolution = processSolution; + } + + public RepositoryNode getWrappedNode() { + return wrappedNode; + } + + public void setWrappedNode(RepositoryNode wrappedNode) { + this.wrappedNode = wrappedNode; + } + + public void setConnectorId(String connectorId) { + this.connectorId = connectorId; + } + + public void setNodeId(String nodeId) { + this.nodeId = nodeId; + } + +} diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/event/ImplementationDoneEvent.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/event/ImplementationDoneEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..984f6a126e04f6ef235aeff001c3ecbd79ee7225 --- /dev/null +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/event/ImplementationDoneEvent.java @@ -0,0 +1,17 @@ +package org.activiti.cycle.impl.processsolution.event; + +import org.activiti.cycle.processsolution.ProcessSolution; + +/** + * Signifies that the implementation-phase is completed for a given + * {@link ProcessSolution} + * + * @author daniel.meyer@camunda.com + */ +public class ImplementationDoneEvent extends ProcessSolutionStateEvent { + + public ImplementationDoneEvent(ProcessSolution processSolution) { + super(processSolution); + } + +} diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/event/ProcessSolutionStateEvent.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/event/ProcessSolutionStateEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..d71939c7635c292da07bf98ff7ba406c92fdbf87 --- /dev/null +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/event/ProcessSolutionStateEvent.java @@ -0,0 +1,22 @@ +package org.activiti.cycle.impl.processsolution.event; + +import org.activiti.cycle.processsolution.ProcessSolution; + +/** + * Signifies that a {@link ProcessSolution} is transitioning to a new state. + * + * @author daniel.meyer@camunda.com + */ +public class ProcessSolutionStateEvent { + + private ProcessSolution processSolution; + + public ProcessSolutionStateEvent(ProcessSolution processSolution) { + this.processSolution = processSolution; + } + + public ProcessSolution getProcessSolution() { + return processSolution; + } + +} diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/event/SpecificationDoneEvent.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/event/SpecificationDoneEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..ca79d15ed77a11b7666a5f423cffa192fab7629b --- /dev/null +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/event/SpecificationDoneEvent.java @@ -0,0 +1,17 @@ +package org.activiti.cycle.impl.processsolution.event; + +import org.activiti.cycle.processsolution.ProcessSolution; + +/** + * Signifies that the specification-phase is completed for a given + * {@link ProcessSolution} + * + * @author daniel.meyer@camunda.com + */ +public class SpecificationDoneEvent extends ProcessSolutionStateEvent { + + public SpecificationDoneEvent(ProcessSolution processSolution) { + super(processSolution); + } + +} diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/event/TechnicalProjectCreatedEvent.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/event/TechnicalProjectCreatedEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..e440db85cff19833900d28cebf90957c12a61448 --- /dev/null +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/event/TechnicalProjectCreatedEvent.java @@ -0,0 +1,30 @@ +package org.activiti.cycle.impl.processsolution.event; + +import org.activiti.cycle.RepositoryFolder; +import org.activiti.cycle.processsolution.ProcessSolution; + +/** + * Signifies that a new technical project has been generated. + * + * @author daniel.meyer@camunda.com + */ +public class TechnicalProjectCreatedEvent { + + private ProcessSolution processSolution; + + private RepositoryFolder repositoryFolder; + + public TechnicalProjectCreatedEvent(ProcessSolution ps, RepositoryFolder folder) { + this.processSolution = ps; + this.repositoryFolder = folder; + } + + public ProcessSolution getProcessSolution() { + return processSolution; + } + + public RepositoryFolder getRepositoryFolder() { + return repositoryFolder; + } + +} diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/event/TechnicalProjectUpdatedEvent.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/event/TechnicalProjectUpdatedEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..85ee1272bf8c9338a13b4258831c96e43e5bac36 --- /dev/null +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/event/TechnicalProjectUpdatedEvent.java @@ -0,0 +1,30 @@ +package org.activiti.cycle.impl.processsolution.event; + +import org.activiti.cycle.RepositoryFolder; +import org.activiti.cycle.processsolution.ProcessSolution; + +/** + * Signifies that a new technical project has been updated. + * + * @author daniel.meyer@camunda.com + */ +public class TechnicalProjectUpdatedEvent { + + private ProcessSolution processSolution; + + private RepositoryFolder repositoryFolder; + + public TechnicalProjectUpdatedEvent(ProcessSolution ps, RepositoryFolder folder) { + this.processSolution = ps; + this.repositoryFolder = folder; + } + + public ProcessSolution getProcessSolution() { + return processSolution; + } + + public RepositoryFolder getRepositoryFolder() { + return repositoryFolder; + } + +} diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/event/TestingDoneEvent.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/event/TestingDoneEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..6ef76c8eebf7d536413b6fe7772b247d3b1a7a36 --- /dev/null +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/event/TestingDoneEvent.java @@ -0,0 +1,17 @@ +package org.activiti.cycle.impl.processsolution.event; + +import org.activiti.cycle.processsolution.ProcessSolution; + +/** + * Signifies that the testing-phase is completed for a given + * {@link ProcessSolution} + * + * @author daniel.meyer@camunda.com + */ +public class TestingDoneEvent extends ProcessSolutionStateEvent { + + public TestingDoneEvent(ProcessSolution processSolution) { + super(processSolution); + } + +} diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/listener/AbstractProcessSolutionStateEmailListener.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/listener/AbstractProcessSolutionStateEmailListener.java new file mode 100644 index 0000000000000000000000000000000000000000..73047c9abf9fc088a90b11cb3931d8f571b7a685 --- /dev/null +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/listener/AbstractProcessSolutionStateEmailListener.java @@ -0,0 +1,50 @@ +package org.activiti.cycle.impl.processsolution.listener; + +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.activiti.cycle.CycleComponentFactory; +import org.activiti.cycle.event.CycleEventListener; +import org.activiti.cycle.impl.components.CycleEmailDispatcher; +import org.activiti.cycle.impl.processsolution.event.ProcessSolutionStateEvent; +import org.activiti.cycle.processsolution.ProcessSolution; +import org.activiti.cycle.service.CycleServiceFactory; +import org.activiti.engine.identity.User; + +/** + * Abstract base class for {@link ProcessSolutionStateEvent}-listeners which + * send a notification via email. + * + * @author daniel.meyer@camunda.com + */ +public abstract class AbstractProcessSolutionStateEmailListener implements CycleEventListener { + + protected Logger logger = Logger.getLogger(getClass().getName()); + + /** + * TODO: Make configurable (it is important to have a valid address here, otherwise the sending fails with a 550 error) + */ + private String fromEmailAddress = "activiti@camunda.com"; + + protected CycleEmailDispatcher cycleEmailDispatcher = CycleComponentFactory.getCycleComponentInstance(CycleEmailDispatcher.class, CycleEmailDispatcher.class); + + public void onEvent(T event) { + try { + for (User user : getRecipients(event.getProcessSolution())) { + cycleEmailDispatcher.sendEmail(fromEmailAddress, user.getEmail(), getSubject(event), getMessage(event)); + } + } catch (Exception e) { + logger.log(Level.SEVERE, "Error while building email.", e); + throw new RuntimeException("Error while building email.", e); + } + } + + protected abstract String getSubject(T event); + protected abstract String getMessage(T event); + + protected List getRecipients(ProcessSolution processSolution) { + return CycleServiceFactory.getProcessSolutionService().getProcessSolutionCollaborators(processSolution.getId(), null); + } + +} diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/listener/AbstractProcessSolutionStateListener.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/listener/AbstractProcessSolutionStateListener.java new file mode 100644 index 0000000000000000000000000000000000000000..96ac900af82ed6363aa346b5e38bf7c001f26eb0 --- /dev/null +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/listener/AbstractProcessSolutionStateListener.java @@ -0,0 +1,27 @@ +package org.activiti.cycle.impl.processsolution.listener; + +import org.activiti.cycle.event.CycleCompensatingEventListener; +import org.activiti.cycle.impl.db.entity.ProcessSolutionEntity; +import org.activiti.cycle.impl.processsolution.event.ProcessSolutionStateEvent; +import org.activiti.cycle.processsolution.ProcessSolutionState; +import org.activiti.cycle.service.CycleProcessSolutionService; +import org.activiti.cycle.service.CycleServiceFactory; + +public abstract class AbstractProcessSolutionStateListener implements CycleCompensatingEventListener { + + private CycleProcessSolutionService processService = CycleServiceFactory.getProcessSolutionService(); + + public void compensateEvent(T event) { + ProcessSolutionEntity ps = (ProcessSolutionEntity) processService.getProcessSolutionById(event.getProcessSolution().getId()); + ps.setState(getCurrentState()); + processService.updateProcessSolution(ps); + } + public void onEvent(T event) { + ProcessSolutionEntity ps = (ProcessSolutionEntity) processService.getProcessSolutionById(event.getProcessSolution().getId()); + ps.setState(getNextState()); + processService.updateProcessSolution(ps); + } + protected abstract ProcessSolutionState getCurrentState(); + protected abstract ProcessSolutionState getNextState(); + +} diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/listener/ImplementationDoneEmailNotificationListener.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/listener/ImplementationDoneEmailNotificationListener.java new file mode 100644 index 0000000000000000000000000000000000000000..8b99d24949de077e23aa1d1564e7dd0652dd0cd4 --- /dev/null +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/listener/ImplementationDoneEmailNotificationListener.java @@ -0,0 +1,42 @@ +package org.activiti.cycle.impl.processsolution.listener; + +import java.io.StringWriter; + +import org.activiti.cycle.annotations.CycleComponent; +import org.activiti.cycle.context.CycleContextType; +import org.activiti.cycle.event.CycleEventListener; +import org.activiti.cycle.impl.processsolution.event.ImplementationDoneEvent; + +/** + * Listener sending an email when the implementation for a given process + * solution is completed. + * + * @author daniel.meyer@camunda.com + */ +@CycleComponent(context = CycleContextType.APPLICATION) +public class ImplementationDoneEmailNotificationListener extends AbstractProcessSolutionStateEmailListener implements + CycleEventListener { + + protected String getSubject(ImplementationDoneEvent event) { + return "Implementation done in " + event.getProcessSolution().getLabel(); + } + + protected String getMessage(ImplementationDoneEvent event) { + StringWriter writer = new StringWriter(); + writer.append("Dear collaborator in project " + event.getProcessSolution().getLabel() + "."); + writer.append("
    "); + writer.append("
    "); + writer.append("The project has completed the implementation phase and is now in testing."); + writer.append("
    "); + writer.append("
    "); + + String psConnectorId = "ps-" + event.getProcessSolution().getId(); + String psLabel = event.getProcessSolution().getLabel(); + writer.append("Go to Cycle Process Solution Homepage"); + + writer.append("
    "); + writer.append("
    "); + writer.append("With best regards from your Activiti Cycle."); + return writer.toString(); + } +} diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/listener/ImplementationDoneStateListener.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/listener/ImplementationDoneStateListener.java new file mode 100644 index 0000000000000000000000000000000000000000..28afeda7aa3d7db670d1ca479863892306279b15 --- /dev/null +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/listener/ImplementationDoneStateListener.java @@ -0,0 +1,21 @@ +package org.activiti.cycle.impl.processsolution.listener; + +import org.activiti.cycle.annotations.CycleComponent; +import org.activiti.cycle.context.CycleContextType; +import org.activiti.cycle.event.CycleCompensatingEventListener; +import org.activiti.cycle.impl.processsolution.event.ImplementationDoneEvent; +import org.activiti.cycle.processsolution.ProcessSolutionState; + +@CycleComponent(context = CycleContextType.APPLICATION) +public class ImplementationDoneStateListener extends AbstractProcessSolutionStateListener implements + CycleCompensatingEventListener { + + protected ProcessSolutionState getCurrentState() { + return ProcessSolutionState.IN_IMPLEMENTATION; + } + + protected ProcessSolutionState getNextState() { + return ProcessSolutionState.IN_TESTING; + } + +} diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/listener/ImplementationDoneUpdateOperationalProcessModels.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/listener/ImplementationDoneUpdateOperationalProcessModels.java new file mode 100644 index 0000000000000000000000000000000000000000..fc58b1aae732d1e4f39deba7b6a6fb92926306e5 --- /dev/null +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/listener/ImplementationDoneUpdateOperationalProcessModels.java @@ -0,0 +1,141 @@ +package org.activiti.cycle.impl.processsolution.listener; + +import java.io.StringWriter; +import java.text.DateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import org.activiti.cycle.Content; +import org.activiti.cycle.CycleComponentFactory; +import org.activiti.cycle.RepositoryArtifact; +import org.activiti.cycle.RepositoryArtifactLink; +import org.activiti.cycle.RepositoryConnector; +import org.activiti.cycle.RepositoryFolder; +import org.activiti.cycle.RepositoryNodeCollection; +import org.activiti.cycle.annotations.CycleComponent; +import org.activiti.cycle.context.CycleContextType; +import org.activiti.cycle.event.CycleEventListener; +import org.activiti.cycle.impl.components.CycleEmailDispatcher; +import org.activiti.cycle.impl.components.RuntimeConnectorList; +import org.activiti.cycle.impl.connector.signavio.SignavioConnectorInterface; +import org.activiti.cycle.impl.connector.signavio.provider.ActivitiCompliantBpmn20Provider; +import org.activiti.cycle.impl.db.entity.RepositoryArtifactLinkEntity; +import org.activiti.cycle.impl.processsolution.ProcessSolutionUtils; +import org.activiti.cycle.impl.processsolution.event.ImplementationDoneEvent; +import org.activiti.cycle.processsolution.ProcessSolution; +import org.activiti.cycle.processsolution.VirtualRepositoryFolder; +import org.activiti.cycle.service.CycleProcessSolutionService; +import org.activiti.cycle.service.CycleRepositoryService; +import org.activiti.cycle.service.CycleServiceFactory; +import org.activiti.engine.identity.User; + +/** + * {@link CycleEventListener} for {@link ImplementationDoneEvent}s. + * + * @author daniel.meyer@camunda.com + */ +@CycleComponent(context = CycleContextType.APPLICATION) +public class ImplementationDoneUpdateOperationalProcessModels implements CycleEventListener { + + private CycleProcessSolutionService processSolutionService = CycleServiceFactory.getProcessSolutionService(); + private CycleRepositoryService repositoryService = CycleServiceFactory.getRepositoryService(); + + public void onEvent(ImplementationDoneEvent event) { + List updatedOperationalModels = new ArrayList(); + VirtualRepositoryFolder processesFolder = getProcessSolutionUtils().getProcessesFolder(event.getProcessSolution()); + VirtualRepositoryFolder implementationFolder = getProcessSolutionUtils().getImplementationFolder(event.getProcessSolution()); + List processModels = getProcessSolutionUtils().getProcessModels(processesFolder); + for (RepositoryArtifact operationalProcessModel : processModels) { + List links = repositoryService.getArtifactLinks(operationalProcessModel.getConnectorId(), operationalProcessModel.getNodeId()); + for (RepositoryArtifactLink repositoryArtifactLink : links) { + if (!RepositoryArtifactLinkEntity.TYPE_IMPLEMENTS.equals(repositoryArtifactLink.getLinkType())) { + continue; + } + RepositoryArtifact implementedProcessModel = repositoryArtifactLink.getTargetArtifact(); + // check whether the implemented process model is last changed after the + // operational process model: + RepositoryConnector operationalProcessModelConnector = RuntimeConnectorList.getMyConnectorById(operationalProcessModel.getConnectorId()); + if (!isEeSignavio(operationalProcessModelConnector)) { + continue; + } + RepositoryConnector implementedProcessModelConnector = RuntimeConnectorList.getMyConnectorById(implementedProcessModel.getConnectorId()); + String operationalProcessModelBpmn = ActivitiCompliantBpmn20Provider.createBpmnXml(operationalProcessModelConnector, operationalProcessModel); + Content implementedProcessModelBpmnContent = implementedProcessModelConnector.getContent(implementedProcessModel.getNodeId()); + String implementedProcessModelBpmn = implementedProcessModelBpmnContent.asString(); + if (implementedProcessModelBpmn.equals(operationalProcessModelBpmn)) { + // no need to update + continue; + } + // backup operational process model: + RepositoryFolder backupFolder = null; + RepositoryNodeCollection childnodes = operationalProcessModelConnector.getChildren(operationalProcessModel.getMetadata().getParentFolderId()); + for (RepositoryFolder potentialBackupFolder : childnodes.getFolderList()) { + if ("backup".equals(potentialBackupFolder.getMetadata().getName())) { + backupFolder = potentialBackupFolder; + break; + } + } + if (backupFolder == null) { + backupFolder = operationalProcessModelConnector.createFolder(operationalProcessModel.getMetadata().getParentFolderId(), "backup"); + } + Content content = operationalProcessModelConnector.getContent(operationalProcessModel.getNodeId()); + String backupModelName = operationalProcessModel.getMetadata().getName() + "_" + DateFormat.getDateTimeInstance().format(new Date()); + operationalProcessModelConnector.createArtifact(backupFolder.getNodeId(), backupModelName, null, content); + // FIXME: limitation of the SignavioConnector: ATM we cannot update the + // content of an artifact. This is why we delete the artifact and create + // a new artifact instead. + operationalProcessModelConnector.deleteArtifact(operationalProcessModel.getNodeId()); + // create the new model: + SignavioConnectorInterface signavioConnectorInterface = (SignavioConnectorInterface) operationalProcessModelConnector; + String json = signavioConnectorInterface.transformBpmn20XmltoJson(implementedProcessModelBpmn); + Content jsoncontent = new Content(); + jsoncontent.setValue(json); + String parentfolderId = operationalProcessModel.getMetadata().getParentFolderId(); + RepositoryFolder parentFolder = operationalProcessModelConnector.getRepositoryFolder(parentfolderId); + RepositoryArtifact newArtifact = operationalProcessModelConnector.createArtifact(parentFolder.getNodeId(), operationalProcessModel.getMetadata() + .getName(), null, jsoncontent); + // delete the link to the deleted artifact + repositoryService.deleteLink(repositoryArtifactLink.getId()); + // create new link + RepositoryArtifactLinkEntity newLink = new RepositoryArtifactLinkEntity(); + newLink.setSourceArtifact(newArtifact); + newLink.setTargetArtifact(implementedProcessModel); + newLink.setLinkType(RepositoryArtifactLinkEntity.TYPE_IMPLEMENTS); + repositoryService.addArtifactLink(newLink); + updatedOperationalModels.add(newArtifact); + + } + } + if (updatedOperationalModels.size() > 0) { + sendEmailnotification(event.getProcessSolution(), updatedOperationalModels); + } + } + + private boolean isEeSignavio(RepositoryConnector connector) { + SignavioConnectorInterface connectorInterface = (SignavioConnectorInterface) connector; + return !connectorInterface.getConfiguration().getSignavioUrl().endsWith("activiti-modeler/") + && !connectorInterface.getConfiguration().getSignavioUrl().endsWith("activiti-modeler"); + } + + private void sendEmailnotification(ProcessSolution processSolution, List updatedOperationalModels) { + CycleEmailDispatcher cycleEmailDispatcher = CycleComponentFactory.getCycleComponentInstance(CycleEmailDispatcher.class); + for (User user : processSolutionService.getProcessSolutionCollaborators(processSolution.getId(), null)) { + StringWriter writer = new StringWriter(); + writer.append("Hi " + user.getFirstName() + " " + user.getLastName() + ",

    "); + writer.append("The following operational models for the process solution " + processSolution.getLabel() + " have been updated:
    "); + writer.append("

      "); + for (RepositoryArtifact updatedModel : updatedOperationalModels) { + writer.append("
    • "); + writer.append(updatedModel.getMetadata().getName()); + writer.append("
    • "); + } + writer.append("
    "); + cycleEmailDispatcher.sendEmail("activiti-cycle@localhost", user.getEmail(), "Operational models updated", writer.toString()); + } + } + + protected ProcessSolutionUtils getProcessSolutionUtils() { + return CycleComponentFactory.getCycleComponentInstance(ProcessSolutionUtils.class); + } +} diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/listener/SpecificationDoneAddHudsonJobListener.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/listener/SpecificationDoneAddHudsonJobListener.java new file mode 100644 index 0000000000000000000000000000000000000000..afa428ddd78fb5c7c191ad9a0f0788d0ad92256b --- /dev/null +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/listener/SpecificationDoneAddHudsonJobListener.java @@ -0,0 +1,80 @@ +package org.activiti.cycle.impl.processsolution.listener; + +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.activiti.cycle.RepositoryFolder; +import org.activiti.cycle.annotations.CycleComponent; +import org.activiti.cycle.context.CycleContextType; +import org.activiti.cycle.event.CycleEventListener; +import org.activiti.cycle.impl.connector.ci.hudson.action.CreateHudsonJob; +import org.activiti.cycle.impl.processsolution.event.TechnicalProjectCreatedEvent; +import org.activiti.cycle.processsolution.ProcessSolution; +import org.activiti.cycle.service.CycleServiceFactory; +import org.activiti.engine.identity.User; + +/** + * {@link CycleEventListener} adding the technical project to the Hudson CI + * server (in the first iteration) and doing nothing in subsequent iterations. + * + * EXPERIMENTAL + * + * TODO: Remove hacky implementation extension for reusing stuff of + * {@link SpecificationDoneGenerateProjectListener} + * + * @author bernd.ruecker@camunda.com + */ +@CycleComponent(context = CycleContextType.APPLICATION) +public class SpecificationDoneAddHudsonJobListener implements CycleEventListener { + + public final String METADATA_SVN_URL = "svnUrl"; + + protected static Logger log = Logger.getLogger(SpecificationDoneAddHudsonJobListener.class.getName()); + + public final String HUDSON_URL = "http://localhost:8080/hudson/"; + + public void onEvent(TechnicalProjectCreatedEvent event) { + ProcessSolution processSolution = event.getProcessSolution(); + + // it should work like this: + RepositoryFolder underlyingFolder = event.getRepositoryFolder(); + + if (isSvnFolder(underlyingFolder)) { + try { + String psConnectorId = "ps-" + processSolution.getId(); + String jobDescription = "This job was generated by Activiti-Cycle, see " + + "Cycle Process Solution Homepage"; + + new CreateHudsonJob().createCIJobForProject(HUDSON_URL, getSvnUrl(underlyingFolder), processSolution.getLabel(), jobDescription, getDeveloperEmails(processSolution)); + } + catch (Exception ex) { + log.log(Level.SEVERE, "Couldn't create Hudson job for Cycle Process Solution " + processSolution.getLabel() + ", see nested exception for details. Ignoring and proceed with specification done events.", ex); + } + } else { + log.warning("Using not supported repository for Hudson in implementation folder: " + underlyingFolder + ". Currently supported are: SVN"); + } + } + + private List getDeveloperEmails(ProcessSolution processSolution) { + ArrayList list = new ArrayList(); + + for (User user : CycleServiceFactory.getProcessSolutionService().getProcessSolutionCollaborators(processSolution.getId(), null)) { + // TODO: Select role developer + list.add(user.getEmail()); + } + + return list; + } + + private boolean isSvnFolder(RepositoryFolder underlyingFolder) { + return getSvnUrl(underlyingFolder) != null; + } + + private String getSvnUrl(RepositoryFolder underlyingFolder) { + return underlyingFolder.getMetadata().getMetadata(METADATA_SVN_URL); + } + +} diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/listener/SpecificationDoneEmailNotificationListener.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/listener/SpecificationDoneEmailNotificationListener.java new file mode 100644 index 0000000000000000000000000000000000000000..a9fcbcb084cc3f0ca0d9ca489b5ea4ce221b13cc --- /dev/null +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/listener/SpecificationDoneEmailNotificationListener.java @@ -0,0 +1,45 @@ +package org.activiti.cycle.impl.processsolution.listener; + +import java.io.StringWriter; + +import org.activiti.cycle.annotations.CycleComponent; +import org.activiti.cycle.context.CycleContextType; +import org.activiti.cycle.event.CycleEventListener; +import org.activiti.cycle.impl.processsolution.event.SpecificationDoneEvent; + +/** + * Listener sending an email when the specification for a given process solution + * is completed. + * + * @author daniel.meyer@camunda.com + */ +@CycleComponent(context = CycleContextType.APPLICATION) +public class SpecificationDoneEmailNotificationListener extends AbstractProcessSolutionStateEmailListener implements + CycleEventListener { + + protected String getSubject(SpecificationDoneEvent event) { + return "Specification done in " + event.getProcessSolution().getLabel(); + } + + protected String getMessage(SpecificationDoneEvent event) { + StringWriter writer = new StringWriter(); + writer.append("Dear collaborator in project " + event.getProcessSolution().getLabel() + "."); + writer.append("
    "); + writer.append("
    "); + writer.append("The project has completed the specification phase and is now in implementation."); + writer.append("
    "); + writer.append("
    "); + + String psConnectorId = "ps-" + event.getProcessSolution().getId(); + String psLabel = event.getProcessSolution().getLabel(); + writer.append("Go to Cycle Process Solution Homepage"); + + writer.append("
    "); + writer.append("
    "); + writer.append("A technical project was created in the folder Implementation."); + writer.append("
    "); + writer.append("
    "); + writer.append("With best regards from your Activiti Cycle."); + return writer.toString(); + } +} diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/listener/SpecificationDoneGenerateProjectListener.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/listener/SpecificationDoneGenerateProjectListener.java new file mode 100644 index 0000000000000000000000000000000000000000..e068f93800e23155ae41de3829f160fbea14601b --- /dev/null +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/listener/SpecificationDoneGenerateProjectListener.java @@ -0,0 +1,203 @@ +package org.activiti.cycle.impl.processsolution.listener; + +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.activiti.cycle.Content; +import org.activiti.cycle.CycleComponentFactory; +import org.activiti.cycle.RepositoryArtifact; +import org.activiti.cycle.RepositoryArtifactLink; +import org.activiti.cycle.RepositoryConnector; +import org.activiti.cycle.RepositoryFolder; +import org.activiti.cycle.annotations.CycleComponent; +import org.activiti.cycle.context.CycleContextType; +import org.activiti.cycle.event.CycleCompensatingEventListener; +import org.activiti.cycle.impl.components.CycleEmailDispatcher; +import org.activiti.cycle.impl.components.RuntimeConnectorList; +import org.activiti.cycle.impl.connector.signavio.action.CreateMavenProjectAction; +import org.activiti.cycle.impl.connector.signavio.provider.ActivitiCompliantBpmn20Provider; +import org.activiti.cycle.impl.db.entity.RepositoryArtifactLinkEntity; +import org.activiti.cycle.impl.processsolution.ProcessSolutionUtils; +import org.activiti.cycle.impl.processsolution.event.SpecificationDoneEvent; +import org.activiti.cycle.impl.processsolution.event.TechnicalProjectCreatedEvent; +import org.activiti.cycle.impl.processsolution.event.TechnicalProjectUpdatedEvent; +import org.activiti.cycle.processsolution.ProcessSolution; +import org.activiti.cycle.processsolution.VirtualRepositoryFolder; +import org.activiti.cycle.service.CycleEventService; +import org.activiti.cycle.service.CycleProcessSolutionService; +import org.activiti.cycle.service.CycleRepositoryService; +import org.activiti.cycle.service.CycleServiceFactory; +import org.activiti.engine.identity.User; + +/** + * {@link CycleCompensatingEventListener} generating a technical project-stub + * (in the first iteration) and updating an existing technical project in + * subsequent iterations. + * + * @author daniel.meyer@camunda.com + */ +@CycleComponent(context = CycleContextType.APPLICATION) +public class SpecificationDoneGenerateProjectListener implements CycleCompensatingEventListener { + + private CycleProcessSolutionService processSolutionService = CycleServiceFactory.getProcessSolutionService(); + private CycleRepositoryService repositoryservice = CycleServiceFactory.getRepositoryService(); + private CycleEventService eventService = CycleServiceFactory.getEventService(); + private ProcessSolutionUtils processSolutionUtils = CycleComponentFactory.getCycleComponentInstance(ProcessSolutionUtils.class); + + public void onEvent(SpecificationDoneEvent event) { + ProcessSolution processSolution = event.getProcessSolution(); + VirtualRepositoryFolder implementationFolder = processSolutionUtils.getImplementationFolder(processSolution); + if (implementationFolder == null) { + return; + } + RepositoryFolder underlyingFolder = repositoryservice + .getRepositoryFolder(implementationFolder.getConnectorId(), implementationFolder.getReferencedNodeId()); + + if (repositoryservice.getChildren(underlyingFolder.getConnectorId(), underlyingFolder.getNodeId()).asList().size() > 0) { + Map processesMappedToBpmnXml = updateProject(processSolution, underlyingFolder); + sendEmailUpdated(processSolution, processesMappedToBpmnXml); + // fire an event signifying that a new technical project has been + // updated: + eventService.fireEvent(new TechnicalProjectUpdatedEvent(processSolution, underlyingFolder)); + } else { + Map processesMappedToBpmnXml = createProject(processSolution, underlyingFolder); + sendEmailCreated(processSolution, processesMappedToBpmnXml); + // fire an event signifying that a new technical project has been + // generated: + eventService.fireEvent(new TechnicalProjectCreatedEvent(processSolution, underlyingFolder)); + } + + } + + protected Map updateProject(ProcessSolution processSolution, RepositoryFolder underlyingTechnicalFolder) { + VirtualRepositoryFolder processes = processSolutionUtils.getProcessesFolder(processSolution); + // get all processmodels in the processes folder + List processModels = processSolutionUtils.getProcessModels(processes); + return updateProcessModels(underlyingTechnicalFolder, processModels); + } + + protected Map updateProcessModels(RepositoryFolder underlyingTechnicalFolder, List processModels) { + List newModels = new ArrayList(); + Map resultMap = new HashMap(); + // update existing models: + for (RepositoryArtifact processModel : processModels) { + List links = repositoryservice.getArtifactLinks(processModel.getConnectorId(), processModel.getNodeId()); + if (links.size() == 0) { + newModels.add(processModel); + } + for (RepositoryArtifactLink link : links) { + if (!RepositoryArtifactLinkEntity.TYPE_IMPLEMENTS.equals(link.getLinkType())) { + continue; + } + RepositoryArtifact implementationArtifact = link.getTargetArtifact(); + if (!implementationArtifact.getConnectorId().equals(underlyingTechnicalFolder.getConnectorId())) { + continue; + } + if (!implementationArtifact.getNodeId().startsWith(underlyingTechnicalFolder.getNodeId())) { + continue; + } + // only update technical model if the ProcessModel is last changed after + // the implementation + Date processModelUpdated = processModel.getMetadata().getLastChanged(); + Date implementationUpdated = implementationArtifact.getMetadata().getLastChanged(); + if (processModelUpdated == null || implementationUpdated == null || processModelUpdated.after(implementationUpdated)) { + RepositoryConnector processModelConnector = RuntimeConnectorList.getMyConnectorById(processModel.getConnectorId()); + Content newContent = new Content(); + newContent.setValue(ActivitiCompliantBpmn20Provider.createBpmnXml(processModelConnector, processModel)); + repositoryservice.updateContent(implementationArtifact.getConnectorId(), implementationArtifact.getNodeId(), newContent); + resultMap.put(processModel, implementationArtifact); + } + } + } + // add new models: + RepositoryConnector targetConnector = RuntimeConnectorList.getMyConnectorById(underlyingTechnicalFolder.getConnectorId()); + // get folder for processModels: + // TODO: hard-coding this ATM: + RepositoryFolder processesFolder = targetConnector.getRepositoryFolder(underlyingTechnicalFolder.getNodeId() + "/src/main/resources/diagrams"); + for (RepositoryArtifact processModel : newModels) { + RepositoryConnector processModelConnector = RuntimeConnectorList.getMyConnectorById(processModel.getConnectorId()); + // do not create artifact if this model is contained in a "backup" folder: + RepositoryFolder parentFolder = processModelConnector.getRepositoryFolder(processModel.getMetadata().getParentFolderId()); + if ("backup".equals(parentFolder.getMetadata().getName())) { + continue; + } + Content newContent = new Content(); + newContent.setValue(ActivitiCompliantBpmn20Provider.createBpmnXml(processModelConnector, processModel)); + String artifactName = processModel.getMetadata().getName() + ".bpmn20.xml"; + RepositoryArtifact implementationArtifact = repositoryservice.createArtifact(processesFolder.getConnectorId(), processesFolder.getNodeId(), artifactName, + null, newContent); + // create link: + RepositoryArtifactLinkEntity link = new RepositoryArtifactLinkEntity(); + link.setLinkType(RepositoryArtifactLinkEntity.TYPE_IMPLEMENTS); + link.setSourceArtifact(processModel); + link.setTargetArtifact(implementationArtifact); + repositoryservice.addArtifactLink(link); + resultMap.put(processModel, implementationArtifact); + } + return resultMap; + } + protected Map createProject(ProcessSolution processSolution, RepositoryFolder underlyingTechnicalFolder) { + VirtualRepositoryFolder processes = processSolutionUtils.getProcessesFolder(processSolution); + // get all processmodels in the processes folder + List processModels = processSolutionUtils.getProcessModels(processes); + // configure parameters for CreateMavenProjectAction + CreateMavenProjectAction createMavenProjectAction = new CreateMavenProjectAction(); + RepositoryConnector targetConnector = RuntimeConnectorList.getMyConnectorById(underlyingTechnicalFolder.getConnectorId()); + String targetFolderId = underlyingTechnicalFolder.getNodeId(); + String targetName = processSolution.getLabel(); + String comment = ""; + boolean createLink = true; + // create the technical project + return createMavenProjectAction.createMavenProject(targetFolderId, targetName, comment, targetConnector, createLink, processModels); + } + + protected void sendEmailCreated(ProcessSolution processSolution, Map processesMappedToBpmnXml) { + CycleEmailDispatcher cycleEmailDispatcher = CycleComponentFactory.getCycleComponentInstance(CycleEmailDispatcher.class, CycleEmailDispatcher.class); + for (User user : processSolutionService.getProcessSolutionCollaborators(processSolution.getId(), null)) { + StringWriter writer = new StringWriter(); + writer.append("Hi " + user.getFirstName() + " " + user.getLastName() + ",

    "); + writer.append("Technical implementation models for the process solution " + processSolution.getLabel() + " have been created:
    "); + writer.append("
      "); + for (Entry processMappedToBpmnXml : processesMappedToBpmnXml.entrySet()) { + writer.append("
    • "); + writer.append("The bpmn20.xml file for the process "); + writer.append(processMappedToBpmnXml.getKey().getMetadata().getName()); + writer.append(" is located in "); + writer.append(processMappedToBpmnXml.getValue().getNodeId() + "."); + writer.append("
    • "); + } + writer.append("
    "); + cycleEmailDispatcher.sendEmail("activiti-cycle@localhost", user.getEmail(), "Technical Model created", writer.toString()); + } + } + + protected void sendEmailUpdated(ProcessSolution processSolution, Map processesMappedToBpmnXml) { + CycleEmailDispatcher cycleEmailDispatcher = CycleComponentFactory.getCycleComponentInstance(CycleEmailDispatcher.class, CycleEmailDispatcher.class); + for (User user : processSolutionService.getProcessSolutionCollaborators(processSolution.getId(), null)) { + StringWriter writer = new StringWriter(); + writer.append("Hi " + user.getFirstName() + " " + user.getLastName() + ",

    "); + writer.append("Technical implementation models for the process solution " + processSolution.getLabel() + " have been updated/created:
    "); + writer.append("
      "); + for (Entry processMappedToBpmnXml : processesMappedToBpmnXml.entrySet()) { + writer.append("
    • "); + writer.append("The bpmn20.xml file for the process "); + writer.append(processMappedToBpmnXml.getKey().getMetadata().getName()); + writer.append(" is located in "); + writer.append(processMappedToBpmnXml.getValue().getNodeId() + "."); + writer.append("
    • "); + } + writer.append("
    "); + cycleEmailDispatcher.sendEmail("activiti-cycle@localhost", user.getEmail(), "Technical Models updated/created", writer.toString()); + } + } + + public void compensateEvent(SpecificationDoneEvent event) { + // TODO: delete technical implementation project in first iteration + } + +} diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/listener/SpecificationDoneStateListener.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/listener/SpecificationDoneStateListener.java new file mode 100644 index 0000000000000000000000000000000000000000..e3c3fc5f84b87c113e5e2d93b5094278b6157a5f --- /dev/null +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/listener/SpecificationDoneStateListener.java @@ -0,0 +1,26 @@ +package org.activiti.cycle.impl.processsolution.listener; + +import org.activiti.cycle.annotations.CycleComponent; +import org.activiti.cycle.context.CycleContextType; +import org.activiti.cycle.event.CycleCompensatingEventListener; +import org.activiti.cycle.impl.processsolution.event.SpecificationDoneEvent; +import org.activiti.cycle.processsolution.ProcessSolutionState; + +/** + * Listener for {@link ProcessSolutionState} Events. + * + * @author daniel.meyer@camunda.com + */ +@CycleComponent(context = CycleContextType.APPLICATION) +public class SpecificationDoneStateListener extends AbstractProcessSolutionStateListener implements + CycleCompensatingEventListener { + + protected ProcessSolutionState getCurrentState() { + return ProcessSolutionState.IN_SPECIFICATION; + } + + protected ProcessSolutionState getNextState() { + return ProcessSolutionState.IN_IMPLEMENTATION; + } + +} diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/listener/TestingDoneEmailNotificationListener.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/listener/TestingDoneEmailNotificationListener.java new file mode 100644 index 0000000000000000000000000000000000000000..2f754365ebe290e334d2779f2b08da842e651051 --- /dev/null +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/listener/TestingDoneEmailNotificationListener.java @@ -0,0 +1,43 @@ +package org.activiti.cycle.impl.processsolution.listener; + +import java.io.StringWriter; + +import org.activiti.cycle.annotations.CycleComponent; +import org.activiti.cycle.context.CycleContextType; +import org.activiti.cycle.event.CycleEventListener; +import org.activiti.cycle.impl.processsolution.event.TestingDoneEvent; + +/** + * Listener sending an email when the implementation for a given process + * solution is completed. + * + * @author daniel.meyer@camunda.com + */ +@CycleComponent(context = CycleContextType.APPLICATION) +public class TestingDoneEmailNotificationListener extends AbstractProcessSolutionStateEmailListener implements + CycleEventListener { + + protected String getSubject(TestingDoneEvent event) { + return "Testing done in " + event.getProcessSolution().getLabel(); + } + + protected String getMessage(TestingDoneEvent event) { + StringWriter writer = new StringWriter(); + writer.append("Dear collaborator in project " + event.getProcessSolution().getLabel() + "."); + writer.append("
    "); + writer.append("
    "); + writer.append("The project has completed the testing phase and the generated product/prototype is ready for rollout/demonstration."); + writer.append("Further requirements can now be specified (the project is back in specificcation)"); + writer.append("
    "); + writer.append("
    "); + + String psConnectorId = "ps-" + event.getProcessSolution().getId(); + String psLabel = event.getProcessSolution().getLabel(); + writer.append("Go to Cycle Process Solution Homepage"); + + writer.append("
    "); + writer.append("
    "); + writer.append("With best regards from your Activiti Cycle."); + return writer.toString(); + } +} diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/listener/TestingDoneStateListener.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/listener/TestingDoneStateListener.java new file mode 100644 index 0000000000000000000000000000000000000000..2f89254ec48682680d6a495f4e1a9de0bf690d7e --- /dev/null +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/listener/TestingDoneStateListener.java @@ -0,0 +1,22 @@ +package org.activiti.cycle.impl.processsolution.listener; + +import org.activiti.cycle.annotations.CycleComponent; +import org.activiti.cycle.context.CycleContextType; +import org.activiti.cycle.event.CycleCompensatingEventListener; +import org.activiti.cycle.impl.processsolution.event.ImplementationDoneEvent; +import org.activiti.cycle.impl.processsolution.event.TestingDoneEvent; +import org.activiti.cycle.processsolution.ProcessSolutionState; + +@CycleComponent(context = CycleContextType.APPLICATION) +public class TestingDoneStateListener extends AbstractProcessSolutionStateListener implements + CycleCompensatingEventListener { + + protected ProcessSolutionState getCurrentState() { + return ProcessSolutionState.IN_TESTING; + } + + protected ProcessSolutionState getNextState() { + return ProcessSolutionState.IN_SPECIFICATION; + } + +} diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/representation/ProcessSolutionHomeContentRepresentation.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/representation/ProcessSolutionHomeContentRepresentation.java new file mode 100644 index 0000000000000000000000000000000000000000..fd7fb07762bd1143ed1d99c7338e9b0392d975ce --- /dev/null +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/processsolution/representation/ProcessSolutionHomeContentRepresentation.java @@ -0,0 +1,56 @@ +package org.activiti.cycle.impl.processsolution.representation; + +import org.activiti.cycle.Content; +import org.activiti.cycle.ContentRepresentation; +import org.activiti.cycle.CycleComponentFactory; +import org.activiti.cycle.MimeType; +import org.activiti.cycle.RenderInfo; +import org.activiti.cycle.RepositoryArtifact; +import org.activiti.cycle.RepositoryArtifactType; +import org.activiti.cycle.annotations.CycleComponent; +import org.activiti.cycle.context.CycleContextType; +import org.activiti.cycle.impl.mimetype.HtmlMimeType; +import org.activiti.cycle.impl.processsolution.artifacttype.ProcessSolutionHomeArtifactType; +import org.activiti.cycle.impl.util.IoUtils; + +/** + * Default {@link ContentRepresentation} for the "Home" screen + * + * @author daniel.meyer@camunda.com + */ +@CycleComponent(context = CycleContextType.APPLICATION) +public class ProcessSolutionHomeContentRepresentation implements ContentRepresentation { + + private static final long serialVersionUID = 1L; + + public String getId() { + return "Home"; + } + + public RenderInfo getRenderInfo() { + return RenderInfo.HTML; + } + + public Content getContent(RepositoryArtifact artifact) { + try { + String parsedForm = IoUtils.readText(getClass().getResourceAsStream("ProcessSolutionHomeContentRepresentation.html")); + Content content = new Content(); + content.setValue(parsedForm); + return content; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + public MimeType getRepresentationMimeType() { + return CycleComponentFactory.getCycleComponentInstance(HtmlMimeType.class, HtmlMimeType.class); + } + + public RepositoryArtifactType getRepositoryArtifactType() { + return CycleComponentFactory.getCycleComponentInstance(ProcessSolutionHomeArtifactType.class, ProcessSolutionHomeArtifactType.class); + } + + public boolean isForDownload() { + return false; + } + +} diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/service/CycleConfigurationServiceImpl.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/service/CycleConfigurationServiceImpl.java index a490b4dcf517ebed10deef51ddc70135b6c8fdf6..df1e41ef901adab25c13503fdba85f805dbe290c 100644 --- a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/service/CycleConfigurationServiceImpl.java +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/service/CycleConfigurationServiceImpl.java @@ -204,8 +204,10 @@ public class CycleConfigurationServiceImpl implements CycleConfigurationService CycleConfigEntity entity = cycleConfigurationDao.selectCycleConfigByGroupAndKey(groupId, key); if (entity == null) { entity = new CycleConfigEntity(); + entity.setGroupName(groupId); + entity.setKey(key); } - entity.setValue(value); + entity.setValue(value); cycleConfigurationDao.saveCycleConfig(entity); } @@ -230,4 +232,8 @@ public class CycleConfigurationServiceImpl implements CycleConfigurationService } return resultMap; } + + public CycleConfigurationDao getCycleConfigurationDao() { + return cycleConfigurationDao; + } } diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/service/CycleEventServiceImpl.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/service/CycleEventServiceImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..4c2f09486b316246b5440aedea4cd3abb63b7e9a --- /dev/null +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/service/CycleEventServiceImpl.java @@ -0,0 +1,58 @@ +package org.activiti.cycle.impl.service; + +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.activiti.cycle.CycleComponentFactory; +import org.activiti.cycle.event.CycleCompensatingEventListener; +import org.activiti.cycle.event.CycleEventListener; +import org.activiti.cycle.impl.event.CycleEvents; +import org.activiti.cycle.service.CycleEventService; + +/** + * Default implementation of the {@link CycleEventService} interface + * + * @author daniel.meyer@camunda.com + */ +public class CycleEventServiceImpl implements CycleEventService { + + Logger log = Logger.getLogger(CycleEventService.class.getName()); + + public void fireEvent(T event) { + Exception exception = null; + List> successfulEventListeners = new ArrayList>(); + CycleEvents cycleEvents = CycleComponentFactory.getCycleComponentInstance(CycleEvents.class, CycleEvents.class); + for (CycleEventListener eventListener : cycleEvents.getEventListeners((Class) event.getClass())) { + try { + eventListener.onEvent(event); + successfulEventListeners.add(eventListener); + } catch (Exception e) { + log.log(Level.SEVERE, "Error while invoking EventListener '" + eventListener.getClass().getName() + "' with event '" + event + "': " + e.getMessage(), + e); + exception = e; + break; + } + } + // try to compensate + if (exception != null) { + for (CycleEventListener cycleEventListener : successfulEventListeners) { + if (cycleEventListener instanceof CycleCompensatingEventListener) { + CycleCompensatingEventListener compensatingListener = (CycleCompensatingEventListener) cycleEventListener; + try { + compensatingListener.compensateEvent(event); + } catch (Exception e) { + log.log(Level.SEVERE, + "Error while compensating EventListener '" + compensatingListener.getClass().getName() + "' with event '" + event + "': " + e.getMessage(), + e); + // let this one pass, error during compensation + + } + } + throw new RuntimeException("Error on" + event + ": " + exception.getMessage(), exception); + } + } + } + +} diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/service/CyclePluginServiceImpl.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/service/CyclePluginServiceImpl.java index b09f00ed65b645862b41388d85d9443f488dfaf2..bc8865d1d8e41e3e42d2842403bbbd3058700d2b 100644 --- a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/service/CyclePluginServiceImpl.java +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/service/CyclePluginServiceImpl.java @@ -110,6 +110,7 @@ public class CyclePluginServiceImpl implements CyclePluginService { for (CreateUrlAction action : getCreateUrlActions(artifact)) { // TODO: Think about id RepositoryArtifactOpenLinkAction link = new RepositoryArtifactOpenLinkAction("Open " + action.getId(), action.getUrl(connector, artifact)); + link.setWarning(action.getWarning(connector, artifact)); list.add(link); } return list; diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/service/CycleProcessSolutionServiceImpl.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/service/CycleProcessSolutionServiceImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..388a4a1f5b05ac4000bb47b6934aa524b1e4fae6 --- /dev/null +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/service/CycleProcessSolutionServiceImpl.java @@ -0,0 +1,75 @@ +package org.activiti.cycle.impl.service; + +import java.util.ArrayList; +import java.util.List; + +import org.activiti.cycle.CycleComponentFactory; +import org.activiti.cycle.impl.components.RuntimeConnectorList; +import org.activiti.cycle.impl.db.CycleProcessSolutionDao; +import org.activiti.cycle.impl.db.entity.ProcessSolutionEntity; +import org.activiti.cycle.impl.processsolution.DefaultProcessSolutionTemplate; +import org.activiti.cycle.impl.processsolution.ProcessSolutionCreate; +import org.activiti.cycle.processsolution.ProcessSolution; +import org.activiti.cycle.processsolution.ProcessSolutionTemplate; +import org.activiti.cycle.processsolution.VirtualRepositoryFolder; +import org.activiti.cycle.service.CycleProcessSolutionService; +import org.activiti.engine.ProcessEngines; +import org.activiti.engine.identity.User; +import org.activiti.engine.impl.identity.UserEntity; + +/** + * Default implementation of the {@link CycleProcessSolutionService}-interface. + * + * @author daniel.meyer@camunda.com + */ +public class CycleProcessSolutionServiceImpl implements CycleProcessSolutionService { + + protected CycleProcessSolutionDao dao; + + public ProcessSolution getProcessSolutionById(String id) { + return dao.getProcessSolutionById(id); + } + + public List getProcessSolutions() { + return new ArrayList(dao.getProcessSolutionList()); + } + + public String createNewProcessSolution(String name) { + ProcessSolutionCreate processSolutionCreate = CycleComponentFactory.getCycleComponentInstance("processSolutionCreate", ProcessSolutionCreate.class); + processSolutionCreate.setName(name); + String processSolutionId = processSolutionCreate.createNewProcessSolution(); + RuntimeConnectorList runtimeConnectorList = CycleComponentFactory.getCycleComponentInstance(RuntimeConnectorList.class, RuntimeConnectorList.class); + runtimeConnectorList.discardConnectors(); + return processSolutionId; + } + + public List getFoldersForProcessSolution(String id) { + return new ArrayList(dao.getVirtualForldersByProcessSolutionId(id)); + } + + public void setDao(CycleProcessSolutionDao dao) { + this.dao = dao; + } + + public VirtualRepositoryFolder getVirtualRepositoryFolderById(String id) { + return dao.getVirtualRepositoryFolderById(id); + } + + public ProcessSolutionTemplate getDefaultProcessSolutionTemplate() { + return CycleComponentFactory.getCycleComponentInstance(DefaultProcessSolutionTemplate.class, DefaultProcessSolutionTemplate.class); + } + + public CycleProcessSolutionDao getDao() { + return dao; + } + + public ProcessSolution updateProcessSolution(ProcessSolution processSolution) { + return dao.saveProcessSolution((ProcessSolutionEntity) processSolution); + } + + public List getProcessSolutionCollaborators(String processSolutionId, String processSolutionCollaboratorRole) { + // for the moment, do nothing: + return ProcessEngines.getDefaultProcessEngine().getIdentityService().createUserQuery().list(); + } + +} diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/service/CycleRepositoryServiceImpl.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/service/CycleRepositoryServiceImpl.java index b596a4aee9e765f7beaf86910e72e20b95ac3c59..0f39298166fb366aa4f7dfa1f2933833bcc08d94 100644 --- a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/service/CycleRepositoryServiceImpl.java +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/service/CycleRepositoryServiceImpl.java @@ -129,6 +129,10 @@ public class CycleRepositoryServiceImpl implements CycleRepositoryService { return getRepositoryConnector(connectorId).createArtifact(parentFolderId, artifactName, artifactType, artifactContent); } + public RepositoryArtifact createEmptyArtifact(String connectorId, String parentFolderId, String artifactName, String artifactType) { + return getRepositoryConnector(connectorId).createEmptyArtifact(parentFolderId, artifactName, artifactType); + } + public RepositoryArtifact createArtifactFromContentRepresentation(String connectorId, String parentFolderId, String artifactName, String artifactType, String contentRepresentationName, Content artifactContent) throws RepositoryNodeNotFoundException { return getRepositoryConnector(connectorId).createArtifactFromContentRepresentation(parentFolderId, artifactName, artifactType, contentRepresentationName, @@ -163,7 +167,7 @@ public class CycleRepositoryServiceImpl implements CycleRepositoryService { public void executeParameterizedAction(String connectorId, String artifactId, String actionId, Map parameters) throws Exception { - ParameterizedAction action = cycleServiceConfiguration.getCyclePluginService().getParameterizedActionById(actionId); + ParameterizedAction action = cycleServiceConfiguration.getPluginService().getParameterizedActionById(actionId); String form = action.getFormAsHtml(); FormHandler formHandler = CycleComponentFactory.getCycleComponentInstance(FormHandler.class, FormHandler.class); formHandler.setValues(form, parameters); @@ -173,8 +177,10 @@ public class CycleRepositoryServiceImpl implements CycleRepositoryService { // TODO: (Nils Preusker, 20.10.2010), find a better way to solve this! for (String key : parameters.keySet()) { if (key.equals("targetConnectorId")) { - RepositoryConnector targetConnector = getRepositoryConnector((String) parameters.get(key)); - parameters.put(key, targetConnector); + if (parameters.get(key) != null && parameters.get(key) instanceof String) { + RepositoryConnector targetConnector = getRepositoryConnector((String) parameters.get(key)); + parameters.put(key, targetConnector); + } } } @@ -187,7 +193,7 @@ public class CycleRepositoryServiceImpl implements CycleRepositoryService { // Retrieve the action and its form String form = null; - for (ParameterizedAction action : cycleServiceConfiguration.getCyclePluginService().getParameterizedActions(artifact)) { + for (ParameterizedAction action : cycleServiceConfiguration.getPluginService().getParameterizedActions(artifact)) { if (action.getId().equals(actionId)) { form = action.getFormAsHtml(); break; diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/service/CycleServiceConfiguration.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/service/CycleServiceConfiguration.java index b6205c3ca2edb0e05505988bfad765b9412ec2ef..30241637bd3ac9255da44e36f07eccc5b28e5c5e 100644 --- a/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/service/CycleServiceConfiguration.java +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/impl/service/CycleServiceConfiguration.java @@ -2,7 +2,10 @@ package org.activiti.cycle.impl.service; import org.activiti.cycle.impl.db.impl.CycleDaoMyBatisImpl; import org.activiti.cycle.service.CycleConfigurationService; +import org.activiti.cycle.service.CycleContentService; +import org.activiti.cycle.service.CycleEventService; import org.activiti.cycle.service.CyclePluginService; +import org.activiti.cycle.service.CycleProcessSolutionService; import org.activiti.cycle.service.CycleRepositoryService; import org.activiti.cycle.service.CycleTagService; @@ -17,7 +20,7 @@ public class CycleServiceConfiguration { private CycleRepositoryServiceImpl repositoryService; private CycleTagServiceImpl tagService; - + private CycleCommentServiceImpl commentService; private CycleConfigurationServiceImpl configurationService; @@ -26,6 +29,10 @@ public class CycleServiceConfiguration { private CycleContentServiceImpl cycleContentServiceImpl; + private CycleProcessSolutionServiceImpl cycleProcessSolutionServiceImpl; + + private CycleEventServiceImpl cycleEventServiceImpl; + private CycleServiceConfiguration() { wireServices(); initializeServices(); @@ -40,6 +47,8 @@ public class CycleServiceConfiguration { commentService = new CycleCommentServiceImpl(); cyclePluginServiceImpl = new CyclePluginServiceImpl(); cycleContentServiceImpl = new CycleContentServiceImpl(); + cycleProcessSolutionServiceImpl = new CycleProcessSolutionServiceImpl(); + cycleEventServiceImpl = new CycleEventServiceImpl(); // wire-up repositoryService.setLinkDao(dao); @@ -47,10 +56,11 @@ public class CycleServiceConfiguration { configurationService.setCycleRepositoryConnectorConfigurationDao(dao); tagService.setTagDao(dao); commentService.setTagDao(dao); + cycleProcessSolutionServiceImpl.setDao(dao); repositoryService.setCycleServiceConfiguration(this); tagService.setCycleServiceConfiguration(this); configurationService.setCycleServiceConfiguration(this); - + } private void initializeServices() { @@ -61,30 +71,38 @@ public class CycleServiceConfiguration { configurationService.initialize(); } - public CycleTagService getTagService() { + public CycleTagServiceImpl getTagService() { return tagService; } public CycleCommentServiceImpl getCommentService() { return commentService; } - - public CycleRepositoryService getRepositoryService() { + + public CycleRepositoryServiceImpl getRepositoryService() { return repositoryService; } - public CycleConfigurationService getConfigurationService() { + public CycleConfigurationServiceImpl getConfigurationService() { return configurationService; } - public CyclePluginService getCyclePluginService() { + public CyclePluginServiceImpl getPluginService() { return cyclePluginServiceImpl; } - public CycleContentServiceImpl getCycleContentServiceImpl() { + public CycleContentServiceImpl getContentService() { return cycleContentServiceImpl; } + public CycleProcessSolutionServiceImpl getProcessSolutionService() { + return cycleProcessSolutionServiceImpl; + } + + public CycleEventServiceImpl getCycleEventService() { + return cycleEventServiceImpl; + } + public static CycleServiceConfiguration getInstance() { if (INSTANCE == null) { synchronized (CycleServiceConfiguration.class) { diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/processsolution/ProcessSolution.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/processsolution/ProcessSolution.java new file mode 100644 index 0000000000000000000000000000000000000000..2817e8aa49a489e95322d00768dfb005a46f0c90 --- /dev/null +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/processsolution/ProcessSolution.java @@ -0,0 +1,28 @@ +package org.activiti.cycle.processsolution; + +/** + * A {@link ProcessSolution} is a container for {@link VirtualRepositoryFolder} + * s. A {@link ProcessSolution} is in a certain {@link ProcessSolutionState}. + * + * + * @author daniel.meyer@camunda.com + */ +public interface ProcessSolution { + + /** + * The id of this {@link ProcessSolution} + */ + public String getId(); + + /** + * @return the label for this {@link ProcessSolution}. + */ + public String getLabel(); + + /** + * @return the {@link ProcessSolutionState} this {@link ProcessSolution} is + * currently in. + */ + public ProcessSolutionState getState(); + +} diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/processsolution/ProcessSolutionState.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/processsolution/ProcessSolutionState.java new file mode 100644 index 0000000000000000000000000000000000000000..92c08084eaa123261a9ef2a99c4c722e1de7953b --- /dev/null +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/processsolution/ProcessSolutionState.java @@ -0,0 +1,10 @@ +package org.activiti.cycle.processsolution; + +/** + * The state a {@link ProcessSolution} is currently in. + * + * @author daniel.meyer@camunda.com + */ +public enum ProcessSolutionState { + IN_SPECIFICATION, IN_IMPLEMENTATION, IN_TESTING, IN_OPERATIONS; +} diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/processsolution/ProcessSolutionTemplate.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/processsolution/ProcessSolutionTemplate.java new file mode 100644 index 0000000000000000000000000000000000000000..fd552b01db2baad31127829001154fff2b34de7e --- /dev/null +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/processsolution/ProcessSolutionTemplate.java @@ -0,0 +1,16 @@ +package org.activiti.cycle.processsolution; + +import java.util.List; + +/** + * A template for creating new {@link ProcessSolution}s + * + * @author daniel.meyer@camunda.com + */ +public interface ProcessSolutionTemplate { + + public List getVirtualRepositoryFolders(); + + public ProcessSolutionState getInitialState(); + +} diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/processsolution/VirtualRepositoryFolder.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/processsolution/VirtualRepositoryFolder.java new file mode 100644 index 0000000000000000000000000000000000000000..3350606229069f4332a3bc956ea290c5a8c2b277 --- /dev/null +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/processsolution/VirtualRepositoryFolder.java @@ -0,0 +1,32 @@ +package org.activiti.cycle.processsolution; + +import org.activiti.cycle.RepositoryFolder; + +/** + * A {@link VirtualRepositoryFolder} is a virtual folder, referencing a concrete + * {@link RepositoryFolder}. + * + * @author daniel.meyer@camunda.com + */ +public interface VirtualRepositoryFolder { + + /** + * @return the id of the {@link ProcessSolution}, this folder is associated + * to, as returned by {@link ProcessSolution#getId()} + */ + public String getProcessSolutionId(); + + /** + * @return the type of a repository folder. + */ + public String getType(); + + public String getReferencedNodeId(); + + public String getConnectorId(); + + public String getLabel(); + + public String getId(); + +} diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/service/CycleEventService.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/service/CycleEventService.java new file mode 100644 index 0000000000000000000000000000000000000000..d7534c0152280bdca9e6939b20f2128a191086ac --- /dev/null +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/service/CycleEventService.java @@ -0,0 +1,15 @@ +package org.activiti.cycle.service; + +/** + * Cycle event service + * + * @author daniel.meyer@camunda.com + */ +public interface CycleEventService { + + /** + * Fire a cycle event + */ + public void fireEvent(T event); + +} diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/service/CycleProcessSolutionService.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/service/CycleProcessSolutionService.java new file mode 100644 index 0000000000000000000000000000000000000000..066bcd2a87b8b977f40fb1ba1b2645e2b0cf538a --- /dev/null +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/service/CycleProcessSolutionService.java @@ -0,0 +1,67 @@ +package org.activiti.cycle.service; + +import java.util.List; + +import org.activiti.cycle.processsolution.ProcessSolution; +import org.activiti.cycle.processsolution.ProcessSolutionTemplate; +import org.activiti.cycle.processsolution.VirtualRepositoryFolder; +import org.activiti.engine.identity.User; + +/** + * Cycle-serivce for managing {@link ProcessSolution}s and + * {@link VirtualRepositoryFolder}s. + * + * @author daniel.meyer@camunda.com + */ +public interface CycleProcessSolutionService { + + /** + * @param the + * id of the {@link ProcessSolution} to be returned. + * @return the {@link ProcessSolution} with the given id. + */ + public ProcessSolution getProcessSolutionById(String id); + + /** + * + * @return a {@link List} of all available {@link ProcessSolution}s + */ + public List getProcessSolutions(); + + /** + * Create a new process solution based on the default configuration + * + * @param the + * name of the new process solution + * @return the id of the new processSolution + */ + public String createNewProcessSolution(String name); + + /** + * @param the + * id of the {@link ProcessSolution} to return the list of + * {@link VirtualRepositoryFolder}s for. + * + * @return a {@link List} of {@link VirtualRepositoryFolder}s for the given + * {@link ProcessSolution}. + */ + public List getFoldersForProcessSolution(String id); + + /** + * @return the {@link VirtualRepositoryFolder} corresponding to the provided + * id. + */ + public VirtualRepositoryFolder getVirtualRepositoryFolderById(String id); + + /** + * @return the default {@link ProcessSolutionTemplate}. + */ + public ProcessSolutionTemplate getDefaultProcessSolutionTemplate(); + + /** + * Update a {@link ProcessSolution} + */ + public ProcessSolution updateProcessSolution(ProcessSolution processSolution); + + public List getProcessSolutionCollaborators(String processSolutionId, String processSolutionCollaboratorRole); +} diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/service/CycleRepositoryService.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/service/CycleRepositoryService.java index 36b943fc55a30a5d7ee7ee0649556523dbcf20a7..f03560b1a1f81122421eec8d7591bc79d9816a3c 100644 --- a/modules/activiti-cycle/src/main/java/org/activiti/cycle/service/CycleRepositoryService.java +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/service/CycleRepositoryService.java @@ -76,9 +76,9 @@ public interface CycleRepositoryService { public Content getContent(String connectorId, String artifactId) throws RepositoryNodeNotFoundException; public List getIncomingArtifactLinks(String targetConnectorId, String targetArtifactId); - + public RepositoryNode getRepositoryNode(String connectorId, String nodeId); - + /** * load the {@link RepositoryArtifact} including details */ @@ -93,12 +93,13 @@ public interface CycleRepositoryService { public RepositoryFolder getRepositoryFolder(String connectorId, String folderId) throws RepositoryNodeNotFoundException; -// /** -// * return the list of supported {@link ArtifactType}s of this -// * {@link RepositoryConnector} for the given folder. Most conenctors doesn't -// * make any difference between the folders, but some may do. -// */ -// public List getSupportedArtifactTypes(String connectorId, String folderId); + // /** + // * return the list of supported {@link ArtifactType}s of this + // * {@link RepositoryConnector} for the given folder. Most conenctors doesn't + // * make any difference between the folders, but some may do. + // */ + // public List getSupportedArtifactTypes(String connectorId, + // String folderId); /** * update artifact content with default {@link ContentRepresentation} @@ -132,6 +133,9 @@ public interface CycleRepositoryService { public String getActionFormTemplate(String connectorId, String artifactId, String actionId); - + /** + * Create a new (empty) artifact as a childNode of the provided parentFodlerId + */ + public RepositoryArtifact createEmptyArtifact(String connectorId, String parentFolderId, String artifactName, String artifactType); } diff --git a/modules/activiti-cycle/src/main/java/org/activiti/cycle/service/CycleServiceFactory.java b/modules/activiti-cycle/src/main/java/org/activiti/cycle/service/CycleServiceFactory.java index 99470fbd1e6dda18e63d67160111e5ef8008ce8e..bc12e34e77648347230920e31e864ff9e58f18bb 100644 --- a/modules/activiti-cycle/src/main/java/org/activiti/cycle/service/CycleServiceFactory.java +++ b/modules/activiti-cycle/src/main/java/org/activiti/cycle/service/CycleServiceFactory.java @@ -19,6 +19,7 @@ import org.activiti.cycle.impl.service.CycleServiceConfiguration; * {@link CycleRepositoryService}, the {@link CycleTagService} and the * {@link CycleConfigurationService}. * + * @author Daniel Meyer */ public class CycleServiceFactory { @@ -35,15 +36,23 @@ public class CycleServiceFactory { } public static CyclePluginService getCyclePluginService() { - return CycleServiceConfiguration.getInstance().getCyclePluginService(); + return CycleServiceConfiguration.getInstance().getPluginService(); } public static CycleContentService getContentService() { - return CycleServiceConfiguration.getInstance().getCycleContentServiceImpl(); + return CycleServiceConfiguration.getInstance().getContentService(); } - + public static CycleCommentService getCommentService() { return CycleServiceConfiguration.getInstance().getCommentService(); } + public static CycleProcessSolutionService getProcessSolutionService() { + return CycleServiceConfiguration.getInstance().getProcessSolutionService(); + } + + public static CycleEventService getEventService() { + return CycleServiceConfiguration.getInstance().getCycleEventService(); + } + } diff --git a/modules/activiti-cycle/src/main/resources/org/activiti/cycle/impl/connector/ci/hudson/action/template.config.xml b/modules/activiti-cycle/src/main/resources/org/activiti/cycle/impl/connector/ci/hudson/action/template.config.xml new file mode 100644 index 0000000000000000000000000000000000000000..a0d0daab5976c9498e9ed9e146a28db2c100342e --- /dev/null +++ b/modules/activiti-cycle/src/main/resources/org/activiti/cycle/impl/connector/ci/hudson/action/template.config.xml @@ -0,0 +1,50 @@ + + + + @JOB_DESCRIPTION@ + false + + + + + @SVN_LOCATION@ + . + + + true + true + + + + + + + true + false + false + false + + + @hourly + + + false + clean install + true + false + false + false + false + false + false + -1 + + + @EMAIL_ADDRESSES@ + false + true + + + + + diff --git a/modules/activiti-cycle/src/main/resources/org/activiti/cycle/impl/connector/signavio/emptyProcessModelTemplate.json b/modules/activiti-cycle/src/main/resources/org/activiti/cycle/impl/connector/signavio/emptyProcessModelTemplate.json new file mode 100644 index 0000000000000000000000000000000000000000..9e0a624957762f3d3554047f3b5432cc1fbd76fb --- /dev/null +++ b/modules/activiti-cycle/src/main/resources/org/activiti/cycle/impl/connector/signavio/emptyProcessModelTemplate.json @@ -0,0 +1,39 @@ +{ + "bounds": { + "lowerRight": { + "y": 0, + "x": 0 + }, + "upperLeft": { + "y": 0, + "x": 0 + } + }, + "resourceId": "canvas", + "childShapes": [], + "properties": { + "monitoring": "", + "modificationdate": "", + "namespaces": "", + "creationdate": "", + "version": "", + "author": "", + "targetnamespace": "http://www.signavio.com/bpmn20", + "name": "", + "documentation": "", + "language": "English", + "auditing": "", + "expressionlanguage": "http://www.w3.org/1999/XPath", + "typelanguage": "http://www.w3.org/2001/XMLSchema" + }, + "stencilset": { + "url": "/activiti-modeler/editor/stencilsets//bpmn2.0/bpmn2.0.json", + "namespace": "http://b3mn.org/stencilset/bpmn2.0#" + }, + "ssextensions": [ + + ], + "stencil": { + "id": "BPMNDiagram" + } +} \ No newline at end of file diff --git a/modules/activiti-cycle/src/main/resources/org/activiti/cycle/impl/processsolution/LinkRequirementWithProcessDiagramAction.html b/modules/activiti-cycle/src/main/resources/org/activiti/cycle/impl/processsolution/LinkRequirementWithProcessDiagramAction.html new file mode 100644 index 0000000000000000000000000000000000000000..9fc3c33164dc7914cd6ba23ae74dfe5a0d844488 --- /dev/null +++ b/modules/activiti-cycle/src/main/resources/org/activiti/cycle/impl/processsolution/LinkRequirementWithProcessDiagramAction.html @@ -0,0 +1,20 @@ +
    +

    Link Requirement to Process Diagram

    + + + + + + +
    +
    +
    +
    \ No newline at end of file diff --git a/modules/activiti-cycle/src/main/resources/org/activiti/cycle/impl/processsolution/representation/ProcessSolutionHomeContentRepresentation.html b/modules/activiti-cycle/src/main/resources/org/activiti/cycle/impl/processsolution/representation/ProcessSolutionHomeContentRepresentation.html new file mode 100644 index 0000000000000000000000000000000000000000..02d6aa3ad45990e0689841e7a9cf9889354b8d7f --- /dev/null +++ b/modules/activiti-cycle/src/main/resources/org/activiti/cycle/impl/processsolution/representation/ProcessSolutionHomeContentRepresentation.html @@ -0,0 +1,3 @@ +
    +Process Solution Home +
    \ No newline at end of file diff --git a/modules/activiti-cycle/src/main/resources/org/activiti/db/cycle/ibatis/activiti.ibatis.mem.conf.xml b/modules/activiti-cycle/src/main/resources/org/activiti/db/cycle/ibatis/activiti.ibatis.mem.conf.xml index e937b19e9d467e9a88704b378adf718a6d41aba8..0db1eee8416ec2d7cec9fc53a55a4cd62fa2ce17 100644 --- a/modules/activiti-cycle/src/main/resources/org/activiti/db/cycle/ibatis/activiti.ibatis.mem.conf.xml +++ b/modules/activiti-cycle/src/main/resources/org/activiti/db/cycle/ibatis/activiti.ibatis.mem.conf.xml @@ -13,5 +13,7 @@ + + diff --git a/modules/activiti-cycle/src/main/resources/org/activiti/db/cycle/ibatis/processSolution.mapping.xml b/modules/activiti-cycle/src/main/resources/org/activiti/db/cycle/ibatis/processSolution.mapping.xml new file mode 100644 index 0000000000000000000000000000000000000000..256bf80f26114927476860cfa0fdee9ea5164234 --- /dev/null +++ b/modules/activiti-cycle/src/main/resources/org/activiti/db/cycle/ibatis/processSolution.mapping.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + insert into ACT_CY_PROCESS_SOLUTION (ID_, LABEL_, STATE_) + values ( + #{id ,jdbcType=VARCHAR}, + #{label, jdbcType=VARCHAR}, + #{stateName, jdbcType=VARCHAR} + ) + + + + delete from ACT_CY_PROCESS_SOLUTION where ID_ = #{id} + + + + update ACT_CY_PROCESS_SOLUTION set + LABEL_ = #{label}, + STATE_ = #{stateName} + where ID_ = #{id} + + + diff --git a/modules/activiti-cycle/src/main/resources/org/activiti/db/cycle/ibatis/virtualRepositoryFolder.mapping.xml b/modules/activiti-cycle/src/main/resources/org/activiti/db/cycle/ibatis/virtualRepositoryFolder.mapping.xml new file mode 100644 index 0000000000000000000000000000000000000000..2aaae36f7695ab847a321fe22e42201e48fe984a --- /dev/null +++ b/modules/activiti-cycle/src/main/resources/org/activiti/db/cycle/ibatis/virtualRepositoryFolder.mapping.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + insert into ACT_CY_V_FOLDER (ID_, LABEL_, CONNECTOR_ID_, PROCESS_SOLUTION_ID_, REFERENCED_NODE_ID_, TYPE_) + values ( + #{id ,jdbcType=VARCHAR}, + #{label, jdbcType=VARCHAR}, + #{connectorId, jdbcType=VARCHAR}, + #{processSolutionId, jdbcType=VARCHAR}, + #{referencedNodeId, jdbcType=VARCHAR}, + #{type, jdbcType=VARCHAR} + ) + + + + delete from ACT_CY_V_FOLDER where ID_ = #{id} + + + + update ACT_CY_V_FOLDER set + LABEL_ = #{label}, + CONNECTOR_ID_= #{connectorId}, + PROCESS_SOLUTION_ID_ = #{processSolutionId}, + REFERENCED_NODE_ID_ = #{referencedNodeId}, + TYPE_ = #{type} + where ID_ = #{id} + + + diff --git a/modules/activiti-cycle/src/test/java/org/activiti/cycle/impl/CycleTestUtils.java b/modules/activiti-cycle/src/test/java/org/activiti/cycle/impl/CycleTestUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..c7a3a71fa34e046669fd428be0e68073f4f85b15 --- /dev/null +++ b/modules/activiti-cycle/src/test/java/org/activiti/cycle/impl/CycleTestUtils.java @@ -0,0 +1,53 @@ +package org.activiti.cycle.impl; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.StringWriter; +import java.nio.charset.Charset; + +/** + * @author daniel.meyer@camunda.com + */ +public class CycleTestUtils { + + public static String loadResourceAsString(String resourceName, Class clazz) { + BufferedReader reader = null; + try { + InputStream is = clazz.getResourceAsStream(resourceName); + reader = new BufferedReader(new InputStreamReader(is, Charset.forName("UTF-8"))); + StringWriter resultWriter = new StringWriter(); + String line; + while ((line = reader.readLine()) != null) { + resultWriter.append(line + "\n"); + } + reader.close(); + return resultWriter.toString(); + } catch (IOException e) { + if (reader == null) + return null; + try { + reader.close(); + } catch (IOException ex) { + + } + return null; + } + } + + public static boolean deleteFileRec(File path) { + if (path.exists() && path.isAbsolute()) { + File[] files = path.listFiles(); + for (int i = 0; i < files.length; i++) { + if (files[i].isDirectory()) { + deleteFileRec(files[i]); + } else { + files[i].delete(); + } + } + } + return path.delete(); + } +} diff --git a/modules/activiti-cycle/src/test/java/org/activiti/cycle/impl/db/impl/CycleDaoMyBatisImplTest.java b/modules/activiti-cycle/src/test/java/org/activiti/cycle/impl/db/impl/CycleDaoMyBatisImplTest.java index e9e7f6514362cdc24941e68830140bfb7a4965d0..fbf922a0a401dd7b8e20c8b01b51abbf71ed25a7 100644 --- a/modules/activiti-cycle/src/test/java/org/activiti/cycle/impl/db/impl/CycleDaoMyBatisImplTest.java +++ b/modules/activiti-cycle/src/test/java/org/activiti/cycle/impl/db/impl/CycleDaoMyBatisImplTest.java @@ -1,23 +1,28 @@ package org.activiti.cycle.impl.db.impl; +import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.UUID; +import junit.framework.Assert; + import org.activiti.cycle.impl.ActivitiCycleDbAwareTest; import org.activiti.cycle.impl.CycleTagContentImpl; import org.activiti.cycle.impl.db.entity.CycleConfigEntity; +import org.activiti.cycle.impl.db.entity.ProcessSolutionEntity; import org.activiti.cycle.impl.db.entity.RepositoryArtifactLinkEntity; import org.activiti.cycle.impl.db.entity.RepositoryNodeCommentEntity; import org.activiti.cycle.impl.db.entity.RepositoryNodePeopleLinkEntity; import org.activiti.cycle.impl.db.entity.RepositoryNodeTagEntity; - +import org.activiti.cycle.impl.db.entity.VirtualRepositoryFolderEntity; +import org.activiti.cycle.processsolution.ProcessSolutionState; public class CycleDaoMyBatisImplTest extends ActivitiCycleDbAwareTest { - + private CycleDaoMyBatisImpl dao = new CycleDaoMyBatisImpl(); - public void testArtifactLinks() throws Throwable { + public void testArtifactLinks() throws Throwable { RepositoryArtifactLinkEntity link = new RepositoryArtifactLinkEntity(); link.setSourceConnectorId("connector1"); link.setSourceArtifactId("artifact1"); @@ -26,9 +31,9 @@ public class CycleDaoMyBatisImplTest extends ActivitiCycleDbAwareTest { link.setLinkType("TEST"); link.setComment("Created in test case"); link.setLinkedBothWays(true); - + dao.insertArtifactLink(link); - + List links1 = dao.getOutgoingArtifactLinks("connector1", "artifact1"); assertEquals(1, links1.size()); assertEquals("Created in test case", links1.get(0).getComment()); @@ -45,13 +50,13 @@ public class CycleDaoMyBatisImplTest extends ActivitiCycleDbAwareTest { assertEquals(1, links4.size()); assertEquals("Created in test case", links4.get(0).getComment()); assertEquals(links1.get(0).getId(), links4.get(0).getId()); - + dao.deleteArtifactLink(links1.get(0).getId()); List links5 = dao.getIncomingArtifactLinks("connector2", "artifact2"); assertEquals(0, links5.size()); } - + public void testPeopleLink() { RepositoryNodePeopleLinkEntity link = new RepositoryNodePeopleLinkEntity(); link.setSourceConnectorId("connector1"); @@ -79,15 +84,15 @@ public class CycleDaoMyBatisImplTest extends ActivitiCycleDbAwareTest { public void testTag() { RepositoryNodeTagEntity tag = new RepositoryNodeTagEntity("name1", "connector1", "artifact1"); dao.insertTag(tag); - + List tags1 = dao.getTagsForNode("connector1", "artifact1"); assertEquals(1, tags1.size()); assertEquals("name1", tags1.get(0).getName()); assertNotNull(tags1.get(0).getId()); - + List tags2 = dao.getTagsForNode("connector2", "artifact2"); assertEquals(0, tags2.size()); - + CycleTagContentImpl tagContent1 = dao.getTagContent("name1"); assertEquals("name1", tagContent1.getName()); assertEquals(1, tagContent1.getUsageCount()); @@ -112,7 +117,7 @@ public class CycleDaoMyBatisImplTest extends ActivitiCycleDbAwareTest { dao.deleteTag("connector1", "artifact2", "name2"); List tags3 = dao.getTagsForNode("connector1", "artifact1"); - assertEquals(0, tags3.size()); + assertEquals(0, tags3.size()); // List similiarTagNames = dao.getSimiliarTagNames("ame"); // assertEquals(2, similiarTagNames.size()); @@ -158,7 +163,7 @@ public class CycleDaoMyBatisImplTest extends ActivitiCycleDbAwareTest { List tags3 = dao.getTagsForNode("connector1", "artifact1"); assertEquals(0, tags3.size()); } - + public void testComment() { RepositoryNodeCommentEntity comment = new RepositoryNodeCommentEntity(); String id = UUID.randomUUID().toString(); @@ -174,37 +179,94 @@ public class CycleDaoMyBatisImplTest extends ActivitiCycleDbAwareTest { dao.deleteComment(id); assertEquals(0, dao.getCommentsForNode("testConnectorId", "testNodeId").size()); } - + public void testSelectCycleConfigGroups() { CycleConfigEntity ce1 = new CycleConfigEntity(); - ce1.setGroupName("g1"); + ce1.setGroupName("g1"); ce1.setKey("key1"); ce1.setValue("value1"); - + CycleConfigEntity ce2 = new CycleConfigEntity(); - ce2.setGroupName("g2"); + ce2.setGroupName("g2"); ce2.setKey("key2"); ce2.setValue("value2"); - + CycleConfigEntity ce3 = new CycleConfigEntity(); - ce3.setGroupName("g1"); // g1 + ce3.setGroupName("g1"); // g1 ce3.setKey("key3"); ce3.setValue("value3"); - + dao.saveCycleConfig(ce1); dao.saveCycleConfig(ce2); dao.saveCycleConfig(ce3); - + List groups = dao.selectCycleConfigurationGroups(); - + assertEquals(2, groups.size()); - + for (String group : groups) { - for (CycleConfigEntity entity : dao.selectCycleConfigByGroup(group)) { - dao.deleteCycleConfigurationEntry(entity.getId()); - } - } - + for (CycleConfigEntity entity : dao.selectCycleConfigByGroup(group)) { + dao.deleteCycleConfigurationEntry(entity.getId()); + } + } + } + + public void testCreateDeleteProcessSolution() { + ProcessSolutionEntity ps = new ProcessSolutionEntity(); + ps.setLabel("My Process Solution"); + ps.setState(ProcessSolutionState.IN_SPECIFICATION); + + Assert.assertNull(ps.getId()); + ps = dao.saveProcessSolution(ps); + Assert.assertNotNull(ps.getId()); + ProcessSolutionEntity ps2 = dao.getProcessSolutionById(ps.getId()); + Assert.assertEquals(ps.getId(), ps2.getId()); + Assert.assertEquals(ps.getLabel(), ps2.getLabel()); + Assert.assertEquals(ps.getState(), ps2.getState()); + + assertTrue(dao.getProcessSolutionList().size() == 1); + + dao.deleteProcessSolutionById(ps.getId()); + Assert.assertNull(dao.getProcessSolutionById(ps.getId())); + } + + public void testCreateDeleteRepositoryFolder() { + VirtualRepositoryFolderEntity vf1 = new VirtualRepositoryFolderEntity(); + vf1.setConnectorId("vfs"); + vf1.setLabel("Management"); + vf1.setReferencedNodeId("/ps1/Management"); + vf1.setType("Management"); + VirtualRepositoryFolderEntity vf2 = new VirtualRepositoryFolderEntity(); + vf2.setConnectorId("vfs"); + vf2.setLabel("Requirements"); + vf2.setReferencedNodeId("/ps1/Requirements"); + vf2.setType("Requirements"); + + ProcessSolutionEntity ps = new ProcessSolutionEntity(); + ps.setLabel("My Process Solution"); + ps.setState(ProcessSolutionState.IN_SPECIFICATION); + ps = dao.saveProcessSolution(ps); + + List folders = new ArrayList(); + folders.add(vf1); + folders.add(vf2); + + folders = dao.addVirtualFoldersToSolution(ps.getId(), folders); + for (VirtualRepositoryFolderEntity virtualRepositoryFolderEntity : folders) { + VirtualRepositoryFolderEntity storedFolder = dao.getVirtualRepositoryFolderById(virtualRepositoryFolderEntity.getId()); + assertNotNull(storedFolder); + assertEquals(virtualRepositoryFolderEntity.getConnectorId(), storedFolder.getConnectorId()); + assertEquals(virtualRepositoryFolderEntity.getLabel(), storedFolder.getLabel()); + assertEquals(virtualRepositoryFolderEntity.getReferencedNodeId(), storedFolder.getReferencedNodeId()); + assertEquals(virtualRepositoryFolderEntity.getType(), storedFolder.getType()); + assertEquals(virtualRepositoryFolderEntity.getProcessSolutionId(), storedFolder.getProcessSolutionId()); + dao.deleteVirtualRepositoryFolderById(storedFolder.getId()); + } + + assertTrue(dao.getVirtualForldersByProcessSolutionId(ps.getId()).size() == 0); + + dao.deleteProcessSolutionById(ps.getId()); + } } diff --git a/modules/activiti-cycle/src/test/java/org/activiti/cycle/impl/event/EventsTest.java b/modules/activiti-cycle/src/test/java/org/activiti/cycle/impl/event/EventsTest.java new file mode 100644 index 0000000000000000000000000000000000000000..bedd138c959da2a558c08a8ff4e5356ead30f316 --- /dev/null +++ b/modules/activiti-cycle/src/test/java/org/activiti/cycle/impl/event/EventsTest.java @@ -0,0 +1,21 @@ +package org.activiti.cycle.impl.event; + +import java.util.Set; + +import org.activiti.cycle.CycleComponentFactory; +import org.activiti.cycle.event.CycleEventListener; +import org.activiti.cycle.impl.ActivitiCycleDbAwareTest; + +/** + * Tests the {@link CycleEvents}-Component + * + * @author daniel.meyer@camunda.com + */ +public class EventsTest extends ActivitiCycleDbAwareTest { + + public void testInitialization() { + CycleEvents events = CycleComponentFactory.getCycleComponentInstance(CycleEvents.class, CycleEvents.class); + Set> listeners = events.getEventListeners(TestEvent.class); + assertTrue(listeners.size() == 1); + } +} diff --git a/modules/activiti-cycle/src/test/java/org/activiti/cycle/impl/event/TestEvent.java b/modules/activiti-cycle/src/test/java/org/activiti/cycle/impl/event/TestEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..ce1c9dbf26f4eb7cdd11d37e4b0f2330145eef1c --- /dev/null +++ b/modules/activiti-cycle/src/test/java/org/activiti/cycle/impl/event/TestEvent.java @@ -0,0 +1,15 @@ +package org.activiti.cycle.impl.event; + +public class TestEvent { + + private String payload; + + public void setPayload(String payload) { + this.payload = payload; + } + + public String getPayload() { + return payload; + } + +} diff --git a/modules/activiti-cycle/src/test/java/org/activiti/cycle/impl/event/TestEventListener.java b/modules/activiti-cycle/src/test/java/org/activiti/cycle/impl/event/TestEventListener.java new file mode 100644 index 0000000000000000000000000000000000000000..9170cdb8bea6325b41359f7d2b775b3fc76e84e2 --- /dev/null +++ b/modules/activiti-cycle/src/test/java/org/activiti/cycle/impl/event/TestEventListener.java @@ -0,0 +1,15 @@ +package org.activiti.cycle.impl.event; + +import org.activiti.cycle.annotations.CycleComponent; +import org.activiti.cycle.context.CycleContextType; +import org.activiti.cycle.event.CycleEventListener; + +@CycleComponent(context = CycleContextType.NONE) +public class TestEventListener implements CycleEventListener { + + static boolean IS_INVOKED = false; + + public void onEvent(TestEvent event) { + IS_INVOKED = true; + } +} diff --git a/modules/activiti-cycle/src/test/java/org/activiti/cycle/impl/processsolution/CreateNewProcessSolutionTest.java b/modules/activiti-cycle/src/test/java/org/activiti/cycle/impl/processsolution/CreateNewProcessSolutionTest.java new file mode 100644 index 0000000000000000000000000000000000000000..aee45f5318d6f22bcb9f500785ea78b55b8b0c62 --- /dev/null +++ b/modules/activiti-cycle/src/test/java/org/activiti/cycle/impl/processsolution/CreateNewProcessSolutionTest.java @@ -0,0 +1,63 @@ +package org.activiti.cycle.impl.processsolution; + +import java.io.File; +import java.util.UUID; + +import org.activiti.cycle.CycleComponentFactory; +import org.activiti.cycle.impl.ActivitiCycleDbAwareTest; +import org.activiti.cycle.impl.CycleTestUtils; +import org.activiti.cycle.impl.components.RuntimeConnectorList; +import org.activiti.cycle.impl.connector.fs.FileSystemConnector; +import org.activiti.cycle.impl.db.entity.CycleConfigEntity; +import org.activiti.cycle.impl.service.CycleConfigurationServiceImpl; +import org.activiti.cycle.impl.service.CycleProcessSolutionServiceImpl; +import org.activiti.cycle.processsolution.ProcessSolution; +import org.activiti.cycle.processsolution.VirtualRepositoryFolder; +import org.activiti.cycle.service.CycleServiceFactory; + +public class CreateNewProcessSolutionTest extends ActivitiCycleDbAwareTest { + + public void testCreateNewProcessSolutionTest() { + // store default configuration: + CycleConfigurationServiceImpl configurationService = (CycleConfigurationServiceImpl) CycleServiceFactory.getConfigurationService(); + // store defaultConfiguration + configurationService.setConfigurationValue("processSolutionTemplates", "default", + CycleTestUtils.loadResourceAsString("GetDefaultProcessSolutionTemplateTest.xml", this.getClass())); + + CycleProcessSolutionServiceImpl processSolutionServiceImpl = (CycleProcessSolutionServiceImpl) CycleServiceFactory.getProcessSolutionService(); + + RuntimeConnectorList runtimeConnectorList = CycleComponentFactory.getCycleComponentInstance(RuntimeConnectorList.class, RuntimeConnectorList.class); + FileSystemConnector connector = new FileSystemConnector(); + connector.setName("Workspace"); + connector.setId("Workspace"); + connector.startConfiguration(); + File tmpDir = null; + try { + File tempFile = File.createTempFile("connectorTest", ""); + tmpDir = new File(tempFile.getParentFile().getAbsoluteFile() + File.separator + UUID.randomUUID().toString()); + tmpDir.mkdir(); + + connector.addConfigurationEntry(FileSystemConnector.CONFIG_KEY_BASE_PATH, tmpDir.getAbsolutePath()); + connector.configurationFinished(); + runtimeConnectorList.registerConnector(connector); + + processSolutionServiceImpl.createNewProcessSolution("test process solution"); + } catch (Exception e) { + e.printStackTrace(); + } finally { + CycleTestUtils.deleteFileRec(tmpDir); + } + + for (ProcessSolution solution : processSolutionServiceImpl.getProcessSolutions()) { + for (VirtualRepositoryFolder virtualFolderEntity : processSolutionServiceImpl.getFoldersForProcessSolution(solution.getId())) { + processSolutionServiceImpl.getDao().deleteVirtualRepositoryFolderById(virtualFolderEntity.getId()); + } + processSolutionServiceImpl.getDao().deleteProcessSolutionById(solution.getId()); + } + + // cleanup + CycleConfigEntity e = configurationService.getCycleConfigurationDao().selectCycleConfigByGroupAndKey("processSolutionTemplates", "default"); + configurationService.getCycleConfigurationDao().deleteCycleConfigurationEntry(e.getId()); + } + +} diff --git a/modules/activiti-cycle/src/test/java/org/activiti/cycle/impl/processsolution/GetDefaultProcessSolutionTemplateTest.java b/modules/activiti-cycle/src/test/java/org/activiti/cycle/impl/processsolution/GetDefaultProcessSolutionTemplateTest.java new file mode 100644 index 0000000000000000000000000000000000000000..8321e20b8f7a80855ed68c3b46d9ccb1e40b51a1 --- /dev/null +++ b/modules/activiti-cycle/src/test/java/org/activiti/cycle/impl/processsolution/GetDefaultProcessSolutionTemplateTest.java @@ -0,0 +1,43 @@ +package org.activiti.cycle.impl.processsolution; + +import org.activiti.cycle.impl.ActivitiCycleDbAwareTest; +import org.activiti.cycle.impl.CycleTestUtils; +import org.activiti.cycle.impl.db.entity.CycleConfigEntity; +import org.activiti.cycle.impl.service.CycleConfigurationServiceImpl; +import org.activiti.cycle.processsolution.ProcessSolutionTemplate; +import org.activiti.cycle.processsolution.VirtualRepositoryFolder; +import org.activiti.cycle.service.CycleProcessSolutionService; +import org.activiti.cycle.service.CycleServiceFactory; + +/** + * Test the + * {@link CycleProcessSolutionService#getDefaultProcessSolutionTemplate()}- + * method + * + * @author daniel.meyer@camunda.com + */ +public class GetDefaultProcessSolutionTemplateTest extends ActivitiCycleDbAwareTest { + + public void testGetDefaultProcessSolutionTemplateTest() throws Exception { + CycleConfigurationServiceImpl configurationService = (CycleConfigurationServiceImpl) CycleServiceFactory.getConfigurationService(); + // store defaultConfiguration + configurationService.setConfigurationValue("processSolutionTemplates", "default", + CycleTestUtils.loadResourceAsString("GetDefaultProcessSolutionTemplateTest.xml", this.getClass())); + + CycleProcessSolutionService processSolutionService = CycleServiceFactory.getProcessSolutionService(); + ProcessSolutionTemplate defaultTemplate = processSolutionService.getDefaultProcessSolutionTemplate(); + + assertEquals(4, defaultTemplate.getVirtualRepositoryFolders().size()); + VirtualRepositoryFolder f1 = defaultTemplate.getVirtualRepositoryFolders().get(0); + + assertEquals(f1.getLabel(), "Management"); + assertEquals(f1.getType(), "Management"); + assertEquals(f1.getConnectorId(), "Workspace"); + assertEquals(f1.getReferencedNodeId(), "/"); + + // cleanup + CycleConfigEntity e = configurationService.getCycleConfigurationDao().selectCycleConfigByGroupAndKey("processSolutionTemplates", "default"); + configurationService.getCycleConfigurationDao().deleteCycleConfigurationEntry(e.getId()); + } + +} diff --git a/modules/activiti-cycle/src/test/resources/activiti.cfg.xml b/modules/activiti-cycle/src/test/resources/activiti.cfg.xml index 975f9d67108c93d29dcf583bde28d3573a4be527..95731464b92fa050e4549e5465359c1b81f766a1 100644 --- a/modules/activiti-cycle/src/test/resources/activiti.cfg.xml +++ b/modules/activiti-cycle/src/test/resources/activiti.cfg.xml @@ -12,7 +12,7 @@ - + diff --git a/modules/activiti-cycle/src/test/resources/org/activiti/cycle/impl/processsolution/GetDefaultProcessSolutionTemplateTest.xml b/modules/activiti-cycle/src/test/resources/org/activiti/cycle/impl/processsolution/GetDefaultProcessSolutionTemplateTest.xml new file mode 100644 index 0000000000000000000000000000000000000000..96a4183c7f161f4489ec0ce3bcbe70ae66a9c5c9 --- /dev/null +++ b/modules/activiti-cycle/src/test/resources/org/activiti/cycle/impl/processsolution/GetDefaultProcessSolutionTemplateTest.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file