From ec844db7e21e1d18aebfeebc6611212ff195b037 Mon Sep 17 00:00:00 2001 From: Ralph Soika Date: Mon, 6 Jan 2020 14:24:35 +0100 Subject: [PATCH] formatting and license --- imixs-code-style-v14.xml | 591 ++- imixs-code-style.xml | 591 ++- .../main/java/org/imixs/workflow/Adapter.java | 31 +- .../org/imixs/workflow/BPMNRuleEngine.java | 136 +- .../java/org/imixs/workflow/FileData.java | 222 +- .../org/imixs/workflow/GenericAdapter.java | 18 +- .../org/imixs/workflow/ItemCollection.java | 3409 +++++++++-------- .../workflow/ItemCollectionComparator.java | 158 +- .../main/java/org/imixs/workflow/Model.java | 143 +- .../java/org/imixs/workflow/ModelManager.java | 91 +- .../main/java/org/imixs/workflow/Plugin.java | 70 +- .../org/imixs/workflow/PluginDependency.java | 32 +- .../java/org/imixs/workflow/RuleEngine.java | 703 ++-- .../org/imixs/workflow/SignalAdapter.java | 21 +- .../org/imixs/workflow/WorkflowContext.java | 45 +- .../org/imixs/workflow/WorkflowKernel.java | 2089 +++++----- .../org/imixs/workflow/WorkflowManager.java | 115 +- .../org/imixs/workflow/bpmn/BPMNModel.java | 729 ++-- .../imixs/workflow/bpmn/BPMNModelHandler.java | 3303 ++++++++-------- .../org/imixs/workflow/bpmn/BPMNParser.java | 145 +- .../exceptions/AccessDeniedException.java | 23 +- .../workflow/exceptions/AdapterException.java | 50 +- .../exceptions/ImixsExceptionHandler.java | 122 +- .../workflow/exceptions/IndexException.java | 95 +- .../exceptions/InvalidAccessException.java | 105 +- .../workflow/exceptions/ModelException.java | 37 +- .../workflow/exceptions/PluginException.java | 59 +- .../exceptions/ProcessingErrorException.java | 51 +- .../workflow/exceptions/QueryException.java | 27 +- .../exceptions/WorkflowException.java | 83 +- .../services/rest/BasicAuthenticator.java | 55 +- .../services/rest/FormAuthenticator.java | 137 +- .../services/rest/JWTAuthenticator.java | 37 +- .../workflow/services/rest/RequestFilter.java | 13 +- .../services/rest/RestAPIException.java | 81 +- .../workflow/services/rest/RestClient.java | 720 ++-- .../java/org/imixs/workflow/util/Base64.java | 412 +- .../imixs/workflow/util/ImixsJSONBuilder.java | 317 +- .../imixs/workflow/util/ImixsJSONParser.java | 499 +-- .../org/imixs/workflow/util/JSONParser.java | 714 ++-- .../org/imixs/workflow/util/XMLParser.java | 586 +-- .../org/imixs/workflow/xml/DocumentTable.java | 117 +- .../java/org/imixs/workflow/xml/XMLCount.java | 17 +- .../imixs/workflow/xml/XMLDataCollection.java | 37 +- .../xml/XMLDataCollectionAdapter.java | 381 +- .../org/imixs/workflow/xml/XMLDocument.java | 53 +- .../workflow/xml/XMLDocumentAdapter.java | 499 ++- .../java/org/imixs/workflow/xml/XMLItem.java | 523 +-- .../imixs/workflow/xml/XMLItemComparator.java | 23 +- .../org/imixs/workflow/xml/XSLHandler.java | 222 +- .../org/imixs/workflow/xml/package-info.java | 10 +- .../imixs/workflow/engine/DocumentEvent.java | 46 +- .../workflow/engine/DocumentService.java | 2135 ++++++----- .../workflow/engine/EventLogService.java | 372 +- .../workflow/engine/HealthCheckService.java | 147 +- .../workflow/engine/ImixsConfigSource.java | 232 +- .../imixs/workflow/engine/MetricService.java | 301 +- .../imixs/workflow/engine/ModelService.java | 843 ++-- .../workflow/engine/ProcessingEvent.java | 44 +- .../imixs/workflow/engine/ReportService.java | 1341 +++---- .../org/imixs/workflow/engine/SetupEvent.java | 23 +- .../imixs/workflow/engine/SetupService.java | 595 ++- .../workflow/engine/SimulationService.java | 311 +- .../org/imixs/workflow/engine/TextEvent.java | 77 +- .../workflow/engine/TextForEachAdapter.java | 163 +- .../workflow/engine/TextItemValueAdapter.java | 495 +-- .../engine/TextPropertyValueAdapter.java | 301 +- .../imixs/workflow/engine/UserGroupEvent.java | 53 +- .../workflow/engine/WorkflowScheduler.java | 902 ++--- .../engine/WorkflowSchedulerController.java | 48 +- .../workflow/engine/WorkflowService.java | 2054 +++++----- .../engine/adapters/AccessAdapter.java | 534 +-- .../engine/adminp/AdminPException.java | 27 +- .../workflow/engine/adminp/AdminPService.java | 572 +-- .../workflow/engine/adminp/JobHandler.java | 64 +- .../engine/adminp/JobHandlerRebuildIndex.java | 386 +- .../engine/adminp/JobHandlerRenameUser.java | 397 +- .../adminp/JobHandlerUpgradeWorkitems.java | 382 +- .../engine/index/DefaultOperator.java | 13 +- .../workflow/engine/index/SchemaService.java | 697 ++-- .../workflow/engine/index/SearchService.java | 111 +- .../workflow/engine/index/SortOrder.java | 117 +- .../workflow/engine/index/UpdateService.java | 82 +- .../imixs/workflow/engine/jpa/Document.java | 342 +- .../imixs/workflow/engine/jpa/EventLog.java | 372 +- .../engine/plugins/AbstractPlugin.java | 227 +- .../workflow/engine/plugins/AccessPlugin.java | 16 +- .../engine/plugins/AnalysisPlugin.java | 417 +- .../engine/plugins/ApplicationPlugin.java | 179 +- .../engine/plugins/ApproverPlugin.java | 294 +- .../plugins/DocumentComposerPlugin.java | 260 +- .../engine/plugins/HistoryPlugin.java | 342 +- .../engine/plugins/IntervalPlugin.java | 176 +- .../workflow/engine/plugins/LogPlugin.java | 57 +- .../workflow/engine/plugins/MailPlugin.java | 1179 +++--- .../workflow/engine/plugins/OwnerPlugin.java | 248 +- .../workflow/engine/plugins/ReportPlugin.java | 245 +- .../workflow/engine/plugins/ResultPlugin.java | 40 +- .../workflow/engine/plugins/RulePlugin.java | 432 ++- .../engine/plugins/SplitAndJoinPlugin.java | 892 ++--- .../engine/plugins/VersionPlugin.java | 373 +- .../workflow/engine/scheduler/Scheduler.java | 84 +- .../SchedulerConfigurationService.java | 51 +- .../engine/scheduler/SchedulerController.java | 233 +- .../engine/scheduler/SchedulerException.java | 55 +- .../engine/scheduler/SchedulerService.java | 992 +++-- src/site/markdown/contributing.md | 8 +- 107 files changed, 20045 insertions(+), 20081 deletions(-) diff --git a/imixs-code-style-v14.xml b/imixs-code-style-v14.xml index 9dde9eaa8..fcc0a71ee 100644 --- a/imixs-code-style-v14.xml +++ b/imixs-code-style-v14.xml @@ -2,337 +2,322 @@ - - - - - - - - - - - - - - - - - - - - - + + + + + + - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + + + + + + + + + + + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + + + + + + + - - - - - - + + + + + + + + + + + + + - - - - - - - + - + + + + + - - - - - - - - - - - - - - - - - - - - - + + - - - - - - - - - - - - - - - - - + + + - - - - - - - + + + + + + + + + + + + - - - - - - - - - - - - - - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + - - - - - - - - - - + + + - - - - - + + + + + + + - - - - - - - - - - - - - - - + + + + + + - - + + + + + + + + + + + + + + - - - - - - - - - + + + + + + + + + + - - - - - + + + + + + + + + + - - - - - + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + + - - - - - - - - - - - + + + + + + + + + + - - - - - - - - + - - - - - - - + + + + + + + + + + + + + + diff --git a/imixs-code-style.xml b/imixs-code-style.xml index 9dde9eaa8..fcc0a71ee 100644 --- a/imixs-code-style.xml +++ b/imixs-code-style.xml @@ -2,337 +2,322 @@ - - - - - - - - - - - - - - - - - - - - - + + + + + + - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + + + + + + + + + + + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + + + + + + + - - - - - - + + + + + + + + + + + + + - - - - - - - + - + + + + + - - - - - - - - - - - - - - - - - - - - - + + - - - - - - - - - - - - - - - - - + + + - - - - - - - + + + + + + + + + + + + - - - - - - - - - - - - - - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + - - - - - - - - - - + + + - - - - - + + + + + + + - - - - - - - - - - - - - - - + + + + + + - - + + + + + + + + + + + + + + - - - - - - - - - + + + + + + + + + + - - - - - + + + + + + + + + + - - - - - + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + + - - - - - - - - - - - + + + + + + + + + + - - - - - - - - + - - - - - - - + + + + + + + + + + + + + + diff --git a/imixs-workflow-core/src/main/java/org/imixs/workflow/Adapter.java b/imixs-workflow-core/src/main/java/org/imixs/workflow/Adapter.java index 21ac05cac..70c93ff30 100644 --- a/imixs-workflow-core/src/main/java/org/imixs/workflow/Adapter.java +++ b/imixs-workflow-core/src/main/java/org/imixs/workflow/Adapter.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *
- *  Imixs Workflow 
+/*  
+ *  Imixs-Workflow 
+ *  
  *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
  *  http://www.imixs.com
  *  
@@ -22,18 +22,17 @@
  *      https://github.com/imixs/imixs-workflow
  *  
  *  Contributors:  
- *      Imixs Software Solutions GmbH - initial API and implementation
+ *      Imixs Software Solutions GmbH - Project Management
  *      Ralph Soika - Software Developer
- * 
- *******************************************************************************/ + */ package org.imixs.workflow; import org.imixs.workflow.exceptions.AdapterException; /** - * An Adapter defines an adapter pattern used by the WorkflowKernel to call adapter implementations - * defined by the BPMN model. + * An Adapter defines an adapter pattern used by the WorkflowKernel to call + * adapter implementations defined by the BPMN model. *

* * @@ -44,14 +43,12 @@ public abstract interface Adapter { - - /** - * @param document the workitem to be processed - * @param event the workflow event containing the processing instructions - * @return updated workitem for further processing - * @throws AdapterException - */ - public ItemCollection execute(ItemCollection document, ItemCollection event) - throws AdapterException; + /** + * @param document the workitem to be processed + * @param event the workflow event containing the processing instructions + * @return updated workitem for further processing + * @throws AdapterException + */ + public ItemCollection execute(ItemCollection document, ItemCollection event) throws AdapterException; } diff --git a/imixs-workflow-core/src/main/java/org/imixs/workflow/BPMNRuleEngine.java b/imixs-workflow-core/src/main/java/org/imixs/workflow/BPMNRuleEngine.java index b5840913e..783bc3df8 100644 --- a/imixs-workflow-core/src/main/java/org/imixs/workflow/BPMNRuleEngine.java +++ b/imixs-workflow-core/src/main/java/org/imixs/workflow/BPMNRuleEngine.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *

- *  Imixs Workflow 
+/*  
+ *  Imixs-Workflow 
+ *  
  *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
  *  http://www.imixs.com
  *  
@@ -22,10 +22,9 @@
  *      https://github.com/imixs/imixs-workflow
  *  
  *  Contributors:  
- *      Imixs Software Solutions GmbH - initial API and implementation
+ *      Imixs Software Solutions GmbH - Project Management
  *      Ralph Soika - Software Developer
- * 
- *******************************************************************************/ + */ package org.imixs.workflow; @@ -33,8 +32,8 @@ import org.imixs.workflow.exceptions.PluginException; /** - * The BPMN Rule Engine can be used to evaluate a business Rule based on a BPMN model with - * conditional events. + * The BPMN Rule Engine can be used to evaluate a business Rule based on a BPMN + * model with conditional events. *

* The rules are evaluated as a chain of conditional events * @@ -43,73 +42,74 @@ */ public class BPMNRuleEngine { - private Model model = null; - - public BPMNRuleEngine(Model model) { - super(); - this.model = model; - } - - /** - * Evaluates a BPMN Business Rule based on the data provided by a workitem. - * - * @param workitem - * @return evaluated task id - * @throws ModelException - */ - public int eval(ItemCollection workitem) throws ModelException { - // setup the workflow rule context - RuleContext ruleContext = new RuleContext(); - WorkflowKernel workflowKernel = new WorkflowKernel(ruleContext); - ruleContext.getModelManager().addModel(model); - int result; - try { - result = workflowKernel.eval(workitem); - } catch (PluginException e) { - throw new ModelException(e.getErrorCode(), e.getMessage(), e); - } - - return result; - } - - /** - * Helper Class to mock a workflow kernel - * - * @author rsoika - * - */ - class RuleContext implements WorkflowContext, ModelManager { - private Model model = null; - @Override - public Object getSessionContext() { - return null; + public BPMNRuleEngine(Model model) { + super(); + this.model = model; } - @Override - public ModelManager getModelManager() { - return this; + /** + * Evaluates a BPMN Business Rule based on the data provided by a workitem. + * + * @param workitem + * @return evaluated task id + * @throws ModelException + */ + public int eval(ItemCollection workitem) throws ModelException { + // setup the workflow rule context + RuleContext ruleContext = new RuleContext(); + WorkflowKernel workflowKernel = new WorkflowKernel(ruleContext); + ruleContext.getModelManager().addModel(model); + int result; + try { + result = workflowKernel.eval(workitem); + } catch (PluginException e) { + throw new ModelException(e.getErrorCode(), e.getMessage(), e); + } + + return result; } - @Override - public Model getModel(String version) throws ModelException { - return model; - } + /** + * Helper Class to mock a workflow kernel + * + * @author rsoika + * + */ + class RuleContext implements WorkflowContext, ModelManager { + + private Model model = null; + + @Override + public Object getSessionContext() { + return null; + } + + @Override + public ModelManager getModelManager() { + return this; + } + + @Override + public Model getModel(String version) throws ModelException { + return model; + } + + @Override + public void addModel(Model model) throws ModelException { + this.model = model; + } + + @Override + public void removeModel(String version) { + } + + @Override + public Model getModelByWorkitem(ItemCollection workitem) throws ModelException { + return model; + } - @Override - public void addModel(Model model) throws ModelException { - this.model = model; } - @Override - public void removeModel(String version) {} - - @Override - public Model getModelByWorkitem(ItemCollection workitem) throws ModelException { - return model; - } - - } - } diff --git a/imixs-workflow-core/src/main/java/org/imixs/workflow/FileData.java b/imixs-workflow-core/src/main/java/org/imixs/workflow/FileData.java index 7e60d7160..1497296eb 100644 --- a/imixs-workflow-core/src/main/java/org/imixs/workflow/FileData.java +++ b/imixs-workflow-core/src/main/java/org/imixs/workflow/FileData.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *

- *  Imixs Workflow 
+/*  
+ *  Imixs-Workflow 
+ *  
  *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
  *  http://www.imixs.com
  *  
@@ -22,10 +22,9 @@
  *      https://github.com/imixs/imixs-workflow
  *  
  *  Contributors:  
- *      Imixs Software Solutions GmbH - initial API and implementation
+ *      Imixs Software Solutions GmbH - Project Management
  *      Ralph Soika - Software Developer
- * 
- *******************************************************************************/ + */ package org.imixs.workflow; @@ -39,121 +38,120 @@ /** * Helper class to abstract the file content stored in a ItemCollection. *

- * A FileData object contains at least the attributes 'name', 'content' and 'contentType'. The - * optional object custom attributes can be added. It represents a {@code Map>} + * A FileData object contains at least the attributes 'name', 'content' and + * 'contentType'. The optional object custom attributes can be added. It + * represents a {@code Map>} * * @see ItemCollection.addFile * @author rsoika * @version 2.0 */ public class FileData { - private String name; - private byte[] content; - private String contentType; - private Map> attributes; - - public static final String DEFAULT_CONTENT_TYPE = "application/unknown"; - - public FileData(String name, byte[] content, String contentType, - Map> attributes) { - super(); - this.content = content; - this.setName(name); - this.setContentType(contentType); - this.setAttributes(attributes); - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public byte[] getContent() { - return content; - } - - public void setContent(byte[] content) { - this.content = content; - } - - public String getContentType() { - return contentType; - } - - public void setContentType(String contentType) { - // validate contentType... - if (contentType == null || "".equals(contentType)) { - contentType = DEFAULT_CONTENT_TYPE; + private String name; + private byte[] content; + private String contentType; + private Map> attributes; + + public static final String DEFAULT_CONTENT_TYPE = "application/unknown"; + + public FileData(String name, byte[] content, String contentType, Map> attributes) { + super(); + this.content = content; + this.setName(name); + this.setContentType(contentType); + this.setAttributes(attributes); + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public byte[] getContent() { + return content; + } + + public void setContent(byte[] content) { + this.content = content; + } + + public String getContentType() { + return contentType; + } + + public void setContentType(String contentType) { + // validate contentType... + if (contentType == null || "".equals(contentType)) { + contentType = DEFAULT_CONTENT_TYPE; + } + this.contentType = contentType; } - this.contentType = contentType; - } - public Map> getAttributes() { - if (attributes == null) { - attributes = new LinkedHashMap>(); + public Map> getAttributes() { + if (attributes == null) { + attributes = new LinkedHashMap>(); + } + return attributes; } - return attributes; - } - - public void setAttributes(Map> attributes) { - this.attributes = attributes; - } - - /** - * Returns the value of the named custom attribute as an Object, or null if no attribute of the - * given name exists. A custom attribute can be set by the method setAttribute(). - * - * @param name a String specifying the name of the custom attribute - * - * @return: an Object containing the value of the attribute, or null if the attribute does not - * exist - **/ - public Object getAttribute(String name) { - if (attributes != null) { - return attributes.get(name); - } else { - return null; + + public void setAttributes(Map> attributes) { + this.attributes = attributes; + } + + /** + * Returns the value of the named custom attribute as an Object, or null if no + * attribute of the given name exists. A custom attribute can be set by the + * method setAttribute(). + * + * @param name a String specifying the name of the custom attribute + * + * @return: an Object containing the value of the attribute, or null if the + * attribute does not exist + **/ + public Object getAttribute(String name) { + if (attributes != null) { + return attributes.get(name); + } else { + return null; + } } - } - - /** - * Set a custom attribute value. - * - * @param name a String specifying the name of the custom attribute - * @param values an Object containing the value of the attribute - */ - public void setAttribute(String name, List values) { - if (attributes == null) { - attributes = new LinkedHashMap>(); + + /** + * Set a custom attribute value. + * + * @param name a String specifying the name of the custom attribute + * @param values an Object containing the value of the attribute + */ + public void setAttribute(String name, List values) { + if (attributes == null) { + attributes = new LinkedHashMap>(); + } + attributes.put(name, values); + } + + /** + * Generates a MD5 from a current file content + * + * @param b + * @return + * @throws NoSuchAlgorithmException + */ + public String generateMD5() throws NoSuchAlgorithmException { + byte[] hash_bytes = MessageDigest.getInstance("MD5").digest(content); + return DatatypeConverter.printHexBinary(hash_bytes); + } + + /** + * Validates a given MD5 checksum + * + * @return true if equal + * @throws NoSuchAlgorithmException + */ + public boolean validateMD5(String checksum) throws NoSuchAlgorithmException { + String testChecksum = generateMD5(); + return (testChecksum.equals(checksum)); } - attributes.put(name, values); - } - - - /** - * Generates a MD5 from a current file content - * - * @param b - * @return - * @throws NoSuchAlgorithmException - */ - public String generateMD5() throws NoSuchAlgorithmException { - byte[] hash_bytes = MessageDigest.getInstance("MD5").digest(content); - return DatatypeConverter.printHexBinary(hash_bytes); - } - - - /** - * Validates a given MD5 checksum - * - * @return true if equal - * @throws NoSuchAlgorithmException - */ - public boolean validateMD5(String checksum) throws NoSuchAlgorithmException { - String testChecksum = generateMD5(); - return (testChecksum.equals(checksum)); - } } diff --git a/imixs-workflow-core/src/main/java/org/imixs/workflow/GenericAdapter.java b/imixs-workflow-core/src/main/java/org/imixs/workflow/GenericAdapter.java index 85b6d7df9..b4d643fb3 100644 --- a/imixs-workflow-core/src/main/java/org/imixs/workflow/GenericAdapter.java +++ b/imixs-workflow-core/src/main/java/org/imixs/workflow/GenericAdapter.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *
- *  Imixs Workflow 
+/*  
+ *  Imixs-Workflow 
+ *  
  *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
  *  http://www.imixs.com
  *  
@@ -22,17 +22,17 @@
  *      https://github.com/imixs/imixs-workflow
  *  
  *  Contributors:  
- *      Imixs Software Solutions GmbH - initial API and implementation
+ *      Imixs Software Solutions GmbH - Project Management
  *      Ralph Soika - Software Developer
- * 
- *******************************************************************************/ + */ package org.imixs.workflow; /** - * A GenericAdapter extends the Adapter Interface. This Adapter is independent from the BPMN Model - * and should not be associated with a BPMN Signal Event. A GenericAdapter is called by the - * WorkfklowKernel during the processing life-cycle before the plugin life-cycle. + * A GenericAdapter extends the Adapter Interface. This Adapter is independent + * from the BPMN Model and should not be associated with a BPMN Signal Event. A + * GenericAdapter is called by the WorkfklowKernel during the processing + * life-cycle before the plugin life-cycle. *

* A GenericAdapter can be a CDI implementation. *

diff --git a/imixs-workflow-core/src/main/java/org/imixs/workflow/ItemCollection.java b/imixs-workflow-core/src/main/java/org/imixs/workflow/ItemCollection.java index 3df9f0c4f..f86544834 100644 --- a/imixs-workflow-core/src/main/java/org/imixs/workflow/ItemCollection.java +++ b/imixs-workflow-core/src/main/java/org/imixs/workflow/ItemCollection.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *

- *  Imixs Workflow 
+/*
+ *  Imixs-Workflow 
+ *  
  *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
  *  http://www.imixs.com
  *  
@@ -22,10 +22,9 @@
  *      https://github.com/imixs/imixs-workflow
  *  
  *  Contributors:  
- *      Imixs Software Solutions GmbH - initial API and implementation
+ *      Imixs Software Solutions GmbH - Project Management
  *      Ralph Soika - Software Developer
- * 
- *******************************************************************************/ + */ package org.imixs.workflow; @@ -60,19 +59,22 @@ import org.imixs.workflow.xml.XMLItem; /** - * This Class defines a ValueObject to be used to exchange data structures used by the - * org.imixs.workflow Framework. Most components of this framework use this wrapper class to easy - * transport workflow data between the different workflow modules. ValueObjects, particular in J2EE - * Applications, have the advantage to improve performance of remote method calls. The Imixs + * This Class defines a ValueObject to be used to exchange data structures used + * by the org.imixs.workflow Framework. Most components of this framework use + * this wrapper class to easy transport workflow data between the different + * workflow modules. ValueObjects, particular in J2EE Applications, have the + * advantage to improve performance of remote method calls. The Imixs * ItemCcollection enables a very flexibly and easy to use data structure. - * - * A ItemCollection contains various Items (attributes). Every Item exist of a Name (String) and a - * list of values (List of Object). Internal every Value is stored inside a Vector Class. All values - * are stored internally in a Map containing key values pairs. - * - * NOTE: An ItemCollection is not serializable and can not be stored into another ItemCollection. To - * serialize a ItemCollection use the XMLItemCollection. @see XMLItemCollectionAdapter. - * + *

+ * A ItemCollection contains various Items (attributes). Every Item exist of a + * Name (String) and a list of values (List of Object). Internal every Value is + * stored inside a Vector Class. All values are stored internally in a Map + * containing key values pairs. + *

+ * NOTE: An ItemCollection is not serializable and can not be stored into + * another ItemCollection. To serialize a ItemCollection use the + * XMLItemCollection. @see XMLItemCollectionAdapter. + *

* * @author Ralph Soika * @version 2.1 @@ -80,1723 +82,1768 @@ */ public class ItemCollection implements Cloneable { - // NOTE: ItemCollection is not serializable - - private static Logger logger = Logger.getLogger(ItemCollection.class.getName()); - - private Map> hash = new Hashtable>(); - - /** - * Creates a new empty ItemCollection - * - */ - public ItemCollection() { - super(); - } - - /** - * Creates a new ItemCollection and makes a deep copy from a given value Map - * - * @param map - with item values - */ - public ItemCollection(Map> map) { - super(); - this.replaceAllItems(map); - } - - /** - * Creates a new ItemCollection and makes a deep copy from a given ItemCollection - * - * @param itemCol - ItemCollection with values - */ - public ItemCollection(ItemCollection itemCol) { - super(); - this.replaceAllItems(itemCol.hash); - } - - /** - * Creates a new ItemCollection by a reference to a given value Map. This method does not make a - * deep copy of the given map and sets the value map by reference. This method can be used in - * cases where values are only read. In all other cases, the constructor method - * 'ItemCollection(Map> map)' should be used. - * - * @param map - reference with item values - */ - public static ItemCollection createByReference(final Map> map) { - ItemCollection reference = new ItemCollection(); - if (map != null) { - reference.hash = map; - } - return reference; - } - - /** - * This method clones the current ItemCollection. The method makes a deep copy of the current - * instance. - */ - @Override - public Object clone() { - ItemCollection clone = new ItemCollection(this); - return clone; - } - - /** - * This method clones the current ItemCollection with a subset of items. The method makes a deep - * copy of the current instance and removes items not defined by the list of itemNames. - * - * @param itemNames - list of properties to be copied into the clone - * @return new ItemCollection - */ - @SuppressWarnings("unchecked") - public ItemCollection clone(final List itemNames) { - ItemCollection clone = (ItemCollection) this.clone(); - // remove all undefined items - if (itemNames != null && itemNames.size() > 0) { - Iterator it = hash.entrySet().iterator(); - while (it.hasNext()) { - Map.Entry> entry = (Map.Entry>) it.next(); - if (!itemNames.contains(entry.getKey())) { - clone.removeItem(entry.getKey()); - } - } - } - return clone; - } - - /** - * This method compares the values of two item collections by comparing the hash maps. This did - * not garantie that also embedded arrays are equal. - */ - public boolean equals(Object o) { - if (!(o instanceof ItemCollection)) - return false; - return hash.equals(((ItemCollection) o).hash); - } - - /** - * Set the value of an item. If the ItemCollection does not contain an item with the specified - * name, the method creates a new item and adds it to the ItemCollection. The ItemName is not case - * sensitive. Use hasItem to verify the existence of an item. All item names will be lower cased. - *

- * Each item can contain a list of values (multivalue item). If a single value is provided the - * method creates a List with one single value (singlevalue item). - *

- * If the value is null the method will remove the item. This is equal to the method call - * removeItem() - *

- * If the ItemValue is not serializable the item will be removed. - *

- * - * @param itemName The name of the item or items you want to replace. - * @param itemValue The value of the new item. The data type of the item depends upon the data - * type of value, and does not need to match the data type of the old item. - */ - - public ItemCollection setItemValue(String itemName, Object itemValue) { - setItemValue(itemName, itemValue, false); - return this; - } - - /** - * Appends a value to an existing item. If the ItemCollection does not contain an item with the - * specified name, the method creates a new item and adds it to the ItemCollection. The ItemName - * is not case sensitive. Use hasItem to verify the existence of an item. All item names will be - * lower cased. - * - * If a value list is provided the method appends each single value. - * - * If the value is null the method will remove the item. This is equal to the method call - * removeItem() - * - * If the ItemValue is not serializable the item will be removed. - * - * - * @param itemName The name of the item or items you want to replace. - * @param itemValue The value of the new item. The data type of the item depends upon the data - * type of value, and does not need to match the data type of the old item. - */ - public ItemCollection appendItemValue(String itemName, Object itemValue) { - setItemValue(itemName, itemValue, true); - return this; - } - - /** - * Returns the value list for the specified Item. The returned list is untyped and the values - * contained in the list are not converted to a specific type. The values have the same object - * type as set by calling the method setItemValue(String itemName, Object itemValue). - * To get a typed value list, see the method - * getItemValue(String itemName, Class itemType) . - * - *

- * If the item does not exist or has no values, the method returns an empty List. - *

- * The ItemName is not case sensitive. Use hasItem to verify the existence of an item. - * - * @param itemName The name of an item. - * @return an untyped list of values contained by the item. - * - */ - @SuppressWarnings("rawtypes") - public List getItemValue(String itemName) { - if (itemName == null) { - return null; - } - itemName = itemName.toLowerCase().trim(); - List o = hash.get(itemName); - if (o == null) - return new ArrayList<>(); - else { - // remove null values - o.removeAll(Collections.singleton(null)); - return o; - } - } - - /** - * Returns the resolved item value of the specified type. The method converts the value to the - * specified type if possible, otherwise the method returns null. If the item has multiple values, - * this method returns the first value. - *

- * If the item isn't present in the itemCollection the method returns null. - *

- * If the specified type is int, float, long, double, Integer, Float, Long or Double, the method - * returns 0 instead of null - *

- * If the item contains no value with the specified type, the method returns null. The ItemName is - * not case sensitive. Use hasItem to verify the existence of an item. - * - * @param itemName The item Name. - * @param itemType The type into which the resolve item value should get converted - * @return the resolved item value as an object of the requested type. - */ - @SuppressWarnings("unchecked") - public T getItemValue(String itemName, Class itemType) { - List values = getItemValue(itemName); - if (values == null || values.size() == 0) { - - // test for Integer - if (itemType == Integer.class || itemType == int.class) { - return (T) new Integer(0); - } - // test for Float - if (itemType == Float.class || itemType == float.class) { - return (T) new Float(0); - } - - // test for Long - if (itemType == Long.class || itemType == long.class) { - return (T) new Long(0); - } - - // test for Double - if (itemType == Double.class || itemType == double.class) { - return (T) new Double(0); - } - - return null; - } - // find first value of specified type - return convertValue(values.get(0), itemType); - } - - /** - * Returns the resolved list of item values of the specified type. The method converts the values - * of the list to the specified type if possible. - *

- * If the item isn't present in the itemCollection the method returns an empty list. - *

- * The ItemName is not case sensitive. Use hasItem to verify the existence of an item. - * - * @param itemName The item Name. - * @param itemType The type into which the resolved item values should get converted - * @return the resolved list of item values of the requested type. - */ - - @SuppressWarnings("unchecked") - public List getItemValueList(String itemName, Class itemType) { - List values = getItemValue(itemName); - // convert values... - values.replaceAll(s -> convertValue(s, itemType)); - return (List) values; - // @see details here: - // https://stackoverflow.com/questions/51937821/how-to-define-a-generic-list-of-types-in-java?noredirect=1#comment90825856_51937821 - } - - /** - * removes a attribute from the item collection - * - * @param name - */ - public void removeItem(String name) { - if (name != null) { - name = name.toLowerCase().trim(); - this.hash.remove(name); - } - } - - /** - * Indicates whether an item exists in the document. - * - * @param aName The name of an item. - * @return true if an item with name exists in the document, false if no item with name exists in - * the document - * - */ - public boolean hasItem(String aName) { - if (aName == null) { - return false; - } - aName = aName.toLowerCase().trim(); - return (hash.get(aName) != null); - } - - /** - * Returns true if the value of an item with a single numeric value is from type 'Integer' - * - * @param aName - * @return boolean true if object is from type Double - * - */ - public boolean isItemValueInteger(String aName) { - List v = getItemValue(aName); - if (v.size() == 0) - return false; - else { - // test for object type... - Object o = v.get(0); - return (o instanceof Integer); - } - } - - /** - * Returns true if the value of an item with a single numeric value is from type 'Long' - * - * @param aName - * @return boolean true if object is from type Double - * - */ - public boolean isItemValueLong(String aName) { - List v = getItemValue(aName); - if (v.size() == 0) - return false; - else { - // test for object type... - Object o = v.get(0); - return (o instanceof Long); - } - } - - /** - * Returns true if the value of an item with a single numeric value is from type 'Double' - * - * @param aName - * @return boolean true if object is from type Double - * - */ - public boolean isItemValueDouble(String aName) { - List v = getItemValue(aName); - if (v.size() == 0) - return false; - else { - // test for object type... - Object o = v.get(0); - return (o instanceof Double); - } - } - - /** - * Returns true if the value of an item with a single numeric value is from type 'Float' - * - * @param aName - * @return boolean true if object is from type Double - * - */ - public boolean isItemValueFloat(String aName) { - List v = getItemValue(aName); - if (v.size() == 0) - return false; - else { - // test for object type... - Object o = v.get(0); - return (o instanceof Float); - } - } - - /** - * Returns true if the value of an item is from type 'Date' - * - * @param aName - * @return boolean true if object is from type Double - * - */ - public boolean isItemValueDate(String aName) { - List v = getItemValue(aName); - if (v.size() == 0) - return false; - else { - // test for object type... - Object o = v.get(0); - return (o instanceof Date); - } - } - - /** - * returns all Items of the Collection as a Map - * - * @return Map with all Items - */ - public Map> getAllItems() { - return hash; - - } - - /** - * replaces the current map object. In different to the method replaceAllItems this method - * overwrites the hash object and did not copy the values - * - * @param aHash - */ - public void setAllItems(Map> aHash) { - hash = aHash; - - } - - /** - * Returns a sorted list of all item names stored in the current ItemCollection. - * - * @return sorted list of item names - */ - public List getItemNames() { - List result = new ArrayList(); - result.addAll(hash.keySet()); - // sort result - Collections.sort(result); - return result; - } - - /** - * Replaces the value of an item. If the ItemCollection does not contain an item with the - * specified name, the method creates a new item and adds it to the ItemCollection. The ItemName - * is not case sensitive. Use hasItem to verify the existence of an item. All item names will be - * lower cased. - *

- * Each item can contain a list of values (multivalue item). If a single value is provided the - * method creates a List with one single value (singlevalue item). - *

- * If the value is null the method will remove the item. This is equal to the method call - * removeItem() - *

- * If the ItemValue is not serializable the item will be removed. This method is deprecated and - * should be replaced by the method setItemvValue. - * - * @see method setItemValue. - * @param itemName The name of the item or items you want to replace. - * @param itemValue The value of the new item. The data type of the item depends upon the data - * type of value, and does not need to match the data type of the old item. - */ - public void replaceItemValue(String itemName, Object itemValue) { - setItemValue(itemName, itemValue, false); - - } - - /** - * Returns the resolved String value of the specified item. The method converts the stored value - * to a String. If the item has no value, the method returns an empty String. If the item has - * multiple values, this method returns the first value. - *

- * The ItemName is not case sensitive. Use hasItem to verify the existence of an item. - * - * @param itemName The name of an item. - * @return the String value of the item - * - */ - public String getItemValueString(String itemName) { - List v = (List) getItemValue(itemName); - if (v.size() == 0) { - return ""; - } else { - // verify if value is null - Object o = v.get(0); - if (o == null) { - return ""; - } else { - return o.toString(); - } - } - - } - - /** - * Returns the resolved Integer value of the specified item. The method converts the stored value - * to an Integer. If the item has no value or the value is not convertible to an Integer, the - * method returns 0. If the item has multiple values, this method returns the first value. - *

- * The ItemName is not case sensitive. Use hasItem to verify the existence of an item. - * - * @param itemName The name of an item. - * @return the integer value of the item - */ - public int getItemValueInteger(String itemName) { - try { - List v = getItemValue(itemName); - if (v.size() == 0) { - return 0; - } - String sValue = v.get(0).toString(); - return new Double(sValue).intValue(); - } catch (NumberFormatException e) { - return 0; - } catch (ClassCastException e) { - return 0; - } - } - - /** - * Returns the resolved Long value of the specified item. The method converts the stored value to - * long. If the item has no value or the value is not convertible to a Long, the method returns 0. - * If the item has multiple values, this method returns the first value. - *

- * The ItemName is not case sensitive. Use hasItem to verify the existence of an item. - * - * @param itemName The name of an item. - * @return the Long value of the item - */ - public long getItemValueLong(String itemName) { - try { - List v = getItemValue(itemName); - if (v.size() == 0) { - return 0; - } - String sValue = v.get(0).toString(); - return new Long(sValue).longValue(); - } catch (NumberFormatException e) { - return 0; - } catch (ClassCastException e) { - return 0; - } - } - - /** - * Returns the resolved Date value of the specified item. If the item has no value or the value is - * not of the type Date, the method returns null. If the item has multiple values, this method - * returns the first value. - *

- * The ItemName is not case sensitive. Use hasItem to verify the existence of an item. - * - * @param itemName The name of an item. - * @return the Date value of the item - */ - public Date getItemValueDate(String aName) { - try { - List v = getItemValue(aName); - if (v.size() == 0) { - return null; - } - Object o = v.get(0); - if (!(o instanceof Date)) { - return null; - } - return (Date) o; - } catch (ClassCastException e) { - return null; - } - } - - /** - * Returns the resolved LocalDateTime value of the specified item. The method converts a Date - * object into a LocalDateTime object using the ZoneId.systemDefault(). - *

- * Note: internaly the ItemCollection store LocalDateTime values as Date objects! - *

- * If the item has no value or the value is not of the type Date, the method returns null. If the - * item has multiple values, this method returns the first value. - *

- * The ItemName is not case sensitive. Use hasItem to verify the existence of an item. - * - * @param itemName The name of an item. - * @return the Date value of the item - */ - public LocalDateTime getItemValueLocalDateTime(String aName) { - Date d = this.getItemValueDate(aName); - LocalDateTime localDateTime = d.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime(); - return localDateTime; - } - - /** - * Returns the resolved Double value of the specified item. The method converts the stored value - * to double. If the item has no value or the value is not convertible to a Double, the method - * returns 0.0. If the item has multiple values, this method returns the first value. - *

- * The ItemName is not case sensitive. Use hasItem to verify the existence of an item. - * - * @param itemName The name of an item. - * @return the double value of the item - */ - public double getItemValueDouble(String itemName) { - try { - List v = getItemValue(itemName); - if (v.size() == 0) - return 0.0; - else { - // test for object type... - Object o = v.get(0); - if (o instanceof Double) - return (Double) o; - - if (o instanceof Float) - return (Float) o; - - if (o instanceof Long) - return (Long) o; - - if (o instanceof Integer) - return (Integer) o; - - // try to parse string..... + // NOTE: ItemCollection is not serializable + + private static Logger logger = Logger.getLogger(ItemCollection.class.getName()); + + private Map> hash = new Hashtable>(); + + /** + * Creates a new empty ItemCollection + * + */ + public ItemCollection() { + super(); + } + + /** + * Creates a new ItemCollection and makes a deep copy from a given value Map + * + * @param map - with item values + */ + public ItemCollection(Map> map) { + super(); + this.replaceAllItems(map); + } + + /** + * Creates a new ItemCollection and makes a deep copy from a given + * ItemCollection + * + * @param itemCol - ItemCollection with values + */ + public ItemCollection(ItemCollection itemCol) { + super(); + this.replaceAllItems(itemCol.hash); + } + + /** + * Creates a new ItemCollection by a reference to a given value Map. This method + * does not make a deep copy of the given map and sets the value map by + * reference. This method can be used in cases where values are only read. In + * all other cases, the constructor method 'ItemCollection(Map> map)' should be used. + * + * @param map - reference with item values + */ + public static ItemCollection createByReference(final Map> map) { + ItemCollection reference = new ItemCollection(); + if (map != null) { + reference.hash = map; + } + return reference; + } + + /** + * This method clones the current ItemCollection. The method makes a deep copy + * of the current instance. + */ + @Override + public Object clone() { + ItemCollection clone = new ItemCollection(this); + return clone; + } + + /** + * This method clones the current ItemCollection with a subset of items. The + * method makes a deep copy of the current instance and removes items not + * defined by the list of itemNames. + * + * @param itemNames - list of properties to be copied into the clone + * @return new ItemCollection + */ + @SuppressWarnings("unchecked") + public ItemCollection clone(final List itemNames) { + ItemCollection clone = (ItemCollection) this.clone(); + // remove all undefined items + if (itemNames != null && itemNames.size() > 0) { + Iterator it = hash.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry> entry = (Map.Entry>) it.next(); + if (!itemNames.contains(entry.getKey())) { + clone.removeItem(entry.getKey()); + } + } + } + return clone; + } + + /** + * This method compares the values of two item collections by comparing the hash + * maps. This did not garantie that also embedded arrays are equal. + */ + public boolean equals(Object o) { + if (!(o instanceof ItemCollection)) + return false; + return hash.equals(((ItemCollection) o).hash); + } + + /** + * Set the value of an item. If the ItemCollection does not contain an item with + * the specified name, the method creates a new item and adds it to the + * ItemCollection. The ItemName is not case sensitive. Use hasItem to verify the + * existence of an item. All item names will be lower cased. + *

+ * Each item can contain a list of values (multivalue item). If a single value + * is provided the method creates a List with one single value (singlevalue + * item). + *

+ * If the value is null the method will remove the item. This is equal to the + * method call removeItem() + *

+ * If the ItemValue is not serializable the item will be removed. + *

+ * + * @param itemName The name of the item or items you want to replace. + * @param itemValue The value of the new item. The data type of the item depends + * upon the data type of value, and does not need to match the + * data type of the old item. + */ + + public ItemCollection setItemValue(String itemName, Object itemValue) { + setItemValue(itemName, itemValue, false); + return this; + } + + /** + * Appends a value to an existing item. If the ItemCollection does not contain + * an item with the specified name, the method creates a new item and adds it to + * the ItemCollection. The ItemName is not case sensitive. Use hasItem to verify + * the existence of an item. All item names will be lower cased. + * + * If a value list is provided the method appends each single value. + * + * If the value is null the method will remove the item. This is equal to the + * method call removeItem() + * + * If the ItemValue is not serializable the item will be removed. + * + * + * @param itemName The name of the item or items you want to replace. + * @param itemValue The value of the new item. The data type of the item depends + * upon the data type of value, and does not need to match the + * data type of the old item. + */ + public ItemCollection appendItemValue(String itemName, Object itemValue) { + setItemValue(itemName, itemValue, true); + return this; + } + + /** + * Returns the value list for the specified Item. The returned list is untyped + * and the values contained in the list are not converted to a specific type. + * The values have the same object type as set by calling the method + * setItemValue(String itemName, Object itemValue). To get a typed + * value list, see the method + * getItemValue(String itemName, Class itemType) . + * + *

+ * If the item does not exist or has no values, the method returns an empty + * List. + *

+ * The ItemName is not case sensitive. Use hasItem to verify the existence of an + * item. + * + * @param itemName The name of an item. + * @return an untyped list of values contained by the item. + * + */ + @SuppressWarnings("rawtypes") + public List getItemValue(String itemName) { + if (itemName == null) { + return null; + } + itemName = itemName.toLowerCase().trim(); + List o = hash.get(itemName); + if (o == null) + return new ArrayList<>(); + else { + // remove null values + o.removeAll(Collections.singleton(null)); + return o; + } + } + + /** + * Returns the resolved item value of the specified type. The method converts + * the value to the specified type if possible, otherwise the method returns + * null. If the item has multiple values, this method returns the first value. + *

+ * If the item isn't present in the itemCollection the method returns null. + *

+ * If the specified type is int, float, long, double, Integer, Float, Long or + * Double, the method returns 0 instead of null + *

+ * If the item contains no value with the specified type, the method returns + * null. The ItemName is not case sensitive. Use hasItem to verify the existence + * of an item. + * + * @param itemName The item Name. + * @param itemType The type into which the resolve item value should get + * converted + * @return the resolved item value as an object of the requested type. + */ + @SuppressWarnings("unchecked") + public T getItemValue(String itemName, Class itemType) { + List values = getItemValue(itemName); + if (values == null || values.size() == 0) { + + // test for Integer + if (itemType == Integer.class || itemType == int.class) { + return (T) new Integer(0); + } + // test for Float + if (itemType == Float.class || itemType == float.class) { + return (T) new Float(0); + } + + // test for Long + if (itemType == Long.class || itemType == long.class) { + return (T) new Long(0); + } + + // test for Double + if (itemType == Double.class || itemType == double.class) { + return (T) new Double(0); + } + + return null; + } + // find first value of specified type + return convertValue(values.get(0), itemType); + } + + /** + * Returns the resolved list of item values of the specified type. The method + * converts the values of the list to the specified type if possible. + *

+ * If the item isn't present in the itemCollection the method returns an empty + * list. + *

+ * The ItemName is not case sensitive. Use hasItem to verify the existence of an + * item. + * + * @param itemName The item Name. + * @param itemType The type into which the resolved item values should get + * converted + * @return the resolved list of item values of the requested type. + */ + + @SuppressWarnings("unchecked") + public List getItemValueList(String itemName, Class itemType) { + List values = getItemValue(itemName); + // convert values... + values.replaceAll(s -> convertValue(s, itemType)); + return (List) values; + // @see details here: + // https://stackoverflow.com/questions/51937821/how-to-define-a-generic-list-of-types-in-java?noredirect=1#comment90825856_51937821 + } + + /** + * removes a attribute from the item collection + * + * @param name + */ + public void removeItem(String name) { + if (name != null) { + name = name.toLowerCase().trim(); + this.hash.remove(name); + } + } + + /** + * Indicates whether an item exists in the document. + * + * @param aName The name of an item. + * @return true if an item with name exists in the document, false if no item + * with name exists in the document + * + */ + public boolean hasItem(String aName) { + if (aName == null) { + return false; + } + aName = aName.toLowerCase().trim(); + return (hash.get(aName) != null); + } + + /** + * Returns true if the value of an item with a single numeric value is from type + * 'Integer' + * + * @param aName + * @return boolean true if object is from type Double + * + */ + public boolean isItemValueInteger(String aName) { + List v = getItemValue(aName); + if (v.size() == 0) + return false; + else { + // test for object type... + Object o = v.get(0); + return (o instanceof Integer); + } + } + + /** + * Returns true if the value of an item with a single numeric value is from type + * 'Long' + * + * @param aName + * @return boolean true if object is from type Double + * + */ + public boolean isItemValueLong(String aName) { + List v = getItemValue(aName); + if (v.size() == 0) + return false; + else { + // test for object type... + Object o = v.get(0); + return (o instanceof Long); + } + } + + /** + * Returns true if the value of an item with a single numeric value is from type + * 'Double' + * + * @param aName + * @return boolean true if object is from type Double + * + */ + public boolean isItemValueDouble(String aName) { + List v = getItemValue(aName); + if (v.size() == 0) + return false; + else { + // test for object type... + Object o = v.get(0); + return (o instanceof Double); + } + } + + /** + * Returns true if the value of an item with a single numeric value is from type + * 'Float' + * + * @param aName + * @return boolean true if object is from type Double + * + */ + public boolean isItemValueFloat(String aName) { + List v = getItemValue(aName); + if (v.size() == 0) + return false; + else { + // test for object type... + Object o = v.get(0); + return (o instanceof Float); + } + } + + /** + * Returns true if the value of an item is from type 'Date' + * + * @param aName + * @return boolean true if object is from type Double + * + */ + public boolean isItemValueDate(String aName) { + List v = getItemValue(aName); + if (v.size() == 0) + return false; + else { + // test for object type... + Object o = v.get(0); + return (o instanceof Date); + } + } + + /** + * returns all Items of the Collection as a Map + * + * @return Map with all Items + */ + public Map> getAllItems() { + return hash; + + } + + /** + * replaces the current map object. In different to the method replaceAllItems + * this method overwrites the hash object and did not copy the values + * + * @param aHash + */ + public void setAllItems(Map> aHash) { + hash = aHash; + + } + + /** + * Returns a sorted list of all item names stored in the current ItemCollection. + * + * @return sorted list of item names + */ + public List getItemNames() { + List result = new ArrayList(); + result.addAll(hash.keySet()); + // sort result + Collections.sort(result); + return result; + } + + /** + * Replaces the value of an item. If the ItemCollection does not contain an item + * with the specified name, the method creates a new item and adds it to the + * ItemCollection. The ItemName is not case sensitive. Use hasItem to verify the + * existence of an item. All item names will be lower cased. + *

+ * Each item can contain a list of values (multivalue item). If a single value + * is provided the method creates a List with one single value (singlevalue + * item). + *

+ * If the value is null the method will remove the item. This is equal to the + * method call removeItem() + *

+ * If the ItemValue is not serializable the item will be removed. This method is + * deprecated and should be replaced by the method setItemvValue. + * + * @see method setItemValue. + * @param itemName The name of the item or items you want to replace. + * @param itemValue The value of the new item. The data type of the item depends + * upon the data type of value, and does not need to match the + * data type of the old item. + */ + public void replaceItemValue(String itemName, Object itemValue) { + setItemValue(itemName, itemValue, false); + + } + + /** + * Returns the resolved String value of the specified item. The method converts + * the stored value to a String. If the item has no value, the method returns an + * empty String. If the item has multiple values, this method returns the first + * value. + *

+ * The ItemName is not case sensitive. Use hasItem to verify the existence of an + * item. + * + * @param itemName The name of an item. + * @return the String value of the item + * + */ + public String getItemValueString(String itemName) { + List v = (List) getItemValue(itemName); + if (v.size() == 0) { + return ""; + } else { + // verify if value is null + Object o = v.get(0); + if (o == null) { + return ""; + } else { + return o.toString(); + } + } + + } + + /** + * Returns the resolved Integer value of the specified item. The method converts + * the stored value to an Integer. If the item has no value or the value is not + * convertible to an Integer, the method returns 0. If the item has multiple + * values, this method returns the first value. + *

+ * The ItemName is not case sensitive. Use hasItem to verify the existence of an + * item. + * + * @param itemName The name of an item. + * @return the integer value of the item + */ + public int getItemValueInteger(String itemName) { try { - return Double.valueOf(v.get(0).toString()); - } catch (ClassCastException | NumberFormatException e) { - return 0.0; - } - } - } catch (ClassCastException e) { - return 0.0; - } - } - - /** - * Returns the resolved Float value of the specified item. The method converts the stored value to - * float. If the item has no value or the value is not convertible to a Float, the method returns - * 0.0. If the item has multiple values, this method returns the first value. - *

- * The ItemName is not case sensitive. Use hasItem to verify the existence of an item. - * - * @param itemName The name of an item. - * @return the float value of the item - */ - public float getItemValueFloat(String itemName) { - try { - List v = getItemValue(itemName); - if (v.size() == 0) - return (float) 0.0; - else { - // test for object type... - Object o = v.get(0); - - if (o instanceof Float) - return (Float) o; - - if (o instanceof Double) { - Double d = (Double) o; - return (float) d.doubleValue(); - } - - if (o instanceof Long) - return (Long) o; - - if (o instanceof Integer) - return (Integer) o; - - // try to parse string..... + List v = getItemValue(itemName); + if (v.size() == 0) { + return 0; + } + String sValue = v.get(0).toString(); + return new Double(sValue).intValue(); + } catch (NumberFormatException e) { + return 0; + } catch (ClassCastException e) { + return 0; + } + } + + /** + * Returns the resolved Long value of the specified item. The method converts + * the stored value to long. If the item has no value or the value is not + * convertible to a Long, the method returns 0. If the item has multiple values, + * this method returns the first value. + *

+ * The ItemName is not case sensitive. Use hasItem to verify the existence of an + * item. + * + * @param itemName The name of an item. + * @return the Long value of the item + */ + public long getItemValueLong(String itemName) { try { - return Float.valueOf(v.get(0).toString()); - } catch (ClassCastException | NumberFormatException e) { - return (float) 0.0; - } - - } - } catch (ClassCastException e) { - return (float) 0.0; - } - } - - /** - * Returns the resolved Boolean value of the specified item. The method converts the stored value - * to Boolean. If the item has no value or the value is not convertible to a Boolean, the method - * returns false. If the item has multiple values, this method returns the first value. - *

- * The ItemName is not case sensitive. Use hasItem to verify the existence of an item. - * - * @param itemName The name of an item. - * @return the boolean value of the item - */ - public boolean getItemValueBoolean(String itemName) { - try { - List v = getItemValue(itemName); - if (v.size() == 0) { - return false; - } - Object sValue = v.get(0); - return Boolean.valueOf(sValue.toString()); - } catch (ClassCastException e) { - return false; - } - } - - /** - * Replaces all items specified in the map with new items, which are assigned to the specified - * values inside the map. - * - * The method makes a deep copy of the source map using serialization. This is to make sure, that - * no object reference is copied. Other wise for example embedded arrays are not cloned. This is - * also important for JPA to avoid changes of attached entity beans with references in the data of - * an ItemCollection. - * - * @see deepCopyOfMap - * @param map - */ - @SuppressWarnings("unchecked") - public void replaceAllItems(Map> map) { - if (map == null) { - return; - } - // make a deep copy of the map - Map> clonedMap = (Map>) deepCopyOfMap(map); - if (clonedMap != null) { - Iterator it = clonedMap.entrySet().iterator(); - while (it.hasNext()) { - Map.Entry> entry = (Map.Entry>) it.next(); - replaceItemValue(entry.getKey().toString(), entry.getValue()); - } - } - } - - /** - * Copies all items of a source ItemCollection. - *

- * The method makes a deep copy of the source map using serialization. This is to make sure, that - * no object reference is copied. Other wise for example embedded arrays are not cloned. This is - * also important for JPA to avoid changes of attached entity beans with references in the data of - * an ItemCollection. - * - * @see deepCopyOfMap - * @param map - */ - public void copy(ItemCollection source) { - replaceAllItems(source.getAllItems()); - } - - /** - * Merges all items from a source map into the current instance. Only Items will be copied if the - * current instance does not have an item with the same name. If you want to copy all item values, - * use the method replaceAllItems instead. - *

- * The method makes a deep copy of the source map using serialization. This is to make sure, that - * no object reference is copied. Other wise for example embedded arrays are not cloned. This is - * also important for JPA to avoid changes of attached entity beans with references in the data of - * an ItemCollection. - * - * @see deepCopyOfMap - * @param map - */ - @SuppressWarnings("unchecked") - public void mergeItems(Map> map) { - if (map == null) { - return; - } - // make a deep copy of the map - Map> clonedMap = (Map>) deepCopyOfMap(map); - if (clonedMap != null) { - Iterator it = clonedMap.entrySet().iterator(); - while (it.hasNext()) { - - Map.Entry> entry = (Map.Entry>) it.next(); - // copy only the item if the hash map does not have an item with the same name - if (!hash.containsKey(entry.getKey())) { - replaceItemValue(entry.getKey().toString(), entry.getValue()); - } - } - } - } - - /** - * This method removes duplicates and null or empty values from an item list - * - * @param itemName - item to be processed - */ - @SuppressWarnings("unchecked") - public void purgeItemValue(String itemName) { - List valueList = this.getItemValue(itemName); - // remove null or empty entries... - valueList.removeIf(item -> item == null || "".equals(item)); - // create a new instance of a List and distinct the List. - List purgedValueList = valueList.stream().distinct().collect(Collectors.toList()); - this.setItemValue(itemName, purgedValueList); - } - - /** - * This method adds a fileData object to the ItemCollection. The item '$file' stores all data - * objects. - * - * @param filedata - a file data object - * @see FileData - */ - @SuppressWarnings("unchecked") - public void addFileData(FileData filedata) { - if (filedata != null) { - List vectorFileInfo = null; - - // Store files using a map.... - Map> mapFiles = null; - List vFiles = getItemValue("$file"); - if (vFiles != null && vFiles.size() > 0) - mapFiles = (Map>) vFiles.get(0); - else - mapFiles = new LinkedHashMap>(); - - // existing file will be overridden! - vectorFileInfo = new ArrayList(); - // put file in a List containing the contentType, content, MD5Checksum and - // optional attributes - vectorFileInfo.add(filedata.getContentType()); - vectorFileInfo.add(filedata.getContent()); - // add optional attributes - vectorFileInfo.add(filedata.getAttributes()); - - mapFiles.put(filedata.getName(), vectorFileInfo); - replaceItemValue("$file", mapFiles); - - // addFile(filedata.content, filedata.name, filedata.contentType); - } - } - - /** - * This method adds a single file to the ItemCollection. files will be stored into the property - * $file. - * - * @param data - byte array with file data - * @param fileName - name of the file attachment - * @param contentType - the contenttype (e.g. 'Text/HTML') - * - */ - @SuppressWarnings("unchecked") - @Deprecated - public void addFile(byte[] data, String fileName, String contentType) { - logger.warning("method addFile() is deprecated - replace with addFileData()"); - if (data != null) { - List vectorFileInfo = null; - - // IE includes '\' characters! so remove all these characters.... - if (fileName.indexOf('\\') > -1) - fileName = fileName.substring(fileName.lastIndexOf('\\') + 1); - if (fileName.indexOf('/') > -1) - fileName = fileName.substring(fileName.lastIndexOf('/') + 1); - - if (contentType == null || "".equals(contentType)) - contentType = "application/unknown"; - - // Store files using a map.... - Map> mapFiles = null; - List vFiles = getItemValue("$file"); - if (vFiles != null && vFiles.size() > 0) - mapFiles = (Map>) vFiles.get(0); - else - mapFiles = new LinkedHashMap>(); - - // existing file will be overridden! - vectorFileInfo = new ArrayList(); - // put file in a vector containing the byte array and also the - // content type - vectorFileInfo.add(contentType); - vectorFileInfo.add(data); - mapFiles.put(fileName, vectorFileInfo); - replaceItemValue("$file", mapFiles); - } - } - - /** - * Returns a data object for an attached file. The data object is a list containing the - * contentType (String) and the content (byte[]) - * - * @param filename - * @return file data contentType (String) and the content (byte[]) - */ - @Deprecated - public List getFile(String filename) { - Map> files = this.getFiles(); - if (files != null) { - return files.get(filename); - } else { - return null; - } - } - - /** - * Returns a FileData object for an attached file. - * - * @param filename - * @return FileData object - */ - public FileData getFileData(String filename) { - if (filename == null || filename.isEmpty()) { - return null; - } - List files = getFileData(); - for (FileData fileData : files) { - if (filename.equals(fileData.getName())) { - return fileData; - } - } - // not found! - return null; - } - - /** - * Returns a list of all FileData objects. - *

- * FileData objects are stored in the attribute '$file'. - * - * @return list of FileData objects - */ - @SuppressWarnings({"unchecked", "rawtypes"}) - public List getFileData() { - - List result = new ArrayList(); - List vFiles = getItemValue("$file"); - if (vFiles != null && vFiles.size() > 0) { - Map testContent = (Map) vFiles.get(0); - for (Entry entry : testContent.entrySet()) { - List data = null; - String sFileName = entry.getKey(); - Object obj = entry.getValue(); - // test if the value part is a List or an Object[] ? - // In case its an Object[] we convert the array to a List - if (obj instanceof List) { - data = (List) obj; - } else { - // convert array to List - data = Arrays.asList(obj); - } - // now we build the FileData object. - String contentType = (String) data.get(0); - byte[] content = (byte[]) data.get(1); - Map> attributes = null; - // test if we have custom attributes? - if (data.size() >= 3) { - attributes = (Map>) data.get(2); - } - if (attributes == null) { - // try to migrate deprecated 'dms' item...... - // in some cases the DMS item does not contain a Map object - // for that reason we test the object type - // see issue #509 - List vDMS = getItemValue("dms"); - // test if we found a match.... - for (Object aMetadataObject : vDMS) { - // issue #509 - if (aMetadataObject instanceof Map) { - Map aMetadata = (Map) aMetadataObject; - String sName = getStringValueFromMap(aMetadata, "txtname"); - if (sFileName.equals(sName)) { - attributes = aMetadata; - break; - } + List v = getItemValue(itemName); + if (v.size() == 0) { + return 0; } - } - } - result.add(new FileData(sFileName, content, contentType, attributes)); - } - } - return result; - - } - - /** - * This method removes a single file attachment from the workitem - * - */ - @SuppressWarnings("unchecked") - public void removeFile(String aFilename) { - /* delete attachment */ - Map> mapFiles = null; - List vFiles = getItemValue("$file"); - if (vFiles != null && vFiles.size() > 0) { - mapFiles = (Map>) vFiles.get(0); - mapFiles.remove(aFilename); - replaceItemValue("$file", mapFiles); - } - - } - - /** - * Returns files stored in the property '$file'. The files are returned in a Map interface where - * the key is the filename and the value is a list with two elements - the ContenType and the file - * content (byte[]). s Files can be added into a ItemCollection using the method addFile(). - * - * @return - */ - @Deprecated - @SuppressWarnings("unchecked") - public Map> getFiles() { - logger.warning("method getFiles() is deprecated - replace with getFileData()"); - List vFiles = getItemValue("$file"); - if (vFiles != null && vFiles.size() > 0) { - // test if the value part is a List or an Object[]. In case its an - // Object[] we convert the array to a List - - Map testContent = (Map) vFiles.get(0); - Map> mapFiles = new LinkedHashMap>(); - for (Entry entry : testContent.entrySet()) { - String sFileName = entry.getKey(); - Object obj = entry.getValue(); - if (obj instanceof List) { - mapFiles.put(sFileName, (List) obj); + String sValue = v.get(0).toString(); + return new Long(sValue).longValue(); + } catch (NumberFormatException e) { + return 0; + } catch (ClassCastException e) { + return 0; + } + } + + /** + * Returns the resolved Date value of the specified item. If the item has no + * value or the value is not of the type Date, the method returns null. If the + * item has multiple values, this method returns the first value. + *

+ * The ItemName is not case sensitive. Use hasItem to verify the existence of an + * item. + * + * @param itemName The name of an item. + * @return the Date value of the item + */ + public Date getItemValueDate(String aName) { + try { + List v = getItemValue(aName); + if (v.size() == 0) { + return null; + } + Object o = v.get(0); + if (!(o instanceof Date)) { + return null; + } + return (Date) o; + } catch (ClassCastException e) { + return null; + } + } + + /** + * Returns the resolved LocalDateTime value of the specified item. The method + * converts a Date object into a LocalDateTime object using the + * ZoneId.systemDefault(). + *

+ * Note: internaly the ItemCollection store LocalDateTime values as Date + * objects! + *

+ * If the item has no value or the value is not of the type Date, the method + * returns null. If the item has multiple values, this method returns the first + * value. + *

+ * The ItemName is not case sensitive. Use hasItem to verify the existence of an + * item. + * + * @param itemName The name of an item. + * @return the Date value of the item + */ + public LocalDateTime getItemValueLocalDateTime(String aName) { + Date d = this.getItemValueDate(aName); + LocalDateTime localDateTime = d.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime(); + return localDateTime; + } + + /** + * Returns the resolved Double value of the specified item. The method converts + * the stored value to double. If the item has no value or the value is not + * convertible to a Double, the method returns 0.0. If the item has multiple + * values, this method returns the first value. + *

+ * The ItemName is not case sensitive. Use hasItem to verify the existence of an + * item. + * + * @param itemName The name of an item. + * @return the double value of the item + */ + public double getItemValueDouble(String itemName) { + try { + List v = getItemValue(itemName); + if (v.size() == 0) + return 0.0; + else { + // test for object type... + Object o = v.get(0); + if (o instanceof Double) + return (Double) o; + + if (o instanceof Float) + return (Float) o; + + if (o instanceof Long) + return (Long) o; + + if (o instanceof Integer) + return (Integer) o; + + // try to parse string..... + try { + return Double.valueOf(v.get(0).toString()); + } catch (ClassCastException | NumberFormatException e) { + return 0.0; + } + } + } catch (ClassCastException e) { + return 0.0; + } + } + + /** + * Returns the resolved Float value of the specified item. The method converts + * the stored value to float. If the item has no value or the value is not + * convertible to a Float, the method returns 0.0. If the item has multiple + * values, this method returns the first value. + *

+ * The ItemName is not case sensitive. Use hasItem to verify the existence of an + * item. + * + * @param itemName The name of an item. + * @return the float value of the item + */ + public float getItemValueFloat(String itemName) { + try { + List v = getItemValue(itemName); + if (v.size() == 0) + return (float) 0.0; + else { + // test for object type... + Object o = v.get(0); + + if (o instanceof Float) + return (Float) o; + + if (o instanceof Double) { + Double d = (Double) o; + return (float) d.doubleValue(); + } + + if (o instanceof Long) + return (Long) o; + + if (o instanceof Integer) + return (Integer) o; + + // try to parse string..... + try { + return Float.valueOf(v.get(0).toString()); + } catch (ClassCastException | NumberFormatException e) { + return (float) 0.0; + } + + } + } catch (ClassCastException e) { + return (float) 0.0; + } + } + + /** + * Returns the resolved Boolean value of the specified item. The method converts + * the stored value to Boolean. If the item has no value or the value is not + * convertible to a Boolean, the method returns false. If the item has multiple + * values, this method returns the first value. + *

+ * The ItemName is not case sensitive. Use hasItem to verify the existence of an + * item. + * + * @param itemName The name of an item. + * @return the boolean value of the item + */ + public boolean getItemValueBoolean(String itemName) { + try { + List v = getItemValue(itemName); + if (v.size() == 0) { + return false; + } + Object sValue = v.get(0); + return Boolean.valueOf(sValue.toString()); + } catch (ClassCastException e) { + return false; + } + } + + /** + * Replaces all items specified in the map with new items, which are assigned to + * the specified values inside the map. + * + * The method makes a deep copy of the source map using serialization. This is + * to make sure, that no object reference is copied. Other wise for example + * embedded arrays are not cloned. This is also important for JPA to avoid + * changes of attached entity beans with references in the data of an + * ItemCollection. + * + * @see deepCopyOfMap + * @param map + */ + @SuppressWarnings("unchecked") + public void replaceAllItems(Map> map) { + if (map == null) { + return; + } + // make a deep copy of the map + Map> clonedMap = (Map>) deepCopyOfMap(map); + if (clonedMap != null) { + Iterator it = clonedMap.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry> entry = (Map.Entry>) it.next(); + replaceItemValue(entry.getKey().toString(), entry.getValue()); + } + } + } + + /** + * Copies all items of a source ItemCollection. + *

+ * The method makes a deep copy of the source map using serialization. This is + * to make sure, that no object reference is copied. Other wise for example + * embedded arrays are not cloned. This is also important for JPA to avoid + * changes of attached entity beans with references in the data of an + * ItemCollection. + * + * @see deepCopyOfMap + * @param map + */ + public void copy(ItemCollection source) { + replaceAllItems(source.getAllItems()); + } + + /** + * Merges all items from a source map into the current instance. Only Items will + * be copied if the current instance does not have an item with the same name. + * If you want to copy all item values, use the method replaceAllItems instead. + *

+ * The method makes a deep copy of the source map using serialization. This is + * to make sure, that no object reference is copied. Other wise for example + * embedded arrays are not cloned. This is also important for JPA to avoid + * changes of attached entity beans with references in the data of an + * ItemCollection. + * + * @see deepCopyOfMap + * @param map + */ + @SuppressWarnings("unchecked") + public void mergeItems(Map> map) { + if (map == null) { + return; + } + // make a deep copy of the map + Map> clonedMap = (Map>) deepCopyOfMap(map); + if (clonedMap != null) { + Iterator it = clonedMap.entrySet().iterator(); + while (it.hasNext()) { + + Map.Entry> entry = (Map.Entry>) it.next(); + // copy only the item if the hash map does not have an item with the same name + if (!hash.containsKey(entry.getKey())) { + replaceItemValue(entry.getKey().toString(), entry.getValue()); + } + } + } + } + + /** + * This method removes duplicates and null or empty values from an item list + * + * @param itemName - item to be processed + */ + @SuppressWarnings("unchecked") + public void purgeItemValue(String itemName) { + List valueList = this.getItemValue(itemName); + // remove null or empty entries... + valueList.removeIf(item -> item == null || "".equals(item)); + // create a new instance of a List and distinct the List. + List purgedValueList = valueList.stream().distinct().collect(Collectors.toList()); + this.setItemValue(itemName, purgedValueList); + } + + /** + * This method adds a fileData object to the ItemCollection. The item '$file' + * stores all data objects. + * + * @param filedata - a file data object + * @see FileData + */ + @SuppressWarnings("unchecked") + public void addFileData(FileData filedata) { + if (filedata != null) { + List vectorFileInfo = null; + + // Store files using a map.... + Map> mapFiles = null; + List vFiles = getItemValue("$file"); + if (vFiles != null && vFiles.size() > 0) + mapFiles = (Map>) vFiles.get(0); + else + mapFiles = new LinkedHashMap>(); + + // existing file will be overridden! + vectorFileInfo = new ArrayList(); + // put file in a List containing the contentType, content, MD5Checksum and + // optional attributes + vectorFileInfo.add(filedata.getContentType()); + vectorFileInfo.add(filedata.getContent()); + // add optional attributes + vectorFileInfo.add(filedata.getAttributes()); + + mapFiles.put(filedata.getName(), vectorFileInfo); + replaceItemValue("$file", mapFiles); + + // addFile(filedata.content, filedata.name, filedata.contentType); + } + } + + /** + * This method adds a single file to the ItemCollection. files will be stored + * into the property $file. + * + * @param data - byte array with file data + * @param fileName - name of the file attachment + * @param contentType - the contenttype (e.g. 'Text/HTML') + * + */ + @SuppressWarnings("unchecked") + @Deprecated + public void addFile(byte[] data, String fileName, String contentType) { + logger.warning("method addFile() is deprecated - replace with addFileData()"); + if (data != null) { + List vectorFileInfo = null; + + // IE includes '\' characters! so remove all these characters.... + if (fileName.indexOf('\\') > -1) + fileName = fileName.substring(fileName.lastIndexOf('\\') + 1); + if (fileName.indexOf('/') > -1) + fileName = fileName.substring(fileName.lastIndexOf('/') + 1); + + if (contentType == null || "".equals(contentType)) + contentType = "application/unknown"; + + // Store files using a map.... + Map> mapFiles = null; + List vFiles = getItemValue("$file"); + if (vFiles != null && vFiles.size() > 0) + mapFiles = (Map>) vFiles.get(0); + else + mapFiles = new LinkedHashMap>(); + + // existing file will be overridden! + vectorFileInfo = new ArrayList(); + // put file in a vector containing the byte array and also the + // content type + vectorFileInfo.add(contentType); + vectorFileInfo.add(data); + mapFiles.put(fileName, vectorFileInfo); + replaceItemValue("$file", mapFiles); + } + } + + /** + * Returns a data object for an attached file. The data object is a list + * containing the contentType (String) and the content (byte[]) + * + * @param filename + * @return file data contentType (String) and the content (byte[]) + */ + @Deprecated + public List getFile(String filename) { + Map> files = this.getFiles(); + if (files != null) { + return files.get(filename); } else { - // convert array to List - mapFiles.put(sFileName, Arrays.asList(obj)); - } - } - return mapFiles; - } - - return null; - } - - /** - * Returns a list of file names attached to the current workitem. File Attachments can be added - * using the method addFile(). - * - * @return - */ - @SuppressWarnings("unchecked") - public List getFileNames() { - // File attachments... - List files = new ArrayList(); - - Map> mapFiles = null; - List vFiles = getItemValue("$file"); - if (vFiles != null && vFiles.size() > 0) { - mapFiles = (Map>) vFiles.get(0); - // files = new String[mapFiles.entrySet().size()]; - Iterator iter = mapFiles.entrySet().iterator(); - while (iter.hasNext()) { - Map.Entry> mapEntry = (Map.Entry>) iter.next(); - String aFileName = mapEntry.getKey().toString(); - files.add(aFileName); - } - } - - return files; - } - - /** - * Returns an ItemAdapter for this instance. - * - * @return - */ - public Map getItem() { - return new ItemAdapter(this); - } - - /** - * Returns an ItemListAdapter for this instance. - * - * @return - */ - public Map getItemList() { - return new ItemListAdapter(this); - } - - /** - * Returns an ItemListArrayAdapter for this instance. - * - * @return - */ - public Map getItemListArray() { - return new ItemListArrayAdapter(this); - } - - /** - * @return current type - */ - public String getType() { - return getItemValueString(WorkflowKernel.TYPE); - } - - /** - * @return current $TaskID - */ - public int getTaskID() { - int result = getItemValueInteger(WorkflowKernel.TASKID); - // test for deprecated version - if (result == 0 && hasItem("$processid") && getItemValueInteger("$processid") != 0) { - // see issue #384 - /* - * logger. warning("The field $processid is deprecated. Please use $taskid instead. " + - * "Processing a workitem with an deprecated $processid is still supported."); - */ - result = getItemValueInteger("$processid"); - // update missing taskID - replaceItemValue(WorkflowKernel.TASKID, result); - } - return result; - } - - /** - * set $taskID - * - * @param taskID - */ - public void setTaskID(int taskID) { - replaceItemValue(WorkflowKernel.TASKID, taskID); - // deprecated processID is still supported for a long period. See issue #384 - replaceItemValue("$processid", taskID); - } - - public ItemCollection task(int taskID) { - setTaskID(taskID); - return this; - } - - /** - * @return current $EventID - */ - public int getEventID() { - // test for deprecated version - int result = getItemValueInteger(WorkflowKernel.EVENTID); - if (result == 0 && hasItem("$activityid") && getItemValueInteger("$activityid") != 0) { - logger.warning("The field $activityid is deprecated. Please use $eventid instead. " - + "Processing a workitem with an deprecated $activityid is still supported."); - result = getItemValueInteger("$activityid"); - // update eventID - replaceItemValue(WorkflowKernel.EVENTID, result); - } - return result; - } - - /** - * set $eventID - * - * @param eventID - */ - public void setEventID(int eventID) { - replaceItemValue(WorkflowKernel.EVENTID, eventID); - - // if deprectaed ActivityID exists we must still support it - if (hasItem("$activityid")) { - replaceItemValue("$activityid", eventID); - } - } - - /** - * Set the event id for a workitem. If a event id is already set, the method appends the event to - * the ACTIVITYIDLIST - * - * @param eventID - * @return - */ - public ItemCollection event(int eventID) { - if (this.getEventID() == 0) { - setEventID(eventID); - } else { - // set - appendItemValue(WorkflowKernel.ACTIVITYIDLIST, eventID); - } - return this; - } - - /** - * @return current $ModelVersion - */ - public String getModelVersion() { - return getItemValueString(WorkflowKernel.MODELVERSION); - } - - /** - * set the $ModelVersion - */ - public void setModelVersion(String modelversion) { - replaceItemValue(WorkflowKernel.MODELVERSION, modelversion); - } - - public ItemCollection model(String modelversion) { - setModelVersion(modelversion); - return this; - } - - /** - * @return current $ModelVersion - */ - public String getWorkflowGroup() { - return getItemValueString(WorkflowKernel.WORKFLOWGROUP); - } - - /** - * set the $ModelVersion - */ - public void setWorkflowGroup(String group) { - replaceItemValue(WorkflowKernel.WORKFLOWGROUP, group); - } - - public ItemCollection workflowGroup(String group) { - setWorkflowGroup(group); - return this; - } - - /** - * @return $UniqueID - */ - public String getUniqueID() { - return getItemValueString(WorkflowKernel.UNIQUEID); - } - - /** - * This method is deprecated. Use instead getTaskID() - * - * @return current $processID - */ - @Deprecated - public int getProcessID() { - int result = getItemValueInteger(WorkflowKernel.PROCESSID); - if (result == 0 && hasItem("$taskid")) { - result = getTaskID(); - } - return result; - } - - /** - * This method is deprecated. Use instead getEventID() - * - * @return current $ActivityID - */ - @Deprecated - public int getActivityID() { - return getEventID(); - } - - /** - * set $ActivityID. This method is deprecated. Use instead setEventID() - * - * @param activityID - */ - @Deprecated - public void setActivityID(int activityID) { - replaceItemValue("$activityid", activityID); - // set new field $eventID - setEventID(activityID); - } - - /** - * This method converts the raw java types String, int, long, float and double. - * - * The method returns null if the type is no raw type. - * - * @param value - * @param type - * @return - */ - @SuppressWarnings("unchecked") - private T convertValue(Object value, Type type) { - - // test String - if (type == String.class) { - if (value == null) { - // return empty if not present - return (T) ""; - } else { - return (T) value.toString(); - } - } - - // test Integer/int - if (type == Integer.class || type == int.class) { - try { - if (value == null) { - // return 0 if not present - return (T) Integer.valueOf(0); - } - int intvalue = new Double(value.toString()).intValue(); - return (T) Integer.valueOf(intvalue); - } catch (NumberFormatException e) { - return (T) Integer.valueOf(0); - } catch (ClassCastException e) { - return (T) Integer.valueOf(0); - } - } - - // test Long/long - if (type == Long.class || type == long.class) { - try { - if (value == null) { - // return 0 if not present - return (T) Long.valueOf(0); - } - long longvalue = new Long(value.toString()).longValue(); - return (T) Long.valueOf(longvalue); - } catch (NumberFormatException e) { - return (T) Long.valueOf(0); - } catch (ClassCastException e) { - return (T) Long.valueOf(0); - } - } - - return null; - } - - /** - * Helper method to replace an ItemValue. - * - * @param itemName - name of the value - * @param itemValue - value - * @param append - true if the value should be appended to an existing list - */ - @SuppressWarnings("unchecked") - private void setItemValue(String itemName, Object itemValue, boolean append) { - List itemValueList = null; - - if (itemName == null) - return; - // lower case itemname - itemName = itemName.toLowerCase().trim(); - - // test if value is null - if (itemValue == null) { - // remove the item? - if (!append) { - this.removeItem(itemName); - } - return; - } - - // test if value is ItemCollection - if (itemValue instanceof ItemCollection) { - // just warn - do not remove - logger.warning("replaceItemValue '" + itemName - + "': ItemCollection can not be stored into an existing ItemCollection - use XMLItemCollection instead."); - } - - // test if value is serializable - if (!(itemValue instanceof java.io.Serializable)) { - logger.warning("replaceItemValue '" + itemName + "': object is not serializable!"); - this.removeItem(itemName); - return; - } - - // test if value is a list and remove null values - if (itemValue instanceof List) { - itemValueList = (List) itemValue; - itemValueList.removeAll(Collections.singleton(null)); - // scan List for null values and remove them - for (int i = 0; i < itemValueList.size(); i++) { - // test if ItemCollection - if (itemValueList.get(i) instanceof ItemCollection) { - // just warn - do not remove - logger.warning("replaceItemValue '" + itemName - + "': ItemCollection can not be stored into an existing ItemCollection - use XMLItemCollection instead."); - } - } - } else { - // create an instance of an ArrayList - itemValueList = new ArrayList(); - itemValueList.add(itemValue); - } - - // now itemValue is of instance List - convertItemValue(itemValueList); - if (!validateItemValue(itemValueList)) { - String message = "setItemValue failed for item '" + itemName - + "', the value is a non supported object type: " + itemValue.getClass().getName() - + " value=" + itemValueList; - logger.warning(message); - throw new InvalidAccessException(message); - } - - // replace item value? - if (append) { - // append item value - List oldValueList = (List) getItemValue(itemName); - - oldValueList.addAll(itemValueList); - - hash.put(itemName, (List) oldValueList); - } else - hash.put(itemName, itemValueList); - - } - - /** - * This method converts specific itemValue in standardized object classes: - *

- * java.util.Calendar => java.util.Date - *

- * java.time.LocalDateTime => java.util.Date - * - * - * @param itemValue - * @return - */ - private void convertItemValue(List itemValues) { - - ListIterator iterator = itemValues.listIterator(); - while (iterator.hasNext()) { - Object o = iterator.next(); - if (o instanceof Calendar) { - Date date = ((Calendar) o).getTime(); - iterator.set(date); - } - if (o instanceof LocalDateTime) { - LocalDateTime ldt = (LocalDateTime) o; - Date date = Date.from(ldt.atZone(ZoneId.systemDefault()).toInstant()); - iterator.set(date); - } - } - } - - /** - * This method validates of a itemValue is acceptable for the ItemCollection. Only basic types are - * supported. - * - * @param itemValue - * @return - */ - @SuppressWarnings("rawtypes") - private boolean validateItemValue(Object itemValue) { - - // first we test if basic type? - if (isBasicType(itemValue)) { - return true; - } - - // list? - if ((itemValue instanceof List)) { - for (Object singleValue : (List) itemValue) { - if (!validateItemValue(singleValue)) { - return false; - } - } - return true; - } else - - // array? - if (itemValue != null && itemValue.getClass().isArray()) { - for (int i = 0; i < Array.getLength(itemValue); i++) { - Object singleValue = Array.get(itemValue, i); - if (!validateItemValue(singleValue)) { - return false; - } - } - return true; - } else - - // map? - if ((itemValue instanceof Map)) { - Map map = (Map) itemValue; - for (Object value : map.values()) { - if (!validateItemValue(value)) { - return false; - } - } - return true; - } - - // unknown type - return false; - } - - /** - * This helper method test if an object is a basic type which can be stored in an ItemCollection. - * - * Validate for raw objects and class types java.lang.*, java.math.* - * - * @return - */ - @SuppressWarnings("rawtypes") - private static boolean isBasicType(java.lang.Object o) { - - if (o == null) { - return true; - } - // test raw array types first - if (o instanceof byte[] || o instanceof boolean[] || o instanceof short[] || o instanceof char[] - || o instanceof int[] || o instanceof long[] || o instanceof float[] - || o instanceof double[] || o instanceof XMLItem[] || o instanceof XMLDocument[]) { - return true; - } - - // test package name - Class c = o.getClass(); - String name = c.getName(); - if (name.startsWith("java.lang.") || name.startsWith("java.math.") - || "java.util.Date".equals(name) || "org.imixs.workflow.xml.XMLItem".equals(name) - || "org.imixs.workflow.xml.XMLDocument".equals(name)) { - return true; - } - - // no basic type - return false; - } - - /** - * This helper method makes a deep copy of a map by serializing and deserializing. - * - * It is assumed that all elements in the object's source graph are serializable. - * - * @see http://www.javaworld.com/article/2077578/learn-java/java-tip-76--an-alternative-to-the-deep-copy-technique.html - * @param map - * @return - */ - private Object deepCopyOfMap(Map> map) { - try { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - ObjectOutputStream oos = new ObjectOutputStream(bos); - // serialize and pass the object - oos.writeObject(map); - oos.flush(); - ByteArrayInputStream bais = new ByteArrayInputStream(bos.toByteArray()); - ObjectInputStream ois = new ObjectInputStream(bais); - return ois.readObject(); - } catch (IOException e) { - logger.warning("Unable to clone values of ItemCollection - " + e); - return null; - } catch (ClassNotFoundException e) { - logger.warning("Unable to clone values of ItemCollection - " + e); - return null; - } - } - - /** - * This is a helper method to check a string value of a map. - * - * @return String object of a named key - */ - @SuppressWarnings({"unchecked", "unused"}) - private static String getStringValueFromMap(Map> hash, String aName) { - - List v = null; - - if (aName == null) { - return null; - } - aName = aName.toLowerCase().trim(); - - Object obj = hash.get(aName); - if (obj == null) { - return ""; - } - - if (obj instanceof List) { - - List oList = (List) obj; - if (oList == null) - v = new Vector(); - else { - v = oList; - // scan vector for null values - for (int i = 0; i < v.size(); i++) { - if (v.get(i) == null) - v.remove(i); - } - } - - if (v.size() == 0) - return ""; - else { - // verify if value is null - Object o = v.get(0); - if (o == null) - return ""; - else - return o.toString(); - } - } else { - // Value is not a list! - logger.warning("getStringValueFromMap - wrong value object found '" + aName + "'"); - return obj.toString(); - } - } - - /** - * This class helps to adapt the behavior of a single value item to be used in a jsf page using a - * expression language like this: - * - * #{mybean.item['txtMyItem']} - * - * - * @author rsoika - * - */ - class ItemAdapter implements Map { - ItemCollection itemCollection; - - public ItemAdapter() { - itemCollection = new ItemCollection(); - } - - public ItemAdapter(ItemCollection acol) { - itemCollection = acol; - } - - public void setItemCollection(ItemCollection acol) { - itemCollection = acol; - } - - /** - * returns a single value out of the ItemCollection if the key does not exist the method will - * create a value automatically - */ - public Object get(Object key) { - // check if a value for this key is available... - // if not create a new empty value - if (!itemCollection.hasItem(key.toString())) - itemCollection.replaceItemValue(key.toString(), ""); - - // return first value from vector if size >0 - List v = itemCollection.getItemValue(key.toString()); - if (v.size() > 0) - return v.get(0); - else - // otherwise return null + return null; + } + } + + /** + * Returns a FileData object for an attached file. + * + * @param filename + * @return FileData object + */ + public FileData getFileData(String filename) { + if (filename == null || filename.isEmpty()) { + return null; + } + List files = getFileData(); + for (FileData fileData : files) { + if (filename.equals(fileData.getName())) { + return fileData; + } + } + // not found! return null; } /** - * puts a single value into the ItemCollection + * Returns a list of all FileData objects. + *

+ * FileData objects are stored in the attribute '$file'. + * + * @return list of FileData objects + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public List getFileData() { + + List result = new ArrayList(); + List vFiles = getItemValue("$file"); + if (vFiles != null && vFiles.size() > 0) { + Map testContent = (Map) vFiles.get(0); + for (Entry entry : testContent.entrySet()) { + List data = null; + String sFileName = entry.getKey(); + Object obj = entry.getValue(); + // test if the value part is a List or an Object[] ? + // In case its an Object[] we convert the array to a List + if (obj instanceof List) { + data = (List) obj; + } else { + // convert array to List + data = Arrays.asList(obj); + } + // now we build the FileData object. + String contentType = (String) data.get(0); + byte[] content = (byte[]) data.get(1); + Map> attributes = null; + // test if we have custom attributes? + if (data.size() >= 3) { + attributes = (Map>) data.get(2); + } + if (attributes == null) { + // try to migrate deprecated 'dms' item...... + // in some cases the DMS item does not contain a Map object + // for that reason we test the object type + // see issue #509 + List vDMS = getItemValue("dms"); + // test if we found a match.... + for (Object aMetadataObject : vDMS) { + // issue #509 + if (aMetadataObject instanceof Map) { + Map aMetadata = (Map) aMetadataObject; + String sName = getStringValueFromMap(aMetadata, "txtname"); + if (sFileName.equals(sName)) { + attributes = aMetadata; + break; + } + } + } + } + result.add(new FileData(sFileName, content, contentType, attributes)); + } + } + return result; + + } + + /** + * This method removes a single file attachment from the workitem + * + */ + @SuppressWarnings("unchecked") + public void removeFile(String aFilename) { + /* delete attachment */ + Map> mapFiles = null; + List vFiles = getItemValue("$file"); + if (vFiles != null && vFiles.size() > 0) { + mapFiles = (Map>) vFiles.get(0); + mapFiles.remove(aFilename); + replaceItemValue("$file", mapFiles); + } + + } + + /** + * Returns files stored in the property '$file'. The files are returned in a Map + * interface where the key is the filename and the value is a list with two + * elements - the ContenType and the file content (byte[]). s Files can be added + * into a ItemCollection using the method addFile(). + * + * @return */ - public Object put(String key, Object value) { - if (key == null) + @Deprecated + @SuppressWarnings("unchecked") + public Map> getFiles() { + logger.warning("method getFiles() is deprecated - replace with getFileData()"); + List vFiles = getItemValue("$file"); + if (vFiles != null && vFiles.size() > 0) { + // test if the value part is a List or an Object[]. In case its an + // Object[] we convert the array to a List + + Map testContent = (Map) vFiles.get(0); + Map> mapFiles = new LinkedHashMap>(); + for (Entry entry : testContent.entrySet()) { + String sFileName = entry.getKey(); + Object obj = entry.getValue(); + if (obj instanceof List) { + mapFiles.put(sFileName, (List) obj); + } else { + // convert array to List + mapFiles.put(sFileName, Arrays.asList(obj)); + } + } + return mapFiles; + } + return null; - itemCollection.replaceItemValue(key, value); - return value; } - /* ############### Default methods ################# */ + /** + * Returns a list of file names attached to the current workitem. File + * Attachments can be added using the method addFile(). + * + * @return + */ + @SuppressWarnings("unchecked") + public List getFileNames() { + // File attachments... + List files = new ArrayList(); + + Map> mapFiles = null; + List vFiles = getItemValue("$file"); + if (vFiles != null && vFiles.size() > 0) { + mapFiles = (Map>) vFiles.get(0); + // files = new String[mapFiles.entrySet().size()]; + Iterator iter = mapFiles.entrySet().iterator(); + while (iter.hasNext()) { + Map.Entry> mapEntry = (Map.Entry>) iter.next(); + String aFileName = mapEntry.getKey().toString(); + files.add(aFileName); + } + } - public void clear() { - itemCollection.getAllItems().clear(); + return files; } - public boolean containsKey(Object key) { - return itemCollection.getAllItems().containsKey(key); + /** + * Returns an ItemAdapter for this instance. + * + * @return + */ + public Map getItem() { + return new ItemAdapter(this); } - public boolean containsValue(Object value) { - return itemCollection.getAllItems().containsValue(value); + /** + * Returns an ItemListAdapter for this instance. + * + * @return + */ + public Map getItemList() { + return new ItemListAdapter(this); } - @SuppressWarnings({"unchecked", "rawtypes"}) - public Set entrySet() { - return itemCollection.getAllItems().entrySet(); + /** + * Returns an ItemListArrayAdapter for this instance. + * + * @return + */ + public Map getItemListArray() { + return new ItemListArrayAdapter(this); } - public boolean isEmpty() { - return itemCollection.getAllItems().isEmpty(); + /** + * @return current type + */ + public String getType() { + return getItemValueString(WorkflowKernel.TYPE); } - @SuppressWarnings({"unchecked", "rawtypes"}) - public Set keySet() { - return itemCollection.getAllItems().keySet(); + /** + * @return current $TaskID + */ + public int getTaskID() { + int result = getItemValueInteger(WorkflowKernel.TASKID); + // test for deprecated version + if (result == 0 && hasItem("$processid") && getItemValueInteger("$processid") != 0) { + // see issue #384 + /* + * logger. + * warning("The field $processid is deprecated. Please use $taskid instead. " + + * "Processing a workitem with an deprecated $processid is still supported."); + */ + result = getItemValueInteger("$processid"); + // update missing taskID + replaceItemValue(WorkflowKernel.TASKID, result); + } + return result; } - @SuppressWarnings({"unchecked", "rawtypes"}) - public void putAll(Map m) { - itemCollection.getAllItems().putAll(m); + /** + * set $taskID + * + * @param taskID + */ + public void setTaskID(int taskID) { + replaceItemValue(WorkflowKernel.TASKID, taskID); + // deprecated processID is still supported for a long period. See issue #384 + replaceItemValue("$processid", taskID); + } + public ItemCollection task(int taskID) { + setTaskID(taskID); + return this; } - public Object remove(Object key) { - return itemCollection.getAllItems().remove(key); + /** + * @return current $EventID + */ + public int getEventID() { + // test for deprecated version + int result = getItemValueInteger(WorkflowKernel.EVENTID); + if (result == 0 && hasItem("$activityid") && getItemValueInteger("$activityid") != 0) { + logger.warning("The field $activityid is deprecated. Please use $eventid instead. " + + "Processing a workitem with an deprecated $activityid is still supported."); + result = getItemValueInteger("$activityid"); + // update eventID + replaceItemValue(WorkflowKernel.EVENTID, result); + } + return result; } - public int size() { - return itemCollection.getAllItems().size(); + /** + * set $eventID + * + * @param eventID + */ + public void setEventID(int eventID) { + replaceItemValue(WorkflowKernel.EVENTID, eventID); + + // if deprectaed ActivityID exists we must still support it + if (hasItem("$activityid")) { + replaceItemValue("$activityid", eventID); + } } - @SuppressWarnings({"unchecked", "rawtypes"}) - public Collection values() { - return itemCollection.getAllItems().values(); + /** + * Set the event id for a workitem. If a event id is already set, the method + * appends the event to the ACTIVITYIDLIST + * + * @param eventID + * @return + */ + public ItemCollection event(int eventID) { + if (this.getEventID() == 0) { + setEventID(eventID); + } else { + // set + appendItemValue(WorkflowKernel.ACTIVITYIDLIST, eventID); + } + return this; } - } + /** + * @return current $ModelVersion + */ + public String getModelVersion() { + return getItemValueString(WorkflowKernel.MODELVERSION); + } - /** - * This class helps to addapt the behavior of a multivalue item to be used in a jsf page using a - * expression language like this: - * - * #{mybean.item['txtMyList']} - * - * - * @author rsoika - * - */ - class ItemListAdapter extends ItemAdapter { + /** + * set the $ModelVersion + */ + public void setModelVersion(String modelversion) { + replaceItemValue(WorkflowKernel.MODELVERSION, modelversion); + } - public ItemListAdapter(ItemCollection acol) { - itemCollection = acol; + public ItemCollection model(String modelversion) { + setModelVersion(modelversion); + return this; } /** - * returns a multi value out of the ItemCollection if the key dos not exist the method will - * create a value automatical + * @return current $ModelVersion */ - public Object get(Object key) { - // check if a value for this key is available... - // if not create a new empty value - if (!itemCollection.hasItem(key.toString())) - itemCollection.replaceItemValue(key.toString(), ""); + public String getWorkflowGroup() { + return getItemValueString(WorkflowKernel.WORKFLOWGROUP); + } - return itemCollection.getItemValue(key.toString()); + /** + * set the $ModelVersion + */ + public void setWorkflowGroup(String group) { + replaceItemValue(WorkflowKernel.WORKFLOWGROUP, group); } - } + public ItemCollection workflowGroup(String group) { + setWorkflowGroup(group); + return this; + } - class ItemListArrayAdapter extends ItemAdapter { + /** + * @return $UniqueID + */ + public String getUniqueID() { + return getItemValueString(WorkflowKernel.UNIQUEID); + } - public ItemListArrayAdapter(ItemCollection acol) { - itemCollection = acol; + /** + * This method is deprecated. Use instead getTaskID() + * + * @return current $processID + */ + @Deprecated + public int getProcessID() { + int result = getItemValueInteger(WorkflowKernel.PROCESSID); + if (result == 0 && hasItem("$taskid")) { + result = getTaskID(); + } + return result; } /** - * returns a multi value out of the ItemCollection if the key dos not exist the method will - * create a value automatical + * This method is deprecated. Use instead getEventID() + * + * @return current $ActivityID */ - @SuppressWarnings("rawtypes") - public Object get(Object key) { - // check if a value for this key is available... - // if not create a new empty value - if (!itemCollection.hasItem(key.toString())) - itemCollection.replaceItemValue(key.toString(), ""); - // return new ArrayList Object containing values from vector - ArrayList aList = new ArrayList(); - Collection col = itemCollection.getItemValue(key.toString()); - for (Object aEntryValue : col) { - aList.add(aEntryValue); - } - return aList; + @Deprecated + public int getActivityID() { + return getEventID(); + } + /** + * set $ActivityID. This method is deprecated. Use instead setEventID() + * + * @param activityID + */ + @Deprecated + public void setActivityID(int activityID) { + replaceItemValue("$activityid", activityID); + // set new field $eventID + setEventID(activityID); } /** - * puts a arraylist value into the ItemCollection + * This method converts the raw java types String, int, long, float and double. + * + * The method returns null if the type is no raw type. + * + * @param value + * @param type + * @return */ - @SuppressWarnings({"rawtypes", "unchecked"}) - public Object put(String key, Object value) { - if (key == null) - return null; + @SuppressWarnings("unchecked") + private T convertValue(Object value, Type type) { + + // test String + if (type == String.class) { + if (value == null) { + // return empty if not present + return (T) ""; + } else { + return (T) value.toString(); + } + } + + // test Integer/int + if (type == Integer.class || type == int.class) { + try { + if (value == null) { + // return 0 if not present + return (T) Integer.valueOf(0); + } + int intvalue = new Double(value.toString()).intValue(); + return (T) Integer.valueOf(intvalue); + } catch (NumberFormatException e) { + return (T) Integer.valueOf(0); + } catch (ClassCastException e) { + return (T) Integer.valueOf(0); + } + } + + // test Long/long + if (type == Long.class || type == long.class) { + try { + if (value == null) { + // return 0 if not present + return (T) Long.valueOf(0); + } + long longvalue = new Long(value.toString()).longValue(); + return (T) Long.valueOf(longvalue); + } catch (NumberFormatException e) { + return (T) Long.valueOf(0); + } catch (ClassCastException e) { + return (T) Long.valueOf(0); + } + } - // skipp null values - if (value == null) { - itemCollection.replaceItemValue(key, new ArrayList()); return null; - } - // convert List into Vector object - if (value instanceof List || value instanceof Object[]) { - List v = new ArrayList(); - // check type of list (array and list are supported but need - // to be read in different ways - if (value instanceof List) - for (Object aEntryValue : (List) value) { - v.add(aEntryValue); - } - else if (value instanceof Object[]) - for (Object aEntryValue : (Object[]) value) { - v.add(aEntryValue); - } - itemCollection.replaceItemValue(key, v); - } else - // non convertable object! - itemCollection.replaceItemValue(key, value); - - return value; - } - } + } + + /** + * Helper method to replace an ItemValue. + * + * @param itemName - name of the value + * @param itemValue - value + * @param append - true if the value should be appended to an existing list + */ + @SuppressWarnings("unchecked") + private void setItemValue(String itemName, Object itemValue, boolean append) { + List itemValueList = null; + + if (itemName == null) + return; + // lower case itemname + itemName = itemName.toLowerCase().trim(); + + // test if value is null + if (itemValue == null) { + // remove the item? + if (!append) { + this.removeItem(itemName); + } + return; + } + + // test if value is ItemCollection + if (itemValue instanceof ItemCollection) { + // just warn - do not remove + logger.warning("replaceItemValue '" + itemName + + "': ItemCollection can not be stored into an existing ItemCollection - use XMLItemCollection instead."); + } + + // test if value is serializable + if (!(itemValue instanceof java.io.Serializable)) { + logger.warning("replaceItemValue '" + itemName + "': object is not serializable!"); + this.removeItem(itemName); + return; + } + + // test if value is a list and remove null values + if (itemValue instanceof List) { + itemValueList = (List) itemValue; + itemValueList.removeAll(Collections.singleton(null)); + // scan List for null values and remove them + for (int i = 0; i < itemValueList.size(); i++) { + // test if ItemCollection + if (itemValueList.get(i) instanceof ItemCollection) { + // just warn - do not remove + logger.warning("replaceItemValue '" + itemName + + "': ItemCollection can not be stored into an existing ItemCollection - use XMLItemCollection instead."); + } + } + } else { + // create an instance of an ArrayList + itemValueList = new ArrayList(); + itemValueList.add(itemValue); + } + + // now itemValue is of instance List + convertItemValue(itemValueList); + if (!validateItemValue(itemValueList)) { + String message = "setItemValue failed for item '" + itemName + + "', the value is a non supported object type: " + itemValue.getClass().getName() + " value=" + + itemValueList; + logger.warning(message); + throw new InvalidAccessException(message); + } + + // replace item value? + if (append) { + // append item value + List oldValueList = (List) getItemValue(itemName); + + oldValueList.addAll(itemValueList); + + hash.put(itemName, (List) oldValueList); + } else + hash.put(itemName, itemValueList); + + } + + /** + * This method converts specific itemValue in standardized object classes: + *

+ * java.util.Calendar => java.util.Date + *

+ * java.time.LocalDateTime => java.util.Date + * + * + * @param itemValue + * @return + */ + private void convertItemValue(List itemValues) { + + ListIterator iterator = itemValues.listIterator(); + while (iterator.hasNext()) { + Object o = iterator.next(); + if (o instanceof Calendar) { + Date date = ((Calendar) o).getTime(); + iterator.set(date); + } + if (o instanceof LocalDateTime) { + LocalDateTime ldt = (LocalDateTime) o; + Date date = Date.from(ldt.atZone(ZoneId.systemDefault()).toInstant()); + iterator.set(date); + } + } + } + + /** + * This method validates of a itemValue is acceptable for the ItemCollection. + * Only basic types are supported. + * + * @param itemValue + * @return + */ + @SuppressWarnings("rawtypes") + private boolean validateItemValue(Object itemValue) { + + // first we test if basic type? + if (isBasicType(itemValue)) { + return true; + } + + // list? + if ((itemValue instanceof List)) { + for (Object singleValue : (List) itemValue) { + if (!validateItemValue(singleValue)) { + return false; + } + } + return true; + } else + + // array? + if (itemValue != null && itemValue.getClass().isArray()) { + for (int i = 0; i < Array.getLength(itemValue); i++) { + Object singleValue = Array.get(itemValue, i); + if (!validateItemValue(singleValue)) { + return false; + } + } + return true; + } else + + // map? + if ((itemValue instanceof Map)) { + Map map = (Map) itemValue; + for (Object value : map.values()) { + if (!validateItemValue(value)) { + return false; + } + } + return true; + } + + // unknown type + return false; + } + + /** + * This helper method test if an object is a basic type which can be stored in + * an ItemCollection. + * + * Validate for raw objects and class types java.lang.*, java.math.* + * + * @return + */ + @SuppressWarnings("rawtypes") + private static boolean isBasicType(java.lang.Object o) { + + if (o == null) { + return true; + } + // test raw array types first + if (o instanceof byte[] || o instanceof boolean[] || o instanceof short[] || o instanceof char[] + || o instanceof int[] || o instanceof long[] || o instanceof float[] || o instanceof double[] + || o instanceof XMLItem[] || o instanceof XMLDocument[]) { + return true; + } + + // test package name + Class c = o.getClass(); + String name = c.getName(); + if (name.startsWith("java.lang.") || name.startsWith("java.math.") || "java.util.Date".equals(name) + || "org.imixs.workflow.xml.XMLItem".equals(name) || "org.imixs.workflow.xml.XMLDocument".equals(name)) { + return true; + } + + // no basic type + return false; + } + + /** + * This helper method makes a deep copy of a map by serializing and + * deserializing. + * + * It is assumed that all elements in the object's source graph are + * serializable. + * + * @see http://www.javaworld.com/article/2077578/learn-java/java-tip-76--an-alternative-to-the-deep-copy-technique.html + * @param map + * @return + */ + private Object deepCopyOfMap(Map> map) { + try { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(bos); + // serialize and pass the object + oos.writeObject(map); + oos.flush(); + ByteArrayInputStream bais = new ByteArrayInputStream(bos.toByteArray()); + ObjectInputStream ois = new ObjectInputStream(bais); + return ois.readObject(); + } catch (IOException e) { + logger.warning("Unable to clone values of ItemCollection - " + e); + return null; + } catch (ClassNotFoundException e) { + logger.warning("Unable to clone values of ItemCollection - " + e); + return null; + } + } + + /** + * This is a helper method to check a string value of a map. + * + * @return String object of a named key + */ + @SuppressWarnings({ "unchecked", "unused" }) + private static String getStringValueFromMap(Map> hash, String aName) { + + List v = null; + + if (aName == null) { + return null; + } + aName = aName.toLowerCase().trim(); + + Object obj = hash.get(aName); + if (obj == null) { + return ""; + } + + if (obj instanceof List) { + + List oList = (List) obj; + if (oList == null) + v = new Vector(); + else { + v = oList; + // scan vector for null values + for (int i = 0; i < v.size(); i++) { + if (v.get(i) == null) + v.remove(i); + } + } + + if (v.size() == 0) + return ""; + else { + // verify if value is null + Object o = v.get(0); + if (o == null) + return ""; + else + return o.toString(); + } + } else { + // Value is not a list! + logger.warning("getStringValueFromMap - wrong value object found '" + aName + "'"); + return obj.toString(); + } + } + + /** + * This class helps to adapt the behavior of a single value item to be used in a + * jsf page using a expression language like this: + * + * #{mybean.item['txtMyItem']} + * + * + * @author rsoika + * + */ + class ItemAdapter implements Map { + ItemCollection itemCollection; + + public ItemAdapter() { + itemCollection = new ItemCollection(); + } + + public ItemAdapter(ItemCollection acol) { + itemCollection = acol; + } + + public void setItemCollection(ItemCollection acol) { + itemCollection = acol; + } + + /** + * returns a single value out of the ItemCollection if the key does not exist + * the method will create a value automatically + */ + public Object get(Object key) { + // check if a value for this key is available... + // if not create a new empty value + if (!itemCollection.hasItem(key.toString())) + itemCollection.replaceItemValue(key.toString(), ""); + + // return first value from vector if size >0 + List v = itemCollection.getItemValue(key.toString()); + if (v.size() > 0) + return v.get(0); + else + // otherwise return null + return null; + } + + /** + * puts a single value into the ItemCollection + */ + public Object put(String key, Object value) { + if (key == null) + return null; + itemCollection.replaceItemValue(key, value); + return value; + } + + /* ############### Default methods ################# */ + + public void clear() { + itemCollection.getAllItems().clear(); + } + + public boolean containsKey(Object key) { + return itemCollection.getAllItems().containsKey(key); + } + + public boolean containsValue(Object value) { + return itemCollection.getAllItems().containsValue(value); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public Set entrySet() { + return itemCollection.getAllItems().entrySet(); + } + + public boolean isEmpty() { + return itemCollection.getAllItems().isEmpty(); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public Set keySet() { + return itemCollection.getAllItems().keySet(); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public void putAll(Map m) { + itemCollection.getAllItems().putAll(m); + + } + + public Object remove(Object key) { + return itemCollection.getAllItems().remove(key); + } + + public int size() { + return itemCollection.getAllItems().size(); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public Collection values() { + return itemCollection.getAllItems().values(); + } + + } + + /** + * This class helps to addapt the behavior of a multivalue item to be used in a + * jsf page using a expression language like this: + * + * #{mybean.item['txtMyList']} + * + * + * @author rsoika + * + */ + class ItemListAdapter extends ItemAdapter { + + public ItemListAdapter(ItemCollection acol) { + itemCollection = acol; + } + + /** + * returns a multi value out of the ItemCollection if the key dos not exist the + * method will create a value automatical + */ + public Object get(Object key) { + // check if a value for this key is available... + // if not create a new empty value + if (!itemCollection.hasItem(key.toString())) + itemCollection.replaceItemValue(key.toString(), ""); + + return itemCollection.getItemValue(key.toString()); + } + + } + + class ItemListArrayAdapter extends ItemAdapter { + + public ItemListArrayAdapter(ItemCollection acol) { + itemCollection = acol; + } + + /** + * returns a multi value out of the ItemCollection if the key dos not exist the + * method will create a value automatical + */ + @SuppressWarnings("rawtypes") + public Object get(Object key) { + // check if a value for this key is available... + // if not create a new empty value + if (!itemCollection.hasItem(key.toString())) + itemCollection.replaceItemValue(key.toString(), ""); + // return new ArrayList Object containing values from vector + ArrayList aList = new ArrayList(); + Collection col = itemCollection.getItemValue(key.toString()); + for (Object aEntryValue : col) { + aList.add(aEntryValue); + } + return aList; + + } + + /** + * puts a arraylist value into the ItemCollection + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + public Object put(String key, Object value) { + if (key == null) + return null; + + // skipp null values + if (value == null) { + itemCollection.replaceItemValue(key, new ArrayList()); + return null; + } + // convert List into Vector object + if (value instanceof List || value instanceof Object[]) { + List v = new ArrayList(); + // check type of list (array and list are supported but need + // to be read in different ways + if (value instanceof List) + for (Object aEntryValue : (List) value) { + v.add(aEntryValue); + } + else if (value instanceof Object[]) + for (Object aEntryValue : (Object[]) value) { + v.add(aEntryValue); + } + itemCollection.replaceItemValue(key, v); + } else + // non convertable object! + itemCollection.replaceItemValue(key, value); + + return value; + } + } } diff --git a/imixs-workflow-core/src/main/java/org/imixs/workflow/ItemCollectionComparator.java b/imixs-workflow-core/src/main/java/org/imixs/workflow/ItemCollectionComparator.java index a69240efb..d90659ae6 100644 --- a/imixs-workflow-core/src/main/java/org/imixs/workflow/ItemCollectionComparator.java +++ b/imixs-workflow-core/src/main/java/org/imixs/workflow/ItemCollectionComparator.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *
- *  Imixs Workflow 
+/*  
+ *  Imixs-Workflow 
+ *  
  *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
  *  http://www.imixs.com
  *  
@@ -22,10 +22,9 @@
  *      https://github.com/imixs/imixs-workflow
  *  
  *  Contributors:  
- *      Imixs Software Solutions GmbH - initial API and implementation
+ *      Imixs Software Solutions GmbH - Project Management
  *      Ralph Soika - Software Developer
- * 
- *******************************************************************************/ + */ package org.imixs.workflow; @@ -35,8 +34,8 @@ import java.util.Locale; /** - * The ItemCollectionComparator provides a Comparator for ItemColections. The item to be compared - * can be provided in the constructor. + * The ItemCollectionComparator provides a Comparator for ItemColections. The + * item to be compared can be provided in the constructor. *

* Usage: *

@@ -46,84 +45,83 @@ * */ public class ItemCollectionComparator implements Comparator { - private final Collator collator; - private final boolean ascending; - private final String itemName; - - public ItemCollectionComparator(String aItemName, boolean ascending, Locale locale) { - this.collator = Collator.getInstance(locale); - this.ascending = ascending; - this.itemName = aItemName; - } - - /** - * This method sorts by the default locale - * - * @param aItemName - * @param ascending - */ - public ItemCollectionComparator(String aItemName, boolean ascending) { - this.collator = Collator.getInstance(Locale.getDefault()); - this.ascending = ascending; - this.itemName = aItemName; - - } - - /** - * This method sorts by the default locale ascending - * - * @param aItemName - * @param ascending - */ - public ItemCollectionComparator(String aItemName) { - this.collator = Collator.getInstance(Locale.getDefault()); - this.ascending = true; - this.itemName = aItemName; - - } - - public int compare(ItemCollection a, ItemCollection b) { - - // date compare? - if (a.isItemValueDate(itemName)) { - - Date dateA = a.getItemValueDate(itemName); - Date dateB = b.getItemValueDate(itemName); - if (dateA == null && dateB != null) { - return 1; - } - if (dateB == null && dateA != null) { - return -1; - } - if (dateB == null && dateA == null) { - return 0; - } - - int result = dateB.compareTo(dateA); - if (!this.ascending) { - result = -result; - } - return result; + private final Collator collator; + private final boolean ascending; + private final String itemName; + + public ItemCollectionComparator(String aItemName, boolean ascending, Locale locale) { + this.collator = Collator.getInstance(locale); + this.ascending = ascending; + this.itemName = aItemName; + } + + /** + * This method sorts by the default locale + * + * @param aItemName + * @param ascending + */ + public ItemCollectionComparator(String aItemName, boolean ascending) { + this.collator = Collator.getInstance(Locale.getDefault()); + this.ascending = ascending; + this.itemName = aItemName; } - // integer compare? - if (a.isItemValueInteger(itemName)) { - int result = a.getItemValueInteger(itemName) - b.getItemValueInteger(itemName); - if (!this.ascending) { - result = -result; - } - return result; + /** + * This method sorts by the default locale ascending + * + * @param aItemName + * @param ascending + */ + public ItemCollectionComparator(String aItemName) { + this.collator = Collator.getInstance(Locale.getDefault()); + this.ascending = true; + this.itemName = aItemName; } - // String compare - int result = - this.collator.compare(a.getItemValueString(itemName), b.getItemValueString(itemName)); - if (!this.ascending) { - result = -result; + public int compare(ItemCollection a, ItemCollection b) { + + // date compare? + if (a.isItemValueDate(itemName)) { + + Date dateA = a.getItemValueDate(itemName); + Date dateB = b.getItemValueDate(itemName); + if (dateA == null && dateB != null) { + return 1; + } + if (dateB == null && dateA != null) { + return -1; + } + if (dateB == null && dateA == null) { + return 0; + } + + int result = dateB.compareTo(dateA); + if (!this.ascending) { + result = -result; + } + return result; + + } + + // integer compare? + if (a.isItemValueInteger(itemName)) { + int result = a.getItemValueInteger(itemName) - b.getItemValueInteger(itemName); + if (!this.ascending) { + result = -result; + } + return result; + + } + + // String compare + int result = this.collator.compare(a.getItemValueString(itemName), b.getItemValueString(itemName)); + if (!this.ascending) { + result = -result; + } + return result; } - return result; - } } diff --git a/imixs-workflow-core/src/main/java/org/imixs/workflow/Model.java b/imixs-workflow-core/src/main/java/org/imixs/workflow/Model.java index c65f41f9f..44597adb8 100644 --- a/imixs-workflow-core/src/main/java/org/imixs/workflow/Model.java +++ b/imixs-workflow-core/src/main/java/org/imixs/workflow/Model.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *

- *  Imixs Workflow 
+/*  
+ *  Imixs-Workflow 
+ *  
  *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
  *  http://www.imixs.com
  *  
@@ -22,10 +22,9 @@
  *      https://github.com/imixs/imixs-workflow
  *  
  *  Contributors:  
- *      Imixs Software Solutions GmbH - initial API and implementation
+ *      Imixs Software Solutions GmbH - Project Management
  *      Ralph Soika - Software Developer
- * 
- *******************************************************************************/ + */ package org.imixs.workflow; @@ -33,16 +32,19 @@ import org.imixs.workflow.exceptions.ModelException; /** - * The IModel interface defines getter methods to navigate through a Imixs Workflow Model. The - * IModel interface is used by the IModelManager. + * The IModel interface defines getter methods to navigate through a Imixs + * Workflow Model. The IModel interface is used by the + * IModelManager. * - * A Imixs-Workflow Model is defined by a collections of Tasks and Events. A Task defines the state - * of a process instance. The Event defines the transition from one state to another. A Task - * contains informations about the processing state e.g. the name or the status description. A Task - * is uniquely identified by its ID. A Event is unambiguously assigned to a Task and uniquely - * identified by by an ID. + * A Imixs-Workflow Model is defined by a collections of Tasks and Events. A + * Task defines the state of a process instance. The Event defines the + * transition from one state to another. A Task contains informations about the + * processing state e.g. the name or the status description. A Task is uniquely + * identified by its ID. A Event is unambiguously assigned to a Task and + * uniquely identified by by an ID. * - * Task and Event elements are implemented as instances of the class ItemCollection. + * Task and Event elements are implemented as instances of the class + * ItemCollection. * * A Model holds a Definition which contains general model information. * @@ -53,68 +55,69 @@ */ public interface Model { - /** - * Returns the model version. - * - * @return - */ - public String getVersion(); + /** + * Returns the model version. + * + * @return + */ + public String getVersion(); - /** - * Returns the model definition containing general model information (e.g. $ModelVersion). - * - * @return - */ - public ItemCollection getDefinition(); + /** + * Returns the model definition containing general model information (e.g. + * $ModelVersion). + * + * @return + */ + public ItemCollection getDefinition(); - /** - * Returns a Task by its Id. - * - * @param taskid - * @param modelVersion - * @return ItemCollection - */ - public ItemCollection getTask(int taskID) throws ModelException; + /** + * Returns a Task by its Id. + * + * @param taskid + * @param modelVersion + * @return ItemCollection + */ + public ItemCollection getTask(int taskID) throws ModelException; - /** - * Returns a Event by its Id and Task-ID. - * - * @param taskid - * @param eventid - * @param modelVersion - * @return ItemCollection - */ - public ItemCollection getEvent(int taskID, int eventID) throws ModelException; + /** + * Returns a Event by its Id and Task-ID. + * + * @param taskid + * @param eventid + * @param modelVersion + * @return ItemCollection + */ + public ItemCollection getEvent(int taskID, int eventID) throws ModelException; - /** - * Returns all Group definitions. - * - * @return - */ - public List getGroups(); + /** + * Returns all Group definitions. + * + * @return + */ + public List getGroups(); - /** - * Returns all Tasks defined in the model. - * - * @param modelVersion - * @return List org.imixs.workflow.ItemCollection - */ - public List findAllTasks(); + /** + * Returns all Tasks defined in the model. + * + * @param modelVersion + * @return List org.imixs.workflow.ItemCollection + */ + public List findAllTasks(); - /** - * Returns all Events assigned to a task. - * - * @param taskid - * @return Collection org.imixs.workflow.ItemCollection - */ - public List findAllEventsByTask(int taskID); + /** + * Returns all Events assigned to a task. + * + * @param taskid + * @return Collection org.imixs.workflow.ItemCollection + */ + public List findAllEventsByTask(int taskID); - /** - * Returns a list of Tasks assigned to a specific workflow group. - * - * @param group - * @return - */ - public List findTasksByGroup(String group); + /** + * Returns a list of Tasks assigned to a specific workflow group. + * + * @param group + * @return + */ + public List findTasksByGroup(String group); } diff --git a/imixs-workflow-core/src/main/java/org/imixs/workflow/ModelManager.java b/imixs-workflow-core/src/main/java/org/imixs/workflow/ModelManager.java index 2db9b0137..a6dd99cd8 100644 --- a/imixs-workflow-core/src/main/java/org/imixs/workflow/ModelManager.java +++ b/imixs-workflow-core/src/main/java/org/imixs/workflow/ModelManager.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *
- *  Imixs Workflow 
+/*  
+ *  Imixs-Workflow 
+ *  
  *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
  *  http://www.imixs.com
  *  
@@ -22,23 +22,22 @@
  *      https://github.com/imixs/imixs-workflow
  *  
  *  Contributors:  
- *      Imixs Software Solutions GmbH - initial API and implementation
+ *      Imixs Software Solutions GmbH - Project Management
  *      Ralph Soika - Software Developer
- * 
- *******************************************************************************/ + */ package org.imixs.workflow; import org.imixs.workflow.exceptions.ModelException; /** - * The interface ModelManager manages instances of a Model. A Model instance is uniquely identified - * by the ModelVersion. The ModelManager is used by the WorkflowKernel to manage the - * workflow of a workitem. + * The interface ModelManager manages instances of a Model. A Model instance is + * uniquely identified by the ModelVersion. The ModelManager is used by the + * WorkflowKernel to manage the workflow of a workitem. *

- * By analyzing the workitem model version the Workflowkernel determines the corresponding model and - * get the Tasks and Events from the model to process the workitem and assign the workitem to the - * next Task defined by the Model. + * By analyzing the workitem model version the Workflowkernel determines the + * corresponding model and get the Tasks and Events from the model to process + * the workitem and assign the workitem to the next Task defined by the Model. * * * @see Model @@ -47,45 +46,39 @@ */ public interface ModelManager { - /** - * Returns a Model by version. The method throws a ModelException in case the model version did - * not exits. - * - * @param version - * @throws ModelException - * @return Model - */ - public Model getModel(String version) throws ModelException;; - - - /** - * Adds a new Model to the ModelManager. - * - * @param model - * @throws ModelException - */ - public void addModel(Model model) throws ModelException;; + /** + * Returns a Model by version. The method throws a ModelException in case the + * model version did not exits. + * + * @param version + * @throws ModelException + * @return Model + */ + public Model getModel(String version) throws ModelException;; + /** + * Adds a new Model to the ModelManager. + * + * @param model + * @throws ModelException + */ + public void addModel(Model model) throws ModelException;; - /** - * Removes a Model from the ModelManager - * - * @param version - */ - public void removeModel(String version); - - - /** - * Returns a Model matching a given workitem. The method throws a ModelException in case the model - * version did not exits. - * - * @param version - * @throws ModelException - * @return Model - */ - public Model getModelByWorkitem(ItemCollection workitem) throws ModelException; + /** + * Removes a Model from the ModelManager + * + * @param version + */ + public void removeModel(String version); + /** + * Returns a Model matching a given workitem. The method throws a ModelException + * in case the model version did not exits. + * + * @param version + * @throws ModelException + * @return Model + */ + public Model getModelByWorkitem(ItemCollection workitem) throws ModelException; } - - diff --git a/imixs-workflow-core/src/main/java/org/imixs/workflow/Plugin.java b/imixs-workflow-core/src/main/java/org/imixs/workflow/Plugin.java index 6afabd936..cdb45c547 100644 --- a/imixs-workflow-core/src/main/java/org/imixs/workflow/Plugin.java +++ b/imixs-workflow-core/src/main/java/org/imixs/workflow/Plugin.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *

- *  Imixs Workflow 
+/*  
+ *  Imixs-Workflow 
+ *  
  *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
  *  http://www.imixs.com
  *  
@@ -22,22 +22,22 @@
  *      https://github.com/imixs/imixs-workflow
  *  
  *  Contributors:  
- *      Imixs Software Solutions GmbH - initial API and implementation
+ *      Imixs Software Solutions GmbH - Project Management
  *      Ralph Soika - Software Developer
- * 
- *******************************************************************************/ + */ package org.imixs.workflow; import org.imixs.workflow.exceptions.PluginException; /** - * A Plugin defines the interface between the WorkflowKernel and the WorkflowManager. Each Plugin - * have to be registered to the WorkflowKernel by the WorkflowManager. The WorkflowKernel executes - * all registered Plugins when processing a workflow event. A Plugin may throw a PluginException in - * case the execution failed. This will stop the execution of the process method. A Plugin methods - * init() and close() can be implemented by Plugin to initialize or tear down external resources or - * data. + * A Plugin defines the interface between the WorkflowKernel and the + * WorkflowManager. Each Plugin have to be registered to the WorkflowKernel by + * the WorkflowManager. The WorkflowKernel executes all registered Plugins when + * processing a workflow event. A Plugin may throw a PluginException in case the + * execution failed. This will stop the execution of the process method. A + * Plugin methods init() and close() can be implemented by Plugin to initialize + * or tear down external resources or data. * * @author Ralph Soika * @version 2.0 @@ -46,28 +46,30 @@ public interface Plugin { - /** - * This method is called before the WorklfowKernel starts the execution. A plugin can for example - * initialize external resources or data. - * - * @param workflowContext defines the context in which the plugin runs. The context can be used to - * get information about the environment - * - */ - public void init(WorkflowContext workflowContext) throws PluginException; + /** + * This method is called before the WorklfowKernel starts the execution. A + * plugin can for example initialize external resources or data. + * + * @param workflowContext defines the context in which the plugin runs. The + * context can be used to get information about the + * environment + * + */ + public void init(WorkflowContext workflowContext) throws PluginException; - /** - * @param document the workitem to be processed - * @param event the workflow event containing the processing instructions - * @return updated workitem for further processing - */ - public ItemCollection run(ItemCollection document, ItemCollection event) throws PluginException; + /** + * @param document the workitem to be processed + * @param event the workflow event containing the processing instructions + * @return updated workitem for further processing + */ + public ItemCollection run(ItemCollection document, ItemCollection event) throws PluginException; - /** - * This method is called after all plugins are executed by the WorkfloKernel. A plugin my tear - * down external resources. - * - * @param rollbackTransaction indicates if the current transaction will be rolled back. - */ - public void close(boolean rollbackTransaction) throws PluginException; + /** + * This method is called after all plugins are executed by the WorkfloKernel. A + * plugin my tear down external resources. + * + * @param rollbackTransaction indicates if the current transaction will be + * rolled back. + */ + public void close(boolean rollbackTransaction) throws PluginException; } diff --git a/imixs-workflow-core/src/main/java/org/imixs/workflow/PluginDependency.java b/imixs-workflow-core/src/main/java/org/imixs/workflow/PluginDependency.java index 59b66cde4..eb5704ba5 100644 --- a/imixs-workflow-core/src/main/java/org/imixs/workflow/PluginDependency.java +++ b/imixs-workflow-core/src/main/java/org/imixs/workflow/PluginDependency.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *
- *  Imixs Workflow 
+/*  
+ *  Imixs-Workflow 
+ *  
  *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
  *  http://www.imixs.com
  *  
@@ -22,20 +22,20 @@
  *      https://github.com/imixs/imixs-workflow
  *  
  *  Contributors:  
- *      Imixs Software Solutions GmbH - initial API and implementation
+ *      Imixs Software Solutions GmbH - Project Management
  *      Ralph Soika - Software Developer
- * 
- *******************************************************************************/ + */ package org.imixs.workflow; import java.util.List; /** - * A plug-in may optionally implement the interface 'PluginDependency' to indicate dependencies on - * other plug-ins. Plug-in dependencies are validated by the WorkflowKernel during processing a - * workflow event. If a plug-in defined by the BPMN model signals dependencies which are not - * reflected by the current model definition, a warning message is logged. + * A plug-in may optionally implement the interface 'PluginDependency' to + * indicate dependencies on other plug-ins. Plug-in dependencies are validated + * by the WorkflowKernel during processing a workflow event. If a plug-in + * defined by the BPMN model signals dependencies which are not reflected by the + * current model definition, a warning message is logged. * * * @author Ralph Soika @@ -45,11 +45,11 @@ public interface PluginDependency { - /** - * Returns a String list of plugin class names which the currend implementation depends on. - * - */ - public List dependsOn(); - + /** + * Returns a String list of plugin class names which the currend implementation + * depends on. + * + */ + public List dependsOn(); } diff --git a/imixs-workflow-core/src/main/java/org/imixs/workflow/RuleEngine.java b/imixs-workflow-core/src/main/java/org/imixs/workflow/RuleEngine.java index 74425b1ac..e647a6cc9 100644 --- a/imixs-workflow-core/src/main/java/org/imixs/workflow/RuleEngine.java +++ b/imixs-workflow-core/src/main/java/org/imixs/workflow/RuleEngine.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *
- *  Imixs Workflow 
+/*  
+ *  Imixs-Workflow 
+ *  
  *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
  *  http://www.imixs.com
  *  
@@ -22,10 +22,9 @@
  *      https://github.com/imixs/imixs-workflow
  *  
  *  Contributors:  
- *      Imixs Software Solutions GmbH - initial API and implementation
+ *      Imixs Software Solutions GmbH - Project Management
  *      Ralph Soika - Software Developer
- * 
- *******************************************************************************/ + */ package org.imixs.workflow; @@ -50,400 +49,408 @@ /** * The Imixs RuleEngine evaluates a business rule provided by an Event. * - * A business rule can be written in any script language supported by the JVM. The Script Language - * is defined by the property 'txtBusinessRuleEngine' from the current Event element. The script is - * defined by the property 'txtBusinessRule'. + * A business rule can be written in any script language supported by the JVM. + * The Script Language is defined by the property 'txtBusinessRuleEngine' from + * the current Event element. The script is defined by the property + * 'txtBusinessRule'. * - * The Script can access all basic item values from the current workItem and also the event by the - * provided JSON objects 'workitem' and 'event'. + * The Script can access all basic item values from the current workItem and + * also the event by the provided JSON objects 'workitem' and 'event'. * * * // test first value of the workitem attribute 'txtname' * var isValid = ('Anna'==workitem.txtname[0]); * * - * A script can add new values for the current workitem by providing the JSON object 'result'. + * A script can add new values for the current workitem by providing the JSON + * object 'result'. * * * var result={ someitem:'Hello World', somenumber:1}; * * - * Also change values of the event object can be made by the script. These changes will be reflected - * back for further processing. + * Also change values of the event object can be made by the script. These + * changes will be reflected back for further processing. * * * // disable mail * event.keymailenabled='0'; * * - * A script can set the variables 'isValid' and 'followUp' to validate a workItem or set a new - * followUp activity. + * A script can set the variables 'isValid' and 'followUp' to validate a + * workItem or set a new followUp activity. * * * result={ isValid:false }; * * - * If the script set the variable 'isValid' to false then the plugin throws a PluginExcpetion. The - * Plugin evaluates the variables 'errorCode' and errorMessage. If these variables are set by the - * Script then the PluginException will be updates with the corresponding errorCode and the - * 'errorMessage' as params[]. If no errorCode is set then the errorCode of the PluginException will - * default to 'VALIDATION_ERROR'. + * If the script set the variable 'isValid' to false then the plugin throws a + * PluginExcpetion. The Plugin evaluates the variables 'errorCode' and + * errorMessage. If these variables are set by the Script then the + * PluginException will be updates with the corresponding errorCode and the + * 'errorMessage' as params[]. If no errorCode is set then the errorCode of the + * PluginException will default to 'VALIDATION_ERROR'. * - * If the script set the variable 'followUp' the follow-up behavior of the current ActivityEntity - * will be updated. + * If the script set the variable 'followUp' the follow-up behavior of the + * current ActivityEntity will be updated. * - * If a script can not be evaluated by the scriptEngin a PluginExcpetion with the errorCode - * 'INVALID_SCRIPT' will be thrown. + * If a script can not be evaluated by the scriptEngin a PluginExcpetion with + * the errorCode 'INVALID_SCRIPT' will be thrown. * - * NOTE: all variable names are case sensitive! All JSON object elements are lower case! + * NOTE: all variable names are case sensitive! All JSON object elements are + * lower case! * * @author Ralph Soika * @version 3.0 * */ public class RuleEngine { - public static final String DEFAULT_SCRIPT_LANGUAGE = "javascript"; - public static final String INVALID_SCRIPT = "INVALID_SCRIPT"; - private static final HashSet> BASIC_OBJECT_TYPES = getBasicObjectTypes(); - - private static Logger logger = Logger.getLogger(RuleEngine.class.getName()); - - private ScriptEngineManager scriptEngineManager; - private ScriptEngine scriptEngine = null; - - /** - * This method initializes the default script engine. - */ - public RuleEngine() { - super(); - init(DEFAULT_SCRIPT_LANGUAGE); - } - - /** - * This method initializes the script engine. - * - * @param scriptLanguage - */ - public RuleEngine(final String scriptLanguage) { - super(); - init(scriptLanguage); - } - - /** - * This method initializes the script engine. - * - * @param scriptLanguage - */ - void init(final String _scriptLanguage) { - String scriptLanguage = _scriptLanguage; - // set default engine to javascript if no engine is specified - if ("".equals(scriptLanguage)) { - scriptLanguage = DEFAULT_SCRIPT_LANGUAGE; - } - // initialize the script engine... - scriptEngineManager = new ScriptEngineManager(); - scriptEngine = scriptEngineManager.getEngineByName(scriptLanguage); - } - - /** - * Returns the instance of the current scriptEngineManager - * - * @return - */ - public ScriptEngineManager getScriptEngineManager() { - return scriptEngineManager; - } - - /** - * Returns the instance of the current ScriptEngine - * - * @return - */ - public ScriptEngine getScriptEngine() { - return scriptEngine; - } - - /** - * This method evaluates the business rule defined by the provided event. The method returns the - * instance of the evaluated result object which can be used to continue evaluation. If a rule - * evaluation was not successful, the method returns null. - * - * @param adocumentContext - * @param adocumentActivity - * @return ScriptEngine instance - * @throws PluginException - */ - public ItemCollection evaluateBusinessRule(String script, ItemCollection documentContext, - ItemCollection event) throws PluginException { - boolean debug = logger.isLoggable(Level.FINE); - // test if a business rule is defined - if ("".equals(script.trim())) - return null; // nothing to do - - // set activity properties into engine - scriptEngine.put("event", convertItemCollection(event)); - scriptEngine.put("workitem", convertItemCollection(documentContext)); - if (debug) { - logger.finest("......SCRIPT:" + script); + public static final String DEFAULT_SCRIPT_LANGUAGE = "javascript"; + public static final String INVALID_SCRIPT = "INVALID_SCRIPT"; + private static final HashSet> BASIC_OBJECT_TYPES = getBasicObjectTypes(); + + private static Logger logger = Logger.getLogger(RuleEngine.class.getName()); + + private ScriptEngineManager scriptEngineManager; + private ScriptEngine scriptEngine = null; + + /** + * This method initializes the default script engine. + */ + public RuleEngine() { + super(); + init(DEFAULT_SCRIPT_LANGUAGE); } - try { - scriptEngine.eval(script); - } catch (ScriptException e) { - // script not valid - throw new PluginException(RuleEngine.class.getSimpleName(), INVALID_SCRIPT, - "BusinessRule contains invalid script:" + e.getMessage(), e); + + /** + * This method initializes the script engine. + * + * @param scriptLanguage + */ + public RuleEngine(final String scriptLanguage) { + super(); + init(scriptLanguage); } - // get the optional result object - ItemCollection result = convertScriptVariableToItemCollection("result"); - - return result; - } - - /** - * This method converts a JSON String into a JavaScript JSON Object and evaluates a script. - *

- * The JSON Object is set as a input variable named 'data' so that the script can access the json - * structure in an easy way. - *

- * Example: - * var result={}; result.name=data.name; - * - *

- * The method returns an ItemCollection with the result object. - * - * @param json - a JSON data string - * @param script - a Script to be evaluated - * @return an ItemCollection returning the Result Object. - * @throws ScriptException - */ - public ItemCollection evaluateJsonByScript(String json, String script) throws ScriptException { - - // create a data object - scriptEngine.put("data", json); - Object jsonDataObject = scriptEngine.eval("JSON.parse(data);"); - // set the parsed JSON object again as 'data'. - scriptEngine.put("data", jsonDataObject); - // evaluate the script - scriptEngine.eval(script); - // get the result object - ItemCollection result = convertScriptVariableToItemCollection("result"); - return result; - } - - /** - * This method evaluates a boolean expression. The method takes a documentContext as argument. - * - * @param adocumentContext - * @return ScriptEngine instance - * @throws PluginException - */ - public boolean evaluateBooleanExpression(String script, ItemCollection documentContext) - throws PluginException { - boolean debug = logger.isLoggable(Level.FINE); - // test if a business rule is defined - if ("".equals(script.trim())) - return false; // nothing to do - - // set activity properties into engine - scriptEngine.put("workitem", convertItemCollection(documentContext)); - - if (debug) { - logger.finest("......SCRIPT:" + script); + /** + * This method initializes the script engine. + * + * @param scriptLanguage + */ + void init(final String _scriptLanguage) { + String scriptLanguage = _scriptLanguage; + // set default engine to javascript if no engine is specified + if ("".equals(scriptLanguage)) { + scriptLanguage = DEFAULT_SCRIPT_LANGUAGE; + } + // initialize the script engine... + scriptEngineManager = new ScriptEngineManager(); + scriptEngine = scriptEngineManager.getEngineByName(scriptLanguage); } - Object result = null; - try { - result = scriptEngine.eval(script); - } catch (ScriptException e) { - // script not valid - throw new PluginException(RuleEngine.class.getSimpleName(), INVALID_SCRIPT, - "BusinessRule contains invalid script:" + e.getMessage(), e); + + /** + * Returns the instance of the current scriptEngineManager + * + * @return + */ + public ScriptEngineManager getScriptEngineManager() { + return scriptEngineManager; } - if (result instanceof Boolean) { - return (boolean) result; - } else { - return false; + + /** + * Returns the instance of the current ScriptEngine + * + * @return + */ + public ScriptEngine getScriptEngine() { + return scriptEngine; } - } - - /** - * This method evaluates a script variable as an native Script array from the script engine. The - * method returns a Object array with the variable values. If the javaScript var is a String a new - * Array will be created. If the javaScript var is a NativeArray the method tries to create a java - * List object. - * - * See the following examples used by the Rhino JavaScript engine bundled with Java 6. - * http://www.rgagnon.com/javadetails/java-0640.html - * - * @return - */ - public Object[] evaluateNativeScriptArray(String expression) { - Object[] params = null; - boolean debug = logger.isLoggable(Level.FINE); - if (scriptEngine == null) { - logger.severe("evaluateScritpObject error: no script engine! - call run()"); - return null; + + /** + * This method evaluates the business rule defined by the provided event. The + * method returns the instance of the evaluated result object which can be used + * to continue evaluation. If a rule evaluation was not successful, the method + * returns null. + * + * @param adocumentContext + * @param adocumentActivity + * @return ScriptEngine instance + * @throws PluginException + */ + public ItemCollection evaluateBusinessRule(String script, ItemCollection documentContext, ItemCollection event) + throws PluginException { + boolean debug = logger.isLoggable(Level.FINE); + // test if a business rule is defined + if ("".equals(script.trim())) + return null; // nothing to do + + // set activity properties into engine + scriptEngine.put("event", convertItemCollection(event)); + scriptEngine.put("workitem", convertItemCollection(documentContext)); + if (debug) { + logger.finest("......SCRIPT:" + script); + } + try { + scriptEngine.eval(script); + } catch (ScriptException e) { + // script not valid + throw new PluginException(RuleEngine.class.getSimpleName(), INVALID_SCRIPT, + "BusinessRule contains invalid script:" + e.getMessage(), e); + } + + // get the optional result object + ItemCollection result = convertScriptVariableToItemCollection("result"); + + return result; } - // first test if expression is a basic string var - Object objectResult = scriptEngine.get(expression); - if (objectResult != null && objectResult instanceof String) { - // just return a simple array with one value - params = new String[1]; - params[0] = objectResult.toString(); - return params; + /** + * This method converts a JSON String into a JavaScript JSON Object and + * evaluates a script. + *

+ * The JSON Object is set as a input variable named 'data' so that the script + * can access the json structure in an easy way. + *

+ * Example: + * var result={}; result.name=data.name; + * + *

+ * The method returns an ItemCollection with the result object. + * + * @param json - a JSON data string + * @param script - a Script to be evaluated + * @return an ItemCollection returning the Result Object. + * @throws ScriptException + */ + public ItemCollection evaluateJsonByScript(String json, String script) throws ScriptException { + + // create a data object + scriptEngine.put("data", json); + Object jsonDataObject = scriptEngine.eval("JSON.parse(data);"); + // set the parsed JSON object again as 'data'. + scriptEngine.put("data", jsonDataObject); + // evaluate the script + scriptEngine.eval(script); + // get the result object + ItemCollection result = convertScriptVariableToItemCollection("result"); + return result; } - // now try to pass the object to engine and convert it into a - // ArryList.... - try { - // Nashorn: check for importClass function and then load if missing - // See: issue #124 - String jsNashorn = - " if (typeof importClass != 'function') { load('nashorn:mozilla_compat.js');}"; - - String jsCode = "importPackage(java.util);" + "var _evaluateScriptParam = Arrays.asList(" - + expression + "); "; - // pass a collection from javascript to java; - scriptEngine.eval(jsNashorn + jsCode); - - @SuppressWarnings("unchecked") - List resultList = (List) scriptEngine.get("_evaluateScriptParam"); - if (resultList == null) { - return null; - } - if ("[undefined]".equals(resultList.toString())) { - return null; - } - // logging - if (debug) { - logger.finest("......evalueateScript object to Java"); - for (Object val : resultList) { - logger.finest(" " + val.toString()); + /** + * This method evaluates a boolean expression. The method takes a + * documentContext as argument. + * + * @param adocumentContext + * @return ScriptEngine instance + * @throws PluginException + */ + public boolean evaluateBooleanExpression(String script, ItemCollection documentContext) throws PluginException { + boolean debug = logger.isLoggable(Level.FINE); + // test if a business rule is defined + if ("".equals(script.trim())) + return false; // nothing to do + + // set activity properties into engine + scriptEngine.put("workitem", convertItemCollection(documentContext)); + + if (debug) { + logger.finest("......SCRIPT:" + script); + } + Object result = null; + try { + result = scriptEngine.eval(script); + } catch (ScriptException e) { + // script not valid + throw new PluginException(RuleEngine.class.getSimpleName(), INVALID_SCRIPT, + "BusinessRule contains invalid script:" + e.getMessage(), e); + } + if (result instanceof Boolean) { + return (boolean) result; + } else { + return false; } - } - - return resultList.toArray(); - } catch (ScriptException se) { - // not convertable! - // se.printStackTrace(); - if (debug) { - logger.finest("......error evaluating " + expression + " - " + se.getMessage()); - } - return null; } - } - - /** - * This method converts the values of an ItemCollection into a Map Object with Arrays of Objects - * for each value - * - * @param itemCol - * @return - */ - private Map convertItemCollection(ItemCollection itemCol) { - Map result = new HashMap(); - Map> itemList = itemCol.getAllItems(); - for (Map.Entry> entry : itemList.entrySet()) { - String key = entry.getKey().toLowerCase(); - List value = (List) entry.getValue(); - // do only put basic values - if (value.size() > 0) { - if (isBasicObjectType(value.get(0).getClass())) { - result.put(key, value.toArray()); + /** + * This method evaluates a script variable as an native Script array from the + * script engine. The method returns a Object array with the variable values. If + * the javaScript var is a String a new Array will be created. If the javaScript + * var is a NativeArray the method tries to create a java List object. + * + * See the following examples used by the Rhino JavaScript engine bundled with + * Java 6. http://www.rgagnon.com/javadetails/java-0640.html + * + * @return + */ + public Object[] evaluateNativeScriptArray(String expression) { + Object[] params = null; + boolean debug = logger.isLoggable(Level.FINE); + if (scriptEngine == null) { + logger.severe("evaluateScritpObject error: no script engine! - call run()"); + return null; } - } - } - return result; - } - - /** - * This method converts a JSON variable by name into a ItemCollection. The variable is expected as - * a JSON object holding single values or arrays in the following format - * - * - * - * {'single_item':'Hello World', 'multi_item':[ 'Hello World', 'Hello Imixs' ] }; - * - * - * - * The converted object is expected as an Map interface. - * - * @param engine - * @return ItemCollection holding the item values of the variable or null if no variable with the - * given name exists or the variable has not properties. - * @throws ScriptException - */ - @SuppressWarnings({"unchecked", "rawtypes"}) - public ItemCollection convertScriptVariableToItemCollection(String variable) { - ItemCollection result = null; - boolean debug = logger.isLoggable(Level.FINE); - // get result object from engine - Map scriptResult = (Map) scriptEngine.get(variable); - // test if the json object exists and has child objects... - if (scriptResult != null) { - result = new ItemCollection(); - // evaluate values if available... - if (scriptResult.entrySet().size() > 0) { - // iterate over all entries - for (Map.Entry entry : scriptResult.entrySet()) { - - // test if the entry value is a single object or an array.... - if (isBasicObjectType(entry.getValue().getClass())) { - // single value - build array.... - if (debug) { - logger.finest("......adding " + variable + " property " + entry.getKey()); + // first test if expression is a basic string var + Object objectResult = scriptEngine.get(expression); + if (objectResult != null && objectResult instanceof String) { + // just return a simple array with one value + params = new String[1]; + params[0] = objectResult.toString(); + return params; + } + + // now try to pass the object to engine and convert it into a + // ArryList.... + try { + // Nashorn: check for importClass function and then load if missing + // See: issue #124 + String jsNashorn = " if (typeof importClass != 'function') { load('nashorn:mozilla_compat.js');}"; + + String jsCode = "importPackage(java.util);" + "var _evaluateScriptParam = Arrays.asList(" + expression + + "); "; + // pass a collection from javascript to java; + scriptEngine.eval(jsNashorn + jsCode); + + @SuppressWarnings("unchecked") + List resultList = (List) scriptEngine.get("_evaluateScriptParam"); + if (resultList == null) { + return null; } - List list = new ArrayList(); - list.add(entry.getValue()); - result.replaceItemValue(entry.getKey(), list); - } else { - // test if array... - String expression = "result['" + entry.getKey() + "']"; - Object[] oScript = evaluateNativeScriptArray(expression); - if (oScript == null) { - continue; + if ("[undefined]".equals(resultList.toString())) { + return null; } + // logging + if (debug) { + logger.finest("......evalueateScript object to Java"); + for (Object val : resultList) { + logger.finest(" " + val.toString()); + } + } + + return resultList.toArray(); + } catch (ScriptException se) { + // not convertable! + // se.printStackTrace(); if (debug) { - logger.finest("......adding " + variable + " property " + entry.getKey()); + logger.finest("......error evaluating " + expression + " - " + se.getMessage()); } - List list = new ArrayList(Arrays.asList(oScript)); - result.replaceItemValue(entry.getKey(), list); - } + return null; } - } + + } + + /** + * This method converts the values of an ItemCollection into a Map Object with + * Arrays of Objects for each value + * + * @param itemCol + * @return + */ + private Map convertItemCollection(ItemCollection itemCol) { + Map result = new HashMap(); + Map> itemList = itemCol.getAllItems(); + for (Map.Entry> entry : itemList.entrySet()) { + String key = entry.getKey().toLowerCase(); + List value = (List) entry.getValue(); + // do only put basic values + if (value.size() > 0) { + if (isBasicObjectType(value.get(0).getClass())) { + result.put(key, value.toArray()); + } + + } + } + return result; + } + + /** + * This method converts a JSON variable by name into a ItemCollection. The + * variable is expected as a JSON object holding single values or arrays in the + * following format + * + * + * + * {'single_item':'Hello World', 'multi_item':[ 'Hello World', 'Hello Imixs' ] + * }; + * + * + * + * The converted object is expected as an Map interface. + * + * @param engine + * @return ItemCollection holding the item values of the variable or null if no + * variable with the given name exists or the variable has not + * properties. + * @throws ScriptException + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public ItemCollection convertScriptVariableToItemCollection(String variable) { + ItemCollection result = null; + boolean debug = logger.isLoggable(Level.FINE); + // get result object from engine + Map scriptResult = (Map) scriptEngine.get(variable); + // test if the json object exists and has child objects... + if (scriptResult != null) { + result = new ItemCollection(); + // evaluate values if available... + if (scriptResult.entrySet().size() > 0) { + // iterate over all entries + for (Map.Entry entry : scriptResult.entrySet()) { + + // test if the entry value is a single object or an array.... + if (isBasicObjectType(entry.getValue().getClass())) { + // single value - build array.... + if (debug) { + logger.finest("......adding " + variable + " property " + entry.getKey()); + } + List list = new ArrayList(); + list.add(entry.getValue()); + result.replaceItemValue(entry.getKey(), list); + } else { + // test if array... + String expression = "result['" + entry.getKey() + "']"; + Object[] oScript = evaluateNativeScriptArray(expression); + if (oScript == null) { + continue; + } + if (debug) { + logger.finest("......adding " + variable + " property " + entry.getKey()); + } + List list = new ArrayList(Arrays.asList(oScript)); + result.replaceItemValue(entry.getKey(), list); + } + } + } + } + return result; + } + + private static boolean isBasicObjectType(Class clazz) { + return BASIC_OBJECT_TYPES.contains(clazz); + } + + private static HashSet> getBasicObjectTypes() { + HashSet> ret = new HashSet>(); + ret.add(Boolean.class); + ret.add(Character.class); + ret.add(Byte.class); + ret.add(Short.class); + ret.add(Integer.class); + ret.add(Long.class); + ret.add(Float.class); + ret.add(Double.class); + ret.add(Void.class); + + ret.add(BigDecimal.class); + ret.add(BigInteger.class); + + ret.add(String.class); + ret.add(Object.class); + ret.add(Date.class); + ret.add(Calendar.class); + ret.add(GregorianCalendar.class); + + return ret; } - return result; - } - - private static boolean isBasicObjectType(Class clazz) { - return BASIC_OBJECT_TYPES.contains(clazz); - } - - private static HashSet> getBasicObjectTypes() { - HashSet> ret = new HashSet>(); - ret.add(Boolean.class); - ret.add(Character.class); - ret.add(Byte.class); - ret.add(Short.class); - ret.add(Integer.class); - ret.add(Long.class); - ret.add(Float.class); - ret.add(Double.class); - ret.add(Void.class); - - ret.add(BigDecimal.class); - ret.add(BigInteger.class); - - ret.add(String.class); - ret.add(Object.class); - ret.add(Date.class); - ret.add(Calendar.class); - ret.add(GregorianCalendar.class); - - return ret; - } } diff --git a/imixs-workflow-core/src/main/java/org/imixs/workflow/SignalAdapter.java b/imixs-workflow-core/src/main/java/org/imixs/workflow/SignalAdapter.java index d62d4f30a..6ae1a9167 100644 --- a/imixs-workflow-core/src/main/java/org/imixs/workflow/SignalAdapter.java +++ b/imixs-workflow-core/src/main/java/org/imixs/workflow/SignalAdapter.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *
- *  Imixs Workflow 
+/*  
+ *  Imixs-Workflow 
+ *  
  *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
  *  http://www.imixs.com
  *  
@@ -22,22 +22,21 @@
  *      https://github.com/imixs/imixs-workflow
  *  
  *  Contributors:  
- *      Imixs Software Solutions GmbH - initial API and implementation
+ *      Imixs Software Solutions GmbH - Project Management
  *      Ralph Soika - Software Developer
- * 
- *******************************************************************************/ + */ package org.imixs.workflow; /** - * A SignalAdapter extends the Adapter Interface. This Adapter can be associated with a BPMN Signal - * Event. A SignalAdapter is called by the WorkfklowKernel during the processing life-cycle before - * the plugin life-cycle. + * A SignalAdapter extends the Adapter Interface. This Adapter can be associated + * with a BPMN Signal Event. A SignalAdapter is called by the WorkfklowKernel + * during the processing life-cycle before the plugin life-cycle. *

* A SignalAdapter can be a CDI implementation. *

- * SignalAdapters are called after the execution of GenericAdapters but before any plugin was - * executed. + * SignalAdapters are called after the execution of GenericAdapters but before + * any plugin was executed. *

* * @author Ralph Soika diff --git a/imixs-workflow-core/src/main/java/org/imixs/workflow/WorkflowContext.java b/imixs-workflow-core/src/main/java/org/imixs/workflow/WorkflowContext.java index f38ad5a1c..04a35634e 100644 --- a/imixs-workflow-core/src/main/java/org/imixs/workflow/WorkflowContext.java +++ b/imixs-workflow-core/src/main/java/org/imixs/workflow/WorkflowContext.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *

- *  Imixs Workflow 
+/*  
+ *  Imixs-Workflow 
+ *  
  *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
  *  http://www.imixs.com
  *  
@@ -22,17 +22,17 @@
  *      https://github.com/imixs/imixs-workflow
  *  
  *  Contributors:  
- *      Imixs Software Solutions GmbH - initial API and implementation
+ *      Imixs Software Solutions GmbH - Project Management
  *      Ralph Soika - Software Developer
- * 
- *******************************************************************************/ + */ package org.imixs.workflow; /** - * This Interface defines the Context which is used to supply a basic enviroment for the exchange - * between a WorkflowManager an the registered Plugin Moduls. Normaly the WorkflowManager - * Implementation itself implents this Interface to provide the Context for the Workflow components. + * This Interface defines the Context which is used to supply a basic enviroment + * for the exchange between a WorkflowManager an the registered Plugin Moduls. + * Normaly the WorkflowManager Implementation itself implents this Interface to + * provide the Context for the Workflow components. * * @author imixs.com * @version 1.0 @@ -41,19 +41,20 @@ public interface WorkflowContext { - /** - * This Methode returns the Runtime enviroment for a workflow Implementation. is usesd to - * initialize the plugin. - * - * @return a Session Object - */ - public Object getSessionContext(); + /** + * This Methode returns the Runtime enviroment for a workflow Implementation. is + * usesd to initialize the plugin. + * + * @return a Session Object + */ + public Object getSessionContext(); - /** - * This method returns an instance of a IModelManager to access model information - * - * @return ModelManager - */ - public ModelManager getModelManager(); + /** + * This method returns an instance of a IModelManager to access model + * information + * + * @return ModelManager + */ + public ModelManager getModelManager(); } diff --git a/imixs-workflow-core/src/main/java/org/imixs/workflow/WorkflowKernel.java b/imixs-workflow-core/src/main/java/org/imixs/workflow/WorkflowKernel.java index eafdadf66..fc80a5e33 100644 --- a/imixs-workflow-core/src/main/java/org/imixs/workflow/WorkflowKernel.java +++ b/imixs-workflow-core/src/main/java/org/imixs/workflow/WorkflowKernel.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *
- *  Imixs Workflow 
+/*  
+ *  Imixs-Workflow 
+ *  
  *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
  *  http://www.imixs.com
  *  
@@ -22,10 +22,9 @@
  *      https://github.com/imixs/imixs-workflow
  *  
  *  Contributors:  
- *      Imixs Software Solutions GmbH - initial API and implementation
+ *      Imixs Software Solutions GmbH - Project Management
  *      Ralph Soika - Software Developer
- * 
- *******************************************************************************/ + */ package org.imixs.workflow; @@ -46,9 +45,10 @@ import org.imixs.workflow.exceptions.ProcessingErrorException; /** - * The Workflowkernel is the core component of this Framework to control the processing of a - * workitem. A Workflowmanager loads an instance of a Workflowkernel and hand over a - * Model and register Plugins for processing one or many workitems. + * The Workflowkernel is the core component of this Framework to control the + * processing of a workitem. A Workflowmanager loads an instance of + * a Workflowkernel and hand over a Model and register + * Plugins for processing one or many workitems. * * @author Ralph Soika * @version 1.1 @@ -57,1116 +57,1115 @@ public class WorkflowKernel { - public static final String MISSING_WORKFLOWCONTEXT = "MISSING_WORKFLOWCONTEXT"; - public static final String UNDEFINED_PROCESSID = "UNDEFINED_PROCESSID"; - public static final String UNDEFINED_ACTIVITYID = "UNDEFINED_ACTIVITYID"; - public static final String UNDEFINED_WORKITEM = "UNDEFINED_WORKITEM"; - public static final String UNDEFINED_PLUGIN_ERROR = "UNDEFINED_PLUGIN_ERROR"; - public static final String ACTIVITY_NOT_FOUND = "ACTIVITY_NOT_FOUND"; - public static final String MODEL_ERROR = "MODEL_ERROR"; - public static final String PLUGIN_NOT_CREATEABLE = "PLUGIN_NOT_CREATEABLE"; - public static final String PLUGIN_NOT_REGISTERED = "PLUGIN_NOT_REGISTERED"; - public static final String PLUGIN_ERROR = "PLUGIN_ERROR"; - - public static final String ISO8601_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS"; - - public static final String UNIQUEID = "$uniqueid"; - public static final String UNIQUEIDSOURCE = "$uniqueidsource"; - public static final String UNIQUEIDVERSIONS = "$uniqueidversions"; - public static final String WORKITEMID = "$workitemid"; - public static final String MODELVERSION = "$modelversion"; - - @Deprecated - public static final String PROCESSID = "$processid"; - - public static final String TASKID = "$taskid"; - public static final String EVENTID = "$eventid"; - - public static final String ACTIVITYIDLIST = "$activityidlist"; - public static final String WORKFLOWGROUP = "$workflowgroup"; - public static final String WORKFLOWSTATUS = "$workflowstatus"; - public static final String ISVERSION = "$isversion"; - public static final String LASTTASK = "$lasttask"; - public static final String LASTEVENT = "$lastevent"; - public static final String LASTEVENTDATE = "$lasteventdate"; - public static final String CREATOR = "$creator"; - public static final String EDITOR = "$editor"; - public static final String LASTEDITOR = "$lasteditor"; - - public static final String CREATED = "$created"; - public static final String MODIFIED = "$modified"; - - public static final String TYPE = "type"; - - public static final int MAXIMUM_ACTIVITYLOGENTRIES = 30; - - private List pluginRegistry = null; - private Map adapterRegistry = null; - - private WorkflowContext ctx = null; - private Vector vectorEdgeHistory = new Vector(); - private List splitWorkitems = null; - private RuleEngine ruleEngine = null; - - private static Logger logger = Logger.getLogger(WorkflowKernel.class.getName()); - - /** - * Constructor initialize the contextObject and plugin vectors - */ - public WorkflowKernel(final WorkflowContext actx) { - // check workflow context - if (actx == null) { - throw new ProcessingErrorException(WorkflowKernel.class.getSimpleName(), - MISSING_WORKFLOWCONTEXT, - "WorkflowKernel can not be initialized: workitemContext is null!"); - } - - ctx = actx; - pluginRegistry = new ArrayList(); - adapterRegistry = new HashMap(); - splitWorkitems = new ArrayList(); - ruleEngine = new RuleEngine(); - } - - /** - * This method generates an immutable universally unique identifier (UUID). A UUID represents a - * 128-bit value. - * - * @see https://docs.oracle.com/javase/8/docs/api/java/util/UUID.html - * - * @return - */ - public static String generateUniqueID() { - String id = UUID.randomUUID().toString(); - return id; - } - - /** - * This method registers a new plugin class. The method throws a PluginException if the class can - * not be registered. - * - * If the new Plugin implements the PluginDependency interface, the method validates dependencies. - * - * @param pluginClass - * @throws PluginException - */ - public void registerPlugin(final Plugin plugin) throws PluginException { - // validate dependencies - if (plugin instanceof PluginDependency) { - List dependencies = ((PluginDependency) plugin).dependsOn(); - for (String dependency : dependencies) { - boolean found = false; - for (Plugin regiseredPlugin : pluginRegistry) { - if (regiseredPlugin.getClass().getName().equals(dependency)) { - found = true; - break; - } + public static final String MISSING_WORKFLOWCONTEXT = "MISSING_WORKFLOWCONTEXT"; + public static final String UNDEFINED_PROCESSID = "UNDEFINED_PROCESSID"; + public static final String UNDEFINED_ACTIVITYID = "UNDEFINED_ACTIVITYID"; + public static final String UNDEFINED_WORKITEM = "UNDEFINED_WORKITEM"; + public static final String UNDEFINED_PLUGIN_ERROR = "UNDEFINED_PLUGIN_ERROR"; + public static final String ACTIVITY_NOT_FOUND = "ACTIVITY_NOT_FOUND"; + public static final String MODEL_ERROR = "MODEL_ERROR"; + public static final String PLUGIN_NOT_CREATEABLE = "PLUGIN_NOT_CREATEABLE"; + public static final String PLUGIN_NOT_REGISTERED = "PLUGIN_NOT_REGISTERED"; + public static final String PLUGIN_ERROR = "PLUGIN_ERROR"; + + public static final String ISO8601_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS"; + + public static final String UNIQUEID = "$uniqueid"; + public static final String UNIQUEIDSOURCE = "$uniqueidsource"; + public static final String UNIQUEIDVERSIONS = "$uniqueidversions"; + public static final String WORKITEMID = "$workitemid"; + public static final String MODELVERSION = "$modelversion"; + + @Deprecated + public static final String PROCESSID = "$processid"; + + public static final String TASKID = "$taskid"; + public static final String EVENTID = "$eventid"; + + public static final String ACTIVITYIDLIST = "$activityidlist"; + public static final String WORKFLOWGROUP = "$workflowgroup"; + public static final String WORKFLOWSTATUS = "$workflowstatus"; + public static final String ISVERSION = "$isversion"; + public static final String LASTTASK = "$lasttask"; + public static final String LASTEVENT = "$lastevent"; + public static final String LASTEVENTDATE = "$lasteventdate"; + public static final String CREATOR = "$creator"; + public static final String EDITOR = "$editor"; + public static final String LASTEDITOR = "$lasteditor"; + + public static final String CREATED = "$created"; + public static final String MODIFIED = "$modified"; + + public static final String TYPE = "type"; + + public static final int MAXIMUM_ACTIVITYLOGENTRIES = 30; + + private List pluginRegistry = null; + private Map adapterRegistry = null; + + private WorkflowContext ctx = null; + private Vector vectorEdgeHistory = new Vector(); + private List splitWorkitems = null; + private RuleEngine ruleEngine = null; + + private static Logger logger = Logger.getLogger(WorkflowKernel.class.getName()); + + /** + * Constructor initialize the contextObject and plugin vectors + */ + public WorkflowKernel(final WorkflowContext actx) { + // check workflow context + if (actx == null) { + throw new ProcessingErrorException(WorkflowKernel.class.getSimpleName(), MISSING_WORKFLOWCONTEXT, + "WorkflowKernel can not be initialized: workitemContext is null!"); } - if (!found) { - logger.warning("Plugin '" + plugin.getClass().getName() - + "' depends on unregistered Plugin class '" + dependency + "'"); - } - } - } - plugin.init(ctx); - pluginRegistry.add(plugin); - } - - /** - * This method registers a new adapter class. - * - * @param adapterClass - */ - public void registerAdapter(final Adapter adapter) { - adapterRegistry.put(adapter.getClass().getName(), adapter); - } - - /** - * This method registers a new plugin based on class name. The plugin will be instantiated by its - * name. The method throws a PluginException if the plugin class can not be created. - * - * @param pluginClass - * @throws PluginException - */ - public void registerPlugin(final String pluginClass) throws PluginException { - - if ((pluginClass != null) && (!"".equals(pluginClass))) { - if (logger.isLoggable(Level.FINEST)) - logger.finest("......register plugin class: " + pluginClass + "..."); - - Class clazz = null; - try { - clazz = Class.forName(pluginClass); - Plugin plugin = (Plugin) clazz.newInstance(); - registerPlugin(plugin); - - } catch (ClassNotFoundException e) { - throw new PluginException(WorkflowKernel.class.getSimpleName(), PLUGIN_NOT_CREATEABLE, - "unable to register plugin: " + pluginClass + " - reason: " + e.toString(), e); - } catch (InstantiationException e) { - throw new PluginException(WorkflowKernel.class.getSimpleName(), PLUGIN_NOT_CREATEABLE, - "unable to register plugin: " + pluginClass + " - reason: " + e.toString(), e); - } catch (IllegalAccessException e) { - throw new PluginException(WorkflowKernel.class.getSimpleName(), PLUGIN_NOT_CREATEABLE, - "unable to register plugin: " + pluginClass + " - reason: " + e.toString(), e); - } + ctx = actx; + pluginRegistry = new ArrayList(); + adapterRegistry = new HashMap(); + splitWorkitems = new ArrayList(); + ruleEngine = new RuleEngine(); } - } - - /** - * This method removes a registered plugin based on its class name. - * - * @param pluginClass - * @throws PluginException if plugin not registered - */ - public void unregisterPlugin(final String pluginClass) throws PluginException { - boolean debug = logger.isLoggable(Level.FINE); - if (debug) { - logger.finest("......unregisterPlugin " + pluginClass); - } - for (Plugin plugin : pluginRegistry) { - if (plugin.getClass().getName().equals(pluginClass)) { - pluginRegistry.remove(plugin); - return; - } + /** + * This method generates an immutable universally unique identifier (UUID). A + * UUID represents a 128-bit value. + * + * @see https://docs.oracle.com/javase/8/docs/api/java/util/UUID.html + * + * @return + */ + public static String generateUniqueID() { + String id = UUID.randomUUID().toString(); + return id; } - // throw PluginExeption - throw new PluginException(WorkflowKernel.class.getSimpleName(), PLUGIN_NOT_REGISTERED, - "unable to unregister plugin: " + pluginClass + " - reason: "); - } - - /** - * This method removes all registered plugins - * - * @param pluginClass - */ - public void unregisterAllPlugins() { - boolean debug = logger.isLoggable(Level.FINE); - if (debug) { - logger.finest("......unregisterAllPlugins..."); + /** + * This method registers a new plugin class. The method throws a PluginException + * if the class can not be registered. + * + * If the new Plugin implements the PluginDependency interface, the method + * validates dependencies. + * + * @param pluginClass + * @throws PluginException + */ + public void registerPlugin(final Plugin plugin) throws PluginException { + // validate dependencies + if (plugin instanceof PluginDependency) { + List dependencies = ((PluginDependency) plugin).dependsOn(); + for (String dependency : dependencies) { + boolean found = false; + for (Plugin regiseredPlugin : pluginRegistry) { + if (regiseredPlugin.getClass().getName().equals(dependency)) { + found = true; + break; + } + } + if (!found) { + logger.warning("Plugin '" + plugin.getClass().getName() + "' depends on unregistered Plugin class '" + + dependency + "'"); + } + } + } + plugin.init(ctx); + pluginRegistry.add(plugin); } - pluginRegistry = new ArrayList(); - } - - /** - * Returns a registry containing all registered plugin instances. - * - * @return - */ - public List getPluginRegistry() { - return pluginRegistry; - } - - /** - * Processes a process instance (workitem) based on the current model definition. A Workitem must - * at least provide the properties $TASKID and $EVENTID. - *

- * During the processing life-cycle more than one event can be processed. This depends on the - * model definition which can define follow-up-events, split-events and conditional events. - *

- * The method executes all plugin and adapter classes and returns an updated instance of the - * workitem. But the method did not persist the process instance. The persitance mechanism is - * covered by the WorkflowService. - * - * @param workitem the process instance to be processed. - * @return updated workitem - * @throws PluginException,ModelException - */ - public ItemCollection process(final ItemCollection workitem) - throws PluginException, ModelException { - - // check document context - if (workitem == null) - throw new ProcessingErrorException(WorkflowKernel.class.getSimpleName(), UNDEFINED_WORKITEM, - "processing error: workitem is null"); - - // check $TaskID - if (workitem.getTaskID() <= 0) - throw new ProcessingErrorException(WorkflowKernel.class.getSimpleName(), UNDEFINED_PROCESSID, - "processing error: $taskID undefined (" + workitem.getTaskID() + ")"); - - // check $eventId - if (workitem.getEventID() <= 0) - throw new ProcessingErrorException(WorkflowKernel.class.getSimpleName(), UNDEFINED_ACTIVITYID, - "processing error: $eventID undefined (" + workitem.getEventID() + ")"); - - // ItemCollection documentResult = new ItemCollection(workitem); - // we do no longer clone the woritem - Issue #507 - ItemCollection documentResult = workitem; - vectorEdgeHistory = new Vector(); - - // Check if $UniqueID is available - if ("".equals(workitem.getItemValueString(UNIQUEID))) { - // generating a new one - documentResult.replaceItemValue(UNIQUEID, generateUniqueID()); + + /** + * This method registers a new adapter class. + * + * @param adapterClass + */ + public void registerAdapter(final Adapter adapter) { + adapterRegistry.put(adapter.getClass().getName(), adapter); } - // store last $lastTask - documentResult.replaceItemValue("$lastTask", workitem.getTaskID()); + /** + * This method registers a new plugin based on class name. The plugin will be + * instantiated by its name. The method throws a PluginException if the plugin + * class can not be created. + * + * @param pluginClass + * @throws PluginException + */ + public void registerPlugin(final String pluginClass) throws PluginException { + + if ((pluginClass != null) && (!"".equals(pluginClass))) { + if (logger.isLoggable(Level.FINEST)) + logger.finest("......register plugin class: " + pluginClass + "..."); + + Class clazz = null; + try { + clazz = Class.forName(pluginClass); + Plugin plugin = (Plugin) clazz.newInstance(); + registerPlugin(plugin); + + } catch (ClassNotFoundException e) { + throw new PluginException(WorkflowKernel.class.getSimpleName(), PLUGIN_NOT_CREATEABLE, + "unable to register plugin: " + pluginClass + " - reason: " + e.toString(), e); + } catch (InstantiationException e) { + throw new PluginException(WorkflowKernel.class.getSimpleName(), PLUGIN_NOT_CREATEABLE, + "unable to register plugin: " + pluginClass + " - reason: " + e.toString(), e); + } catch (IllegalAccessException e) { + throw new PluginException(WorkflowKernel.class.getSimpleName(), PLUGIN_NOT_CREATEABLE, + "unable to register plugin: " + pluginClass + " - reason: " + e.toString(), e); + } - // Check if $WorkItemID is available - if ("".equals(workitem.getItemValueString(WorkflowKernel.WORKITEMID))) { - documentResult.replaceItemValue(WorkflowKernel.WORKITEMID, generateUniqueID()); - } + } - // now process all events defined by the model - while (documentResult.getEventID() > 0) { - // set $lastEventDate - documentResult.replaceItemValue(LASTEVENTDATE, new Date()); - // load event... - ItemCollection event = loadEvent(documentResult); - documentResult = processEvent(documentResult, event); - documentResult = updateEventList(documentResult, event); } - return documentResult; - } - - /** - * Evaluates the next taskID for a process instance (workitem) based on the current model - * definition. A Workitem must at least provide the properties $TASKID and $EVENTID. - *

- * During the evaluation life-cycle more than one event can be evaluated. This depends on the - * model definition which can define follow-up-events, split-events and conditional events. - *

- * The method did not persist the process instance or execute any plugin or adapter class. - * - * @param workitem the process instance to be evaluated. - * @return result TaskID - * @throws PluginException,ModelException - */ - public int eval(final ItemCollection workitem) throws PluginException, ModelException { - - // check document context - if (workitem == null) - throw new ProcessingErrorException(WorkflowKernel.class.getSimpleName(), UNDEFINED_WORKITEM, - "processing error: workitem is null"); - - // check $TaskID - if (workitem.getTaskID() <= 0) - throw new ProcessingErrorException(WorkflowKernel.class.getSimpleName(), UNDEFINED_PROCESSID, - "processing error: $taskID undefined (" + workitem.getTaskID() + ")"); - - // check $eventId - if (workitem.getEventID() <= 0) - throw new ProcessingErrorException(WorkflowKernel.class.getSimpleName(), UNDEFINED_ACTIVITYID, - "processing error: $eventID undefined (" + workitem.getEventID() + ")"); - - // clone the woritem to avoid pollution of the origin workitem - ItemCollection workitemClone = (ItemCollection) workitem.clone(); - - // now evaluate all events defined by the model - while (workitemClone.getEventID() > 0) { - // load event... - ItemCollection event = loadEvent(workitemClone); - ItemCollection task = findNextTask(workitemClone, event); - // Update the attributes $taskID - workitemClone.setTaskID(Integer.valueOf(task.getItemValueInteger("numprocessid"))); - workitemClone = updateEventList(workitemClone, event); - } + /** + * This method removes a registered plugin based on its class name. + * + * @param pluginClass + * @throws PluginException if plugin not registered + */ + public void unregisterPlugin(final String pluginClass) throws PluginException { + boolean debug = logger.isLoggable(Level.FINE); + if (debug) { + logger.finest("......unregisterPlugin " + pluginClass); + } + for (Plugin plugin : pluginRegistry) { + if (plugin.getClass().getName().equals(pluginClass)) { + pluginRegistry.remove(plugin); + return; + } + } - // return the evaluated task id - return workitemClone.getTaskID(); - } - - /** - * This method computes the next task based on a Model Event element. If the event did not point - * to a new task, the current task will be returned. - * - * The method supports the 'conditional-events' and 'split-events'. - * - * A conditional-event contains the attribute 'keyExclusiveConditions' defining conditional - * targets (tasks) or adds conditional follow up events - * - * A split-event contains the attribute 'keySplitConditions' defining the target for the current - * master version (condition evaluates to 'true') - * - * @return Task entity - * @throws ModelException - * @throws PluginException - */ - private ItemCollection findNextTask(ItemCollection documentContext, ItemCollection event) - throws ModelException, PluginException { - boolean debug = logger.isLoggable(Level.FINE); - ItemCollection itemColNextTask = null; - - int iNewProcessID = event.getItemValueInteger("numnextprocessid"); - if (debug) { - logger.finest("......next $taskID=" + iNewProcessID + ""); - } - // test if we have an conditional exclusive Task exits... - itemColNextTask = findConditionalExclusiveTask(event, documentContext); - if (itemColNextTask != null) { - return itemColNextTask; + // throw PluginExeption + throw new PluginException(WorkflowKernel.class.getSimpleName(), PLUGIN_NOT_REGISTERED, + "unable to unregister plugin: " + pluginClass + " - reason: "); } - itemColNextTask = findConditionalSplitTask(event, documentContext); - if (itemColNextTask != null) { - return itemColNextTask; + /** + * This method removes all registered plugins + * + * @param pluginClass + */ + public void unregisterAllPlugins() { + boolean debug = logger.isLoggable(Level.FINE); + if (debug) { + logger.finest("......unregisterAllPlugins..."); + } + pluginRegistry = new ArrayList(); } - // default behavior - if (iNewProcessID > 0) { - itemColNextTask = this.ctx.getModelManager().getModel(documentContext.getModelVersion()) - .getTask(iNewProcessID); - } else { - // get current task... - itemColNextTask = - this.ctx.getModelManager().getModel(documentContext.getItemValueString(MODELVERSION)) - .getTask(documentContext.getTaskID()); - } - return itemColNextTask; - } - - /** - * This method returns new SplitWorkitems evaluated during the last processing life-cycle. - * - * @return - */ - public List getSplitWorkitems() { - return splitWorkitems; - } - - /** - * This method controls the Event-Chain. If the attribute $activityidlist has more valid - * ActivityIDs the next activiytID will be loaded into $activity. - * - **/ - private ItemCollection updateEventList(final ItemCollection documentContext, - final ItemCollection event) { - ItemCollection documentResult = documentContext; - boolean debug = logger.isLoggable(Level.FINE); - // first clear the eventID - documentResult.setEventID(Integer.valueOf(0)); - - // test if a FollowUp event is defined for the given event... - String sFollowUp = event.getItemValueString("keyFollowUp"); - int iNextActivityID = event.getItemValueInteger("numNextActivityID"); - if ("1".equals(sFollowUp) && iNextActivityID > 0) { - // append the next event id..... - documentResult = appendActivityID(documentResult, iNextActivityID); + /** + * Returns a registry containing all registered plugin instances. + * + * @return + */ + public List getPluginRegistry() { + return pluginRegistry; } - // evaluate is $eventid already provided? - if ((documentContext.getEventID() <= 0)) { - // no $eventID provided, so we test for property $ActivityIDList - List vActivityList = documentContext.getItemValue(ACTIVITYIDLIST); - - // remove 0 values if contained! - while (vActivityList.indexOf(Integer.valueOf(0)) > -1) { - vActivityList.remove(vActivityList.indexOf(Integer.valueOf(0))); - } - - // test if an id is found.... - if (vActivityList.size() > 0) { - // yes - load next ID from activityID List - int iNextID = 0; - Object oA = vActivityList.get(0); - if (oA instanceof Integer) - iNextID = ((Integer) oA).intValue(); - if (oA instanceof Double) - iNextID = ((Double) oA).intValue(); - - if (iNextID > 0) { - // load activity - if (debug) { - logger.finest("......processing=" + documentContext.getItemValueString(UNIQUEID) - + " -> loading next activityID = " + iNextID); - } - vActivityList.remove(0); - // update document context - documentResult.setEventID(Integer.valueOf(iNextID)); - documentResult.replaceItemValue(ACTIVITYIDLIST, vActivityList); + /** + * Processes a process instance (workitem) based on the current model + * definition. A Workitem must at least provide the properties $TASKID and + * $EVENTID. + *

+ * During the processing life-cycle more than one event can be processed. This + * depends on the model definition which can define follow-up-events, + * split-events and conditional events. + *

+ * The method executes all plugin and adapter classes and returns an updated + * instance of the workitem. But the method did not persist the process + * instance. The persitance mechanism is covered by the WorkflowService. + * + * @param workitem the process instance to be processed. + * @return updated workitem + * @throws PluginException,ModelException + */ + public ItemCollection process(final ItemCollection workitem) throws PluginException, ModelException { + + // check document context + if (workitem == null) + throw new ProcessingErrorException(WorkflowKernel.class.getSimpleName(), UNDEFINED_WORKITEM, + "processing error: workitem is null"); + + // check $TaskID + if (workitem.getTaskID() <= 0) + throw new ProcessingErrorException(WorkflowKernel.class.getSimpleName(), UNDEFINED_PROCESSID, + "processing error: $taskID undefined (" + workitem.getTaskID() + ")"); + + // check $eventId + if (workitem.getEventID() <= 0) + throw new ProcessingErrorException(WorkflowKernel.class.getSimpleName(), UNDEFINED_ACTIVITYID, + "processing error: $eventID undefined (" + workitem.getEventID() + ")"); + + // ItemCollection documentResult = new ItemCollection(workitem); + // we do no longer clone the woritem - Issue #507 + ItemCollection documentResult = workitem; + vectorEdgeHistory = new Vector(); + + // Check if $UniqueID is available + if ("".equals(workitem.getItemValueString(UNIQUEID))) { + // generating a new one + documentResult.replaceItemValue(UNIQUEID, generateUniqueID()); } - } - } - return documentResult; - } - - /** - * This method processes a single event on a workflow instance. All registered adapter and plug-in - * classes will be executed. - *

- * During the processing life-cycle more than one event can be processed. This depends on the - * model definition which can define follow-up-events, split-events and conditional events. - *

- * After all adapter and plug-in classes have been executed, the attributes type, $taskID, - * $workflowstatus and $workflowgroup are updated based on the definition of the target task - * element. - *

- * In case of an AdapterException, the exception data will be wrapped into items with the prefix - * 'adapter.' - * - * @throws PluginException,ModelException - */ - private ItemCollection processEvent(final ItemCollection documentContext, - final ItemCollection event) throws PluginException, ModelException { - ItemCollection documentResult = documentContext; - boolean debug = logger.isLoggable(Level.FINE); - // log the general processing message - String msg = "⚙ processing: " + documentContext.getItemValueString(UNIQUEID) + " (" - + documentContext.getItemValueString(MODELVERSION) + " ▷ " + documentContext.getTaskID() - + "→" + documentContext.getEventID() + ")"; - - if (ctx == null) { - logger.warning("no WorkflowContext defined!"); - } - logger.info(msg); - - // execute SignalAdapters - executeSignalAdapters(documentResult, event); - - // execute plugins - PluginExceptions will bubble up.... - try { - documentResult = runPlugins(documentResult, event); - } catch (PluginException pe) { - // close plugins - closePlugins(true); - // throw exeption - throw pe; - } - // Successful close plugins - closePlugins(false); - // execute GenericAdapters - executeGenericAdapters(documentResult, event); + // store last $lastTask + documentResult.replaceItemValue("$lastTask", workitem.getTaskID()); - // write event log - documentResult = logEvent(documentResult, event); + // Check if $WorkItemID is available + if ("".equals(workitem.getItemValueString(WorkflowKernel.WORKITEMID))) { + documentResult.replaceItemValue(WorkflowKernel.WORKITEMID, generateUniqueID()); + } - // put current edge in history - vectorEdgeHistory.addElement(event.getItemValueInteger("numprocessid") + "." - + event.getItemValueInteger("numactivityid")); + // now process all events defined by the model + while (documentResult.getEventID() > 0) { + // set $lastEventDate + documentResult.replaceItemValue(LASTEVENTDATE, new Date()); + // load event... + ItemCollection event = loadEvent(documentResult); + documentResult = processEvent(documentResult, event); + documentResult = updateEventList(documentResult, event); + } - // update the next task (can be updated by plugins or conditional events.... - // issue #470 - ItemCollection itemColNextTask = findNextTask(documentResult, event); + return documentResult; + } - // evaluate a split-event and create new versions of the current process - // instance. - evaluateSplitEvent(event, documentResult); + /** + * Evaluates the next taskID for a process instance (workitem) based on the + * current model definition. A Workitem must at least provide the properties + * $TASKID and $EVENTID. + *

+ * During the evaluation life-cycle more than one event can be evaluated. This + * depends on the model definition which can define follow-up-events, + * split-events and conditional events. + *

+ * The method did not persist the process instance or execute any plugin or + * adapter class. + * + * @param workitem the process instance to be evaluated. + * @return result TaskID + * @throws PluginException,ModelException + */ + public int eval(final ItemCollection workitem) throws PluginException, ModelException { + + // check document context + if (workitem == null) + throw new ProcessingErrorException(WorkflowKernel.class.getSimpleName(), UNDEFINED_WORKITEM, + "processing error: workitem is null"); + + // check $TaskID + if (workitem.getTaskID() <= 0) + throw new ProcessingErrorException(WorkflowKernel.class.getSimpleName(), UNDEFINED_PROCESSID, + "processing error: $taskID undefined (" + workitem.getTaskID() + ")"); + + // check $eventId + if (workitem.getEventID() <= 0) + throw new ProcessingErrorException(WorkflowKernel.class.getSimpleName(), UNDEFINED_ACTIVITYID, + "processing error: $eventID undefined (" + workitem.getEventID() + ")"); + + // clone the woritem to avoid pollution of the origin workitem + ItemCollection workitemClone = (ItemCollection) workitem.clone(); + + // now evaluate all events defined by the model + while (workitemClone.getEventID() > 0) { + // load event... + ItemCollection event = loadEvent(workitemClone); + ItemCollection task = findNextTask(workitemClone, event); + // Update the attributes $taskID + workitemClone.setTaskID(Integer.valueOf(task.getItemValueInteger("numprocessid"))); + workitemClone = updateEventList(workitemClone, event); + } - // Update the attributes $taskID and $WorkflowStatus - documentResult.setTaskID(Integer.valueOf(itemColNextTask.getItemValueInteger("numprocessid"))); - if (debug) { - logger.finest("......new $taskID=" + documentResult.getTaskID()); - } - documentResult.replaceItemValue(WORKFLOWSTATUS, itemColNextTask.getItemValueString("txtname")); - documentResult.replaceItemValue(WORKFLOWGROUP, - itemColNextTask.getItemValueString("txtworkflowgroup")); - if (debug) { - logger - .finest("......new $workflowStatus=" + documentResult.getItemValueString(WORKFLOWSTATUS)); - } - // update deprecated attributes txtworkflowStatus and txtworkflowGroup - documentResult.replaceItemValue("txtworkflowStatus", - documentResult.getItemValueString(WORKFLOWSTATUS)); - documentResult.replaceItemValue("txtworkflowGroup", - documentResult.getItemValueString(WORKFLOWGROUP)); - - // update the type attribute if defined. - // the type attribute can only be overwritten by a plug-in if the type is not - // defined by the task! - String sType = itemColNextTask.getItemValueString("txttype"); - if (!"".equals(sType)) { - documentResult.replaceItemValue(TYPE, sType); + // return the evaluated task id + return workitemClone.getTaskID(); } - return documentResult; - } - - /** - * This method executes all SignalAdapters associated with the model. - *

- * A StaticAdaper should not be associated with a BPMN Signal Event. - * - * @param documentResult - * @param event - * @throws PluginException - */ - private void executeSignalAdapters(ItemCollection documentResult, ItemCollection event) - throws PluginException { - boolean debug = logger.isLoggable(Level.FINE); - if (debug) { - logger.finest("......executing SignalAdapters..."); - } - // execute adapters if adapter class is defined.... - String adapterClass = event.getItemValueString("adapter.id"); - if (!adapterClass.isEmpty() && adapterClass.matches("^(?:\\w+|\\w+\\.\\w+)+$")) { - Adapter adapter = adapterRegistry.get(adapterClass); - if (adapter != null) { - - if (adapter instanceof GenericAdapter) { - logger.warning("...GenericAdapter '" + adapterClass - + "' should not be associated with a Signal Event!"); - // ...stop execution as the GenericAdapter was already executed by the method - // executeGenericAdapters... - } else { - // execute only instance of signal Adapters... - if (adapter instanceof SignalAdapter) { - executeAdaper(adapter, documentResult, event); + /** + * This method computes the next task based on a Model Event element. If the + * event did not point to a new task, the current task will be returned. + * + * The method supports the 'conditional-events' and 'split-events'. + * + * A conditional-event contains the attribute 'keyExclusiveConditions' defining + * conditional targets (tasks) or adds conditional follow up events + * + * A split-event contains the attribute 'keySplitConditions' defining the target + * for the current master version (condition evaluates to 'true') + * + * @return Task entity + * @throws ModelException + * @throws PluginException + */ + private ItemCollection findNextTask(ItemCollection documentContext, ItemCollection event) + throws ModelException, PluginException { + boolean debug = logger.isLoggable(Level.FINE); + ItemCollection itemColNextTask = null; + + int iNewProcessID = event.getItemValueInteger("numnextprocessid"); + if (debug) { + logger.finest("......next $taskID=" + iNewProcessID + ""); + } + // test if we have an conditional exclusive Task exits... + itemColNextTask = findConditionalExclusiveTask(event, documentContext); + if (itemColNextTask != null) { + return itemColNextTask; + } - } else { - throw new PluginException(WorkflowKernel.class.getSimpleName(), PLUGIN_ERROR, - "Abstract Adapter '" + adapterClass - + "' can not be executed - use SignalAdapter or GenericAdapter instead!"); + itemColNextTask = findConditionalSplitTask(event, documentContext); + if (itemColNextTask != null) { + return itemColNextTask; + } - } + // default behavior + if (iNewProcessID > 0) { + itemColNextTask = this.ctx.getModelManager().getModel(documentContext.getModelVersion()) + .getTask(iNewProcessID); + } else { + // get current task... + itemColNextTask = this.ctx.getModelManager().getModel(documentContext.getItemValueString(MODELVERSION)) + .getTask(documentContext.getTaskID()); } - } else { - logger.warning("...Adapter '" + adapterClass + "' not registered - verify model!"); - } - } - } - - /** - * This method executes all StaticAdapters. StaticAdapters are executed before the SignalAdapters - * - * @param documentResult - * @param event - */ - private void executeGenericAdapters(ItemCollection documentResult, ItemCollection event) { - boolean debug = logger.isLoggable(Level.FINE); - if (debug) { - logger.finest("......executing GenericAdapters..."); - } - // execute all GenericAdapters - Collection adapters = adapterRegistry.values(); - for (Adapter adapter : adapters) { - // test if Adapter is static - if (adapter instanceof GenericAdapter) { - // execute... - executeAdaper(adapter, documentResult, event); - } + return itemColNextTask; } - } - - /** - * This method executes an instance of Adapter and logs adapter error messages. - *

- * In case of an AdapterException, the exception data will be wrapped into items with the prefix - * 'adapter.' - * - * @param adapter - * @param workitem - * @param event - */ - private void executeAdaper(Adapter adapter, ItemCollection workitem, ItemCollection event) { - // execute... - try { - // remove adapter errors.. - workitem.removeItem("adapter.error_context"); - workitem.removeItem("adapter.error_code"); - workitem.removeItem("adapter.error_params"); - workitem.removeItem("adapter.error_message"); - workitem = adapter.execute(workitem, event); - } catch (AdapterException e) { - logger.warning("...execution of adapter failed: " + e.getMessage()); - // update workitem with adapter exception.... - workitem.setItemValue("adapter.error_context", e.getErrorContext()); - workitem.setItemValue("adapter.error_code", e.getErrorCode()); - workitem.setItemValue("adapter.error_params", e.getErrorParameters()); - workitem.setItemValue("adapter.error_message", e.getMessage()); - e.printStackTrace(); + + /** + * This method returns new SplitWorkitems evaluated during the last processing + * life-cycle. + * + * @return + */ + public List getSplitWorkitems() { + return splitWorkitems; } - } - - /** - * This method returns the first conditional Task or Event of a given Event object. The method - * evaluates conditional expressions to 'true'. If no conditional expression exists or no - * expression evaluates to true the the method returns null - * - * @param conditions - * @param documentContext - * @return conditional Task or Event object or null if no condition exits. - * @throws PluginException - * @throws ModelException - */ - @SuppressWarnings("unchecked") - private ItemCollection findConditionalExclusiveTask(ItemCollection event, - ItemCollection documentContext) throws PluginException, ModelException { - boolean debug = logger.isLoggable(Level.FINE); - Map conditions = null; - // test if we have an exclusive condition - if (event.hasItem("keyExclusiveConditions")) { - // get first element - conditions = (Map) event.getItemValue("keyExclusiveConditions").get(0); - - if (conditions != null && conditions.size() > 0) { - - // evaluate all conditions and return the fist match... - for (Map.Entry entry : conditions.entrySet()) { - String key = entry.getKey(); - String expression = entry.getValue(); - if (key.startsWith("task=")) { - int taskID = Integer.parseInt(key.substring(5)); - boolean bmatch = ruleEngine.evaluateBooleanExpression(expression, documentContext); - if (bmatch) { - if (debug) { - logger.finest("......matching conditional event: " + expression); - } - ItemCollection conditionslTask = this.ctx.getModelManager() - .getModel(documentContext.getModelVersion()).getTask(taskID); - if (conditionslTask != null) { - return conditionslTask; - } + + /** + * This method controls the Event-Chain. If the attribute $activityidlist has + * more valid ActivityIDs the next activiytID will be loaded into $activity. + * + **/ + private ItemCollection updateEventList(final ItemCollection documentContext, final ItemCollection event) { + ItemCollection documentResult = documentContext; + boolean debug = logger.isLoggable(Level.FINE); + // first clear the eventID + documentResult.setEventID(Integer.valueOf(0)); + + // test if a FollowUp event is defined for the given event... + String sFollowUp = event.getItemValueString("keyFollowUp"); + int iNextActivityID = event.getItemValueInteger("numNextActivityID"); + if ("1".equals(sFollowUp) && iNextActivityID > 0) { + // append the next event id..... + documentResult = appendActivityID(documentResult, iNextActivityID); + } + + // evaluate is $eventid already provided? + if ((documentContext.getEventID() <= 0)) { + // no $eventID provided, so we test for property $ActivityIDList + List vActivityList = documentContext.getItemValue(ACTIVITYIDLIST); + + // remove 0 values if contained! + while (vActivityList.indexOf(Integer.valueOf(0)) > -1) { + vActivityList.remove(vActivityList.indexOf(Integer.valueOf(0))); } - } - - if (key.startsWith("event=")) { - int eventID = Integer.parseInt(key.substring(6)); - boolean bmatch = ruleEngine.evaluateBooleanExpression(expression, documentContext); - if (bmatch) { - if (debug) { - logger.finest("......matching conditional event: " + expression); - } - // we update the documentContext.... - ItemCollection itemColEvent = - this.ctx.getModelManager().getModel(documentContext.getModelVersion()) - .getEvent(documentContext.getTaskID(), eventID); - if (itemColEvent != null) { - // create follow up event.... - - event.replaceItemValue("keyFollowUp", "1"); - event.replaceItemValue("numNextActivityID", eventID); - - // get current task... - ItemCollection itemColNextTask = this.ctx.getModelManager() - .getModel(documentContext.getItemValueString(MODELVERSION)) - .getTask(documentContext.getTaskID()); - return itemColNextTask; - } + // test if an id is found.... + if (vActivityList.size() > 0) { + // yes - load next ID from activityID List + int iNextID = 0; + Object oA = vActivityList.get(0); + if (oA instanceof Integer) + iNextID = ((Integer) oA).intValue(); + if (oA instanceof Double) + iNextID = ((Double) oA).intValue(); + + if (iNextID > 0) { + // load activity + if (debug) { + logger.finest("......processing=" + documentContext.getItemValueString(UNIQUEID) + + " -> loading next activityID = " + iNextID); + } + vActivityList.remove(0); + // update document context + documentResult.setEventID(Integer.valueOf(iNextID)); + documentResult.replaceItemValue(ACTIVITYIDLIST, vActivityList); + } } - } } + return documentResult; + } + + /** + * This method processes a single event on a workflow instance. All registered + * adapter and plug-in classes will be executed. + *

+ * During the processing life-cycle more than one event can be processed. This + * depends on the model definition which can define follow-up-events, + * split-events and conditional events. + *

+ * After all adapter and plug-in classes have been executed, the attributes + * type, $taskID, $workflowstatus and $workflowgroup are updated based on the + * definition of the target task element. + *

+ * In case of an AdapterException, the exception data will be wrapped into items + * with the prefix 'adapter.' + * + * @throws PluginException,ModelException + */ + private ItemCollection processEvent(final ItemCollection documentContext, final ItemCollection event) + throws PluginException, ModelException { + ItemCollection documentResult = documentContext; + boolean debug = logger.isLoggable(Level.FINE); + // log the general processing message + String msg = "⚙ processing: " + documentContext.getItemValueString(UNIQUEID) + " (" + + documentContext.getItemValueString(MODELVERSION) + " ▷ " + documentContext.getTaskID() + "→" + + documentContext.getEventID() + ")"; + + if (ctx == null) { + logger.warning("no WorkflowContext defined!"); + } + logger.info(msg); + + // execute SignalAdapters + executeSignalAdapters(documentResult, event); + + // execute plugins - PluginExceptions will bubble up.... + try { + documentResult = runPlugins(documentResult, event); + } catch (PluginException pe) { + // close plugins + closePlugins(true); + // throw exeption + throw pe; + } + // Successful close plugins + closePlugins(false); + + // execute GenericAdapters + executeGenericAdapters(documentResult, event); + + // write event log + documentResult = logEvent(documentResult, event); + + // put current edge in history + vectorEdgeHistory.addElement( + event.getItemValueInteger("numprocessid") + "." + event.getItemValueInteger("numactivityid")); + + // update the next task (can be updated by plugins or conditional events.... + // issue #470 + ItemCollection itemColNextTask = findNextTask(documentResult, event); + + // evaluate a split-event and create new versions of the current process + // instance. + evaluateSplitEvent(event, documentResult); + + // Update the attributes $taskID and $WorkflowStatus + documentResult.setTaskID(Integer.valueOf(itemColNextTask.getItemValueInteger("numprocessid"))); + if (debug) { + logger.finest("......new $taskID=" + documentResult.getTaskID()); + } + documentResult.replaceItemValue(WORKFLOWSTATUS, itemColNextTask.getItemValueString("txtname")); + documentResult.replaceItemValue(WORKFLOWGROUP, itemColNextTask.getItemValueString("txtworkflowgroup")); if (debug) { - logger.finest("......conditional event: no matching condition found."); + logger.finest("......new $workflowStatus=" + documentResult.getItemValueString(WORKFLOWSTATUS)); + } + // update deprecated attributes txtworkflowStatus and txtworkflowGroup + documentResult.replaceItemValue("txtworkflowStatus", documentResult.getItemValueString(WORKFLOWSTATUS)); + documentResult.replaceItemValue("txtworkflowGroup", documentResult.getItemValueString(WORKFLOWGROUP)); + + // update the type attribute if defined. + // the type attribute can only be overwritten by a plug-in if the type is not + // defined by the task! + String sType = itemColNextTask.getItemValueString("txttype"); + if (!"".equals(sType)) { + documentResult.replaceItemValue(TYPE, sType); } - } + + return documentResult; } - return null; - } - - /** - * This method returns the first conditional Split Task or Event of a given Event object. The - * method evaluates conditional expressions to 'true'. If no conditional expression exists or no - * expression evaluates to true the the method returns null - * - * @param conditions - * @param documentContext - * @return conditional Task or Event object or null if no condition exits. - * @throws PluginException - * @throws ModelException - */ - @SuppressWarnings("unchecked") - private ItemCollection findConditionalSplitTask(ItemCollection event, - ItemCollection documentContext) throws PluginException, ModelException { - boolean debug = logger.isLoggable(Level.FINE); - // test if we have an split event - Map conditions = null; - // test if we have an split event - if (event.hasItem("keySplitConditions")) { - // get first element - conditions = (Map) event.getItemValue("keySplitConditions").get(0); - - if (conditions != null) { - - // evaluate all conditions and return the fist match evaluating to true (this is - // the flow for the master version)... - for (Map.Entry entry : conditions.entrySet()) { - String key = entry.getKey(); - String expression = entry.getValue(); - if (key.startsWith("task=")) { - int taskID = Integer.parseInt(key.substring(5)); - boolean bmatch = ruleEngine.evaluateBooleanExpression(expression, documentContext); - if (bmatch) { - if (debug) { - logger.finest("......matching split Task found: " + expression); - } - ItemCollection itemColNextTask = this.ctx.getModelManager() - .getModel(documentContext.getModelVersion()).getTask(taskID); - if (itemColNextTask != null) { - // Conditional Target Task evaluated to 'true' was found! - return itemColNextTask; - } - } - } - - if (key.startsWith("event=")) { - int eventID = Integer.parseInt(key.substring(6)); - boolean bmatch = ruleEngine.evaluateBooleanExpression(expression, documentContext); - if (bmatch) { - if (debug) { - logger.finest("......matching split Event found: " + expression); - } - // we update the documentContext.... - ItemCollection itemColEvent = - this.ctx.getModelManager().getModel(documentContext.getModelVersion()) - .getEvent(documentContext.getTaskID(), eventID); - if (itemColEvent != null) { - // create follow up event.... - event.replaceItemValue("keyFollowUp", "1"); - event.replaceItemValue("numNextActivityID", eventID); - // get current task... - ItemCollection itemColNextTask = this.ctx.getModelManager() - .getModel(documentContext.getItemValueString(MODELVERSION)) - .getTask(documentContext.getTaskID()); - return itemColNextTask; - } + + /** + * This method executes all SignalAdapters associated with the model. + *

+ * A StaticAdaper should not be associated with a BPMN Signal Event. + * + * @param documentResult + * @param event + * @throws PluginException + */ + private void executeSignalAdapters(ItemCollection documentResult, ItemCollection event) throws PluginException { + boolean debug = logger.isLoggable(Level.FINE); + if (debug) { + logger.finest("......executing SignalAdapters..."); + } + // execute adapters if adapter class is defined.... + String adapterClass = event.getItemValueString("adapter.id"); + if (!adapterClass.isEmpty() && adapterClass.matches("^(?:\\w+|\\w+\\.\\w+)+$")) { + Adapter adapter = adapterRegistry.get(adapterClass); + if (adapter != null) { + + if (adapter instanceof GenericAdapter) { + logger.warning( + "...GenericAdapter '" + adapterClass + "' should not be associated with a Signal Event!"); + // ...stop execution as the GenericAdapter was already executed by the method + // executeGenericAdapters... + } else { + // execute only instance of signal Adapters... + if (adapter instanceof SignalAdapter) { + executeAdaper(adapter, documentResult, event); + + } else { + throw new PluginException(WorkflowKernel.class.getSimpleName(), PLUGIN_ERROR, + "Abstract Adapter '" + adapterClass + + "' can not be executed - use SignalAdapter or GenericAdapter instead!"); + + } + } + } else { + logger.warning("...Adapter '" + adapterClass + "' not registered - verify model!"); } - } } - // we found not condition evaluated to 'true', so the workitem will not leave - // the current task. + } + + /** + * This method executes all StaticAdapters. StaticAdapters are executed before + * the SignalAdapters + * + * @param documentResult + * @param event + */ + private void executeGenericAdapters(ItemCollection documentResult, ItemCollection event) { + boolean debug = logger.isLoggable(Level.FINE); if (debug) { - logger.finest("......split event: no matching condition, current Task will not change."); + logger.finest("......executing GenericAdapters..."); + } + // execute all GenericAdapters + Collection adapters = adapterRegistry.values(); + for (Adapter adapter : adapters) { + // test if Adapter is static + if (adapter instanceof GenericAdapter) { + // execute... + executeAdaper(adapter, documentResult, event); + } } - } } - return null; - } - - /** - * This method evaluates conditional split expressions to 'false'. For each condition a new - * process instance will be created and processed by the expected follow-up event. The expression - * evaluated to 'false' MUST be followed by an Event. If not, a ModelExcption is thrown to - * indicate that the new version can not be processed correctly! - * - * @param conditions - * @param documentContext - * @return conditional Task or Event object or null if no condition exits. - * @throws PluginException - * @throws ModelException - * @throws AdapterException - */ - @SuppressWarnings("unchecked") - private void evaluateSplitEvent(ItemCollection event, ItemCollection documentContext) - throws PluginException, ModelException { - boolean debug = logger.isLoggable(Level.FINE); - // test if we have an split event - Map conditions = null; - // test if we have an split event - if (event.hasItem("keySplitConditions")) { - // get first element - conditions = (Map) event.getItemValue("keySplitConditions").get(0); - - if (conditions != null) { - - // evaluate all conditions and return the fist match evaluating to true (this is - // the flow for the master version)... - for (Map.Entry entry : conditions.entrySet()) { - String key = entry.getKey(); - String expression = entry.getValue(); - if (key.startsWith("task=")) { - // if a task evaluated to false, the model is invalid. - boolean bmatch = ruleEngine.evaluateBooleanExpression(expression, documentContext); - if (!bmatch) { - String sErrorMessage = - "Outcome of Split-Event " + event.getItemValueInteger("numProcessid") + "." - + +event.getItemValueInteger("numActivityid") + " (" + event.getModelVersion() - + ") evaluate to false must not be connected to a task. "; - - logger.warning(sErrorMessage + " Condition = " + expression); - throw new ModelException(ModelException.INVALID_MODEL, sErrorMessage); - } - } - - if (key.startsWith("event=")) { - int eventID = Integer.parseInt(key.substring(6)); - boolean bmatch = ruleEngine.evaluateBooleanExpression(expression, documentContext); - if (!bmatch) { - if (debug) { - logger.finest("......matching conditional event: " + expression); - } - // we update the documentContext.... - ItemCollection itemColEvent = - this.ctx.getModelManager().getModel(documentContext.getModelVersion()) - .getEvent(documentContext.getTaskID(), eventID); - if (itemColEvent != null) { - // get current task... - ItemCollection itemColNextTask = this.ctx.getModelManager() - .getModel(documentContext.getItemValueString(MODELVERSION)) - .getTask(documentContext.getTaskID()); + /** + * This method executes an instance of Adapter and logs adapter error messages. + *

+ * In case of an AdapterException, the exception data will be wrapped into items + * with the prefix 'adapter.' + * + * @param adapter + * @param workitem + * @param event + */ + private void executeAdaper(Adapter adapter, ItemCollection workitem, ItemCollection event) { + // execute... + try { + // remove adapter errors.. + workitem.removeItem("adapter.error_context"); + workitem.removeItem("adapter.error_code"); + workitem.removeItem("adapter.error_params"); + workitem.removeItem("adapter.error_message"); + workitem = adapter.execute(workitem, event); + } catch (AdapterException e) { + logger.warning("...execution of adapter failed: " + e.getMessage()); + // update workitem with adapter exception.... + workitem.setItemValue("adapter.error_context", e.getErrorContext()); + workitem.setItemValue("adapter.error_code", e.getErrorCode()); + workitem.setItemValue("adapter.error_params", e.getErrorParameters()); + workitem.setItemValue("adapter.error_message", e.getMessage()); + e.printStackTrace(); + } + } - // clone current instance to a new version... - ItemCollection cloned = createVersion(documentContext); + /** + * This method returns the first conditional Task or Event of a given Event + * object. The method evaluates conditional expressions to 'true'. If no + * conditional expression exists or no expression evaluates to true the the + * method returns null + * + * @param conditions + * @param documentContext + * @return conditional Task or Event object or null if no condition exits. + * @throws PluginException + * @throws ModelException + */ + @SuppressWarnings("unchecked") + private ItemCollection findConditionalExclusiveTask(ItemCollection event, ItemCollection documentContext) + throws PluginException, ModelException { + boolean debug = logger.isLoggable(Level.FINE); + Map conditions = null; + // test if we have an exclusive condition + if (event.hasItem("keyExclusiveConditions")) { + // get first element + conditions = (Map) event.getItemValue("keyExclusiveConditions").get(0); + + if (conditions != null && conditions.size() > 0) { + + // evaluate all conditions and return the fist match... + for (Map.Entry entry : conditions.entrySet()) { + String key = entry.getKey(); + String expression = entry.getValue(); + if (key.startsWith("task=")) { + int taskID = Integer.parseInt(key.substring(5)); + boolean bmatch = ruleEngine.evaluateBooleanExpression(expression, documentContext); + if (bmatch) { + if (debug) { + logger.finest("......matching conditional event: " + expression); + } + ItemCollection conditionslTask = this.ctx.getModelManager() + .getModel(documentContext.getModelVersion()).getTask(taskID); + if (conditionslTask != null) { + return conditionslTask; + } + } + } + + if (key.startsWith("event=")) { + int eventID = Integer.parseInt(key.substring(6)); + boolean bmatch = ruleEngine.evaluateBooleanExpression(expression, documentContext); + if (bmatch) { + if (debug) { + logger.finest("......matching conditional event: " + expression); + } + // we update the documentContext.... + ItemCollection itemColEvent = this.ctx.getModelManager() + .getModel(documentContext.getModelVersion()) + .getEvent(documentContext.getTaskID(), eventID); + if (itemColEvent != null) { + // create follow up event.... + + event.replaceItemValue("keyFollowUp", "1"); + event.replaceItemValue("numNextActivityID", eventID); + + // get current task... + ItemCollection itemColNextTask = this.ctx.getModelManager() + .getModel(documentContext.getItemValueString(MODELVERSION)) + .getTask(documentContext.getTaskID()); + + return itemColNextTask; + } + } + } + } if (debug) { - logger.finest("......created new version=" + cloned.getUniqueID()); + logger.finest("......conditional event: no matching condition found."); } - // set new $taskID - cloned.setTaskID( - Integer.valueOf(itemColNextTask.getItemValueInteger("numprocessid"))); - cloned.setEventID(eventID); - // add temporary attribute $isversion... - cloned.replaceItemValue(ISVERSION, true); - cloned = this.process(cloned); - // remove temporary attribute $isversion... - cloned.removeItem(ISVERSION); - // add to cache... - splitWorkitems.add(cloned); - - } } - } - } - if (debug) { - logger.finest("......split event: no matching condition"); } - } + return null; } - } - - /** - * This method creates a new instance of a sourceWorkitem. The method did not save the workitem!. - *

- * The new property $UniqueIDSource will be added to the new version, which points to the - * $uniqueID of the sourceWorkitem. - *

- * The new property $UniqueIDVersions will be added to the sourceWorktiem which points to the id - * of the new version. - * - * @param sourceItemCollection the ItemCollection which should be versioned - * @return new version of the source ItemCollection - * - * @throws PluginException - * @throws Exception - */ - private ItemCollection createVersion(ItemCollection sourceItemCollection) throws PluginException { - - // clone the source workitem with its '$workitemid' - ItemCollection itemColNewVersion = (ItemCollection) sourceItemCollection.clone(); - String id = sourceItemCollection.getUniqueID(); - - // create a new $Uniqueid to force the generation of a new Entity Instance. - itemColNewVersion.replaceItemValue(UNIQUEID, WorkflowKernel.generateUniqueID()); - - // update $unqiueIDSource - itemColNewVersion.replaceItemValue(UNIQUEIDSOURCE, id); - - // remove $UniqueIDVersions - itemColNewVersion.removeItem(UNIQUEIDVERSIONS); - - // append the version uniqueid to the source ItemCollection - sourceItemCollection.appendItemValue(UNIQUEIDVERSIONS, itemColNewVersion.getUniqueID()); - - return itemColNewVersion; - - } - - /** - * This method adds a new ActivityID into the current activityList ($ActivityIDList) The activity - * list may not contain 0 values. - * - */ - @SuppressWarnings("unchecked") - private ItemCollection appendActivityID(final ItemCollection documentContext, final int aID) { - boolean debug = logger.isLoggable(Level.FINE); - ItemCollection documentResult = documentContext; - // check if activityidlist is available - List vActivityList = (List) documentContext.getItemValue(ACTIVITYIDLIST); - // clear list? - if ((vActivityList.size() == 1) && ("".equals(vActivityList.get(0).toString()))) - vActivityList = new Vector(); - - vActivityList.add(Integer.valueOf(aID)); - - // remove 0 values if contained! - while (vActivityList.indexOf(Integer.valueOf(0)) > -1) { - vActivityList.remove(vActivityList.indexOf(Integer.valueOf(0))); - } + /** + * This method returns the first conditional Split Task or Event of a given + * Event object. The method evaluates conditional expressions to 'true'. If no + * conditional expression exists or no expression evaluates to true the the + * method returns null + * + * @param conditions + * @param documentContext + * @return conditional Task or Event object or null if no condition exits. + * @throws PluginException + * @throws ModelException + */ + @SuppressWarnings("unchecked") + private ItemCollection findConditionalSplitTask(ItemCollection event, ItemCollection documentContext) + throws PluginException, ModelException { + boolean debug = logger.isLoggable(Level.FINE); + // test if we have an split event + Map conditions = null; + // test if we have an split event + if (event.hasItem("keySplitConditions")) { + // get first element + conditions = (Map) event.getItemValue("keySplitConditions").get(0); + + if (conditions != null) { + + // evaluate all conditions and return the fist match evaluating to true (this is + // the flow for the master version)... + for (Map.Entry entry : conditions.entrySet()) { + String key = entry.getKey(); + String expression = entry.getValue(); + if (key.startsWith("task=")) { + int taskID = Integer.parseInt(key.substring(5)); + boolean bmatch = ruleEngine.evaluateBooleanExpression(expression, documentContext); + if (bmatch) { + if (debug) { + logger.finest("......matching split Task found: " + expression); + } + ItemCollection itemColNextTask = this.ctx.getModelManager() + .getModel(documentContext.getModelVersion()).getTask(taskID); + if (itemColNextTask != null) { + // Conditional Target Task evaluated to 'true' was found! + return itemColNextTask; + } + } + } + + if (key.startsWith("event=")) { + int eventID = Integer.parseInt(key.substring(6)); + boolean bmatch = ruleEngine.evaluateBooleanExpression(expression, documentContext); + if (bmatch) { + if (debug) { + logger.finest("......matching split Event found: " + expression); + } + // we update the documentContext.... + ItemCollection itemColEvent = this.ctx.getModelManager() + .getModel(documentContext.getModelVersion()) + .getEvent(documentContext.getTaskID(), eventID); + if (itemColEvent != null) { + // create follow up event.... + event.replaceItemValue("keyFollowUp", "1"); + event.replaceItemValue("numNextActivityID", eventID); + // get current task... + ItemCollection itemColNextTask = this.ctx.getModelManager() + .getModel(documentContext.getItemValueString(MODELVERSION)) + .getTask(documentContext.getTaskID()); + return itemColNextTask; + } + } + } + } + // we found not condition evaluated to 'true', so the workitem will not leave + // the current task. + if (debug) { + logger.finest("......split event: no matching condition, current Task will not change."); + } + } + } - documentResult.replaceItemValue(ACTIVITYIDLIST, vActivityList); - if (debug) { - logger.finest("......append new Activity ID=" + aID); + return null; } - return documentResult; - } - - /** - * This method is responsible for the internal workflow log. The attribute $eventlog logs the - * transition from one process to another. - * - * Format: - * - * - timestamp|model-version|1000.10|1000| - timestamp|model-version|1000.20|1010| - timestamp|model-version|1010.10|1010|comment - * - * - * The comment is an optional information generated by Plugins. If a property - * 'txtworkflowactivitylogComment' exits the value will be appended. - * - * The method restrict the maximum count of entries to avoid a overflow. (issue #179) - * - */ - @SuppressWarnings("unchecked") - private ItemCollection logEvent(final ItemCollection documentContext, - final ItemCollection event) { - boolean debug = logger.isLoggable(Level.FINE); - ItemCollection documentResult = documentContext; - StringBuffer sLogEntry = new StringBuffer(); - // 22.9.2004 13:50:41|modelversion|1000.90|1000| - - sLogEntry.append(new SimpleDateFormat(ISO8601_FORMAT).format(new Date())); - - sLogEntry.append("|"); - sLogEntry.append(documentContext.getItemValueString(MODELVERSION)); - - sLogEntry.append("|"); - sLogEntry.append(event.getItemValueInteger("numprocessid") + "." - + event.getItemValueInteger("numactivityid")); - - sLogEntry.append("|"); - sLogEntry.append(event.getItemValueInteger("numnextprocessid")); - sLogEntry.append("|"); - - // check for optional log comment - String sLogComment = documentContext.getItemValueString("txtworkflowactivitylogComment"); - if (!sLogComment.isEmpty()) - sLogEntry.append(sLogComment); - - // support deprecated field txtworkflowactivitylog - List logEntries = null; - if (!documentContext.hasItem("$eventlog")) - logEntries = (List) documentContext.getItemValue("txtworkflowactivitylog"); // deprecated - else - logEntries = (List) documentContext.getItemValue("$eventlog"); - logEntries.add(sLogEntry.toString()); - - // test if the log has exceeded the maximum count of entries - while (logEntries.size() > MAXIMUM_ACTIVITYLOGENTRIES) { - if (debug) { - logger.finest("......maximum activity log entries=" + MAXIMUM_ACTIVITYLOGENTRIES - + " exceeded, remove first entry..."); - } - logEntries.remove(0); - } + /** + * This method evaluates conditional split expressions to 'false'. For each + * condition a new process instance will be created and processed by the + * expected follow-up event. The expression evaluated to 'false' MUST be + * followed by an Event. If not, a ModelExcption is thrown to indicate that the + * new version can not be processed correctly! + * + * @param conditions + * @param documentContext + * @return conditional Task or Event object or null if no condition exits. + * @throws PluginException + * @throws ModelException + * @throws AdapterException + */ + @SuppressWarnings("unchecked") + private void evaluateSplitEvent(ItemCollection event, ItemCollection documentContext) + throws PluginException, ModelException { + boolean debug = logger.isLoggable(Level.FINE); + // test if we have an split event + Map conditions = null; + // test if we have an split event + if (event.hasItem("keySplitConditions")) { + // get first element + conditions = (Map) event.getItemValue("keySplitConditions").get(0); + + if (conditions != null) { + + // evaluate all conditions and return the fist match evaluating to true (this is + // the flow for the master version)... + for (Map.Entry entry : conditions.entrySet()) { + String key = entry.getKey(); + String expression = entry.getValue(); + if (key.startsWith("task=")) { + // if a task evaluated to false, the model is invalid. + boolean bmatch = ruleEngine.evaluateBooleanExpression(expression, documentContext); + if (!bmatch) { + String sErrorMessage = "Outcome of Split-Event " + event.getItemValueInteger("numProcessid") + + "." + +event.getItemValueInteger("numActivityid") + " (" + event.getModelVersion() + + ") evaluate to false must not be connected to a task. "; + + logger.warning(sErrorMessage + " Condition = " + expression); + throw new ModelException(ModelException.INVALID_MODEL, sErrorMessage); + } + } + + if (key.startsWith("event=")) { + int eventID = Integer.parseInt(key.substring(6)); + boolean bmatch = ruleEngine.evaluateBooleanExpression(expression, documentContext); + if (!bmatch) { + if (debug) { + logger.finest("......matching conditional event: " + expression); + } + // we update the documentContext.... + ItemCollection itemColEvent = this.ctx.getModelManager() + .getModel(documentContext.getModelVersion()) + .getEvent(documentContext.getTaskID(), eventID); + if (itemColEvent != null) { + // get current task... + ItemCollection itemColNextTask = this.ctx.getModelManager() + .getModel(documentContext.getItemValueString(MODELVERSION)) + .getTask(documentContext.getTaskID()); + + // clone current instance to a new version... + ItemCollection cloned = createVersion(documentContext); + if (debug) { + logger.finest("......created new version=" + cloned.getUniqueID()); + } + // set new $taskID + cloned.setTaskID(Integer.valueOf(itemColNextTask.getItemValueInteger("numprocessid"))); + cloned.setEventID(eventID); + // add temporary attribute $isversion... + cloned.replaceItemValue(ISVERSION, true); + cloned = this.process(cloned); + // remove temporary attribute $isversion... + cloned.removeItem(ISVERSION); + // add to cache... + splitWorkitems.add(cloned); + + } + } + } + } + if (debug) { + logger.finest("......split event: no matching condition"); + } + } + } - documentResult.replaceItemValue("$eventlog", logEntries); - - documentResult.replaceItemValue("$lastEvent", - Integer.valueOf(event.getItemValueInteger("numactivityid"))); - // deprecated - documentResult.replaceItemValue("numlastactivityid", - Integer.valueOf(event.getItemValueInteger("numactivityid"))); - - return documentResult; - } - - /** - * This Method loads the current event from the provided Model - * - * The method also verifies the activity to be valid - * - * @return workflow event object. - */ - private ItemCollection loadEvent(final ItemCollection documentContext) { - boolean debug = logger.isLoggable(Level.FINE); - ItemCollection event = null; - int taskID = documentContext.getTaskID(); - int eventID = documentContext.getEventID(); - - // determine model version - String version = documentContext.getItemValueString(MODELVERSION); - - try { - Model model = ctx.getModelManager().getModelByWorkitem(documentContext); - event = model.getEvent(taskID, eventID); - } catch (ModelException e) { - throw new ProcessingErrorException(WorkflowKernel.class.getSimpleName(), MODEL_ERROR, - e.getMessage()); } - if (event == null) - throw new ProcessingErrorException(WorkflowKernel.class.getSimpleName(), ACTIVITY_NOT_FOUND, - "[loadEvent] model entry " + taskID + "." + eventID + " not found for model version '" - + version + "'"); - if (debug) { - logger.finest(".......event: " + taskID + "." + eventID + " loaded"); + /** + * This method creates a new instance of a sourceWorkitem. The method did not + * save the workitem!. + *

+ * The new property $UniqueIDSource will be added to the new version, which + * points to the $uniqueID of the sourceWorkitem. + *

+ * The new property $UniqueIDVersions will be added to the sourceWorktiem which + * points to the id of the new version. + * + * @param sourceItemCollection the ItemCollection which should be versioned + * @return new version of the source ItemCollection + * + * @throws PluginException + * @throws Exception + */ + private ItemCollection createVersion(ItemCollection sourceItemCollection) throws PluginException { + + // clone the source workitem with its '$workitemid' + ItemCollection itemColNewVersion = (ItemCollection) sourceItemCollection.clone(); + String id = sourceItemCollection.getUniqueID(); + + // create a new $Uniqueid to force the generation of a new Entity Instance. + itemColNewVersion.replaceItemValue(UNIQUEID, WorkflowKernel.generateUniqueID()); + + // update $unqiueIDSource + itemColNewVersion.replaceItemValue(UNIQUEIDSOURCE, id); + + // remove $UniqueIDVersions + itemColNewVersion.removeItem(UNIQUEIDVERSIONS); + + // append the version uniqueid to the source ItemCollection + sourceItemCollection.appendItemValue(UNIQUEIDVERSIONS, itemColNewVersion.getUniqueID()); + + return itemColNewVersion; + } - // Check for loop in edge history - if (vectorEdgeHistory != null && vectorEdgeHistory.indexOf((taskID + "." + eventID)) != -1) { - throw new ProcessingErrorException(WorkflowKernel.class.getSimpleName(), MODEL_ERROR, - "[loadEvent] loop detected " + taskID + "." + eventID + "," - + vectorEdgeHistory.toString()); + + /** + * This method adds a new ActivityID into the current activityList + * ($ActivityIDList) The activity list may not contain 0 values. + * + */ + @SuppressWarnings("unchecked") + private ItemCollection appendActivityID(final ItemCollection documentContext, final int aID) { + boolean debug = logger.isLoggable(Level.FINE); + ItemCollection documentResult = documentContext; + // check if activityidlist is available + List vActivityList = (List) documentContext.getItemValue(ACTIVITYIDLIST); + // clear list? + if ((vActivityList.size() == 1) && ("".equals(vActivityList.get(0).toString()))) + vActivityList = new Vector(); + + vActivityList.add(Integer.valueOf(aID)); + + // remove 0 values if contained! + while (vActivityList.indexOf(Integer.valueOf(0)) > -1) { + vActivityList.remove(vActivityList.indexOf(Integer.valueOf(0))); + } + + documentResult.replaceItemValue(ACTIVITYIDLIST, vActivityList); + if (debug) { + logger.finest("......append new Activity ID=" + aID); + } + + return documentResult; } - return event; + /** + * This method is responsible for the internal workflow log. The attribute + * $eventlog logs the transition from one process to another. + * + * Format: + * + * + timestamp|model-version|1000.10|1000| + timestamp|model-version|1000.20|1010| + timestamp|model-version|1010.10|1010|comment + * + * + * The comment is an optional information generated by Plugins. If a property + * 'txtworkflowactivitylogComment' exits the value will be appended. + * + * The method restrict the maximum count of entries to avoid a overflow. (issue + * #179) + * + */ + @SuppressWarnings("unchecked") + private ItemCollection logEvent(final ItemCollection documentContext, final ItemCollection event) { + boolean debug = logger.isLoggable(Level.FINE); + ItemCollection documentResult = documentContext; + StringBuffer sLogEntry = new StringBuffer(); + // 22.9.2004 13:50:41|modelversion|1000.90|1000| + + sLogEntry.append(new SimpleDateFormat(ISO8601_FORMAT).format(new Date())); + + sLogEntry.append("|"); + sLogEntry.append(documentContext.getItemValueString(MODELVERSION)); + + sLogEntry.append("|"); + sLogEntry.append(event.getItemValueInteger("numprocessid") + "." + event.getItemValueInteger("numactivityid")); + + sLogEntry.append("|"); + sLogEntry.append(event.getItemValueInteger("numnextprocessid")); + sLogEntry.append("|"); + + // check for optional log comment + String sLogComment = documentContext.getItemValueString("txtworkflowactivitylogComment"); + if (!sLogComment.isEmpty()) + sLogEntry.append(sLogComment); + + // support deprecated field txtworkflowactivitylog + List logEntries = null; + if (!documentContext.hasItem("$eventlog")) + logEntries = (List) documentContext.getItemValue("txtworkflowactivitylog"); // deprecated + else + logEntries = (List) documentContext.getItemValue("$eventlog"); + logEntries.add(sLogEntry.toString()); + + // test if the log has exceeded the maximum count of entries + while (logEntries.size() > MAXIMUM_ACTIVITYLOGENTRIES) { + if (debug) { + logger.finest("......maximum activity log entries=" + MAXIMUM_ACTIVITYLOGENTRIES + + " exceeded, remove first entry..."); + } + logEntries.remove(0); + } + + documentResult.replaceItemValue("$eventlog", logEntries); - } + documentResult.replaceItemValue("$lastEvent", Integer.valueOf(event.getItemValueInteger("numactivityid"))); + // deprecated + documentResult.replaceItemValue("numlastactivityid", + Integer.valueOf(event.getItemValueInteger("numactivityid"))); - /** - * This method runs all registered plugins until the run method of a plugin breaks with an error - * In this case the method stops. - * - * @throws PluginException - */ - private ItemCollection runPlugins(final ItemCollection documentContext, - final ItemCollection event) throws PluginException { - boolean debug = logger.isLoggable(Level.FINE); - ItemCollection documentResult = documentContext; - String sPluginName = null; - List localPluginLog = new Vector(); + return documentResult; + } - try { - for (Plugin plugin : pluginRegistry) { + /** + * This Method loads the current event from the provided Model + * + * The method also verifies the activity to be valid + * + * @return workflow event object. + */ + private ItemCollection loadEvent(final ItemCollection documentContext) { + boolean debug = logger.isLoggable(Level.FINE); + ItemCollection event = null; + int taskID = documentContext.getTaskID(); + int eventID = documentContext.getEventID(); + + // determine model version + String version = documentContext.getItemValueString(MODELVERSION); + + try { + Model model = ctx.getModelManager().getModelByWorkitem(documentContext); + event = model.getEvent(taskID, eventID); + } catch (ModelException e) { + throw new ProcessingErrorException(WorkflowKernel.class.getSimpleName(), MODEL_ERROR, e.getMessage()); + } - sPluginName = plugin.getClass().getName(); + if (event == null) + throw new ProcessingErrorException(WorkflowKernel.class.getSimpleName(), ACTIVITY_NOT_FOUND, + "[loadEvent] model entry " + taskID + "." + eventID + " not found for model version '" + version + + "'"); if (debug) { - logger.finest("......running Plugin: " + sPluginName + "..."); + logger.finest(".......event: " + taskID + "." + eventID + " loaded"); } - long lPluginTime = System.currentTimeMillis(); - documentResult = plugin.run(documentResult, event); - if (debug) { - logger.fine("...Plugin '" + sPluginName + "' processing time=" - + (System.currentTimeMillis() - lPluginTime) + "ms"); + // Check for loop in edge history + if (vectorEdgeHistory != null && vectorEdgeHistory.indexOf((taskID + "." + eventID)) != -1) { + throw new ProcessingErrorException(WorkflowKernel.class.getSimpleName(), MODEL_ERROR, + "[loadEvent] loop detected " + taskID + "." + eventID + "," + vectorEdgeHistory.toString()); } - if (documentResult == null) { - logger.severe("[runPlugins] PLUGIN_ERROR: " + sPluginName); - for (String sLogEntry : localPluginLog) - logger.severe("[runPlugins] " + sLogEntry); - throw new PluginException(WorkflowKernel.class.getSimpleName(), PLUGIN_ERROR, - "plugin: " + sPluginName + " returned null"); - } - // write PluginLog - String sLog = new SimpleDateFormat(ISO8601_FORMAT).format(new Date()); - sLog = sLog + " " + plugin.getClass().getName(); - - localPluginLog.add(sLog); - - } - return documentResult; - - } catch (PluginException e) { - // log plugin stack!.... - logger.severe("Plugin-Error at " + e.getErrorContext() + ": " + e.getErrorCode() + " (" - + e.getMessage() + ")"); - if (debug) { - logger.severe("Last Plugins run successfull:"); - for (String sLogEntry : localPluginLog) - logger.severe(" ..." + sLogEntry); - } - throw e; + return event; + } - } + /** + * This method runs all registered plugins until the run method of a plugin + * breaks with an error In this case the method stops. + * + * @throws PluginException + */ + private ItemCollection runPlugins(final ItemCollection documentContext, final ItemCollection event) + throws PluginException { + boolean debug = logger.isLoggable(Level.FINE); + ItemCollection documentResult = documentContext; + String sPluginName = null; + List localPluginLog = new Vector(); + + try { + for (Plugin plugin : pluginRegistry) { + + sPluginName = plugin.getClass().getName(); + if (debug) { + logger.finest("......running Plugin: " + sPluginName + "..."); + } + long lPluginTime = System.currentTimeMillis(); + documentResult = plugin.run(documentResult, event); + if (debug) { + logger.fine("...Plugin '" + sPluginName + "' processing time=" + + (System.currentTimeMillis() - lPluginTime) + "ms"); + } + if (documentResult == null) { + logger.severe("[runPlugins] PLUGIN_ERROR: " + sPluginName); + for (String sLogEntry : localPluginLog) + logger.severe("[runPlugins] " + sLogEntry); + + throw new PluginException(WorkflowKernel.class.getSimpleName(), PLUGIN_ERROR, + "plugin: " + sPluginName + " returned null"); + } + // write PluginLog + String sLog = new SimpleDateFormat(ISO8601_FORMAT).format(new Date()); + sLog = sLog + " " + plugin.getClass().getName(); + + localPluginLog.add(sLog); - private void closePlugins(boolean rollbackTransaction) throws PluginException { - for (int i = 0; i < pluginRegistry.size(); i++) { - Plugin plugin = (Plugin) pluginRegistry.get(i); - if (logger.isLoggable(Level.FINEST)) - logger.finest("closing Plugin: " + plugin.getClass().getName() + "..."); - plugin.close(rollbackTransaction); + } + return documentResult; + + } catch (PluginException e) { + // log plugin stack!.... + logger.severe( + "Plugin-Error at " + e.getErrorContext() + ": " + e.getErrorCode() + " (" + e.getMessage() + ")"); + if (debug) { + logger.severe("Last Plugins run successfull:"); + for (String sLogEntry : localPluginLog) + logger.severe(" ..." + sLogEntry); + } + throw e; + } + + } + + private void closePlugins(boolean rollbackTransaction) throws PluginException { + for (int i = 0; i < pluginRegistry.size(); i++) { + Plugin plugin = (Plugin) pluginRegistry.get(i); + if (logger.isLoggable(Level.FINEST)) + logger.finest("closing Plugin: " + plugin.getClass().getName() + "..."); + plugin.close(rollbackTransaction); + } } - } } diff --git a/imixs-workflow-core/src/main/java/org/imixs/workflow/WorkflowManager.java b/imixs-workflow-core/src/main/java/org/imixs/workflow/WorkflowManager.java index 032f1daef..21b36e75e 100644 --- a/imixs-workflow-core/src/main/java/org/imixs/workflow/WorkflowManager.java +++ b/imixs-workflow-core/src/main/java/org/imixs/workflow/WorkflowManager.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *

- *  Imixs Workflow 
+/*  
+ *  Imixs-Workflow 
+ *  
  *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
  *  http://www.imixs.com
  *  
@@ -22,10 +22,9 @@
  *      https://github.com/imixs/imixs-workflow
  *  
  *  Contributors:  
- *      Imixs Software Solutions GmbH - initial API and implementation
+ *      Imixs Software Solutions GmbH - Project Management
  *      Ralph Soika - Software Developer
- * 
- *******************************************************************************/ + */ package org.imixs.workflow; @@ -35,10 +34,11 @@ import org.imixs.workflow.exceptions.ProcessingErrorException; /** - * The WorkflowManager is the general interface for a concrete implementation of a workflow - * management system. The Interface defines the basic methods for processing and encountering a - * workItem. The WorkflowManger instantiate a WorkflowKernel, an supports the platform dependent - * environment for concrete Workitems and Workflow models. + * The WorkflowManager is the general interface for a concrete implementation of + * a workflow management system. The Interface defines the basic methods for + * processing and encountering a workItem. The WorkflowManger instantiate a + * WorkflowKernel, an supports the platform dependent environment for concrete + * Workitems and Workflow models. * * @author Ralph Soika * @version 1.1 @@ -47,52 +47,57 @@ public interface WorkflowManager { - /** - * This method processes a workItem. The workItem needs at least provide the valid attributes - * $taskID and $EventID (integer values) to identify the current processEntity the workItem - * belongs to and the concrete activtyEntity which should be processed by the wokflowManager - * implementation. If the workItem is new the method creates a new instance for the corresponding - * process. - *

- * The method is responsible to persist the workItem after successfully processing. The method - * returns the workItem with additional workflow informations defined by the workfowManager - * Implementation. - *

- * The Method throws an InvalidWorkitemException if the provided workItem is invalid or the - * provided attributes $taskID and $EventID (integer) did not match an valid modelEntity the - * workItem can be processed to. - * - * @param workitem a workItem instance which should be processed - * @return the workItem instance after successful processing - * - * @throws AccessDeniedException - thrown if the user has insufficient access to update - * the workItem - * @throws ProcessingErrorException - thrown if the workitem could not be processed by the - * workflowKernel - * @throws AdapterExceptionAdapterException - thrown if processing by an adapter fails - * @throws PluginException - thrown if processing by a plugin fails - */ - public ItemCollection processWorkItem(ItemCollection workitem) - throws AccessDeniedException, ProcessingErrorException, PluginException, ModelException; + /** + * This method processes a workItem. The workItem needs at least provide the + * valid attributes $taskID and $EventID (integer values) to identify the + * current processEntity the workItem belongs to and the concrete activtyEntity + * which should be processed by the wokflowManager implementation. If the + * workItem is new the method creates a new instance for the corresponding + * process. + *

+ * The method is responsible to persist the workItem after successfully + * processing. The method returns the workItem with additional workflow + * informations defined by the workfowManager Implementation. + *

+ * The Method throws an InvalidWorkitemException if the provided workItem is + * invalid or the provided attributes $taskID and $EventID (integer) did not + * match an valid modelEntity the workItem can be processed to. + * + * @param workitem a workItem instance which should be processed + * @return the workItem instance after successful processing + * + * @throws AccessDeniedException - thrown if the user has + * insufficient access to update the + * workItem + * @throws ProcessingErrorException - thrown if the workitem could not + * be processed by the workflowKernel + * @throws AdapterExceptionAdapterException - thrown if processing by an adapter + * fails + * @throws PluginException - thrown if processing by a plugin + * fails + */ + public ItemCollection processWorkItem(ItemCollection workitem) + throws AccessDeniedException, ProcessingErrorException, PluginException, ModelException; - /** - * returns a workItem by its uniuqeID ($uniqueID) - * - * @param uniqueid - * @return WorkItem - * - */ - public ItemCollection getWorkItem(String uniqueid); + /** + * returns a workItem by its uniuqeID ($uniqueID) + * + * @param uniqueid + * @return WorkItem + * + */ + public ItemCollection getWorkItem(String uniqueid); - /** - * The method removes the provide Workitem form the persistence unit managed by the - * WorkflowManager implementation. - * - * The Method throws an InvalidWorkitemException if the provided Workitem is invalid. - * - * @param uniqueid of the WorkItem to be removed - * @throws AccessDeniedException - */ - public void removeWorkItem(ItemCollection workitem) throws AccessDeniedException; + /** + * The method removes the provide Workitem form the persistence unit managed by + * the WorkflowManager implementation. + * + * The Method throws an InvalidWorkitemException if the provided Workitem is + * invalid. + * + * @param uniqueid of the WorkItem to be removed + * @throws AccessDeniedException + */ + public void removeWorkItem(ItemCollection workitem) throws AccessDeniedException; } diff --git a/imixs-workflow-core/src/main/java/org/imixs/workflow/bpmn/BPMNModel.java b/imixs-workflow-core/src/main/java/org/imixs/workflow/bpmn/BPMNModel.java index 0477ed1af..c93336b40 100644 --- a/imixs-workflow-core/src/main/java/org/imixs/workflow/bpmn/BPMNModel.java +++ b/imixs-workflow-core/src/main/java/org/imixs/workflow/bpmn/BPMNModel.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *

- *  Imixs Workflow 
+/*  
+ *  Imixs-Workflow 
+ *  
  *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
  *  http://www.imixs.com
  *  
@@ -22,10 +22,9 @@
  *      https://github.com/imixs/imixs-workflow
  *  
  *  Contributors:  
- *      Imixs Software Solutions GmbH - initial API and implementation
+ *      Imixs Software Solutions GmbH - Project Management
  *      Ralph Soika - Software Developer
- * 
- *******************************************************************************/ + */ package org.imixs.workflow.bpmn; @@ -44,8 +43,8 @@ import org.imixs.workflow.exceptions.ModelException; /** - * The BPMNModel implements the Imixs Model Interface. The class is used by the class - * BPMNModelHandler. + * The BPMNModel implements the Imixs Model Interface. The class is used by the + * class BPMNModelHandler. * * @see BPMNModelHandler * @author rsoika @@ -53,397 +52,391 @@ */ public class BPMNModel implements Model { - public final static String TASK_ITEM_NAME = "name"; - public final static String TASK_ITEM_DOCUMENTATION = "documentation"; - public final static String TASK_ITEM_WORKFLOW_SUMMARY = "workflow.summary"; - public final static String TASK_ITEM_WORKFLOW_ABSTRACT = "workflow.abstract"; - public final static String TASK_ITEM_APPLICATION_EDITOR = "application.editor"; - public final static String TASK_ITEM_APPLICATION_ICON = "application.icon"; - public final static String TASK_ITEM_APPLICATION_TYPE = "application.type"; - public final static String TASK_ITEM_ACL_OWNER_LIST = "acl.owner_list"; - public final static String TASK_ITEM_ACL_OWNER_LIST_MAPPING = "acl.owner_list_mapping"; - public final static String TASK_ITEM_ACL_READACCESS_LIST = "acl.readaccess_list"; - public final static String TASK_ITEM_ACL_READACCESS_LIST_MAPPING = "acl.readaccess_list_mapping"; - public final static String TASK_ITEM_ACL_WRITEACCESS_LIST = "acl.writeaccess_list"; - public final static String TASK_ITEM_ACL_WRITEACCESS_LIST_MAPPING = - "acl.writeaccess_list_mapping"; - public final static String TASK_ITEM_ACL_UPDATE = "acl.update"; - - public final static String EVENT_ITEM_NAME = "name"; - public final static String EVENT_ITEM_DOCUMENTATION = "documentation"; - public final static String EVENT_ITEM_ACL_OWNER_LIST = "acl.owner_list"; - public final static String EVENT_ITEM_ACL_OWNER_LIST_MAPPING = "acl.owner_list_mapping"; - public final static String EVENT_ITEM_ACL_READACCESS_LIST = "acl.readaccess_list"; - public final static String EVENT_ITEM_ACL_READACCESS_LIST_MAPPING = "acl.readaccess_list_mapping"; - public final static String EVENT_ITEM_ACL_WRITEACCESS_LIST = "acl.writeaccess_list"; - public final static String EVENT_ITEM_ACL_WRITEACCESS_LIST_MAPPING = - "acl.writeaccess_list_mapping"; - public final static String EVENT_ITEM_ACL_UPDATE = "acl.update"; - - public final static String EVENT_ITEM_WORKFLOW_RESULT = "workflow.result"; - public final static String EVENT_ITEM_WORKFLOW_PUBLIC = "workflow.public"; - public final static String EVENT_ITEM_WORKFLOW_PUBLIC_ACTORS = "workflow.public_actors"; - public final static String EVENT_ITEM_READACCESS = "$readaccess"; - public final static String EVENT_ITEM_HISTORY_MESSAGE = "history.message"; - public final static String EVENT_ITEM_MAIL_SUBJECT = "mail.subject"; - public final static String EVENT_ITEM_MAIL_BODY = "mail.body"; - public final static String EVENT_ITEM_MAIL_TO_LIST = "mail.to_list"; - public final static String EVENT_ITEM_MAIL_TO_LIST_MAPPING = "mail.to_list_mapping"; - public final static String EVENT_ITEM_MAIL_CC_LIST = "mail.cc_list"; - public final static String EVENT_ITEM_MAIL_CC_LIST_MAPPING = "mail.cc_list_mapping"; - public final static String EVENT_ITEM_MAIL_BCC_LIST = "mail.bcc_list"; - public final static String EVENT_ITEM_MAIL_BCC_LIST_MAPPING = "mail.bcc_list_mapping"; - public final static String EVENT_ITEM_RULE_ENGINE = "rule.engine"; - public final static String EVENT_ITEM_RULE_DEFINITION = "rule.definition"; - - public final static String EVENT_ITEM_REPORT_NAME = "report.name"; - public final static String EVENT_ITEM_REPORT_PATH = "report.path"; - public final static String EVENT_ITEM_REPORT_OPTIONS = "report.options"; - public final static String EVENT_ITEM_REPORT_TARGET = "report.target"; - public final static String EVENT_ITEM_VERSION_MODE = "version.mode"; - public final static String EVENT_ITEM_VERSION_EVENT = "version.event"; - - public final static String EVENT_ITEM_TIMER_ACTIVE = "timer.active"; - public final static String EVENT_ITEM_TIMER_SELECTION = "timer.selection"; - public final static String EVENT_ITEM_TIMER_DELAY = "timer.delay"; - public final static String EVENT_ITEM_TIMER_DELAY_UNIT = "timer.delay_unit"; - public final static String EVENT_ITEM_TIMER_DELAY_BASE = "timer.delay_base"; - public final static String EVENT_ITEM_TIMER_DELAY_BASE_PROPERTY = "timer.delay_base_property"; - - private Map taskList = null; - private Map> eventList = null; - private List workflowGroups = null; - private ItemCollection definition = null; - private byte[] rawData = null; - private static Logger logger = Logger.getLogger(BPMNModel.class.getName()); - - public BPMNModel() { - taskList = new TreeMap(); - eventList = new TreeMap>(); - workflowGroups = new ArrayList(); - } - - /** - * Returns the raw data of the BPMN file - * - * @return - */ - public byte[] getRawData() { - return rawData; - } - - /** - * Set the raw data of the bpmn source file - * - * @param rawData - */ - public void setRawData(byte[] data) { - this.rawData = data; - } - - @Override - public String getVersion() { - if (definition != null) { - return definition.getModelVersion(); + public final static String TASK_ITEM_NAME = "name"; + public final static String TASK_ITEM_DOCUMENTATION = "documentation"; + public final static String TASK_ITEM_WORKFLOW_SUMMARY = "workflow.summary"; + public final static String TASK_ITEM_WORKFLOW_ABSTRACT = "workflow.abstract"; + public final static String TASK_ITEM_APPLICATION_EDITOR = "application.editor"; + public final static String TASK_ITEM_APPLICATION_ICON = "application.icon"; + public final static String TASK_ITEM_APPLICATION_TYPE = "application.type"; + public final static String TASK_ITEM_ACL_OWNER_LIST = "acl.owner_list"; + public final static String TASK_ITEM_ACL_OWNER_LIST_MAPPING = "acl.owner_list_mapping"; + public final static String TASK_ITEM_ACL_READACCESS_LIST = "acl.readaccess_list"; + public final static String TASK_ITEM_ACL_READACCESS_LIST_MAPPING = "acl.readaccess_list_mapping"; + public final static String TASK_ITEM_ACL_WRITEACCESS_LIST = "acl.writeaccess_list"; + public final static String TASK_ITEM_ACL_WRITEACCESS_LIST_MAPPING = "acl.writeaccess_list_mapping"; + public final static String TASK_ITEM_ACL_UPDATE = "acl.update"; + + public final static String EVENT_ITEM_NAME = "name"; + public final static String EVENT_ITEM_DOCUMENTATION = "documentation"; + public final static String EVENT_ITEM_ACL_OWNER_LIST = "acl.owner_list"; + public final static String EVENT_ITEM_ACL_OWNER_LIST_MAPPING = "acl.owner_list_mapping"; + public final static String EVENT_ITEM_ACL_READACCESS_LIST = "acl.readaccess_list"; + public final static String EVENT_ITEM_ACL_READACCESS_LIST_MAPPING = "acl.readaccess_list_mapping"; + public final static String EVENT_ITEM_ACL_WRITEACCESS_LIST = "acl.writeaccess_list"; + public final static String EVENT_ITEM_ACL_WRITEACCESS_LIST_MAPPING = "acl.writeaccess_list_mapping"; + public final static String EVENT_ITEM_ACL_UPDATE = "acl.update"; + + public final static String EVENT_ITEM_WORKFLOW_RESULT = "workflow.result"; + public final static String EVENT_ITEM_WORKFLOW_PUBLIC = "workflow.public"; + public final static String EVENT_ITEM_WORKFLOW_PUBLIC_ACTORS = "workflow.public_actors"; + public final static String EVENT_ITEM_READACCESS = "$readaccess"; + public final static String EVENT_ITEM_HISTORY_MESSAGE = "history.message"; + public final static String EVENT_ITEM_MAIL_SUBJECT = "mail.subject"; + public final static String EVENT_ITEM_MAIL_BODY = "mail.body"; + public final static String EVENT_ITEM_MAIL_TO_LIST = "mail.to_list"; + public final static String EVENT_ITEM_MAIL_TO_LIST_MAPPING = "mail.to_list_mapping"; + public final static String EVENT_ITEM_MAIL_CC_LIST = "mail.cc_list"; + public final static String EVENT_ITEM_MAIL_CC_LIST_MAPPING = "mail.cc_list_mapping"; + public final static String EVENT_ITEM_MAIL_BCC_LIST = "mail.bcc_list"; + public final static String EVENT_ITEM_MAIL_BCC_LIST_MAPPING = "mail.bcc_list_mapping"; + public final static String EVENT_ITEM_RULE_ENGINE = "rule.engine"; + public final static String EVENT_ITEM_RULE_DEFINITION = "rule.definition"; + + public final static String EVENT_ITEM_REPORT_NAME = "report.name"; + public final static String EVENT_ITEM_REPORT_PATH = "report.path"; + public final static String EVENT_ITEM_REPORT_OPTIONS = "report.options"; + public final static String EVENT_ITEM_REPORT_TARGET = "report.target"; + public final static String EVENT_ITEM_VERSION_MODE = "version.mode"; + public final static String EVENT_ITEM_VERSION_EVENT = "version.event"; + + public final static String EVENT_ITEM_TIMER_ACTIVE = "timer.active"; + public final static String EVENT_ITEM_TIMER_SELECTION = "timer.selection"; + public final static String EVENT_ITEM_TIMER_DELAY = "timer.delay"; + public final static String EVENT_ITEM_TIMER_DELAY_UNIT = "timer.delay_unit"; + public final static String EVENT_ITEM_TIMER_DELAY_BASE = "timer.delay_base"; + public final static String EVENT_ITEM_TIMER_DELAY_BASE_PROPERTY = "timer.delay_base_property"; + + private Map taskList = null; + private Map> eventList = null; + private List workflowGroups = null; + private ItemCollection definition = null; + private byte[] rawData = null; + private static Logger logger = Logger.getLogger(BPMNModel.class.getName()); + + public BPMNModel() { + taskList = new TreeMap(); + eventList = new TreeMap>(); + workflowGroups = new ArrayList(); } - return null; - } - - /** - * Returns the model profile entity - * - * @return - */ - public ItemCollection getDefinition() { - return new ItemCollection(definition); - } - - /** - * This method returns all Tasks coming from a Start event - * - * @return - */ - public List getStartTasks() { - - Collection allTasks = taskList.values(); - - List result = allTasks.stream() // convert list to stream - .filter(task -> task.getItemValueBoolean("startTask")) // we care only for startTasks - .collect(Collectors.toList()); // collect the output and convert streams to a List - return result; - } - - /** - * This method returns all Tasks followed by a End event - * - * @return - */ - public List getEndTasks() { - - Collection allTasks = taskList.values(); - - List result = allTasks.stream() // convert list to stream - .filter(task -> task.getItemValueBoolean("endTask")) // we care only for endTasks - .collect(Collectors.toList()); // collect the output and convert streams to a List - return result; - } - - /** - * This method returns start Events for a given Start Task. - *

- * If the task is not a start task, the method returns null! - *

- * If one of the events is connected to the BPMN:startEvent then the method returns this event - * only! - *

- * In case of none event is connected to the BPMN:startEvent then the method returns all events - * which are not follow up events - * - * @return - */ - public List getStartEvents(int taskID) { - - ItemCollection task = taskList.get(taskID); - if (task == null || !task.getItemValueBoolean("startTask")) { - // not a start task! - return null; + + /** + * Returns the raw data of the BPMN file + * + * @return + */ + public byte[] getRawData() { + return rawData; } - // check the events... - List eventsOfTask = eventList.get(taskID); + /** + * Set the raw data of the bpmn source file + * + * @param rawData + */ + public void setRawData(byte[] data) { + this.rawData = data; + } - // 1st test if we have true startEvents - List result = eventsOfTask.stream() // convert list to stream - .filter(event -> event.getItemValueBoolean("startEvent")) // we care only for startEvent - .collect(Collectors.toList()); // collect the output and convert streams to a List - if (result != null && result.size() > 0) { - // yes there are true start events! - return result; + @Override + public String getVersion() { + if (definition != null) { + return definition.getModelVersion(); + } + return null; } - // we have no true start event, so lets return all what is a Task Root Event - result = eventsOfTask.stream() // convert list to stream - .filter(event -> event.getItemValueBoolean("rootEvent")) // we care only for rootEvents - .collect(Collectors.toList()); // collect the output and convert streams to a List - return result; - - } - - @Override - public ItemCollection getTask(int taskid) throws ModelException { - ItemCollection task = taskList.get(taskid); - if (task != null) { - return new ItemCollection(task); - } else { - throw new ModelException(ModelException.UNDEFINED_MODEL_ENTRY, - "BPMN Task " + taskid + " not defined by version '" + this.getVersion() + "'"); + /** + * Returns the model profile entity + * + * @return + */ + public ItemCollection getDefinition() { + return new ItemCollection(definition); } - } - - @Override - public ItemCollection getEvent(int processid, int activityid) throws ModelException { - List activities = findAllEventsByTask(processid); - for (ItemCollection aactivity : activities) { - if (activityid == aactivity.getItemValueInteger("numactivityid")) { - return new ItemCollection(aactivity); - } + + /** + * This method returns all Tasks coming from a Start event + * + * @return + */ + public List getStartTasks() { + + Collection allTasks = taskList.values(); + + List result = allTasks.stream() // convert list to stream + .filter(task -> task.getItemValueBoolean("startTask")) // we care only for startTasks + .collect(Collectors.toList()); // collect the output and convert streams to a List + return result; } - // not found! - throw new ModelException(ModelException.UNDEFINED_MODEL_ENTRY, "BPMN Event " + processid + "." - + activityid + " not defined by version '" + this.getVersion() + "'"); - } - - public List getGroups() { - return new ArrayList<>(workflowGroups); - } - - /** - * Returns a list of all tasks. The result set is sorted by taskID. - * - * The list is a clone of the internal map values! - * - * @return list of tasks - */ - @Override - public List findAllTasks() { - List _tasks = new ArrayList(taskList.values()); - // clone task list - ArrayList result = new ArrayList(); - for (ItemCollection _task : _tasks) { - result.add(new ItemCollection(_task)); + + /** + * This method returns all Tasks followed by a End event + * + * @return + */ + public List getEndTasks() { + + Collection allTasks = taskList.values(); + + List result = allTasks.stream() // convert list to stream + .filter(task -> task.getItemValueBoolean("endTask")) // we care only for endTasks + .collect(Collectors.toList()); // collect the output and convert streams to a List + return result; } - return result; - - } - - /** - * Returns a list of all events for a given taskID. The result set is sorted by event id - * (numactivityID) - * - * @return list of tasks - */ - @Override - public List findAllEventsByTask(int processid) { - List _events = eventList.get(processid); - if (_events == null) { - return new ArrayList(); + + /** + * This method returns start Events for a given Start Task. + *

+ * If the task is not a start task, the method returns null! + *

+ * If one of the events is connected to the BPMN:startEvent then the method + * returns this event only! + *

+ * In case of none event is connected to the BPMN:startEvent then the method + * returns all events which are not follow up events + * + * @return + */ + public List getStartEvents(int taskID) { + + ItemCollection task = taskList.get(taskID); + if (task == null || !task.getItemValueBoolean("startTask")) { + // not a start task! + return null; + } + + // check the events... + List eventsOfTask = eventList.get(taskID); + + // 1st test if we have true startEvents + List result = eventsOfTask.stream() // convert list to stream + .filter(event -> event.getItemValueBoolean("startEvent")) // we care only for startEvent + .collect(Collectors.toList()); // collect the output and convert streams to a List + if (result != null && result.size() > 0) { + // yes there are true start events! + return result; + } + + // we have no true start event, so lets return all what is a Task Root Event + result = eventsOfTask.stream() // convert list to stream + .filter(event -> event.getItemValueBoolean("rootEvent")) // we care only for rootEvents + .collect(Collectors.toList()); // collect the output and convert streams to a List + return result; + } - // clone event list - ArrayList result = new ArrayList(); - for (ItemCollection _event : _events) { - result.add(new ItemCollection(_event)); + + @Override + public ItemCollection getTask(int taskid) throws ModelException { + ItemCollection task = taskList.get(taskid); + if (task != null) { + return new ItemCollection(task); + } else { + throw new ModelException(ModelException.UNDEFINED_MODEL_ENTRY, + "BPMN Task " + taskid + " not defined by version '" + this.getVersion() + "'"); + } } - return result; - } - - /*** - * Returns a list of tasks filtered by the workflow group (txtWorkflowGroup). The result set is - * sorted by taskID. - */ - @Override - public List findTasksByGroup(String group) { - List result = new ArrayList(); - if (group != null && !group.isEmpty()) { - List allTasks = findAllTasks(); - for (ItemCollection task : allTasks) { - if (group.equals(task.getItemValueString("txtworkflowgroup"))) { - result.add(task); + + @Override + public ItemCollection getEvent(int processid, int activityid) throws ModelException { + List activities = findAllEventsByTask(processid); + for (ItemCollection aactivity : activities) { + if (activityid == aactivity.getItemValueInteger("numactivityid")) { + return new ItemCollection(aactivity); + } } - } + // not found! + throw new ModelException(ModelException.UNDEFINED_MODEL_ENTRY, + "BPMN Event " + processid + "." + activityid + " not defined by version '" + this.getVersion() + "'"); } - return result; - } - - /** - * This method assigns a startTask and a startEvent to a given workitem. - *

- * The method updates the following items - *

    - *
  • $modelversion
  • - *
  • $taskid
  • - *
  • $eventid
  • - *
- *

- * In case a $taskID or $eventID is already assigned, the method did not modify the $taskID or - * $eventID. - * - * @throws ModelException - */ - public void initStartEvent(ItemCollection workitem) throws ModelException { - if (workitem == null) { - return; + + public List getGroups() { + return new ArrayList<>(workflowGroups); } - // update the model version - workitem.setModelVersion(this.getVersion()); + /** + * Returns a list of all tasks. The result set is sorted by taskID. + * + * The list is a clone of the internal map values! + * + * @return list of tasks + */ + @Override + public List findAllTasks() { + List _tasks = new ArrayList(taskList.values()); + // clone task list + ArrayList result = new ArrayList(); + for (ItemCollection _task : _tasks) { + result.add(new ItemCollection(_task)); + } + return result; - // is a $taskId and $eventId already assigned? - if (workitem.getEventID() > 0 && workitem.getTaskID() > 0) { - // no op! - return; } - // do we have a $taskID - if (workitem.getTaskID() == 0) { - // no so we take the first startTask from the model - List startTasks = this.getStartTasks(); - if (startTasks == null || startTasks.size() == 0) { - throw new ModelException(ModelException.INVALID_MODEL, "Model does not define a StartTask"); - } - workitem.setTaskID(startTasks.get(0).getItemValueInteger("numprocessid")); + /** + * Returns a list of all events for a given taskID. The result set is sorted by + * event id (numactivityID) + * + * @return list of tasks + */ + @Override + public List findAllEventsByTask(int processid) { + List _events = eventList.get(processid); + if (_events == null) { + return new ArrayList(); + } + // clone event list + ArrayList result = new ArrayList(); + for (ItemCollection _event : _events) { + result.add(new ItemCollection(_event)); + } + return result; } - // do we have a $eventID - if (workitem.getEventID() == 0) { - // no so we take the first startEvent from the start Task - int taskID = workitem.getTaskID(); - ItemCollection task = this.getTask(taskID); - if (task == null) { - throw new ModelException(ModelException.INVALID_MODEL, - "$taskid " + taskID + " not defined by model!"); - } - List startEvents = - this.getStartEvents(task.getItemValueInteger("numprocessid")); - if (startEvents == null || startEvents.size() == 0) { - throw new ModelException(ModelException.INVALID_MODEL, - "Task " + taskID + " does not define a StartEvent!"); - } - workitem.setEventID(startEvents.get(0).getItemValueInteger("numactivityid")); + /*** + * Returns a list of tasks filtered by the workflow group (txtWorkflowGroup). + * The result set is sorted by taskID. + */ + @Override + public List findTasksByGroup(String group) { + List result = new ArrayList(); + if (group != null && !group.isEmpty()) { + List allTasks = findAllTasks(); + for (ItemCollection task : allTasks) { + if (group.equals(task.getItemValueString("txtworkflowgroup"))) { + result.add(task); + } + } + } + return result; } - } - - protected void setDefinition(ItemCollection profile) { - this.definition = profile; - } - - /** - * Adds a ProcessEntiy into the process list - * - * @param entity - * @throws ModelException - */ - protected void addTask(ItemCollection entity) throws ModelException { - if (entity == null) - return; - - if (!"ProcessEntity".equals(entity.getItemValueString("type"))) { - logger.warning( - "Invalid Process Entity - wrong type '" + entity.getItemValueString("type") + "'"); - throw new ModelException(ModelException.INVALID_MODEL_ENTRY, - "Invalid Process Entity - wrong type '" + entity.getItemValueString("type") + "'"); - } + /** + * This method assigns a startTask and a startEvent to a given workitem. + *

+ * The method updates the following items + *

    + *
  • $modelversion
  • + *
  • $taskid
  • + *
  • $eventid
  • + *
+ *

+ * In case a $taskID or $eventID is already assigned, the method did not modify + * the $taskID or $eventID. + * + * @throws ModelException + */ + public void initStartEvent(ItemCollection workitem) throws ModelException { + if (workitem == null) { + return; + } + + // update the model version + workitem.setModelVersion(this.getVersion()); + + // is a $taskId and $eventId already assigned? + if (workitem.getEventID() > 0 && workitem.getTaskID() > 0) { + // no op! + return; + } + + // do we have a $taskID + if (workitem.getTaskID() == 0) { + // no so we take the first startTask from the model + List startTasks = this.getStartTasks(); + if (startTasks == null || startTasks.size() == 0) { + throw new ModelException(ModelException.INVALID_MODEL, "Model does not define a StartTask"); + } + workitem.setTaskID(startTasks.get(0).getItemValueInteger("numprocessid")); + } + + // do we have a $eventID + if (workitem.getEventID() == 0) { + // no so we take the first startEvent from the start Task + int taskID = workitem.getTaskID(); + ItemCollection task = this.getTask(taskID); + if (task == null) { + throw new ModelException(ModelException.INVALID_MODEL, "$taskid " + taskID + " not defined by model!"); + } + List startEvents = this.getStartEvents(task.getItemValueInteger("numprocessid")); + if (startEvents == null || startEvents.size() == 0) { + throw new ModelException(ModelException.INVALID_MODEL, + "Task " + taskID + " does not define a StartEvent!"); + } + workitem.setEventID(startEvents.get(0).getItemValueInteger("numactivityid")); + } - // add group? - String group = entity.getItemValueString("txtworkflowgroup"); - if (!workflowGroups.contains(group)) { - workflowGroups.add(group); - } - taskList.put(entity.getItemValueInteger("numprocessid"), entity); - } - - /** - * Adds a ProcessEntiy into the process list - * - * @param entity - */ - protected void addEvent(ItemCollection aentity) throws ModelException { - if (aentity == null) - return; - - // we need to clone the entity because of shared events.... - ItemCollection clonedEntity = new ItemCollection(aentity); - - if (!"ActivityEntity".equals(clonedEntity.getItemValueString("type"))) { - logger.warning( - "Invalid Activity Entity - wrong type '" + clonedEntity.getItemValueString("type") + "'"); } - int pID = clonedEntity.getItemValueInteger("numprocessid"); - if (pID <= 0) { - logger.warning("Invalid Activiyt Entity - no numprocessid defined!"); - throw new ModelException(ModelException.INVALID_MODEL_ENTRY, - "Invalid Activiyt Entity - no numprocessid defined!"); + protected void setDefinition(ItemCollection profile) { + this.definition = profile; } - // test version - String activitymodelversion = clonedEntity.getItemValueString(WorkflowKernel.MODELVERSION); - ItemCollection process = this.getTask(pID); - if (process == null) { - logger.warning("Invalid Activiyt Entity - no numprocessid defined in model version '" - + activitymodelversion + "' "); - throw new ModelException(ModelException.INVALID_MODEL_ENTRY, - "Invalid Activiyt Entity - no numprocessid defined!"); + /** + * Adds a ProcessEntiy into the process list + * + * @param entity + * @throws ModelException + */ + protected void addTask(ItemCollection entity) throws ModelException { + if (entity == null) + return; + + if (!"ProcessEntity".equals(entity.getItemValueString("type"))) { + logger.warning("Invalid Process Entity - wrong type '" + entity.getItemValueString("type") + "'"); + throw new ModelException(ModelException.INVALID_MODEL_ENTRY, + "Invalid Process Entity - wrong type '" + entity.getItemValueString("type") + "'"); + } + + // add group? + String group = entity.getItemValueString("txtworkflowgroup"); + if (!workflowGroups.contains(group)) { + workflowGroups.add(group); + } + taskList.put(entity.getItemValueInteger("numprocessid"), entity); } - List activities = findAllEventsByTask(pID); + /** + * Adds a ProcessEntiy into the process list + * + * @param entity + */ + protected void addEvent(ItemCollection aentity) throws ModelException { + if (aentity == null) + return; + + // we need to clone the entity because of shared events.... + ItemCollection clonedEntity = new ItemCollection(aentity); + + if (!"ActivityEntity".equals(clonedEntity.getItemValueString("type"))) { + logger.warning("Invalid Activity Entity - wrong type '" + clonedEntity.getItemValueString("type") + "'"); + } + + int pID = clonedEntity.getItemValueInteger("numprocessid"); + if (pID <= 0) { + logger.warning("Invalid Activiyt Entity - no numprocessid defined!"); + throw new ModelException(ModelException.INVALID_MODEL_ENTRY, + "Invalid Activiyt Entity - no numprocessid defined!"); + } + + // test version + String activitymodelversion = clonedEntity.getItemValueString(WorkflowKernel.MODELVERSION); + ItemCollection process = this.getTask(pID); + if (process == null) { + logger.warning("Invalid Activiyt Entity - no numprocessid defined in model version '" + activitymodelversion + + "' "); + throw new ModelException(ModelException.INVALID_MODEL_ENTRY, + "Invalid Activiyt Entity - no numprocessid defined!"); + } + + List activities = findAllEventsByTask(pID); - activities.add(clonedEntity); + activities.add(clonedEntity); - // sort event list - Collections.sort(activities, new ItemCollectionComparator("numactivityid", true)); + // sort event list + Collections.sort(activities, new ItemCollectionComparator("numactivityid", true)); - eventList.put(pID, activities); - } + eventList.put(pID, activities); + } } diff --git a/imixs-workflow-core/src/main/java/org/imixs/workflow/bpmn/BPMNModelHandler.java b/imixs-workflow-core/src/main/java/org/imixs/workflow/bpmn/BPMNModelHandler.java index cc3570188..ce8e253e4 100644 --- a/imixs-workflow-core/src/main/java/org/imixs/workflow/bpmn/BPMNModelHandler.java +++ b/imixs-workflow-core/src/main/java/org/imixs/workflow/bpmn/BPMNModelHandler.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *

- *  Imixs Workflow 
+/*  
+ *  Imixs-Workflow 
+ *  
  *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
  *  http://www.imixs.com
  *  
@@ -22,10 +22,9 @@
  *      https://github.com/imixs/imixs-workflow
  *  
  *  Contributors:  
- *      Imixs Software Solutions GmbH - initial API and implementation
+ *      Imixs Software Solutions GmbH - Project Management
  *      Ralph Soika - Software Developer
- * 
- *******************************************************************************/ + */ package org.imixs.workflow.bpmn; @@ -44,1830 +43,1824 @@ import org.xml.sax.helpers.DefaultHandler; /** - * The Imixs BPMNDefaultHandler is used to extract the Imixs Task and Event Elements of a Imixs BPMN - * model. + * The Imixs BPMNDefaultHandler is used to extract the Imixs Task and Event + * Elements of a Imixs BPMN model. * - * A BPMN file can either be a simple diagram with one process or a collaboration diagram with a - * bpmn2:collaboration definition. For collaboration diagrams the currentWorkflowGroup is read from - * the bpmn2:collaboration element. For a simple BPMN diagram type the currentWorkflowGroup is read - * from the bpmn2:process element. + * A BPMN file can either be a simple diagram with one process or a + * collaboration diagram with a bpmn2:collaboration definition. For + * collaboration diagrams the currentWorkflowGroup is read from the + * bpmn2:collaboration element. For a simple BPMN diagram type the + * currentWorkflowGroup is read from the bpmn2:process element. * - * #issue 113: The parser connects pairs of catch and throw link events with a virtual SequenceFlow - * to support the same behavior as if the link events were connected directly. + * #issue 113: The parser connects pairs of catch and throw link events with a + * virtual SequenceFlow to support the same behavior as if the link events were + * connected directly. * * @author rsoika * */ public class BPMNModelHandler extends DefaultHandler { - private static Logger logger = Logger.getLogger(BPMNModelHandler.class.getName()); - - private boolean bDefinitions = false; - private boolean bMessage = false; - private boolean bAnnotation = false; - private boolean bDataObject = false; - private boolean bSignal = false; - private boolean bExtensionElements = false; - private boolean bImixsTask = false; - private boolean bImixsEvent = false; - private boolean bThrowEvent = false; - private boolean bCatchEvent = false; - private boolean bLinkThrowEvent = false; - private boolean bLinkCatchEvent = false; - private boolean bItemValue = false; - private boolean bdocumentation = false; - private boolean bSequenceFlow = false; - - private boolean bconditionExpression = false; - - private ItemCollection currentEntity = null; - private String currentItemName = null; - private String currentItemType = null; - private String currentWorkflowGroup = null; - private String currentMessageName = null; - private String currentAnnotationName = null; - private String currentDataObjectName = null; - private String currentDataObjectID = null; - private String currentSignalID = null; - private String currentSignalName = null; - private String currentSignalRefID = null; - private String currentLinkName = null; - - private String bpmnID = null; - private StringBuilder characterStream = null; - - private BPMNModel model = null; - - private Map taskCache = null; - private Map eventCache = null; - - private Map linkThrowEventCache = null; - private Map linkCatchEventCache = null; - - private Map sequenceCache = null; - private Map associationCache = null; - private Map messageCache = null; - private Map annotationCache = null; - private Map> dataObjectCache = null; - private Map signalCache = null; - - private Map conditionCache = null; - - private List startEvents = null; - private List endEvents = null; - private List conditionalGatewayCache = null; - private List parallelGatewayCache = null; - - private ItemCollection definition = null; - - private List ignoreItemList = null; - - public BPMNModelHandler() { - super(); - model = new BPMNModel(); - // initalize cache objects - taskCache = new HashMap(); - eventCache = new HashMap(); - messageCache = new HashMap(); - annotationCache = new HashMap(); - dataObjectCache = new HashMap>(); - signalCache = new HashMap(); - conditionCache = new HashMap(); - - linkThrowEventCache = new HashMap(); - linkCatchEventCache = new HashMap(); - - conditionalGatewayCache = new ArrayList(); - parallelGatewayCache = new ArrayList(); - - startEvents = new ArrayList(); - endEvents = new ArrayList(); - - sequenceCache = new HashMap(); - associationCache = new HashMap(); - - // define items to be ignored for import - String[] array = {"txtname", "txtworkflowgroup", "numprocessid", "numactivityid", "type"}; - ignoreItemList = new ArrayList(Arrays.asList(array)); - - } - - @Override - public void startElement(String uri, String localName, String qName, Attributes attributes) - throws SAXException { - logger.finest("......Start Element :" + qName); - - // bpmn2:definitions - if (qName.equalsIgnoreCase("bpmn2:definitions")) { - bDefinitions = true; - currentEntity = new ItemCollection(); - // initialize profile entity... - currentEntity.replaceItemValue("type", "WorkflowEnvironmentEntity"); - currentEntity.replaceItemValue("txtname", "environment.profile"); + private static Logger logger = Logger.getLogger(BPMNModelHandler.class.getName()); + + private boolean bDefinitions = false; + private boolean bMessage = false; + private boolean bAnnotation = false; + private boolean bDataObject = false; + private boolean bSignal = false; + private boolean bExtensionElements = false; + private boolean bImixsTask = false; + private boolean bImixsEvent = false; + private boolean bThrowEvent = false; + private boolean bCatchEvent = false; + private boolean bLinkThrowEvent = false; + private boolean bLinkCatchEvent = false; + private boolean bItemValue = false; + private boolean bdocumentation = false; + private boolean bSequenceFlow = false; + + private boolean bconditionExpression = false; + + private ItemCollection currentEntity = null; + private String currentItemName = null; + private String currentItemType = null; + private String currentWorkflowGroup = null; + private String currentMessageName = null; + private String currentAnnotationName = null; + private String currentDataObjectName = null; + private String currentDataObjectID = null; + private String currentSignalID = null; + private String currentSignalName = null; + private String currentSignalRefID = null; + private String currentLinkName = null; + + private String bpmnID = null; + private StringBuilder characterStream = null; + + private BPMNModel model = null; + + private Map taskCache = null; + private Map eventCache = null; + + private Map linkThrowEventCache = null; + private Map linkCatchEventCache = null; + + private Map sequenceCache = null; + private Map associationCache = null; + private Map messageCache = null; + private Map annotationCache = null; + private Map> dataObjectCache = null; + private Map signalCache = null; + + private Map conditionCache = null; + + private List startEvents = null; + private List endEvents = null; + private List conditionalGatewayCache = null; + private List parallelGatewayCache = null; + + private ItemCollection definition = null; + + private List ignoreItemList = null; + + public BPMNModelHandler() { + super(); + model = new BPMNModel(); + // initalize cache objects + taskCache = new HashMap(); + eventCache = new HashMap(); + messageCache = new HashMap(); + annotationCache = new HashMap(); + dataObjectCache = new HashMap>(); + signalCache = new HashMap(); + conditionCache = new HashMap(); + + linkThrowEventCache = new HashMap(); + linkCatchEventCache = new HashMap(); + + conditionalGatewayCache = new ArrayList(); + parallelGatewayCache = new ArrayList(); + + startEvents = new ArrayList(); + endEvents = new ArrayList(); + + sequenceCache = new HashMap(); + associationCache = new HashMap(); + + // define items to be ignored for import + String[] array = { "txtname", "txtworkflowgroup", "numprocessid", "numactivityid", "type" }; + ignoreItemList = new ArrayList(Arrays.asList(array)); + + } + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + logger.finest("......Start Element :" + qName); + + // bpmn2:definitions + if (qName.equalsIgnoreCase("bpmn2:definitions")) { + bDefinitions = true; + currentEntity = new ItemCollection(); + // initialize profile entity... + currentEntity.replaceItemValue("type", "WorkflowEnvironmentEntity"); + currentEntity.replaceItemValue("txtname", "environment.profile"); - } + } - // bpmn2:process - parse workflowGroup - if (qName.equalsIgnoreCase("bpmn2:process")) { - if (bDefinitions && currentEntity != null) { - definition = currentEntity; - bDefinitions = false; - - } - currentWorkflowGroup = attributes.getValue("name"); - if (currentWorkflowGroup == null || currentWorkflowGroup.isEmpty()) { - logger.warning("No process name defined!"); - currentWorkflowGroup = "Default"; - } - } + // bpmn2:process - parse workflowGroup + if (qName.equalsIgnoreCase("bpmn2:process")) { + if (bDefinitions && currentEntity != null) { + definition = currentEntity; + bDefinitions = false; - // bpmn2:startEvent - if (qName.equalsIgnoreCase("bpmn2:startEvent")) { - startEvents.add(attributes.getValue("id")); - } + } + currentWorkflowGroup = attributes.getValue("name"); + if (currentWorkflowGroup == null || currentWorkflowGroup.isEmpty()) { + logger.warning("No process name defined!"); + currentWorkflowGroup = "Default"; + } + } - // bpmn2:endEvent - if (qName.equalsIgnoreCase("bpmn2:endEvent")) { - endEvents.add(attributes.getValue("id")); - } + // bpmn2:startEvent + if (qName.equalsIgnoreCase("bpmn2:startEvent")) { + startEvents.add(attributes.getValue("id")); + } - // bpmn2:task - identify a Imixs Workflow Taks element - if (qName.equalsIgnoreCase("bpmn2:task")) { - - // imixs Task element? - String value = attributes.getValue("imixs:processid"); - if (value == null) { - return; - } - - bImixsTask = true; - int currentID = Integer.parseInt(value); - currentEntity = new ItemCollection(); - bpmnID = attributes.getValue("id"); - currentEntity.replaceItemValue("type", "ProcessEntity"); - currentEntity.replaceItemValue("txtname", attributes.getValue("name")); - currentEntity.replaceItemValue("txtworkflowgroup", currentWorkflowGroup); - currentEntity.replaceItemValue("numprocessid", currentID); - } + // bpmn2:endEvent + if (qName.equalsIgnoreCase("bpmn2:endEvent")) { + endEvents.add(attributes.getValue("id")); + } - // bpmn2:intermediateCatchEvent - identify link events... - if (qName.equalsIgnoreCase("bpmn2:intermediateThrowEvent") - && attributes.getValue("imixs:activityid") == null) { - bThrowEvent = true; - currentLinkName = attributes.getValue("name"); - bpmnID = attributes.getValue("id"); - return; - } - if (bThrowEvent && qName.equalsIgnoreCase("bpmn2:linkEventDefinition")) { - bLinkThrowEvent = true; - bThrowEvent = false; - return; - } - // bpmn2:intermediateCatchEvent - identify link events... - if (qName.equalsIgnoreCase("bpmn2:intermediateCatchEvent") - && attributes.getValue("imixs:activityid") == null) { - bCatchEvent = true; - currentLinkName = attributes.getValue("name"); - bpmnID = attributes.getValue("id"); - return; - } - if (bCatchEvent && qName.equalsIgnoreCase("bpmn2:linkEventDefinition")) { - bLinkCatchEvent = true; - bCatchEvent = false; - return; - } + // bpmn2:task - identify a Imixs Workflow Taks element + if (qName.equalsIgnoreCase("bpmn2:task")) { - // bpmn2:intermediateCatchEvent - identify a Imixs Workflow Event - // element - if (qName.equalsIgnoreCase("bpmn2:intermediateCatchEvent") - || qName.equalsIgnoreCase("bpmn2:intermediateThrowEvent")) { - - // imixs Event element? - String value = attributes.getValue("imixs:activityid"); - if (value == null) { - return; - } - - bImixsEvent = true; - int currentID = Integer.parseInt(value); - currentEntity = new ItemCollection(); - bpmnID = attributes.getValue("id"); - currentEntity.replaceItemValue("type", "ActivityEntity"); - currentEntity.replaceItemValue("txtname", attributes.getValue("name")); - currentEntity.replaceItemValue("numactivityid", currentID); - currentSignalRefID = null; - } + // imixs Task element? + String value = attributes.getValue("imixs:processid"); + if (value == null) { + return; + } - // bpmn2:sequenceFlow - cache all sequenceFlows... - if (qName.equalsIgnoreCase("bpmn2:sequenceFlow")) { - bpmnID = attributes.getValue("id"); - bSequenceFlow = true; - String source = attributes.getValue("sourceRef"); - String target = attributes.getValue("targetRef"); - sequenceCache.put(bpmnID, new SequenceFlow(source, target)); - } + bImixsTask = true; + int currentID = Integer.parseInt(value); + currentEntity = new ItemCollection(); + bpmnID = attributes.getValue("id"); + currentEntity.replaceItemValue("type", "ProcessEntity"); + currentEntity.replaceItemValue("txtname", attributes.getValue("name")); + currentEntity.replaceItemValue("txtworkflowgroup", currentWorkflowGroup); + currentEntity.replaceItemValue("numprocessid", currentID); + } - // bpmn2:association - cache all association... - if (qName.equalsIgnoreCase("bpmn2:association")) { - bpmnID = attributes.getValue("id"); - String source = attributes.getValue("sourceRef"); - String target = attributes.getValue("targetRef"); - associationCache.put(bpmnID, new SequenceFlow(source, target)); - } + // bpmn2:intermediateCatchEvent - identify link events... + if (qName.equalsIgnoreCase("bpmn2:intermediateThrowEvent") && attributes.getValue("imixs:activityid") == null) { + bThrowEvent = true; + currentLinkName = attributes.getValue("name"); + bpmnID = attributes.getValue("id"); + return; + } + if (bThrowEvent && qName.equalsIgnoreCase("bpmn2:linkEventDefinition")) { + bLinkThrowEvent = true; + bThrowEvent = false; + return; + } + // bpmn2:intermediateCatchEvent - identify link events... + if (qName.equalsIgnoreCase("bpmn2:intermediateCatchEvent") && attributes.getValue("imixs:activityid") == null) { + bCatchEvent = true; + currentLinkName = attributes.getValue("name"); + bpmnID = attributes.getValue("id"); + return; + } + if (bCatchEvent && qName.equalsIgnoreCase("bpmn2:linkEventDefinition")) { + bLinkCatchEvent = true; + bCatchEvent = false; + return; + } - /* - * parse a imixs:item - */ - if (qName.equalsIgnoreCase("imixs:item")) { - // check attributes - currentItemName = attributes.getValue("name"); - currentItemType = attributes.getValue("type"); - } + // bpmn2:intermediateCatchEvent - identify a Imixs Workflow Event + // element + if (qName.equalsIgnoreCase("bpmn2:intermediateCatchEvent") + || qName.equalsIgnoreCase("bpmn2:intermediateThrowEvent")) { - /* - * parse a imixs:value - */ - if (qName.equalsIgnoreCase("imixs:value")) { - bItemValue = true; - characterStream = new StringBuilder(); - } + // imixs Event element? + String value = attributes.getValue("imixs:activityid"); + if (value == null) { + return; + } - if (qName.equalsIgnoreCase("bpmn2:extensionElements")) { - bExtensionElements = true; - } + bImixsEvent = true; + int currentID = Integer.parseInt(value); + currentEntity = new ItemCollection(); + bpmnID = attributes.getValue("id"); + currentEntity.replaceItemValue("type", "ActivityEntity"); + currentEntity.replaceItemValue("txtname", attributes.getValue("name")); + currentEntity.replaceItemValue("numactivityid", currentID); + currentSignalRefID = null; + } - if (qName.equalsIgnoreCase("bpmn2:documentation")) { - bdocumentation = true; - characterStream = new StringBuilder(); - } + // bpmn2:sequenceFlow - cache all sequenceFlows... + if (qName.equalsIgnoreCase("bpmn2:sequenceFlow")) { + bpmnID = attributes.getValue("id"); + bSequenceFlow = true; + String source = attributes.getValue("sourceRef"); + String target = attributes.getValue("targetRef"); + sequenceCache.put(bpmnID, new SequenceFlow(source, target)); + } - if (qName.equalsIgnoreCase("bpmn2:message")) { - bMessage = true; - currentMessageName = attributes.getValue("name"); - } + // bpmn2:association - cache all association... + if (qName.equalsIgnoreCase("bpmn2:association")) { + bpmnID = attributes.getValue("id"); + String source = attributes.getValue("sourceRef"); + String target = attributes.getValue("targetRef"); + associationCache.put(bpmnID, new SequenceFlow(source, target)); + } - // parallel gateway - if (qName.equalsIgnoreCase("bpmn2:exclusiveGateway") - || qName.equalsIgnoreCase("bpmn2:inclusiveGateway") - || qName.equalsIgnoreCase("bpmn2:eventBasedGateway")) { - // Put conditional Gateway ID into the gateway cache... - conditionalGatewayCache.add(attributes.getValue("id")); - } + /* + * parse a imixs:item + */ + if (qName.equalsIgnoreCase("imixs:item")) { + // check attributes + currentItemName = attributes.getValue("name"); + currentItemType = attributes.getValue("type"); + } - // conditional gateway - if (qName.equalsIgnoreCase("bpmn2:parallelGateway")) { - // Put parallel Gateway ID into the gateway cache... - parallelGatewayCache.add(attributes.getValue("id")); - } + /* + * parse a imixs:value + */ + if (qName.equalsIgnoreCase("imixs:value")) { + bItemValue = true; + characterStream = new StringBuilder(); + } - // test for conditional Expression... - if (qName.equalsIgnoreCase("bpmn2:conditionExpression")) { - bconditionExpression = true; - characterStream = new StringBuilder(); - } + if (qName.equalsIgnoreCase("bpmn2:extensionElements")) { + bExtensionElements = true; + } - if (qName.equalsIgnoreCase("bpmn2:textAnnotation")) { - bAnnotation = true; - currentAnnotationName = attributes.getValue("id"); - } + if (qName.equalsIgnoreCase("bpmn2:documentation")) { + bdocumentation = true; + characterStream = new StringBuilder(); + } - if (qName.equalsIgnoreCase("bpmn2:dataObject")) { - bDataObject = true; - currentDataObjectID = attributes.getValue("id"); - currentDataObjectName = attributes.getValue("name"); - } + if (qName.equalsIgnoreCase("bpmn2:message")) { + bMessage = true; + currentMessageName = attributes.getValue("name"); + } - if (qName.equalsIgnoreCase("bpmn2:signal")) { - bSignal = true; - currentSignalID = attributes.getValue("id"); - currentSignalName = attributes.getValue("name"); - } + // parallel gateway + if (qName.equalsIgnoreCase("bpmn2:exclusiveGateway") || qName.equalsIgnoreCase("bpmn2:inclusiveGateway") + || qName.equalsIgnoreCase("bpmn2:eventBasedGateway")) { + // Put conditional Gateway ID into the gateway cache... + conditionalGatewayCache.add(attributes.getValue("id")); + } - if (qName.equalsIgnoreCase("bpmn2:signalEventDefinition")) { - currentSignalRefID = attributes.getValue("signalRef"); - } + // conditional gateway + if (qName.equalsIgnoreCase("bpmn2:parallelGateway")) { + // Put parallel Gateway ID into the gateway cache... + parallelGatewayCache.add(attributes.getValue("id")); + } - } + // test for conditional Expression... + if (qName.equalsIgnoreCase("bpmn2:conditionExpression")) { + bconditionExpression = true; + characterStream = new StringBuilder(); + } - @SuppressWarnings({"unchecked", "rawtypes"}) - @Override - public void endElement(String uri, String localName, String qName) throws SAXException { + if (qName.equalsIgnoreCase("bpmn2:textAnnotation")) { + bAnnotation = true; + currentAnnotationName = attributes.getValue("id"); + } - // end of bpmn2:process - if (qName.equalsIgnoreCase("bpmn2:process")) { - if (currentWorkflowGroup != null) { - currentWorkflowGroup = null; - } - } + if (qName.equalsIgnoreCase("bpmn2:dataObject")) { + bDataObject = true; + currentDataObjectID = attributes.getValue("id"); + currentDataObjectName = attributes.getValue("name"); + } - // end of bpmn2:task - - if (bImixsTask && qName.equalsIgnoreCase("bpmn2:task")) { - bImixsTask = false; + if (qName.equalsIgnoreCase("bpmn2:signal")) { + bSignal = true; + currentSignalID = attributes.getValue("id"); + currentSignalName = attributes.getValue("name"); + } - // adapt deprecated proptery format - adaptDeprecatedTaskProperties(currentEntity); + if (qName.equalsIgnoreCase("bpmn2:signalEventDefinition")) { + currentSignalRefID = attributes.getValue("signalRef"); + } - taskCache.put(bpmnID, currentEntity); } - if (qName.equalsIgnoreCase("bpmn2:extensionElements")) { - bExtensionElements = false; - } + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override + public void endElement(String uri, String localName, String qName) throws SAXException { - // end of bpmn2:intermediateCatchEvent - - if (bImixsEvent && (qName.equalsIgnoreCase("bpmn2:intermediateCatchEvent") - || qName.equalsIgnoreCase("bpmn2:intermediateThrowEvent"))) { - bImixsEvent = false; + // end of bpmn2:process + if (qName.equalsIgnoreCase("bpmn2:process")) { + if (currentWorkflowGroup != null) { + currentWorkflowGroup = null; + } + } - // adapt deprecated proptery format - adaptDeprecatedEventProperties(currentEntity); + // end of bpmn2:task - + if (bImixsTask && qName.equalsIgnoreCase("bpmn2:task")) { + bImixsTask = false; - // adapter ? - if (currentSignalRefID != null && !currentSignalRefID.isEmpty()) { - String signalName = signalCache.get(currentSignalRefID); - if (signalName != null && !signalName.isEmpty()) { - currentEntity.setItemValue("adapter.id", signalName); - } else { - logger.warning("Event " + currentEntity.getItemValueInteger("id") + " Signal Ref " - + signalName + " is not defined!"); + // adapt deprecated proptery format + adaptDeprecatedTaskProperties(currentEntity); + + taskCache.put(bpmnID, currentEntity); } - } - // we need to cache the activities because the sequence flows must be - // analyzed later - eventCache.put(bpmnID, currentEntity); - } + if (qName.equalsIgnoreCase("bpmn2:extensionElements")) { + bExtensionElements = false; + } - /* - * End of a imixs:value - */ - if (qName.equalsIgnoreCase("imixs:value")) { - if (bExtensionElements && bItemValue && currentEntity != null && characterStream != null) { + // end of bpmn2:intermediateCatchEvent - + if (bImixsEvent && (qName.equalsIgnoreCase("bpmn2:intermediateCatchEvent") + || qName.equalsIgnoreCase("bpmn2:intermediateThrowEvent"))) { + bImixsEvent = false; + + // adapt deprecated proptery format + adaptDeprecatedEventProperties(currentEntity); + + // adapter ? + if (currentSignalRefID != null && !currentSignalRefID.isEmpty()) { + String signalName = signalCache.get(currentSignalRefID); + if (signalName != null && !signalName.isEmpty()) { + currentEntity.setItemValue("adapter.id", signalName); + } else { + logger.warning("Event " + currentEntity.getItemValueInteger("id") + " Signal Ref " + signalName + + " is not defined!"); + } + } - String svalue = characterStream.toString(); - List valueList = currentEntity.getItemValue(currentItemName); + // we need to cache the activities because the sequence flows must be + // analyzed later + eventCache.put(bpmnID, currentEntity); + } - if ("xs:boolean".equals(currentItemType.toLowerCase())) { - valueList.add(Boolean.valueOf(svalue)); - } else if ("xs:integer".equals(currentItemType.toLowerCase())) { - valueList.add(Integer.valueOf(svalue)); - } else { - valueList.add(svalue); + /* + * End of a imixs:value + */ + if (qName.equalsIgnoreCase("imixs:value")) { + if (bExtensionElements && bItemValue && currentEntity != null && characterStream != null) { + + String svalue = characterStream.toString(); + List valueList = currentEntity.getItemValue(currentItemName); + + if ("xs:boolean".equals(currentItemType.toLowerCase())) { + valueList.add(Boolean.valueOf(svalue)); + } else if ("xs:integer".equals(currentItemType.toLowerCase())) { + valueList.add(Integer.valueOf(svalue)); + } else { + valueList.add(svalue); + } + + // item will only be added if it is not listed in the ignoreItem + // List! + if (!ignoreItemList.contains(currentItemName)) { + currentEntity.replaceItemValue(currentItemName, valueList); + } + } + bItemValue = false; + characterStream = null; } - // item will only be added if it is not listed in the ignoreItem - // List! - if (!ignoreItemList.contains(currentItemName)) { - currentEntity.replaceItemValue(currentItemName, valueList); + if (qName.equalsIgnoreCase("bpmn2:documentation")) { + if (currentEntity != null) { + currentEntity.replaceItemValue("rtfdescription", characterStream.toString()); + } + + // bpmn2:message? + if (bMessage) { + // cache the message... + messageCache.put(currentMessageName, characterStream.toString()); + bMessage = false; + } + + // bpmn2:annotation? + if (bAnnotation) { + // cache the annotation + annotationCache.put(currentAnnotationName, characterStream.toString()); + bAnnotation = false; + } + + // bpmn2:dataObject? + if (bDataObject) { + // cache the dataObject + List dataobject = new ArrayList(); + dataobject.add(currentDataObjectName); + dataobject.add(characterStream.toString()); + dataObjectCache.put(currentDataObjectID, dataobject); + bDataObject = false; + } + + characterStream = null; + bdocumentation = false; } - } - bItemValue = false; - characterStream = null; - } - if (qName.equalsIgnoreCase("bpmn2:documentation")) { - if (currentEntity != null) { - currentEntity.replaceItemValue("rtfdescription", characterStream.toString()); - } - - // bpmn2:message? - if (bMessage) { - // cache the message... - messageCache.put(currentMessageName, characterStream.toString()); - bMessage = false; - } - - // bpmn2:annotation? - if (bAnnotation) { - // cache the annotation - annotationCache.put(currentAnnotationName, characterStream.toString()); - bAnnotation = false; - } - - // bpmn2:dataObject? - if (bDataObject) { - // cache the dataObject - List dataobject = new ArrayList(); - dataobject.add(currentDataObjectName); - dataobject.add(characterStream.toString()); - dataObjectCache.put(currentDataObjectID, dataobject); - bDataObject = false; - } - - characterStream = null; - bdocumentation = false; - } + // bpmn2:signal? + if (bSignal) { + // cache the Signal + signalCache.put(currentSignalID, currentSignalName); + bSignal = false; + } - // bpmn2:signal? - if (bSignal) { - // cache the Signal - signalCache.put(currentSignalID, currentSignalName); - bSignal = false; - } + // end of bpmn2:intermediateThrowEvent - + if (bLinkThrowEvent && !bLinkCatchEvent && (qName.equalsIgnoreCase("bpmn2:linkEventDefinition"))) { + bLinkThrowEvent = false; + // we need to cache the link name + linkThrowEventCache.put(bpmnID, currentLinkName); + } - // end of bpmn2:intermediateThrowEvent - - if (bLinkThrowEvent && !bLinkCatchEvent - && (qName.equalsIgnoreCase("bpmn2:linkEventDefinition"))) { - bLinkThrowEvent = false; - // we need to cache the link name - linkThrowEventCache.put(bpmnID, currentLinkName); - } + // end of bpmn2:intermediateCatchEvent - + if (bLinkCatchEvent && !bLinkThrowEvent && (qName.equalsIgnoreCase("bpmn2:linkEventDefinition"))) { + bLinkCatchEvent = false; + // we need to cache the link name + linkCatchEventCache.put(currentLinkName, bpmnID); + } - // end of bpmn2:intermediateCatchEvent - - if (bLinkCatchEvent && !bLinkThrowEvent - && (qName.equalsIgnoreCase("bpmn2:linkEventDefinition"))) { - bLinkCatchEvent = false; - // we need to cache the link name - linkCatchEventCache.put(currentLinkName, bpmnID); - } + // test conditional sequence flow... + if (bSequenceFlow && bconditionExpression && qName.equalsIgnoreCase("bpmn2:conditionExpression")) { + String svalue = characterStream.toString(); + logger.finest("......conditional SequenceFlow:" + bpmnID + "=" + svalue); + bconditionExpression = false; + conditionCache.put(bpmnID, svalue); + } - // test conditional sequence flow... - if (bSequenceFlow && bconditionExpression - && qName.equalsIgnoreCase("bpmn2:conditionExpression")) { - String svalue = characterStream.toString(); - logger.finest("......conditional SequenceFlow:" + bpmnID + "=" + svalue); - bconditionExpression = false; - conditionCache.put(bpmnID, svalue); } - } + // @SuppressWarnings({ "rawtypes", "unchecked" }) + @Override + public void characters(char ch[], int start, int length) throws SAXException { - // @SuppressWarnings({ "rawtypes", "unchecked" }) - @Override - public void characters(char ch[], int start, int length) throws SAXException { + if (bdocumentation || bconditionExpression) { + characterStream = characterStream.append(new String(ch, start, length)); + } - if (bdocumentation || bconditionExpression) { - characterStream = characterStream.append(new String(ch, start, length)); - } + /* + * parse a imixs:value + */ + if (bExtensionElements && bItemValue && currentEntity != null) { + characterStream = characterStream.append(new String(ch, start, length)); + } - /* - * parse a imixs:value - */ - if (bExtensionElements && bItemValue && currentEntity != null) { - characterStream = characterStream.append(new String(ch, start, length)); } - } - - /** - * This method builds the model from the information parsed by the handler. First all task - * elements were adds as unique process entities into the model. In the second step the method - * adds the Activity elements to the assigned Task. We look also for activities with no incoming - * SequenceFlow. - * - * The builder verifies the ProcessIDs for each task element to guaranty that the numProcessID is - * unique - * - * The build connects pairs of Catch and Throw LinkEvents with a virtual SequenceFlow to support - * the same behavior as if those elements where connected directly in the model. - * - * The method tests the model for bpmn2:message elements and replace links in Activity elements - * attribute 'rtfMailBody' - * - * @throws ModelException - */ - public BPMNModel buildModel() throws ModelException { - - String modelVersion = definition.getItemValueString("txtworkflowmodelversion"); - definition.replaceItemValue("$modelversion", modelVersion); - model = new BPMNModel(); - - model.setDefinition(definition); - - // create virtual sequence Flows for LinkEvents if available - for (String sourceID : linkThrowEventCache.keySet()) { - String linkName = linkThrowEventCache.get(sourceID); - // test if we found a matching target CatchEvent - String targetID = linkCatchEventCache.get(linkName); - if (targetID != null) { - // build a virtual sequenceFlow... - sequenceCache.put(WorkflowKernel.generateUniqueID(), new SequenceFlow(sourceID, targetID)); - } - } + /** + * This method builds the model from the information parsed by the handler. + * First all task elements were adds as unique process entities into the model. + * In the second step the method adds the Activity elements to the assigned + * Task. We look also for activities with no incoming SequenceFlow. + * + * The builder verifies the ProcessIDs for each task element to guaranty that + * the numProcessID is unique + * + * The build connects pairs of Catch and Throw LinkEvents with a virtual + * SequenceFlow to support the same behavior as if those elements where + * connected directly in the model. + * + * The method tests the model for bpmn2:message elements and replace links in + * Activity elements attribute 'rtfMailBody' + * + * @throws ModelException + */ + public BPMNModel buildModel() throws ModelException { + + String modelVersion = definition.getItemValueString("txtworkflowmodelversion"); + definition.replaceItemValue("$modelversion", modelVersion); + model = new BPMNModel(); + + model.setDefinition(definition); + + // create virtual sequence Flows for LinkEvents if available + for (String sourceID : linkThrowEventCache.keySet()) { + String linkName = linkThrowEventCache.get(sourceID); + // test if we found a matching target CatchEvent + String targetID = linkCatchEventCache.get(linkName); + if (targetID != null) { + // build a virtual sequenceFlow... + sequenceCache.put(WorkflowKernel.generateUniqueID(), new SequenceFlow(sourceID, targetID)); + } + } - // add all Imixs tasks into the model and validate the processids - List processIDList = new ArrayList(); - for (String key : taskCache.keySet()) { - ItemCollection task = taskCache.get(key); - // check if numProcessID is unique... - int pId = task.getItemValueInteger("numProcessID"); - if (processIDList.contains(pId)) { - // we need a new pid! - pId = processIDList.get(processIDList.size() - 1); - pId = pId + 100; - logger.warning("Task " + task.getItemValueInteger("numProcessID") + " (" - + task.getItemValueString("txtname") + ") is not unique, assigning new ProcessID " + pId - + ". Please verify the XML content."); - task.replaceItemValue("numProcessID", pId); - // update task in cache - taskCache.put(key, task); - } - - // update modelversion... - task.replaceItemValue("$modelVersion", modelVersion); - - // look for optional annotations.... - // annotation text will be added to the task if the task has yet no - // documentation - String annotationText = getAnnotationForElement(key); - if (annotationText != null && task.getItemValueString("rtfdescription").isEmpty()) { - // we take the annotation as the new documentation - task.replaceItemValue("rtfdescription", annotationText); - } - - // look for optional dataObjects... - List> dataObjectList = getDataObjectsForElement(key); - if (dataObjectList != null) { - // we take the annotation as the new documentation - task.replaceItemValue("dataObjects", dataObjectList); - } - - if (isStartTask(key)) { - task.setItemValue("startTask", true); - } - if (isEndTask(key)) { - task.setItemValue("endTask", true); - } - - model.addTask(task); - - // add id and resort - processIDList.add(pId); - Collections.sort(processIDList); - } + // add all Imixs tasks into the model and validate the processids + List processIDList = new ArrayList(); + for (String key : taskCache.keySet()) { + ItemCollection task = taskCache.get(key); + // check if numProcessID is unique... + int pId = task.getItemValueInteger("numProcessID"); + if (processIDList.contains(pId)) { + // we need a new pid! + pId = processIDList.get(processIDList.size() - 1); + pId = pId + 100; + logger.warning("Task " + task.getItemValueInteger("numProcessID") + " (" + + task.getItemValueString("txtname") + ") is not unique, assigning new ProcessID " + pId + + ". Please verify the XML content."); + task.replaceItemValue("numProcessID", pId); + // update task in cache + taskCache.put(key, task); + } - // Iterate over all Imixs Event IDs and add them to the corresponding - // Imixs Task Elements - for (String eventID : eventCache.keySet()) { - List sourceTaskList = findSourceTasks(eventID); - for (ItemCollection sourceTask : sourceTaskList) { - addImixsEvent(eventID, sourceTask); - } - } + // update modelversion... + task.replaceItemValue("$modelVersion", modelVersion); - return model; - - } - - /** - * This method returns the documentation of connected objects (e.g. annotations or dataObjects) to - * a given Element - * - * @param elementID - BPMN element linked with an annotation - * @return - the documentation text or null if no annotation was linked - **/ - private String getAnnotationForElement(String elementID) { - StringBuilder builder = new StringBuilder(); - // check all annotations.... - for (Map.Entry entry : annotationCache.entrySet()) { - String id = entry.getKey(); - String annotation = entry.getValue(); - if (annotation == null || annotation.trim().isEmpty()) { - continue; - } - // test if the elementID is connected to this annotation.... - List resultList = findIncomingAssociations(elementID); - for (SequenceFlow flow : resultList) { - if (flow.source.equals(id)) { - builder.append(annotation); - } - } - } + // look for optional annotations.... + // annotation text will be added to the task if the task has yet no + // documentation + String annotationText = getAnnotationForElement(key); + if (annotationText != null && task.getItemValueString("rtfdescription").isEmpty()) { + // we take the annotation as the new documentation + task.replaceItemValue("rtfdescription", annotationText); + } - if (builder.length() > 0) { - return builder.toString(); - } else { - return null; - } + // look for optional dataObjects... + List> dataObjectList = getDataObjectsForElement(key); + if (dataObjectList != null) { + // we take the annotation as the new documentation + task.replaceItemValue("dataObjects", dataObjectList); + } - } - - /** - * This method returns the documentations of connected dataObjects to a given Element - * - * @param elementID - BPMN element linked with an annotation - * @return - a list of arrays containing the dataObjectID and the documentation - **/ - private List> getDataObjectsForElement(String elementID) { - List> result = new ArrayList>(); - - // check all annotations.... - for (Map.Entry> entry : dataObjectCache.entrySet()) { - String id = entry.getKey(); - List dataobject = entry.getValue(); - if (dataobject == null || dataobject.size() == 0) { - continue; - } - // test if the elementID is connected to this annotation.... - List resultList = findIncomingAssociations(elementID); - for (SequenceFlow flow : resultList) { - if (flow.source.equals(id)) { - result.add(dataobject); - } - } - } + if (isStartTask(key)) { + task.setItemValue("startTask", true); + } + if (isEndTask(key)) { + task.setItemValue("endTask", true); + } - if (result.size() > 0) { - return result; - } else { - return null; - } + model.addTask(task); - } + // add id and resort + processIDList.add(pId); + Collections.sort(processIDList); + } - // check if this task is connected to a start event.... - private boolean isStartTask(String taskID) { - List inFlows = findIncomingFlows(taskID); - if (inFlows != null && inFlows.size() > 0) { - for (SequenceFlow aFlow : inFlows) { - String id = new ElementResolver().findStartEvent(aFlow, true); - if (id != null) { - return true; + // Iterate over all Imixs Event IDs and add them to the corresponding + // Imixs Task Elements + for (String eventID : eventCache.keySet()) { + List sourceTaskList = findSourceTasks(eventID); + for (ItemCollection sourceTask : sourceTaskList) { + addImixsEvent(eventID, sourceTask); + } } - } - } - return false; - } - - // check if this event is connected to a start event.... - private boolean isStartEvent(String eventID) { - List inFlows = findIncomingFlows(eventID); - if (inFlows != null && inFlows.size() > 0) { - for (SequenceFlow aFlow : inFlows) { - String id = new ElementResolver().findStartEvent(aFlow, false); - if (id != null) { - return true; - } - } - } - return false; - } - - // check if this event is a root event, connected directly to a task.... - private boolean isRootEvent(String eventID) { - List inFlows = findIncomingFlows(eventID); - if (inFlows != null) { - - if (inFlows.size() == 0) { - // this is a loop event - return true; - } - - for (SequenceFlow aFlow : inFlows) { - List sourceTaskList = new ArrayList(); - sourceTaskList = new ElementResolver().findAllImixsSourceTasks(aFlow, sourceTaskList); - if (sourceTaskList != null && sourceTaskList.size() > 0) { - return true; - } - } - } - return false; - } - - // check if this task is connected to an end event.... - private boolean isEndTask(String taskID) { - List outFlows = findOutgoingFlows(taskID); - if (outFlows != null && outFlows.size() > 0) { - for (SequenceFlow aFlow : outFlows) { - String id = new ElementResolver().findEndEvent(aFlow); - if (id != null) { - return true; - } - } - } - return false; - } - - /** - * This method returns all SourceTask Elements connected to a given eventID. The method takes care - * about loop events and follow up events. Later ones are handled by the method addImixsEvent(). - * For that reason, the result of this method can be also an empty list. - * - * An event can be a shared event so it is possible that more than one source tasks are found - * - * @param eventID - * @throws ModelException - */ - private List findSourceTasks(String eventID) throws ModelException { - List result = new ArrayList(); - boolean isFollowUp = false; - - // first we lookup all possible incoming flows to identify direct source - // tasks - List inFlows = findIncomingFlows(eventID); - - if (inFlows != null && inFlows.size() > 0) { - for (SequenceFlow aFlow : inFlows) { - List sourceTaskList = new ArrayList(); - sourceTaskList = new ElementResolver().findAllImixsSourceTasks(aFlow, sourceTaskList); - if (sourceTaskList.size() > 0) { - result.addAll(sourceTaskList); - } else { - // we found no source task. Test if the incoming flow is a - // event. Than we can ignore this flow! (follow up event) - ItemCollection sourceEvent = new ElementResolver().findImixsSourceEvent(aFlow); - if (sourceEvent != null) { - isFollowUp = true; - // ignore - continue; - } else { - // now as we found no task or event we check if we got a - // start event! - if (startEvents.contains(aFlow.source)) { - // all possible target Tasks are the source tasks - // for this event! - List outFlows = findOutgoingFlows(eventID); - List targetTaskList = new ArrayList(); - - for (SequenceFlow outgoingFlow : outFlows) { - targetTaskList = - new ElementResolver().findAllImixsTargetTaskIDs(outgoingFlow, targetTaskList); - } - // here we return all possible target tasks - for (String targetID : targetTaskList) { - result.add(taskCache.get(targetID)); - } - } - } - } - } - - // finish? - if (result.size() > 0) { - // we do not test for loop events - return result; - } + + return model; + } - // possible a loop event, if this is no followUp. - // so we test the target task... + /** + * This method returns the documentation of connected objects (e.g. annotations + * or dataObjects) to a given Element + * + * @param elementID - BPMN element linked with an annotation + * @return - the documentation text or null if no annotation was linked + **/ + private String getAnnotationForElement(String elementID) { + StringBuilder builder = new StringBuilder(); + // check all annotations.... + for (Map.Entry entry : annotationCache.entrySet()) { + String id = entry.getKey(); + String annotation = entry.getValue(); + if (annotation == null || annotation.trim().isEmpty()) { + continue; + } + // test if the elementID is connected to this annotation.... + List resultList = findIncomingAssociations(elementID); + for (SequenceFlow flow : resultList) { + if (flow.source.equals(id)) { + builder.append(annotation); + } + } + } - if (!isFollowUp) { + if (builder.length() > 0) { + return builder.toString(); + } else { + return null; + } - List outFlows = findOutgoingFlows(eventID); - if (outFlows != null && outFlows.size() != 1) { - // invalid model!! - throw new ModelException(ModelException.INVALID_MODEL, - "Imixs BPMN Event '" + eventID + "' has none or more than one targets!"); - } + } - // test target element.... - // issue #211 - verify all outFlows! - // SequenceFlow outgoingFlow = outFlows.get(0); + /** + * This method returns the documentations of connected dataObjects to a given + * Element + * + * @param elementID - BPMN element linked with an annotation + * @return - a list of arrays containing the dataObjectID and the documentation + **/ + private List> getDataObjectsForElement(String elementID) { + List> result = new ArrayList>(); + + // check all annotations.... + for (Map.Entry> entry : dataObjectCache.entrySet()) { + String id = entry.getKey(); + List dataobject = entry.getValue(); + if (dataobject == null || dataobject.size() == 0) { + continue; + } + // test if the elementID is connected to this annotation.... + List resultList = findIncomingAssociations(elementID); + for (SequenceFlow flow : resultList) { + if (flow.source.equals(id)) { + result.add(dataobject); + } + } + } - for (SequenceFlow outgoingFlow : outFlows) { - ItemCollection targetTask = new ElementResolver().findImixsTargetTask(outgoingFlow); - if (targetTask != null) { - result.add(targetTask); + if (result.size() > 0) { + return result; + } else { + return null; } - } - } - logger.finest("......Imixs BPMN Event '" + eventID + "' is directly assigend to " - + result.size() + " task elements"); - return result; - } - - /** - * This method computes the target for an event and adds the event to a source task. The method - * call recursive if the target is a followUp Event. - * - * If a event has no target the method throws an exception - * - * If a event has more than one targets (task or event elements) then the event is handled as a - * loop event. - * - * If a event is already assigned to the sourceTask, the method returns without adding the event. - * - * @param sourceTask - * @param event - * @throws ModelException - */ - private void addImixsEvent(String eventID, ItemCollection sourceTask) throws ModelException { - - ItemCollection event = eventCache.get(eventID); - // test event for null - if (event == null) { - // invalid model (should not happen) - throw new ModelException(ModelException.INVALID_MODEL, - "Imixs BPMN Event '" + eventID + "' unknown!"); } - // clone event - event = new ItemCollection(event); - String eventName = event.getItemValueString("txtname"); - - // test sourceTask for null - if (sourceTask == null) { - // invalid model!! - throw new ModelException(ModelException.INVALID_MODEL, - "Imixs BPMN Event '" + eventName + "' has no source task!"); + + // check if this task is connected to a start event.... + private boolean isStartTask(String taskID) { + List inFlows = findIncomingFlows(taskID); + if (inFlows != null && inFlows.size() > 0) { + for (SequenceFlow aFlow : inFlows) { + String id = new ElementResolver().findStartEvent(aFlow, true); + if (id != null) { + return true; + } + } + } + return false; } - // if the event is already assigned to the sourceTask, then we can - // skip, because there is no need to duplicate an event! - try { - if (model.getEvent(sourceTask.getItemValueInteger("numProcessID"), - event.getItemValueInteger("numactivityid")) != null) { - logger.finest( - "......Imixs BPMN Event '" + eventName + "' is already assigned tosource task!"); - return; - } - } catch (ModelException me1) { - // ok we need to add the event.... + // check if this event is connected to a start event.... + private boolean isStartEvent(String eventID) { + List inFlows = findIncomingFlows(eventID); + if (inFlows != null && inFlows.size() > 0) { + for (SequenceFlow aFlow : inFlows) { + String id = new ElementResolver().findStartEvent(aFlow, false); + if (id != null) { + return true; + } + } + } + return false; } - logger.finest("......adding event '" + eventName + "'"); + // check if this event is a root event, connected directly to a task.... + private boolean isRootEvent(String eventID) { + List inFlows = findIncomingFlows(eventID); + if (inFlows != null) { - List outFlows = findOutgoingFlows(eventID); - if (outFlows == null || outFlows.size() == 0) { - // invalid model!! - throw new ModelException(ModelException.INVALID_MODEL, - "Imixs BPMN Event '" + eventName + "' has no target!"); - } + if (inFlows.size() == 0) { + // this is a loop event + return true; + } - // test if the element has multiple targets. In this case the event is - // handled as a loop event - List targetList = new ArrayList(); - for (SequenceFlow outgoingFlow : outFlows) { - targetList = new ElementResolver().findAllImixsTargetIDs(outgoingFlow, targetList); - } - if (targetList.size() > 1) { - // we have a multi event which need to be handled like a loop event - event.removeItem("keyFollowUp"); - event.replaceItemValue("numNextProcessID", sourceTask.getItemValue("numProcessID")); - - // test if this is a conditional event - search for conditional gateways... - List outgoingList = this.findOutgoingFlows(eventID); - if (outgoingList != null && outgoingList.size() > 0) { - Map conditions = new HashMap(); - for (SequenceFlow flow : outgoingList) { - // lookup for a exclusive gateway.... - String exclusiveGatewayID = new ElementResolver().findExclusiveGateway(flow); - if (exclusiveGatewayID != null) { - - String conditionalGatewayID = exclusiveGatewayID; // flow.target; - // get all outgoing flows from this gateway - List conditionalFlows = this.findOutgoingFlows(conditionalGatewayID); - for (SequenceFlow condFlow : conditionalFlows) { - ItemCollection targetTask = new ElementResolver().findImixsTargetTask(condFlow); - // build the condition - if (targetTask != null) { - String sExpression = findConditionBySquenceFlow(condFlow); - if (sExpression != null && !sExpression.trim().isEmpty()) { - logger.finest("......add condition: " - + targetTask.getItemValueInteger("numProcessid") + "=" + sExpression); - conditions.put("task=" + targetTask.getItemValueInteger("numProcessid"), - sExpression); - } - } else { - // test for an event.... - String targetEventID = new ElementResolver().findImixsTargetEventID(condFlow); - ItemCollection targetEvent = eventCache.get(targetEventID); - if (targetEvent != null) { - String sExpression = findConditionBySquenceFlow(condFlow); - if (sExpression != null && !sExpression.trim().isEmpty()) { - logger.finest("......add condition: " - + targetEvent.getItemValueInteger("numActivityid") + "=" + sExpression); - conditions.put("event=" + targetEvent.getItemValueInteger("numActivityid"), - sExpression); - } + for (SequenceFlow aFlow : inFlows) { + List sourceTaskList = new ArrayList(); + sourceTaskList = new ElementResolver().findAllImixsSourceTasks(aFlow, sourceTaskList); + if (sourceTaskList != null && sourceTaskList.size() > 0) { + return true; } + } + } + return false; + } - } + // check if this task is connected to an end event.... + private boolean isEndTask(String taskID) { + List outFlows = findOutgoingFlows(taskID); + if (outFlows != null && outFlows.size() > 0) { + for (SequenceFlow aFlow : outFlows) { + String id = new ElementResolver().findEndEvent(aFlow); + if (id != null) { + return true; + } } + } + return false; + } - } + /** + * This method returns all SourceTask Elements connected to a given eventID. The + * method takes care about loop events and follow up events. Later ones are + * handled by the method addImixsEvent(). For that reason, the result of this + * method can be also an empty list. + * + * An event can be a shared event so it is possible that more than one source + * tasks are found + * + * @param eventID + * @throws ModelException + */ + private List findSourceTasks(String eventID) throws ModelException { + List result = new ArrayList(); + boolean isFollowUp = false; + + // first we lookup all possible incoming flows to identify direct source + // tasks + List inFlows = findIncomingFlows(eventID); + + if (inFlows != null && inFlows.size() > 0) { + for (SequenceFlow aFlow : inFlows) { + List sourceTaskList = new ArrayList(); + sourceTaskList = new ElementResolver().findAllImixsSourceTasks(aFlow, sourceTaskList); + if (sourceTaskList.size() > 0) { + result.addAll(sourceTaskList); + } else { + // we found no source task. Test if the incoming flow is a + // event. Than we can ignore this flow! (follow up event) + ItemCollection sourceEvent = new ElementResolver().findImixsSourceEvent(aFlow); + if (sourceEvent != null) { + isFollowUp = true; + // ignore + continue; + } else { + // now as we found no task or event we check if we got a + // start event! + if (startEvents.contains(aFlow.source)) { + // all possible target Tasks are the source tasks + // for this event! + List outFlows = findOutgoingFlows(eventID); + List targetTaskList = new ArrayList(); + + for (SequenceFlow outgoingFlow : outFlows) { + targetTaskList = new ElementResolver().findAllImixsTargetTaskIDs(outgoingFlow, + targetTaskList); + } + // here we return all possible target tasks + for (String targetID : targetTaskList) { + result.add(taskCache.get(targetID)); + } + } + } + } + } - } - // add the attribute 'keyExclusiveConditions' if available... - if (!conditions.isEmpty()) { - event.replaceItemValue("keyExclusiveConditions", conditions); + // finish? + if (result.size() > 0) { + // we do not test for loop events + return result; + } } - } + // possible a loop event, if this is no followUp. + // so we test the target task... - // test if this is a split event - search for parallel gateway... - outgoingList = this.findOutgoingFlows(eventID); - if (outgoingList != null && outgoingList.size() > 0) { - Map conditions = new HashMap(); - for (SequenceFlow flow : outgoingList) { - if (parallelGatewayCache.contains(flow.target)) { + if (!isFollowUp) { - String parallelGatewayID = flow.target; - // get all outgoing flows from this gateway - List parallelFlows = this.findOutgoingFlows(parallelGatewayID); - for (SequenceFlow parallelFlow : parallelFlows) { - ItemCollection targetTask = new ElementResolver().findImixsTargetTask(parallelFlow); - // build the condition - if (targetTask != null) { - String sExpression = findConditionBySquenceFlow(parallelFlow); - if (sExpression != null && !sExpression.trim().isEmpty()) { - logger.finest("......add condition: " - + targetTask.getItemValueInteger("numProcessid") + "=" + sExpression); - conditions.put("task=" + targetTask.getItemValueInteger("numProcessid"), - sExpression); - } - } else { - // test for an event.... - String targetEventID = new ElementResolver().findImixsTargetEventID(parallelFlow); - ItemCollection targetEvent = eventCache.get(targetEventID); - if (targetEvent != null) { - String sExpression = findConditionBySquenceFlow(parallelFlow); - if (sExpression != null && !sExpression.trim().isEmpty()) { - logger.finest("......add condition: " - + targetEvent.getItemValueInteger("numActivityid") + "=" + sExpression); - conditions.put("event=" + targetEvent.getItemValueInteger("numActivityid"), - sExpression); - } - } - } + List outFlows = findOutgoingFlows(eventID); + if (outFlows != null && outFlows.size() != 1) { + // invalid model!! + throw new ModelException(ModelException.INVALID_MODEL, + "Imixs BPMN Event '" + eventID + "' has none or more than one targets!"); } - } + // test target element.... + // issue #211 - verify all outFlows! + // SequenceFlow outgoingFlow = outFlows.get(0); - } - // add the attribute 'keySplitConditions' if available... - if (!conditions.isEmpty()) { - event.replaceItemValue("keySplitConditions", conditions); + for (SequenceFlow outgoingFlow : outFlows) { + ItemCollection targetTask = new ElementResolver().findImixsTargetTask(outgoingFlow); + if (targetTask != null) { + result.add(targetTask); + } + } } - } + logger.finest( + "......Imixs BPMN Event '" + eventID + "' is directly assigend to " + result.size() + " task elements"); + return result; + } + + /** + * This method computes the target for an event and adds the event to a source + * task. The method call recursive if the target is a followUp Event. + * + * If a event has no target the method throws an exception + * + * If a event has more than one targets (task or event elements) then the event + * is handled as a loop event. + * + * If a event is already assigned to the sourceTask, the method returns without + * adding the event. + * + * @param sourceTask + * @param event + * @throws ModelException + */ + private void addImixsEvent(String eventID, ItemCollection sourceTask) throws ModelException { - // here we need to check if one of the targets is an event - this - // need to be handled in a recursive call - for (String elementID : targetList) { - // test if the target is a Imixs Event - ItemCollection imixsElement = eventCache.get(elementID); - if (imixsElement != null) { - // recursive call! - addImixsEvent(elementID, sourceTask); + ItemCollection event = eventCache.get(eventID); + // test event for null + if (event == null) { + // invalid model (should not happen) + throw new ModelException(ModelException.INVALID_MODEL, "Imixs BPMN Event '" + eventID + "' unknown!"); } - } + // clone event + event = new ItemCollection(event); + String eventName = event.getItemValueString("txtname"); - } else { - // normal case - the event has one outgoing target and we test the - // target element now + // test sourceTask for null + if (sourceTask == null) { + // invalid model!! + throw new ModelException(ModelException.INVALID_MODEL, + "Imixs BPMN Event '" + eventName + "' has no source task!"); + } - // test target element.... - // SequenceFlow outgoingFlow = outFlows.get(0); - String followUpEventID = null; - for (SequenceFlow outgoingFlow : outFlows) { - // is this Event connected to a followUp Activity? - followUpEventID = new ElementResolver().findImixsTargetEventID(outgoingFlow); - if (followUpEventID != null) { - break; + // if the event is already assigned to the sourceTask, then we can + // skip, because there is no need to duplicate an event! + try { + if (model.getEvent(sourceTask.getItemValueInteger("numProcessID"), + event.getItemValueInteger("numactivityid")) != null) { + logger.finest("......Imixs BPMN Event '" + eventName + "' is already assigned tosource task!"); + return; + } + } catch (ModelException me1) { + // ok we need to add the event.... } - } - if (followUpEventID != null) { - // recursive call! - addImixsEvent(followUpEventID, sourceTask); - ItemCollection followUpEvent = eventCache.get(followUpEventID); - event.replaceItemValue("keyFollowUp", "1"); - event.replaceItemValue("numNextActivityID", followUpEvent.getItemValue("numactivityid")); + logger.finest("......adding event '" + eventName + "'"); - } else { - // test if we found a target task. - ItemCollection targetTask = null; - for (SequenceFlow outgoingFlow : outFlows) { - // invalid model if more than one target tasks!! - if (targetTask != null) + List outFlows = findOutgoingFlows(eventID); + if (outFlows == null || outFlows.size() == 0) { + // invalid model!! throw new ModelException(ModelException.INVALID_MODEL, - "Imixs BPMN Event '" + eventName + "' has more than one target task element!"); - targetTask = new ElementResolver().findImixsTargetTask(outgoingFlow); - if (targetTask != null) { - event.removeItem("keyFollowUp"); - event.replaceItemValue("numNextProcessID", targetTask.getItemValue("numProcessID")); - break; - } + "Imixs BPMN Event '" + eventName + "' has no target!"); } - if (targetTask == null) { - // invalid model!! - no target task - throw new ModelException(ModelException.INVALID_MODEL, - "Imixs BPMN Event '" + eventName + "' has no target task element!"); + + // test if the element has multiple targets. In this case the event is + // handled as a loop event + List targetList = new ArrayList(); + for (SequenceFlow outgoingFlow : outFlows) { + targetList = new ElementResolver().findAllImixsTargetIDs(outgoingFlow, targetList); } - } - } - // source found - event.replaceItemValue("numProcessID", sourceTask.getItemValue("numProcessID")); - event.replaceItemValue("$modelVersion", sourceTask.getModelVersion()); - replaceMessageTags(event); - - // look for optional dataObjects... - List> dataObjectList = getDataObjectsForElement(eventID); - if (dataObjectList != null) { - // we take the annotation as the new documentation - event.replaceItemValue("dataObjects", dataObjectList); - } + if (targetList.size() > 1) { + // we have a multi event which need to be handled like a loop event + event.removeItem("keyFollowUp"); + event.replaceItemValue("numNextProcessID", sourceTask.getItemValue("numProcessID")); + + // test if this is a conditional event - search for conditional gateways... + List outgoingList = this.findOutgoingFlows(eventID); + if (outgoingList != null && outgoingList.size() > 0) { + Map conditions = new HashMap(); + for (SequenceFlow flow : outgoingList) { + // lookup for a exclusive gateway.... + String exclusiveGatewayID = new ElementResolver().findExclusiveGateway(flow); + if (exclusiveGatewayID != null) { + + String conditionalGatewayID = exclusiveGatewayID; // flow.target; + // get all outgoing flows from this gateway + List conditionalFlows = this.findOutgoingFlows(conditionalGatewayID); + for (SequenceFlow condFlow : conditionalFlows) { + ItemCollection targetTask = new ElementResolver().findImixsTargetTask(condFlow); + // build the condition + if (targetTask != null) { + String sExpression = findConditionBySquenceFlow(condFlow); + if (sExpression != null && !sExpression.trim().isEmpty()) { + logger.finest("......add condition: " + + targetTask.getItemValueInteger("numProcessid") + "=" + sExpression); + conditions.put("task=" + targetTask.getItemValueInteger("numProcessid"), + sExpression); + } + } else { + // test for an event.... + String targetEventID = new ElementResolver().findImixsTargetEventID(condFlow); + ItemCollection targetEvent = eventCache.get(targetEventID); + if (targetEvent != null) { + String sExpression = findConditionBySquenceFlow(condFlow); + if (sExpression != null && !sExpression.trim().isEmpty()) { + logger.finest("......add condition: " + + targetEvent.getItemValueInteger("numActivityid") + "=" + sExpression); + conditions.put("event=" + targetEvent.getItemValueInteger("numActivityid"), + sExpression); + } + } + + } + } + + } - if (isStartEvent(eventID)) { - event.setItemValue("startEvent", true); - } + } + // add the attribute 'keyExclusiveConditions' if available... + if (!conditions.isEmpty()) { + event.replaceItemValue("keyExclusiveConditions", conditions); + } - if (isRootEvent(eventID)) { - event.setItemValue("rootEvent", true); - } + } - if (event.getItemValueInteger("numprocessID") == event - .getItemValueInteger("numNextProcessID")) { - event.setItemValue("loopEvent", true); - } + // test if this is a split event - search for parallel gateway... + outgoingList = this.findOutgoingFlows(eventID); + if (outgoingList != null && outgoingList.size() > 0) { + Map conditions = new HashMap(); + for (SequenceFlow flow : outgoingList) { + if (parallelGatewayCache.contains(flow.target)) { + + String parallelGatewayID = flow.target; + // get all outgoing flows from this gateway + List parallelFlows = this.findOutgoingFlows(parallelGatewayID); + for (SequenceFlow parallelFlow : parallelFlows) { + ItemCollection targetTask = new ElementResolver().findImixsTargetTask(parallelFlow); + // build the condition + if (targetTask != null) { + String sExpression = findConditionBySquenceFlow(parallelFlow); + if (sExpression != null && !sExpression.trim().isEmpty()) { + logger.finest("......add condition: " + + targetTask.getItemValueInteger("numProcessid") + "=" + sExpression); + conditions.put("task=" + targetTask.getItemValueInteger("numProcessid"), + sExpression); + } + } else { + // test for an event.... + String targetEventID = new ElementResolver().findImixsTargetEventID(parallelFlow); + ItemCollection targetEvent = eventCache.get(targetEventID); + if (targetEvent != null) { + String sExpression = findConditionBySquenceFlow(parallelFlow); + if (sExpression != null && !sExpression.trim().isEmpty()) { + logger.finest("......add condition: " + + targetEvent.getItemValueInteger("numActivityid") + "=" + sExpression); + conditions.put("event=" + targetEvent.getItemValueInteger("numActivityid"), + sExpression); + } + } + } + } + + } - model.addEvent(verifyActiviytIdForEvent(event)); - } - - /** - * This helper method verifies if the activity of the event is still unique for the task element. - * If not the method computes a new one and updates the event - * - * @param event - * @param task - * @return - */ - private ItemCollection verifyActiviytIdForEvent(ItemCollection event) { - // ItemCollection event = activityCache.get(eventID); - int processid = event.getItemValueInteger("numprocessid"); - int activityid = event.getItemValueInteger("numactivityid"); - - List assignedActivities = model.findAllEventsByTask(processid); - int bestID = -1; - for (ItemCollection aactivity : assignedActivities) { - int aid = aactivity.getItemValueInteger("numactivityid"); - if (aid >= bestID) { - bestID = aid + 10; - } - if (aid == activityid) { - // problem! - String name = event.getItemValueString("txtname"); - logger.warning( - "ActivityID " + name + " ID=" + activityid + " is not unique for task " + processid); - activityid = -1; - } - } + } + // add the attribute 'keySplitConditions' if available... + if (!conditions.isEmpty()) { + event.replaceItemValue("keySplitConditions", conditions); + } - // suggest new activityid? - if (activityid <= 0) { - // replace id - logger.warning("new ActivityID suggested for task " + processid + "=" + bestID); - event.replaceItemValue("numactivityid", bestID); + } - // processCache.put(eventID, event); - } else { - // no changes needed! - } + // here we need to check if one of the targets is an event - this + // need to be handled in a recursive call + for (String elementID : targetList) { + // test if the target is a Imixs Event + ItemCollection imixsElement = eventCache.get(elementID); + if (imixsElement != null) { + // recursive call! + addImixsEvent(elementID, sourceTask); + } + } - return event; - } - - /** - * This method returns all incoming sequence flows for a given element ID - * - * @param elementID - * @return - */ - private List findIncomingFlows(String elementID) { - - List result = new ArrayList(); - for (String aFlowID : sequenceCache.keySet()) { - SequenceFlow aFlow = sequenceCache.get(aFlowID); - if (aFlow.target.equals(elementID)) { - result.add(aFlow); - } - } + } else { + // normal case - the event has one outgoing target and we test the + // target element now + + // test target element.... + // SequenceFlow outgoingFlow = outFlows.get(0); + String followUpEventID = null; + for (SequenceFlow outgoingFlow : outFlows) { + // is this Event connected to a followUp Activity? + followUpEventID = new ElementResolver().findImixsTargetEventID(outgoingFlow); + if (followUpEventID != null) { + break; + } + } - return result; - } - - /** - * This method returns all outgoing sequence flows for a given element ID - * - * @param elementID - * @return - */ - private List findOutgoingFlows(String elementID) { - List result = new ArrayList(); - for (String aFlowID : sequenceCache.keySet()) { - SequenceFlow aFlow = sequenceCache.get(aFlowID); - if (aFlow.source.equals(elementID)) { - result.add(aFlow); - } - } - return result; - } - - /** - * This method returns all incoming Associations flows for a given element ID - * - * @param elementID - * @return - */ - private List findIncomingAssociations(String elementID) { - - List result = new ArrayList(); - for (String aFlowID : associationCache.keySet()) { - SequenceFlow aFlow = associationCache.get(aFlowID); - if (aFlow.target.equals(elementID)) { - result.add(aFlow); - } - } + if (followUpEventID != null) { + // recursive call! + addImixsEvent(followUpEventID, sourceTask); + ItemCollection followUpEvent = eventCache.get(followUpEventID); + event.replaceItemValue("keyFollowUp", "1"); + event.replaceItemValue("numNextActivityID", followUpEvent.getItemValue("numactivityid")); + + } else { + // test if we found a target task. + ItemCollection targetTask = null; + for (SequenceFlow outgoingFlow : outFlows) { + // invalid model if more than one target tasks!! + if (targetTask != null) + throw new ModelException(ModelException.INVALID_MODEL, + "Imixs BPMN Event '" + eventName + "' has more than one target task element!"); + targetTask = new ElementResolver().findImixsTargetTask(outgoingFlow); + if (targetTask != null) { + event.removeItem("keyFollowUp"); + event.replaceItemValue("numNextProcessID", targetTask.getItemValue("numProcessID")); + break; + } + } + if (targetTask == null) { + // invalid model!! - no target task + throw new ModelException(ModelException.INVALID_MODEL, + "Imixs BPMN Event '" + eventName + "' has no target task element!"); + } + } + } + // source found + event.replaceItemValue("numProcessID", sourceTask.getItemValue("numProcessID")); + event.replaceItemValue("$modelVersion", sourceTask.getModelVersion()); + replaceMessageTags(event); + + // look for optional dataObjects... + List> dataObjectList = getDataObjectsForElement(eventID); + if (dataObjectList != null) { + // we take the annotation as the new documentation + event.replaceItemValue("dataObjects", dataObjectList); + } - return result; - } - - /** - * This method returns all outgoing Associations flows for a given element ID - * - * @param elementID - * @return - */ - @SuppressWarnings("unused") - private List findOutgoingAssociations(String elementID) { - List result = new ArrayList(); - for (String aFlowID : associationCache.keySet()) { - SequenceFlow aFlow = associationCache.get(aFlowID); - if (aFlow.source.equals(elementID)) { - result.add(aFlow); - } - } - return result; - } - - /** - * This method returns an optional condition for a given sequenceFlow object. The method iterates - * the conditionCache to lookup the condition - * - * @param flow - * @return the condition if available or null - */ - private String findConditionBySquenceFlow(SequenceFlow flow) { - if (conditionCache == null) { - return null; - } - // first we need do figure out the squenceFlowID for the flow object - String sequenceID = null; - for (Map.Entry entry : sequenceCache.entrySet()) { - String key = entry.getKey(); - SequenceFlow value = entry.getValue(); - if (value == flow) { - sequenceID = key; - break; - } - } - return conditionCache.get(sequenceID); - } - - /** - * This method parses an event for the text fragment ... and - * replaces the tag with the corresponding message if available - * - * @param itemcol - */ - private void replaceMessageTags(ItemCollection itemcol) { - - String[] fieldList = {"rtfmailbody", "txtmailsubject"}; - for (String field : fieldList) { - - String value = itemcol.getItemValueString(field); - int parsingPos = 0; - boolean bNewValue = false; - while (value.indexOf("", parsingPos) > -1) { - - int istart = value.indexOf("", parsingPos); - int iend = value.indexOf("", parsingPos); - if (istart > -1 && iend > -1 && iend > istart) { - String messageName = value.substring(istart + 15, iend); - String message = messageCache.get(messageName); - if (message != null) { - value = value.substring(0, istart) + message + value.substring(iend + 16); - bNewValue = true; - } - } - - parsingPos = parsingPos + 15; - } - - if (bNewValue) { - itemcol.replaceItemValue(field, value); - } + if (isStartEvent(eventID)) { + event.setItemValue("startEvent", true); + } - } + if (isRootEvent(eventID)) { + event.setItemValue("rootEvent", true); + } - } - - /** - * This is a helper method to adapt the old property names into the new. The method also works the - * other way around so that new imixs-workflow can handle old bpmn files too. - * - * @param currentEntity2 - */ - private void adaptDeprecatedTaskProperties(ItemCollection taskEntity) { - - adaptDeprecatedItem(taskEntity, BPMNModel.TASK_ITEM_NAME, "txtname"); - adaptDeprecatedItem(taskEntity, BPMNModel.TASK_ITEM_DOCUMENTATION, "rtfdescription"); - - adaptDeprecatedItem(taskEntity, BPMNModel.TASK_ITEM_WORKFLOW_SUMMARY, "txtworkflowsummary"); - adaptDeprecatedItem(taskEntity, BPMNModel.TASK_ITEM_WORKFLOW_ABSTRACT, "txtworkflowabstract"); - - adaptDeprecatedItem(taskEntity, BPMNModel.TASK_ITEM_APPLICATION_EDITOR, "txteditorid"); - adaptDeprecatedItem(taskEntity, BPMNModel.TASK_ITEM_APPLICATION_ICON, "txtimageurl"); - adaptDeprecatedItem(taskEntity, BPMNModel.TASK_ITEM_APPLICATION_TYPE, "txttype"); - - adaptDeprecatedItem(taskEntity, BPMNModel.TASK_ITEM_ACL_OWNER_LIST, "namownershipnames"); - adaptDeprecatedItem(taskEntity, BPMNModel.TASK_ITEM_ACL_OWNER_LIST_MAPPING, - "keyownershipfields"); - adaptDeprecatedItem(taskEntity, BPMNModel.TASK_ITEM_ACL_READACCESS_LIST, "namaddreadaccess"); - adaptDeprecatedItem(taskEntity, BPMNModel.TASK_ITEM_ACL_READACCESS_LIST_MAPPING, - "keyaddreadfields"); - adaptDeprecatedItem(taskEntity, BPMNModel.TASK_ITEM_ACL_WRITEACCESS_LIST, "namaddwriteaccess"); - adaptDeprecatedItem(taskEntity, BPMNModel.TASK_ITEM_ACL_WRITEACCESS_LIST_MAPPING, - "keyaddwritefields"); - adaptDeprecatedItem(taskEntity, BPMNModel.TASK_ITEM_ACL_UPDATE, "keyupdateacl"); - - } - - /** - * This is a helper method to adapt the old property names into the new. The method also works the - * other way around so that new imixs-workflow can handle old bpmn files too. - * - * @param currentEntity2 - */ - private void adaptDeprecatedEventProperties(ItemCollection eventEntity) { - - adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_NAME, "txtname"); - adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_DOCUMENTATION, "rtfdescription"); - - // migrate keypublicresult - if (!eventEntity.hasItem("keypublicresult")) { - if (!eventEntity.hasItem(BPMNModel.EVENT_ITEM_WORKFLOW_PUBLIC)) { - eventEntity.setItemValue(BPMNModel.EVENT_ITEM_WORKFLOW_PUBLIC, true); - } else { - if (!eventEntity.hasItem(BPMNModel.EVENT_ITEM_WORKFLOW_PUBLIC)) { - eventEntity.setItemValue(BPMNModel.EVENT_ITEM_WORKFLOW_PUBLIC, - !"0".equals(eventEntity.getItemValueString("keypublicresult"))); - } - } - } else { - if (!eventEntity.hasItem(BPMNModel.EVENT_ITEM_WORKFLOW_PUBLIC)) { - eventEntity.setItemValue(BPMNModel.EVENT_ITEM_WORKFLOW_PUBLIC, - !"0".equals(eventEntity.getItemValueString("keypublicresult"))); - } - } - adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_WORKFLOW_PUBLIC_ACTORS, - "keyrestrictedvisibility"); - - // acl - adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_ACL_OWNER_LIST, "namownershipnames"); - adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_ACL_OWNER_LIST_MAPPING, - "keyownershipfields"); - adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_ACL_READACCESS_LIST, "namaddreadaccess"); - adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_ACL_READACCESS_LIST_MAPPING, - "keyaddreadfields"); - adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_ACL_WRITEACCESS_LIST, - "namaddwriteaccess"); - adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_ACL_WRITEACCESS_LIST_MAPPING, - "keyaddwritefields"); - adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_ACL_UPDATE, "keyupdateacl"); - - // workflow - adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_WORKFLOW_RESULT, "txtactivityresult"); - - // history - adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_HISTORY_MESSAGE, "rtfresultlog"); - - // mail - adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_MAIL_SUBJECT, "txtmailsubject"); - adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_MAIL_BODY, "rtfmailbody"); - adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_MAIL_TO_LIST, "nammailreceiver"); - adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_MAIL_TO_LIST_MAPPING, - "keymailreceiverfields"); - adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_MAIL_CC_LIST, "nammailreceivercc"); - adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_MAIL_CC_LIST_MAPPING, - "keymailreceiverfieldscc"); - adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_MAIL_BCC_LIST, "nammailreceiverbcc"); - adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_MAIL_BCC_LIST_MAPPING, - "keymailreceiverfieldsbcc"); - - // rule - adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_RULE_ENGINE, "txtbusinessruleengine"); - adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_RULE_DEFINITION, "txtbusinessrule"); - - // report - adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_REPORT_NAME, "txtreportname"); - adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_REPORT_PATH, "txtreportfilepath"); - adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_REPORT_OPTIONS, "txtreportparams"); - adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_REPORT_TARGET, "txtreporttarget"); - - // version - adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_VERSION_MODE, "keyversion"); - adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_VERSION_EVENT, "numversionactivityid"); - - // timer - if (!eventEntity.hasItem(BPMNModel.EVENT_ITEM_TIMER_ACTIVE)) { - eventEntity.setItemValue(BPMNModel.EVENT_ITEM_TIMER_ACTIVE, - new Boolean("1".equals(eventEntity.getItemValueString("keyscheduledactivity")))); - } - if (!eventEntity.hasItem("keyscheduledactivity")) { - if (eventEntity.getItemValueBoolean(BPMNModel.EVENT_ITEM_TIMER_ACTIVE)) { - eventEntity.setItemValue("keyscheduledactivity", "1"); - } else { - eventEntity.setItemValue("keyscheduledactivity", "0"); - } - } - adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_TIMER_SELECTION, "txtscheduledview"); - adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_TIMER_DELAY, "numactivitydelay"); - adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_TIMER_DELAY_UNIT, "keyactivitydelayunit"); - adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_TIMER_DELAY_BASE, - "keyscheduledbaseobject"); - adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_TIMER_DELAY_BASE_PROPERTY, - "keytimecomparefield"); - - } - - /** - * Helper method to adopt a old name into a new one - * - * @param taskEntity - * @param newItemName - * @param oldItemName - */ - private void adaptDeprecatedItem(ItemCollection taskEntity, String newItemName, - String oldItemName) { - - // test if old name is provided with a value... - if (taskEntity.getItemValueString(newItemName).isEmpty() - && !taskEntity.getItemValueString(oldItemName).isEmpty()) { - taskEntity.replaceItemValue(newItemName, taskEntity.getItemValue(oldItemName)); - } + if (event.getItemValueInteger("numprocessID") == event.getItemValueInteger("numNextProcessID")) { + event.setItemValue("loopEvent", true); + } - // now we support backward compatibility and add the old name if missing - if (taskEntity.getItemValueString(oldItemName).isEmpty()) { - taskEntity.replaceItemValue(oldItemName, taskEntity.getItemValue(newItemName)); + model.addEvent(verifyActiviytIdForEvent(event)); } - } + /** + * This helper method verifies if the activity of the event is still unique for + * the task element. If not the method computes a new one and updates the event + * + * @param event + * @param task + * @return + */ + private ItemCollection verifyActiviytIdForEvent(ItemCollection event) { + // ItemCollection event = activityCache.get(eventID); + int processid = event.getItemValueInteger("numprocessid"); + int activityid = event.getItemValueInteger("numactivityid"); + + List assignedActivities = model.findAllEventsByTask(processid); + int bestID = -1; + for (ItemCollection aactivity : assignedActivities) { + int aid = aactivity.getItemValueInteger("numactivityid"); + if (aid >= bestID) { + bestID = aid + 10; + } + if (aid == activityid) { + // problem! + String name = event.getItemValueString("txtname"); + logger.warning("ActivityID " + name + " ID=" + activityid + " is not unique for task " + processid); + activityid = -1; + } + } - class SequenceFlow { - private String target = null; - private String source = null; + // suggest new activityid? + if (activityid <= 0) { + // replace id + logger.warning("new ActivityID suggested for task " + processid + "=" + bestID); + event.replaceItemValue("numactivityid", bestID); - public SequenceFlow(String source, String target) { - this.target = target; - this.source = source; - } + // processCache.put(eventID, event); + } else { + // no changes needed! + } - } - - /** - * This helper class provides methods to resolve the connected Imixs elements to a flow element. - * The constructor is used to initialize a loopDetection cache - * - * @author rsoika - * - */ - class ElementResolver { - private List loopFlowCache = null; - - public ElementResolver() { - // initalize loop dedection - loopFlowCache = new ArrayList(); + return event; } /** - * This method searches a Imixs Task Element connected to the given SequenceFlow element. If the - * Sequence Flow is not connected to a Imixs Task element the method returns null. - * + * This method returns all incoming sequence flows for a given element ID * - * @return the Imixs Task element or null if no Task Element was found. + * @param elementID * @return */ - public List findAllImixsSourceTasks(SequenceFlow flow, - List sourceList) { - - if (flow.source == null) { - return sourceList; - } - - // detect loops... - if (loopFlowCache.contains(flow.source)) { - // loop! - return sourceList; - } else { - loopFlowCache.add(flow.source); - } - - // test if the source is a Imixs task - ItemCollection imixstask = taskCache.get(flow.source); - if (imixstask != null) { - sourceList.add(imixstask); - return sourceList; - } - - // test if the source is a Imixs Event - than we are in a follow up - // event! - ItemCollection imixsevent = eventCache.get(flow.source); - if (imixsevent != null) { - // event is connected to a event - so we are in a follow up - // event! - return sourceList; - } - - // no Imixs task found so we are trying to look for the next - // incoming - // flow elements. - List refList = findIncomingFlows(flow.source); - for (SequenceFlow aflow : refList) { - sourceList = findAllImixsSourceTasks(aflow, sourceList); - } - return sourceList; + private List findIncomingFlows(String elementID) { + + List result = new ArrayList(); + for (String aFlowID : sequenceCache.keySet()) { + SequenceFlow aFlow = sequenceCache.get(aFlowID); + if (aFlow.target.equals(elementID)) { + result.add(aFlow); + } + } + + return result; } /** - * This method searches a Imixs Event Element connected to the given SequenceFlow element. If - * the Sequence Flow is not connected to a Imixs Event element the method returns null. - * + * This method returns all outgoing sequence flows for a given element ID * - * @return the Imixs event element or null if no event Element was found. + * @param elementID * @return */ - public ItemCollection findImixsSourceEvent(SequenceFlow flow) { - - if (flow.source == null) { - return null; - } - - // detect loops... - if (loopFlowCache.contains(flow.source)) { - // loop! - return null; - } else { - loopFlowCache.add(flow.source); - } - - // test if the source is a Imixs Event - than we are in a follow up - // event! - ItemCollection imixsevent = eventCache.get(flow.source); - if (imixsevent != null) { - return imixsevent; - } - - // test if the source is a Imixs task - ItemCollection imixstask = taskCache.get(flow.source); - if (imixstask != null) { - // event is connected to a task - so we are not in a follow up - // event! - return null; - } - - // no Imixs task found so we are trying to look for the next - // incoming - // flow elements. - List refList = findIncomingFlows(flow.source); - for (SequenceFlow aflow : refList) { - return (findImixsSourceEvent(aflow)); - } - return null; + private List findOutgoingFlows(String elementID) { + List result = new ArrayList(); + for (String aFlowID : sequenceCache.keySet()) { + SequenceFlow aFlow = sequenceCache.get(aFlowID); + if (aFlow.source.equals(elementID)) { + result.add(aFlow); + } + } + return result; } /** - * This method searches a BPMN2:Start Event Element connected to the given SequenceFlow element. - * If the Sequence Flow is not connected to a BPMN2:Start Event element the method returns null. + * This method returns all incoming Associations flows for a given element ID * - * @return the id of the start event or null if no event Element was found. + * @param elementID * @return */ - public String findStartEvent(SequenceFlow flow, boolean forTask) { - - if (flow.source == null) { - return null; - } - - // detect loops... - if (loopFlowCache.contains(flow.source)) { - // loop! - return null; - } else { - loopFlowCache.add(flow.source); - } - - // did we search a start task? - if (forTask) { - // yes, if the source is another task than beak! - ItemCollection imixstask = taskCache.get(flow.source); - if (imixstask != null) { - // flow is connected to a task - so this is not a start Task - return null; - } - } else { - // we search a start event - so tasks and events are breaking! - ItemCollection imixstask = taskCache.get(flow.source); - if (imixstask != null) { - // event is connected to a task - so this is not a start Task - return null; - } - // we check for a start event... - ItemCollection imixsevent = eventCache.get(flow.source); - if (imixsevent != null) { - // event is connected to a task - so this is not a start Task - return null; - } - } - - // test if the source is a Imixs Event - than we are in a follow up - // event! - int pos = startEvents.indexOf(flow.source); - if (pos > -1) { - return startEvents.get(pos); - } - - // no start event found so we are trying to look for the next - // incoming flow elements. - List refList = findIncomingFlows(flow.source); - for (SequenceFlow aflow : refList) { - return (findStartEvent(aflow, forTask)); - } - return null; + private List findIncomingAssociations(String elementID) { + + List result = new ArrayList(); + for (String aFlowID : associationCache.keySet()) { + SequenceFlow aFlow = associationCache.get(aFlowID); + if (aFlow.target.equals(elementID)) { + result.add(aFlow); + } + } + + return result; } /** - * This method searches a BPMN2:End Event Element connected to the given SequenceFlow element. - * If the Sequence Flow is not connected to a BPMN2:Event Event element the method returns null. + * This method returns all outgoing Associations flows for a given element ID * - * @return the id of the end event or null if no event Element was found. + * @param elementID * @return */ - public String findEndEvent(SequenceFlow flow) { - - if (flow.target == null) { - return null; - } - - // detect loops... - if (loopFlowCache.contains(flow.target)) { - // loop! - return null; - } else { - loopFlowCache.add(flow.target); - } - - // test if the source is a Imixs task - ItemCollection imixstask = taskCache.get(flow.target); - if (imixstask != null) { - // event is connected to a task - so this is not a end Task - return null; - } - - // test if the source is a Imixs Event - than we are in a follow up - // event! - int pos = endEvents.indexOf(flow.target); - if (pos > -1) { - return endEvents.get(pos); - } - - // no end task found so we are trying to look for the next - // incoming flow elements. - List refList = findIncomingFlows(flow.target); - for (SequenceFlow aflow : refList) { - return (findEndEvent(aflow)); - } - return null; + @SuppressWarnings("unused") + private List findOutgoingAssociations(String elementID) { + List result = new ArrayList(); + for (String aFlowID : associationCache.keySet()) { + SequenceFlow aFlow = associationCache.get(aFlowID); + if (aFlow.source.equals(elementID)) { + result.add(aFlow); + } + } + return result; } /** - * This method searches a Imixs Task Element targeted from the given SequenceFlow element. If - * the Sequence Flow is not connected to a Imixs Task element the method returns null. - * - * If the target is a Event (FollowUp Event) the method returns null. + * This method returns an optional condition for a given sequenceFlow object. + * The method iterates the conditionCache to lookup the condition * - * @return the Imixs Task element or null if no Task Element was found. + * @param flow + * @return the condition if available or null */ - public ItemCollection findImixsTargetTask(SequenceFlow flow) { - if (flow.source == null) { - return null; - } - // detect loops... - if (loopFlowCache.contains(flow.target)) { - // loop! - return null; - } else { - loopFlowCache.add(flow.target); - } - - // test if the target is a Imixs task - ItemCollection imixstask = taskCache.get(flow.target); - if (imixstask != null) { - return imixstask; - } - - // test if the target is a Imixs event - return null if yes! - ItemCollection imixsevent = eventCache.get(flow.target); - if (imixsevent != null) { - return null; - } - - // no Imixs task or event found so we are trying to look for the - // next outgoing flow elements. (issue #211) - List refList = findOutgoingFlows(flow.target); - for (SequenceFlow aflow : refList) { - // recursive call.... - ItemCollection aResult = findImixsTargetTask(aflow); - if (aResult != null) { - // we got the task! - return aResult; - } - } - return null; + private String findConditionBySquenceFlow(SequenceFlow flow) { + if (conditionCache == null) { + return null; + } + // first we need do figure out the squenceFlowID for the flow object + String sequenceID = null; + for (Map.Entry entry : sequenceCache.entrySet()) { + String key = entry.getKey(); + SequenceFlow value = entry.getValue(); + if (value == flow) { + sequenceID = key; + break; + } + } + return conditionCache.get(sequenceID); } /** - * This method searches a Conditional Gateway targeted from the given SequenceFlow element. If - * no conditional gateway was found the method returns null. - *

- * If a ImxisEvent or ImixsTask was found, the search must stop! + * This method parses an event for the text fragment + * ... and replaces the tag with the + * corresponding message if available * - * @return id of the conditional gateway if found. + * @param itemcol */ - public String findExclusiveGateway(SequenceFlow flow) { - if (flow.source == null) { - return null; - } - // detect loops... - if (loopFlowCache.contains(flow.target)) { - // loop! - return null; - } else { - loopFlowCache.add(flow.target); - } - - String id = flow.target; - for (String condID : conditionalGatewayCache) { - if (id.equals(condID)) { - return condID; - } - } - - // no Imixs task or event found so we are trying to look for the - // next outgoing flow elements. (issue #211) - List refList = findOutgoingFlows(flow.target); - for (SequenceFlow aflow : refList) { - - // Issue #590 - // a) test if the target is a Imixs Task - ItemCollection imixsElement = taskCache.get(aflow.target); - if (imixsElement != null) { - // stop here! - return null; - } - // b) test if the target is a Imixs Event - imixsElement = eventCache.get(aflow.target); - if (imixsElement != null) { - // stop here! - return null; - } - - // recursive call.... - String aResult = findExclusiveGateway(aflow); - if (aResult != null) { - // we got a gateway - return aResult; - } - } - return null; + private void replaceMessageTags(ItemCollection itemcol) { + + String[] fieldList = { "rtfmailbody", "txtmailsubject" }; + for (String field : fieldList) { + + String value = itemcol.getItemValueString(field); + int parsingPos = 0; + boolean bNewValue = false; + while (value.indexOf("", parsingPos) > -1) { + + int istart = value.indexOf("", parsingPos); + int iend = value.indexOf("", parsingPos); + if (istart > -1 && iend > -1 && iend > istart) { + String messageName = value.substring(istart + 15, iend); + String message = messageCache.get(messageName); + if (message != null) { + value = value.substring(0, istart) + message + value.substring(iend + 16); + bNewValue = true; + } + } + + parsingPos = parsingPos + 15; + } + + if (bNewValue) { + itemcol.replaceItemValue(field, value); + } + + } + } /** - * This method searches the id for a Imixs follow-Up activity. This is the case if the target is - * another Imixs Event element. The method returns the id of the followup event + * This is a helper method to adapt the old property names into the new. The + * method also works the other way around so that new imixs-workflow can handle + * old bpmn files too. * - * @return the ID of the Imixs Event element or null if no Event Element was found. - * @return + * @param currentEntity2 */ - public String findImixsTargetEventID(SequenceFlow flow) { - - if (flow.source == null) { - return null; - } - // detect loops... - if (loopFlowCache.contains(flow.target)) { - // loop! - return null; - } else { - loopFlowCache.add(flow.target); - } - - // test if the target is a Imixs task - ItemCollection imixsElement = taskCache.get(flow.target); - if (imixsElement != null) { - // stop here! - return null; - } - - // test if the target is a Imixs Event - imixsElement = eventCache.get(flow.target); - if (imixsElement != null) { - return flow.target; - } - - // no Imixs task or event found so we are trying to look for the - // next incoming flow elements. - List refList = findOutgoingFlows(flow.target); - for (SequenceFlow aflow : refList) { - return (findImixsTargetEventID(aflow)); - } - return null; + private void adaptDeprecatedTaskProperties(ItemCollection taskEntity) { + + adaptDeprecatedItem(taskEntity, BPMNModel.TASK_ITEM_NAME, "txtname"); + adaptDeprecatedItem(taskEntity, BPMNModel.TASK_ITEM_DOCUMENTATION, "rtfdescription"); + + adaptDeprecatedItem(taskEntity, BPMNModel.TASK_ITEM_WORKFLOW_SUMMARY, "txtworkflowsummary"); + adaptDeprecatedItem(taskEntity, BPMNModel.TASK_ITEM_WORKFLOW_ABSTRACT, "txtworkflowabstract"); + + adaptDeprecatedItem(taskEntity, BPMNModel.TASK_ITEM_APPLICATION_EDITOR, "txteditorid"); + adaptDeprecatedItem(taskEntity, BPMNModel.TASK_ITEM_APPLICATION_ICON, "txtimageurl"); + adaptDeprecatedItem(taskEntity, BPMNModel.TASK_ITEM_APPLICATION_TYPE, "txttype"); + + adaptDeprecatedItem(taskEntity, BPMNModel.TASK_ITEM_ACL_OWNER_LIST, "namownershipnames"); + adaptDeprecatedItem(taskEntity, BPMNModel.TASK_ITEM_ACL_OWNER_LIST_MAPPING, "keyownershipfields"); + adaptDeprecatedItem(taskEntity, BPMNModel.TASK_ITEM_ACL_READACCESS_LIST, "namaddreadaccess"); + adaptDeprecatedItem(taskEntity, BPMNModel.TASK_ITEM_ACL_READACCESS_LIST_MAPPING, "keyaddreadfields"); + adaptDeprecatedItem(taskEntity, BPMNModel.TASK_ITEM_ACL_WRITEACCESS_LIST, "namaddwriteaccess"); + adaptDeprecatedItem(taskEntity, BPMNModel.TASK_ITEM_ACL_WRITEACCESS_LIST_MAPPING, "keyaddwritefields"); + adaptDeprecatedItem(taskEntity, BPMNModel.TASK_ITEM_ACL_UPDATE, "keyupdateacl"); + } /** - * This method searches for all target events or task for a outgoing sequence flow. The method - * returns a List of possible target elemetns. - * + * This is a helper method to adapt the old property names into the new. The + * method also works the other way around so that new imixs-workflow can handle + * old bpmn files too. * - * @return the ID of the Imixs Event element or null if no Event Element was found. - * @return + * @param currentEntity2 */ - public List findAllImixsTargetIDs(SequenceFlow flow, List targetList) { - - if (targetList == null) { - targetList = new ArrayList(); - } - - if (flow.source == null) { - return targetList; - } - // detect loops... - if (loopFlowCache.contains(flow.target)) { - // loop! - return targetList; - } else { - loopFlowCache.add(flow.target); - } - - // test if the target is a Imixs task - ItemCollection imixsElement = taskCache.get(flow.target); - if (imixsElement != null) { - targetList.add(flow.target); - // stop here! - return targetList; - } - - // test if the target is a Imixs Event - imixsElement = eventCache.get(flow.target); - if (imixsElement != null) { - targetList.add(flow.target); - // stop here! - return targetList; - } - - // no Imixs task or event found so we are trying to look for the - // next outgoing flow elements. - List refList = findOutgoingFlows(flow.target); - for (SequenceFlow aflow : refList) { - targetList = findAllImixsTargetIDs(aflow, targetList); - } - return targetList; + private void adaptDeprecatedEventProperties(ItemCollection eventEntity) { + + adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_NAME, "txtname"); + adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_DOCUMENTATION, "rtfdescription"); + + // migrate keypublicresult + if (!eventEntity.hasItem("keypublicresult")) { + if (!eventEntity.hasItem(BPMNModel.EVENT_ITEM_WORKFLOW_PUBLIC)) { + eventEntity.setItemValue(BPMNModel.EVENT_ITEM_WORKFLOW_PUBLIC, true); + } else { + if (!eventEntity.hasItem(BPMNModel.EVENT_ITEM_WORKFLOW_PUBLIC)) { + eventEntity.setItemValue(BPMNModel.EVENT_ITEM_WORKFLOW_PUBLIC, + !"0".equals(eventEntity.getItemValueString("keypublicresult"))); + } + } + } else { + if (!eventEntity.hasItem(BPMNModel.EVENT_ITEM_WORKFLOW_PUBLIC)) { + eventEntity.setItemValue(BPMNModel.EVENT_ITEM_WORKFLOW_PUBLIC, + !"0".equals(eventEntity.getItemValueString("keypublicresult"))); + } + } + adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_WORKFLOW_PUBLIC_ACTORS, "keyrestrictedvisibility"); + + // acl + adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_ACL_OWNER_LIST, "namownershipnames"); + adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_ACL_OWNER_LIST_MAPPING, "keyownershipfields"); + adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_ACL_READACCESS_LIST, "namaddreadaccess"); + adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_ACL_READACCESS_LIST_MAPPING, "keyaddreadfields"); + adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_ACL_WRITEACCESS_LIST, "namaddwriteaccess"); + adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_ACL_WRITEACCESS_LIST_MAPPING, "keyaddwritefields"); + adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_ACL_UPDATE, "keyupdateacl"); + + // workflow + adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_WORKFLOW_RESULT, "txtactivityresult"); + + // history + adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_HISTORY_MESSAGE, "rtfresultlog"); + + // mail + adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_MAIL_SUBJECT, "txtmailsubject"); + adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_MAIL_BODY, "rtfmailbody"); + adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_MAIL_TO_LIST, "nammailreceiver"); + adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_MAIL_TO_LIST_MAPPING, "keymailreceiverfields"); + adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_MAIL_CC_LIST, "nammailreceivercc"); + adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_MAIL_CC_LIST_MAPPING, "keymailreceiverfieldscc"); + adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_MAIL_BCC_LIST, "nammailreceiverbcc"); + adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_MAIL_BCC_LIST_MAPPING, "keymailreceiverfieldsbcc"); + + // rule + adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_RULE_ENGINE, "txtbusinessruleengine"); + adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_RULE_DEFINITION, "txtbusinessrule"); + + // report + adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_REPORT_NAME, "txtreportname"); + adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_REPORT_PATH, "txtreportfilepath"); + adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_REPORT_OPTIONS, "txtreportparams"); + adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_REPORT_TARGET, "txtreporttarget"); + + // version + adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_VERSION_MODE, "keyversion"); + adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_VERSION_EVENT, "numversionactivityid"); + + // timer + if (!eventEntity.hasItem(BPMNModel.EVENT_ITEM_TIMER_ACTIVE)) { + eventEntity.setItemValue(BPMNModel.EVENT_ITEM_TIMER_ACTIVE, + new Boolean("1".equals(eventEntity.getItemValueString("keyscheduledactivity")))); + } + if (!eventEntity.hasItem("keyscheduledactivity")) { + if (eventEntity.getItemValueBoolean(BPMNModel.EVENT_ITEM_TIMER_ACTIVE)) { + eventEntity.setItemValue("keyscheduledactivity", "1"); + } else { + eventEntity.setItemValue("keyscheduledactivity", "0"); + } + } + adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_TIMER_SELECTION, "txtscheduledview"); + adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_TIMER_DELAY, "numactivitydelay"); + adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_TIMER_DELAY_UNIT, "keyactivitydelayunit"); + adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_TIMER_DELAY_BASE, "keyscheduledbaseobject"); + adaptDeprecatedItem(eventEntity, BPMNModel.EVENT_ITEM_TIMER_DELAY_BASE_PROPERTY, "keytimecomparefield"); + } /** - * This method searches for all target tasks for a outgoing sequence flow. The method returns a - * List of possible imixs task elements. + * Helper method to adopt a old name into a new one * - * - * @return the ID of the Imixs Event element or null if no Event Element was found. - * @return + * @param taskEntity + * @param newItemName + * @param oldItemName */ - public List findAllImixsTargetTaskIDs(SequenceFlow flow, List targetList) { - - if (targetList == null) { - targetList = new ArrayList(); - } - - if (flow.source == null) { - return targetList; - } - // detect loops... - if (loopFlowCache.contains(flow.target)) { - // loop! - return targetList; - } else { - loopFlowCache.add(flow.target); - } - - // test if the target is a Imixs task - ItemCollection imixsElement = taskCache.get(flow.target); - if (imixsElement != null) { - targetList.add(flow.target); - // stop here! - return targetList; - } - - // no Imixs task or event found so we are trying to look for the - // next outgoing flow elements. - List refList = findOutgoingFlows(flow.target); - for (SequenceFlow aflow : refList) { - targetList = findAllImixsTargetIDs(aflow, targetList); - } - return targetList; + private void adaptDeprecatedItem(ItemCollection taskEntity, String newItemName, String oldItemName) { + + // test if old name is provided with a value... + if (taskEntity.getItemValueString(newItemName).isEmpty() + && !taskEntity.getItemValueString(oldItemName).isEmpty()) { + taskEntity.replaceItemValue(newItemName, taskEntity.getItemValue(oldItemName)); + } + + // now we support backward compatibility and add the old name if missing + if (taskEntity.getItemValueString(oldItemName).isEmpty()) { + taskEntity.replaceItemValue(oldItemName, taskEntity.getItemValue(newItemName)); + } + } - } + class SequenceFlow { + private String target = null; + private String source = null; + + public SequenceFlow(String source, String target) { + this.target = target; + this.source = source; + } + + } + + /** + * This helper class provides methods to resolve the connected Imixs elements to + * a flow element. The constructor is used to initialize a loopDetection cache + * + * @author rsoika + * + */ + class ElementResolver { + private List loopFlowCache = null; + + public ElementResolver() { + // initalize loop dedection + loopFlowCache = new ArrayList(); + } + + /** + * This method searches a Imixs Task Element connected to the given SequenceFlow + * element. If the Sequence Flow is not connected to a Imixs Task element the + * method returns null. + * + * + * @return the Imixs Task element or null if no Task Element was found. + * @return + */ + public List findAllImixsSourceTasks(SequenceFlow flow, List sourceList) { + + if (flow.source == null) { + return sourceList; + } + + // detect loops... + if (loopFlowCache.contains(flow.source)) { + // loop! + return sourceList; + } else { + loopFlowCache.add(flow.source); + } + + // test if the source is a Imixs task + ItemCollection imixstask = taskCache.get(flow.source); + if (imixstask != null) { + sourceList.add(imixstask); + return sourceList; + } + + // test if the source is a Imixs Event - than we are in a follow up + // event! + ItemCollection imixsevent = eventCache.get(flow.source); + if (imixsevent != null) { + // event is connected to a event - so we are in a follow up + // event! + return sourceList; + } + + // no Imixs task found so we are trying to look for the next + // incoming + // flow elements. + List refList = findIncomingFlows(flow.source); + for (SequenceFlow aflow : refList) { + sourceList = findAllImixsSourceTasks(aflow, sourceList); + } + return sourceList; + } + + /** + * This method searches a Imixs Event Element connected to the given + * SequenceFlow element. If the Sequence Flow is not connected to a Imixs Event + * element the method returns null. + * + * + * @return the Imixs event element or null if no event Element was found. + * @return + */ + public ItemCollection findImixsSourceEvent(SequenceFlow flow) { + + if (flow.source == null) { + return null; + } + + // detect loops... + if (loopFlowCache.contains(flow.source)) { + // loop! + return null; + } else { + loopFlowCache.add(flow.source); + } + + // test if the source is a Imixs Event - than we are in a follow up + // event! + ItemCollection imixsevent = eventCache.get(flow.source); + if (imixsevent != null) { + return imixsevent; + } + + // test if the source is a Imixs task + ItemCollection imixstask = taskCache.get(flow.source); + if (imixstask != null) { + // event is connected to a task - so we are not in a follow up + // event! + return null; + } + + // no Imixs task found so we are trying to look for the next + // incoming + // flow elements. + List refList = findIncomingFlows(flow.source); + for (SequenceFlow aflow : refList) { + return (findImixsSourceEvent(aflow)); + } + return null; + } + + /** + * This method searches a BPMN2:Start Event Element connected to the given + * SequenceFlow element. If the Sequence Flow is not connected to a BPMN2:Start + * Event element the method returns null. + * + * @return the id of the start event or null if no event Element was found. + * @return + */ + public String findStartEvent(SequenceFlow flow, boolean forTask) { + + if (flow.source == null) { + return null; + } + + // detect loops... + if (loopFlowCache.contains(flow.source)) { + // loop! + return null; + } else { + loopFlowCache.add(flow.source); + } + + // did we search a start task? + if (forTask) { + // yes, if the source is another task than beak! + ItemCollection imixstask = taskCache.get(flow.source); + if (imixstask != null) { + // flow is connected to a task - so this is not a start Task + return null; + } + } else { + // we search a start event - so tasks and events are breaking! + ItemCollection imixstask = taskCache.get(flow.source); + if (imixstask != null) { + // event is connected to a task - so this is not a start Task + return null; + } + // we check for a start event... + ItemCollection imixsevent = eventCache.get(flow.source); + if (imixsevent != null) { + // event is connected to a task - so this is not a start Task + return null; + } + } + + // test if the source is a Imixs Event - than we are in a follow up + // event! + int pos = startEvents.indexOf(flow.source); + if (pos > -1) { + return startEvents.get(pos); + } + + // no start event found so we are trying to look for the next + // incoming flow elements. + List refList = findIncomingFlows(flow.source); + for (SequenceFlow aflow : refList) { + return (findStartEvent(aflow, forTask)); + } + return null; + } + + /** + * This method searches a BPMN2:End Event Element connected to the given + * SequenceFlow element. If the Sequence Flow is not connected to a BPMN2:Event + * Event element the method returns null. + * + * @return the id of the end event or null if no event Element was found. + * @return + */ + public String findEndEvent(SequenceFlow flow) { + + if (flow.target == null) { + return null; + } + + // detect loops... + if (loopFlowCache.contains(flow.target)) { + // loop! + return null; + } else { + loopFlowCache.add(flow.target); + } + + // test if the source is a Imixs task + ItemCollection imixstask = taskCache.get(flow.target); + if (imixstask != null) { + // event is connected to a task - so this is not a end Task + return null; + } + + // test if the source is a Imixs Event - than we are in a follow up + // event! + int pos = endEvents.indexOf(flow.target); + if (pos > -1) { + return endEvents.get(pos); + } + + // no end task found so we are trying to look for the next + // incoming flow elements. + List refList = findIncomingFlows(flow.target); + for (SequenceFlow aflow : refList) { + return (findEndEvent(aflow)); + } + return null; + } + + /** + * This method searches a Imixs Task Element targeted from the given + * SequenceFlow element. If the Sequence Flow is not connected to a Imixs Task + * element the method returns null. + * + * If the target is a Event (FollowUp Event) the method returns null. + * + * @return the Imixs Task element or null if no Task Element was found. + */ + public ItemCollection findImixsTargetTask(SequenceFlow flow) { + if (flow.source == null) { + return null; + } + // detect loops... + if (loopFlowCache.contains(flow.target)) { + // loop! + return null; + } else { + loopFlowCache.add(flow.target); + } + + // test if the target is a Imixs task + ItemCollection imixstask = taskCache.get(flow.target); + if (imixstask != null) { + return imixstask; + } + + // test if the target is a Imixs event - return null if yes! + ItemCollection imixsevent = eventCache.get(flow.target); + if (imixsevent != null) { + return null; + } + + // no Imixs task or event found so we are trying to look for the + // next outgoing flow elements. (issue #211) + List refList = findOutgoingFlows(flow.target); + for (SequenceFlow aflow : refList) { + // recursive call.... + ItemCollection aResult = findImixsTargetTask(aflow); + if (aResult != null) { + // we got the task! + return aResult; + } + } + return null; + } + + /** + * This method searches a Conditional Gateway targeted from the given + * SequenceFlow element. If no conditional gateway was found the method returns + * null. + *

+ * If a ImxisEvent or ImixsTask was found, the search must stop! + * + * @return id of the conditional gateway if found. + */ + public String findExclusiveGateway(SequenceFlow flow) { + if (flow.source == null) { + return null; + } + // detect loops... + if (loopFlowCache.contains(flow.target)) { + // loop! + return null; + } else { + loopFlowCache.add(flow.target); + } + + String id = flow.target; + for (String condID : conditionalGatewayCache) { + if (id.equals(condID)) { + return condID; + } + } + + // no Imixs task or event found so we are trying to look for the + // next outgoing flow elements. (issue #211) + List refList = findOutgoingFlows(flow.target); + for (SequenceFlow aflow : refList) { + + // Issue #590 + // a) test if the target is a Imixs Task + ItemCollection imixsElement = taskCache.get(aflow.target); + if (imixsElement != null) { + // stop here! + return null; + } + // b) test if the target is a Imixs Event + imixsElement = eventCache.get(aflow.target); + if (imixsElement != null) { + // stop here! + return null; + } + + // recursive call.... + String aResult = findExclusiveGateway(aflow); + if (aResult != null) { + // we got a gateway + return aResult; + } + } + return null; + } + + /** + * This method searches the id for a Imixs follow-Up activity. This is the case + * if the target is another Imixs Event element. The method returns the id of + * the followup event + * + * @return the ID of the Imixs Event element or null if no Event Element was + * found. + * @return + */ + public String findImixsTargetEventID(SequenceFlow flow) { + + if (flow.source == null) { + return null; + } + // detect loops... + if (loopFlowCache.contains(flow.target)) { + // loop! + return null; + } else { + loopFlowCache.add(flow.target); + } + + // test if the target is a Imixs task + ItemCollection imixsElement = taskCache.get(flow.target); + if (imixsElement != null) { + // stop here! + return null; + } + + // test if the target is a Imixs Event + imixsElement = eventCache.get(flow.target); + if (imixsElement != null) { + return flow.target; + } + + // no Imixs task or event found so we are trying to look for the + // next incoming flow elements. + List refList = findOutgoingFlows(flow.target); + for (SequenceFlow aflow : refList) { + return (findImixsTargetEventID(aflow)); + } + return null; + } + + /** + * This method searches for all target events or task for a outgoing sequence + * flow. The method returns a List of possible target elemetns. + * + * + * @return the ID of the Imixs Event element or null if no Event Element was + * found. + * @return + */ + public List findAllImixsTargetIDs(SequenceFlow flow, List targetList) { + + if (targetList == null) { + targetList = new ArrayList(); + } + + if (flow.source == null) { + return targetList; + } + // detect loops... + if (loopFlowCache.contains(flow.target)) { + // loop! + return targetList; + } else { + loopFlowCache.add(flow.target); + } + + // test if the target is a Imixs task + ItemCollection imixsElement = taskCache.get(flow.target); + if (imixsElement != null) { + targetList.add(flow.target); + // stop here! + return targetList; + } + + // test if the target is a Imixs Event + imixsElement = eventCache.get(flow.target); + if (imixsElement != null) { + targetList.add(flow.target); + // stop here! + return targetList; + } + + // no Imixs task or event found so we are trying to look for the + // next outgoing flow elements. + List refList = findOutgoingFlows(flow.target); + for (SequenceFlow aflow : refList) { + targetList = findAllImixsTargetIDs(aflow, targetList); + } + return targetList; + } + + /** + * This method searches for all target tasks for a outgoing sequence flow. The + * method returns a List of possible imixs task elements. + * + * + * @return the ID of the Imixs Event element or null if no Event Element was + * found. + * @return + */ + public List findAllImixsTargetTaskIDs(SequenceFlow flow, List targetList) { + + if (targetList == null) { + targetList = new ArrayList(); + } + + if (flow.source == null) { + return targetList; + } + // detect loops... + if (loopFlowCache.contains(flow.target)) { + // loop! + return targetList; + } else { + loopFlowCache.add(flow.target); + } + + // test if the target is a Imixs task + ItemCollection imixsElement = taskCache.get(flow.target); + if (imixsElement != null) { + targetList.add(flow.target); + // stop here! + return targetList; + } + + // no Imixs task or event found so we are trying to look for the + // next outgoing flow elements. + List refList = findOutgoingFlows(flow.target); + for (SequenceFlow aflow : refList) { + targetList = findAllImixsTargetIDs(aflow, targetList); + } + return targetList; + } + + } } diff --git a/imixs-workflow-core/src/main/java/org/imixs/workflow/bpmn/BPMNParser.java b/imixs-workflow-core/src/main/java/org/imixs/workflow/bpmn/BPMNParser.java index f99317828..b9807c392 100644 --- a/imixs-workflow-core/src/main/java/org/imixs/workflow/bpmn/BPMNParser.java +++ b/imixs-workflow-core/src/main/java/org/imixs/workflow/bpmn/BPMNParser.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *

- *  Imixs Workflow 
+/*  
+ *  Imixs-Workflow 
+ *  
  *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
  *  http://www.imixs.com
  *  
@@ -22,10 +22,9 @@
  *      https://github.com/imixs/imixs-workflow
  *  
  *  Contributors:  
- *      Imixs Software Solutions GmbH - initial API and implementation
+ *      Imixs Software Solutions GmbH - Project Management
  *      Ralph Soika - Software Developer
- * 
- *******************************************************************************/ + */ package org.imixs.workflow.bpmn; @@ -42,7 +41,8 @@ import org.xml.sax.SAXException; /** - * This class parses an BPMN model and transform the content into a Imixs Workflow Model definition. + * This class parses an BPMN model and transform the content into a Imixs + * Workflow Model definition. * * * @@ -56,81 +56,80 @@ */ public class BPMNParser { - private static Logger logger = Logger.getLogger(BPMNParser.class.getName()); + private static Logger logger = Logger.getLogger(BPMNParser.class.getName()); - /** - * This method parses a BPMN model from a input stream and returns a instance of BPMNModel class. - * The InputStream is converted into a byte array to be stored into the BPMNModel as rawData. The - * rawData can be used to persist the input stream. - * - * - * @param bpmnInputStream - * @param encoding - default encoding use to parse the stream - * @return List a model definition - * @throws ParseException - * @throws SAXException - * @throws ParserConfigurationException - * @throws IOException - * @throws ModelException - */ - public final static BPMNModel parseModel(InputStream bpmnInputStream, String encoding) - throws ParseException, ParserConfigurationException, SAXException, IOException, - ModelException { + /** + * This method parses a BPMN model from a input stream and returns a instance of + * BPMNModel class. The InputStream is converted into a byte array to be stored + * into the BPMNModel as rawData. The rawData can be used to persist the input + * stream. + * + * + * @param bpmnInputStream + * @param encoding - default encoding use to parse the stream + * @return List a model definition + * @throws ParseException + * @throws SAXException + * @throws ParserConfigurationException + * @throws IOException + * @throws ModelException + */ + public final static BPMNModel parseModel(InputStream bpmnInputStream, String encoding) + throws ParseException, ParserConfigurationException, SAXException, IOException, ModelException { - long lTime = System.currentTimeMillis(); - if (bpmnInputStream == null) { - logger.severe("[BPMNParser] parseModel - inputStream is null!"); - throw new ParseException("inputStream is null", -1); - } + long lTime = System.currentTimeMillis(); + if (bpmnInputStream == null) { + logger.severe("[BPMNParser] parseModel - inputStream is null!"); + throw new ParseException("inputStream is null", -1); + } - // copy stream into byte array to store content later in BMPMModel object - byte[] rawData = streamToByteArray(bpmnInputStream); - // Parse XML.... - SAXParserFactory factory = SAXParserFactory.newInstance(); - SAXParser saxParser = factory.newSAXParser(); - BPMNModelHandler bpmnHandler = new BPMNModelHandler(); - saxParser.parse(new ByteArrayInputStream(rawData), bpmnHandler); - // build the model - BPMNModel model = bpmnHandler.buildModel(); + // copy stream into byte array to store content later in BMPMModel object + byte[] rawData = streamToByteArray(bpmnInputStream); + // Parse XML.... + SAXParserFactory factory = SAXParserFactory.newInstance(); + SAXParser saxParser = factory.newSAXParser(); + BPMNModelHandler bpmnHandler = new BPMNModelHandler(); + saxParser.parse(new ByteArrayInputStream(rawData), bpmnHandler); + // build the model + BPMNModel model = bpmnHandler.buildModel(); - // store file content from input stream into the BPMNmodel - model.setRawData(rawData); + // store file content from input stream into the BPMNmodel + model.setRawData(rawData); - logger.fine("...BPMN Model '" + model.getVersion() + "' parsed in " - + (System.currentTimeMillis() - lTime) + "ms"); - return model; + logger.fine( + "...BPMN Model '" + model.getVersion() + "' parsed in " + (System.currentTimeMillis() - lTime) + "ms"); + return model; - } + } - private static byte[] streamToByteArray(InputStream ins) throws IOException { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - byte[] byteBuffer = new byte[1024]; - int len; - while ((len = ins.read(byteBuffer)) > -1) { - baos.write(byteBuffer, 0, len); + private static byte[] streamToByteArray(InputStream ins) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + byte[] byteBuffer = new byte[1024]; + int len; + while ((len = ins.read(byteBuffer)) > -1) { + baos.write(byteBuffer, 0, len); + } + baos.flush(); + return baos.toByteArray(); } - baos.flush(); - return baos.toByteArray(); - } - /** - * This method parses a BPMN model from a byte array. - * - * @param requestBodyStream - * @param encoding - default encoding use to parse the stream - * @return List a model definition - * @throws ParseException - * @throws SAXException - * @throws ParserConfigurationException - * @throws IOException - * @throws ModelException - */ - public final static BPMNModel parseModel(byte[] bpmnByteArray, String encoding) - throws ParseException, ParserConfigurationException, SAXException, IOException, - ModelException { + /** + * This method parses a BPMN model from a byte array. + * + * @param requestBodyStream + * @param encoding - default encoding use to parse the stream + * @return List a model definition + * @throws ParseException + * @throws SAXException + * @throws ParserConfigurationException + * @throws IOException + * @throws ModelException + */ + public final static BPMNModel parseModel(byte[] bpmnByteArray, String encoding) + throws ParseException, ParserConfigurationException, SAXException, IOException, ModelException { - ByteArrayInputStream input = new ByteArrayInputStream(bpmnByteArray); - return parseModel(input, encoding); + ByteArrayInputStream input = new ByteArrayInputStream(bpmnByteArray); + return parseModel(input, encoding); - } + } } diff --git a/imixs-workflow-core/src/main/java/org/imixs/workflow/exceptions/AccessDeniedException.java b/imixs-workflow-core/src/main/java/org/imixs/workflow/exceptions/AccessDeniedException.java index 99889c129..4460b30aa 100644 --- a/imixs-workflow-core/src/main/java/org/imixs/workflow/exceptions/AccessDeniedException.java +++ b/imixs-workflow-core/src/main/java/org/imixs/workflow/exceptions/AccessDeniedException.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *
- *  Imixs Workflow 
+/*  
+ *  Imixs-Workflow 
+ *  
  *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
  *  http://www.imixs.com
  *  
@@ -22,26 +22,25 @@
  *      https://github.com/imixs/imixs-workflow
  *  
  *  Contributors:  
- *      Imixs Software Solutions GmbH - initial API and implementation
+ *      Imixs Software Solutions GmbH - Project Management
  *      Ralph Soika - Software Developer
- * 
- *******************************************************************************/ + */ package org.imixs.workflow.exceptions; /** - * An AccessDeniedException should be thrown by a Imixs Workflow component if the callerPrincipal is - * not allowed to access an instance of a workitem. + * An AccessDeniedException should be thrown by a Imixs Workflow component if + * the callerPrincipal is not allowed to access an instance of a workitem. * * @author rsoika * */ public class AccessDeniedException extends InvalidAccessException { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 1L; - public AccessDeniedException(String aErrorCode, String message) { - super(aErrorCode, message); - } + public AccessDeniedException(String aErrorCode, String message) { + super(aErrorCode, message); + } } diff --git a/imixs-workflow-core/src/main/java/org/imixs/workflow/exceptions/AdapterException.java b/imixs-workflow-core/src/main/java/org/imixs/workflow/exceptions/AdapterException.java index bf2b996cf..79781a329 100644 --- a/imixs-workflow-core/src/main/java/org/imixs/workflow/exceptions/AdapterException.java +++ b/imixs-workflow-core/src/main/java/org/imixs/workflow/exceptions/AdapterException.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *
- *  Imixs Workflow 
+/*  
+ *  Imixs-Workflow 
+ *  
  *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
  *  http://www.imixs.com
  *  
@@ -22,10 +22,9 @@
  *      https://github.com/imixs/imixs-workflow
  *  
  *  Contributors:  
- *      Imixs Software Solutions GmbH - initial API and implementation
+ *      Imixs Software Solutions GmbH - Project Management
  *      Ralph Soika - Software Developer
- * 
- *******************************************************************************/ + */ package org.imixs.workflow.exceptions; /** @@ -36,31 +35,28 @@ */ public class AdapterException extends WorkflowException { - private static final long serialVersionUID = 1L; - private Object[] params = null; - - - public AdapterException(String aErrorContext, String aErrorCode, String message) { - super(aErrorContext, aErrorCode, message); - } + private static final long serialVersionUID = 1L; + private Object[] params = null; - public AdapterException(String aErrorContext, String aErrorCode, String message, Exception e) { - super(aErrorContext, aErrorCode, message, e); - } + public AdapterException(String aErrorContext, String aErrorCode, String message) { + super(aErrorContext, aErrorCode, message); + } + public AdapterException(String aErrorContext, String aErrorCode, String message, Exception e) { + super(aErrorContext, aErrorCode, message, e); + } - public AdapterException(String aErrorContext, String aErrorCode, String message, - Object[] params) { - super(aErrorContext, aErrorCode, message); - this.params = params; - } + public AdapterException(String aErrorContext, String aErrorCode, String message, Object[] params) { + super(aErrorContext, aErrorCode, message); + this.params = params; + } - public Object[] getErrorParameters() { - return params; - } + public Object[] getErrorParameters() { + return params; + } - protected void setErrorParameters(Object[] aparams) { - this.params = aparams; - } + protected void setErrorParameters(Object[] aparams) { + this.params = aparams; + } } diff --git a/imixs-workflow-core/src/main/java/org/imixs/workflow/exceptions/ImixsExceptionHandler.java b/imixs-workflow-core/src/main/java/org/imixs/workflow/exceptions/ImixsExceptionHandler.java index ca60ece68..eefa5078b 100644 --- a/imixs-workflow-core/src/main/java/org/imixs/workflow/exceptions/ImixsExceptionHandler.java +++ b/imixs-workflow-core/src/main/java/org/imixs/workflow/exceptions/ImixsExceptionHandler.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *
- *  Imixs Workflow 
+/*  
+ *  Imixs-Workflow 
+ *  
  *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
  *  http://www.imixs.com
  *  
@@ -22,10 +22,9 @@
  *      https://github.com/imixs/imixs-workflow
  *  
  *  Contributors:  
- *      Imixs Software Solutions GmbH - initial API and implementation
+ *      Imixs Software Solutions GmbH - Project Management
  *      Ralph Soika - Software Developer
- * 
- *******************************************************************************/ + */ package org.imixs.workflow.exceptions; @@ -36,13 +35,14 @@ import org.imixs.workflow.exceptions.WorkflowException; /** - * The ExceptionHandler provides a method to add a error message to the given workItem, based on the - * data in a WorkflowException or InvalidAccessException. This kind of error message can be - * displayed in a page evaluating the properties '$error_code' and '$error_message'. These - * attributes will not be stored. + * The ExceptionHandler provides a method to add a error message to the given + * workItem, based on the data in a WorkflowException or InvalidAccessException. + * This kind of error message can be displayed in a page evaluating the + * properties '$error_code' and '$error_message'. These attributes will not be + * stored. *

- * If a PluginException or ValidationException contains an optional object array the message is - * parsed for params to be replaced + * If a PluginException or ValidationException contains an optional object array + * the message is parsed for params to be replaced *

* Example: * @@ -55,60 +55,60 @@ */ public class ImixsExceptionHandler { - /** - * This method adds a error message to the given workItem, based on the data in a - * WorkflowException or InvalidAccessException. - * - * @param pe - */ - public static ItemCollection addErrorMessage(Exception pe, ItemCollection aworkitem) { + /** + * This method adds a error message to the given workItem, based on the data in + * a WorkflowException or InvalidAccessException. + * + * @param pe + */ + public static ItemCollection addErrorMessage(Exception pe, ItemCollection aworkitem) { - Throwable rootCause = findCauseUsingPlainJava(pe); + Throwable rootCause = findCauseUsingPlainJava(pe); - if (pe instanceof WorkflowException) { - // String message = ((WorkflowException) pe).getErrorCode(); - String message = pe.getMessage(); + if (pe instanceof WorkflowException) { + // String message = ((WorkflowException) pe).getErrorCode(); + String message = pe.getMessage(); - // parse message for params - if (pe instanceof PluginException) { - PluginException p = (PluginException) pe; - if (p.getErrorParameters() != null && p.getErrorParameters().length > 0) { - for (int i = 0; i < p.getErrorParameters().length; i++) { - message = message.replace("{" + i + "}", p.getErrorParameters()[i].toString()); - } + // parse message for params + if (pe instanceof PluginException) { + PluginException p = (PluginException) pe; + if (p.getErrorParameters() != null && p.getErrorParameters().length > 0) { + for (int i = 0; i < p.getErrorParameters().length; i++) { + message = message.replace("{" + i + "}", p.getErrorParameters()[i].toString()); + } + } + } + aworkitem.replaceItemValue("$error_code", ((WorkflowException) pe).getErrorCode()); + aworkitem.replaceItemValue("$error_message", message); + } else if (rootCause instanceof InvalidAccessException) { + aworkitem.replaceItemValue("$error_code", ((InvalidAccessException) rootCause).getErrorCode()); + aworkitem.replaceItemValue("$error_message", rootCause.getMessage()); + } else { + aworkitem.replaceItemValue("$error_code", "INTERNAL ERROR"); + aworkitem.replaceItemValue("$error_message", pe.getMessage()); } - } - aworkitem.replaceItemValue("$error_code", ((WorkflowException) pe).getErrorCode()); - aworkitem.replaceItemValue("$error_message", message); - } else if (rootCause instanceof InvalidAccessException) { - aworkitem.replaceItemValue("$error_code", - ((InvalidAccessException) rootCause).getErrorCode()); - aworkitem.replaceItemValue("$error_message", rootCause.getMessage()); - } else { - aworkitem.replaceItemValue("$error_code", "INTERNAL ERROR"); - aworkitem.replaceItemValue("$error_message", pe.getMessage()); - } - return aworkitem; - } + return aworkitem; + } - /** - * Find the Root Cause Using Plain Java - *

- * We'll loop through all the causes until it reaches the root. Notice that we've added an extra - * condition in our loop to avoid infinite loops when handling recursive causes. - *

- * - * @see https://www.baeldung.com/java-exception-root-cause - * @param throwable - * @return - */ - public static Throwable findCauseUsingPlainJava(Throwable throwable) { - Objects.requireNonNull(throwable); - Throwable rootCause = throwable; - while (rootCause.getCause() != null && rootCause.getCause() != rootCause) { - rootCause = rootCause.getCause(); + /** + * Find the Root Cause Using Plain Java + *

+ * We'll loop through all the causes until it reaches the root. Notice that + * we've added an extra condition in our loop to avoid infinite loops when + * handling recursive causes. + *

+ * + * @see https://www.baeldung.com/java-exception-root-cause + * @param throwable + * @return + */ + public static Throwable findCauseUsingPlainJava(Throwable throwable) { + Objects.requireNonNull(throwable); + Throwable rootCause = throwable; + while (rootCause.getCause() != null && rootCause.getCause() != rootCause) { + rootCause = rootCause.getCause(); + } + return rootCause; } - return rootCause; - } } diff --git a/imixs-workflow-core/src/main/java/org/imixs/workflow/exceptions/IndexException.java b/imixs-workflow-core/src/main/java/org/imixs/workflow/exceptions/IndexException.java index a4b7d10e3..18ae66245 100644 --- a/imixs-workflow-core/src/main/java/org/imixs/workflow/exceptions/IndexException.java +++ b/imixs-workflow-core/src/main/java/org/imixs/workflow/exceptions/IndexException.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *

- *  Imixs Workflow 
+/*  
+ *  Imixs-Workflow 
+ *  
  *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
  *  http://www.imixs.com
  *  
@@ -22,16 +22,15 @@
  *      https://github.com/imixs/imixs-workflow
  *  
  *  Contributors:  
- *      Imixs Software Solutions GmbH - initial API and implementation
+ *      Imixs Software Solutions GmbH - Project Management
  *      Ralph Soika - Software Developer
- * 
- *******************************************************************************/ + */ package org.imixs.workflow.exceptions; /** - * An IndexException is a runtime exception which is thrown by a Imixs Workflow component if a index - * is not read or writable. . + * An IndexException is a runtime exception which is thrown by a Imixs Workflow + * component if a index is not read or writable. . * * @see org.imixs.workflow.engine.lucene.LuceneUpdateService * @author rsoika @@ -39,58 +38,58 @@ */ public class IndexException extends RuntimeException { - public static final String INVALID_INDEX = "INVALID_INDEX"; + public static final String INVALID_INDEX = "INVALID_INDEX"; - protected String errorCode = "UNDEFINED"; - protected String errorContext = "UNDEFINED"; + protected String errorCode = "UNDEFINED"; + protected String errorContext = "UNDEFINED"; - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 1L; - public IndexException(String message) { - super(message); - } + public IndexException(String message) { + super(message); + } - public IndexException(String message, Exception e) { - super(message, e); - } + public IndexException(String message, Exception e) { + super(message, e); + } - public IndexException(String aErrorCode, String message) { - super(message); - errorCode = aErrorCode; - } + public IndexException(String aErrorCode, String message) { + super(message); + errorCode = aErrorCode; + } - public IndexException(String aErrorCode, String message, Exception e) { - super(message, e); - errorCode = aErrorCode; - } + public IndexException(String aErrorCode, String message, Exception e) { + super(message, e); + errorCode = aErrorCode; + } - public IndexException(String aErrorContext, String aErrorCode, String message) { - super(message); - errorContext = aErrorContext; - errorCode = aErrorCode; + public IndexException(String aErrorContext, String aErrorCode, String message) { + super(message); + errorContext = aErrorContext; + errorCode = aErrorCode; - } + } - public IndexException(String aErrorContext, String aErrorCode, String message, Exception e) { - super(message, e); - errorContext = aErrorContext; - errorCode = aErrorCode; + public IndexException(String aErrorContext, String aErrorCode, String message, Exception e) { + super(message, e); + errorContext = aErrorContext; + errorCode = aErrorCode; - } + } - public String getErrorContext() { - return errorContext; - } + public String getErrorContext() { + return errorContext; + } - public void setErrorContext(String errorContext) { - this.errorContext = errorContext; - } + public void setErrorContext(String errorContext) { + this.errorContext = errorContext; + } - public String getErrorCode() { - return errorCode; - } + public String getErrorCode() { + return errorCode; + } - public void setErrorCode(String errorCode) { - this.errorCode = errorCode; - } + public void setErrorCode(String errorCode) { + this.errorCode = errorCode; + } } diff --git a/imixs-workflow-core/src/main/java/org/imixs/workflow/exceptions/InvalidAccessException.java b/imixs-workflow-core/src/main/java/org/imixs/workflow/exceptions/InvalidAccessException.java index 3f366d436..835803c82 100644 --- a/imixs-workflow-core/src/main/java/org/imixs/workflow/exceptions/InvalidAccessException.java +++ b/imixs-workflow-core/src/main/java/org/imixs/workflow/exceptions/InvalidAccessException.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *
- *  Imixs Workflow 
+/*  
+ *  Imixs-Workflow 
+ *  
  *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
  *  http://www.imixs.com
  *  
@@ -22,80 +22,79 @@
  *      https://github.com/imixs/imixs-workflow
  *  
  *  Contributors:  
- *      Imixs Software Solutions GmbH - initial API and implementation
+ *      Imixs Software Solutions GmbH - Project Management
  *      Ralph Soika - Software Developer
- * 
- *******************************************************************************/ + */ package org.imixs.workflow.exceptions; /** - * An InvalidAccessException is a runtime exception which should be thrown by a Imixs Workflow - * component if a method call is invalid or the data structure is in an invalid state. + * An InvalidAccessException is a runtime exception which should be thrown by a + * Imixs Workflow component if a method call is invalid or the data structure is + * in an invalid state. * - * The property errorCode specifies the exception type. Extensions of this Exception may add - * additional errorCodes. + * The property errorCode specifies the exception type. Extensions of this + * Exception may add additional errorCodes. * * @author rsoika * */ public class InvalidAccessException extends RuntimeException { - public static final String OPERATION_NOTALLOWED = "OPERATION_NOTALLOWED"; - public static final String INVALID_ID = "INVALID_ID"; - public static final String INVALID_INDEX = "INVALID_INDEX"; + public static final String OPERATION_NOTALLOWED = "OPERATION_NOTALLOWED"; + public static final String INVALID_ID = "INVALID_ID"; + public static final String INVALID_INDEX = "INVALID_INDEX"; - protected String errorCode = "UNDEFINED"; - protected String errorContext = "UNDEFINED"; + protected String errorCode = "UNDEFINED"; + protected String errorContext = "UNDEFINED"; - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 1L; - public InvalidAccessException(String message) { - super(message); - } + public InvalidAccessException(String message) { + super(message); + } - public InvalidAccessException(String message, Exception e) { - super(message, e); - } + public InvalidAccessException(String message, Exception e) { + super(message, e); + } - public InvalidAccessException(String aErrorCode, String message) { - super(message); - errorCode = aErrorCode; - } + public InvalidAccessException(String aErrorCode, String message) { + super(message); + errorCode = aErrorCode; + } - public InvalidAccessException(String aErrorCode, String message, Exception e) { - super(message, e); - errorCode = aErrorCode; - } + public InvalidAccessException(String aErrorCode, String message, Exception e) { + super(message, e); + errorCode = aErrorCode; + } - public InvalidAccessException(String aErrorContext, String aErrorCode, String message) { - super(message); - errorContext = aErrorContext; - errorCode = aErrorCode; + public InvalidAccessException(String aErrorContext, String aErrorCode, String message) { + super(message); + errorContext = aErrorContext; + errorCode = aErrorCode; - } + } - public InvalidAccessException(String aErrorContext, String aErrorCode, String message, - Exception e) { - super(message, e); - errorContext = aErrorContext; - errorCode = aErrorCode; + public InvalidAccessException(String aErrorContext, String aErrorCode, String message, Exception e) { + super(message, e); + errorContext = aErrorContext; + errorCode = aErrorCode; - } + } - public String getErrorContext() { - return errorContext; - } + public String getErrorContext() { + return errorContext; + } - public void setErrorContext(String errorContext) { - this.errorContext = errorContext; - } + public void setErrorContext(String errorContext) { + this.errorContext = errorContext; + } - public String getErrorCode() { - return errorCode; - } + public String getErrorCode() { + return errorCode; + } - public void setErrorCode(String errorCode) { - this.errorCode = errorCode; - } + public void setErrorCode(String errorCode) { + this.errorCode = errorCode; + } } diff --git a/imixs-workflow-core/src/main/java/org/imixs/workflow/exceptions/ModelException.java b/imixs-workflow-core/src/main/java/org/imixs/workflow/exceptions/ModelException.java index 7ffe083a0..adb5dc0e4 100644 --- a/imixs-workflow-core/src/main/java/org/imixs/workflow/exceptions/ModelException.java +++ b/imixs-workflow-core/src/main/java/org/imixs/workflow/exceptions/ModelException.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *
- *  Imixs Workflow 
+/*  
+ *  Imixs-Workflow 
+ *  
  *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
  *  http://www.imixs.com
  *  
@@ -22,35 +22,34 @@
  *      https://github.com/imixs/imixs-workflow
  *  
  *  Contributors:  
- *      Imixs Software Solutions GmbH - initial API and implementation
+ *      Imixs Software Solutions GmbH - Project Management
  *      Ralph Soika - Software Developer
- * 
- *******************************************************************************/ + */ package org.imixs.workflow.exceptions; /** - * An ModelException should be thrown by a service component if a model entity is invalid or does - * not exist + * An ModelException should be thrown by a service component if a model entity + * is invalid or does not exist * * @author rsoika * */ public class ModelException extends WorkflowException { - public static final String INVALID_MODEL = "INVALID_MODEL"; - public static final String INVALID_MODEL_ENTRY = "INVALID_MODEL_ENTRY"; - public static final String UNDEFINED_MODEL_ENTRY = "UNDEFINED_MODEL_ENTRY"; - public static final String UNDEFINED_MODEL_VERSION = "UNDEFINED_MODEL_VERSION"; + public static final String INVALID_MODEL = "INVALID_MODEL"; + public static final String INVALID_MODEL_ENTRY = "INVALID_MODEL_ENTRY"; + public static final String UNDEFINED_MODEL_ENTRY = "UNDEFINED_MODEL_ENTRY"; + public static final String UNDEFINED_MODEL_VERSION = "UNDEFINED_MODEL_VERSION"; - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 1L; - public ModelException(String aErrorCode, String message) { - super(aErrorCode, message); - } + public ModelException(String aErrorCode, String message) { + super(aErrorCode, message); + } - public ModelException(String aErrorCode, String message, Exception e) { - super(aErrorCode, message, e); - } + public ModelException(String aErrorCode, String message, Exception e) { + super(aErrorCode, message, e); + } } diff --git a/imixs-workflow-core/src/main/java/org/imixs/workflow/exceptions/PluginException.java b/imixs-workflow-core/src/main/java/org/imixs/workflow/exceptions/PluginException.java index 46fe6e7e2..b0787a138 100644 --- a/imixs-workflow-core/src/main/java/org/imixs/workflow/exceptions/PluginException.java +++ b/imixs-workflow-core/src/main/java/org/imixs/workflow/exceptions/PluginException.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *
- *  Imixs Workflow 
+/*  
+ *  Imixs-Workflow 
+ *  
  *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
  *  http://www.imixs.com
  *  
@@ -22,10 +22,9 @@
  *      https://github.com/imixs/imixs-workflow
  *  
  *  Contributors:  
- *      Imixs Software Solutions GmbH - initial API and implementation
+ *      Imixs Software Solutions GmbH - Project Management
  *      Ralph Soika - Software Developer
- * 
- *******************************************************************************/ + */ package org.imixs.workflow.exceptions; @@ -37,38 +36,32 @@ */ public class PluginException extends WorkflowException { - private static final long serialVersionUID = 1L; - private java.lang.Object[] params = null; - - - public PluginException(String aErrorContext, String aErrorCode, String message) { - super(aErrorContext, aErrorCode, message); - } - - public PluginException(String aErrorContext, String aErrorCode, String message, Exception e) { - super(aErrorContext, aErrorCode, message, e); - } - - - public PluginException(AdapterException e) { - super(e.getErrorContext(), e.getErrorCode(), e.getMessage(), e); - } + private static final long serialVersionUID = 1L; + private java.lang.Object[] params = null; + public PluginException(String aErrorContext, String aErrorCode, String message) { + super(aErrorContext, aErrorCode, message); + } - public PluginException(String aErrorContext, String aErrorCode, String message, - java.lang.Object[] params) { - super(aErrorContext, aErrorCode, message); - this.params = params; - } + public PluginException(String aErrorContext, String aErrorCode, String message, Exception e) { + super(aErrorContext, aErrorCode, message, e); + } - public Object[] getErrorParameters() { - return params; - } + public PluginException(AdapterException e) { + super(e.getErrorContext(), e.getErrorCode(), e.getMessage(), e); + } - protected void setErrorParameters(java.lang.Object[] aparams) { - this.params = aparams; - } + public PluginException(String aErrorContext, String aErrorCode, String message, java.lang.Object[] params) { + super(aErrorContext, aErrorCode, message); + this.params = params; + } + public Object[] getErrorParameters() { + return params; + } + protected void setErrorParameters(java.lang.Object[] aparams) { + this.params = aparams; + } } diff --git a/imixs-workflow-core/src/main/java/org/imixs/workflow/exceptions/ProcessingErrorException.java b/imixs-workflow-core/src/main/java/org/imixs/workflow/exceptions/ProcessingErrorException.java index e0aa13d0b..d64414fb9 100644 --- a/imixs-workflow-core/src/main/java/org/imixs/workflow/exceptions/ProcessingErrorException.java +++ b/imixs-workflow-core/src/main/java/org/imixs/workflow/exceptions/ProcessingErrorException.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *
- *  Imixs Workflow 
+/*  
+ *  Imixs-Workflow 
+ *  
  *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
  *  http://www.imixs.com
  *  
@@ -22,46 +22,43 @@
  *      https://github.com/imixs/imixs-workflow
  *  
  *  Contributors:  
- *      Imixs Software Solutions GmbH - initial API and implementation
+ *      Imixs Software Solutions GmbH - Project Management
  *      Ralph Soika - Software Developer
- * 
- *******************************************************************************/ + */ package org.imixs.workflow.exceptions; /** - * An ProcessingErrorException is a RuntimeExcption thrown by the workflowManager if an error occurs - * during the process method + * An ProcessingErrorException is a RuntimeExcption thrown by the + * workflowManager if an error occurs during the process method * * @author rsoika * */ public class ProcessingErrorException extends InvalidAccessException { - private static final long serialVersionUID = 1L; - - public static final String INVALID_MODELVERSION = "INVALID_MODELVERSION"; - public static final String INVALID_WORKITEM = "INVALID_WORKITEM"; - public static final String INVALID_PROCESSID = "INVALID_PROCESSID"; + private static final long serialVersionUID = 1L; - public ProcessingErrorException(String aErrorCode, String message) { - super(aErrorCode, message); - } + public static final String INVALID_MODELVERSION = "INVALID_MODELVERSION"; + public static final String INVALID_WORKITEM = "INVALID_WORKITEM"; + public static final String INVALID_PROCESSID = "INVALID_PROCESSID"; - public ProcessingErrorException(String aErrorContext, String aErrorCode, String message) { - super(message); - errorContext = aErrorContext; - errorCode = aErrorCode; + public ProcessingErrorException(String aErrorCode, String message) { + super(aErrorCode, message); + } - } + public ProcessingErrorException(String aErrorContext, String aErrorCode, String message) { + super(message); + errorContext = aErrorContext; + errorCode = aErrorCode; - public ProcessingErrorException(String aErrorContext, String aErrorCode, String message, - Exception e) { - super(message, e); - errorContext = aErrorContext; - errorCode = aErrorCode; + } - } + public ProcessingErrorException(String aErrorContext, String aErrorCode, String message, Exception e) { + super(message, e); + errorContext = aErrorContext; + errorCode = aErrorCode; + } } diff --git a/imixs-workflow-core/src/main/java/org/imixs/workflow/exceptions/QueryException.java b/imixs-workflow-core/src/main/java/org/imixs/workflow/exceptions/QueryException.java index ebc34d277..a06048344 100644 --- a/imixs-workflow-core/src/main/java/org/imixs/workflow/exceptions/QueryException.java +++ b/imixs-workflow-core/src/main/java/org/imixs/workflow/exceptions/QueryException.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *
- *  Imixs Workflow 
+/*  
+ *  Imixs-Workflow 
+ *  
  *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
  *  http://www.imixs.com
  *  
@@ -22,10 +22,9 @@
  *      https://github.com/imixs/imixs-workflow
  *  
  *  Contributors:  
- *      Imixs Software Solutions GmbH - initial API and implementation
+ *      Imixs Software Solutions GmbH - Project Management
  *      Ralph Soika - Software Developer
- * 
- *******************************************************************************/ + */ package org.imixs.workflow.exceptions; @@ -38,16 +37,16 @@ */ public class QueryException extends WorkflowException { - public static final String QUERY_NOT_UNDERSTANDABLE = "QUERY_NOT_UNDERSTANDABLE"; + public static final String QUERY_NOT_UNDERSTANDABLE = "QUERY_NOT_UNDERSTANDABLE"; - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 1L; - public QueryException(String aErrorCode, String message) { - super(aErrorCode, message); - } + public QueryException(String aErrorCode, String message) { + super(aErrorCode, message); + } - public QueryException(String aErrorCode, String message, Exception e) { - super(aErrorCode, message, e); - } + public QueryException(String aErrorCode, String message, Exception e) { + super(aErrorCode, message, e); + } } diff --git a/imixs-workflow-core/src/main/java/org/imixs/workflow/exceptions/WorkflowException.java b/imixs-workflow-core/src/main/java/org/imixs/workflow/exceptions/WorkflowException.java index 787ecdf31..f0295f2ee 100644 --- a/imixs-workflow-core/src/main/java/org/imixs/workflow/exceptions/WorkflowException.java +++ b/imixs-workflow-core/src/main/java/org/imixs/workflow/exceptions/WorkflowException.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *
- *  Imixs Workflow 
+/*  
+ *  Imixs-Workflow 
+ *  
  *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
  *  http://www.imixs.com
  *  
@@ -22,68 +22,67 @@
  *      https://github.com/imixs/imixs-workflow
  *  
  *  Contributors:  
- *      Imixs Software Solutions GmbH - initial API and implementation
+ *      Imixs Software Solutions GmbH - Project Management
  *      Ralph Soika - Software Developer
- * 
- *******************************************************************************/ + */ package org.imixs.workflow.exceptions; /** - * WorkflowException is the abstract super class for all Imixs Workflow Exception classes. A - * WorkflowException signals an error in the business logic. WorkflowExceptions need to be caught. + * WorkflowException is the abstract super class for all Imixs Workflow + * Exception classes. A WorkflowException signals an error in the business + * logic. WorkflowExceptions need to be caught. * * @author rsoika */ public abstract class WorkflowException extends Exception { - private static final long serialVersionUID = 1L; - - protected String errorContext = "UNDEFINED"; - protected String errorCode = "UNDEFINED"; + private static final long serialVersionUID = 1L; - public WorkflowException(String aErrorCode, String message) { - super(message); - errorCode = aErrorCode; + protected String errorContext = "UNDEFINED"; + protected String errorCode = "UNDEFINED"; - } + public WorkflowException(String aErrorCode, String message) { + super(message); + errorCode = aErrorCode; - public WorkflowException(String aErrorContext, String aErrorCode, String message) { - super(message); - errorContext = aErrorContext; - errorCode = aErrorCode; + } - } + public WorkflowException(String aErrorContext, String aErrorCode, String message) { + super(message); + errorContext = aErrorContext; + errorCode = aErrorCode; - public WorkflowException(String aErrorContext, String aErrorCode, String message, Exception e) { - super(message, e); - errorContext = aErrorContext; - errorCode = aErrorCode; + } - } + public WorkflowException(String aErrorContext, String aErrorCode, String message, Exception e) { + super(message, e); + errorContext = aErrorContext; + errorCode = aErrorCode; + } - public WorkflowException(String aErrorCode, String message, Exception e) { - super(message, e); + public WorkflowException(String aErrorCode, String message, Exception e) { + super(message, e); - errorCode = aErrorCode; + errorCode = aErrorCode; - } + } - public String getErrorContext() { - return errorContext; - } + public String getErrorContext() { + return errorContext; + } - public void setErrorContext(String errorContext) { - this.errorContext = errorContext; - } + public void setErrorContext(String errorContext) { + this.errorContext = errorContext; + } - public String getErrorCode() { - return errorCode; - } + public String getErrorCode() { + return errorCode; + } - public void setErrorCode(String errorCode) { - this.errorCode = errorCode; - } + public void setErrorCode(String errorCode) { + this.errorCode = errorCode; + } } diff --git a/imixs-workflow-core/src/main/java/org/imixs/workflow/services/rest/BasicAuthenticator.java b/imixs-workflow-core/src/main/java/org/imixs/workflow/services/rest/BasicAuthenticator.java index b77e8044e..6bdf15169 100644 --- a/imixs-workflow-core/src/main/java/org/imixs/workflow/services/rest/BasicAuthenticator.java +++ b/imixs-workflow-core/src/main/java/org/imixs/workflow/services/rest/BasicAuthenticator.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *
- *  Imixs Workflow 
+/*  
+ *  Imixs-Workflow 
+ *  
  *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
  *  http://www.imixs.com
  *  
@@ -22,10 +22,9 @@
  *      https://github.com/imixs/imixs-workflow
  *  
  *  Contributors:  
- *      Imixs Software Solutions GmbH - initial API and implementation
+ *      Imixs Software Solutions GmbH - Project Management
  *      Ralph Soika - Software Developer
- * 
- *******************************************************************************/ + */ package org.imixs.workflow.services.rest; @@ -42,32 +41,32 @@ */ public class BasicAuthenticator implements RequestFilter { - private final String user; - private final String password; + private final String user; + private final String password; - public BasicAuthenticator(String user, String password) { - this.user = user; - this.password = password; - } + public BasicAuthenticator(String user, String password) { + this.user = user; + this.password = password; + } - public void filter(HttpURLConnection connection) throws IOException { - connection.setRequestProperty("Authorization", "Basic " + getBasicAuthentication()); + public void filter(HttpURLConnection connection) throws IOException { + connection.setRequestProperty("Authorization", "Basic " + getBasicAuthentication()); - } + } - /** - * This methos set the user password information for basic authentication - */ - private String getBasicAuthentication() { - String sURLAccess = ""; - // UserID:Passwort - String sUserCode = user + ":" + password; - // String convertieren - // sURLAccess = Base64.encodeBase64(sUserCode.getBytes()).toString(); - char[] authcode = Base64.encode(sUserCode.getBytes()); + /** + * This methos set the user password information for basic authentication + */ + private String getBasicAuthentication() { + String sURLAccess = ""; + // UserID:Passwort + String sUserCode = user + ":" + password; + // String convertieren + // sURLAccess = Base64.encodeBase64(sUserCode.getBytes()).toString(); + char[] authcode = Base64.encode(sUserCode.getBytes()); - sURLAccess = String.valueOf(authcode); - return sURLAccess; - } + sURLAccess = String.valueOf(authcode); + return sURLAccess; + } } diff --git a/imixs-workflow-core/src/main/java/org/imixs/workflow/services/rest/FormAuthenticator.java b/imixs-workflow-core/src/main/java/org/imixs/workflow/services/rest/FormAuthenticator.java index 11753d2b7..12ff768b5 100644 --- a/imixs-workflow-core/src/main/java/org/imixs/workflow/services/rest/FormAuthenticator.java +++ b/imixs-workflow-core/src/main/java/org/imixs/workflow/services/rest/FormAuthenticator.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *
- *  Imixs Workflow 
+/*  
+ *  Imixs-Workflow 
+ *  
  *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
  *  http://www.imixs.com
  *  
@@ -22,10 +22,9 @@
  *      https://github.com/imixs/imixs-workflow
  *  
  *  Contributors:  
- *      Imixs Software Solutions GmbH - initial API and implementation
+ *      Imixs Software Solutions GmbH - Project Management
  *      Ralph Soika - Software Developer
- * 
- *******************************************************************************/ + */ package org.imixs.workflow.services.rest; @@ -44,81 +43,81 @@ import java.util.logging.Logger; /** - * This RequestFilter performs a form based authentication. The filter can be used with a - * javax.ws.rs.client.Client. + * This RequestFilter performs a form based authentication. The filter can be + * used with a javax.ws.rs.client.Client. * * @author rsoika * */ public class FormAuthenticator implements RequestFilter { - private List cookies; - private final String USER_AGENT = "Mozilla/5.0"; - private final static Logger logger = Logger.getLogger(FormAuthenticator.class.getName()); + private List cookies; + private final String USER_AGENT = "Mozilla/5.0"; + private final static Logger logger = Logger.getLogger(FormAuthenticator.class.getName()); - public FormAuthenticator(String baseUri, String username, String password) { - // extend the base uri with /j_security_check.... - baseUri += "/j_security_check"; - logger.finest("......baseUIR= " + baseUri); - // Access secure page on server. In response to this request we will receive - // the JSESSIONID to be used for further requests. - try { - // Instantiate CookieManager; - // make sure to set CookiePolicy - CookieManager manager = new CookieManager(); - manager.setCookiePolicy(CookiePolicy.ACCEPT_ALL); - CookieHandler.setDefault(manager); - // create the httpURLConnection... - URL obj = new URL(baseUri); - HttpURLConnection con = (HttpURLConnection) obj.openConnection(); - con.setRequestProperty("Connection", "close"); + public FormAuthenticator(String baseUri, String username, String password) { + // extend the base uri with /j_security_check.... + baseUri += "/j_security_check"; + logger.finest("......baseUIR= " + baseUri); + // Access secure page on server. In response to this request we will receive + // the JSESSIONID to be used for further requests. + try { + // Instantiate CookieManager; + // make sure to set CookiePolicy + CookieManager manager = new CookieManager(); + manager.setCookiePolicy(CookiePolicy.ACCEPT_ALL); + CookieHandler.setDefault(manager); + // create the httpURLConnection... + URL obj = new URL(baseUri); + HttpURLConnection con = (HttpURLConnection) obj.openConnection(); + con.setRequestProperty("Connection", "close"); - // add request header - con.setRequestMethod("POST"); - con.setRequestProperty("User-Agent", USER_AGENT); - // add Post parameters - String urlParameters = "j_username=" + username + "&j_password=" + password; - // Send post request - con.setDoOutput(true); - con.setDoInput(true); - DataOutputStream wr = new DataOutputStream(con.getOutputStream()); - wr.writeBytes(urlParameters); - wr.flush(); - wr.close(); - int responseCode = con.getResponseCode(); - logger.finest(".....Response Code : " + responseCode); - con.connect(); - // get cookies from underlying CookieStore - CookieStore cookieJar = manager.getCookieStore(); - cookies = cookieJar.getCookies(); + // add request header + con.setRequestMethod("POST"); + con.setRequestProperty("User-Agent", USER_AGENT); + // add Post parameters + String urlParameters = "j_username=" + username + "&j_password=" + password; + // Send post request + con.setDoOutput(true); + con.setDoInput(true); + DataOutputStream wr = new DataOutputStream(con.getOutputStream()); + wr.writeBytes(urlParameters); + wr.flush(); + wr.close(); + int responseCode = con.getResponseCode(); + logger.finest(".....Response Code : " + responseCode); + con.connect(); + // get cookies from underlying CookieStore + CookieStore cookieJar = manager.getCookieStore(); + cookies = cookieJar.getCookies(); - // get stream and read from it, just to close the response which is important - BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream())); - String inputLine; - StringBuffer response = new StringBuffer(); - while ((inputLine = in.readLine()) != null) { - response.append(inputLine); - } - in.close(); + // get stream and read from it, just to close the response which is important + BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream())); + String inputLine; + StringBuffer response = new StringBuffer(); + while ((inputLine = in.readLine()) != null) { + response.append(inputLine); + } + in.close(); - } catch (IOException e) { - // something went wrong... - e.printStackTrace(); - } + } catch (IOException e) { + // something went wrong... + e.printStackTrace(); + } - } + } - /** - * In the filter method we put the cookies form the login into the request. - */ - public void filter(HttpURLConnection connection) throws IOException { - if (cookies != null && cookies.size() > 0) { - String values = ""; - for (HttpCookie acookie : cookies) { - values = values + acookie + ","; - } - connection.setRequestProperty("Cookie", values); + /** + * In the filter method we put the cookies form the login into the request. + */ + public void filter(HttpURLConnection connection) throws IOException { + if (cookies != null && cookies.size() > 0) { + String values = ""; + for (HttpCookie acookie : cookies) { + values = values + acookie + ","; + } + connection.setRequestProperty("Cookie", values); + } } - } } diff --git a/imixs-workflow-core/src/main/java/org/imixs/workflow/services/rest/JWTAuthenticator.java b/imixs-workflow-core/src/main/java/org/imixs/workflow/services/rest/JWTAuthenticator.java index d0fa4ac50..dd2984990 100644 --- a/imixs-workflow-core/src/main/java/org/imixs/workflow/services/rest/JWTAuthenticator.java +++ b/imixs-workflow-core/src/main/java/org/imixs/workflow/services/rest/JWTAuthenticator.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *
- *  Imixs Workflow 
+/*  
+ *  Imixs-Workflow 
+ *  
  *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
  *  http://www.imixs.com
  *  
@@ -22,10 +22,9 @@
  *      https://github.com/imixs/imixs-workflow
  *  
  *  Contributors:  
- *      Imixs Software Solutions GmbH - initial API and implementation
+ *      Imixs Software Solutions GmbH - Project Management
  *      Ralph Soika - Software Developer
- * 
- *******************************************************************************/ + */ package org.imixs.workflow.services.rest; @@ -42,23 +41,23 @@ */ public class JWTAuthenticator implements RequestFilter { - private final String jwt; - private final static Logger logger = Logger.getLogger(JWTAuthenticator.class.getName()); + private final String jwt; + private final static Logger logger = Logger.getLogger(JWTAuthenticator.class.getName()); - public JWTAuthenticator(String jwt) { - this.jwt = jwt; - } + public JWTAuthenticator(String jwt) { + this.jwt = jwt; + } - public void filter(HttpURLConnection connection) throws IOException { + public void filter(HttpURLConnection connection) throws IOException { - URL uri = connection.getURL();// .getUri(); + URL uri = connection.getURL();// .getUri(); - String url = uri.toString(); - if (!url.contains("jwt=")) { - logger.info("adding JSON Web Token..."); - connection.setRequestProperty("jwt", jwt); - } + String url = uri.toString(); + if (!url.contains("jwt=")) { + logger.info("adding JSON Web Token..."); + connection.setRequestProperty("jwt", jwt); + } - } + } } diff --git a/imixs-workflow-core/src/main/java/org/imixs/workflow/services/rest/RequestFilter.java b/imixs-workflow-core/src/main/java/org/imixs/workflow/services/rest/RequestFilter.java index adba3a8e8..88cfde504 100644 --- a/imixs-workflow-core/src/main/java/org/imixs/workflow/services/rest/RequestFilter.java +++ b/imixs-workflow-core/src/main/java/org/imixs/workflow/services/rest/RequestFilter.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *
- *  Imixs Workflow 
+/*  
+ *  Imixs-Workflow 
+ *  
  *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
  *  http://www.imixs.com
  *  
@@ -22,10 +22,9 @@
  *      https://github.com/imixs/imixs-workflow
  *  
  *  Contributors:  
- *      Imixs Software Solutions GmbH - initial API and implementation
+ *      Imixs Software Solutions GmbH - Project Management
  *      Ralph Soika - Software Developer
- * 
- *******************************************************************************/ + */ package org.imixs.workflow.services.rest; @@ -33,5 +32,5 @@ import java.net.HttpURLConnection; public interface RequestFilter { - public void filter(HttpURLConnection connection) throws IOException; + public void filter(HttpURLConnection connection) throws IOException; } diff --git a/imixs-workflow-core/src/main/java/org/imixs/workflow/services/rest/RestAPIException.java b/imixs-workflow-core/src/main/java/org/imixs/workflow/services/rest/RestAPIException.java index 0428a7591..40065176a 100644 --- a/imixs-workflow-core/src/main/java/org/imixs/workflow/services/rest/RestAPIException.java +++ b/imixs-workflow-core/src/main/java/org/imixs/workflow/services/rest/RestAPIException.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *
- *  Imixs Workflow 
+/*  
+ *  Imixs-Workflow 
+ *  
  *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
  *  http://www.imixs.com
  *  
@@ -22,16 +22,15 @@
  *      https://github.com/imixs/imixs-workflow
  *  
  *  Contributors:  
- *      Imixs Software Solutions GmbH - initial API and implementation
+ *      Imixs Software Solutions GmbH - Project Management
  *      Ralph Soika - Software Developer
- * 
- *******************************************************************************/ + */ package org.imixs.workflow.services.rest; /** - * RestAPIException signals an error in the communication with the Imixs Rest API using the Imixs - * RestClient. The error code is the HTTP responce code. + * RestAPIException signals an error in the communication with the Imixs Rest + * API using the Imixs RestClient. The error code is the HTTP responce code. * * @see RestClient * @author rsoika @@ -39,52 +38,52 @@ */ public class RestAPIException extends Exception { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 1L; - protected String errorContext = "UNDEFINED"; - protected int errorCode = 0; + protected String errorContext = "UNDEFINED"; + protected int errorCode = 0; - public RestAPIException(int aErrorCode, String message) { - super(message); - errorCode = aErrorCode; + public RestAPIException(int aErrorCode, String message) { + super(message); + errorCode = aErrorCode; - } + } - public RestAPIException(String aErrorContext, int aErrorCode, String message) { - super(message); - errorContext = aErrorContext; - errorCode = aErrorCode; + public RestAPIException(String aErrorContext, int aErrorCode, String message) { + super(message); + errorContext = aErrorContext; + errorCode = aErrorCode; - } + } - public RestAPIException(String aErrorContext, int aErrorCode, String message, Exception e) { - super(message, e); - errorContext = aErrorContext; - errorCode = aErrorCode; + public RestAPIException(String aErrorContext, int aErrorCode, String message, Exception e) { + super(message, e); + errorContext = aErrorContext; + errorCode = aErrorCode; - } + } - public RestAPIException(int aErrorCode, String message, Exception e) { - super(message, e); + public RestAPIException(int aErrorCode, String message, Exception e) { + super(message, e); - errorCode = aErrorCode; + errorCode = aErrorCode; - } + } - public String getErrorContext() { - return errorContext; - } + public String getErrorContext() { + return errorContext; + } - public void setErrorContext(String errorContext) { - this.errorContext = errorContext; - } + public void setErrorContext(String errorContext) { + this.errorContext = errorContext; + } - public int getErrorCode() { - return errorCode; - } + public int getErrorCode() { + return errorCode; + } - public void setErrorCode(int errorCode) { - this.errorCode = errorCode; - } + public void setErrorCode(int errorCode) { + this.errorCode = errorCode; + } } diff --git a/imixs-workflow-core/src/main/java/org/imixs/workflow/services/rest/RestClient.java b/imixs-workflow-core/src/main/java/org/imixs/workflow/services/rest/RestClient.java index 1f62307a3..5e017bccd 100644 --- a/imixs-workflow-core/src/main/java/org/imixs/workflow/services/rest/RestClient.java +++ b/imixs-workflow-core/src/main/java/org/imixs/workflow/services/rest/RestClient.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *
- *  Imixs Workflow 
+/*  
+ *  Imixs-Workflow 
+ *  
  *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
  *  http://www.imixs.com
  *  
@@ -22,10 +22,9 @@
  *      https://github.com/imixs/imixs-workflow
  *  
  *  Contributors:  
- *      Imixs Software Solutions GmbH - initial API and implementation
+ *      Imixs Software Solutions GmbH - Project Management
  *      Ralph Soika - Software Developer
- * 
- *******************************************************************************/ + */ package org.imixs.workflow.services.rest; @@ -47,399 +46,396 @@ import java.util.logging.Logger; /** - * The Imixs RestClient is a helper class for a Rest based communication without the use of Jax-rs. + * The Imixs RestClient is a helper class for a Rest based communication without + * the use of Jax-rs. *

* The Imixs RestClient provides methods to GET and POST data objects. *

* The client throws a RestAPIException in case of an communication error. *

- * For a convinient way to access the Imixs-Rest API use the Imixs-Melman project on Github. + * For a convinient way to access the Imixs-Rest API use the Imixs-Melman + * project on Github. * * @author Ralph Soika */ public class RestClient { - private String serviceEndpoint; - private Map requestProperties = null; - private String encoding = "UTF-8"; - private int iLastHTTPResult = 0; - private String rootURL = null; - private final static Logger logger = Logger.getLogger(RestClient.class.getName()); + private String serviceEndpoint; + private Map requestProperties = null; + private String encoding = "UTF-8"; + private int iLastHTTPResult = 0; + private String rootURL = null; + private final static Logger logger = Logger.getLogger(RestClient.class.getName()); + + protected List requestFilterList; + + public RestClient() { + super(); + requestFilterList = new ArrayList(); + } + + public RestClient(String rootURL) { + this(); + if (rootURL != null && !rootURL.endsWith("/")) { + rootURL += "/"; + } - protected List requestFilterList; + this.rootURL = rootURL; + } - public RestClient() { - super(); - requestFilterList = new ArrayList(); - } + /** + * Register a ClientRequestFilter instance. + * + * @param filter - request filter instance. + */ + public void registerRequestFilter(RequestFilter filter) { + logger.finest("......register new request filter: " + filter.getClass().getSimpleName()); - public RestClient(String rootURL) { - this(); - if (rootURL != null && !rootURL.endsWith("/")) { - rootURL += "/"; + // client.register(filter); + requestFilterList.add(filter); } - this.rootURL = rootURL; - } - - /** - * Register a ClientRequestFilter instance. - * - * @param filter - request filter instance. - */ - public void registerRequestFilter(RequestFilter filter) { - logger.finest("......register new request filter: " + filter.getClass().getSimpleName()); - - // client.register(filter); - requestFilterList.add(filter); - } - - public String getEncoding() { - return encoding; - } - - public void setEncoding(String aEncoding) { - encoding = aEncoding; - } - - public String getServiceEndpoint() { - return serviceEndpoint; - } - - /** - * This method builds the serviceEndpoint based on a given URI . The method prafix the URI with - * the root uri if the uri starts with / - *

- * If the URI is with protocol :// then the servcieEndpoint is set directly. - * - * - * @param uri - * @throws RestAPIException - */ - void setServiceEndpoint(String uri) throws RestAPIException { - // test for protocoll - if (uri.contains("://")) { - this.serviceEndpoint = uri; - return; + public String getEncoding() { + return encoding; } - if (rootURL == null) { - throw new RestAPIException(0, "rootURL is null!"); + public void setEncoding(String aEncoding) { + encoding = aEncoding; } - // test for double / - if (uri != null && uri.startsWith("/")) { - uri = uri.substring(1); + public String getServiceEndpoint() { + return serviceEndpoint; } - // add root URL - uri = rootURL + uri; + /** + * This method builds the serviceEndpoint based on a given URI . The method + * prafix the URI with the root uri if the uri starts with / + *

+ * If the URI is with protocol :// then the servcieEndpoint is set directly. + * + * + * @param uri + * @throws RestAPIException + */ + void setServiceEndpoint(String uri) throws RestAPIException { + // test for protocoll + if (uri.contains("://")) { + this.serviceEndpoint = uri; + return; + } - this.serviceEndpoint = uri; - } + if (rootURL == null) { + throw new RestAPIException(0, "rootURL is null!"); + } - /** - * Set a single header request property - */ - public void setRequestProperty(String key, String value) { - if (requestProperties == null) { - requestProperties = new HashMap(); - } - requestProperties.put(key, value); - } - - /** - * Posts a String data object with a specific Content-Type to a Rest Service URI Endpoint. This - * method can be used to simulate different post scenarios. - *

- * The parameter 'contnetType' can be used to request a specific media type. - * - * @param uri - Rest Endpoint URI - * @param dataString - content - * @param contentType - request MediaType - * @return content - * @throws RestAPIException - * @throws Exception - */ - public String post(String uri, String dataString, String contentType) throws RestAPIException { - return post(uri, dataString, contentType, null); - } - - /** - * Posts a String data object with a specific Content-Type to a Rest Service URI Endpoint. This - * method can be used to simulate different post scenarios. - *

- * The parameter 'contnetType' and 'acceptType' can be used to request and accept specific media - * types. - * - * @param uri - Rest Endpoint URI - * @param dataString - content - * @param contentType - request MediaType - * @param acceptType - accept MediaType - * @return content - * @throws RestAPIException - */ - public String post(String uri, String dataString, final String _contentType, String acceptType) - throws RestAPIException { - PrintWriter printWriter = null; - String contentType = _contentType; - - if (contentType == null || contentType.isEmpty()) { - contentType = "application/xml"; + // test for double / + if (uri != null && uri.startsWith("/")) { + uri = uri.substring(1); + } + + // add root URL + uri = rootURL + uri; + + this.serviceEndpoint = uri; } - if (acceptType == null || acceptType.isEmpty()) { - acceptType = contentType; + + /** + * Set a single header request property + */ + public void setRequestProperty(String key, String value) { + if (requestProperties == null) { + requestProperties = new HashMap(); + } + requestProperties.put(key, value); } - HttpURLConnection urlConnection = null; - try { - serviceEndpoint = uri; - iLastHTTPResult = 500; - - urlConnection = (HttpURLConnection) new URL(serviceEndpoint).openConnection(); - urlConnection.setRequestMethod("POST"); - urlConnection.setDoOutput(true); - urlConnection.setDoInput(true); - urlConnection.setAllowUserInteraction(false); - - /** * HEADER ** */ - urlConnection.setRequestProperty("Content-Type", contentType + "; charset=" + encoding); - urlConnection.setRequestProperty("Accept-Charset", encoding); - urlConnection.setRequestProperty("Accept", acceptType); - - // process filters.... - for (RequestFilter filter : requestFilterList) { - filter.filter(urlConnection); - } - - StringWriter writer = new StringWriter(); - writer.write(dataString); - writer.flush(); - - // compute length - urlConnection.setRequestProperty("Content-Length", - "" + Integer.valueOf(writer.toString().getBytes().length)); - - printWriter = new PrintWriter( - new BufferedWriter(new OutputStreamWriter(urlConnection.getOutputStream(), encoding))); - printWriter.write(writer.toString()); - printWriter.close(); - - String sHTTPResponse = urlConnection.getHeaderField(0); - try { - iLastHTTPResult = Integer.parseInt(sHTTPResponse.substring(9, 12)); - } catch (Exception eNumber) { - // eNumber.printStackTrace(); - iLastHTTPResult = 500; - } - String content = readResponse(urlConnection); - - return content; - - } catch (IOException ioe) { - String error = "Error POST request '" + uri + " - " + ioe.getMessage(); - logger.warning(error); - throw new RestAPIException(500, error, ioe); - } finally { - // Release current connection - if (printWriter != null) - printWriter.close(); + /** + * Posts a String data object with a specific Content-Type to a Rest Service URI + * Endpoint. This method can be used to simulate different post scenarios. + *

+ * The parameter 'contnetType' can be used to request a specific media type. + * + * @param uri - Rest Endpoint URI + * @param dataString - content + * @param contentType - request MediaType + * @return content + * @throws RestAPIException + * @throws Exception + */ + public String post(String uri, String dataString, String contentType) throws RestAPIException { + return post(uri, dataString, contentType, null); } - } - - - - /** - * Posts a byte array to a Rest Service URI Endpoint. This method can be used to simulate - * different post scenarios. - *

- * The parameter 'contnetType' and 'acceptType' can be used to request and accept specific media - * types. - * - * @param uri - Rest Endpoint URI - * @param data - content - * @param contentType - request MediaType - * @param acceptType - accept MediaType - * @return content - * @throws RestAPIException - */ - public String post(String uri, byte[] data, final String _contentType) throws RestAPIException { - return post(uri, data, _contentType, null); - } - - /** - * Posts a byte array to a Rest Service URI Endpoint. This method can be used to simulate - * different post scenarios. - *

- * The parameter 'contnetType' and 'acceptType' can be used to request and accept specific media - * types. - * - * @param uri - Rest Endpoint URI - * @param data - content - * @param contentType - request MediaType - * @param acceptType - accept MediaType - * @return content - * @throws RestAPIException - */ - public String post(String uri, byte[] data, final String _contentType, String acceptType) - throws RestAPIException { - - String contentType = _contentType; - - if (contentType == null || contentType.isEmpty()) { - contentType = "application/xml"; + + /** + * Posts a String data object with a specific Content-Type to a Rest Service URI + * Endpoint. This method can be used to simulate different post scenarios. + *

+ * The parameter 'contnetType' and 'acceptType' can be used to request and + * accept specific media types. + * + * @param uri - Rest Endpoint URI + * @param dataString - content + * @param contentType - request MediaType + * @param acceptType - accept MediaType + * @return content + * @throws RestAPIException + */ + public String post(String uri, String dataString, final String _contentType, String acceptType) + throws RestAPIException { + PrintWriter printWriter = null; + String contentType = _contentType; + + if (contentType == null || contentType.isEmpty()) { + contentType = "application/xml"; + } + if (acceptType == null || acceptType.isEmpty()) { + acceptType = contentType; + } + + HttpURLConnection urlConnection = null; + try { + serviceEndpoint = uri; + iLastHTTPResult = 500; + + urlConnection = (HttpURLConnection) new URL(serviceEndpoint).openConnection(); + urlConnection.setRequestMethod("POST"); + urlConnection.setDoOutput(true); + urlConnection.setDoInput(true); + urlConnection.setAllowUserInteraction(false); + + /** * HEADER ** */ + urlConnection.setRequestProperty("Content-Type", contentType + "; charset=" + encoding); + urlConnection.setRequestProperty("Accept-Charset", encoding); + urlConnection.setRequestProperty("Accept", acceptType); + + // process filters.... + for (RequestFilter filter : requestFilterList) { + filter.filter(urlConnection); + } + + StringWriter writer = new StringWriter(); + writer.write(dataString); + writer.flush(); + + // compute length + urlConnection.setRequestProperty("Content-Length", + "" + Integer.valueOf(writer.toString().getBytes().length)); + + printWriter = new PrintWriter( + new BufferedWriter(new OutputStreamWriter(urlConnection.getOutputStream(), encoding))); + printWriter.write(writer.toString()); + printWriter.close(); + + String sHTTPResponse = urlConnection.getHeaderField(0); + try { + iLastHTTPResult = Integer.parseInt(sHTTPResponse.substring(9, 12)); + } catch (Exception eNumber) { + // eNumber.printStackTrace(); + iLastHTTPResult = 500; + } + String content = readResponse(urlConnection); + + return content; + + } catch (IOException ioe) { + String error = "Error POST request '" + uri + " - " + ioe.getMessage(); + logger.warning(error); + throw new RestAPIException(500, error, ioe); + } finally { + // Release current connection + if (printWriter != null) + printWriter.close(); + } } - if (acceptType == null || acceptType.isEmpty()) { - acceptType = contentType; + + /** + * Posts a byte array to a Rest Service URI Endpoint. This method can be used to + * simulate different post scenarios. + *

+ * The parameter 'contnetType' and 'acceptType' can be used to request and + * accept specific media types. + * + * @param uri - Rest Endpoint URI + * @param data - content + * @param contentType - request MediaType + * @param acceptType - accept MediaType + * @return content + * @throws RestAPIException + */ + public String post(String uri, byte[] data, final String _contentType) throws RestAPIException { + return post(uri, data, _contentType, null); } - HttpURLConnection urlConnection = null; - try { - serviceEndpoint = uri; - iLastHTTPResult = 500; - - urlConnection = (HttpURLConnection) new URL(serviceEndpoint).openConnection(); - urlConnection.setRequestMethod("POST"); - urlConnection.setDoOutput(true); - urlConnection.setDoInput(true); - urlConnection.setAllowUserInteraction(true); - - /** * HEADER ** */ - urlConnection.setRequestProperty("Content-Type", contentType + "; charset=" + encoding); - urlConnection.setRequestProperty("Accept-Charset", encoding); - urlConnection.setRequestProperty("Accept", acceptType); - - if (requestProperties != null) { - for (Map.Entry entry : requestProperties.entrySet()) { - urlConnection.setRequestProperty(entry.getKey(), entry.getValue()); + /** + * Posts a byte array to a Rest Service URI Endpoint. This method can be used to + * simulate different post scenarios. + *

+ * The parameter 'contnetType' and 'acceptType' can be used to request and + * accept specific media types. + * + * @param uri - Rest Endpoint URI + * @param data - content + * @param contentType - request MediaType + * @param acceptType - accept MediaType + * @return content + * @throws RestAPIException + */ + public String post(String uri, byte[] data, final String _contentType, String acceptType) throws RestAPIException { + + String contentType = _contentType; + + if (contentType == null || contentType.isEmpty()) { + contentType = "application/xml"; + } + if (acceptType == null || acceptType.isEmpty()) { + acceptType = contentType; } - } - - // process filters.... - for (RequestFilter filter : requestFilterList) { - filter.filter(urlConnection); - } - - // transfer data - OutputStream outputStreamToRequestBody = urlConnection.getOutputStream(); - BufferedWriter httpRequestBodyWriter = - new BufferedWriter(new OutputStreamWriter(outputStreamToRequestBody)); - outputStreamToRequestBody.write(data); // , 0, bytesRead); - outputStreamToRequestBody.flush(); - // Close the streams - outputStreamToRequestBody.close(); - httpRequestBodyWriter.close(); - String content = readResponse(urlConnection); - return content; - } catch (IOException ioe) { - String error = "Error POST request '" + uri + " - " + ioe.getMessage(); - logger.warning(error); - throw new RestAPIException(500, error, ioe); - } finally { - } - } - - /** - * This method returns the last HTTP Result - * - * @return - */ - public int getLastHTTPResult() { - return iLastHTTPResult; - } - - /** - * Gets the content of a GET request from a Rest Service URI Endpoint. I case of an error the - * method throws a RestAPIException. - * - * @param uri - Rest Endpoint RUI - * @return - content or null if no content is available. - */ - public String get(String uri) throws RestAPIException { - - setServiceEndpoint(uri); - try { - HttpURLConnection urlConnection = - (HttpURLConnection) new URL(serviceEndpoint).openConnection(); - - // optional default is GET - urlConnection.setRequestMethod("GET"); - - urlConnection.setDoOutput(true); - urlConnection.setDoInput(true); - urlConnection.setAllowUserInteraction(false); - - if (requestProperties != null) { - for (Map.Entry entry : requestProperties.entrySet()) { - urlConnection.setRequestProperty(entry.getKey(), entry.getValue()); + HttpURLConnection urlConnection = null; + try { + serviceEndpoint = uri; + iLastHTTPResult = 500; + + urlConnection = (HttpURLConnection) new URL(serviceEndpoint).openConnection(); + urlConnection.setRequestMethod("POST"); + urlConnection.setDoOutput(true); + urlConnection.setDoInput(true); + urlConnection.setAllowUserInteraction(true); + + /** * HEADER ** */ + urlConnection.setRequestProperty("Content-Type", contentType + "; charset=" + encoding); + urlConnection.setRequestProperty("Accept-Charset", encoding); + urlConnection.setRequestProperty("Accept", acceptType); + + if (requestProperties != null) { + for (Map.Entry entry : requestProperties.entrySet()) { + urlConnection.setRequestProperty(entry.getKey(), entry.getValue()); + } + } + + // process filters.... + for (RequestFilter filter : requestFilterList) { + filter.filter(urlConnection); + } + + // transfer data + OutputStream outputStreamToRequestBody = urlConnection.getOutputStream(); + BufferedWriter httpRequestBodyWriter = new BufferedWriter( + new OutputStreamWriter(outputStreamToRequestBody)); + outputStreamToRequestBody.write(data); // , 0, bytesRead); + outputStreamToRequestBody.flush(); + // Close the streams + outputStreamToRequestBody.close(); + httpRequestBodyWriter.close(); + String content = readResponse(urlConnection); + return content; + } catch (IOException ioe) { + String error = "Error POST request '" + uri + " - " + ioe.getMessage(); + logger.warning(error); + throw new RestAPIException(500, error, ioe); + } finally { + } - } - - // process filters.... - for (RequestFilter filter : requestFilterList) { - filter.filter(urlConnection); - } - - iLastHTTPResult = urlConnection.getResponseCode(); - logger.finest("......Sending 'GET' request to URL : " + serviceEndpoint); - logger.finest("......Response Code : " + iLastHTTPResult); - // read response if response was successful - if (iLastHTTPResult >= 200 && iLastHTTPResult <= 299) { - return readResponse(urlConnection); - } else { - String error = "Error " + iLastHTTPResult + " - failed GET request from '" + uri + "'"; - logger.warning(error); - throw new RestAPIException(iLastHTTPResult, error); - } - } catch (IOException e) { - String error = "Error GET request from '" + uri + " - " + e.getMessage(); - logger.warning(error); - throw new RestAPIException(0, error, e); + } + /** + * This method returns the last HTTP Result + * + * @return + */ + public int getLastHTTPResult() { + return iLastHTTPResult; } - } - - /** - * Reads the response from a http request. - * - * @param urlConnection - * @throws IOException - */ - private String readResponse(URLConnection urlConnection) throws IOException { - // get content of result - logger.finest("......readResponse...."); - StringWriter writer = new StringWriter(); - BufferedReader in = null; - try { - // test if content encoding is provided - String sContentEncoding = urlConnection.getContentEncoding(); - if (sContentEncoding == null || sContentEncoding.isEmpty()) { - // no so lets see if the client has defined an encoding.. - if (encoding != null && !encoding.isEmpty()) - sContentEncoding = encoding; - } - - // if an encoding is provided read stream with encoding..... - if (sContentEncoding != null && !sContentEncoding.isEmpty()) - in = new BufferedReader( - new InputStreamReader(urlConnection.getInputStream(), sContentEncoding)); - else - in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream())); - String inputLine; - while ((inputLine = in.readLine()) != null) { - logger.finest("......" + inputLine); - writer.write(inputLine); - } - } catch (IOException e) { - e.printStackTrace(); - } finally { - if (in != null) - in.close(); + + /** + * Gets the content of a GET request from a Rest Service URI Endpoint. I case of + * an error the method throws a RestAPIException. + * + * @param uri - Rest Endpoint RUI + * @return - content or null if no content is available. + */ + public String get(String uri) throws RestAPIException { + + setServiceEndpoint(uri); + try { + HttpURLConnection urlConnection = (HttpURLConnection) new URL(serviceEndpoint).openConnection(); + + // optional default is GET + urlConnection.setRequestMethod("GET"); + + urlConnection.setDoOutput(true); + urlConnection.setDoInput(true); + urlConnection.setAllowUserInteraction(false); + + if (requestProperties != null) { + for (Map.Entry entry : requestProperties.entrySet()) { + urlConnection.setRequestProperty(entry.getKey(), entry.getValue()); + } + } + + // process filters.... + for (RequestFilter filter : requestFilterList) { + filter.filter(urlConnection); + } + + iLastHTTPResult = urlConnection.getResponseCode(); + logger.finest("......Sending 'GET' request to URL : " + serviceEndpoint); + logger.finest("......Response Code : " + iLastHTTPResult); + // read response if response was successful + if (iLastHTTPResult >= 200 && iLastHTTPResult <= 299) { + return readResponse(urlConnection); + } else { + String error = "Error " + iLastHTTPResult + " - failed GET request from '" + uri + "'"; + logger.warning(error); + throw new RestAPIException(iLastHTTPResult, error); + } + } catch (IOException e) { + String error = "Error GET request from '" + uri + " - " + e.getMessage(); + logger.warning(error); + throw new RestAPIException(0, error, e); + + } } - return writer.toString(); + /** + * Reads the response from a http request. + * + * @param urlConnection + * @throws IOException + */ + private String readResponse(URLConnection urlConnection) throws IOException { + // get content of result + logger.finest("......readResponse...."); + StringWriter writer = new StringWriter(); + BufferedReader in = null; + try { + // test if content encoding is provided + String sContentEncoding = urlConnection.getContentEncoding(); + if (sContentEncoding == null || sContentEncoding.isEmpty()) { + // no so lets see if the client has defined an encoding.. + if (encoding != null && !encoding.isEmpty()) + sContentEncoding = encoding; + } + + // if an encoding is provided read stream with encoding..... + if (sContentEncoding != null && !sContentEncoding.isEmpty()) + in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream(), sContentEncoding)); + else + in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream())); + String inputLine; + while ((inputLine = in.readLine()) != null) { + logger.finest("......" + inputLine); + writer.write(inputLine); + } + } catch (IOException e) { + e.printStackTrace(); + } finally { + if (in != null) + in.close(); + } + + return writer.toString(); - } + } } diff --git a/imixs-workflow-core/src/main/java/org/imixs/workflow/util/Base64.java b/imixs-workflow-core/src/main/java/org/imixs/workflow/util/Base64.java index 4efaa4e31..dff1121d7 100644 --- a/imixs-workflow-core/src/main/java/org/imixs/workflow/util/Base64.java +++ b/imixs-workflow-core/src/main/java/org/imixs/workflow/util/Base64.java @@ -29,246 +29,248 @@ import java.io.*; // needed only for main() method. /** - * Provides encoding of raw bytes to base64-encoded characters, and decoding of base64 characters to - * raw bytes. + * Provides encoding of raw bytes to base64-encoded characters, and decoding of + * base64 characters to raw bytes. * * @author Kevin Kelley (kelley@ruralnet.net) - * @version 1.3 date 06 August 1998 modified 14 February 2000 modified 22 September 2000 + * @version 1.3 date 06 August 1998 modified 14 February 2000 modified 22 + * September 2000 */ public class Base64 { - /** - * returns an array of base64-encoded characters to represent the passed data array. - * - * @param data the array of bytes to encode - * @return base64-coded character array. - */ - static public char[] encode(byte[] data) { - char[] out = new char[((data.length + 2) / 3) * 4]; + /** + * returns an array of base64-encoded characters to represent the passed data + * array. + * + * @param data the array of bytes to encode + * @return base64-coded character array. + */ + static public char[] encode(byte[] data) { + char[] out = new char[((data.length + 2) / 3) * 4]; - // - // 3 bytes encode to 4 chars. Output is always an even - // multiple of 4 characters. - // - for (int i = 0, index = 0; i < data.length; i += 3, index += 4) { - boolean quad = false; - boolean trip = false; + // + // 3 bytes encode to 4 chars. Output is always an even + // multiple of 4 characters. + // + for (int i = 0, index = 0; i < data.length; i += 3, index += 4) { + boolean quad = false; + boolean trip = false; - int val = (0xFF & (int) data[i]); - val <<= 8; - if ((i + 1) < data.length) { - val |= (0xFF & (int) data[i + 1]); - trip = true; - } - val <<= 8; - if ((i + 2) < data.length) { - val |= (0xFF & (int) data[i + 2]); - quad = true; - } - out[index + 3] = alphabet[(quad ? (val & 0x3F) : 64)]; - val >>= 6; - out[index + 2] = alphabet[(trip ? (val & 0x3F) : 64)]; - val >>= 6; - out[index + 1] = alphabet[val & 0x3F]; - val >>= 6; - out[index + 0] = alphabet[val & 0x3F]; + int val = (0xFF & (int) data[i]); + val <<= 8; + if ((i + 1) < data.length) { + val |= (0xFF & (int) data[i + 1]); + trip = true; + } + val <<= 8; + if ((i + 2) < data.length) { + val |= (0xFF & (int) data[i + 2]); + quad = true; + } + out[index + 3] = alphabet[(quad ? (val & 0x3F) : 64)]; + val >>= 6; + out[index + 2] = alphabet[(trip ? (val & 0x3F) : 64)]; + val >>= 6; + out[index + 1] = alphabet[val & 0x3F]; + val >>= 6; + out[index + 0] = alphabet[val & 0x3F]; + } + return out; } - return out; - } - /** - * Decodes a BASE-64 encoded stream to recover the original data. White space before and after - * will be trimmed away, but no other manipulation of the input will be performed. - * - * As of version 1.2 this method will properly handle input containing junk characters (newlines - * and the like) rather than throwing an error. It does this by pre-parsing the input and - * generating from that a count of VALID input characters. - **/ - static public byte[] decode(char[] data) { - // as our input could contain non-BASE64 data (newlines, - // whitespace of any sort, whatever) we must first adjust - // our count of USABLE data so that... - // (a) we don't misallocate the output array, and - // (b) think that we miscalculated our data length - // just because of extraneous throw-away junk + /** + * Decodes a BASE-64 encoded stream to recover the original data. White space + * before and after will be trimmed away, but no other manipulation of the input + * will be performed. + * + * As of version 1.2 this method will properly handle input containing junk + * characters (newlines and the like) rather than throwing an error. It does + * this by pre-parsing the input and generating from that a count of VALID input + * characters. + **/ + static public byte[] decode(char[] data) { + // as our input could contain non-BASE64 data (newlines, + // whitespace of any sort, whatever) we must first adjust + // our count of USABLE data so that... + // (a) we don't misallocate the output array, and + // (b) think that we miscalculated our data length + // just because of extraneous throw-away junk - int tempLen = data.length; - for (int ix = 0; ix < data.length; ix++) { - if ((data[ix] > 255) || codes[data[ix]] < 0) - --tempLen; // ignore non-valid chars and padding - } - // calculate required length: - // -- 3 bytes for every 4 valid base64 chars - // -- plus 2 bytes if there are 3 extra base64 chars, - // or plus 1 byte if there are 2 extra. + int tempLen = data.length; + for (int ix = 0; ix < data.length; ix++) { + if ((data[ix] > 255) || codes[data[ix]] < 0) + --tempLen; // ignore non-valid chars and padding + } + // calculate required length: + // -- 3 bytes for every 4 valid base64 chars + // -- plus 2 bytes if there are 3 extra base64 chars, + // or plus 1 byte if there are 2 extra. - int len = (tempLen / 4) * 3; - if ((tempLen % 4) == 3) - len += 2; - if ((tempLen % 4) == 2) - len += 1; + int len = (tempLen / 4) * 3; + if ((tempLen % 4) == 3) + len += 2; + if ((tempLen % 4) == 2) + len += 1; - byte[] out = new byte[len]; + byte[] out = new byte[len]; - int shift = 0; // # of excess bits stored in accum - int accum = 0; // excess bits - int index = 0; + int shift = 0; // # of excess bits stored in accum + int accum = 0; // excess bits + int index = 0; - // we now go through the entire array (NOT using the 'tempLen' value) - for (int ix = 0; ix < data.length; ix++) { - int value = (data[ix] > 255) ? -1 : codes[data[ix]]; + // we now go through the entire array (NOT using the 'tempLen' value) + for (int ix = 0; ix < data.length; ix++) { + int value = (data[ix] > 255) ? -1 : codes[data[ix]]; - if (value >= 0) // skip over non-code - { - accum <<= 6; // bits shift up by 6 each time thru - shift += 6; // loop, with new bits being put in - accum |= value; // at the bottom. - if (shift >= 8) // whenever there are 8 or more shifted in, - { - shift -= 8; // write them out (from the top, leaving any - out[index++] = // excess at the bottom for next iteration. - (byte) ((accum >> shift) & 0xff); + if (value >= 0) // skip over non-code + { + accum <<= 6; // bits shift up by 6 each time thru + shift += 6; // loop, with new bits being put in + accum |= value; // at the bottom. + if (shift >= 8) // whenever there are 8 or more shifted in, + { + shift -= 8; // write them out (from the top, leaving any + out[index++] = // excess at the bottom for next iteration. + (byte) ((accum >> shift) & 0xff); + } + } + // we will also have skipped processing a padding null byte ('=') + // here; + // these are used ONLY for padding to an even length and do not + // legally + // occur as encoded data. for this reason we can ignore the fact + // that + // no index++ operation occurs in that special case: the out[] array + // is + // initialized to all-zero bytes to start with and that works to our + // advantage in this combination. } - } - // we will also have skipped processing a padding null byte ('=') - // here; - // these are used ONLY for padding to an even length and do not - // legally - // occur as encoded data. for this reason we can ignore the fact - // that - // no index++ operation occurs in that special case: the out[] array - // is - // initialized to all-zero bytes to start with and that works to our - // advantage in this combination. - } - // if there is STILL something wrong we just have to throw up now! - if (index != out.length) { - throw new Error( - "Miscalculated data length (wrote " + index + " instead of " + out.length + ")"); + // if there is STILL something wrong we just have to throw up now! + if (index != out.length) { + throw new Error("Miscalculated data length (wrote " + index + " instead of " + out.length + ")"); + } + + return out; } - return out; - } + // + // code characters for values 0..63 + // + static private char[] alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".toCharArray(); - // - // code characters for values 0..63 - // - static private char[] alphabet = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".toCharArray(); + // + // lookup table for converting base64 characters to value in range 0..63 + // + static private byte[] codes = new byte[256]; + static { + for (int i = 0; i < 256; i++) + codes[i] = -1; + for (int i = 'A'; i <= 'Z'; i++) + codes[i] = (byte) (i - 'A'); + for (int i = 'a'; i <= 'z'; i++) + codes[i] = (byte) (26 + i - 'a'); + for (int i = '0'; i <= '9'; i++) + codes[i] = (byte) (52 + i - '0'); + codes['+'] = 62; + codes['/'] = 63; + } - // - // lookup table for converting base64 characters to value in range 0..63 - // - static private byte[] codes = new byte[256]; - static { - for (int i = 0; i < 256; i++) - codes[i] = -1; - for (int i = 'A'; i <= 'Z'; i++) - codes[i] = (byte) (i - 'A'); - for (int i = 'a'; i <= 'z'; i++) - codes[i] = (byte) (26 + i - 'a'); - for (int i = '0'; i <= '9'; i++) - codes[i] = (byte) (52 + i - '0'); - codes['+'] = 62; - codes['/'] = 63; - } + // ///////////////////////////////////////////////// + // remainder (main method and helper functions) is + // for testing purposes only, feel free to clip it. + // ///////////////////////////////////////////////// - // ///////////////////////////////////////////////// - // remainder (main method and helper functions) is - // for testing purposes only, feel free to clip it. - // ///////////////////////////////////////////////// + public static void main(String[] args) { + boolean decode = false; - public static void main(String[] args) { - boolean decode = false; + if (args.length == 0) { + System.out.println("usage: java Base64 [-d[ecode]] filename"); + System.exit(0); + } + for (int i = 0; i < args.length; i++) { + if ("-decode".equalsIgnoreCase(args[i])) + decode = true; + else if ("-d".equalsIgnoreCase(args[i])) + decode = true; + } - if (args.length == 0) { - System.out.println("usage: java Base64 [-d[ecode]] filename"); - System.exit(0); - } - for (int i = 0; i < args.length; i++) { - if ("-decode".equalsIgnoreCase(args[i])) - decode = true; - else if ("-d".equalsIgnoreCase(args[i])) - decode = true; - } + String filename = args[args.length - 1]; + File file = new File(filename); + if (!file.exists()) { + System.out.println("Error: file '" + filename + "' doesn't exist!"); + System.exit(0); + } - String filename = args[args.length - 1]; - File file = new File(filename); - if (!file.exists()) { - System.out.println("Error: file '" + filename + "' doesn't exist!"); - System.exit(0); + if (decode) { + char[] encoded = readChars(file); + byte[] decoded = decode(encoded); + writeBytes(file, decoded); + } else { + byte[] decoded = readBytes(file); + char[] encoded = encode(decoded); + writeChars(file, encoded); + } } - if (decode) { - char[] encoded = readChars(file); - byte[] decoded = decode(encoded); - writeBytes(file, decoded); - } else { - byte[] decoded = readBytes(file); - char[] encoded = encode(decoded); - writeChars(file, encoded); - } - } + private static byte[] readBytes(File file) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try { + InputStream fis = new FileInputStream(file); + InputStream is = new BufferedInputStream(fis); + int count = 0; + byte[] buf = new byte[16384]; + while ((count = is.read(buf)) != -1) { + if (count > 0) + baos.write(buf, 0, count); + } + is.close(); + } catch (Exception e) { + e.printStackTrace(); + } - private static byte[] readBytes(File file) { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - try { - InputStream fis = new FileInputStream(file); - InputStream is = new BufferedInputStream(fis); - int count = 0; - byte[] buf = new byte[16384]; - while ((count = is.read(buf)) != -1) { - if (count > 0) - baos.write(buf, 0, count); - } - is.close(); - } catch (Exception e) { - e.printStackTrace(); + return baos.toByteArray(); } - return baos.toByteArray(); - } + private static char[] readChars(File file) { + CharArrayWriter caw = new CharArrayWriter(); + try { + Reader fr = new FileReader(file); + Reader in = new BufferedReader(fr); + int count = 0; + char[] buf = new char[16384]; + while ((count = in.read(buf)) != -1) { + if (count > 0) + caw.write(buf, 0, count); + } + in.close(); + } catch (Exception e) { + e.printStackTrace(); + } - private static char[] readChars(File file) { - CharArrayWriter caw = new CharArrayWriter(); - try { - Reader fr = new FileReader(file); - Reader in = new BufferedReader(fr); - int count = 0; - char[] buf = new char[16384]; - while ((count = in.read(buf)) != -1) { - if (count > 0) - caw.write(buf, 0, count); - } - in.close(); - } catch (Exception e) { - e.printStackTrace(); + return caw.toCharArray(); } - return caw.toCharArray(); - } - - private static void writeBytes(File file, byte[] data) { - try { - OutputStream fos = new FileOutputStream(file); - OutputStream os = new BufferedOutputStream(fos); - os.write(data); - os.close(); - } catch (Exception e) { - e.printStackTrace(); + private static void writeBytes(File file, byte[] data) { + try { + OutputStream fos = new FileOutputStream(file); + OutputStream os = new BufferedOutputStream(fos); + os.write(data); + os.close(); + } catch (Exception e) { + e.printStackTrace(); + } } - } - private static void writeChars(File file, char[] data) { - try { - Writer fos = new FileWriter(file); - Writer os = new BufferedWriter(fos); - os.write(data); - os.close(); - } catch (Exception e) { - e.printStackTrace(); + private static void writeChars(File file, char[] data) { + try { + Writer fos = new FileWriter(file); + Writer os = new BufferedWriter(fos); + os.write(data); + os.close(); + } catch (Exception e) { + e.printStackTrace(); + } } - } } diff --git a/imixs-workflow-core/src/main/java/org/imixs/workflow/util/ImixsJSONBuilder.java b/imixs-workflow-core/src/main/java/org/imixs/workflow/util/ImixsJSONBuilder.java index 8e554f706..db534411e 100644 --- a/imixs-workflow-core/src/main/java/org/imixs/workflow/util/ImixsJSONBuilder.java +++ b/imixs-workflow-core/src/main/java/org/imixs/workflow/util/ImixsJSONBuilder.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *

- *  Imixs Workflow 
+/*  
+ *  Imixs-Workflow 
+ *  
  *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
  *  http://www.imixs.com
  *  
@@ -22,10 +22,9 @@
  *      https://github.com/imixs/imixs-workflow
  *  
  *  Contributors:  
- *      Imixs Software Solutions GmbH - initial API and implementation
+ *      Imixs Software Solutions GmbH - Project Management
  *      Ralph Soika - Software Developer
- * 
- *******************************************************************************/ + */ package org.imixs.workflow.util; @@ -40,170 +39,172 @@ import org.imixs.workflow.ItemCollection; /** - * The ImixsJSONBuilder is an utility class to generate a typed json string from an Imixs - * ItemCollection. + * The ImixsJSONBuilder is an utility class to generate a typed json string from + * an Imixs ItemCollection. *

- * The result can be converted back into a ItemCollection by using the ImixsJSONParser class. + * The result can be converted back into a ItemCollection by using the + * ImixsJSONParser class. * * @See ImixsJSONParser * @author rsoika */ public class ImixsJSONBuilder { - public static final String ISO8601DATEFORMAT = "yyyy-MM-dd'T'HH:mm:ssZ"; - - /** - * This method builds a typed JSON output stream from a Imixs ItemCollection. - * - * Example Output: - * { - "item":[ - {"name":"$isauthor","value":{"@type":"xs:boolean","$":"true"}}, - {"name":"$readaccess","value":{"@type":"xs:string","$":"Anna"}}, - {"name":"txtmessage","value":{"@type":"xs:string","$":"worklist"}}, - {"name":"txtlog","value":[ - {"@type":"xs:string","$":"A"}, - {"@type":"xs:string","$":"B"}, - {"@type":"xs:string","$":"C"}] - }, - {"name":"$eventid","value":{"@type":"xs:int","$":"0"}} - ] - } - * - * - * @param worktiem - ItemCollection to be translated into JSON - * @return a JSON string - * @throws ParseException - * @throws UnsupportedEncodingException - */ - @SuppressWarnings("unchecked") - public final static String build(final ItemCollection workitem) - throws ParseException, UnsupportedEncodingException { - - StringBuffer out = new StringBuffer(); - out.append("{\"item\":["); - - // iterate over all items... - List itemNames = workitem.getItemNames(); - - for (int i = 0; i < itemNames.size(); i++) { - - String itemName = itemNames.get(i); - out.append("{\"name\":\"" + itemName + "\",\"value\":"); - List values = workitem.getItemValue(itemName); - buildValues(values, out); - - // add comma? - if (i < (itemNames.size() - 1)) { - out.append(","); - } - } - out.append("]}"); - - return out.toString(); - } - - /** - * This helper method converts a value list into a json string - *

- * In case values contains more than one item the values are ordered into a json array. - *

- * e.g. - *

- * - * {"name":"$isauthor","value":{"@type":"xs:boolean","$":true}}, - * {"name":"$readaccess","value":{"@type":"xs:string","$":"Anna"}}, - * {"name":"txtmessage","value":{"@type":"xs:string","$":"worklist"}}, - * {"name":"$activityid","value":{"@type":"xs:int","$":10}}, - * {"name":"$processid","value":{"@type":"xs:int","$":100}} - * - * - * @param token - * @throws ParseException - */ - private static void buildValues(List values, StringBuffer out) { - if (values == null || values.size() == 0) { - out.append("{}"); - return; - } - if (values.size() > 1) { - out.append("["); + public static final String ISO8601DATEFORMAT = "yyyy-MM-dd'T'HH:mm:ssZ"; + + /** + * This method builds a typed JSON output stream from a Imixs ItemCollection. + * + * Example Output: + * { + "item":[ + {"name":"$isauthor","value":{"@type":"xs:boolean","$":"true"}}, + {"name":"$readaccess","value":{"@type":"xs:string","$":"Anna"}}, + {"name":"txtmessage","value":{"@type":"xs:string","$":"worklist"}}, + {"name":"txtlog","value":[ + {"@type":"xs:string","$":"A"}, + {"@type":"xs:string","$":"B"}, + {"@type":"xs:string","$":"C"}] + }, + {"name":"$eventid","value":{"@type":"xs:int","$":"0"}} + ] + } + * + * + * @param worktiem - ItemCollection to be translated into JSON + * @return a JSON string + * @throws ParseException + * @throws UnsupportedEncodingException + */ + @SuppressWarnings("unchecked") + public final static String build(final ItemCollection workitem) + throws ParseException, UnsupportedEncodingException { + + StringBuffer out = new StringBuffer(); + out.append("{\"item\":["); + + // iterate over all items... + List itemNames = workitem.getItemNames(); + + for (int i = 0; i < itemNames.size(); i++) { + + String itemName = itemNames.get(i); + out.append("{\"name\":\"" + itemName + "\",\"value\":"); + List values = workitem.getItemValue(itemName); + buildValues(values, out); + + // add comma? + if (i < (itemNames.size() - 1)) { + out.append(","); + } + } + out.append("]}"); + + return out.toString(); } - // print each output... - for (int i = 0; i < values.size(); i++) { - out.append("{"); - // {"@type":"xs:string","$":"worklist"} - Object valueObject = values.get(i); - String type = ""; - // test raw types first - if (valueObject instanceof String) { - type = "\"@type\":\"xs:string\""; - } - if (valueObject instanceof Boolean) { - type = "\"@type\":\"xs:boolean\""; - } - if (valueObject instanceof Short) { - type = "\"@type\":\"xs:short\""; - } - if (valueObject instanceof Integer) { - type = "\"@type\":\"xs:int\""; - } - if (valueObject instanceof Long) { - type = "\"@type\":\"xs:long\""; - } - if (valueObject instanceof Float) { - type = "\"@type\":\"xs:floatt\""; - } - if (valueObject instanceof Double) { - type = "\"@type\":\"xs:double\""; - } - if (valueObject instanceof Date || valueObject instanceof Calendar) { - type = "\"@type\":\"xs:dateTime\""; - } - if (valueObject instanceof BigInteger) { - type = "\"@type\":\"xs:integer\""; - } - if (valueObject instanceof BigDecimal) { - type = "\"@type\":\"xs:decimal\""; - } - - out.append(type + ","); - - // print the value... - // "$":"worklist"} - String valueString = null; - if (valueObject instanceof Date || valueObject instanceof Calendar) { - // convert 2013-10-07T22:18:55.476+02:00 - Date date = null; - if (valueObject instanceof Calendar) { - date = ((Calendar) valueObject).getTime(); - } else { - date = (Date) valueObject; + /** + * This helper method converts a value list into a json string + *

+ * In case values contains more than one item the values are ordered into a json + * array. + *

+ * e.g. + *

+ * + * {"name":"$isauthor","value":{"@type":"xs:boolean","$":true}}, + * {"name":"$readaccess","value":{"@type":"xs:string","$":"Anna"}}, + * {"name":"txtmessage","value":{"@type":"xs:string","$":"worklist"}}, + * {"name":"$activityid","value":{"@type":"xs:int","$":10}}, + * {"name":"$processid","value":{"@type":"xs:int","$":100}} + * + * + * @param token + * @throws ParseException + */ + private static void buildValues(List values, StringBuffer out) { + if (values == null || values.size() == 0) { + out.append("{}"); + return; + } + if (values.size() > 1) { + out.append("["); } - SimpleDateFormat sdf = new SimpleDateFormat(ISO8601DATEFORMAT); - // sdf.setTimeZone(TimeZone.getTimeZone("CET")); - valueString = sdf.format(date); - } else { - // simple convert to string - valueString = valueObject.toString(); - } - - out.append("\"$\":\"" + valueString + "\""); - - out.append("}"); - // add comma? - if ((i) < (values.size() - 1)) { - out.append(","); - } - } - if (values.size() > 1) { - out.append("]"); + // print each output... + for (int i = 0; i < values.size(); i++) { + out.append("{"); + // {"@type":"xs:string","$":"worklist"} + Object valueObject = values.get(i); + String type = ""; + // test raw types first + if (valueObject instanceof String) { + type = "\"@type\":\"xs:string\""; + } + if (valueObject instanceof Boolean) { + type = "\"@type\":\"xs:boolean\""; + } + if (valueObject instanceof Short) { + type = "\"@type\":\"xs:short\""; + } + if (valueObject instanceof Integer) { + type = "\"@type\":\"xs:int\""; + } + if (valueObject instanceof Long) { + type = "\"@type\":\"xs:long\""; + } + if (valueObject instanceof Float) { + type = "\"@type\":\"xs:floatt\""; + } + if (valueObject instanceof Double) { + type = "\"@type\":\"xs:double\""; + } + if (valueObject instanceof Date || valueObject instanceof Calendar) { + type = "\"@type\":\"xs:dateTime\""; + } + if (valueObject instanceof BigInteger) { + type = "\"@type\":\"xs:integer\""; + } + if (valueObject instanceof BigDecimal) { + type = "\"@type\":\"xs:decimal\""; + } + + out.append(type + ","); + + // print the value... + // "$":"worklist"} + String valueString = null; + if (valueObject instanceof Date || valueObject instanceof Calendar) { + // convert 2013-10-07T22:18:55.476+02:00 + Date date = null; + if (valueObject instanceof Calendar) { + date = ((Calendar) valueObject).getTime(); + } else { + date = (Date) valueObject; + } + SimpleDateFormat sdf = new SimpleDateFormat(ISO8601DATEFORMAT); + // sdf.setTimeZone(TimeZone.getTimeZone("CET")); + valueString = sdf.format(date); + } else { + // simple convert to string + valueString = valueObject.toString(); + } + + out.append("\"$\":\"" + valueString + "\""); + + out.append("}"); + // add comma? + if ((i) < (values.size() - 1)) { + out.append(","); + } + } - } + if (values.size() > 1) { + out.append("]"); - out.append("}"); - } + } + + out.append("}"); + } } diff --git a/imixs-workflow-core/src/main/java/org/imixs/workflow/util/ImixsJSONParser.java b/imixs-workflow-core/src/main/java/org/imixs/workflow/util/ImixsJSONParser.java index 97e6a9b3c..82e0f5d62 100644 --- a/imixs-workflow-core/src/main/java/org/imixs/workflow/util/ImixsJSONParser.java +++ b/imixs-workflow-core/src/main/java/org/imixs/workflow/util/ImixsJSONParser.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *
- *  Imixs Workflow 
+/*  
+ *  Imixs-Workflow 
+ *  
  *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
  *  http://www.imixs.com
  *  
@@ -22,10 +22,9 @@
  *      https://github.com/imixs/imixs-workflow
  *  
  *  Contributors:  
- *      Imixs Software Solutions GmbH - initial API and implementation
+ *      Imixs Software Solutions GmbH - Project Management
  *      Ralph Soika - Software Developer
- * 
- *******************************************************************************/ + */ package org.imixs.workflow.util; @@ -42,8 +41,9 @@ import org.imixs.workflow.ItemCollection; /** - * The ImixsJSONParser is an utility class to parse JSON structures of Imixs Documents. The parser - * supports single document structures as also collections of documents (data element). + * The ImixsJSONParser is an utility class to parse JSON structures of Imixs + * Documents. The parser supports single document structures as also collections + * of documents (data element). *

* The method 'parse()' returns in any case a collection of ItemCollection. *

@@ -52,271 +52,272 @@ */ public class ImixsJSONParser { - public static final String DATA_ELEMENT = "data"; - public static final String ITEM_ELEMENT = "item"; - public static final String NAME_ELEMENT = "name"; - public static final String VALUE_ELEMENT = "value"; + public static final String DATA_ELEMENT = "data"; + public static final String ITEM_ELEMENT = "item"; + public static final String NAME_ELEMENT = "name"; + public static final String VALUE_ELEMENT = "value"; - /** - * This method parses an Imixs JSON input stream and returns a List of Imixs ItemCollection - * instances. - *

- * The method supports both - a single document (item element) or a collection of documents (data - * element). In both cases the method returns a collection of ItemCollections - * - * Example-1: - * { - "item":[ - {"name":"$isauthor","value":{"@type":"xs:boolean","$":"true"}}, - {"name":"$readaccess","value":{"@type":"xs:string","$":"Anna"}}, - {"name":"txtmessage","value":{"@type":"xs:string","$":"worklist"}}, - {"name":"txtlog","value":[ - {"@type":"xs:string","$":"A"}, - {"@type":"xs:string","$":"B"}, - {"@type":"xs:string","$":"C"}] - }, - {"name":"$activityid","value":{"@type":"xs:int","$":"0"}} - ] - } - * - * - * - * Example-2: - * { - * "data": [ - * { - "item":[ - {"name":"$isauthor","value":{"@type":"xs:boolean","$":"true"}}, - {"name":"$readaccess","value":{"@type":"xs:string","$":"Anna"}} - ] - }, - { - "item":[ - {"name":"$isauthor","value":{"@type":"xs:boolean","$":"true"}}, - {"name":"$readaccess","value":{"@type":"xs:string","$":"Anna"}} - ] - } - ] - } - * - * - * - * @param requestBodyStream - * @param encoding - default encoding use to parse the stream - * @return a workitem - * @throws ParseException - * @throws UnsupportedEncodingException - */ - public final static List parse(final InputStream jsonDataStream) - throws ParseException, UnsupportedEncodingException { - boolean isarray = false; - List result = null; + /** + * This method parses an Imixs JSON input stream and returns a List of Imixs + * ItemCollection instances. + *

+ * The method supports both - a single document (item element) or a collection + * of documents (data element). In both cases the method returns a collection of + * ItemCollections + * + * Example-1: + * { + "item":[ + {"name":"$isauthor","value":{"@type":"xs:boolean","$":"true"}}, + {"name":"$readaccess","value":{"@type":"xs:string","$":"Anna"}}, + {"name":"txtmessage","value":{"@type":"xs:string","$":"worklist"}}, + {"name":"txtlog","value":[ + {"@type":"xs:string","$":"A"}, + {"@type":"xs:string","$":"B"}, + {"@type":"xs:string","$":"C"}] + }, + {"name":"$activityid","value":{"@type":"xs:int","$":"0"}} + ] + } + * + * + * + * Example-2: + * { + * "data": [ + * { + "item":[ + {"name":"$isauthor","value":{"@type":"xs:boolean","$":"true"}}, + {"name":"$readaccess","value":{"@type":"xs:string","$":"Anna"}} + ] + }, + { + "item":[ + {"name":"$isauthor","value":{"@type":"xs:boolean","$":"true"}}, + {"name":"$readaccess","value":{"@type":"xs:string","$":"Anna"}} + ] + } + ] + } + * + * + * + * @param requestBodyStream + * @param encoding - default encoding use to parse the stream + * @return a workitem + * @throws ParseException + * @throws UnsupportedEncodingException + */ + public final static List parse(final InputStream jsonDataStream) + throws ParseException, UnsupportedEncodingException { + boolean isarray = false; + List result = null; - if (jsonDataStream == null) { - return null; - } + if (jsonDataStream == null) { + return null; + } - JsonParser parser = Json.createParser(jsonDataStream); - Event event = null; - while (true) { + JsonParser parser = Json.createParser(jsonDataStream); + Event event = null; + while (true) { - try { - event = parser.next(); // START_OBJECT - if (event == null) { - return null; - } + try { + event = parser.next(); // START_OBJECT + if (event == null) { + return null; + } - if (event.name().equals(Event.START_ARRAY.toString())) { - isarray = true; - continue; - } + if (event.name().equals(Event.START_ARRAY.toString())) { + isarray = true; + continue; + } - if (event.name().equals(Event.KEY_NAME.toString())) { - String jsonkey = parser.getString(); - // data element? - if (DATA_ELEMENT.equals(jsonkey)) { - if (result != null) { - // we do not expect a second data element! - JsonLocation location = parser.getLocation(); - throw new ParseException( - "Invalid JSON Data Structure - element 'data' not expected (line: " - + location.getLineNumber() + " column: " + location.getColumnNumber() + ")", - (int) location.getStreamOffset()); - } else { - // create result List - result = new ArrayList(); - } - } + if (event.name().equals(Event.KEY_NAME.toString())) { + String jsonkey = parser.getString(); + // data element? + if (DATA_ELEMENT.equals(jsonkey)) { + if (result != null) { + // we do not expect a second data element! + JsonLocation location = parser.getLocation(); + throw new ParseException( + "Invalid JSON Data Structure - element 'data' not expected (line: " + + location.getLineNumber() + " column: " + location.getColumnNumber() + ")", + (int) location.getStreamOffset()); + } else { + // create result List + result = new ArrayList(); + } + } - // item element? - if (ITEM_ELEMENT.equals(jsonkey)) { - ItemCollection document = new ItemCollection(); - parseDocument(parser, document); - // late init of collection? - if (result == null) { - result = new ArrayList(); - } - result.add(document); - } - } + // item element? + if (ITEM_ELEMENT.equals(jsonkey)) { + ItemCollection document = new ItemCollection(); + parseDocument(parser, document); + // late init of collection? + if (result == null) { + result = new ArrayList(); + } + result.add(document); + } + } - if (isarray && event.name().equals(Event.END_ARRAY.toString())) { - break; - } - if (!isarray && event.name().equals(Event.END_OBJECT.toString())) { - break; + if (isarray && event.name().equals(Event.END_ARRAY.toString())) { + break; + } + if (!isarray && event.name().equals(Event.END_OBJECT.toString())) { + break; + } + + } catch (NoSuchElementException e) { + return null; + } } - } catch (NoSuchElementException e) { - return null; - } + return result; } - return result; - } - - /** - * parsing an item array (document) fragment: - "item":[ - {"name":"$isauthor","value":{"@type":"xs:boolean","$":"true"}}, - {"name":"$readaccess","value":{"@type":"xs:string","$":"Anna"}} - ] - * - **/ - private static void parseDocument(JsonParser parser, ItemCollection document) { - while (true) { - try { - Event event = parser.next(); // START_OBJECT - if (event.name().equals(Event.START_OBJECT.toString())) { - parseItem(parser, document); - } - // if end of object or array we can break here - if (event.name().equals(Event.END_OBJECT.toString()) - || event.name().equals(Event.END_ARRAY.toString())) { - break; + /** + * parsing an item array (document) fragment: + "item":[ + {"name":"$isauthor","value":{"@type":"xs:boolean","$":"true"}}, + {"name":"$readaccess","value":{"@type":"xs:string","$":"Anna"}} + ] + * + **/ + private static void parseDocument(JsonParser parser, ItemCollection document) { + while (true) { + try { + Event event = parser.next(); // START_OBJECT + if (event.name().equals(Event.START_OBJECT.toString())) { + parseItem(parser, document); + } + // if end of object or array we can break here + if (event.name().equals(Event.END_OBJECT.toString()) + || event.name().equals(Event.END_ARRAY.toString())) { + break; + } + } catch (NoSuchElementException e) { + break; + } } - } catch (NoSuchElementException e) { - break; - } } - } - /** - * parsing an item object fragment: - {"name":"$isauthor","value":{"@type":"xs:boolean","$":"true"}}, - * - **/ - private static void parseItem(JsonParser parser, ItemCollection document) { - Object itemValue = null; - String itemName = null; - boolean isItem = true; - while (isItem) { - try { - Event event = parser.next(); // START_OBJECT - if (event.name().equals(Event.KEY_NAME.toString())) { - String jsonkey = parser.getString(); - // data element? - if (NAME_ELEMENT.equals(jsonkey)) { - parser.next(); - itemName = parser.getString(); - continue; - } - if (VALUE_ELEMENT.equals(jsonkey)) { - // parser.next(); - itemValue = parseValue(parser, document); - continue; - } - } - // END of Object? - if (event.name().equals(Event.END_OBJECT.toString())) { - isItem = false; - // add item...? - if (itemName != null && itemValue != null) { - document.setItemValue(itemName, itemValue); - } - break; + /** + * parsing an item object fragment: + {"name":"$isauthor","value":{"@type":"xs:boolean","$":"true"}}, + * + **/ + private static void parseItem(JsonParser parser, ItemCollection document) { + Object itemValue = null; + String itemName = null; + boolean isItem = true; + while (isItem) { + try { + Event event = parser.next(); // START_OBJECT + if (event.name().equals(Event.KEY_NAME.toString())) { + String jsonkey = parser.getString(); + // data element? + if (NAME_ELEMENT.equals(jsonkey)) { + parser.next(); + itemName = parser.getString(); + continue; + } + if (VALUE_ELEMENT.equals(jsonkey)) { + // parser.next(); + itemValue = parseValue(parser, document); + continue; + } + } + // END of Object? + if (event.name().equals(Event.END_OBJECT.toString())) { + isItem = false; + // add item...? + if (itemName != null && itemValue != null) { + document.setItemValue(itemName, itemValue); + } + break; + } + } catch (NoSuchElementException e) { + break; + } } - } catch (NoSuchElementException e) { - break; - } } - } - /** - * parsing an item value fragment: - "value":{"@type":"xs:boolean","$":"true"}, - * - **/ - private static List parseValue(JsonParser parser, ItemCollection document) { - String type = null; - String stringValue = null; - Object value = null; - Boolean isarray = false; - List valueList = new ArrayList(); - while (true) { - try { - Event event = parser.next(); // START_OBJECT - if (event.name().equals(Event.START_ARRAY.toString())) { - isarray = true; - continue; - } + /** + * parsing an item value fragment: + "value":{"@type":"xs:boolean","$":"true"}, + * + **/ + private static List parseValue(JsonParser parser, ItemCollection document) { + String type = null; + String stringValue = null; + Object value = null; + Boolean isarray = false; + List valueList = new ArrayList(); + while (true) { + try { + Event event = parser.next(); // START_OBJECT + if (event.name().equals(Event.START_ARRAY.toString())) { + isarray = true; + continue; + } - if (event.name().equals(Event.KEY_NAME.toString())) { - String jsonkey = parser.getString(); - // data element? - if ("@type".equals(jsonkey)) { - parser.next(); - type = parser.getString(); - continue; - } - if ("$".equals(jsonkey)) { - parser.next(); - stringValue = parser.getString(); - continue; - } - } + if (event.name().equals(Event.KEY_NAME.toString())) { + String jsonkey = parser.getString(); + // data element? + if ("@type".equals(jsonkey)) { + parser.next(); + type = parser.getString(); + continue; + } + if ("$".equals(jsonkey)) { + parser.next(); + stringValue = parser.getString(); + continue; + } + } - // END of Object? - if (event.name().equals(Event.END_OBJECT.toString())) { + // END of Object? + if (event.name().equals(Event.END_OBJECT.toString())) { - // convert value to Object Type - if ("xs:boolean".equalsIgnoreCase(type)) { - value = Boolean.parseBoolean(stringValue); - } - if ("xs:integer".equalsIgnoreCase(type) || "xs:int".equalsIgnoreCase(type)) { - value = Integer.parseInt(stringValue); - } - if ("xs:long".equalsIgnoreCase(type)) { - value = Long.parseLong(stringValue); - } - if ("xs:float".equalsIgnoreCase(type)) { - value = new Float(stringValue); - } - if ("xs:double".equalsIgnoreCase(type)) { - value = new Double(stringValue); - } - // default to string - if (value == null) { - value = stringValue; - } + // convert value to Object Type + if ("xs:boolean".equalsIgnoreCase(type)) { + value = Boolean.parseBoolean(stringValue); + } + if ("xs:integer".equalsIgnoreCase(type) || "xs:int".equalsIgnoreCase(type)) { + value = Integer.parseInt(stringValue); + } + if ("xs:long".equalsIgnoreCase(type)) { + value = Long.parseLong(stringValue); + } + if ("xs:float".equalsIgnoreCase(type)) { + value = new Float(stringValue); + } + if ("xs:double".equalsIgnoreCase(type)) { + value = new Double(stringValue); + } + // default to string + if (value == null) { + value = stringValue; + } - valueList.add(value); - value = null; - if (!isarray) { - return valueList; - } - // return value; - } + valueList.add(value); + value = null; + if (!isarray) { + return valueList; + } + // return value; + } - if (event.name().equals(Event.END_ARRAY.toString())) { - return valueList; - } + if (event.name().equals(Event.END_ARRAY.toString())) { + return valueList; + } - } catch (NoSuchElementException e) { - break; - } + } catch (NoSuchElementException e) { + break; + } + } + return null; } - return null; - } } diff --git a/imixs-workflow-core/src/main/java/org/imixs/workflow/util/JSONParser.java b/imixs-workflow-core/src/main/java/org/imixs/workflow/util/JSONParser.java index adbc34bd4..6cd6b4031 100644 --- a/imixs-workflow-core/src/main/java/org/imixs/workflow/util/JSONParser.java +++ b/imixs-workflow-core/src/main/java/org/imixs/workflow/util/JSONParser.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *
- *  Imixs Workflow 
+/*  
+ *  Imixs-Workflow 
+ *  
  *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
  *  http://www.imixs.com
  *  
@@ -22,10 +22,9 @@
  *      https://github.com/imixs/imixs-workflow
  *  
  *  Contributors:  
- *      Imixs Software Solutions GmbH - initial API and implementation
+ *      Imixs Software Solutions GmbH - Project Management
  *      Ralph Soika - Software Developer
- * 
- *******************************************************************************/ + */ package org.imixs.workflow.util; @@ -46,390 +45,389 @@ import org.imixs.workflow.ItemCollection; /** - * The JSONParser is an utility class to parse JSON structures. The parser provides methods to - * transfer a Imixs JSON structure into a Imixs ItemCollection as well as helper methods to extract - * single values from a JSON structure. + * The JSONParser is an utility class to parse JSON structures. The parser + * provides methods to transfer a Imixs JSON structure into a Imixs + * ItemCollection as well as helper methods to extract single values from a JSON + * structure. *

- * The method parseWorkitem translates a JSON structure containing a Imixs Document into a - * ItemCollection. + * The method parseWorkitem translates a JSON structure containing a Imixs + * Document into a ItemCollection. * * @author rsoika */ public class JSONParser { - private static Logger logger = Logger.getLogger(JSONParser.class.getName()); - - /** - * This method extracts a single key from a JSON structure. It does not matter where the key is - * defined within the JSON structure. The method simply returns the first match. - *

- * It is also possible to get a JSON object or an JSON array embedded in the given JSON structure. - * This object can be parsed again with this method. - * - * @param key - * @param json - * @return - the json value or the json object for the corresponding json key - */ - public static String getKey(String key, String json) { - if (json == null || json.isEmpty()) { - return null; - } - String result = null; - // now extract the key - JsonParser parser = Json.createParser(new StringReader(json)); - // {"key":"b38b84614af36f874ba4f08dd4ea40c4e66e0607"} - - Event event = null; - while (true) { - - try { - event = parser.next(); // START_OBJECT - if (event == null) { - return null; + private static Logger logger = Logger.getLogger(JSONParser.class.getName()); + + /** + * This method extracts a single key from a JSON structure. It does not matter + * where the key is defined within the JSON structure. The method simply returns + * the first match. + *

+ * It is also possible to get a JSON object or an JSON array embedded in the + * given JSON structure. This object can be parsed again with this method. + * + * @param key + * @param json + * @return - the json value or the json object for the corresponding json key + */ + public static String getKey(String key, String json) { + if (json == null || json.isEmpty()) { + return null; } - if (event.name().equals(Event.KEY_NAME.toString())) { - String jsonkey = parser.getString(); - if (key.equals(jsonkey)) { - event = parser.next(); // value - if (event.name().equals(Event.VALUE_STRING.toString())) { - result = parser.getString(); - break; - } - if (event.name().equals(Event.VALUE_NUMBER.toString())) { - result = parser.getBigDecimal() + ""; - break; - } - if (event.name().equals(Event.VALUE_TRUE.toString())) { - result = "true"; - break; - } - if (event.name().equals(Event.VALUE_FALSE.toString())) { - result = "false"; - break; - } - if (event.name().equals(Event.VALUE_NULL.toString())) { - result = null; - break; + String result = null; + // now extract the key + JsonParser parser = Json.createParser(new StringReader(json)); + // {"key":"b38b84614af36f874ba4f08dd4ea40c4e66e0607"} + + Event event = null; + while (true) { + + try { + event = parser.next(); // START_OBJECT + if (event == null) { + return null; + } + if (event.name().equals(Event.KEY_NAME.toString())) { + String jsonkey = parser.getString(); + if (key.equals(jsonkey)) { + event = parser.next(); // value + if (event.name().equals(Event.VALUE_STRING.toString())) { + result = parser.getString(); + break; + } + if (event.name().equals(Event.VALUE_NUMBER.toString())) { + result = parser.getBigDecimal() + ""; + break; + } + if (event.name().equals(Event.VALUE_TRUE.toString())) { + result = "true"; + break; + } + if (event.name().equals(Event.VALUE_FALSE.toString())) { + result = "false"; + break; + } + if (event.name().equals(Event.VALUE_NULL.toString())) { + result = null; + break; + } + if (event.name().equals(Event.START_OBJECT.toString())) { + // just return the next json object here + result = parser.getObject().toString(); + break; + } + if (event.name().equals(Event.START_ARRAY.toString())) { + // just return the next json object here + result = parser.getArray().toString(); + break; + } + } + } + } catch (NoSuchElementException e) { + return null; } - if (event.name().equals(Event.START_OBJECT.toString())) { - // just return the next json object here - result = parser.getObject().toString(); - break; - } - if (event.name().equals(Event.START_ARRAY.toString())) { - // just return the next json object here - result = parser.getArray().toString(); - break; - } - } } - } catch (NoSuchElementException e) { - return null; - } - } - return result; - } - - /** - * This method parses an Imixs JSON input stream and returns a Imixs ItemCollection. - * - * Example: - * { - "item":[ - {"name":"$isauthor","value":{"@type":"xs:boolean","$":"true"}}, - {"name":"$readaccess","value":{"@type":"xs:string","$":"Anna"}}, - {"name":"txtmessage","value":{"@type":"xs:string","$":"worklist"}}, - {"name":"txtlog","value":[ - {"@type":"xs:string","$":"A"}, - {"@type":"xs:string","$":"B"}, - {"@type":"xs:string","$":"C"}] - }, - {"name":"$activityid","value":{"@type":"xs:int","$":"0"}} - ] - } - * - * - * @param requestBodyStream - * @param encoding - default encoding use to parse the stream - * @return a workitem - * @throws ParseException - * @throws UnsupportedEncodingException - */ - @Deprecated - public final static ItemCollection parseWorkitem(final InputStream requestBodyStream, - final String _encoding) throws ParseException, UnsupportedEncodingException { - boolean debug = logger.isLoggable(Level.FINE); - String encoding = _encoding; - - if (requestBodyStream == null) { - logger.severe("parseWorkitem - inputStream is null!"); - throw new ParseException("inputStream is null", -1); + return result; } - // default encoding? - if (encoding == null || encoding.isEmpty()) { - if (debug) { - logger.finest("......parseWorkitem - switch to default encoding 'UTF-8'"); - } - encoding = "UTF-8"; - } - - // Vector vMultiValueFieldNames = new Vector(); - BufferedReader in = new BufferedReader(new InputStreamReader(requestBodyStream, encoding)); - - String inputLine; - ItemCollection workitem = new ItemCollection(); - - String content = null; - String token = null; - String name = null; - StringBuffer stringBuffer = new StringBuffer(); - int iPos = -1; - int iStart = -1; - int iEnd = -1; - try { - // first we concat all lines - while ((inputLine = in.readLine()) != null) { - stringBuffer.append(inputLine); - if (debug) { - logger.finest("......parseWorkitem - read line:" + inputLine + ""); + /** + * This method parses an Imixs JSON input stream and returns a Imixs + * ItemCollection. + * + * Example: + * { + "item":[ + {"name":"$isauthor","value":{"@type":"xs:boolean","$":"true"}}, + {"name":"$readaccess","value":{"@type":"xs:string","$":"Anna"}}, + {"name":"txtmessage","value":{"@type":"xs:string","$":"worklist"}}, + {"name":"txtlog","value":[ + {"@type":"xs:string","$":"A"}, + {"@type":"xs:string","$":"B"}, + {"@type":"xs:string","$":"C"}] + }, + {"name":"$activityid","value":{"@type":"xs:int","$":"0"}} + ] + } + * + * + * @param requestBodyStream + * @param encoding - default encoding use to parse the stream + * @return a workitem + * @throws ParseException + * @throws UnsupportedEncodingException + */ + @Deprecated + public final static ItemCollection parseWorkitem(final InputStream requestBodyStream, final String _encoding) + throws ParseException, UnsupportedEncodingException { + boolean debug = logger.isLoggable(Level.FINE); + String encoding = _encoding; + + if (requestBodyStream == null) { + logger.severe("parseWorkitem - inputStream is null!"); + throw new ParseException("inputStream is null", -1); } - } - content = stringBuffer.toString(); - - // find start ...."item":[... - content = content.substring(content.indexOf('[') + 0); - if (debug) { - logger.finest("......parseWorkitem - start parsing..."); - } - while (content != null) { - - // find name => "name" : "$isauthor" , - iPos = content.indexOf(':'); - content = content.substring(iPos); - - token = content.substring(0, content.indexOf(',')); - iStart = token.indexOf('"') + 1; - iEnd = token.lastIndexOf('"'); - if (iEnd < iStart) - throw new java.text.ParseException("Unexpected position of '}", iEnd); - - name = token.substring(iStart, iEnd); - - content = content.substring(token.length()); - if (!isValueArray(content)) { - // now find the value token => - // "value":{"@type":"xs:boolean","$":"true"}}, - iStart = findNextChar(content, '{') + 1; - iEnd = findNextChar(content, '}'); - if (iEnd < iStart) - throw new java.text.ParseException("Unexpected position of '}", iEnd); - token = content.substring(iStart, iEnd); - content = content.substring(iEnd + 1); - storeValue(name, token, workitem); - } else { - // get content of array - iStart = findNextChar(content, '[') + 1; - iEnd = findNextChar(content, ']'); - if (iEnd < iStart) - throw new java.text.ParseException("Unexpected position of '}", iEnd); - - String arrayContent = content.substring(iStart, iEnd); - content = content.substring(iEnd + 1); - // parse array values.... - while (arrayContent != null) { - // now find the value token => - // "value":{"@type":"xs:boolean","$":"true"}}, - iStart = findNextChar(arrayContent, '{') + 1; - iEnd = findNextChar(arrayContent, '}'); - if (iEnd < iStart) - throw new java.text.ParseException("Unexpected position of '}", iEnd); - token = arrayContent.substring(iStart, iEnd); - arrayContent = arrayContent.substring(iEnd + 1); - storeValue(name, token, workitem); + // default encoding? + if (encoding == null || encoding.isEmpty()) { + if (debug) { + logger.finest("......parseWorkitem - switch to default encoding 'UTF-8'"); + } + encoding = "UTF-8"; + } - if (!arrayContent.contains("{")) - break; - } + // Vector vMultiValueFieldNames = new Vector(); + BufferedReader in = new BufferedReader(new InputStreamReader(requestBodyStream, encoding)); + + String inputLine; + ItemCollection workitem = new ItemCollection(); + + String content = null; + String token = null; + String name = null; + StringBuffer stringBuffer = new StringBuffer(); + int iPos = -1; + int iStart = -1; + int iEnd = -1; + try { + // first we concat all lines + while ((inputLine = in.readLine()) != null) { + stringBuffer.append(inputLine); + if (debug) { + logger.finest("......parseWorkitem - read line:" + inputLine + ""); + } + } + content = stringBuffer.toString(); - } + // find start ...."item":[... + content = content.substring(content.indexOf('[') + 0); + if (debug) { + logger.finest("......parseWorkitem - start parsing..."); + } + while (content != null) { + + // find name => "name" : "$isauthor" , + iPos = content.indexOf(':'); + content = content.substring(iPos); + + token = content.substring(0, content.indexOf(',')); + iStart = token.indexOf('"') + 1; + iEnd = token.lastIndexOf('"'); + if (iEnd < iStart) + throw new java.text.ParseException("Unexpected position of '}", iEnd); + + name = token.substring(iStart, iEnd); + + content = content.substring(token.length()); + if (!isValueArray(content)) { + // now find the value token => + // "value":{"@type":"xs:boolean","$":"true"}}, + iStart = findNextChar(content, '{') + 1; + iEnd = findNextChar(content, '}'); + if (iEnd < iStart) + throw new java.text.ParseException("Unexpected position of '}", iEnd); + token = content.substring(iStart, iEnd); + content = content.substring(iEnd + 1); + storeValue(name, token, workitem); + } else { + // get content of array + iStart = findNextChar(content, '[') + 1; + iEnd = findNextChar(content, ']'); + if (iEnd < iStart) + throw new java.text.ParseException("Unexpected position of '}", iEnd); + + String arrayContent = content.substring(iStart, iEnd); + content = content.substring(iEnd + 1); + // parse array values.... + while (arrayContent != null) { + // now find the value token => + // "value":{"@type":"xs:boolean","$":"true"}}, + iStart = findNextChar(arrayContent, '{') + 1; + iEnd = findNextChar(arrayContent, '}'); + if (iEnd < iStart) + throw new java.text.ParseException("Unexpected position of '}", iEnd); + + token = arrayContent.substring(iStart, iEnd); + arrayContent = arrayContent.substring(iEnd + 1); + storeValue(name, token, workitem); + + if (!arrayContent.contains("{")) + break; + } + + } + + if (!content.contains("{")) + break; - if (!content.contains("{")) - break; - - } - } catch (IOException e1) { - // logger.severe("Unable to parse workitem data!"); - e1.printStackTrace(); - return null; - } finally { - try { - in.close(); - } catch (IOException e) { - e.printStackTrace(); - } + } + } catch (IOException e1) { + // logger.severe("Unable to parse workitem data!"); + e1.printStackTrace(); + return null; + } finally { + try { + in.close(); + } catch (IOException e) { + e.printStackTrace(); + } - } + } - return workitem; - } - - /** - * This helper method extracts the type and value of a token and stores the value into the - * workitem - * - * e.g. - * - * {"name":"$isauthor","value":{"@type":"xs:boolean","$":true}}, - * {"name":"$readaccess","value":{"@type":"xs:string","$":"Anna"}}, - * {"name":"txtmessage","value":{"@type":"xs:string","$":"worklist"}}, - * {"name":"$activityid","value":{"@type":"xs:int","$":10}}, - * {"name":"XXXX","value":{"$":10,"@type":"xs:int"}}, - * {"name":"$processid","value":{"@type":"xs:int","$":100}} - * - * @param token - * @throws ParseException - */ - @SuppressWarnings({"rawtypes", "unchecked"}) - private static void storeValue(String name, String token, ItemCollection workitem) - throws ParseException { - boolean debug = logger.isLoggable(Level.FINE); - int iPos, iStart, iEnd; - Object value; - String type = null; - - // check if "@type" exists - iPos = token.indexOf("\"@type\""); - if (iPos > -1) { - iStart = token.indexOf('"', iPos + "\"@type\"".length() + 1) + 1; - iEnd = token.indexOf('"', iStart); - if (iEnd < iStart) - throw new ParseException("Unexpected position of '}", iEnd); - - type = token.substring(iStart, iEnd); - // token = token.substring(iEnd + 1); + return workitem; } + /** + * This helper method extracts the type and value of a token and stores the + * value into the workitem + * + * e.g. + * + * {"name":"$isauthor","value":{"@type":"xs:boolean","$":true}}, + * {"name":"$readaccess","value":{"@type":"xs:string","$":"Anna"}}, + * {"name":"txtmessage","value":{"@type":"xs:string","$":"worklist"}}, + * {"name":"$activityid","value":{"@type":"xs:int","$":10}}, + * {"name":"XXXX","value":{"$":10,"@type":"xs:int"}}, + * {"name":"$processid","value":{"@type":"xs:int","$":100}} + * + * @param token + * @throws ParseException + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + private static void storeValue(String name, String token, ItemCollection workitem) throws ParseException { + boolean debug = logger.isLoggable(Level.FINE); + int iPos, iStart, iEnd; + Object value; + String type = null; + + // check if "@type" exists + iPos = token.indexOf("\"@type\""); + if (iPos > -1) { + iStart = token.indexOf('"', iPos + "\"@type\"".length() + 1) + 1; + iEnd = token.indexOf('"', iStart); + if (iEnd < iStart) + throw new ParseException("Unexpected position of '}", iEnd); + type = token.substring(iStart, iEnd); + // token = token.substring(iEnd + 1); + } - // check if "$" exists - String stringValue = null; - iPos = token.indexOf("\"$\""); - if (iPos > -1) { - iStart = token.indexOf('"', iPos + "\"$\"".length() + 1) + 0; + // check if "$" exists + String stringValue = null; + iPos = token.indexOf("\"$\""); + if (iPos > -1) { + iStart = token.indexOf('"', iPos + "\"$\"".length() + 1) + 0; + + // check for , + int commaPos = token.indexOf(',', iStart); + if (commaPos > -1) { + stringValue = token.substring(iStart, commaPos); + } else { + stringValue = token.substring(iStart); + } + } - // check for , - int commaPos = token.indexOf(',', iStart); - if (commaPos > -1) { - stringValue = token.substring(iStart, commaPos); - } else { - stringValue = token.substring(iStart); - } - } + // remove " from string value + if (stringValue.startsWith("\"")) { + stringValue = stringValue.substring(1); + } + if (stringValue.endsWith("\"")) { + stringValue = stringValue.substring(0, stringValue.length() - 1); + } + value = stringValue; - // remove " from string value - if (stringValue.startsWith("\"")) { - stringValue = stringValue.substring(1); - } - if (stringValue.endsWith("\"")) { - stringValue = stringValue.substring(0, stringValue.length() - 1); - } + // convert value to Object Type + if ("xs:boolean".equalsIgnoreCase(type)) { + value = Boolean.parseBoolean(stringValue); + if (debug) { + logger.finest("......storeValue - datatype=xs:boolean"); + } + } + if ("xs:integer".equalsIgnoreCase(type) || "xs:int".equalsIgnoreCase(type)) { + value = Integer.parseInt(stringValue); + if (debug) { + logger.finest("......storeValue - datatype=xs:integer"); + } + } + if ("xs:long".equalsIgnoreCase(type)) { + value = Long.parseLong(stringValue); + if (debug) { + logger.finest("......storeValue - datatype=xs:long"); + } + } + if ("xs:float".equalsIgnoreCase(type)) { + value = new Float(stringValue); + if (debug) { + logger.finest("......storeValue - datatype=xs:float"); + } + } + if ("xs:double".equalsIgnoreCase(type)) { + value = new Double(stringValue); + if (debug) { + logger.finest("......storeValue - datatype=xs:double"); + } + } - value = stringValue; + // store value + if (!workitem.hasItem(name)) { + // frist value + workitem.replaceItemValue(name, value); + if (debug) { + logger.finest("......storeValue: '" + name + "' = '" + value + "'"); + } + } else { + // add value + List valueList = workitem.getItemValue(name); + valueList.add(value); + workitem.replaceItemValue(name, valueList); + if (debug) { + logger.finest("......store multivalue: '" + name + "' = '" + value + "'"); + } + } - // convert value to Object Type - if ("xs:boolean".equalsIgnoreCase(type)) { - value = Boolean.parseBoolean(stringValue); - if (debug) { - logger.finest("......storeValue - datatype=xs:boolean"); - } - } - if ("xs:integer".equalsIgnoreCase(type) || "xs:int".equalsIgnoreCase(type)) { - value = Integer.parseInt(stringValue); - if (debug) { - logger.finest("......storeValue - datatype=xs:integer"); - } - } - if ("xs:long".equalsIgnoreCase(type)) { - value = Long.parseLong(stringValue); - if (debug) { - logger.finest("......storeValue - datatype=xs:long"); - } - } - if ("xs:float".equalsIgnoreCase(type)) { - value = new Float(stringValue); - if (debug) { - logger.finest("......storeValue - datatype=xs:float"); - } - } - if ("xs:double".equalsIgnoreCase(type)) { - value = new Double(stringValue); - if (debug) { - logger.finest("......storeValue - datatype=xs:double"); - } } - // store value - if (!workitem.hasItem(name)) { - // frist value - workitem.replaceItemValue(name, value); - if (debug) { - logger.finest("......storeValue: '" + name + "' = '" + value + "'"); - } - } else { - // add value - List valueList = workitem.getItemValue(name); - valueList.add(value); - workitem.replaceItemValue(name, valueList); - if (debug) { - logger.finest("......store multivalue: '" + name + "' = '" + value + "'"); - } + /** + * Checks if the value is an array of values + * + * ,"value":[ {"@type":"xs:string","$":"A"}, + * + * @param token + * @return + */ + private static boolean isValueArray(String token) { + int b1 = findNextChar(token, '['); + int b2 = findNextChar(token, '{'); + if (b1 > -1 && b1 < b2) + return true; + else + return false; } - } - - /** - * Checks if the value is an array of values - * - * ,"value":[ {"@type":"xs:string","$":"A"}, - * - * @param token - * @return - */ - private static boolean isValueArray(String token) { - int b1 = findNextChar(token, '['); - int b2 = findNextChar(token, '{'); - if (b1 > -1 && b1 < b2) - return true; - else - return false; - } - - /** - * This method finds the next position of a char. The method scips excapte characters like '\"' or - * '\[' - * - * @param token - * @param c - * @return - */ - private static int findNextChar(String token, char c) { - int iPos = token.indexOf(c); - - if (iPos <= 0) - return iPos; - - // check if the char before is a \ - while ((token.charAt(iPos - 1)) == '\\') { - iPos = token.indexOf(c, iPos + 2); - if (iPos == -1) - break; - } + /** + * This method finds the next position of a char. The method scips excapte + * characters like '\"' or '\[' + * + * @param token + * @param c + * @return + */ + private static int findNextChar(String token, char c) { + int iPos = token.indexOf(c); + + if (iPos <= 0) + return iPos; + + // check if the char before is a \ + while ((token.charAt(iPos - 1)) == '\\') { + iPos = token.indexOf(c, iPos + 2); + if (iPos == -1) + break; + } - return iPos; + return iPos; - } + } } diff --git a/imixs-workflow-core/src/main/java/org/imixs/workflow/util/XMLParser.java b/imixs-workflow-core/src/main/java/org/imixs/workflow/util/XMLParser.java index 6200db884..7d9cb12ee 100644 --- a/imixs-workflow-core/src/main/java/org/imixs/workflow/util/XMLParser.java +++ b/imixs-workflow-core/src/main/java/org/imixs/workflow/util/XMLParser.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *

- *  Imixs Workflow 
+/*  
+ *  Imixs-Workflow 
+ *  
  *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
  *  http://www.imixs.com
  *  
@@ -22,10 +22,9 @@
  *      https://github.com/imixs/imixs-workflow
  *  
  *  Contributors:  
- *      Imixs Software Solutions GmbH - initial API and implementation
+ *      Imixs Software Solutions GmbH - Project Management
  *      Ralph Soika - Software Developer
- * 
- *******************************************************************************/ + */ package org.imixs.workflow.util; @@ -70,321 +69,324 @@ */ public class XMLParser { - private static Logger logger = Logger.getLogger(XMLParser.class.getName()); + private static Logger logger = Logger.getLogger(XMLParser.class.getName()); - /** - * This method parses a xml tag for attributes. The method returns a Map with all attributes found - * in the content string - * - * e.g. - * - * returns Map: {field=a, number=1} - * - * @param content - * @return - */ - public static Map findAttributes(String content) { - Map result = new HashMap(); - Pattern p = null; - // short version of [A-Za-z0-9\-] - // Pattern p = - // Pattern.compile("([\\w\\-]+)=\"*((?<=\")[^\"]+(?=\")|([^\\s]+))\"*"); - // Pattern p = - // Pattern.compile("([\\w\\-]+)=\"*((?<=\")[^\"]+(?=\")|([^\\s]+))\"*"); + /** + * This method parses a xml tag for attributes. The method returns a Map with + * all attributes found in the content string + * + * e.g. + * + * returns Map: {field=a, number=1} + * + * @param content + * @return + */ + public static Map findAttributes(String content) { + Map result = new HashMap(); + Pattern p = null; + // short version of [A-Za-z0-9\-] + // Pattern p = + // Pattern.compile("([\\w\\-]+)=\"*((?<=\")[^\"]+(?=\")|([^\\s]+))\"*"); + // Pattern p = + // Pattern.compile("([\\w\\-]+)=\"*((?<=\")[^\"]+(?=\")|([^\\s]+))\"*"); - // Pattern p = - // Pattern.compile("(\\S+)=[\"']?((?:.(?![\"']?\\s+(?:\\S+)=|[>\"']))+.)[\"']?"); + // Pattern p = + // Pattern.compile("(\\S+)=[\"']?((?:.(?![\"']?\\s+(?:\\S+)=|[>\"']))+.)[\"']?"); - p = Pattern.compile("(\\S+)\\s*=\\s*[\"']?((?:.(?![\"']?\\s+(?:\\S+)=|[>\"']))?[^\"']*)[\"']?"); + p = Pattern.compile("(\\S+)\\s*=\\s*[\"']?((?:.(?![\"']?\\s+(?:\\S+)=|[>\"']))?[^\"']*)[\"']?"); - Matcher m = p.matcher(content); - while (m.find()) { - result.put(m.group(1), m.group(2)); + Matcher m = p.matcher(content); + while (m.find()) { + result.put(m.group(1), m.group(2)); + } + return result; } - return result; - } - // [\"'] [\"'] - // /^\s?([^=]+)\s?=\s?("([^"]+)"|\'([^\']+)\')\s?/ + // [\"'] [\"'] + // /^\s?([^=]+)\s?=\s?("([^"]+)"|\'([^\']+)\')\s?/ - /** - * This method parses a xml tag for a single named attribute. The method returns the value of the - * attribute found in the content string - * - * e.g. - * - * returns "abc" - * - * @param content - * @return - */ - public static String findAttribute(String content, String name) { - Map attriubtes = findAttributes(content); - return attriubtes.get(name); - } + /** + * This method parses a xml tag for a single named attribute. The method returns + * the value of the attribute found in the content string + * + * e.g. + * + * returns "abc" + * + * @param content + * @return + */ + public static String findAttribute(String content, String name) { + Map attriubtes = findAttributes(content); + return attriubtes.get(name); + } - /** - * This method find specific tags inside a string and returns a list with all tags. - *

- * e.g. an empty tag: - *

- * {@code} - *

- * or a tag with content: - *

- * {@codedef} - * - *

- * - * Note: In case of complex XML with not empty tags use the method - * 'findNoEmptyTags' - * - * @param content - * @return - */ - public static List findTags(String content, String tag) { - List result = new ArrayList(); + /** + * This method find specific tags inside a string and returns a list with all + * tags. + *

+ * e.g. an empty tag: + *

+ * {@code} + *

+ * or a tag with content: + *

+ * {@codedef} + * + *

+ * + * Note: In case of complex XML with not empty tags use the + * method 'findNoEmptyTags' + * + * @param content + * @return + */ + public static List findTags(String content, String tag) { + List result = new ArrayList(); - String regex = "<(?i)(" + tag + ")" + // matches the tag itself - "([^<]+)" + // then anything in between the opening and closing - // of the tag - "(|/>)"; // and finally the end tag corresponding - // to what we matched as the first group - // (Exony_Credit_Card_ID, tag1 or tag2) + String regex = "<(?i)(" + tag + ")" + // matches the tag itself + "([^<]+)" + // then anything in between the opening and closing + // of the tag + "(|/>)"; // and finally the end tag corresponding + // to what we matched as the first group + // (Exony_Credit_Card_ID, tag1 or tag2) - Pattern p = Pattern.compile(regex); - Matcher m = p.matcher(content); - while (m.find()) { - result.add(m.group()); + Pattern p = Pattern.compile(regex); + Matcher m = p.matcher(content); + while (m.find()) { + result.add(m.group()); + } + return result; } - return result; - } - /** - * This method find not-empty tags inside a string and returns a list with all tags. - *

- * e.g.: {@codedef} - *

- * Note: To fine also empty tags use 'findTags' - * - * @param content - * @return - */ - public static List findNoEmptyTags(String content, String tag) { - List result = new ArrayList(); - String regex = "<" + tag + ".*>((.|\n)*?)<\\/" + tag + ">"; - Pattern p = Pattern.compile(regex); - Matcher m = p.matcher(content); - while (m.find()) { - result.add(m.group()); + /** + * This method find not-empty tags inside a string and returns a list with all + * tags. + *

+ * e.g.: {@codedef} + *

+ * Note: To fine also empty tags use 'findTags' + * + * @param content + * @return + */ + public static List findNoEmptyTags(String content, String tag) { + List result = new ArrayList(); + String regex = "<" + tag + ".*>((.|\n)*?)<\\/" + tag + ">"; + Pattern p = Pattern.compile(regex); + Matcher m = p.matcher(content); + while (m.find()) { + result.add(m.group()); + } + return result; } - return result; - } - /** - * This method returns the tag values of a specific xml tag - * - * e.g. 2016-12-31 - * - * returns 2016-12-31 - * - * @param content - * @return - */ - public static List findTagValues(String content, String tag) { - List result = new ArrayList(); - List tags = findTags(content, tag); - // opening tag can contain optional attributes - String regex = "(<" + tag + ".+?>|<" + tag + ">)(.+?)(2016-12-31 + * + * returns 2016-12-31 + * + * @param content + * @return + */ + public static List findTagValues(String content, String tag) { + List result = new ArrayList(); + List tags = findTags(content, tag); + // opening tag can contain optional attributes + String regex = "(<" + tag + ".+?>|<" + tag + ">)(.+?)(2016-12-31 - * - * returns 2016-12-31 - * - * @param content - * @return - */ - public static String findTagValue(String content, String tag) { - // opening tag can contain optional attributes - List tags = findTags(content, tag); - if (tags.size() > 0) { - // only first tag... - content = tags.get(0); - } - String regex = "(<" + tag + ".+?>|<" + tag + ">)(.+?)(2016-12-31 + * + * returns 2016-12-31 + * + * @param content + * @return + */ + public static String findTagValue(String content, String tag) { + // opening tag can contain optional attributes + List tags = findTags(content, tag); + if (tags.size() > 0) { + // only first tag... + content = tags.get(0); + } + String regex = "(<" + tag + ".+?>|<" + tag + ">)(.+?)( - * {@code - * - * 1.0.0 - * 1000 - * 10 - * - * } - * - * - * @param evalItemCollection - * @throws PluginException - */ - public static ItemCollection parseItemStructure(String xmlContent) throws PluginException { - return parseTag("" + xmlContent + "", "item"); - } - /** - * This method parses the xml content of a XML tag and returns a new ItemCollection containing all - * embedded tags. Each tag is evaluated as the item name. The tag value is returned as a item - * value. - *

- * MultiValues are currently not supported. - *

- * Example: - *

- * The tag 'code' of: - * - *

-   * {@code
-   * 	  
-   *    1.0.0
-   *    1000
-   *    10
-   * 
-   * }
-   * 
- *

- * Returns an ItemCollection with 3 items (modelversion, task and event) - * - * @param evalItemCollection - * @throws PluginException - */ - public static ItemCollection parseTag(String xmlContent, String tag) throws PluginException { - boolean debug = logger.isLoggable(Level.FINE); - if (debug) { - logger.finest("......parseItemStructure..."); + /** + * This method parses the xml content of a item element and returns a new + * ItemCollection containing all item values. Each tag is evaluated as the item + * name. + * + * MultiValues are currently not supported. + * + * Example: + * + *

+     * {@code
+     * 	  
+     *    1.0.0
+     *    1000
+     *    10
+     * 
+     * }
+     * 
+ * + * @param evalItemCollection + * @throws PluginException + */ + public static ItemCollection parseItemStructure(String xmlContent) throws PluginException { + return parseTag("" + xmlContent + "", "item"); } - ItemCollection result = new ItemCollection(); - if (xmlContent.length() > 0) { - try { - // parse item list... - DocumentBuilder documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); - Document doc = documentBuilder.parse(new InputSource(new StringReader(xmlContent))); - Node node = doc.importNode(doc.getDocumentElement(), true); + /** + * This method parses the xml content of a XML tag and returns a new + * ItemCollection containing all embedded tags. Each tag is evaluated as the + * item name. The tag value is returned as a item value. + *

+ * MultiValues are currently not supported. + *

+ * Example: + *

+ * The tag 'code' of: + * + *

+     * {@code
+     * 	  
+     *    1.0.0
+     *    1000
+     *    10
+     * 
+     * }
+     * 
+ *

+ * Returns an ItemCollection with 3 items (modelversion, task and event) + * + * @param evalItemCollection + * @throws PluginException + */ + public static ItemCollection parseTag(String xmlContent, String tag) throws PluginException { + boolean debug = logger.isLoggable(Level.FINE); + if (debug) { + logger.finest("......parseItemStructure..."); + } + ItemCollection result = new ItemCollection(); + if (xmlContent.length() > 0) { + try { - // we expect the tag name as the root tag of the xml structure! - if (node != null && node.getNodeName().equals(tag)) { - // collect all child nodes... - DocumentFragment docfrag = doc.createDocumentFragment(); - while (node.hasChildNodes()) { - docfrag.appendChild(node.removeChild(node.getFirstChild())); - } + // parse item list... + DocumentBuilder documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + Document doc = documentBuilder.parse(new InputSource(new StringReader(xmlContent))); + Node node = doc.importNode(doc.getDocumentElement(), true); - // append all items into the evalItemCollection... - NodeList childs = docfrag.getChildNodes(); - int itemCount = childs.getLength(); - for (int i = 0; i < itemCount; i++) { - Node childNode = childs.item(i); - if (childNode instanceof Element && childNode.getFirstChild() != null) { - String name = childNode.getNodeName(); - // String value = - // childNode.getFirstChild().getNodeValue(); - String value = innerXml(childNode); + // we expect the tag name as the root tag of the xml structure! + if (node != null && node.getNodeName().equals(tag)) { + // collect all child nodes... + DocumentFragment docfrag = doc.createDocumentFragment(); + while (node.hasChildNodes()) { + docfrag.appendChild(node.removeChild(node.getFirstChild())); + } - result.replaceItemValue(name, value); - if (debug) { - logger.finest("......parsing item '" + name + "' value=" + value); - } - } - } + // append all items into the evalItemCollection... + NodeList childs = docfrag.getChildNodes(); + int itemCount = childs.getLength(); + for (int i = 0; i < itemCount; i++) { + Node childNode = childs.item(i); + if (childNode instanceof Element && childNode.getFirstChild() != null) { + String name = childNode.getNodeName(); + // String value = + // childNode.getFirstChild().getNodeValue(); + String value = innerXml(childNode); - } + result.replaceItemValue(name, value); + if (debug) { + logger.finest("......parsing item '" + name + "' value=" + value); + } + } + } - } catch (ParserConfigurationException | TransformerFactoryConfigurationError | SAXException - | IOException e) { - throw new PluginException(XMLParser.class.getName(), "INVALID_FORMAT", - "Parsing item content failed: " + e.getMessage()); + } - } + } catch (ParserConfigurationException | TransformerFactoryConfigurationError | SAXException + | IOException e) { + throw new PluginException(XMLParser.class.getName(), "INVALID_FORMAT", + "Parsing item content failed: " + e.getMessage()); + + } + } + return result; } - return result; - } - /** - * This method extracts the content of a XML node and prevents inner XML tags - * - * @see https://stackoverflow.com/questions/3300839/get-a-nodes-inner-xml-as-string-in-java-dom?noredirect=1#comment90136258_42456679 - * @param node - * @return - * @throws TransformerFactoryConfigurationError - * @throws TransformerException - */ - private static String innerXml(Node node) { - DOMImplementationLS lsImpl = - (DOMImplementationLS) node.getOwnerDocument().getImplementation().getFeature("LS", "3.0"); - LSSerializer lsSerializer = lsImpl.createLSSerializer(); - lsSerializer.getDomConfig().setParameter("xml-declaration", false); - NodeList childNodes = node.getChildNodes(); - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < childNodes.getLength(); i++) { - Node innerNode = childNodes.item(i); - // verify innerNode... - if (innerNode != null) { - if (innerNode.hasChildNodes()) { - // lets do the stuff by the LSSerializer... - sb.append(lsSerializer.writeToString(innerNode)); - } else { - // just write the node value... - sb.append(innerNode.getNodeValue()); + /** + * This method extracts the content of a XML node and prevents inner XML tags + * + * @see https://stackoverflow.com/questions/3300839/get-a-nodes-inner-xml-as-string-in-java-dom?noredirect=1#comment90136258_42456679 + * @param node + * @return + * @throws TransformerFactoryConfigurationError + * @throws TransformerException + */ + private static String innerXml(Node node) { + DOMImplementationLS lsImpl = (DOMImplementationLS) node.getOwnerDocument().getImplementation().getFeature("LS", + "3.0"); + LSSerializer lsSerializer = lsImpl.createLSSerializer(); + lsSerializer.getDomConfig().setParameter("xml-declaration", false); + NodeList childNodes = node.getChildNodes(); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < childNodes.getLength(); i++) { + Node innerNode = childNodes.item(i); + // verify innerNode... + if (innerNode != null) { + if (innerNode.hasChildNodes()) { + // lets do the stuff by the LSSerializer... + sb.append(lsSerializer.writeToString(innerNode)); + } else { + // just write the node value... + sb.append(innerNode.getNodeValue()); + } + } } - } + return sb.toString(); } - return sb.toString(); - } - /** - * @see https://stackoverflow.com/questions/3300839/get-a-nodes-inner-xml-as-string-in-java-dom?noredirect=1#comment90136258_42456679 - */ - @SuppressWarnings("unused") - @Deprecated - private static String oldinnerXml(Node node) - throws TransformerFactoryConfigurationError, TransformerException { - long l = System.currentTimeMillis(); - StringWriter writer = new StringWriter(); - String xml = null; - Transformer transformer; - transformer = TransformerFactory.newInstance().newTransformer(); - transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); - transformer.transform(new DOMSource(node), new StreamResult(writer)); - // now we remove the outer tag.... - xml = writer.toString(); - xml = xml.substring(xml.indexOf(">") + 1, xml.lastIndexOf("") + 1, xml.lastIndexOf(" - * Imixs Workflow +/* + * Imixs-Workflow + * * Copyright (C) 2001-2020 Imixs Software Solutions GmbH, * http://www.imixs.com * @@ -22,11 +22,9 @@ * https://github.com/imixs/imixs-workflow * * Contributors: - * Imixs Software Solutions GmbH - initial API and implementation + * Imixs Software Solutions GmbH - Project Management * Ralph Soika - Software Developer - * - *******************************************************************************/ - + */ package org.imixs.workflow.xml; @@ -34,9 +32,9 @@ import javax.xml.bind.annotation.XmlRootElement; /** - * The JAXB DocumentTable represents a list of documents in a table format. For each document the - * same list of items will be added into a separate row. The property labels contans the table - * headers. + * The JAXB DocumentTable represents a list of documents in a table format. For + * each document the same list of items will be added into a separate row. The + * property labels contans the table headers. * * * @author rsoika @@ -45,56 +43,53 @@ @XmlRootElement(name = "data") public class DocumentTable implements java.io.Serializable { - private static final long serialVersionUID = 1L; - private XMLDocument[] document; - private List items; - private List labels; - private String encoding; - - public DocumentTable() { - setDocument(new XMLDocument[] {}); - } - - public DocumentTable(XMLDocument[] documents, List items, List labels, - String encoding) { - setDocument(documents); - setItems(items); - setLabels(labels); - setEncoding(encoding); - } - - public XMLDocument[] getDocument() { - return document; - } - - public void setDocument(XMLDocument[] document) { - this.document = document; - } - - public List getItems() { - return items; - } - - public void setItems(List items) { - this.items = items; - } - - public List getLabels() { - return labels; - } - - public void setLabels(List labels) { - this.labels = labels; - } - - public String getEncoding() { - return encoding; - } - - public void setEncoding(String encoding) { - this.encoding = encoding; - } - - + private static final long serialVersionUID = 1L; + private XMLDocument[] document; + private List items; + private List labels; + private String encoding; + + public DocumentTable() { + setDocument(new XMLDocument[] {}); + } + + public DocumentTable(XMLDocument[] documents, List items, List labels, String encoding) { + setDocument(documents); + setItems(items); + setLabels(labels); + setEncoding(encoding); + } + + public XMLDocument[] getDocument() { + return document; + } + + public void setDocument(XMLDocument[] document) { + this.document = document; + } + + public List getItems() { + return items; + } + + public void setItems(List items) { + this.items = items; + } + + public List getLabels() { + return labels; + } + + public void setLabels(List labels) { + this.labels = labels; + } + + public String getEncoding() { + return encoding; + } + + public void setEncoding(String encoding) { + this.encoding = encoding; + } } diff --git a/imixs-workflow-core/src/main/java/org/imixs/workflow/xml/XMLCount.java b/imixs-workflow-core/src/main/java/org/imixs/workflow/xml/XMLCount.java index 009e0ac17..b553f81ea 100644 --- a/imixs-workflow-core/src/main/java/org/imixs/workflow/xml/XMLCount.java +++ b/imixs-workflow-core/src/main/java/org/imixs/workflow/xml/XMLCount.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *

- *  Imixs Workflow 
+/*  
+ *  Imixs-Workflow 
+ *  
  *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
  *  http://www.imixs.com
  *  
@@ -22,10 +22,9 @@
  *      https://github.com/imixs/imixs-workflow
  *  
  *  Contributors:  
- *      Imixs Software Solutions GmbH - initial API and implementation
+ *      Imixs Software Solutions GmbH - Project Management
  *      Ralph Soika - Software Developer
- * 
- *******************************************************************************/ + */ package org.imixs.workflow.xml; @@ -41,8 +40,8 @@ @XmlRootElement(name = "count") public class XMLCount implements java.io.Serializable { - private static final long serialVersionUID = 1L; - @XmlValue - public Long count; + private static final long serialVersionUID = 1L; + @XmlValue + public Long count; } diff --git a/imixs-workflow-core/src/main/java/org/imixs/workflow/xml/XMLDataCollection.java b/imixs-workflow-core/src/main/java/org/imixs/workflow/xml/XMLDataCollection.java index 6cfb3e9fa..ec6f57219 100644 --- a/imixs-workflow-core/src/main/java/org/imixs/workflow/xml/XMLDataCollection.java +++ b/imixs-workflow-core/src/main/java/org/imixs/workflow/xml/XMLDataCollection.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *
- *  Imixs Workflow 
+/*  
+ *  Imixs-Workflow 
+ *  
  *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
  *  http://www.imixs.com
  *  
@@ -22,18 +22,17 @@
  *      https://github.com/imixs/imixs-workflow
  *  
  *  Contributors:  
- *      Imixs Software Solutions GmbH - initial API and implementation
+ *      Imixs Software Solutions GmbH - Project Management
  *      Ralph Soika - Software Developer
- * 
- *******************************************************************************/ + */ package org.imixs.workflow.xml; import javax.xml.bind.annotation.XmlRootElement; /** - * The XMLDataCollection represents a list of XMLItemCollections. This root element is used by JAXB - * api + * The XMLDataCollection represents a list of XMLItemCollections. This root + * element is used by JAXB api * * @author rsoika * @version 0.0.1 @@ -41,19 +40,19 @@ @XmlRootElement(name = "data") public class XMLDataCollection implements java.io.Serializable { - private static final long serialVersionUID = 1L; - private XMLDocument[] document; + private static final long serialVersionUID = 1L; + private XMLDocument[] document; - public XMLDataCollection() { - setDocument(new XMLDocument[] {}); - } + public XMLDataCollection() { + setDocument(new XMLDocument[] {}); + } - public XMLDocument[] getDocument() { - return document; - } + public XMLDocument[] getDocument() { + return document; + } - public void setDocument(XMLDocument[] entity) { - this.document = entity; - } + public void setDocument(XMLDocument[] entity) { + this.document = entity; + } } diff --git a/imixs-workflow-core/src/main/java/org/imixs/workflow/xml/XMLDataCollectionAdapter.java b/imixs-workflow-core/src/main/java/org/imixs/workflow/xml/XMLDataCollectionAdapter.java index 3f3060786..f4ae9ffd2 100644 --- a/imixs-workflow-core/src/main/java/org/imixs/workflow/xml/XMLDataCollectionAdapter.java +++ b/imixs-workflow-core/src/main/java/org/imixs/workflow/xml/XMLDataCollectionAdapter.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *
- *  Imixs Workflow 
+/*  
+ *  Imixs-Workflow 
+ *  
  *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
  *  http://www.imixs.com
  *  
@@ -22,10 +22,9 @@
  *      https://github.com/imixs/imixs-workflow
  *  
  *  Contributors:  
- *      Imixs Software Solutions GmbH - initial API and implementation
+ *      Imixs Software Solutions GmbH - Project Management
  *      Ralph Soika - Software Developer
- * 
- *******************************************************************************/ + */ package org.imixs.workflow.xml; @@ -47,8 +46,9 @@ import org.imixs.workflow.ItemCollection; /** - * An XMLItemCollectionAdapter converts a org.imixs.workflow.xml.XMLItemCollection into - * a org.imixs.workflow.ItemCollection and reverse + * An XMLItemCollectionAdapter converts a + * org.imixs.workflow.xml.XMLItemCollection into a + * org.imixs.workflow.ItemCollection and reverse * * @author imixs.com - Ralph Soika * @version 1.1 @@ -56,213 +56,212 @@ */ public class XMLDataCollectionAdapter { - private static Logger logger = Logger.getLogger(XMLDataCollectionAdapter.class.getName()); + private static Logger logger = Logger.getLogger(XMLDataCollectionAdapter.class.getName()); - /** - * This Method converts a org.imixs.workflow.xml.DocumentCollection into a List of - * org.imixs.workflow.ItemCollection - * - * The method returns an empty list if the collection is empty or null - * - * @param entity - * @return ItemCollection - */ - public static List putDataCollection(XMLDataCollection xmlDocuments) { - List result = new ArrayList(); + /** + * This Method converts a org.imixs.workflow.xml.DocumentCollection + * into a List of org.imixs.workflow.ItemCollection + * + * The method returns an empty list if the collection is empty or null + * + * @param entity + * @return ItemCollection + */ + public static List putDataCollection(XMLDataCollection xmlDocuments) { + List result = new ArrayList(); - if (xmlDocuments != null && xmlDocuments.getDocument() != null) { - for (int i = 0; i < xmlDocuments.getDocument().length; i++) { - XMLDocument xmlItemCol = xmlDocuments.getDocument()[i]; - result.add(XMLDocumentAdapter.putDocument(xmlItemCol)); - } + if (xmlDocuments != null && xmlDocuments.getDocument() != null) { + for (int i = 0; i < xmlDocuments.getDocument().length; i++) { + XMLDocument xmlItemCol = xmlDocuments.getDocument()[i]; + result.add(XMLDocumentAdapter.putDocument(xmlItemCol)); + } + } + return result; } - return result; - } - /** - * This method transforms a Collection into a DocumentCollection - * - * @param documents - * @return - */ - public static XMLDataCollection getDataCollection(final Collection documents) { - return getDataCollection(documents, null); - } + /** + * This method transforms a Collection into a DocumentCollection + * + * @param documents + * @return + */ + public static XMLDataCollection getDataCollection(final Collection documents) { + return getDataCollection(documents, null); + } + + /** + * This method transforms a Collection into a + * XMLDocumentCollection + * + * If the attribute List is provided only the corresponding properties will be + * returned. + * + * @param documents - collection of ItemCollection objects to be converted + * @param itemNames - optional list of item names to be converted. If null all + * items will be converted + * @return + */ + public static XMLDataCollection getDataCollection(final Collection documents, + final List itemNames) { + XMLDataCollection entiCol = new XMLDataCollection(); + Iterator it = documents.iterator(); + int max = documents.size(); + int i = 0; + XMLDocument[] entities = new XMLDocument[max]; + while (it.hasNext()) { + ItemCollection icw = (ItemCollection) it.next(); + if (icw != null) { + XMLDocument entity = XMLDocumentAdapter.getDocument(icw, itemNames); + entities[i] = entity; + i++; + } + } + if (max > 0) + entiCol.setDocument(entities); + return entiCol; + } - /** - * This method transforms a Collection into a XMLDocumentCollection - * - * If the attribute List is provided only the corresponding properties will be returned. - * - * @param documents - collection of ItemCollection objects to be converted - * @param itemNames - optional list of item names to be converted. If null all items will be - * converted - * @return - */ - public static XMLDataCollection getDataCollection(final Collection documents, - final List itemNames) { - XMLDataCollection entiCol = new XMLDataCollection(); - Iterator it = documents.iterator(); - int max = documents.size(); - int i = 0; - XMLDocument[] entities = new XMLDocument[max]; - while (it.hasNext()) { - ItemCollection icw = (ItemCollection) it.next(); - if (icw != null) { - XMLDocument entity = XMLDocumentAdapter.getDocument(icw, itemNames); - entities[i] = entity; - i++; - } + /** + * This method transforms a single ItemCollection into a XMLDocumentCollection + * + */ + public static XMLDataCollection getDataCollection(final ItemCollection document) { + List col = new ArrayList(); + col.add(document); + return getDataCollection(col, null); } - if (max > 0) - entiCol.setDocument(entities); - return entiCol; - } - /** - * This method transforms a single ItemCollection into a XMLDocumentCollection - * - */ - public static XMLDataCollection getDataCollection(final ItemCollection document) { - List col = new ArrayList(); - col.add(document); - return getDataCollection(col, null); - } + /** + * This method transforms a single ItemCollection into a XMLDocumentCollection + * + */ + public static XMLDataCollection getDataCollection(final ItemCollection document, final List itemNames) { + List col = new ArrayList(); + col.add(document); + return getDataCollection(col, itemNames); + } - /** - * This method transforms a single ItemCollection into a XMLDocumentCollection - * - */ - public static XMLDataCollection getDataCollection(final ItemCollection document, - final List itemNames) { - List col = new ArrayList(); - col.add(document); - return getDataCollection(col, itemNames); - } + /** + * This method imports an xml entity data stream and returns a List of + * ItemCollection objects. The method can import any kind of entity data like + * model or configuration data an xml export of workitems. + * + * @param inputStream xml input stream + * @throws JAXBException + * @throws IOException + * @return List of ItemCollection objects + */ + public static List readCollectionFromInputStream(InputStream inputStream) + throws JAXBException, IOException { + byte[] byteInput = null; - /** - * This method imports an xml entity data stream and returns a List of ItemCollection objects. The - * method can import any kind of entity data like model or configuration data an xml export of - * workitems. - * - * @param inputStream xml input stream - * @throws JAXBException - * @throws IOException - * @return List of ItemCollection objects - */ - public static List readCollectionFromInputStream(InputStream inputStream) - throws JAXBException, IOException { - byte[] byteInput = null; + if (inputStream == null) { + return null; + } + byteInput = getBytesFromStream(inputStream); + return readCollection(byteInput); - if (inputStream == null) { - return null; } - byteInput = getBytesFromStream(inputStream); - return readCollection(byteInput); - } + /** + * This method imports an xml entity data byte array and returns a List of + * ItemCollection objects. The method can import any kind of entity data like + * model or configuration data an xml export of workitems. + * + * @param inputStream xml input stream + * @throws JAXBException + * @throws IOException + * @return List of ItemCollection objects + */ + public static List readCollection(byte[] byteInput) throws JAXBException, IOException { + boolean debug = logger.isLoggable(Level.FINE); + List resultList = new ArrayList(); - /** - * This method imports an xml entity data byte array and returns a List of ItemCollection objects. - * The method can import any kind of entity data like model or configuration data an xml export of - * workitems. - * - * @param inputStream xml input stream - * @throws JAXBException - * @throws IOException - * @return List of ItemCollection objects - */ - public static List readCollection(byte[] byteInput) - throws JAXBException, IOException { - boolean debug = logger.isLoggable(Level.FINE); - List resultList = new ArrayList(); + if (byteInput == null || byteInput.length == 0) { + return null; + } - if (byteInput == null || byteInput.length == 0) { - return null; - } + XMLDataCollection ecol = null; + if (debug) { + logger.finest("......readCollection importXmlEntityData - verifing content...."); + } + JAXBContext context = JAXBContext.newInstance(XMLDataCollection.class); + Unmarshaller m = context.createUnmarshaller(); - XMLDataCollection ecol = null; - if (debug) { - logger.finest("......readCollection importXmlEntityData - verifing content...."); - } - JAXBContext context = JAXBContext.newInstance(XMLDataCollection.class); - Unmarshaller m = context.createUnmarshaller(); + ByteArrayInputStream input = new ByteArrayInputStream(byteInput); + Object jaxbObject = m.unmarshal(input); + if (jaxbObject == null) { + throw new RuntimeException("readCollection error - wrong xml file format - unable to read content!"); + } - ByteArrayInputStream input = new ByteArrayInputStream(byteInput); - Object jaxbObject = m.unmarshal(input); - if (jaxbObject == null) { - throw new RuntimeException( - "readCollection error - wrong xml file format - unable to read content!"); - } + ecol = (XMLDataCollection) jaxbObject; - ecol = (XMLDataCollection) jaxbObject; + // convert entities.... + if (ecol.getDocument().length > 0) { + for (XMLDocument aentity : ecol.getDocument()) { + resultList.add(XMLDocumentAdapter.putDocument(aentity)); + } + if (debug) { + logger.fine("readCollection" + ecol.getDocument().length + " entries sucessfull imported"); + } + } + return resultList; - // convert entities.... - if (ecol.getDocument().length > 0) { - for (XMLDocument aentity : ecol.getDocument()) { - resultList.add(XMLDocumentAdapter.putDocument(aentity)); - } - if (debug) { - logger.fine("readCollection" + ecol.getDocument().length + " entries sucessfull imported"); - } } - return resultList; - - } - /** - * This method writes a collection of ItemCollection into a Byte array representing a - * XMLDataCollection - * - * @param inputStream xml input stream - * @throws JAXBException - * @throws IOException - * @return List of ItemCollection objects - */ - public static byte[] writeItemCollection(final Collection documents) - throws JAXBException, IOException { - if (documents == null || documents.size() == 0) { - return null; + /** + * This method writes a collection of ItemCollection into a Byte array + * representing a XMLDataCollection + * + * @param inputStream xml input stream + * @throws JAXBException + * @throws IOException + * @return List of ItemCollection objects + */ + public static byte[] writeItemCollection(final Collection documents) + throws JAXBException, IOException { + if (documents == null || documents.size() == 0) { + return null; + } + XMLDataCollection ecol = XMLDataCollectionAdapter.getDataCollection(documents); + StringWriter writer = new StringWriter(); + JAXBContext context = JAXBContext.newInstance(XMLDataCollection.class); + Marshaller m = context.createMarshaller(); + m.marshal(ecol, writer); + return writer.toString().getBytes(); } - XMLDataCollection ecol = XMLDataCollectionAdapter.getDataCollection(documents); - StringWriter writer = new StringWriter(); - JAXBContext context = JAXBContext.newInstance(XMLDataCollection.class); - Marshaller m = context.createMarshaller(); - m.marshal(ecol, writer); - return writer.toString().getBytes(); - } - /** - * This method writes a ItemCollection into a Byte array representing a XMLDataCollection - * - * @param inputStream xml input stream - * @throws JAXBException - * @throws IOException - * @return List of ItemCollection objects - */ - public static byte[] writeItemCollection(ItemCollection document) - throws JAXBException, IOException { - if (document == null) { - return null; + /** + * This method writes a ItemCollection into a Byte array representing a + * XMLDataCollection + * + * @param inputStream xml input stream + * @throws JAXBException + * @throws IOException + * @return List of ItemCollection objects + */ + public static byte[] writeItemCollection(ItemCollection document) throws JAXBException, IOException { + if (document == null) { + return null; + } + XMLDataCollection ecol = XMLDataCollectionAdapter.getDataCollection(document); + StringWriter writer = new StringWriter(); + JAXBContext context = JAXBContext.newInstance(XMLDataCollection.class); + Marshaller m = context.createMarshaller(); + m.marshal(ecol, writer); + return writer.toString().getBytes(); } - XMLDataCollection ecol = XMLDataCollectionAdapter.getDataCollection(document); - StringWriter writer = new StringWriter(); - JAXBContext context = JAXBContext.newInstance(XMLDataCollection.class); - Marshaller m = context.createMarshaller(); - m.marshal(ecol, writer); - return writer.toString().getBytes(); - } - public static byte[] getBytesFromStream(InputStream is) throws IOException { - ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - int nRead; - byte[] data = new byte[0x4000]; - while ((nRead = is.read(data, 0, data.length)) != -1) { - buffer.write(data, 0, nRead); + public static byte[] getBytesFromStream(InputStream is) throws IOException { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + int nRead; + byte[] data = new byte[0x4000]; + while ((nRead = is.read(data, 0, data.length)) != -1) { + buffer.write(data, 0, nRead); + } + buffer.flush(); + is.close(); + return buffer.toByteArray(); } - buffer.flush(); - is.close(); - return buffer.toByteArray(); - } } diff --git a/imixs-workflow-core/src/main/java/org/imixs/workflow/xml/XMLDocument.java b/imixs-workflow-core/src/main/java/org/imixs/workflow/xml/XMLDocument.java index e281e431c..132333f70 100644 --- a/imixs-workflow-core/src/main/java/org/imixs/workflow/xml/XMLDocument.java +++ b/imixs-workflow-core/src/main/java/org/imixs/workflow/xml/XMLDocument.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *
- *  Imixs Workflow 
+/*  
+ *  Imixs-Workflow 
+ *  
  *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
  *  http://www.imixs.com
  *  
@@ -22,10 +22,9 @@
  *      https://github.com/imixs/imixs-workflow
  *  
  *  Contributors:  
- *      Imixs Software Solutions GmbH - initial API and implementation
+ *      Imixs Software Solutions GmbH - Project Management
  *      Ralph Soika - Software Developer
- * 
- *******************************************************************************/ + */ package org.imixs.workflow.xml; @@ -34,8 +33,8 @@ import javax.xml.bind.annotation.XmlRootElement; /** - * The XMLitemCollection is a basic serializable representation of a pojo to map a - * org.imixs.workflow.ItemCollection into a xml representation using JAXB api + * The XMLitemCollection is a basic serializable representation of a pojo to map + * a org.imixs.workflow.ItemCollection into a xml representation using JAXB api * * @author rsoika * @version 0.0.1 @@ -43,28 +42,28 @@ @XmlRootElement(name = "document") public class XMLDocument implements Serializable { - private static final long serialVersionUID = 1L; - private XMLItem[] item; + private static final long serialVersionUID = 1L; + private XMLItem[] item; - public XMLDocument() { - this.setItem(new XMLItem[] {}); - } + public XMLDocument() { + this.setItem(new XMLItem[] {}); + } - public XMLItem[] getItem() { - return item; - } + public XMLItem[] getItem() { + return item; + } - public void setItem(XMLItem[] item) { - this.item = item; - } + public void setItem(XMLItem[] item) { + this.item = item; + } - /** - * This method compares the item array - */ - public boolean equals(Object o) { - if (!(o instanceof XMLDocument)) - return false; - return Arrays.equals(item, ((XMLDocument) o).item); - } + /** + * This method compares the item array + */ + public boolean equals(Object o) { + if (!(o instanceof XMLDocument)) + return false; + return Arrays.equals(item, ((XMLDocument) o).item); + } } diff --git a/imixs-workflow-core/src/main/java/org/imixs/workflow/xml/XMLDocumentAdapter.java b/imixs-workflow-core/src/main/java/org/imixs/workflow/xml/XMLDocumentAdapter.java index ce206a2d1..6de83e2e3 100644 --- a/imixs-workflow-core/src/main/java/org/imixs/workflow/xml/XMLDocumentAdapter.java +++ b/imixs-workflow-core/src/main/java/org/imixs/workflow/xml/XMLDocumentAdapter.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *
- *  Imixs Workflow 
+/*  
+ *  Imixs-Workflow 
+ *  
  *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
  *  http://www.imixs.com
  *  
@@ -22,10 +22,9 @@
  *      https://github.com/imixs/imixs-workflow
  *  
  *  Contributors:  
- *      Imixs Software Solutions GmbH - initial API and implementation
+ *      Imixs Software Solutions GmbH - Project Management
  *      Ralph Soika - Software Developer
- * 
- *******************************************************************************/ + */ package org.imixs.workflow.xml; @@ -50,7 +49,8 @@ import org.imixs.workflow.ItemCollection; /** - * An XMLDocumentAdapter converts a org.imixs.workflow.xml.XMLDocument into a + * An XMLDocumentAdapter converts a + * org.imixs.workflow.xml.XMLDocument into a * org.imixs.workflow.ItemCollection and reverse * * @author imixs.com - Ralph Soika @@ -59,270 +59,267 @@ */ public class XMLDocumentAdapter { - private static Logger logger = Logger.getLogger(XMLDocumentAdapter.class.getName()); - - /** - * This Method converts a org.imixs.workflow.xml.XMLItemCollection into a - * org.imixs.workflow.ItemCollection Returns null if entity == null - * - * @param entity - * @return ItemCollection - */ - @SuppressWarnings({"rawtypes"}) - public static ItemCollection putDocument(final XMLDocument xmlDocument) { - ItemCollection itemCol = new ItemCollection(); - if (xmlDocument == null) { - return itemCol; + private static Logger logger = Logger.getLogger(XMLDocumentAdapter.class.getName()); + + /** + * This Method converts a org.imixs.workflow.xml.XMLItemCollection + * into a org.imixs.workflow.ItemCollection Returns null if entity + * == null + * + * @param entity + * @return ItemCollection + */ + @SuppressWarnings({ "rawtypes" }) + public static ItemCollection putDocument(final XMLDocument xmlDocument) { + ItemCollection itemCol = new ItemCollection(); + if (xmlDocument == null) { + return itemCol; + } + + XMLItem items[] = xmlDocument.getItem(); + if (items != null) + for (int i = 0; i < items.length; i++) { + XMLItem it = items[i]; + if (it == null) + continue; + String key = it.getName(); + + Object[] valueArray = it.transformValue(); + if (valueArray == null || valueArray.length == 0) { + // no value found + itemCol.replaceItemValue(key, new Vector()); + } else { + // create a mutable list + List valueList = new ArrayList<>(Arrays.asList(valueArray)); + itemCol.replaceItemValue(key, valueList); + } + } + return itemCol; } - XMLItem items[] = xmlDocument.getItem(); - if (items != null) - for (int i = 0; i < items.length; i++) { - XMLItem it = items[i]; - if (it == null) - continue; - String key = it.getName(); - - - Object[] valueArray = it.transformValue(); - if (valueArray == null || valueArray.length == 0) { - // no value found - itemCol.replaceItemValue(key, new Vector()); - } else { - // create a mutable list - List valueList = new ArrayList<>(Arrays.asList(valueArray)); - itemCol.replaceItemValue(key, valueList); - } - } - return itemCol; - } - - /** - * This Method converts a org.imixs.workflow.ItemCollection into a - * XMLDocument - * - * @param document instance of a ItemCollection to be converted - */ - public static XMLDocument getDocument(final ItemCollection document) { - List list = null; - return getDocument(document, list); - } - - /** - * This Method converts a org.imixs.workflow.ItemCollection into a - * XMLDocument - * - *

- * The method verifies if the values stored are basic java types. If not these values will not be - * converted! - * - * @param document instance of a ItemCollection to be converted - * @param itemNames - optional list of item names to be converted. If null all items will be - * converted - */ - @SuppressWarnings({"unchecked"}) - public static XMLDocument getDocument(final ItemCollection document, - final List itemNames) { - - // create a deep copy of the source - ItemCollection aItemCollection = (ItemCollection) document.clone(); - - String itemName = null; - XMLDocument entity = new XMLDocument(); - int i = 0; - XMLItem[] items = null; - - if (aItemCollection != null) { - // test if only a sublist of items should be converted - if (itemNames != null && itemNames.size() > 0) { - items = new XMLItem[itemNames.size()]; - for (String aField : itemNames) { - // this code block guarantees that the order of items - // returned - itemName = aField; - XMLItem item = new XMLItem(); - // test the ItemValue - List vOrg = aItemCollection.getItemValue(aField); - item.setName(itemName); - item.setValue(vOrg.toArray()); - - items[i] = item; - i++; - } + /** + * This Method converts a org.imixs.workflow.ItemCollection into a + * XMLDocument + * + * @param document instance of a ItemCollection to be converted + */ + public static XMLDocument getDocument(final ItemCollection document) { + List list = null; + return getDocument(document, list); + } - } else { - // convert all items (no itemname list is provided) - Iterator it = aItemCollection.getAllItems().entrySet().iterator(); - int max = aItemCollection.getAllItems().entrySet().size(); - items = new XMLItem[max]; - - // iterate over all items if no itemNames are provided - while (it.hasNext()) { - Map.Entry> entry = (Entry>) it.next(); - itemName = entry.getKey(); - XMLItem item = null; - item = new XMLItem(); - item.setName(itemName); - if (entry.getValue() != null) { - item.setValue(entry.getValue().toArray()); - if (item != null) { - items[i] = item; - i++; + /** + * This Method converts a org.imixs.workflow.ItemCollection into a + * XMLDocument + * + *

+ * The method verifies if the values stored are basic java types. If not these + * values will not be converted! + * + * @param document instance of a ItemCollection to be converted + * @param itemNames - optional list of item names to be converted. If null all + * items will be converted + */ + @SuppressWarnings({ "unchecked" }) + public static XMLDocument getDocument(final ItemCollection document, final List itemNames) { + + // create a deep copy of the source + ItemCollection aItemCollection = (ItemCollection) document.clone(); + + String itemName = null; + XMLDocument entity = new XMLDocument(); + int i = 0; + XMLItem[] items = null; + + if (aItemCollection != null) { + // test if only a sublist of items should be converted + if (itemNames != null && itemNames.size() > 0) { + items = new XMLItem[itemNames.size()]; + for (String aField : itemNames) { + // this code block guarantees that the order of items + // returned + itemName = aField; + XMLItem item = new XMLItem(); + // test the ItemValue + List vOrg = aItemCollection.getItemValue(aField); + item.setName(itemName); + item.setValue(vOrg.toArray()); + + items[i] = item; + i++; + } + + } else { + // convert all items (no itemname list is provided) + Iterator it = aItemCollection.getAllItems().entrySet().iterator(); + int max = aItemCollection.getAllItems().entrySet().size(); + items = new XMLItem[max]; + + // iterate over all items if no itemNames are provided + while (it.hasNext()) { + Map.Entry> entry = (Entry>) it.next(); + itemName = entry.getKey(); + XMLItem item = null; + item = new XMLItem(); + item.setName(itemName); + if (entry.getValue() != null) { + item.setValue(entry.getValue().toArray()); + if (item != null) { + items[i] = item; + i++; + } + } else { + logger.warning("putItemCollection - itemName=" + itemName + " has null value"); + } + } } - } else { - logger.warning("putItemCollection - itemName=" + itemName + " has null value"); - } + + entity.setItem(items); } - } - entity.setItem(items); - } + entity = sortItemsByName(entity); - entity = sortItemsByName(entity); - - return entity; - } - - /** - * This Method converts a org.imixs.workflow.ItemCollection into a - * XMLDocument - * - *

- * The method verifies if the values stored are basic java types. If not these values will not be - * converted! - * - * @param document instance of a ItemCollection to be converted - * @param itemNames - optional list of item names to be converted. If null all items will be - * converted - */ - public static XMLDocument getDocument(final ItemCollection document, final String... itemNames) { - return getDocument(document, itemNames); - } - - - /** - * This method sorts all items of a XMLItemCollection by item name. - * - * @param xmlDocument - * @return - */ - public static XMLDocument sortItemsByName(XMLDocument xmlDocument) { - - XMLItem[] items = xmlDocument.getItem(); - Arrays.sort(items, new XMLItemComparator()); - - xmlDocument.setItem(items); - - return xmlDocument; - } - - /** - * This method imports an xml entity data stream containing a singel document and returns the - * ItemCollection. - * - * @param inputStream xml input stream - * @throws JAXBException - * @throws IOException - * @return List of ItemCollection objects - */ - public static ItemCollection readItemCollectionFromInputStream(InputStream inputStream) - throws JAXBException, IOException { - byte[] byteInput = null; - - if (inputStream == null) { - return null; + return entity; } - byteInput = getBytesFromStream(inputStream); - return readItemCollection(byteInput); - - } - - /** - * This method imports a single XMLItemCollection and returns the ItemCollection object. - * - * @param inputStream xml input stream - * @throws JAXBException - * @throws IOException - * @return List of ItemCollection objects - */ - public static ItemCollection readItemCollection(byte[] byteInput) - throws JAXBException, IOException { - XMLDocument ecol = readXMLDocument(byteInput); - if (ecol != null) { - // convert entity.... - ItemCollection itemCol = XMLDocumentAdapter.putDocument(ecol); - return itemCol; + + /** + * This Method converts a org.imixs.workflow.ItemCollection into a + * XMLDocument + * + *

+ * The method verifies if the values stored are basic java types. If not these + * values will not be converted! + * + * @param document instance of a ItemCollection to be converted + * @param itemNames - optional list of item names to be converted. If null all + * items will be converted + */ + public static XMLDocument getDocument(final ItemCollection document, final String... itemNames) { + return getDocument(document, itemNames); } - return null; - } - - /** - * This method reads a XMLItemCollection from a byte array. - * - * @param byteInput - xml data - * @throws JAXBException - * @throws IOException - * @return List of ItemCollection objects - */ - public static XMLDocument readXMLDocument(byte[] byteInput) throws JAXBException, IOException { - boolean debug = logger.isLoggable(Level.FINE); - if (byteInput == null) { - return null; + + /** + * This method sorts all items of a XMLItemCollection by item name. + * + * @param xmlDocument + * @return + */ + public static XMLDocument sortItemsByName(XMLDocument xmlDocument) { + + XMLItem[] items = xmlDocument.getItem(); + Arrays.sort(items, new XMLItemComparator()); + + xmlDocument.setItem(items); + + return xmlDocument; } - XMLDocument ecol = null; - if (debug) { - logger.finest("......importXmlEntityData - verifing content...."); + /** + * This method imports an xml entity data stream containing a singel document + * and returns the ItemCollection. + * + * @param inputStream xml input stream + * @throws JAXBException + * @throws IOException + * @return List of ItemCollection objects + */ + public static ItemCollection readItemCollectionFromInputStream(InputStream inputStream) + throws JAXBException, IOException { + byte[] byteInput = null; + + if (inputStream == null) { + return null; + } + byteInput = getBytesFromStream(inputStream); + return readItemCollection(byteInput); + } - JAXBContext context = JAXBContext.newInstance(XMLDocument.class); - Unmarshaller m = context.createUnmarshaller(); - - ByteArrayInputStream input = new ByteArrayInputStream(byteInput); - Object jaxbObject = m.unmarshal(input); - if (jaxbObject == null) { - throw new RuntimeException( - "readItemCollection error - wrong xml file format - unable to read content!"); + + /** + * This method imports a single XMLItemCollection and returns the ItemCollection + * object. + * + * @param inputStream xml input stream + * @throws JAXBException + * @throws IOException + * @return List of ItemCollection objects + */ + public static ItemCollection readItemCollection(byte[] byteInput) throws JAXBException, IOException { + XMLDocument ecol = readXMLDocument(byteInput); + if (ecol != null) { + // convert entity.... + ItemCollection itemCol = XMLDocumentAdapter.putDocument(ecol); + return itemCol; + } + return null; } - ecol = (XMLDocument) jaxbObject; + /** + * This method reads a XMLItemCollection from a byte array. + * + * @param byteInput - xml data + * @throws JAXBException + * @throws IOException + * @return List of ItemCollection objects + */ + public static XMLDocument readXMLDocument(byte[] byteInput) throws JAXBException, IOException { + boolean debug = logger.isLoggable(Level.FINE); + if (byteInput == null) { + return null; + } + + XMLDocument ecol = null; + if (debug) { + logger.finest("......importXmlEntityData - verifing content...."); + } + JAXBContext context = JAXBContext.newInstance(XMLDocument.class); + Unmarshaller m = context.createUnmarshaller(); - return ecol; + ByteArrayInputStream input = new ByteArrayInputStream(byteInput); + Object jaxbObject = m.unmarshal(input); + if (jaxbObject == null) { + throw new RuntimeException("readItemCollection error - wrong xml file format - unable to read content!"); + } - } + ecol = (XMLDocument) jaxbObject; - /** - * This method writes a ItemCollection into a Byte array representing a XMLDocument - * - * @param inputStream xml input stream - * @throws JAXBException - * @throws IOException - * @return List of ItemCollection objects - */ - public static byte[] writeItemCollection(ItemCollection document) - throws JAXBException, IOException { + return ecol; - if (document == null) { - return null; } - XMLDocument ecol = XMLDocumentAdapter.getDocument(document); - StringWriter writer = new StringWriter(); - JAXBContext context = JAXBContext.newInstance(XMLDocument.class); - Marshaller m = context.createMarshaller(); - m.marshal(ecol, writer); - return writer.toString().getBytes(); - } - - private static byte[] getBytesFromStream(InputStream is) throws IOException { - ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - int nRead; - byte[] data = new byte[0x4000]; - while ((nRead = is.read(data, 0, data.length)) != -1) { - buffer.write(data, 0, nRead); + /** + * This method writes a ItemCollection into a Byte array representing a + * XMLDocument + * + * @param inputStream xml input stream + * @throws JAXBException + * @throws IOException + * @return List of ItemCollection objects + */ + public static byte[] writeItemCollection(ItemCollection document) throws JAXBException, IOException { + + if (document == null) { + return null; + } + + XMLDocument ecol = XMLDocumentAdapter.getDocument(document); + StringWriter writer = new StringWriter(); + JAXBContext context = JAXBContext.newInstance(XMLDocument.class); + Marshaller m = context.createMarshaller(); + m.marshal(ecol, writer); + return writer.toString().getBytes(); + } + + private static byte[] getBytesFromStream(InputStream is) throws IOException { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + int nRead; + byte[] data = new byte[0x4000]; + while ((nRead = is.read(data, 0, data.length)) != -1) { + buffer.write(data, 0, nRead); + } + buffer.flush(); + is.close(); + return buffer.toByteArray(); } - buffer.flush(); - is.close(); - return buffer.toByteArray(); - } } diff --git a/imixs-workflow-core/src/main/java/org/imixs/workflow/xml/XMLItem.java b/imixs-workflow-core/src/main/java/org/imixs/workflow/xml/XMLItem.java index a304c22d3..b9dee3f4d 100644 --- a/imixs-workflow-core/src/main/java/org/imixs/workflow/xml/XMLItem.java +++ b/imixs-workflow-core/src/main/java/org/imixs/workflow/xml/XMLItem.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *

- *  Imixs Workflow 
+/*  
+ *  Imixs-Workflow 
+ *  
  *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
  *  http://www.imixs.com
  *  
@@ -22,10 +22,9 @@
  *      https://github.com/imixs/imixs-workflow
  *  
  *  Contributors:  
- *      Imixs Software Solutions GmbH - initial API and implementation
+ *      Imixs Software Solutions GmbH - Project Management
  *      Ralph Soika - Software Developer
- * 
- *******************************************************************************/ + */ package org.imixs.workflow.xml; @@ -45,295 +44,297 @@ import javax.xml.datatype.XMLGregorianCalendar; /** - * Represents a single item inside a XMLItemCollection. An XMLItem has a name and a value. The value - * can be any Serializable collection of objects. + * Represents a single item inside a XMLItemCollection. An XMLItem has a name + * and a value. The value can be any Serializable collection of objects. * * @author rsoika * */ -@XmlSeeAlso({XMLItem[].class}) // important! to support arrays of XMLItem +@XmlSeeAlso({ XMLItem[].class }) // important! to support arrays of XMLItem @XmlRootElement(name = "item") public class XMLItem implements Serializable { - private static final long serialVersionUID = 1L; - private static Logger logger = Logger.getLogger(XMLItem.class.getName()); - - private String name; - - private Object[] value; - - @XmlAttribute - public java.lang.String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public Object[] getValue() { - return value; - } - - /** - * This method set the value array of the item. The method verifies if the values are from basic - * type, XMLItem or implementing the Map or List interface. - *

- * Map or List interface will be converted into instances of XMLItem. - *

- * Null values will be converted into an empty vector. - *

- * In case an value is not convertible the method prints a warning into the log file. - *

- * issue #52: the method also converts XMLGregorianCalendar into java.util.Date - * - * - * @param values - array of objects - */ - @SuppressWarnings("unchecked") - public void setValue(Object[] _values) { - - if (_values == null || _values.length == 0) { - // add empty vector - Vector vOrg = new Vector(); - vOrg.add(null); - value = vOrg.toArray(); - return; - } + private static final long serialVersionUID = 1L; + private static Logger logger = Logger.getLogger(XMLItem.class.getName()); - // issue #52 - Object[] values = convertXMLGregorianCalendar(_values); + private String name; - // convert values... - List listOfObjects = new ArrayList(); - boolean conversionSuccessfull = true; - for (Object aSingleObject : values) { - if (isBasicType(aSingleObject)) { + private Object[] value; - if (aSingleObject instanceof String) { - // issue #502 - // we test the string for NonValidXMLCharacters - aSingleObject = stripNonValidXMLCharacters((String) aSingleObject); - } - // normal type.... - listOfObjects.add(aSingleObject); - } else { - // maybe we have a Map? - if (aSingleObject instanceof Map) { - @SuppressWarnings("rawtypes") - XMLItem[] embeddedxmlMap = XMLItem.convertMap((Map) aSingleObject); - listOfObjects.add(embeddedxmlMap); - } else { - // maybe we have a List? - if (aSingleObject instanceof List) { - // we create a nameles xmlItem and add the list as the value - XMLItem xmlVal = new XMLItem(); - // convert into array... - xmlVal.setValue(((List) aSingleObject).toArray()); - listOfObjects.add(xmlVal); - } else { - conversionSuccessfull = false; - logger.warning("WARNING : XMLItem - property '" + this.name - + "' contains unsupported java types: " + aSingleObject.getClass().getName()); - break; - } - } - } - } - if (conversionSuccessfull) { - this.value = listOfObjects.toArray(); + @XmlAttribute + public java.lang.String getName() { + return name; } - } - - /** - * This method ensures that the output String has only valid XML unicode characters as specified - * by the XML 1.0 standard. For reference, please see - * the standard. This method will - * return an empty String if the input is null or empty. - * - * @param itemValue The String whose non-valid characters we want to remove. - * @param itemName - the item name used just for logging - * @return The in String, stripped of non-valid characters. - */ - private String stripNonValidXMLCharacters(String itemValue) { - StringBuffer out = new StringBuffer(); // Used to hold the output. - char current; // Used to reference the current character. - - if (itemValue == null || ("".equals(itemValue))) - return ""; // vacancy test. - for (int i = 0; i < itemValue.length(); i++) { - current = itemValue.charAt(i); // NOTE: No IndexOutOfBoundsException caught here; it should - // not happen. - if ((current == 0x9) || (current == 0xA) || (current == 0xD) - || ((current >= 0x20) && (current <= 0xD7FF)) - || ((current >= 0xE000) && (current <= 0xFFFD)) - || ((current >= 0x10000) && (current <= 0x10FFFF))) { - out.append(current); - } else { - logger.warning("invalid xml character at position " + i + " in item '" + name + "'"); - } + public void setName(String name) { + this.name = name; } - return out.toString(); - } - - /** - * This method returns a transformed version of the XMLItem value array. - *

- * In case an object value is an instance of a XMLItem, the method converts the object into the - * corresponding Map or List interface. - *

- * - * @see XMLDocumentAdapter#putDocument(XMLDocument) - * @return - */ - public java.lang.Object[] transformValue() { - - if (value == null) { - return null; + + public Object[] getValue() { + return value; } - Object[] result = new Object[value.length]; - int j = 0; - for (Object aSingleObject : value) { - - if (aSingleObject instanceof XMLItem) { - XMLItem embeddedXMLItem = (XMLItem) aSingleObject; - Object[] embeddedValueArray = embeddedXMLItem.transformValue(); - // convert to mutable list (Issue #593) - result[j] = new ArrayList<>(Arrays.asList(embeddedValueArray)); - } else { - - // is value object of type XMLItem >> Map - if (aSingleObject instanceof XMLItem[]) { - XMLItem[] innerlist = (XMLItem[]) aSingleObject; - // create map - HashMap> map = new HashMap>(); - for (XMLItem x : innerlist) { - // create mutable list (Issue #593) - map.put(x.getName(), new ArrayList<>(Arrays.asList(x.transformValue()))); - } - result[j] = map; + /** + * This method set the value array of the item. The method verifies if the + * values are from basic type, XMLItem or implementing the Map or List + * interface. + *

+ * Map or List interface will be converted into instances of XMLItem. + *

+ * Null values will be converted into an empty vector. + *

+ * In case an value is not convertible the method prints a warning into the log + * file. + *

+ * issue #52: the method also converts XMLGregorianCalendar into java.util.Date + * + * + * @param values - array of objects + */ + @SuppressWarnings("unchecked") + public void setValue(Object[] _values) { + + if (_values == null || _values.length == 0) { + // add empty vector + Vector vOrg = new Vector(); + vOrg.add(null); + value = vOrg.toArray(); + return; } - else { - // raw type... - result[j] = aSingleObject; + // issue #52 + Object[] values = convertXMLGregorianCalendar(_values); + + // convert values... + List listOfObjects = new ArrayList(); + boolean conversionSuccessfull = true; + for (Object aSingleObject : values) { + if (isBasicType(aSingleObject)) { + + if (aSingleObject instanceof String) { + // issue #502 + // we test the string for NonValidXMLCharacters + aSingleObject = stripNonValidXMLCharacters((String) aSingleObject); + } + // normal type.... + listOfObjects.add(aSingleObject); + } else { + // maybe we have a Map? + if (aSingleObject instanceof Map) { + @SuppressWarnings("rawtypes") + XMLItem[] embeddedxmlMap = XMLItem.convertMap((Map) aSingleObject); + listOfObjects.add(embeddedxmlMap); + } else { + // maybe we have a List? + if (aSingleObject instanceof List) { + // we create a nameles xmlItem and add the list as the value + XMLItem xmlVal = new XMLItem(); + // convert into array... + xmlVal.setValue(((List) aSingleObject).toArray()); + listOfObjects.add(xmlVal); + } else { + conversionSuccessfull = false; + logger.warning("WARNING : XMLItem - property '" + this.name + + "' contains unsupported java types: " + aSingleObject.getClass().getName()); + break; + } + } + } + } + if (conversionSuccessfull) { + this.value = listOfObjects.toArray(); } - } - - j++; } - return result; - } - - /** - * This method compares the item name and value array - */ - public boolean equals(Object o) { - if (!(o instanceof XMLItem)) - return false; - XMLItem _xmlItem = (XMLItem) o; - return (name != null && name.equals(_xmlItem.name) && value != null - && Arrays.equals(value, _xmlItem.value)); - } - - /** - * Converts a Map interface into a Array of XMLItem objects - * - * @return - */ - private static XMLItem[] convertMap(Map map) { - Set> entrySet = map.entrySet(); - XMLItem[] result = new XMLItem[entrySet.size()]; - int i = 0; - for (Entry mapentry : entrySet) { - XMLItem singleXMLItem = new XMLItem(); - singleXMLItem.setName(mapentry.getKey()); - if (mapentry.getValue() instanceof List) { - singleXMLItem.setValue(((List) mapentry.getValue()).toArray()); - } else { - // create single list entry - /* - * ArrayList aList = new ArrayList(); - * aList.add(mapentry.getValue().toString()); singleXMLItem.setValue(aList.toArray()); - */ - // Issue 535 - ArrayList aList = new ArrayList<>(); - aList.add(mapentry.getValue()); - singleXMLItem.setValue(aList.toArray()); - } - result[i] = singleXMLItem; - i++; + /** + * This method ensures that the output String has only valid XML unicode + * characters as specified by the XML 1.0 standard. For reference, please see + * the + * standard. This method will return an empty String if the input is null or + * empty. + * + * @param itemValue The String whose non-valid characters we want to remove. + * @param itemName - the item name used just for logging + * @return The in String, stripped of non-valid characters. + */ + private String stripNonValidXMLCharacters(String itemValue) { + StringBuffer out = new StringBuffer(); // Used to hold the output. + char current; // Used to reference the current character. + + if (itemValue == null || ("".equals(itemValue))) + return ""; // vacancy test. + for (int i = 0; i < itemValue.length(); i++) { + current = itemValue.charAt(i); // NOTE: No IndexOutOfBoundsException caught here; it should + // not happen. + if ((current == 0x9) || (current == 0xA) || (current == 0xD) || ((current >= 0x20) && (current <= 0xD7FF)) + || ((current >= 0xE000) && (current <= 0xFFFD)) + || ((current >= 0x10000) && (current <= 0x10FFFF))) { + out.append(current); + } else { + logger.warning("invalid xml character at position " + i + " in item '" + name + "'"); + } + } + return out.toString(); } - return result; - } + /** + * This method returns a transformed version of the XMLItem value array. + *

+ * In case an object value is an instance of a XMLItem, the method converts the + * object into the corresponding Map or List interface. + *

+ * + * @see XMLDocumentAdapter#putDocument(XMLDocument) + * @return + */ + public java.lang.Object[] transformValue() { + + if (value == null) { + return null; + } - /** - * This helper method test if a value if of a basic java type including check for raw arrays, - * java.lang.*, java.math.* - * - * @return - */ - @SuppressWarnings("rawtypes") - private static boolean isBasicType(java.lang.Object o) { + Object[] result = new Object[value.length]; + int j = 0; + for (Object aSingleObject : value) { + + if (aSingleObject instanceof XMLItem) { + XMLItem embeddedXMLItem = (XMLItem) aSingleObject; + Object[] embeddedValueArray = embeddedXMLItem.transformValue(); + // convert to mutable list (Issue #593) + result[j] = new ArrayList<>(Arrays.asList(embeddedValueArray)); + } else { + + // is value object of type XMLItem >> Map + if (aSingleObject instanceof XMLItem[]) { + XMLItem[] innerlist = (XMLItem[]) aSingleObject; + // create map + HashMap> map = new HashMap>(); + for (XMLItem x : innerlist) { + // create mutable list (Issue #593) + map.put(x.getName(), new ArrayList<>(Arrays.asList(x.transformValue()))); + } + result[j] = map; + } + + else { + // raw type... + result[j] = aSingleObject; + } + } + + j++; - if (o == null) { - return true; + } + + return result; } - // test raw types first - if (o instanceof byte[] || o instanceof String[] || o instanceof boolean[] - || o instanceof short[] || o instanceof char[] || o instanceof int[] || o instanceof long[] - || o instanceof float[] || o instanceof double[] || o instanceof Long[] - || o instanceof Integer[] || o instanceof Double[] || o instanceof Float[] - || o instanceof Short[] || o instanceof XMLItem[]) { - return true; + /** + * This method compares the item name and value array + */ + public boolean equals(Object o) { + if (!(o instanceof XMLItem)) + return false; + XMLItem _xmlItem = (XMLItem) o; + return (name != null && name.equals(_xmlItem.name) && value != null && Arrays.equals(value, _xmlItem.value)); } - // text mixed object arrays... - if (o instanceof Object[]) { - Object[] objects = (Object[]) o; - for (Object oneObject : objects) { - if (!isBasicType(oneObject)) { - return false; + /** + * Converts a Map interface into a Array of XMLItem objects + * + * @return + */ + private static XMLItem[] convertMap(Map map) { + Set> entrySet = map.entrySet(); + XMLItem[] result = new XMLItem[entrySet.size()]; + int i = 0; + for (Entry mapentry : entrySet) { + XMLItem singleXMLItem = new XMLItem(); + singleXMLItem.setName(mapentry.getKey()); + if (mapentry.getValue() instanceof List) { + singleXMLItem.setValue(((List) mapentry.getValue()).toArray()); + } else { + // create single list entry + /* + * ArrayList aList = new ArrayList(); + * aList.add(mapentry.getValue().toString()); + * singleXMLItem.setValue(aList.toArray()); + */ + // Issue 535 + ArrayList aList = new ArrayList<>(); + aList.add(mapentry.getValue()); + singleXMLItem.setValue(aList.toArray()); + } + result[i] = singleXMLItem; + i++; } - } - // all elements are supported - return true; + + return result; } - // finaly test package name - Class c = o.getClass(); - String name = c.getName(); - if (name.startsWith("java.lang.") || name.startsWith("java.math.") - || "java.util.Date".equals(name) || "org.imixs.workflow.xml.XMLItem".equals(name)) { - return true; + /** + * This helper method test if a value if of a basic java type including check + * for raw arrays, java.lang.*, java.math.* + * + * @return + */ + @SuppressWarnings("rawtypes") + private static boolean isBasicType(java.lang.Object o) { + + if (o == null) { + return true; + } + + // test raw types first + if (o instanceof byte[] || o instanceof String[] || o instanceof boolean[] || o instanceof short[] + || o instanceof char[] || o instanceof int[] || o instanceof long[] || o instanceof float[] + || o instanceof double[] || o instanceof Long[] || o instanceof Integer[] || o instanceof Double[] + || o instanceof Float[] || o instanceof Short[] || o instanceof XMLItem[]) { + return true; + } + + // text mixed object arrays... + if (o instanceof Object[]) { + Object[] objects = (Object[]) o; + for (Object oneObject : objects) { + if (!isBasicType(oneObject)) { + return false; + } + } + // all elements are supported + return true; + } + + // finaly test package name + Class c = o.getClass(); + String name = c.getName(); + if (name.startsWith("java.lang.") || name.startsWith("java.math.") || "java.util.Date".equals(name) + || "org.imixs.workflow.xml.XMLItem".equals(name)) { + return true; + } + + // unsupported object type + return false; } - // unsupported object type - return false; - } - - /** - * This helper method converts instances of XMLGregorianCalendar into java.util.Date objects. - * - * See issue #52 - * - */ - private static Object[] convertXMLGregorianCalendar(final Object[] objectArray) { - // test the content for GregorianCalendar... (issue #52) - for (int j = 0; j < objectArray.length; j++) { - if (objectArray[j] instanceof XMLGregorianCalendar) { - XMLGregorianCalendar xmlCal = (XMLGregorianCalendar) objectArray[j]; - // convert into Date object - objectArray[j] = xmlCal.toGregorianCalendar().getTime(); - } + /** + * This helper method converts instances of XMLGregorianCalendar into + * java.util.Date objects. + * + * See issue #52 + * + */ + private static Object[] convertXMLGregorianCalendar(final Object[] objectArray) { + // test the content for GregorianCalendar... (issue #52) + for (int j = 0; j < objectArray.length; j++) { + if (objectArray[j] instanceof XMLGregorianCalendar) { + XMLGregorianCalendar xmlCal = (XMLGregorianCalendar) objectArray[j]; + // convert into Date object + objectArray[j] = xmlCal.toGregorianCalendar().getTime(); + } + } + return objectArray; } - return objectArray; - } } diff --git a/imixs-workflow-core/src/main/java/org/imixs/workflow/xml/XMLItemComparator.java b/imixs-workflow-core/src/main/java/org/imixs/workflow/xml/XMLItemComparator.java index dfa1377d9..6002a143e 100644 --- a/imixs-workflow-core/src/main/java/org/imixs/workflow/xml/XMLItemComparator.java +++ b/imixs-workflow-core/src/main/java/org/imixs/workflow/xml/XMLItemComparator.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *
- *  Imixs Workflow 
+/*  
+ *  Imixs-Workflow 
+ *  
  *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
  *  http://www.imixs.com
  *  
@@ -22,17 +22,17 @@
  *      https://github.com/imixs/imixs-workflow
  *  
  *  Contributors:  
- *      Imixs Software Solutions GmbH - initial API and implementation
+ *      Imixs Software Solutions GmbH - Project Management
  *      Ralph Soika - Software Developer
- * 
- *******************************************************************************/ + */ package org.imixs.workflow.xml; import java.util.Comparator; /** - * The XMLItemComparator provides a Comparator for XMLItems contained by a XMLItemCollection. + * The XMLItemComparator provides a Comparator for XMLItems contained by a + * XMLItemCollection. *

* Usage: *

@@ -43,10 +43,9 @@ */ public class XMLItemComparator implements Comparator { - @Override - public int compare(XMLItem o1, XMLItem o2) { - return o1.getName().compareTo(o2.getName()); - } - + @Override + public int compare(XMLItem o1, XMLItem o2) { + return o1.getName().compareTo(o2.getName()); + } } diff --git a/imixs-workflow-core/src/main/java/org/imixs/workflow/xml/XSLHandler.java b/imixs-workflow-core/src/main/java/org/imixs/workflow/xml/XSLHandler.java index 3b3249484..06e0b6e66 100644 --- a/imixs-workflow-core/src/main/java/org/imixs/workflow/xml/XSLHandler.java +++ b/imixs-workflow-core/src/main/java/org/imixs/workflow/xml/XSLHandler.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *

- *  Imixs Workflow 
+/*  
+ *  Imixs-Workflow 
+ *  
  *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
  *  http://www.imixs.com
  *  
@@ -22,10 +22,9 @@
  *      https://github.com/imixs/imixs-workflow
  *  
  *  Contributors:  
- *      Imixs Software Solutions GmbH - initial API and implementation
+ *      Imixs Software Solutions GmbH - Project Management
  *      Ralph Soika - Software Developer
- * 
- *******************************************************************************/ + */ package org.imixs.workflow.xml; @@ -52,120 +51,121 @@ /** * This class can be used to transform xml by XSL template. * - * The class is used by the ReportRestService to execute a report and also by the MailPluign to - * transform the mail body + * The class is used by the ReportRestService to execute a report and also by + * the MailPluign to transform the mail body * * @author imixs.com - Ralph Soika * @version 1.0 */ public class XSLHandler { - private static Logger logger = Logger.getLogger(XSLHandler.class.getName()); - - /** - * This method transforms an XML source with a provided XSL template. The result will be written - * into a output stream. - * - * @param xmlSource - - * @param xslSource - * @param encoding (default UTF-8) - * @return - * @throws UnsupportedEncodingException - * @throws TransformerException - */ - - public static void transform(String xmlSource, String xslSource, String encoding, - OutputStream output) throws UnsupportedEncodingException, TransformerException { - boolean debug = logger.isLoggable(Level.FINE); - try { - if (encoding == null || encoding.isEmpty()) { - encoding = "UTF-8"; - } - TransformerFactory transFact = TransformerFactory.newInstance(); - if (debug) { - logger.finest("......xslTransformation: encoding=" + encoding); - } - // generate XML InputStream Reader with encoding - ByteArrayInputStream baisXML = new ByteArrayInputStream(xmlSource.getBytes()); - InputStreamReader isreaderXML; - - isreaderXML = new InputStreamReader(baisXML, encoding); - - Source xmlSrc = new StreamSource(isreaderXML); - - // generate XSL InputStream Reader with encoding - ByteArrayInputStream baisXSL = new ByteArrayInputStream(xslSource.getBytes()); - InputStreamReader isreaderXSL = new InputStreamReader(baisXSL, encoding); - Source xslSrc = new StreamSource(isreaderXSL); - - Transformer trans = transFact.newTransformer(xslSrc); - trans.transform(xmlSrc, new StreamResult(output)); - - } finally { + private static Logger logger = Logger.getLogger(XSLHandler.class.getName()); + + /** + * This method transforms an XML source with a provided XSL template. The result + * will be written into a output stream. + * + * @param xmlSource - + * @param xslSource + * @param encoding (default UTF-8) + * @return + * @throws UnsupportedEncodingException + * @throws TransformerException + */ + + public static void transform(String xmlSource, String xslSource, String encoding, OutputStream output) + throws UnsupportedEncodingException, TransformerException { + boolean debug = logger.isLoggable(Level.FINE); + try { + if (encoding == null || encoding.isEmpty()) { + encoding = "UTF-8"; + } + TransformerFactory transFact = TransformerFactory.newInstance(); + if (debug) { + logger.finest("......xslTransformation: encoding=" + encoding); + } + // generate XML InputStream Reader with encoding + ByteArrayInputStream baisXML = new ByteArrayInputStream(xmlSource.getBytes()); + InputStreamReader isreaderXML; + + isreaderXML = new InputStreamReader(baisXML, encoding); + + Source xmlSrc = new StreamSource(isreaderXML); + + // generate XSL InputStream Reader with encoding + ByteArrayInputStream baisXSL = new ByteArrayInputStream(xslSource.getBytes()); + InputStreamReader isreaderXSL = new InputStreamReader(baisXSL, encoding); + Source xslSrc = new StreamSource(isreaderXSL); + + Transformer trans = transFact.newTransformer(xslSrc); + trans.transform(xmlSrc, new StreamResult(output)); + + } finally { + + } } - } - - - /** - * This method transforms an Collection of Documents into XML and translates the result based on a - * provided XSL template. The result will be written into a output stream. - * - * @param xmlSource - - * @param xslSource - * @param encoding (default UTF-8) - * @return - * @throws JAXBException - * @throws TransformerException - * @throws IOException - */ - public static void transform(List dataSource, String xslSource, String encoding, - OutputStream output) throws JAXBException, TransformerException, IOException { - - if (encoding == null || encoding.isEmpty()) { - encoding = "UTF-8"; - } - XMLDataCollection xmlDataCollection = XMLDataCollectionAdapter.getDataCollection(dataSource); - - StringWriter writer = new StringWriter(); - - JAXBContext context = JAXBContext.newInstance(XMLDataCollection.class); - Marshaller m = context.createMarshaller(); - m.setProperty("jaxb.encoding", encoding); - m.marshal(xmlDataCollection, writer); - - XSLHandler.transform(writer.toString(), xslSource, encoding, output); - } - - /** - * This method transforms a single Documents (ItemCollection) into XML and translates the result - * based on a provided XSL template. The result will be written into a output stream. - * - * @param xmlSource - - * @param xslSource - * @param encoding (default UTF-8) - * @return - * @throws JAXBException - * @throws TransformerException - * @throws IOException - */ - public static void transform(ItemCollection dataSource, String xslSource, String encoding, - OutputStream output) throws JAXBException, TransformerException, IOException { - - // byte[] result=null; - if (encoding == null || encoding.isEmpty()) { - encoding = "UTF-8"; + /** + * This method transforms an Collection of Documents into XML and translates the + * result based on a provided XSL template. The result will be written into a + * output stream. + * + * @param xmlSource - + * @param xslSource + * @param encoding (default UTF-8) + * @return + * @throws JAXBException + * @throws TransformerException + * @throws IOException + */ + public static void transform(List dataSource, String xslSource, String encoding, + OutputStream output) throws JAXBException, TransformerException, IOException { + + if (encoding == null || encoding.isEmpty()) { + encoding = "UTF-8"; + } + XMLDataCollection xmlDataCollection = XMLDataCollectionAdapter.getDataCollection(dataSource); + + StringWriter writer = new StringWriter(); + + JAXBContext context = JAXBContext.newInstance(XMLDataCollection.class); + Marshaller m = context.createMarshaller(); + m.setProperty("jaxb.encoding", encoding); + m.marshal(xmlDataCollection, writer); + + XSLHandler.transform(writer.toString(), xslSource, encoding, output); } - XMLDocument xmlDocument = XMLDocumentAdapter.getDocument(dataSource); - StringWriter writer = new StringWriter(); - - JAXBContext context = JAXBContext.newInstance(XMLDocument.class); - Marshaller m = context.createMarshaller(); - m.setProperty("jaxb.encoding", encoding); - m.marshal(xmlDocument, writer); - - XSLHandler.transform(writer.toString(), xslSource, encoding, output); - } + /** + * This method transforms a single Documents (ItemCollection) into XML and + * translates the result based on a provided XSL template. The result will be + * written into a output stream. + * + * @param xmlSource - + * @param xslSource + * @param encoding (default UTF-8) + * @return + * @throws JAXBException + * @throws TransformerException + * @throws IOException + */ + public static void transform(ItemCollection dataSource, String xslSource, String encoding, OutputStream output) + throws JAXBException, TransformerException, IOException { + + // byte[] result=null; + if (encoding == null || encoding.isEmpty()) { + encoding = "UTF-8"; + } + XMLDocument xmlDocument = XMLDocumentAdapter.getDocument(dataSource); + + StringWriter writer = new StringWriter(); + + JAXBContext context = JAXBContext.newInstance(XMLDocument.class); + Marshaller m = context.createMarshaller(); + m.setProperty("jaxb.encoding", encoding); + m.marshal(xmlDocument, writer); + + XSLHandler.transform(writer.toString(), xslSource, encoding, output); + } } diff --git a/imixs-workflow-core/src/main/java/org/imixs/workflow/xml/package-info.java b/imixs-workflow-core/src/main/java/org/imixs/workflow/xml/package-info.java index 79682b20c..f6a4ebdf8 100644 --- a/imixs-workflow-core/src/main/java/org/imixs/workflow/xml/package-info.java +++ b/imixs-workflow-core/src/main/java/org/imixs/workflow/xml/package-info.java @@ -1,6 +1,6 @@ -@XmlSchema(elementFormDefault = XmlNsForm.QUALIFIED, attributeFormDefault = XmlNsForm.UNQUALIFIED, - xmlns = {@XmlNs(namespaceURI = "http://www.w3.org/2001/XMLSchema-instance", prefix = "xsi"), - @XmlNs(namespaceURI = "http://www.w3.org/2001/XMLSchema", prefix = "xs")} +@XmlSchema(elementFormDefault = XmlNsForm.QUALIFIED, attributeFormDefault = XmlNsForm.UNQUALIFIED, xmlns = { + @XmlNs(namespaceURI = "http://www.w3.org/2001/XMLSchema-instance", prefix = "xsi"), + @XmlNs(namespaceURI = "http://www.w3.org/2001/XMLSchema", prefix = "xs") } ) package org.imixs.workflow.xml; @@ -10,8 +10,8 @@ import javax.xml.bind.annotation.XmlSchema; /** - * package-info.java is used to define the general xml name spaces used by the jax-b marshaler. This - * will define the following namespaces : + * package-info.java is used to define the general xml name spaces used by the + * jax-b marshaler. This will define the following namespaces : * * * xmlns:xs="http://www.w3.org/2001/XMLSchema" diff --git a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/DocumentEvent.java b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/DocumentEvent.java index f00a4b358..802236d6d 100644 --- a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/DocumentEvent.java +++ b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/DocumentEvent.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *
- *  Imixs Workflow 
+/*  
+ *  Imixs-Workflow 
+ *  
  *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
  *  http://www.imixs.com
  *  
@@ -22,18 +22,18 @@
  *      https://github.com/imixs/imixs-workflow
  *  
  *  Contributors:  
- *      Imixs Software Solutions GmbH - initial API and implementation
+ *      Imixs Software Solutions GmbH - Project Management
  *      Ralph Soika - Software Developer
- * 
- *******************************************************************************/ + */ package org.imixs.workflow.engine; import org.imixs.workflow.ItemCollection; /** - * The DocumentEvent provides a CDI observer pattern. The DocumentEvent is fired by the - * DocumentService EJB. An event Observer can react on a save or load event. + * The DocumentEvent provides a CDI observer pattern. The DocumentEvent is fired + * by the DocumentService EJB. An event Observer can react on a save or load + * event. * * * The DocumentEvent defines the following event types: @@ -49,24 +49,24 @@ */ public class DocumentEvent { - public static final int ON_DOCUMENT_SAVE = 1; - public static final int ON_DOCUMENT_LOAD = 2; - public static final int ON_DOCUMENT_DELETE = 3; + public static final int ON_DOCUMENT_SAVE = 1; + public static final int ON_DOCUMENT_LOAD = 2; + public static final int ON_DOCUMENT_DELETE = 3; - private int eventType; - private ItemCollection document; + private int eventType; + private ItemCollection document; - public DocumentEvent(ItemCollection document, int eventType) { - this.eventType = eventType; - this.document = document; - } + public DocumentEvent(ItemCollection document, int eventType) { + this.eventType = eventType; + this.document = document; + } - public int getEventType() { - return eventType; - } + public int getEventType() { + return eventType; + } - public ItemCollection getDocument() { - return document; - } + public ItemCollection getDocument() { + return document; + } } diff --git a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/DocumentService.java b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/DocumentService.java index 3095474b7..543ab348e 100644 --- a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/DocumentService.java +++ b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/DocumentService.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *
- *  Imixs Workflow 
+/*  
+ *  Imixs-Workflow 
+ *  
  *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
  *  http://www.imixs.com
  *  
@@ -22,10 +22,9 @@
  *      https://github.com/imixs/imixs-workflow
  *  
  *  Contributors:  
- *      Imixs Software Solutions GmbH - initial API and implementation
+ *      Imixs Software Solutions GmbH - Project Management
  *      Ralph Soika - Software Developer
- * 
- *******************************************************************************/ + */ package org.imixs.workflow.engine; @@ -72,37 +71,44 @@ import org.imixs.workflow.exceptions.QueryException; /** - * The DocumentService is used to save and load instances of ItemCollections into a Database. The - * DocumentService throws an AccessDeniedException if the CallerPrincipal is not allowed to save or - * read a specific Document from the database. So the DocumentService can be used to save business - * objects into a database with individual read- or writeAccess restrictions. + * The DocumentService is used to save and load instances of ItemCollections + * into a Database. The DocumentService throws an AccessDeniedException if the + * CallerPrincipal is not allowed to save or read a specific Document from the + * database. So the DocumentService can be used to save business objects into a + * database with individual read- or writeAccess restrictions. *

- * The Bean holds an instance of an EntityPersistenceManager for the persistence unit - * 'org.imixs.workflow.jpa' to manage the Document entity bean class. The Document entity bean is - * used to store the attributes of a ItemCollection into the connected database. + * The Bean holds an instance of an EntityPersistenceManager for the persistence + * unit 'org.imixs.workflow.jpa' to manage the Document entity bean class. The + * Document entity bean is used to store the attributes of a ItemCollection into + * the connected database. *

- * The save() method persists any instance of an ItemCollection. If a ItemCollection is saved the - * first time the DocumentService generates the attribute $uniqueid which will be included in the - * ItemCollection returned by this method. If a ItemCollection was saved before the method updates - * the corresponding Document Object. + * The save() method persists any instance of an ItemCollection. If a + * ItemCollection is saved the first time the DocumentService generates the + * attribute $uniqueid which will be included in the ItemCollection returned by + * this method. If a ItemCollection was saved before the method updates the + * corresponding Document Object. *

- * The load() and find() methods are used to read ItemCollections from the database. The remove() - * method deletes a saved ItemCollection from the database. + * The load() and find() methods are used to read ItemCollections from the + * database. The remove() method deletes a saved ItemCollection from the + * database. *

- * All methods expect and return Instances of the object org.imixs.workflow.ItemCollection which is - * no entity EJB. So these objects are not managed by any instance of an EntityPersistenceManager. + * All methods expect and return Instances of the object + * org.imixs.workflow.ItemCollection which is no entity EJB. So these objects + * are not managed by any instance of an EntityPersistenceManager. *

- * A collection of ItemCollections can be read using the find() method using EQL syntax. + * A collection of ItemCollections can be read using the find() method using EQL + * syntax. *

* - * Additional to the basic functionality to save and load instances of the object - * org.imixs.workflow.ItemCollection the method also manages the read- and writeAccess for each - * instance of an ItemCollection. Therefore the save() method scans an ItemCollection for the - * attributes '$ReadAccess' and '$WriteAccess'. The DocumentService verifies in each call of the - * save() load(), remove() and find() methods if the current callerPrincipal is granted to the - * affected entities. If an ItemCollection was saved with read- or writeAccess the access to an - * Instance of a saved ItemCollection will be protected for a callerPrincipal with missing read- or - * writeAccess. + * Additional to the basic functionality to save and load instances of the + * object org.imixs.workflow.ItemCollection the method also manages the read- + * and writeAccess for each instance of an ItemCollection. Therefore the save() + * method scans an ItemCollection for the attributes '$ReadAccess' and + * '$WriteAccess'. The DocumentService verifies in each call of the save() + * load(), remove() and find() methods if the current callerPrincipal is granted + * to the affected entities. If an ItemCollection was saved with read- or + * writeAccess the access to an Instance of a saved ItemCollection will be + * protected for a callerPrincipal with missing read- or writeAccess. *

* * @see org.imixs.workflow.engine.jpa.Document @@ -111,1137 +117,1150 @@ * */ -@DeclareRoles({"org.imixs.ACCESSLEVEL.NOACCESS", "org.imixs.ACCESSLEVEL.READERACCESS", - "org.imixs.ACCESSLEVEL.AUTHORACCESS", "org.imixs.ACCESSLEVEL.EDITORACCESS", - "org.imixs.ACCESSLEVEL.MANAGERACCESS"}) -@RolesAllowed({"org.imixs.ACCESSLEVEL.NOACCESS", "org.imixs.ACCESSLEVEL.READERACCESS", - "org.imixs.ACCESSLEVEL.AUTHORACCESS", "org.imixs.ACCESSLEVEL.EDITORACCESS", - "org.imixs.ACCESSLEVEL.MANAGERACCESS"}) +@DeclareRoles({ "org.imixs.ACCESSLEVEL.NOACCESS", "org.imixs.ACCESSLEVEL.READERACCESS", + "org.imixs.ACCESSLEVEL.AUTHORACCESS", "org.imixs.ACCESSLEVEL.EDITORACCESS", + "org.imixs.ACCESSLEVEL.MANAGERACCESS" }) +@RolesAllowed({ "org.imixs.ACCESSLEVEL.NOACCESS", "org.imixs.ACCESSLEVEL.READERACCESS", + "org.imixs.ACCESSLEVEL.AUTHORACCESS", "org.imixs.ACCESSLEVEL.EDITORACCESS", + "org.imixs.ACCESSLEVEL.MANAGERACCESS" }) @Stateless @LocalBean public class DocumentService { - public static final String ACCESSLEVEL_NOACCESS = "org.imixs.ACCESSLEVEL.NOACCESS"; + public static final String ACCESSLEVEL_NOACCESS = "org.imixs.ACCESSLEVEL.NOACCESS"; - public static final String ACCESSLEVEL_READERACCESS = "org.imixs.ACCESSLEVEL.READERACCESS"; + public static final String ACCESSLEVEL_READERACCESS = "org.imixs.ACCESSLEVEL.READERACCESS"; - public static final String ACCESSLEVEL_AUTHORACCESS = "org.imixs.ACCESSLEVEL.AUTHORACCESS"; + public static final String ACCESSLEVEL_AUTHORACCESS = "org.imixs.ACCESSLEVEL.AUTHORACCESS"; - public static final String ACCESSLEVEL_EDITORACCESS = "org.imixs.ACCESSLEVEL.EDITORACCESS"; + public static final String ACCESSLEVEL_EDITORACCESS = "org.imixs.ACCESSLEVEL.EDITORACCESS"; - public static final String ACCESSLEVEL_MANAGERACCESS = "org.imixs.ACCESSLEVEL.MANAGERACCESS"; + public static final String ACCESSLEVEL_MANAGERACCESS = "org.imixs.ACCESSLEVEL.MANAGERACCESS"; - public static final String EVENTLOG_TOPIC_INDEX_ADD = "index.add"; - public static final String EVENTLOG_TOPIC_INDEX_REMOVE = "index.remove"; - - public static final String READACCESS = "$readaccess"; - public static final String WRITEACCESS = "$writeaccess"; - public static final String ISAUTHOR = "$isAuthor"; - public static final String NOINDEX = "$noindex"; - public static final String IMMUTABLE = "$immutable"; - public static final String VERSION = "$version"; - - public static final String USER_GROUP_LIST = "org.imixs.USER.GROUPLIST"; - - private final static Logger logger = Logger.getLogger(DocumentService.class.getName()); - - public static final String OPERATION_NOTALLOWED = "OPERATION_NOTALLOWED"; - public static final String INVALID_PARAMETER = "INVALID_PARAMETER"; - public static final String INVALID_UNIQUEID = "INVALID_UNIQUEID"; - - @Resource - SessionContext ctx; - - @Resource(name = "ACCESS_ROLES") - private String accessRoles = ""; - - @Resource(name = "DISABLE_OPTIMISTIC_LOCKING") - private Boolean disableOptimisticLocking = false; - - @PersistenceContext(unitName = "org.imixs.workflow.jpa") - private EntityManager manager; - - @Inject - private UpdateService indexUpdateService; - - @Inject - private SearchService indexSearchService; - - @Inject - private EventLogService eventLogService; - - @Inject - protected Event documentEvents; - - @Inject - protected Event userGroupEvents; - - @Inject - @ConfigProperty(name = "index.defaultOperator", defaultValue = "AND") - private String indexDefaultOperator; - - /** - * Returns a comma separated list of additional Access-Roles defined for this service - * - * @return - */ - public String getAccessRoles() { - return accessRoles; - } - - public void setAccessRoles(String accessRoles) { - this.accessRoles = accessRoles; - } - - /** - * returns the disable optimistic locking status - * - * @return - true if optimistic locking is disabled - */ - public void setDisableOptimisticLocking(Boolean disableOptimisticLocking) { - this.disableOptimisticLocking = disableOptimisticLocking; - } - - public Boolean getDisableOptimisticLocking() { - return disableOptimisticLocking; - } - - /** - * This method returns a list of user names, roles and application groups the user belongs to. - *

- * A client can extend the list of user groups associated with a userId by reacting on the CDI - * event 'UserGrouptEvent'. - * - * @see UserGroupEvent - * @return - */ - public List getUserNameList() { - - List userNameList = new Vector(); - - // Begin with the username - userNameList.add(ctx.getCallerPrincipal().getName().toString()); - // now construct role list - String roleList = - "org.imixs.ACCESSLEVEL.READERACCESS,org.imixs.ACCESSLEVEL.AUTHORACCESS,org.imixs.ACCESSLEVEL.EDITORACCESS,org.imixs.ACCESSLEVEL.MANAGERACCESS," - + accessRoles; - // and add each role the user is in to the list - StringTokenizer roleListTokens = new StringTokenizer(roleList, ","); - while (roleListTokens.hasMoreTokens()) { - try { - String testRole = roleListTokens.nextToken().trim(); - if (!"".equals(testRole) && ctx.isCallerInRole(testRole)) - userNameList.add(testRole); - } catch (Exception e) { - // no operation - Role simply not defined - // this could be an configuration/test issue and need not to be - // handled as an error - } - } + public static final String EVENTLOG_TOPIC_INDEX_ADD = "index.add"; + public static final String EVENTLOG_TOPIC_INDEX_REMOVE = "index.remove"; - // To extend UserGroups we fire the CDI Event UserGroupEvent... - if (userGroupEvents != null) { - // create Group Event - UserGroupEvent groupEvent = new UserGroupEvent(ctx.getCallerPrincipal().getName().toString()); - userGroupEvents.fire(groupEvent); - if (groupEvent.getGroups() != null) { - userNameList.addAll(groupEvent.getGroups()); - } - - } else { - logger.warning("Missing CDI support for Event !"); - } + public static final String READACCESS = "$readaccess"; + public static final String WRITEACCESS = "$writeaccess"; + public static final String ISAUTHOR = "$isAuthor"; + public static final String NOINDEX = "$noindex"; + public static final String IMMUTABLE = "$immutable"; + public static final String VERSION = "$version"; - // String[] applicationGroups = getUserGroupList(); - // if (applicationGroups != null) - // for (String auserRole : applicationGroups) - // userNameList.add(auserRole); - - return userNameList; - } - - /** - * This method returns true, if at least one element of the current UserNameList is contained in a - * given name list. The comparison is case sensitive! - * - * @param nameList - * @return - */ - public boolean isUserContained(List nameList) { - if (nameList == null) { - return false; - } - List userNameList = getUserNameList(); - // check each element of the given nameList - for (String aName : nameList) { - if (aName != null && !aName.isEmpty()) { - if (userNameList.stream().anyMatch(aName::equals)) { - return true; - } - } - } - // not found - return false; - } - - /** - * Test if the caller has a given security role. - * - * @param rolename - * @return true if user is in role - */ - public boolean isUserInRole(String rolename) { - try { - return ctx.isCallerInRole(rolename); - } catch (Exception e) { - // avoid a exception for a role request which is not defined - return false; - } - } - - /** - * This Method saves an ItemCollection into the database. If the ItemCollection is saved the first - * time the method generates a uniqueID ('$uniqueid') which can be used to identify the - * ItemCollection by its ID. If the ItemCollection was saved before, the method updates the - * existing ItemCollection stored in the database. - * - *

- * The Method returns an updated instance of the ItemCollection containing the attributes - * $modified, $created, and $uniqueId - * - *

- * The method throws an AccessDeniedException if the CallerPrincipal is not allowed to save or - * update the ItemCollection in the database. The CallerPrincipial should have at least the access - * Role org.imixs.ACCESSLEVEL.AUTHORACCESS - * - *

- * The method adds/updates the document into the lucene index. - * - *

- * The method returns a itemCollection without the $VersionNumber from the persisted entity. (see - * issue #226) - *

- * - *

- * issue #230: - * - * The document will be marked as 'saved' so that the methods load() and getDocumentsByQuery() can - * evaluate this flag. Depending on the state, the methods can decide the correct behavior. In - * general we detach a document in the load() and getDocumentsByQuery() method. This is for - * performance reasons and the fact, that a ItemCollection can hold byte arrays which will be - * copied by reference. In cases where these methods are called after a document was saved - * (document is now managed), in one single transaction, the detach call will discard the changes - * made by the save() method. For that reason we flag the entity and evaluate this flag in the - * load method evaluates the save status. - * - * - * @param ItemCollection to be saved - * @return updated ItemCollection - * @throws AccessDeniedException - */ - public ItemCollection save(ItemCollection document) throws AccessDeniedException { - boolean debug = logger.isLoggable(Level.FINE); - long lSaveTime = System.currentTimeMillis(); - if (debug) { - logger.finest("......save - ID=" + document.getUniqueID() + ", provided version=" - + document.getItemValueInteger(VERSION)); - } - Document persistedDocument = null; - // Now set flush Mode to COMMIT - manager.setFlushMode(FlushModeType.COMMIT); - - // check if a $uniqueid is available - String sID = document.getItemValueString(WorkflowKernel.UNIQUEID); - if (!sID.isEmpty()) { - // yes so we can try to find the Entity by its primary key - persistedDocument = manager.find(Document.class, sID); - if (debug && persistedDocument == null) { - logger.finest("......Document '" + sID + "' not found!"); - } - } + public static final String USER_GROUP_LIST = "org.imixs.USER.GROUPLIST"; - // did the document exist? - if (persistedDocument == null) { - // entity not found in database, create a new instance using the - // provided id. Test if user is allowed to create Entities.... - if (!(ctx.isCallerInRole(ACCESSLEVEL_MANAGERACCESS) - || ctx.isCallerInRole(ACCESSLEVEL_EDITORACCESS) - || ctx.isCallerInRole(ACCESSLEVEL_AUTHORACCESS))) { - throw new AccessDeniedException(OPERATION_NOTALLOWED, - "You are not allowed to perform this operation"); - } - // create new one with the provided id - persistedDocument = new Document(sID); - // if $Created is provided than overtake this information - Date datCreated = document.getItemValueDate("$created"); - if (datCreated != null) { - Calendar cal = Calendar.getInstance(); - cal.setTime(datCreated); - // Overwrite Creation Date - persistedDocument.setCreated(cal); - } - // now persist the new EntityBean! - if (debug) { - logger.finest("......persist activeEntity"); - } - manager.persist(persistedDocument); - - } else { - // activeEntity exists - verify if current user has write- and - // readaccess - if (!isCallerAuthor(persistedDocument) || !isCallerReader(persistedDocument)) { - throw new AccessDeniedException(OPERATION_NOTALLOWED, - "You are not allowed to perform this operation"); - } - - // test if persistedDocument is IMMUTABLE - if (ItemCollection.createByReference(persistedDocument.getData()) - .getItemValueBoolean(IMMUTABLE)) { - throw new AccessDeniedException(OPERATION_NOTALLOWED, - "Operation not allowed, document is immutable!"); - } - // there is no need to merge the persistedDocument because it is - // already managed by JPA! - } + private final static Logger logger = Logger.getLogger(DocumentService.class.getName()); - // after all the persistedDocument is now managed through JPA! - if (debug) { - logger.finest("......save - ID=" + document.getUniqueID() + " managed version=" - + persistedDocument.getVersion()); - } - // remove the property $isauthor - document.removeItem(ISAUTHOR); - - // verify type attribute - String aType = document.getItemValueString("type"); - if ("".equals(aType)) { - aType = "document"; - document.replaceItemValue("type", aType); - } - // update type attribute - persistedDocument.setType(aType); - - // update the standard attributes $modified $created and $uniqueID - document.replaceItemValue("$uniqueid", persistedDocument.getId()); - document.replaceItemValue("$created", persistedDocument.getCreated().getTime()); - // synchronize Document.modified and $modified - Calendar cal = Calendar.getInstance(); - persistedDocument.setModified(cal); - document.replaceItemValue("$modified", cal.getTime()); - - // Finally we fire the DocumentEvent ON_DOCUMENT_SAVE - if (documentEvents != null) { - documentEvents.fire(new DocumentEvent(document, DocumentEvent.ON_DOCUMENT_SAVE)); - } else { - logger.warning("Missing CDI support for Event !"); - } - // check consistency of $uniqueid and $created after event was processed. - if ((!persistedDocument.getId().equals(document.getUniqueID())) || (!persistedDocument - .getCreated().getTime().equals(document.getItemValueDate("$created")))) { - throw new InvalidAccessException(InvalidAccessException.INVALID_ID, - "Invalid data after DocumentEvent 'ON_DOCUMENT_SAVE'."); - } + public static final String OPERATION_NOTALLOWED = "OPERATION_NOTALLOWED"; + public static final String INVALID_PARAMETER = "INVALID_PARAMETER"; + public static final String INVALID_UNIQUEID = "INVALID_UNIQUEID"; - // Now prepare document for persisting...... + @Resource + SessionContext ctx; - // update current version number into managed entity! - if (disableOptimisticLocking) { - // in case of optimistic locking is disabled we remove $version - document.removeItem("$Version"); - } - if (!disableOptimisticLocking && document.hasItem(VERSION) - && document.getItemValueInteger(VERSION) > 0) { - // if $version is provided we update the version number of - // the managaed document to ensure that optimisticLock exception is - // handled the right way! - int version = document.getItemValueInteger(VERSION); - persistedDocument.setVersion(version); - } + @Resource(name = "ACCESS_ROLES") + private String accessRoles = ""; - // finally update the data field by cloning the map object (deep copy) - ItemCollection clone = (ItemCollection) document.clone(); - persistedDocument.setData(clone.getAllItems()); + @Resource(name = "DISABLE_OPTIMISTIC_LOCKING") + private Boolean disableOptimisticLocking = false; - /* - * Issue #220 - * - * No em.flush() call - see issue #226 - **/ + @PersistenceContext(unitName = "org.imixs.workflow.jpa") + private EntityManager manager; + + @Inject + private UpdateService indexUpdateService; + + @Inject + private SearchService indexSearchService; + + @Inject + private EventLogService eventLogService; - /* - * issue #226 + @Inject + protected Event documentEvents; + + @Inject + protected Event userGroupEvents; + + @Inject + @ConfigProperty(name = "index.defaultOperator", defaultValue = "AND") + private String indexDefaultOperator; + + /** + * Returns a comma separated list of additional Access-Roles defined for this + * service * - * No em.flush(), em.detach() or em.clear() is needed here. Finally we remove the $version - * property from the ItemCollection returned to the client. This is important for the case that - * the method is called multiple times in one single transaction. + * @return */ + public String getAccessRoles() { + return accessRoles; + } - // remove $version from ItemCollection - document.removeItem(VERSION); + public void setAccessRoles(String accessRoles) { + this.accessRoles = accessRoles; + } - // update the $isauthor flag - document.replaceItemValue(ISAUTHOR, isCallerAuthor(persistedDocument)); + /** + * returns the disable optimistic locking status + * + * @return - true if optimistic locking is disabled + */ + public void setDisableOptimisticLocking(Boolean disableOptimisticLocking) { + this.disableOptimisticLocking = disableOptimisticLocking; + } - // add/update document into lucene index - if (!document.getItemValueBoolean(NOINDEX)) { - addDocumentToIndex(document); - } else { - // remove from index - removeDocumentFromIndex(document.getUniqueID()); + public Boolean getDisableOptimisticLocking() { + return disableOptimisticLocking; } - /* - * issue #230 + /** + * This method returns a list of user names, roles and application groups the + * user belongs to. + *

+ * A client can extend the list of user groups associated with a userId by + * reacting on the CDI event 'UserGrouptEvent'. * - * flag this entity which is still managed + * @see UserGroupEvent + * @return */ - persistedDocument.setPending(true); + public List getUserNameList() { + + List userNameList = new Vector(); + + // Begin with the username + userNameList.add(ctx.getCallerPrincipal().getName().toString()); + // now construct role list + String roleList = "org.imixs.ACCESSLEVEL.READERACCESS,org.imixs.ACCESSLEVEL.AUTHORACCESS,org.imixs.ACCESSLEVEL.EDITORACCESS,org.imixs.ACCESSLEVEL.MANAGERACCESS," + + accessRoles; + // and add each role the user is in to the list + StringTokenizer roleListTokens = new StringTokenizer(roleList, ","); + while (roleListTokens.hasMoreTokens()) { + try { + String testRole = roleListTokens.nextToken().trim(); + if (!"".equals(testRole) && ctx.isCallerInRole(testRole)) + userNameList.add(testRole); + } catch (Exception e) { + // no operation - Role simply not defined + // this could be an configuration/test issue and need not to be + // handled as an error + } + } - if (debug) { - logger.fine("...'" + document.getUniqueID() + "' saved in " - + (System.currentTimeMillis() - lSaveTime) + "ms"); - } - // return the updated document - return document; - } - - /** - * This method adds a single document into the to the Lucene index. Before the document is added - * to the index, a new eventLog is created. The document will be indexed after the method - * flushEventLog is called. This method is called by the LuceneSearchService finder methods. - *

- * The method supports committed read. This means that a running transaction will not read an - * uncommitted document from the Lucene index. - * - * - * @param documentContext - */ - public void addDocumentToIndex(ItemCollection document) { - // skip if the flag 'noindex' = true - if (!document.getItemValueBoolean(DocumentService.NOINDEX)) { - // write a new EventLog entry for each document.... - eventLogService.createEvent(EVENTLOG_TOPIC_INDEX_ADD, document.getUniqueID()); + // To extend UserGroups we fire the CDI Event UserGroupEvent... + if (userGroupEvents != null) { + // create Group Event + UserGroupEvent groupEvent = new UserGroupEvent(ctx.getCallerPrincipal().getName().toString()); + userGroupEvents.fire(groupEvent); + if (groupEvent.getGroups() != null) { + userNameList.addAll(groupEvent.getGroups()); + } + + } else { + logger.warning("Missing CDI support for Event !"); + } + + // String[] applicationGroups = getUserGroupList(); + // if (applicationGroups != null) + // for (String auserRole : applicationGroups) + // userNameList.add(auserRole); + + return userNameList; } - } - - /** - * This method adds a new eventLog for a document to be deleted from the index. The document will - * be removed from the index after the method fluschEventLog is called. This method is called by - * the LuceneSearchService finder method only. - * - * - * @param uniqueID of the workitem to be removed - * @throws PluginException - */ - public void removeDocumentFromIndex(String uniqueID) { - boolean debug = logger.isLoggable(Level.FINE); - long ltime = System.currentTimeMillis(); - eventLogService.createEvent(EVENTLOG_TOPIC_INDEX_REMOVE, uniqueID); - if (debug) { - logger.fine("... update eventLog cache in " + (System.currentTimeMillis() - ltime) - + " ms (1 document to be removed)"); + + /** + * This method returns true, if at least one element of the current UserNameList + * is contained in a given name list. The comparison is case sensitive! + * + * @param nameList + * @return + */ + public boolean isUserContained(List nameList) { + if (nameList == null) { + return false; + } + List userNameList = getUserNameList(); + // check each element of the given nameList + for (String aName : nameList) { + if (aName != null && !aName.isEmpty()) { + if (userNameList.stream().anyMatch(aName::equals)) { + return true; + } + } + } + // not found + return false; } - } - - /** - * This method saves a workitem in a new transaction. The method can be used by plugins to isolate - * a save request from the current transaction context. - * - * To call this method a EJB session context is necessary: - * workitem= sessionContext.getBusinessObject(EntityService.class) - .saveByNewTransaction(workitem); - * - * - * @param itemcol - * @return - * @throws AccessDeniedException - */ - @TransactionAttribute(value = TransactionAttributeType.REQUIRES_NEW) - public ItemCollection saveByNewTransaction(ItemCollection itemcol) throws AccessDeniedException { - return save(itemcol); - } - - /** - * This method loads an ItemCollection from the Database. The method expects a valid $uniqueID to - * identify the Document entity saved before into the database. The method returns null if no - * Document with the corresponding ID exists. - *

- * The method checks if the CallerPrincipal has read access to Document stored in the database. If - * not, the method returns null. - *

- * Note: The method dose not throw an AccessDeniedException if the user is not - * allowed to read the entity to prevent a aggressor with informations about the existence of that - * specific Document. - *

- * The CallerPrincipial need to have at least the access level org.imixs.ACCESSLEVEL.READACCESS - * - *

- * issue #230 - * - * In case a document is not flagged (not saved during same transaction), we detach the loaded - * entity. In case a document is flagged (saved during save transaction) we may not detach it, but - * make a deepCopy (clone) of the document instance. This will avoid the effect, that data written - * to a document get lost in a long running transaction with save and load calls. - * - * @param id - the $uniqueid of the ItemCollection to be loaded - * @return ItemCollection object or null if the Document dose not exist or the CallerPrincipal hat - * insufficient read access. - * - */ - public ItemCollection load(String id) { - boolean debug = logger.isLoggable(Level.FINE); - long lLoadTime = System.currentTimeMillis(); - Document persistedDocument = null; - - if (id == null || id.isEmpty()) { - return null; + + /** + * Test if the caller has a given security role. + * + * @param rolename + * @return true if user is in role + */ + public boolean isUserInRole(String rolename) { + try { + return ctx.isCallerInRole(rolename); + } catch (Exception e) { + // avoid a exception for a role request which is not defined + return false; + } } - persistedDocument = manager.find(Document.class, id); - // create instance of ItemCollection - if (persistedDocument != null && isCallerReader(persistedDocument)) { + /** + * This Method saves an ItemCollection into the database. If the ItemCollection + * is saved the first time the method generates a uniqueID ('$uniqueid') which + * can be used to identify the ItemCollection by its ID. If the ItemCollection + * was saved before, the method updates the existing ItemCollection stored in + * the database. + * + *

+ * The Method returns an updated instance of the ItemCollection containing the + * attributes $modified, $created, and $uniqueId + * + *

+ * The method throws an AccessDeniedException if the CallerPrincipal is not + * allowed to save or update the ItemCollection in the database. The + * CallerPrincipial should have at least the access Role + * org.imixs.ACCESSLEVEL.AUTHORACCESS + * + *

+ * The method adds/updates the document into the lucene index. + * + *

+ * The method returns a itemCollection without the $VersionNumber from the + * persisted entity. (see issue #226) + *

+ * + *

+ * issue #230: + * + * The document will be marked as 'saved' so that the methods load() and + * getDocumentsByQuery() can evaluate this flag. Depending on the state, the + * methods can decide the correct behavior. In general we detach a document in + * the load() and getDocumentsByQuery() method. This is for performance reasons + * and the fact, that a ItemCollection can hold byte arrays which will be copied + * by reference. In cases where these methods are called after a document was + * saved (document is now managed), in one single transaction, the detach call + * will discard the changes made by the save() method. For that reason we flag + * the entity and evaluate this flag in the load method evaluates the save + * status. + * + * + * @param ItemCollection to be saved + * @return updated ItemCollection + * @throws AccessDeniedException + */ + public ItemCollection save(ItemCollection document) throws AccessDeniedException { + boolean debug = logger.isLoggable(Level.FINE); + long lSaveTime = System.currentTimeMillis(); + if (debug) { + logger.finest("......save - ID=" + document.getUniqueID() + ", provided version=" + + document.getItemValueInteger(VERSION)); + } + Document persistedDocument = null; + // Now set flush Mode to COMMIT + manager.setFlushMode(FlushModeType.COMMIT); + + // check if a $uniqueid is available + String sID = document.getItemValueString(WorkflowKernel.UNIQUEID); + if (!sID.isEmpty()) { + // yes so we can try to find the Entity by its primary key + persistedDocument = manager.find(Document.class, sID); + if (debug && persistedDocument == null) { + logger.finest("......Document '" + sID + "' not found!"); + } + } - ItemCollection result = null;// new ItemCollection(); - if (persistedDocument.isPending()) { - // we clone but do not detach + // did the document exist? + if (persistedDocument == null) { + // entity not found in database, create a new instance using the + // provided id. Test if user is allowed to create Entities.... + if (!(ctx.isCallerInRole(ACCESSLEVEL_MANAGERACCESS) || ctx.isCallerInRole(ACCESSLEVEL_EDITORACCESS) + || ctx.isCallerInRole(ACCESSLEVEL_AUTHORACCESS))) { + throw new AccessDeniedException(OPERATION_NOTALLOWED, "You are not allowed to perform this operation"); + } + // create new one with the provided id + persistedDocument = new Document(sID); + // if $Created is provided than overtake this information + Date datCreated = document.getItemValueDate("$created"); + if (datCreated != null) { + Calendar cal = Calendar.getInstance(); + cal.setTime(datCreated); + // Overwrite Creation Date + persistedDocument.setCreated(cal); + } + // now persist the new EntityBean! + if (debug) { + logger.finest("......persist activeEntity"); + } + manager.persist(persistedDocument); + + } else { + // activeEntity exists - verify if current user has write- and + // readaccess + if (!isCallerAuthor(persistedDocument) || !isCallerReader(persistedDocument)) { + throw new AccessDeniedException(OPERATION_NOTALLOWED, "You are not allowed to perform this operation"); + } + + // test if persistedDocument is IMMUTABLE + if (ItemCollection.createByReference(persistedDocument.getData()).getItemValueBoolean(IMMUTABLE)) { + throw new AccessDeniedException(OPERATION_NOTALLOWED, "Operation not allowed, document is immutable!"); + } + // there is no need to merge the persistedDocument because it is + // already managed by JPA! + } + + // after all the persistedDocument is now managed through JPA! if (debug) { - logger.finest("......clone manged entity '" + id + "' pending status=" - + persistedDocument.isPending()); + logger.finest( + "......save - ID=" + document.getUniqueID() + " managed version=" + persistedDocument.getVersion()); } - result = new ItemCollection(persistedDocument.getData()); - } else { - // the document is not managed, so we detach it - result = new ItemCollection(); - result.setAllItems(persistedDocument.getData()); - manager.detach(persistedDocument); - } - - updateMetaData(result, persistedDocument); - - // fire event - if (documentEvents != null) { - documentEvents.fire(new DocumentEvent(result, DocumentEvent.ON_DOCUMENT_LOAD)); - } else { - logger.warning("Missing CDI support for Event !"); - } - if (debug) { - logger.fine("...'" + result.getUniqueID() + "' loaded in " - + (System.currentTimeMillis() - lLoadTime) + "ms"); - } - return result; - } else - return null; - } - - /** - * This method removes an ItemCollection from the database. If the CallerPrincipal is not allowed - * to access the ItemColleciton the method throws an AccessDeniedException. - *

- * The CallerPrincipial should have at least the access Role org.imixs.ACCESSLEVEL.AUTHORACCESS - *

- * Also the method removes the document form the lucene index. - * - * - * @param ItemCollection to be removed - * @throws AccessDeniedException - */ - public void remove(ItemCollection document) throws AccessDeniedException { - if (document == null) { - return; - } + // remove the property $isauthor + document.removeItem(ISAUTHOR); + + // verify type attribute + String aType = document.getItemValueString("type"); + if ("".equals(aType)) { + aType = "document"; + document.replaceItemValue("type", aType); + } + // update type attribute + persistedDocument.setType(aType); - Document persistedDocument = null; - String sID = document.getItemValueString("$uniqueid"); - persistedDocument = manager.find(Document.class, sID); - - if (persistedDocument != null) { - if (!isCallerReader(persistedDocument) || !isCallerAuthor(persistedDocument)) - throw new AccessDeniedException(OPERATION_NOTALLOWED, - "remove - You are not allowed to perform this operation"); - - // fire event - if (documentEvents != null) { - documentEvents.fire(new DocumentEvent(document, DocumentEvent.ON_DOCUMENT_DELETE)); - } else { - logger.warning("Missing CDI support for Event !"); - } - - // remove document... - manager.remove(persistedDocument); - // remove document form index - @see issue #412 - if (!document.getItemValueBoolean(NOINDEX)) { - removeDocumentFromIndex(document.getUniqueID()); - } - - } else - throw new AccessDeniedException(INVALID_UNIQUEID, "remove - invalid $uniqueid"); - } - - /** - * Returns the total hits for a given search query. The provided search term will be extended with - * a users roles to test the read access level of each workitem matching the search term. The - * usernames and user roles will be search lowercase! - * - * @see search(String, int, int, Sort, Operator) - * - * @param sSearchTerm - * @return total hits of search result - * @throws QueryException in case the searchterm is not understandable. - */ - public int count(String searchTerm) throws QueryException { - return count(searchTerm, 0); - } - - /** - * Returns the total hits for a given search query. The provided search term will be extended with - * a users roles to test the read access level of each workitem matching the search term. The - * usernames and user roles will be search lowercase! - * - * The optional param 'maxResult' can be set to overwrite the DEFAULT_MAX_SEARCH_RESULT. - * - * @see search(String, int, int, Sort, Operator) - * - * @param sSearchTerm - * @param maxResult - max search result - * @param defaultOperator - optional to change the default search operator - * - * @return total hits of search result - * @throws QueryException in case the searchterm is not understandable. - */ - public int count(String sSearchTerm, int maxResult) throws QueryException { - indexUpdateService.updateIndex(); - return indexSearchService.getTotalHits(sSearchTerm, maxResult, null); - } - - /** - * Returns the total pages for a given search term and a given page size. - * - * @see count(String sSearchTerm) - * - * @param searchTerm - * @param pageSize - * @return total pages of search result - * @throws QueryException in case the searchterm is not understandable. - */ - public int countPages(String searchTerm, int pageSize) throws QueryException { - double pages = 1; - double count = count(searchTerm); - if (count > 0) { - pages = Math.ceil(count / pageSize); - } - return ((int) pages); - } - - /** - * The method returns a list of ItemCollections from the search-index. The method expects an valid - * Lucene search term. - *

- * The method returns only ItemCollections which are readable by the CallerPrincipal. With the - * pageSize and pageNumber it is possible to paginate. - * - * @param searchTerm - Lucene search term - * @param pageSize - total docs per page - * @param pageIndex - number of page to start (default = 0) - * @return list of ItemCollection elements - * @throws QueryException - * - * @see org.imixs.workflow.engine.index.SearchService - */ - public List find(String searchTerm, int pageSize, int pageIndex) - throws QueryException { - return find(searchTerm, pageSize, pageIndex, null, false); - } - - /** - * The method returns a sorted list of ItemCollections from the search-index. The result list can - * be sorted by a sortField and a sort direction. - *

- * The method expects an valid Lucene search term. The method returns only ItemCollections which - * are readable by the CallerPrincipal. With the pageSize and pageNumber it is possible to - * paginate. - * - * @param searchTerm - Lucene search term - * @param pageSize - total docs per page - * @param pageIndex - number of page to start (default = 0) - * - * @param sortBy -optional field to sort the result - * @param sortReverse - optional sort direction - * - * @return list of ItemCollection elements - * @throws QueryException - * - * @see org.imixs.workflow.engine.index.SearchService - */ - public List find(String searchTerm, int pageSize, int pageIndex, String sortBy, - boolean sortReverse) throws QueryException { - boolean debug = logger.isLoggable(Level.FINE); - if (debug) { - logger.finest("......find - SearchTerm=" + searchTerm + " , pageSize=" + pageSize - + " pageNumber=" + pageIndex + " , sortBy=" + sortBy + " reverse=" + sortReverse); - } - // create sort object - SortOrder sortOrder = null; - if (sortBy != null && !sortBy.isEmpty()) { - // we do not support multi values here - see - // LuceneUpdateService.addItemValues - // it would be possible if we use a SortedSetSortField class here - sortOrder = new SortOrder(sortBy, sortReverse); - } + // update the standard attributes $modified $created and $uniqueID + document.replaceItemValue("$uniqueid", persistedDocument.getId()); + document.replaceItemValue("$created", persistedDocument.getCreated().getTime()); + // synchronize Document.modified and $modified + Calendar cal = Calendar.getInstance(); + persistedDocument.setModified(cal); + document.replaceItemValue("$modified", cal.getTime()); - // flush eventlog (see issue #411) - indexUpdateService.updateIndex(); + // Finally we fire the DocumentEvent ON_DOCUMENT_SAVE + if (documentEvents != null) { + documentEvents.fire(new DocumentEvent(document, DocumentEvent.ON_DOCUMENT_SAVE)); + } else { + logger.warning("Missing CDI support for Event !"); + } + // check consistency of $uniqueid and $created after event was processed. + if ((!persistedDocument.getId().equals(document.getUniqueID())) + || (!persistedDocument.getCreated().getTime().equals(document.getItemValueDate("$created")))) { + throw new InvalidAccessException(InvalidAccessException.INVALID_ID, + "Invalid data after DocumentEvent 'ON_DOCUMENT_SAVE'."); + } - // evaluate default index operator - DefaultOperator defaultOperator = null; + // Now prepare document for persisting...... - if (indexDefaultOperator != null && "OR".equals(indexDefaultOperator.toUpperCase())) { - defaultOperator = DefaultOperator.OR; - } else { - defaultOperator = DefaultOperator.AND; - } - return indexSearchService.search(searchTerm, pageSize, pageIndex, sortOrder, defaultOperator, - false); - - } - - /** - * The method returns a sorted list of Document Stubs from the search-index. A document stub - * contains only the items stored in the search index. These items can be defined by the property - * lucence.indexFieldListStore. See the LuceneUpdateService for details. - *

- * The result list can be sorted by a sortField and a sort direction. - *

- * The method expects an valid Lucene search term. The method returns only ItemCollections which - * are readable by the CallerPrincipal. With the pageSize and pageNumber it is possible to - * paginate. - *

- * - * @param searchTerm - Lucene search term - * @param pageSize - total docs per page - * @param pageIndex - number of page to start (default = 0) - * - * @param sortBy -optional field to sort the result - * @param sortReverse - optional sort direction - * - * @return list of ItemCollection elements - * @throws QueryException - * - * @see org.imixs.workflow.engine.index.SearchService - */ - public List findStubs(String searchTerm, int pageSize, int pageIndex, - String sortBy, boolean sortReverse) throws QueryException { - boolean debug = logger.isLoggable(Level.FINE); - if (debug) { - logger.finest("......find - SearchTerm=" + searchTerm + " , pageSize=" + pageSize - + " pageNumber=" + pageIndex + " , sortBy=" + sortBy + " reverse=" + sortReverse); + // update current version number into managed entity! + if (disableOptimisticLocking) { + // in case of optimistic locking is disabled we remove $version + document.removeItem("$Version"); + } + if (!disableOptimisticLocking && document.hasItem(VERSION) && document.getItemValueInteger(VERSION) > 0) { + // if $version is provided we update the version number of + // the managaed document to ensure that optimisticLock exception is + // handled the right way! + int version = document.getItemValueInteger(VERSION); + persistedDocument.setVersion(version); + } + + // finally update the data field by cloning the map object (deep copy) + ItemCollection clone = (ItemCollection) document.clone(); + persistedDocument.setData(clone.getAllItems()); + + /* + * Issue #220 + * + * No em.flush() call - see issue #226 + **/ + + /* + * issue #226 + * + * No em.flush(), em.detach() or em.clear() is needed here. Finally we remove + * the $version property from the ItemCollection returned to the client. This is + * important for the case that the method is called multiple times in one single + * transaction. + */ + + // remove $version from ItemCollection + document.removeItem(VERSION); + + // update the $isauthor flag + document.replaceItemValue(ISAUTHOR, isCallerAuthor(persistedDocument)); + + // add/update document into lucene index + if (!document.getItemValueBoolean(NOINDEX)) { + addDocumentToIndex(document); + } else { + // remove from index + removeDocumentFromIndex(document.getUniqueID()); + } + + /* + * issue #230 + * + * flag this entity which is still managed + */ + persistedDocument.setPending(true); + + if (debug) { + logger.fine( + "...'" + document.getUniqueID() + "' saved in " + (System.currentTimeMillis() - lSaveTime) + "ms"); + } + // return the updated document + return document; } - // create sort object - SortOrder sortOrder = null; - if (sortBy != null && !sortBy.isEmpty()) { - // we do not support multi values here - see - // LuceneUpdateService.addItemValues - // it would be possible if we use a SortedSetSortField class here - sortOrder = new SortOrder(sortBy, sortReverse); + + /** + * This method adds a single document into the to the Lucene index. Before the + * document is added to the index, a new eventLog is created. The document will + * be indexed after the method flushEventLog is called. This method is called by + * the LuceneSearchService finder methods. + *

+ * The method supports committed read. This means that a running transaction + * will not read an uncommitted document from the Lucene index. + * + * + * @param documentContext + */ + public void addDocumentToIndex(ItemCollection document) { + // skip if the flag 'noindex' = true + if (!document.getItemValueBoolean(DocumentService.NOINDEX)) { + // write a new EventLog entry for each document.... + eventLogService.createEvent(EVENTLOG_TOPIC_INDEX_ADD, document.getUniqueID()); + } } - // flush eventlog (see issue #411) - indexUpdateService.updateIndex(); + /** + * This method adds a new eventLog for a document to be deleted from the index. + * The document will be removed from the index after the method fluschEventLog + * is called. This method is called by the LuceneSearchService finder method + * only. + * + * + * @param uniqueID of the workitem to be removed + * @throws PluginException + */ + public void removeDocumentFromIndex(String uniqueID) { + boolean debug = logger.isLoggable(Level.FINE); + long ltime = System.currentTimeMillis(); + eventLogService.createEvent(EVENTLOG_TOPIC_INDEX_REMOVE, uniqueID); + if (debug) { + logger.fine("... update eventLog cache in " + (System.currentTimeMillis() - ltime) + + " ms (1 document to be removed)"); + } + } - // evaluate default index operator - DefaultOperator defaultOperator = null;; - if (indexDefaultOperator != null && "OR".equals(indexDefaultOperator.toUpperCase())) { - defaultOperator = DefaultOperator.OR; - } else { - defaultOperator = DefaultOperator.AND; + /** + * This method saves a workitem in a new transaction. The method can be used by + * plugins to isolate a save request from the current transaction context. + * + * To call this method a EJB session context is necessary: + * workitem= sessionContext.getBusinessObject(EntityService.class) + .saveByNewTransaction(workitem); + * + * + * @param itemcol + * @return + * @throws AccessDeniedException + */ + @TransactionAttribute(value = TransactionAttributeType.REQUIRES_NEW) + public ItemCollection saveByNewTransaction(ItemCollection itemcol) throws AccessDeniedException { + return save(itemcol); } - // find stubs only! - return indexSearchService.search(searchTerm, pageSize, pageIndex, sortOrder, defaultOperator, - true); - - } - - /** - * The method returns a collection of ItemCollections referred by a $uniqueid. - *

- * The method returns only ItemCollections which are readable by the CallerPrincipal. With the - * pageSize and pageNumber it is possible to paginate. - * - * - * @param uniqueIdRef - $uniqueId to be referred by the collected documents - * @param pageSize - total docs per page - * @param pageIndex - number of page to start (default = 0) - * @return resultset - * - */ - public List findDocumentsByRef(String uniqueIdRef, int pageSize, int pageIndex) { - String searchTerm = "(" + "$uniqueidref:\"" + uniqueIdRef + "\")"; - try { - return find(searchTerm, pageSize, pageIndex); - } catch (QueryException e) { - logger.severe("findDocumentsByRef - invalid query: " + e.getMessage()); - return null; + /** + * This method loads an ItemCollection from the Database. The method expects a + * valid $uniqueID to identify the Document entity saved before into the + * database. The method returns null if no Document with the corresponding ID + * exists. + *

+ * The method checks if the CallerPrincipal has read access to Document stored + * in the database. If not, the method returns null. + *

+ * Note: The method dose not throw an AccessDeniedException if + * the user is not allowed to read the entity to prevent a aggressor with + * informations about the existence of that specific Document. + *

+ * The CallerPrincipial need to have at least the access level + * org.imixs.ACCESSLEVEL.READACCESS + * + *

+ * issue #230 + * + * In case a document is not flagged (not saved during same transaction), we + * detach the loaded entity. In case a document is flagged (saved during save + * transaction) we may not detach it, but make a deepCopy (clone) of the + * document instance. This will avoid the effect, that data written to a + * document get lost in a long running transaction with save and load calls. + * + * @param id - the $uniqueid of the ItemCollection to be loaded + * @return ItemCollection object or null if the Document dose not exist or the + * CallerPrincipal hat insufficient read access. + * + */ + public ItemCollection load(String id) { + boolean debug = logger.isLoggable(Level.FINE); + long lLoadTime = System.currentTimeMillis(); + Document persistedDocument = null; + + if (id == null || id.isEmpty()) { + return null; + } + persistedDocument = manager.find(Document.class, id); + + // create instance of ItemCollection + if (persistedDocument != null && isCallerReader(persistedDocument)) { + + ItemCollection result = null;// new ItemCollection(); + if (persistedDocument.isPending()) { + // we clone but do not detach + if (debug) { + logger.finest( + "......clone manged entity '" + id + "' pending status=" + persistedDocument.isPending()); + } + result = new ItemCollection(persistedDocument.getData()); + } else { + // the document is not managed, so we detach it + result = new ItemCollection(); + result.setAllItems(persistedDocument.getData()); + manager.detach(persistedDocument); + } + + updateMetaData(result, persistedDocument); + + // fire event + if (documentEvents != null) { + documentEvents.fire(new DocumentEvent(result, DocumentEvent.ON_DOCUMENT_LOAD)); + } else { + logger.warning("Missing CDI support for Event !"); + } + if (debug) { + logger.fine("...'" + result.getUniqueID() + "' loaded in " + (System.currentTimeMillis() - lLoadTime) + + "ms"); + } + return result; + } else + return null; } - } - - /** - * Returns an unordered list of all documents of a specific type. The method throws an - * InvalidAccessException in case no type attribute is defined. - * - * @param type - * @return - * @throws InvalidAccessException - */ - public List getDocumentsByType(String type) { - if (type == null || type.isEmpty()) { - throw new InvalidAccessException(INVALID_PARAMETER, "undefined type attribute"); + + /** + * This method removes an ItemCollection from the database. If the + * CallerPrincipal is not allowed to access the ItemColleciton the method throws + * an AccessDeniedException. + *

+ * The CallerPrincipial should have at least the access Role + * org.imixs.ACCESSLEVEL.AUTHORACCESS + *

+ * Also the method removes the document form the lucene index. + * + * + * @param ItemCollection to be removed + * @throws AccessDeniedException + */ + public void remove(ItemCollection document) throws AccessDeniedException { + if (document == null) { + return; + } + + Document persistedDocument = null; + String sID = document.getItemValueString("$uniqueid"); + persistedDocument = manager.find(Document.class, sID); + + if (persistedDocument != null) { + if (!isCallerReader(persistedDocument) || !isCallerAuthor(persistedDocument)) + throw new AccessDeniedException(OPERATION_NOTALLOWED, + "remove - You are not allowed to perform this operation"); + + // fire event + if (documentEvents != null) { + documentEvents.fire(new DocumentEvent(document, DocumentEvent.ON_DOCUMENT_DELETE)); + } else { + logger.warning("Missing CDI support for Event !"); + } + + // remove document... + manager.remove(persistedDocument); + // remove document form index - @see issue #412 + if (!document.getItemValueBoolean(NOINDEX)) { + removeDocumentFromIndex(document.getUniqueID()); + } + + } else + throw new AccessDeniedException(INVALID_UNIQUEID, "remove - invalid $uniqueid"); } - String query = "SELECT document FROM Document AS document "; - query += " WHERE document.type = '" + type + "'"; - query += " ORDER BY document.created DESC"; - return getDocumentsByQuery(query); - } - - /** - * Returns all documents of by JPQL statement - * - * @param query - JPQL statement - * @return - * - */ - public List getDocumentsByQuery(String query) { - return getDocumentsByQuery(query, -1); - - } - - /** - * Returns all documents of by JPQL statement. - * - * @param query - JPQL statement - * @param maxResult - maximum result set - * @return - * - */ - public List getDocumentsByQuery(String query, int maxResult) { - return getDocumentsByQuery(query, 0, maxResult); - } - - /** - * Returns all documents of by JPQL statement. - * - * @param query - JPQL statement - * @param maxResult - maximum result set - * @return - * - */ - public List getDocumentsByQuery(String query, int firstResult, int maxResult) { - boolean debug = logger.isLoggable(Level.FINE); - List result = new ArrayList(); - Query q = manager.createQuery(query); - - // setMaxResults ? - if (maxResult > 0) { - q.setMaxResults(maxResult); + /** + * Returns the total hits for a given search query. The provided search term + * will be extended with a users roles to test the read access level of each + * workitem matching the search term. The usernames and user roles will be + * search lowercase! + * + * @see search(String, int, int, Sort, Operator) + * + * @param sSearchTerm + * @return total hits of search result + * @throws QueryException in case the searchterm is not understandable. + */ + public int count(String searchTerm) throws QueryException { + return count(searchTerm, 0); } - if (firstResult > 0) { - q.setFirstResult(firstResult); + /** + * Returns the total hits for a given search query. The provided search term + * will be extended with a users roles to test the read access level of each + * workitem matching the search term. The usernames and user roles will be + * search lowercase! + * + * The optional param 'maxResult' can be set to overwrite the + * DEFAULT_MAX_SEARCH_RESULT. + * + * @see search(String, int, int, Sort, Operator) + * + * @param sSearchTerm + * @param maxResult - max search result + * @param defaultOperator - optional to change the default search operator + * + * @return total hits of search result + * @throws QueryException in case the searchterm is not understandable. + */ + public int count(String sSearchTerm, int maxResult) throws QueryException { + indexUpdateService.updateIndex(); + return indexSearchService.getTotalHits(sSearchTerm, maxResult, null); } - long l = System.currentTimeMillis(); - @SuppressWarnings("unchecked") - Collection documentList = q.getResultList(); + /** + * Returns the total pages for a given search term and a given page size. + * + * @see count(String sSearchTerm) + * + * @param searchTerm + * @param pageSize + * @return total pages of search result + * @throws QueryException in case the searchterm is not understandable. + */ + public int countPages(String searchTerm, int pageSize) throws QueryException { + double pages = 1; + double count = count(searchTerm); + if (count > 0) { + pages = Math.ceil(count / pageSize); + } + return ((int) pages); + } - if (documentList == null) { - if (debug) { - logger.finest("......getDocumentsByQuery - no ducuments found."); - } - return result; + /** + * The method returns a list of ItemCollections from the search-index. The + * method expects an valid Lucene search term. + *

+ * The method returns only ItemCollections which are readable by the + * CallerPrincipal. With the pageSize and pageNumber it is possible to paginate. + * + * @param searchTerm - Lucene search term + * @param pageSize - total docs per page + * @param pageIndex - number of page to start (default = 0) + * @return list of ItemCollection elements + * @throws QueryException + * + * @see org.imixs.workflow.engine.index.SearchService + */ + public List find(String searchTerm, int pageSize, int pageIndex) throws QueryException { + return find(searchTerm, pageSize, pageIndex, null, false); } - // filter result set by read access - for (Document doc : documentList) { - if (isCallerReader(doc)) { + /** + * The method returns a sorted list of ItemCollections from the search-index. + * The result list can be sorted by a sortField and a sort direction. + *

+ * The method expects an valid Lucene search term. The method returns only + * ItemCollections which are readable by the CallerPrincipal. With the pageSize + * and pageNumber it is possible to paginate. + * + * @param searchTerm - Lucene search term + * @param pageSize - total docs per page + * @param pageIndex - number of page to start (default = 0) + * + * @param sortBy -optional field to sort the result + * @param sortReverse - optional sort direction + * + * @return list of ItemCollection elements + * @throws QueryException + * + * @see org.imixs.workflow.engine.index.SearchService + */ + public List find(String searchTerm, int pageSize, int pageIndex, String sortBy, boolean sortReverse) + throws QueryException { + boolean debug = logger.isLoggable(Level.FINE); + if (debug) { + logger.finest("......find - SearchTerm=" + searchTerm + " , pageSize=" + pageSize + " pageNumber=" + + pageIndex + " , sortBy=" + sortBy + " reverse=" + sortReverse); + } + // create sort object + SortOrder sortOrder = null; + if (sortBy != null && !sortBy.isEmpty()) { + // we do not support multi values here - see + // LuceneUpdateService.addItemValues + // it would be possible if we use a SortedSetSortField class here + sortOrder = new SortOrder(sortBy, sortReverse); + } - ItemCollection _tmp = null; + // flush eventlog (see issue #411) + indexUpdateService.updateIndex(); - if (doc.isPending()) { - // we clone but do not detach - if (debug) { - logger.finest("......clone manged entity '" + doc.getId() + "' pending status=" - + doc.isPending()); - } - _tmp = new ItemCollection(doc.getData()); + // evaluate default index operator + DefaultOperator defaultOperator = null; + + if (indexDefaultOperator != null && "OR".equals(indexDefaultOperator.toUpperCase())) { + defaultOperator = DefaultOperator.OR; } else { - // the document is not managed, so we detach it - _tmp = new ItemCollection(); - _tmp.setAllItems(doc.getData()); - manager.detach(doc); + defaultOperator = DefaultOperator.AND; } + return indexSearchService.search(searchTerm, pageSize, pageIndex, sortOrder, defaultOperator, false); - updateMetaData(_tmp, doc); - - result.add(_tmp); - } - } - if (debug) { - logger.fine("...getDocumentsByQuery - found " + documentList.size() + " documents in " - + (System.currentTimeMillis() - l) + " ms"); - } - return result; - } - - /** - * This method creates a backup of the result set form a Lucene search query. The document list - * will be stored into the file system. The method stores the Map from the ItemCollection to be - * independent from version upgrades. To manage large dataSets the method reads the documents in - * smaller blocks - * - * @param entities - * @throws IOException - * @throws QueryException - */ - public void backup(String query, String filePath) throws IOException, QueryException { - boolean hasMoreData = true; - int JUNK_SIZE = 100; - long totalcount = 0; - int pageIndex = 0; - int icount = 0; - - logger.info("backup - starting..."); - logger.info("backup - query=" + query); - logger.info("backup - target=" + filePath); - - if (filePath == null || filePath.isEmpty()) { - logger.severe("Invalid FilePath!"); - return; } - FileOutputStream fos = new FileOutputStream(filePath); - ObjectOutputStream out = new ObjectOutputStream(fos); - while (hasMoreData) { - // read a junk.... - - Collection col = find(query, JUNK_SIZE, pageIndex); - totalcount = totalcount + col.size(); - logger.info("backup - processing...... " + col.size() + " documents read...."); - - if (col.size() < JUNK_SIZE) { - hasMoreData = false; - logger.finest("......all data read."); - } else { - pageIndex++; - logger.finest("......next page..."); - } - - for (ItemCollection aworkitem : col) { - // get serialized data - Map hmap = aworkitem.getAllItems(); - // write object - out.writeObject(hmap); - icount++; - } - } - out.close(); - logger.info("backup - finished: " + icount + " documents read totaly."); - } - - /** - * This method restores a backup from the file system and imports the Documents into the database. - * - * @param filepath - * @throws IOException - */ - @SuppressWarnings({"rawtypes", "unchecked"}) - public void restore(String filePath) throws IOException { - int JUNK_SIZE = 100; - long totalcount = 0; - long errorCount = 0; - int icount = 0; - - FileInputStream fis = new FileInputStream(filePath); - ObjectInputStream in = new ObjectInputStream(fis); - logger.info("...starting restor form file " + filePath + "..."); - long l = System.currentTimeMillis(); - while (true) { - try { - // read one more object - Map hmap = (Map) in.readObject(); - ItemCollection itemCol = new ItemCollection(hmap); - // remove the $version property! - itemCol.removeItem(VERSION); - // now save imported data - // issue #407 - call new transaction context... - itemCol = ctx.getBusinessObject(DocumentService.class).saveByNewTransaction(itemCol); - - totalcount++; - icount++; - if (icount >= JUNK_SIZE) { - icount = 0; - logger.info("...restored " + totalcount + " document in " - + (System.currentTimeMillis() - l) + "ms...."); - l = System.currentTimeMillis(); + /** + * The method returns a sorted list of Document Stubs from the search-index. A + * document stub contains only the items stored in the search index. These items + * can be defined by the property lucence.indexFieldListStore. See + * the LuceneUpdateService for details. + *

+ * The result list can be sorted by a sortField and a sort direction. + *

+ * The method expects an valid Lucene search term. The method returns only + * ItemCollections which are readable by the CallerPrincipal. With the pageSize + * and pageNumber it is possible to paginate. + *

+ * + * @param searchTerm - Lucene search term + * @param pageSize - total docs per page + * @param pageIndex - number of page to start (default = 0) + * + * @param sortBy -optional field to sort the result + * @param sortReverse - optional sort direction + * + * @return list of ItemCollection elements + * @throws QueryException + * + * @see org.imixs.workflow.engine.index.SearchService + */ + public List findStubs(String searchTerm, int pageSize, int pageIndex, String sortBy, + boolean sortReverse) throws QueryException { + boolean debug = logger.isLoggable(Level.FINE); + if (debug) { + logger.finest("......find - SearchTerm=" + searchTerm + " , pageSize=" + pageSize + " pageNumber=" + + pageIndex + " , sortBy=" + sortBy + " reverse=" + sortReverse); + } + // create sort object + SortOrder sortOrder = null; + if (sortBy != null && !sortBy.isEmpty()) { + // we do not support multi values here - see + // LuceneUpdateService.addItemValues + // it would be possible if we use a SortedSetSortField class here + sortOrder = new SortOrder(sortBy, sortReverse); } - } catch (java.io.EOFException eofe) { - break; - } catch (ClassNotFoundException e) { - errorCount++; - logger.warning("...error importing workitem at position " + (totalcount + errorCount) - + " Error: " + e.getMessage()); - } catch (AccessDeniedException e) { - errorCount++; - logger.warning("...error importing workitem at position " + (totalcount + errorCount) - + " Error: " + e.getMessage()); - } - } - in.close(); + // flush eventlog (see issue #411) + indexUpdateService.updateIndex(); - String loginfo = "Import successfull! " + totalcount + " Entities imported. " + errorCount - + " Errors. Import FileName:" + filePath; + // evaluate default index operator + DefaultOperator defaultOperator = null; + ; + if (indexDefaultOperator != null && "OR".equals(indexDefaultOperator.toUpperCase())) { + defaultOperator = DefaultOperator.OR; + } else { + defaultOperator = DefaultOperator.AND; + } - logger.info(loginfo); - } + // find stubs only! + return indexSearchService.search(searchTerm, pageSize, pageIndex, sortOrder, defaultOperator, true); - /** - * Verifies if the caller has write access to the current ItemCollection - * - * @return - */ - public boolean isAuthor(ItemCollection itemcol) { - @SuppressWarnings("unchecked") - List writeAccessList = itemcol.getItemValue(WRITEACCESS); + } /** - * 1.) org.imixs.ACCESSLEVEL.NOACCESS allways false - now write access! + * The method returns a collection of ItemCollections referred by a $uniqueid. + *

+ * The method returns only ItemCollections which are readable by the + * CallerPrincipal. With the pageSize and pageNumber it is possible to paginate. + * + * + * @param uniqueIdRef - $uniqueId to be referred by the collected documents + * @param pageSize - total docs per page + * @param pageIndex - number of page to start (default = 0) + * @return resultset + * */ - if (ctx.isCallerInRole(ACCESSLEVEL_NOACCESS)) - return false; + public List findDocumentsByRef(String uniqueIdRef, int pageSize, int pageIndex) { + String searchTerm = "(" + "$uniqueidref:\"" + uniqueIdRef + "\")"; + try { + return find(searchTerm, pageSize, pageIndex); + } catch (QueryException e) { + logger.severe("findDocumentsByRef - invalid query: " + e.getMessage()); + return null; + } + } /** - * 2.) org.imixs.ACCESSLEVEL.MANAGERACCESS or org.imixs.ACCESSLEVEL.EDITOR Always true - grant - * writeaccess. + * Returns an unordered list of all documents of a specific type. The method + * throws an InvalidAccessException in case no type attribute is defined. + * + * @param type + * @return + * @throws InvalidAccessException */ - if (ctx.isCallerInRole(ACCESSLEVEL_MANAGERACCESS) - || ctx.isCallerInRole(ACCESSLEVEL_EDITORACCESS)) - return true; + public List getDocumentsByType(String type) { + if (type == null || type.isEmpty()) { + throw new InvalidAccessException(INVALID_PARAMETER, "undefined type attribute"); + } + + String query = "SELECT document FROM Document AS document "; + query += " WHERE document.type = '" + type + "'"; + query += " ORDER BY document.created DESC"; + return getDocumentsByQuery(query); + } /** - * 2.) org.imixs.ACCESSLEVEL.AUTHOR + * Returns all documents of by JPQL statement + * + * @param query - JPQL statement + * @return * - * check write access in detail */ + public List getDocumentsByQuery(String query) { + return getDocumentsByQuery(query, -1); - if (ctx.isCallerInRole(ACCESSLEVEL_AUTHORACCESS)) { - if (isUserContained(writeAccessList)) { - // user role known - grant access - return true; - } } - return false; - } - - /** - * This method udates the metadata of a new loaded ItemCollection based on the document - * attributes. - *

- * The metadata which is updated is: - *

    - *
  • $version - set to current version if OptimisticLocking is not disabled
  • - *
  • $modified - the modify timestamp from the document entity
  • - *
  • $isauthor - computed on the current users access level
  • - *
- * - * @see issue #497 - * @param itemColection - * @param doc - */ - private void updateMetaData(ItemCollection itemColection, Document doc) { - // if disable Optimistic Locking is TRUE we do not add the - // version number - if (disableOptimisticLocking) { - itemColection.removeItem(VERSION); - } else { - itemColection.replaceItemValue(VERSION, doc.getVersion()); + + /** + * Returns all documents of by JPQL statement. + * + * @param query - JPQL statement + * @param maxResult - maximum result set + * @return + * + */ + public List getDocumentsByQuery(String query, int maxResult) { + return getDocumentsByQuery(query, 0, maxResult); } - // Update $modified base on doc.getModified! (see issue #497) - itemColection.replaceItemValue("$modified", doc.getModified().getTime()); + /** + * Returns all documents of by JPQL statement. + * + * @param query - JPQL statement + * @param maxResult - maximum result set + * @return + * + */ + public List getDocumentsByQuery(String query, int firstResult, int maxResult) { + boolean debug = logger.isLoggable(Level.FINE); + List result = new ArrayList(); + Query q = manager.createQuery(query); + + // setMaxResults ? + if (maxResult > 0) { + q.setMaxResults(maxResult); + } - // update the $isauthor flag - itemColection.replaceItemValue(ISAUTHOR, isCallerAuthor(doc)); - } + if (firstResult > 0) { + q.setFirstResult(firstResult); + } - /** - * This method checks if the Caller Principal has read access for the document. - * - * @return true if user has readaccess - */ - private boolean isCallerReader(Document document) { + long l = System.currentTimeMillis(); + @SuppressWarnings("unchecked") + Collection documentList = q.getResultList(); - ItemCollection itemcol = ItemCollection.createByReference(document.getData()); + if (documentList == null) { + if (debug) { + logger.finest("......getDocumentsByQuery - no ducuments found."); + } + return result; + } - @SuppressWarnings("unchecked") - List readAccessList = itemcol.getItemValue(READACCESS); + // filter result set by read access + for (Document doc : documentList) { + if (isCallerReader(doc)) { + + ItemCollection _tmp = null; + + if (doc.isPending()) { + // we clone but do not detach + if (debug) { + logger.finest( + "......clone manged entity '" + doc.getId() + "' pending status=" + doc.isPending()); + } + _tmp = new ItemCollection(doc.getData()); + } else { + // the document is not managed, so we detach it + _tmp = new ItemCollection(); + _tmp.setAllItems(doc.getData()); + manager.detach(doc); + } + + updateMetaData(_tmp, doc); + + result.add(_tmp); + } + } + if (debug) { + logger.fine("...getDocumentsByQuery - found " + documentList.size() + " documents in " + + (System.currentTimeMillis() - l) + " ms"); + } + return result; + } /** - * 1.) org.imixs.ACCESSLEVEL.NOACCESS + * This method creates a backup of the result set form a Lucene search query. + * The document list will be stored into the file system. The method stores the + * Map from the ItemCollection to be independent from version upgrades. To + * manage large dataSets the method reads the documents in smaller blocks * - * always = false -> no access + * @param entities + * @throws IOException + * @throws QueryException */ + public void backup(String query, String filePath) throws IOException, QueryException { + boolean hasMoreData = true; + int JUNK_SIZE = 100; + long totalcount = 0; + int pageIndex = 0; + int icount = 0; + + logger.info("backup - starting..."); + logger.info("backup - query=" + query); + logger.info("backup - target=" + filePath); + + if (filePath == null || filePath.isEmpty()) { + logger.severe("Invalid FilePath!"); + return; + } - if (ctx.isCallerInRole(ACCESSLEVEL_NOACCESS)) - return false; + FileOutputStream fos = new FileOutputStream(filePath); + ObjectOutputStream out = new ObjectOutputStream(fos); + while (hasMoreData) { + // read a junk.... + + Collection col = find(query, JUNK_SIZE, pageIndex); + totalcount = totalcount + col.size(); + logger.info("backup - processing...... " + col.size() + " documents read...."); + + if (col.size() < JUNK_SIZE) { + hasMoreData = false; + logger.finest("......all data read."); + } else { + pageIndex++; + logger.finest("......next page..."); + } + + for (ItemCollection aworkitem : col) { + // get serialized data + Map hmap = aworkitem.getAllItems(); + // write object + out.writeObject(hmap); + icount++; + } + } + out.close(); + logger.info("backup - finished: " + icount + " documents read totaly."); + } /** - * 2.) org.imixs.ACCESSLEVEL.MANAGERACCESS + * This method restores a backup from the file system and imports the Documents + * into the database. * - * always = true -> grant access. + * @param filepath + * @throws IOException */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + public void restore(String filePath) throws IOException { + int JUNK_SIZE = 100; + long totalcount = 0; + long errorCount = 0; + int icount = 0; + + FileInputStream fis = new FileInputStream(filePath); + ObjectInputStream in = new ObjectInputStream(fis); + logger.info("...starting restor form file " + filePath + "..."); + long l = System.currentTimeMillis(); + while (true) { + try { + // read one more object + Map hmap = (Map) in.readObject(); + ItemCollection itemCol = new ItemCollection(hmap); + // remove the $version property! + itemCol.removeItem(VERSION); + // now save imported data + // issue #407 - call new transaction context... + itemCol = ctx.getBusinessObject(DocumentService.class).saveByNewTransaction(itemCol); + + totalcount++; + icount++; + if (icount >= JUNK_SIZE) { + icount = 0; + logger.info("...restored " + totalcount + " document in " + (System.currentTimeMillis() - l) + + "ms...."); + l = System.currentTimeMillis(); + } + + } catch (java.io.EOFException eofe) { + break; + } catch (ClassNotFoundException e) { + errorCount++; + logger.warning("...error importing workitem at position " + (totalcount + errorCount) + " Error: " + + e.getMessage()); + } catch (AccessDeniedException e) { + errorCount++; + logger.warning("...error importing workitem at position " + (totalcount + errorCount) + " Error: " + + e.getMessage()); + } + } + in.close(); + + String loginfo = "Import successfull! " + totalcount + " Entities imported. " + errorCount + + " Errors. Import FileName:" + filePath; - if (ctx.isCallerInRole(ACCESSLEVEL_MANAGERACCESS)) - return true; + logger.info(loginfo); + } /** - * 2.) org.imixs.ACCESSLEVEL.EDITOR org.imixs.ACCESSLEVEL.AUTHOR ACCESSLEVEL.READER + * Verifies if the caller has write access to the current ItemCollection * - * check read access + * @return */ - if (isEmptyList(readAccessList) || isUserContained(readAccessList)) { - return true; + public boolean isAuthor(ItemCollection itemcol) { + @SuppressWarnings("unchecked") + List writeAccessList = itemcol.getItemValue(WRITEACCESS); + + /** + * 1.) org.imixs.ACCESSLEVEL.NOACCESS allways false - now write access! + */ + if (ctx.isCallerInRole(ACCESSLEVEL_NOACCESS)) + return false; + + /** + * 2.) org.imixs.ACCESSLEVEL.MANAGERACCESS or org.imixs.ACCESSLEVEL.EDITOR + * Always true - grant writeaccess. + */ + if (ctx.isCallerInRole(ACCESSLEVEL_MANAGERACCESS) || ctx.isCallerInRole(ACCESSLEVEL_EDITORACCESS)) + return true; + + /** + * 2.) org.imixs.ACCESSLEVEL.AUTHOR + * + * check write access in detail + */ + + if (ctx.isCallerInRole(ACCESSLEVEL_AUTHORACCESS)) { + if (isUserContained(writeAccessList)) { + // user role known - grant access + return true; + } + } + return false; } - return false; - } - - /** - * Verifies if the caller has write access to the given ItemCollection (document). - * - * @return true if the current user has author access - */ - private boolean isCallerAuthor(Document document) { - ItemCollection itemcol = ItemCollection.createByReference(document.getData()); - return isAuthor(itemcol); - } - - /** - * This method returns true if the given list is empty or contains only null or '' values. - * - * @param aList - * @return - */ - public boolean isEmptyList(List aList) { - if (aList == null || aList.size() == 0) { - return true; + /** + * This method udates the metadata of a new loaded ItemCollection based on the + * document attributes. + *

+ * The metadata which is updated is: + *

    + *
  • $version - set to current version if OptimisticLocking is not + * disabled
  • + *
  • $modified - the modify timestamp from the document entity
  • + *
  • $isauthor - computed on the current users access level
  • + *
+ * + * @see issue #497 + * @param itemColection + * @param doc + */ + private void updateMetaData(ItemCollection itemColection, Document doc) { + // if disable Optimistic Locking is TRUE we do not add the + // version number + if (disableOptimisticLocking) { + itemColection.removeItem(VERSION); + } else { + itemColection.replaceItemValue(VERSION, doc.getVersion()); + } + + // Update $modified base on doc.getModified! (see issue #497) + itemColection.replaceItemValue("$modified", doc.getModified().getTime()); + + // update the $isauthor flag + itemColection.replaceItemValue(ISAUTHOR, isCallerAuthor(doc)); } - // check each element - for (String aEntry : aList) { - if (aEntry != null && !aEntry.isEmpty()) { + + /** + * This method checks if the Caller Principal has read access for the document. + * + * @return true if user has readaccess + */ + private boolean isCallerReader(Document document) { + + ItemCollection itemcol = ItemCollection.createByReference(document.getData()); + + @SuppressWarnings("unchecked") + List readAccessList = itemcol.getItemValue(READACCESS); + + /** + * 1.) org.imixs.ACCESSLEVEL.NOACCESS + * + * always = false -> no access + */ + + if (ctx.isCallerInRole(ACCESSLEVEL_NOACCESS)) + return false; + + /** + * 2.) org.imixs.ACCESSLEVEL.MANAGERACCESS + * + * always = true -> grant access. + */ + + if (ctx.isCallerInRole(ACCESSLEVEL_MANAGERACCESS)) + return true; + + /** + * 2.) org.imixs.ACCESSLEVEL.EDITOR org.imixs.ACCESSLEVEL.AUTHOR + * ACCESSLEVEL.READER + * + * check read access + */ + if (isEmptyList(readAccessList) || isUserContained(readAccessList)) { + return true; + } + return false; - } } - return true; - } + + /** + * Verifies if the caller has write access to the given ItemCollection + * (document). + * + * @return true if the current user has author access + */ + private boolean isCallerAuthor(Document document) { + ItemCollection itemcol = ItemCollection.createByReference(document.getData()); + return isAuthor(itemcol); + } + + /** + * This method returns true if the given list is empty or contains only null or + * '' values. + * + * @param aList + * @return + */ + public boolean isEmptyList(List aList) { + if (aList == null || aList.size() == 0) { + return true; + } + // check each element + for (String aEntry : aList) { + if (aEntry != null && !aEntry.isEmpty()) { + return false; + } + } + return true; + } } diff --git a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/EventLogService.java b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/EventLogService.java index fb66e0e59..8534d2520 100644 --- a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/EventLogService.java +++ b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/EventLogService.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *
- *  Imixs Workflow 
+/*  
+ *  Imixs-Workflow 
+ *  
  *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
  *  http://www.imixs.com
  *  
@@ -22,10 +22,9 @@
  *      https://github.com/imixs/imixs-workflow
  *  
  *  Contributors:  
- *      Imixs Software Solutions GmbH - initial API and implementation
+ *      Imixs Software Solutions GmbH - Project Management
  *      Ralph Soika - Software Developer
- * 
- *******************************************************************************/ + */ package org.imixs.workflow.engine; @@ -45,14 +44,15 @@ /** * The EventLogService is a service to create and access log events. *

- * An event that occurs during an update or a processing function within a transaction becomes a - * fact when the transaction completes successfully. The EventLogService can be used to create this - * kind of "Change Data Capture" events. An example is the LuceneUpdateService, which should update - * the index of a document only if the document was successfully written to the database. + * An event that occurs during an update or a processing function within a + * transaction becomes a fact when the transaction completes successfully. The + * EventLogService can be used to create this kind of "Change Data Capture" + * events. An example is the LuceneUpdateService, which should update the index + * of a document only if the document was successfully written to the database. *

- * The service is bound to the current PersistenceContext and stores a defined type of document - * entity directly in the database to represent an event. These types of events can be queried by - * clients through the service. + * The service is bound to the current PersistenceContext and stores a defined + * type of document entity directly in the database to represent an event. These + * types of events can be queried by clients through the service. * * @see org.imixs.workflow.engine.index.UpdateService * @author rsoika @@ -63,184 +63,188 @@ @Stateless public class EventLogService { - @PersistenceContext(unitName = "org.imixs.workflow.jpa") - private EntityManager manager; - - private static Logger logger = Logger.getLogger(EventLogService.class.getName()); - - /** - * Creates/updates a new event log entry. - * - * @param refID - uniqueid of the document to be assigned to the event - * @param topic - the topic of the event. - * @return - generated event log entry - */ - public EventLog createEvent(String topic, String refID) { - return createEvent(topic, refID, (Map>) null); - } - - /** - * Creates/updates a new event log entry. - * - * @param refID - uniqueId of the document to be assigned to the event - * @param topic - the topic of the event. - * @param document - optional document providing a data map - * @return - generated event log entry - */ - public EventLog createEvent(String topic, String refID, ItemCollection document) { - return this.createEvent(topic, refID, document.getAllItems()); - } - - /** - * Creates/updates a new event log entry. - * - * @param refID - uniqueId of the document to be assigned to the event - * @param topic - the topic of the event. - * @param data - optional data map - * @return - generated event log entry - */ - public EventLog createEvent(String topic, String refID, Map> data) { - boolean debug = logger.isLoggable(Level.FINE); - if (refID == null || refID.isEmpty()) { - logger.warning("create EventLog failed - given ref-id is empty!"); - return null; - } - // Now set flush Mode to COMMIT - manager.setFlushMode(FlushModeType.COMMIT); - // now create a new event log entry - EventLog eventLog = new EventLog(topic, refID, data); - manager.persist(eventLog); - if (debug) { - logger.finest("......created new eventLog '" + refID + "' => " + topic); - } - return eventLog; - } - - /** - * Finds events for one or many given topics - * - * @param maxCount - maximum count of events to be returned - * @param topic - list of topics - * @return - list of eventLogEntries - */ - @SuppressWarnings("unchecked") - public List findEventsByTopic(int maxCount, String... topic) { - boolean debug = logger.isLoggable(Level.FINE); - List result = new ArrayList<>(); - String query = "SELECT eventlog FROM EventLog AS eventlog "; - query += "WHERE ("; - for (String _topic : topic) { - if (_topic != null && !_topic.isEmpty()) { - query += "eventlog.topic = '" + _topic + "' OR "; - } - } - // cut last OR - query = query.substring(0, query.length() - 3); - query += ") ORDER BY eventlog.created ASC"; - - // find all eventLogEntries.... - Query q = manager.createQuery(query); - q.setMaxResults(maxCount); - result = q.getResultList(); - if (debug) { - logger.fine("found " + result.size() + " event for topic " + topic); - } - return result; - - } - - /** - * Finds events for one or many given topics assigned to a given document reference ($uniqueId). - * The method returns an empty list if no event log entries exist of the given refId, - * - * @param maxCount - maximum count of events to be returned - * @param ref - a reference ID for an assigned Document or Workitem instance - * @param topic - list of topics - * - * @return - list of eventLogEntries - */ - @SuppressWarnings("unchecked") - public List findEventsByRef(int maxCount, String ref, String... topic) { - boolean debug = logger.isLoggable(Level.FINE); - List result = null; - String query = "SELECT eventlog FROM EventLog AS eventlog "; - query += "WHERE (eventlog.ref = '" + ref + "' AND ("; - for (String _topic : topic) { - query += "eventlog.topic = '" + _topic + "' OR "; + @PersistenceContext(unitName = "org.imixs.workflow.jpa") + private EntityManager manager; + + private static Logger logger = Logger.getLogger(EventLogService.class.getName()); + + /** + * Creates/updates a new event log entry. + * + * @param refID - uniqueid of the document to be assigned to the event + * @param topic - the topic of the event. + * @return - generated event log entry + */ + public EventLog createEvent(String topic, String refID) { + return createEvent(topic, refID, (Map>) null); } - // cut last OR - query = query.substring(0, query.length() - 3); - query += ") ORDER BY eventlog.created ASC"; - - // find all eventLogEntries.... - Query q = manager.createQuery(query); - q.setMaxResults(maxCount); - result = q.getResultList(); - if (debug) { - logger.fine("found " + result.size() + " event for topic " + topic); + + /** + * Creates/updates a new event log entry. + * + * @param refID - uniqueId of the document to be assigned to the event + * @param topic - the topic of the event. + * @param document - optional document providing a data map + * @return - generated event log entry + */ + public EventLog createEvent(String topic, String refID, ItemCollection document) { + return this.createEvent(topic, refID, document.getAllItems()); } - return result; - - } - - /** - * Deletes an existing eventLog. The method catches javax.persistence.OptimisticLockException as - * this may occur during parallel requests. - * - * @param eventLog - */ - public void removeEvent(final EventLog _eventLog) { - boolean debug = logger.isLoggable(Level.FINE); - EventLog eventLog = _eventLog; - if (eventLog != null && !manager.contains(eventLog)) { - // entity is not atached - so lookup the entity.... - eventLog = manager.find(EventLog.class, eventLog.getId()); + + /** + * Creates/updates a new event log entry. + * + * @param refID - uniqueId of the document to be assigned to the event + * @param topic - the topic of the event. + * @param data - optional data map + * @return - generated event log entry + */ + public EventLog createEvent(String topic, String refID, Map> data) { + boolean debug = logger.isLoggable(Level.FINE); + if (refID == null || refID.isEmpty()) { + logger.warning("create EventLog failed - given ref-id is empty!"); + return null; + } + // Now set flush Mode to COMMIT + manager.setFlushMode(FlushModeType.COMMIT); + // now create a new event log entry + EventLog eventLog = new EventLog(topic, refID, data); + manager.persist(eventLog); + if (debug) { + logger.finest("......created new eventLog '" + refID + "' => " + topic); + } + return eventLog; } - if (eventLog != null) { - try { - manager.remove(eventLog); - } catch (javax.persistence.OptimisticLockException e) { - // no todo - can occure during parallel requests + + /** + * Finds events for one or many given topics + * + * @param maxCount - maximum count of events to be returned + * @param topic - list of topics + * @return - list of eventLogEntries + */ + @SuppressWarnings("unchecked") + public List findEventsByTopic(int maxCount, String... topic) { + boolean debug = logger.isLoggable(Level.FINE); + List result = new ArrayList<>(); + String query = "SELECT eventlog FROM EventLog AS eventlog "; + query += "WHERE ("; + for (String _topic : topic) { + if (_topic != null && !_topic.isEmpty()) { + query += "eventlog.topic = '" + _topic + "' OR "; + } + } + // cut last OR + query = query.substring(0, query.length() - 3); + query += ") ORDER BY eventlog.created ASC"; + + // find all eventLogEntries.... + Query q = manager.createQuery(query); + q.setMaxResults(maxCount); + result = q.getResultList(); if (debug) { - logger.finest(e.getMessage()); + logger.fine("found " + result.size() + " event for topic " + topic); } - } + return result; + } - } - - /** - * Deletes an existing eventLog by its id. The method catches - * javax.persistence.OptimisticLockException as this may occur during parallel requests. - * - * @param eventLog - */ - public void removeEvent(final String id) { - EventLog eventLog = null; - boolean debug = logger.isLoggable(Level.FINE); - // lookup the entity.... - eventLog = manager.find(EventLog.class, id); - - if (eventLog != null) { - try { - manager.remove(eventLog); - } catch (javax.persistence.OptimisticLockException e) { - // no todo - can occure during parallel requests + + /** + * Finds events for one or many given topics assigned to a given document + * reference ($uniqueId). The method returns an empty list if no event log + * entries exist of the given refId, + * + * @param maxCount - maximum count of events to be returned + * @param ref - a reference ID for an assigned Document or Workitem + * instance + * @param topic - list of topics + * + * @return - list of eventLogEntries + */ + @SuppressWarnings("unchecked") + public List findEventsByRef(int maxCount, String ref, String... topic) { + boolean debug = logger.isLoggable(Level.FINE); + List result = null; + String query = "SELECT eventlog FROM EventLog AS eventlog "; + query += "WHERE (eventlog.ref = '" + ref + "' AND ("; + for (String _topic : topic) { + query += "eventlog.topic = '" + _topic + "' OR "; + } + // cut last OR + query = query.substring(0, query.length() - 3); + query += ") ORDER BY eventlog.created ASC"; + + // find all eventLogEntries.... + Query q = manager.createQuery(query); + q.setMaxResults(maxCount); + result = q.getResultList(); if (debug) { - logger.finest(e.getMessage()); + logger.fine("found " + result.size() + " event for topic " + topic); + } + return result; + + } + + /** + * Deletes an existing eventLog. The method catches + * javax.persistence.OptimisticLockException as this may occur during parallel + * requests. + * + * @param eventLog + */ + public void removeEvent(final EventLog _eventLog) { + boolean debug = logger.isLoggable(Level.FINE); + EventLog eventLog = _eventLog; + if (eventLog != null && !manager.contains(eventLog)) { + // entity is not atached - so lookup the entity.... + eventLog = manager.find(EventLog.class, eventLog.getId()); + } + if (eventLog != null) { + try { + manager.remove(eventLog); + } catch (javax.persistence.OptimisticLockException e) { + // no todo - can occure during parallel requests + if (debug) { + logger.finest(e.getMessage()); + } + } } - } } - } - - /** - * Returns an detached event log entry by its ID. - * - * @param id - id of the eventLog Entry - * @return detached eventLog entry or null if not found - */ - public EventLog getEvent(String id) { - EventLog eventLog = manager.find(EventLog.class, id); - manager.detach(eventLog); - return eventLog; - } + + /** + * Deletes an existing eventLog by its id. The method catches + * javax.persistence.OptimisticLockException as this may occur during parallel + * requests. + * + * @param eventLog + */ + public void removeEvent(final String id) { + EventLog eventLog = null; + boolean debug = logger.isLoggable(Level.FINE); + // lookup the entity.... + eventLog = manager.find(EventLog.class, id); + + if (eventLog != null) { + try { + manager.remove(eventLog); + } catch (javax.persistence.OptimisticLockException e) { + // no todo - can occure during parallel requests + if (debug) { + logger.finest(e.getMessage()); + } + } + } + } + + /** + * Returns an detached event log entry by its ID. + * + * @param id - id of the eventLog Entry + * @return detached eventLog entry or null if not found + */ + public EventLog getEvent(String id) { + EventLog eventLog = manager.find(EventLog.class, id); + manager.detach(eventLog); + return eventLog; + } } diff --git a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/HealthCheckService.java b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/HealthCheckService.java index c79be97aa..4bc191d68 100644 --- a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/HealthCheckService.java +++ b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/HealthCheckService.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *

- *  Imixs Workflow 
+/*  
+ *  Imixs-Workflow 
+ *  
  *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
  *  http://www.imixs.com
  *  
@@ -22,10 +22,9 @@
  *      https://github.com/imixs/imixs-workflow
  *  
  *  Contributors:  
- *      Imixs Software Solutions GmbH - initial API and implementation
+ *      Imixs Software Solutions GmbH - Project Management
  *      Ralph Soika - Software Developer
- * 
- *******************************************************************************/ + */ package org.imixs.workflow.engine; @@ -41,14 +40,16 @@ import org.eclipse.microprofile.health.HealthCheckResponseBuilder; /** - * The Imixs HealthCheckService implements the Microservice HealthCheck interface. + * The Imixs HealthCheckService implements the Microservice HealthCheck + * interface. *

* The service returns the count of workflow models *

- * Example: {"data":{"model.count":1},"name":"imixs-workflow","state":"UP"} + * Example: + * {"data":{"model.count":1},"name":"imixs-workflow","state":"UP"} *

- * This check indicates the overall status of the workflow engine. If models are available also - * database access and security works. + * This check indicates the overall status of the workflow engine. If models are + * available also database access and security works. * * @author rsoika * @version 1.0 @@ -57,75 +58,75 @@ @ApplicationScoped public class HealthCheckService implements HealthCheck { - private String workflowVersion = null; - private static Logger logger = Logger.getLogger(HealthCheckService.class.getName()); + private String workflowVersion = null; + private static Logger logger = Logger.getLogger(HealthCheckService.class.getName()); - @Inject - private SetupService setupService; + @Inject + private SetupService setupService; - /** - * This is the implementation for the health check call back method. - *

- * The method returns the status 'UP' together with the count of workflow models - *

- * Example: {"data":{"model.count":1},"name":"imixs-workflow","state":"UP"} - *

- * This check indicates the overall status of the workflow engine. If models are available also - * database access and security works. - * - */ - @Override - public HealthCheckResponse call() { - HealthCheckResponseBuilder builder = null; - int modelCount = 0; - int groupCount = 0; - boolean failure = false; - try { - modelCount = setupService.getModelVersionCount(); - groupCount = setupService.getModelGroupCount(); - } catch (Exception e) { - // failed! - modelCount = 0; - failure = true; - } + /** + * This is the implementation for the health check call back method. + *

+ * The method returns the status 'UP' together with the count of workflow models + *

+ * Example: + * {"data":{"model.count":1},"name":"imixs-workflow","state":"UP"} + *

+ * This check indicates the overall status of the workflow engine. If models are + * available also database access and security works. + * + */ + @Override + public HealthCheckResponse call() { + HealthCheckResponseBuilder builder = null; + int modelCount = 0; + int groupCount = 0; + boolean failure = false; + try { + modelCount = setupService.getModelVersionCount(); + groupCount = setupService.getModelGroupCount(); + } catch (Exception e) { + // failed! + modelCount = 0; + failure = true; + } - if (!failure) { - builder = HealthCheckResponse.named("imixs-workflow") - .withData("engine.version", getWorkflowVersion()).withData("model.versions", modelCount) - .withData("model.groups", groupCount).up(); - } else { - builder = HealthCheckResponse.named("imixs-workflow").down(); - } + if (!failure) { + builder = HealthCheckResponse.named("imixs-workflow").withData("engine.version", getWorkflowVersion()) + .withData("model.versions", modelCount).withData("model.groups", groupCount).up(); + } else { + builder = HealthCheckResponse.named("imixs-workflow").down(); + } - return builder.build(); - } + return builder.build(); + } - /** - * This method extracts the workflow version form the maven pom.properties - * - * META-INF/maven/${groupId}/${artifactId}/pom.properties - * - */ - private String getWorkflowVersion() { - if (workflowVersion == null) { - try { - InputStream resourceAsStream = this.getClass().getResourceAsStream( - "/META-INF/maven/org.imixs.workflow/imixs-workflow-engine/pom.properties"); - if (resourceAsStream != null) { - Properties prop = new Properties(); - prop.load(resourceAsStream); - workflowVersion = prop.getProperty("version"); + /** + * This method extracts the workflow version form the maven pom.properties + * + * META-INF/maven/${groupId}/${artifactId}/pom.properties + * + */ + private String getWorkflowVersion() { + if (workflowVersion == null) { + try { + InputStream resourceAsStream = this.getClass() + .getResourceAsStream("/META-INF/maven/org.imixs.workflow/imixs-workflow-engine/pom.properties"); + if (resourceAsStream != null) { + Properties prop = new Properties(); + prop.load(resourceAsStream); + workflowVersion = prop.getProperty("version"); + } + } catch (IOException e1) { + logger.warning("failed to load pom.properties"); + } + } + // if not found -> 'unknown' + if (workflowVersion == null || workflowVersion.isEmpty()) { + workflowVersion = "unknown"; } - } catch (IOException e1) { - logger.warning("failed to load pom.properties"); - } - } - // if not found -> 'unknown' - if (workflowVersion == null || workflowVersion.isEmpty()) { - workflowVersion = "unknown"; - } - return workflowVersion; - } + return workflowVersion; + } } diff --git a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/ImixsConfigSource.java b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/ImixsConfigSource.java index 9321e241d..e8c5028ee 100644 --- a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/ImixsConfigSource.java +++ b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/ImixsConfigSource.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *

- *  Imixs Workflow 
+/*  
+ *  Imixs-Workflow 
+ *  
  *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
  *  http://www.imixs.com
  *  
@@ -22,10 +22,9 @@
  *      https://github.com/imixs/imixs-workflow
  *  
  *  Contributors:  
- *      Imixs Software Solutions GmbH - initial API and implementation
+ *      Imixs Software Solutions GmbH - Project Management
  *      Ralph Soika - Software Developer
- * 
- *******************************************************************************/ + */ package org.imixs.workflow.engine; @@ -37,16 +36,20 @@ import org.eclipse.microprofile.config.spi.ConfigSource; /** - * The ImixsConfigSource is a custom config source based on Microprofile Config API. + * The ImixsConfigSource is a custom config source based on Microprofile Config + * API. *

- * The config source reads the Imixs-Workflow property file named 'imxis.properties'. + * The config source reads the Imixs-Workflow property file named + * 'imxis.properties'. *

- * With this custom config source the imixs.properties file can be reused without the need to - * migrate all properties into the file META-INF/microprofile-config.properties. It is recommended - * to store imixs specific properties into the file imixs.properties + * With this custom config source the imixs.properties file can be reused + * without the need to migrate all properties into the file + * META-INF/microprofile-config.properties. It is recommended to store imixs + * specific properties into the file imixs.properties *

- * As per SPI it is necessary to register the implementation in META-INF/services by adding an entry - * in a file called 'org.eclipse.microprofile.config.spi.ConfigSource' + * As per SPI it is necessary to register the implementation in + * META-INF/services by adding an entry in a file called + * 'org.eclipse.microprofile.config.spi.ConfigSource' * * @author rsoika * @@ -54,118 +57,119 @@ public class ImixsConfigSource implements ConfigSource { - public static final String NAME = "ImixsConfigSource"; - private Map properties = null; - private static Logger logger = Logger.getLogger(ImixsConfigSource.class.getName()); - - @Override - public int getOrdinal() { - return 900; - } + public static final String NAME = "ImixsConfigSource"; + private Map properties = null; + private static Logger logger = Logger.getLogger(ImixsConfigSource.class.getName()); - @Override - public String getValue(String key) { - if (properties == null) { - loadProperties(); + @Override + public int getOrdinal() { + return 900; } - String value = properties.get(key); - // search alterntive / deprecated imixs.property? - if (value == null || value.isEmpty()) { - String keyAlternative = getAlternative(key); - if (keyAlternative != null && !keyAlternative.isEmpty()) { - value = properties.get(keyAlternative); - if (value != null && !value.isEmpty()) { - logger.warning("Deprecated imixs.property '" + keyAlternative - + "' should be replaced by '" + key + "'"); + @Override + public String getValue(String key) { + if (properties == null) { + loadProperties(); } - } - } - return value; - } - - @Override - public String getName() { - return NAME; - } - - @Override - public Map getProperties() { - if (properties == null) { - loadProperties(); - } - return properties; - } - - /** - * This method is used to load a imixs.property file into the property Map - *

- * The imixs.property file is loaded from the current threads classpath. - * - */ - private void loadProperties() { - properties = new HashMap(); - Properties fileProperties = new Properties(); - try { - fileProperties.load(Thread.currentThread().getContextClassLoader() - .getResource("imixs.properties").openStream()); - - // now we put the values into the property Map..... - for (Object key : fileProperties.keySet()) { - String value = fileProperties.getProperty(key.toString()); - if (value != null && !value.isEmpty()) { - properties.put(key.toString(), value); - } - } + String value = properties.get(key); + // search alterntive / deprecated imixs.property? + if (value == null || value.isEmpty()) { + String keyAlternative = getAlternative(key); + if (keyAlternative != null && !keyAlternative.isEmpty()) { + value = properties.get(keyAlternative); + if (value != null && !value.isEmpty()) { + logger.warning( + "Deprecated imixs.property '" + keyAlternative + "' should be replaced by '" + key + "'"); + } + } - } catch (Exception e) { - logger.warning("unable to find imixs.properties in current classpath"); - if (logger.isLoggable(Level.FINE)) { - e.printStackTrace(); - } + } + return value; } - } - - /** - * This method provides key alternatives for deprecated imixs.property values - * - *

    - *
  • lucence.fulltextFieldList - index.fields
  • - *
  • lucence.indexFieldListAnalyze - index.fields.analyze
  • - *
  • lucence.indexFieldListNoAnalyze - index.fields.noanalyze
  • - *
  • lucence.indexFieldListStore - index.fields.store
  • - * - *
  • lucence.defaultOperator - index.operator
  • - *
  • lucence.splitOnWhitespace - index.splitwhitespace
  • - *
- * - * @param key - * @return - */ - private String getAlternative(String key) { - - if ("index.fields".equals(key)) { - return "lucence.fulltextFieldList"; - } - if ("index.fields.analyze".equals(key)) { - return "lucence.indexFieldListAnalyze"; - } - if ("index.fields.noanalyze".equals(key)) { - return "lucence.indexFieldListNoAnalyze"; - } - if ("index.fields.store".equals(key)) { - return "lucence.indexFieldListStore"; + @Override + public String getName() { + return NAME; } - if ("index.operator".equals(key)) { - return "lucence.defaultOperator"; + @Override + public Map getProperties() { + if (properties == null) { + loadProperties(); + } + return properties; } - if ("index.splitwhitespace".equals(key)) { - return "lucence.splitOnWhitespace"; + + /** + * This method is used to load a imixs.property file into the property + * Map + *

+ * The imixs.property file is loaded from the current threads classpath. + * + */ + private void loadProperties() { + properties = new HashMap(); + Properties fileProperties = new Properties(); + try { + fileProperties + .load(Thread.currentThread().getContextClassLoader().getResource("imixs.properties").openStream()); + + // now we put the values into the property Map..... + for (Object key : fileProperties.keySet()) { + String value = fileProperties.getProperty(key.toString()); + if (value != null && !value.isEmpty()) { + properties.put(key.toString(), value); + } + } + + } catch (Exception e) { + logger.warning("unable to find imixs.properties in current classpath"); + if (logger.isLoggable(Level.FINE)) { + e.printStackTrace(); + } + } + } - return null; - } + /** + * This method provides key alternatives for deprecated imixs.property values + * + *

    + *
  • lucence.fulltextFieldList - index.fields
  • + *
  • lucence.indexFieldListAnalyze - index.fields.analyze
  • + *
  • lucence.indexFieldListNoAnalyze - index.fields.noanalyze
  • + *
  • lucence.indexFieldListStore - index.fields.store
  • + * + *
  • lucence.defaultOperator - index.operator
  • + *
  • lucence.splitOnWhitespace - index.splitwhitespace
  • + *
+ * + * @param key + * @return + */ + private String getAlternative(String key) { + + if ("index.fields".equals(key)) { + return "lucence.fulltextFieldList"; + } + if ("index.fields.analyze".equals(key)) { + return "lucence.indexFieldListAnalyze"; + } + if ("index.fields.noanalyze".equals(key)) { + return "lucence.indexFieldListNoAnalyze"; + } + if ("index.fields.store".equals(key)) { + return "lucence.indexFieldListStore"; + } + + if ("index.operator".equals(key)) { + return "lucence.defaultOperator"; + } + if ("index.splitwhitespace".equals(key)) { + return "lucence.splitOnWhitespace"; + } + + return null; + } } diff --git a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/MetricService.java b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/MetricService.java index d1b23d1a6..97f38fce2 100644 --- a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/MetricService.java +++ b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/MetricService.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *
- *  Imixs Workflow 
+/*  
+ *  Imixs-Workflow 
+ *  
  *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
  *  http://www.imixs.com
  *  
@@ -22,10 +22,9 @@
  *      https://github.com/imixs/imixs-workflow
  *  
  *  Contributors:  
- *      Imixs Software Solutions GmbH - initial API and implementation
+ *      Imixs Software Solutions GmbH - Project Management
  *      Ralph Soika - Software Developer
- * 
- *******************************************************************************/ + */ package org.imixs.workflow.engine; @@ -45,18 +44,22 @@ import org.imixs.workflow.exceptions.AccessDeniedException; /** - * The Imixs MetricSerivce is a monitoring resource for Imixs-Workflow in the prometheus format. The - * MetricService is based on Microprofile 2.2 and MP-Metric-API 2.2 + * The Imixs MetricSerivce is a monitoring resource for Imixs-Workflow in the + * prometheus format. The MetricService is based on Microprofile 2.2 and + * MP-Metric-API 2.2 *

- * A metric is created each time when a Imixs ProcessingEvent or Imixs DocumentEvent is fired. The - * service exports metrics in prometheus text format. + * A metric is created each time when a Imixs ProcessingEvent or Imixs + * DocumentEvent is fired. The service exports metrics in prometheus text + * format. *

- * The service provides counter metrics for document access and processed workitems. A counter will - * always increase. To extract the values in prometheus use the rate function - Example: + * The service provides counter metrics for document access and processed + * workitems. A counter will always increase. To extract the values in + * prometheus use the rate function - Example: *

* rate(http_requests_total[5m]) *

- * The service expects MP Metrics v2.0. A warning is logged if corresponding version is missing. + * The service expects MP Metrics v2.0. A warning is logged if corresponding + * version is missing. *

* To enable the metric service the imixs.property ... must be set to true *

@@ -68,157 +71,155 @@ @ApplicationScoped public class MetricService { - public static final String METRIC_DOCUMENTS_TOTAL = "documents_total"; - public static final String METRIC_WORKITEMS_TOTAL = "workitems_total"; - - @Inject - @ConfigProperty(name = "metrics.enabled", defaultValue = "false") - private boolean metricsEnabled; - - @Inject - @RegistryType(type = MetricRegistry.Type.APPLICATION) - MetricRegistry metricRegistry; - - boolean mpMetricNoSupport = false; - - private static Logger logger = Logger.getLogger(MetricService.class.getName()); - - /** - * ProcessingEvent listener to generate a metric. - * - * @param processingEvent - * @throws AccessDeniedException - */ - public void onProcessingEvent(@Observes ProcessingEvent processingEvent) - throws AccessDeniedException { - - if (!metricsEnabled) { - return; - } - if (processingEvent == null) { - return; - } - if (mpMetricNoSupport) { - // missing MP Metric support! - return; + public static final String METRIC_DOCUMENTS_TOTAL = "documents_total"; + public static final String METRIC_WORKITEMS_TOTAL = "workitems_total"; + + @Inject + @ConfigProperty(name = "metrics.enabled", defaultValue = "false") + private boolean metricsEnabled; + + @Inject + @RegistryType(type = MetricRegistry.Type.APPLICATION) + MetricRegistry metricRegistry; + + boolean mpMetricNoSupport = false; + + private static Logger logger = Logger.getLogger(MetricService.class.getName()); + + /** + * ProcessingEvent listener to generate a metric. + * + * @param processingEvent + * @throws AccessDeniedException + */ + public void onProcessingEvent(@Observes ProcessingEvent processingEvent) throws AccessDeniedException { + + if (!metricsEnabled) { + return; + } + if (processingEvent == null) { + return; + } + if (mpMetricNoSupport) { + // missing MP Metric support! + return; + } + + // NOTE: Issue #514 - just uncomment this code! + try { + Counter counter = buildWorkitemMetric(processingEvent); + counter.inc(); + } catch (IncompatibleClassChangeError | ObserverException oe) { + mpMetricNoSupport = true; + logger.warning("...Microprofile Metrics v2.2 not supported!"); + } } - // NOTE: Issue #514 - just uncomment this code! - try { - Counter counter = buildWorkitemMetric(processingEvent); - counter.inc(); - } catch (IncompatibleClassChangeError | ObserverException oe) { - mpMetricNoSupport = true; - logger.warning("...Microprofile Metrics v2.2 not supported!"); - } - } - - /** - * DocumentEvent listener to generate a metric. - * - * @param documentEvent - * @throws AccessDeniedException - */ - public void onDocumentEvent(@Observes DocumentEvent documentEvent) throws AccessDeniedException { - - if (!metricsEnabled) { - return; - } - if (documentEvent == null) { - return; - } - if (mpMetricNoSupport) { - // missing MP Metric support! - return; + /** + * DocumentEvent listener to generate a metric. + * + * @param documentEvent + * @throws AccessDeniedException + */ + public void onDocumentEvent(@Observes DocumentEvent documentEvent) throws AccessDeniedException { + + if (!metricsEnabled) { + return; + } + if (documentEvent == null) { + return; + } + if (mpMetricNoSupport) { + // missing MP Metric support! + return; + } + + // NOTE: Issue #514 - just uncomment this code! + try { + Counter counter = buildDocumentMetric(documentEvent); + counter.inc(); + + } catch (IncompatibleClassChangeError | ObserverException oe) { + mpMetricNoSupport = true; + logger.warning("...Microprofile Metrics v2.0 not supported!"); + oe.printStackTrace(); + } } + /** + * This method builds a Microprofile Metric for a Counter. The metric contains + * the tag 'method'. + * + * @return Counter metric + */ + // NOTE: Issue #514 - just uncomment this code! - try { - Counter counter = buildDocumentMetric(documentEvent); - counter.inc(); - - } catch (IncompatibleClassChangeError | ObserverException oe) { - mpMetricNoSupport = true; - logger.warning("...Microprofile Metrics v2.0 not supported!"); - oe.printStackTrace(); - } - } - /** - * This method builds a Microprofile Metric for a Counter. The metric contains the tag 'method'. - * - * @return Counter metric - */ + private Counter buildDocumentMetric(DocumentEvent event) { - // NOTE: Issue #514 - just uncomment this code! + // Constructs a Metadata object from a map with the following keys: + // - name - The name of the metric + // - displayName - The display (friendly) name of the metric + // - description - The description of the metric + // - type - The type of the metric + // - tags - The tags of the metric - cannot be null + // - reusable - If true, this metric name is permitted to be used at multiple - private Counter buildDocumentMetric(DocumentEvent event) { + Metadata metadata = Metadata.builder().withName(METRIC_DOCUMENTS_TOTAL) + .withDescription("Imixs-Workflow count documents").withType(MetricType.COUNTER).build(); - // Constructs a Metadata object from a map with the following keys: - // - name - The name of the metric - // - displayName - The display (friendly) name of the metric - // - description - The description of the metric - // - type - The type of the metric - // - tags - The tags of the metric - cannot be null - // - reusable - If true, this metric name is permitted to be used at multiple + String method = null; + // build tags... + if (DocumentEvent.ON_DOCUMENT_SAVE == event.getEventType()) { + method = "save"; + } - Metadata metadata = Metadata.builder().withName(METRIC_DOCUMENTS_TOTAL) - .withDescription("Imixs-Workflow count documents").withType(MetricType.COUNTER).build(); + if (DocumentEvent.ON_DOCUMENT_LOAD == event.getEventType()) { + method = "load"; + } - String method = null; - // build tags... - if (DocumentEvent.ON_DOCUMENT_SAVE == event.getEventType()) { - method = "save"; - } + if (DocumentEvent.ON_DOCUMENT_DELETE == event.getEventType()) { + method = "delete"; + } - if (DocumentEvent.ON_DOCUMENT_LOAD == event.getEventType()) { - method = "load"; - } + Tag[] tags = { new Tag("method", method) }; - if (DocumentEvent.ON_DOCUMENT_DELETE == event.getEventType()) { - method = "delete"; + Counter counter = metricRegistry.counter(metadata, tags); + + return counter; } - Tag[] tags = {new Tag("method", method)}; - - Counter counter = metricRegistry.counter(metadata, tags); - - return counter; - } - - /** - * This method builds a Microprofile Metric for a Counter. The metric contains the tags 'task', - * 'event', 'type', 'workflowgroup', 'worklowstatus', 'modelversion' - * - * @return Counter metric - */ - private Counter buildWorkitemMetric(ProcessingEvent event) { - - // Constructs a Metadata object from a map with the following keys: - // - name - The name of the metric - // - displayName - The display (friendly) name of the metric - // - description - The description of the metric - // - type - The type of the metric - // - tags - The tags of the metric - cannot be null - // - reusable - If true, this metric name is permitted to be used at multiple - - Metadata metadata = Metadata.builder().withName(METRIC_WORKITEMS_TOTAL) - .withDescription("Imixs-Workflow count procssed workitems").withType(MetricType.COUNTER) - .build(); - - // build tags... - Tag[] tags = {new Tag("type", event.getDocument().getType()), - new Tag("modelversion", event.getDocument().getModelVersion()), - new Tag("task", event.getDocument().getTaskID() + ""), - new Tag("event", event.getDocument().getItemValueInteger("$lastevent") + ""), - new Tag("workflowgroup", - event.getDocument().getItemValueString(WorkflowKernel.WORKFLOWGROUP)), - new Tag("workflowstatus", - event.getDocument().getItemValueString(WorkflowKernel.WORKFLOWSTATUS))}; - - Counter counter = metricRegistry.counter(metadata, tags); - - return counter; - } + /** + * This method builds a Microprofile Metric for a Counter. The metric contains + * the tags 'task', 'event', 'type', 'workflowgroup', 'worklowstatus', + * 'modelversion' + * + * @return Counter metric + */ + private Counter buildWorkitemMetric(ProcessingEvent event) { + + // Constructs a Metadata object from a map with the following keys: + // - name - The name of the metric + // - displayName - The display (friendly) name of the metric + // - description - The description of the metric + // - type - The type of the metric + // - tags - The tags of the metric - cannot be null + // - reusable - If true, this metric name is permitted to be used at multiple + + Metadata metadata = Metadata.builder().withName(METRIC_WORKITEMS_TOTAL) + .withDescription("Imixs-Workflow count procssed workitems").withType(MetricType.COUNTER).build(); + + // build tags... + Tag[] tags = { new Tag("type", event.getDocument().getType()), + new Tag("modelversion", event.getDocument().getModelVersion()), + new Tag("task", event.getDocument().getTaskID() + ""), + new Tag("event", event.getDocument().getItemValueInteger("$lastevent") + ""), + new Tag("workflowgroup", event.getDocument().getItemValueString(WorkflowKernel.WORKFLOWGROUP)), + new Tag("workflowstatus", event.getDocument().getItemValueString(WorkflowKernel.WORKFLOWSTATUS)) }; + + Counter counter = metricRegistry.counter(metadata, tags); + + return counter; + } } diff --git a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/ModelService.java b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/ModelService.java index 8305712c7..e3b5cb362 100644 --- a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/ModelService.java +++ b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/ModelService.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *
- *  Imixs Workflow 
+/*  
+ *  Imixs-Workflow 
+ *  
  *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
  *  http://www.imixs.com
  *  
@@ -22,10 +22,9 @@
  *      https://github.com/imixs/imixs-workflow
  *  
  *  Contributors:  
- *      Imixs Software Solutions GmbH - initial API and implementation
+ *      Imixs Software Solutions GmbH - Project Management
  *      Ralph Soika - Software Developer
- * 
- *******************************************************************************/ + */ package org.imixs.workflow.engine; @@ -60,467 +59,469 @@ import org.imixs.workflow.exceptions.ModelException; /** - * The ModelManager is independent form the IX JEE Entity EJBs and uses the standard IntemCollection - * Object as a data transfer object to communicate with clients. + * The ModelManager is independent form the IX JEE Entity EJBs and uses the + * standard IntemCollection Object as a data transfer object to communicate with + * clients. * * * Since Version 1.7.0 * - * The Implementation handles multiple model versions. Different Versions of an Model Entity can be - * saved and updated. The Getter methods can be furthermore Controlled by providing a valid Model - * Version. If no model version is set this Implementation automatically defaults to the highest - * available ModelVersion + * The Implementation handles multiple model versions. Different Versions of an + * Model Entity can be saved and updated. The Getter methods can be furthermore + * Controlled by providing a valid Model Version. If no model version is set + * this Implementation automatically defaults to the highest available + * ModelVersion * * @see org.imixs.workflow.ModelManager * @see org.imixs.workflow.jee.ejb.ModelManager * @author rsoika * */ -@DeclareRoles({"org.imixs.ACCESSLEVEL.NOACCESS", "org.imixs.ACCESSLEVEL.READERACCESS", - "org.imixs.ACCESSLEVEL.AUTHORACCESS", "org.imixs.ACCESSLEVEL.EDITORACCESS", - "org.imixs.ACCESSLEVEL.MANAGERACCESS"}) -@RolesAllowed({"org.imixs.ACCESSLEVEL.NOACCESS", "org.imixs.ACCESSLEVEL.READERACCESS", - "org.imixs.ACCESSLEVEL.AUTHORACCESS", "org.imixs.ACCESSLEVEL.EDITORACCESS", - "org.imixs.ACCESSLEVEL.MANAGERACCESS"}) +@DeclareRoles({ "org.imixs.ACCESSLEVEL.NOACCESS", "org.imixs.ACCESSLEVEL.READERACCESS", + "org.imixs.ACCESSLEVEL.AUTHORACCESS", "org.imixs.ACCESSLEVEL.EDITORACCESS", + "org.imixs.ACCESSLEVEL.MANAGERACCESS" }) +@RolesAllowed({ "org.imixs.ACCESSLEVEL.NOACCESS", "org.imixs.ACCESSLEVEL.READERACCESS", + "org.imixs.ACCESSLEVEL.AUTHORACCESS", "org.imixs.ACCESSLEVEL.EDITORACCESS", + "org.imixs.ACCESSLEVEL.MANAGERACCESS" }) @Singleton @LocalBean public class ModelService implements ModelManager { - private Map modelStore = null; - private static Logger logger = Logger.getLogger(ModelService.class.getName()); - @Inject - private DocumentService documentService; - @Resource - private SessionContext ctx; - - public ModelService() { - super(); - } - - /** - * This method initializes the modelManager and loads existing Models from the database. The - * method can not be annotated with @PostConstruct because in case a servlet with @RunAs - * annotation will not propagate the principal in a PostConstruct. For that reason the method is - * called indirectly. - * - * @throws AccessDeniedException - */ - void init() throws AccessDeniedException { - boolean debug = logger.isLoggable(Level.FINE); - // load existing models into the ModelManager.... - if (debug) { - logger.finest("......Initalizing ModelService..."); + private Map modelStore = null; + private static Logger logger = Logger.getLogger(ModelService.class.getName()); + @Inject + private DocumentService documentService; + @Resource + private SessionContext ctx; + + public ModelService() { + super(); } - // first remove existing model entities - Collection col = documentService.getDocumentsByType("model"); - for (ItemCollection modelEntity : col) { - List files = modelEntity.getFileData(); - for (FileData file : files) { + /** + * This method initializes the modelManager and loads existing Models from the + * database. The method can not be annotated with @PostConstruct because in case + * a servlet with @RunAs annotation will not propagate the principal in a + * PostConstruct. For that reason the method is called indirectly. + * + * @throws AccessDeniedException + */ + void init() throws AccessDeniedException { + boolean debug = logger.isLoggable(Level.FINE); + // load existing models into the ModelManager.... if (debug) { - logger.finest("......loading file:" + file.getName()); + logger.finest("......Initalizing ModelService..."); } - byte[] rawData = file.getContent(); - InputStream bpmnInputStream = new ByteArrayInputStream(rawData); - try { - Model model = BPMNParser.parseModel(bpmnInputStream, "UTF-8"); - ItemCollection definition = model.getDefinition(); - if (definition != null) { - String modelVersion = definition.getModelVersion(); - try { - if (getModel(modelVersion) != null) { - // no op - logger.warning("Model '" + modelVersion - + "' is dupplicated! Please update the model version!"); - } - } catch (ModelException e) { - // exception is expected - addModel(model); + // first remove existing model entities + Collection col = documentService.getDocumentsByType("model"); + for (ItemCollection modelEntity : col) { + List files = modelEntity.getFileData(); + + for (FileData file : files) { + if (debug) { + logger.finest("......loading file:" + file.getName()); + } + byte[] rawData = file.getContent(); + InputStream bpmnInputStream = new ByteArrayInputStream(rawData); + try { + Model model = BPMNParser.parseModel(bpmnInputStream, "UTF-8"); + ItemCollection definition = model.getDefinition(); + if (definition != null) { + String modelVersion = definition.getModelVersion(); + try { + if (getModel(modelVersion) != null) { + // no op + logger.warning("Model '" + modelVersion + + "' is dupplicated! Please update the model version!"); + } + } catch (ModelException e) { + // exception is expected + addModel(model); + } + } + } catch (Exception e) { + logger.warning("Failed to load model '" + file.getName() + "' : " + e.getMessage()); + } } - } - } catch (Exception e) { - logger.warning("Failed to load model '" + file.getName() + "' : " + e.getMessage()); } - } } - } - - /** - * This Method adds a model into the internal model store. The model will not be saved in the - * database! Use saveModel to store the model permanently. - */ - @Override - public void addModel(Model model) throws ModelException { - ItemCollection definition = model.getDefinition(); - if (definition == null) { - throw new ModelException(ModelException.INVALID_MODEL, - "Invalid Model: Model Definition not provided! "); - } - String modelVersion = definition.getModelVersion(); - if (modelVersion.isEmpty()) { - throw new ModelException(ModelException.INVALID_MODEL, - "Invalid Model: Model Version not provided! "); - } - logger.info("⟳ updated model version: '" + model.getVersion() + "'"); - getModelStore().put(modelVersion, model); - } - - /** - * This method removes a specific ModelVersion form the internal model store. If modelVersion is - * null the method will remove all models. The model will not be removed from the database. Use - * deleteModel to delete the model from the database. - * - * @throws AccessDeniedException - */ - public void removeModel(String modelversion) { - boolean debug = logger.isLoggable(Level.FINE); - getModelStore().remove(modelversion); - if (debug) { - logger.finest("......removed BPMNModel '" + modelversion + "'..."); + + /** + * This Method adds a model into the internal model store. The model will not be + * saved in the database! Use saveModel to store the model permanently. + */ + @Override + public void addModel(Model model) throws ModelException { + ItemCollection definition = model.getDefinition(); + if (definition == null) { + throw new ModelException(ModelException.INVALID_MODEL, "Invalid Model: Model Definition not provided! "); + } + String modelVersion = definition.getModelVersion(); + if (modelVersion.isEmpty()) { + throw new ModelException(ModelException.INVALID_MODEL, "Invalid Model: Model Version not provided! "); + } + logger.info("⟳ updated model version: '" + model.getVersion() + "'"); + getModelStore().put(modelVersion, model); } - } - - /** - * Returns a Model by version. In case no matching model version exits, the method throws a - * ModelException. - **/ - @Override - public Model getModel(String version) throws ModelException { - Model model = getModelStore().get(version); - if (model == null) { - throw new ModelException(ModelException.UNDEFINED_MODEL_VERSION, - "Modelversion '" + version + "' not found!"); + + /** + * This method removes a specific ModelVersion form the internal model store. If + * modelVersion is null the method will remove all models. The model will not be + * removed from the database. Use deleteModel to delete the model from the + * database. + * + * @throws AccessDeniedException + */ + public void removeModel(String modelversion) { + boolean debug = logger.isLoggable(Level.FINE); + getModelStore().remove(modelversion); + if (debug) { + logger.finest("......removed BPMNModel '" + modelversion + "'..."); + } } - return model; - } - - /** - * Returns a Model matching a given workitem. In case not matching model version exits, the method - * returns the highest Model Version matching the corresponding workflow group. - * - * The method throws a ModelException in case the model version did not exits. - **/ - @Override - public Model getModelByWorkitem(ItemCollection workitem) throws ModelException { - boolean debug = logger.isLoggable(Level.FINE); - String modelVersion = workitem.getModelVersion(); - String workflowGroup = workitem.getItemValueString(WorkflowKernel.WORKFLOWGROUP); - // if $workflowgroup is empty try deprecated field txtworkflowgroup - if (workflowGroup.isEmpty()) { - workflowGroup = workitem.getItemValueString("txtworkflowgroup"); + + /** + * Returns a Model by version. In case no matching model version exits, the + * method throws a ModelException. + **/ + @Override + public Model getModel(String version) throws ModelException { + Model model = getModelStore().get(version); + if (model == null) { + throw new ModelException(ModelException.UNDEFINED_MODEL_VERSION, + "Modelversion '" + version + "' not found!"); + } + return model; } - Model model = null; - try { - model = getModel(modelVersion); - } catch (ModelException me) { - model = null; - List versions = null; - if (debug) { - logger.finest(me.getMessage()); - } - // try to find latest version by regex.... - if (modelVersion != null && !modelVersion.isEmpty()) { - versions = findVersionsByRegEx(modelVersion); - if (!versions.isEmpty()) { - // we found a match by regex! - String newVersion = versions.get(0); - logger - .info("...... match version '" + newVersion + "' -> by regex '" + modelVersion + "'"); - workitem.replaceItemValue(WorkflowKernel.MODELVERSION, newVersion); - model = getModel(newVersion); + /** + * Returns a Model matching a given workitem. In case not matching model version + * exits, the method returns the highest Model Version matching the + * corresponding workflow group. + * + * The method throws a ModelException in case the model version did not exits. + **/ + @Override + public Model getModelByWorkitem(ItemCollection workitem) throws ModelException { + boolean debug = logger.isLoggable(Level.FINE); + String modelVersion = workitem.getModelVersion(); + String workflowGroup = workitem.getItemValueString(WorkflowKernel.WORKFLOWGROUP); + // if $workflowgroup is empty try deprecated field txtworkflowgroup + if (workflowGroup.isEmpty()) { + workflowGroup = workitem.getItemValueString("txtworkflowgroup"); } - } - - // try to find model version by group - if (model == null && !workflowGroup.isEmpty()) { - versions = findVersionsByGroup(workflowGroup); - if (!versions.isEmpty()) { - String newVersion = versions.get(0); - logger.warning("Deprecated model version: '" + modelVersion + "' -> migrating to '" - + newVersion + "', $workflowgroup: '" + workflowGroup + "', $uniqueid: " - + workitem.getUniqueID()); - workitem.replaceItemValue(WorkflowKernel.MODELVERSION, newVersion); - model = getModel(newVersion); + + Model model = null; + try { + model = getModel(modelVersion); + } catch (ModelException me) { + model = null; + List versions = null; + if (debug) { + logger.finest(me.getMessage()); + } + // try to find latest version by regex.... + if (modelVersion != null && !modelVersion.isEmpty()) { + versions = findVersionsByRegEx(modelVersion); + if (!versions.isEmpty()) { + // we found a match by regex! + String newVersion = versions.get(0); + logger.info("...... match version '" + newVersion + "' -> by regex '" + modelVersion + "'"); + workitem.replaceItemValue(WorkflowKernel.MODELVERSION, newVersion); + model = getModel(newVersion); + } + } + + // try to find model version by group + if (model == null && !workflowGroup.isEmpty()) { + versions = findVersionsByGroup(workflowGroup); + if (!versions.isEmpty()) { + String newVersion = versions.get(0); + logger.warning("Deprecated model version: '" + modelVersion + "' -> migrating to '" + newVersion + + "', $workflowgroup: '" + workflowGroup + "', $uniqueid: " + workitem.getUniqueID()); + workitem.replaceItemValue(WorkflowKernel.MODELVERSION, newVersion); + model = getModel(newVersion); + } + } + } + + // check if model was found.... + if (model == null) { + throw new ModelException(ModelException.UNDEFINED_MODEL_VERSION, + "No matching $modelversion found for '" + modelVersion + "', $workflowgroup: '" + workflowGroup + + "', $uniqueid: " + workitem.getUniqueID()); } - } + + return model; } - // check if model was found.... - if (model == null) { - throw new ModelException(ModelException.UNDEFINED_MODEL_VERSION, - "No matching $modelversion found for '" + modelVersion + "', $workflowgroup: '" - + workflowGroup + "', $uniqueid: " + workitem.getUniqueID()); + /** + * returns a sorted String list of all stored model versions + * + * @return + */ + public List getVersions() { + // convert Set to List + Set set = getModelStore().keySet(); + List result = new ArrayList(set); + return result; } - return model; - } - - /** - * returns a sorted String list of all stored model versions - * - * @return - */ - public List getVersions() { - // convert Set to List - Set set = getModelStore().keySet(); - List result = new ArrayList(set); - return result; - } - - /** - * Returns a sorted String list of the latest version for each workflowGroup - * - * @return - */ - public List getLatestVersions() { - List result = new ArrayList(); - List groups = getGroups(); - for (String group : groups) { - List versions = findVersionsByGroup(group); - if (versions != null && versions.size() > 0) { - // add the latest version - String version = versions.get(0); - if (!result.contains(version)) { - result.add(version); + /** + * Returns a sorted String list of the latest version for each workflowGroup + * + * @return + */ + public List getLatestVersions() { + List result = new ArrayList(); + List groups = getGroups(); + for (String group : groups) { + List versions = findVersionsByGroup(group); + if (versions != null && versions.size() > 0) { + // add the latest version + String version = versions.get(0); + if (!result.contains(version)) { + result.add(version); + } + } } - } + // sort result + Collections.sort(result); + return result; } - // sort result - Collections.sort(result); - return result; - } - - /** - * The method returns a sorted list of all available workflow groups - * - * @return - */ - public List getGroups() { - List result = new ArrayList(); - Collection models = getModelStore().values(); - for (Model amodel : models) { - for (String group : amodel.getGroups()) { - if (!result.contains(group)) { - result.add(group); + + /** + * The method returns a sorted list of all available workflow groups + * + * @return + */ + public List getGroups() { + List result = new ArrayList(); + Collection models = getModelStore().values(); + for (Model amodel : models) { + for (String group : amodel.getGroups()) { + if (!result.contains(group)) { + result.add(group); + } + } } - } + // sort result + Collections.sort(result); + return result; } - // sort result - Collections.sort(result); - return result; - } - - /** - * This method returns a sorted list of model versions containing the requested workflow group. - * The result is sorted in reverse order, so the highest version number is the first in the result - * list. - * - * @param group - * @return - */ - public List findVersionsByGroup(String group) { - boolean debug = logger.isLoggable(Level.FINE); - List result = new ArrayList(); - if (debug) { - logger.finest("......searching model versions for workflowgroup '" + group + "'..."); + + /** + * This method returns a sorted list of model versions containing the requested + * workflow group. The result is sorted in reverse order, so the highest version + * number is the first in the result list. + * + * @param group + * @return + */ + public List findVersionsByGroup(String group) { + boolean debug = logger.isLoggable(Level.FINE); + List result = new ArrayList(); + if (debug) { + logger.finest("......searching model versions for workflowgroup '" + group + "'..."); + } + // try to find matching model version by group + Collection models = getModelStore().values(); + for (Model amodel : models) { + if (amodel.getGroups().contains(group)) { + result.add(amodel.getVersion()); + } + } + // sort result + Collections.sort(result, Collections.reverseOrder()); + return result; } - // try to find matching model version by group - Collection models = getModelStore().values(); - for (Model amodel : models) { - if (amodel.getGroups().contains(group)) { - result.add(amodel.getVersion()); - } + + /** + * This method returns a sorted list of model versions matching a given regex + * for a model version. The result is sorted in reverse order, so the highest + * version number is the first in the result list. + * + * @param group + * @return + */ + public List findVersionsByRegEx(String modelRegex) { + boolean debug = logger.isLoggable(Level.FINE); + List result = new ArrayList(); + if (debug) { + logger.finest("......searching model versions for regex '" + modelRegex + "'..."); + } + // try to find matching model version by regex + Collection models = getModelStore().values(); + for (Model amodel : models) { + if (Pattern.compile(modelRegex).matcher(amodel.getVersion()).find()) { + result.add(amodel.getVersion()); + } + } + // sort result + Collections.sort(result, Collections.reverseOrder()); + return result; } - // sort result - Collections.sort(result, Collections.reverseOrder()); - return result; - } - - /** - * This method returns a sorted list of model versions matching a given regex for a model version. - * The result is sorted in reverse order, so the highest version number is the first in the result - * list. - * - * @param group - * @return - */ - public List findVersionsByRegEx(String modelRegex) { - boolean debug = logger.isLoggable(Level.FINE); - List result = new ArrayList(); - if (debug) { - logger.finest("......searching model versions for regex '" + modelRegex + "'..."); + + /** + * This method saves a BPMNModel into the database and adds the model into the + * internal model store. + *

+ * If a model with the same model version exists in the database the old version + * will be deleted form the database first. + * + * @param model + * @throws ModelException + */ + public void saveModel(BPMNModel model) throws ModelException { + saveModel(model, null); } - // try to find matching model version by regex - Collection models = getModelStore().values(); - for (Model amodel : models) { - if (Pattern.compile(modelRegex).matcher(amodel.getVersion()).find()) { - result.add(amodel.getVersion()); - } + + /** + * This method saves a BPMNModel into the database and adds the model into the + * internal model store. The model is attached as an embedded file with the + * given filename. + *

+ * If a model with the same model version exists in the database the old version + * will be deleted form the database first. + *

+ * The param 'filename' is used to store the bpmn file in the correspondig model + * document. + * + * @param model + * @throws ModelException + */ + public void saveModel(BPMNModel model, String _filename) throws ModelException { + if (model != null) { + boolean debug = logger.isLoggable(Level.FINE); + // first delete existing model entities + deleteModel(model.getVersion()); + // store model into internal cache + if (debug) { + logger.finest("......save BPMNModel '" + model.getVersion() + "'..."); + } + BPMNModel bpmnModel = (BPMNModel) model; + addModel(model); + ItemCollection modelItemCol = new ItemCollection(); + modelItemCol.replaceItemValue("type", "model"); + modelItemCol.replaceItemValue("namcreator", ctx.getCallerPrincipal().getName()); + modelItemCol.replaceItemValue("txtname", bpmnModel.getVersion()); + + String filename = _filename; + if (filename == null || filename.isEmpty()) { + // default filename + filename = bpmnModel.getVersion() + ".bpmn"; + } + + FileData fileData = new FileData(filename, bpmnModel.getRawData(), "application/xml", null); + modelItemCol.addFileData(fileData); + // store model in database + modelItemCol.replaceItemValue(DocumentService.NOINDEX, true); + documentService.save(modelItemCol); + } } - // sort result - Collections.sort(result, Collections.reverseOrder()); - return result; - } - - /** - * This method saves a BPMNModel into the database and adds the model into the internal model - * store. - *

- * If a model with the same model version exists in the database the old version will be deleted - * form the database first. - * - * @param model - * @throws ModelException - */ - public void saveModel(BPMNModel model) throws ModelException { - saveModel(model, null); - } - - /** - * This method saves a BPMNModel into the database and adds the model into the internal model - * store. The model is attached as an embedded file with the given filename. - *

- * If a model with the same model version exists in the database the old version will be deleted - * form the database first. - *

- * The param 'filename' is used to store the bpmn file in the correspondig model document. - * - * @param model - * @throws ModelException - */ - public void saveModel(BPMNModel model, String _filename) throws ModelException { - if (model != null) { - boolean debug = logger.isLoggable(Level.FINE); - // first delete existing model entities - deleteModel(model.getVersion()); - // store model into internal cache - if (debug) { - logger.finest("......save BPMNModel '" + model.getVersion() + "'..."); - } - BPMNModel bpmnModel = (BPMNModel) model; - addModel(model); - ItemCollection modelItemCol = new ItemCollection(); - modelItemCol.replaceItemValue("type", "model"); - modelItemCol.replaceItemValue("namcreator", ctx.getCallerPrincipal().getName()); - modelItemCol.replaceItemValue("txtname", bpmnModel.getVersion()); - - String filename = _filename; - if (filename == null || filename.isEmpty()) { - // default filename - filename = bpmnModel.getVersion() + ".bpmn"; - } - - FileData fileData = new FileData(filename, bpmnModel.getRawData(), "application/xml", null); - modelItemCol.addFileData(fileData); - // store model in database - modelItemCol.replaceItemValue(DocumentService.NOINDEX, true); - documentService.save(modelItemCol); + + /** + * This method deletes an existing Model from the database and removes the model + * form the internal ModelStore. + *

+ * A model entity is identified by the type='model' and its name (model + * version). After the model entity was deleted form the database, the method + * will also remove the model from the ModelManager + * + * @param model + */ + public void deleteModel(String version) { + if (version != null && !version.isEmpty()) { + boolean debug = logger.isLoggable(Level.FINE); + if (debug) { + logger.finest("......delete BPMNModel '" + version + "'..."); + } + Collection col = documentService.getDocumentsByType("model"); + for (ItemCollection modelEntity : col) { + // test version... + String oldVersion = modelEntity.getItemValueString("txtname"); + if (version.equals(oldVersion)) { + documentService.remove(modelEntity); + } + } + + removeModel(version); + } else { + logger.severe("deleteModel - invalid model version!"); + throw new InvalidAccessException(InvalidAccessException.INVALID_ID, "deleteModel - invalid model version!"); + } } - } - - /** - * This method deletes an existing Model from the database and removes the model form the internal - * ModelStore. - *

- * A model entity is identified by the type='model' and its name (model version). After the model - * entity was deleted form the database, the method will also remove the model from the - * ModelManager - * - * @param model - */ - public void deleteModel(String version) { - if (version != null && !version.isEmpty()) { - boolean debug = logger.isLoggable(Level.FINE); - if (debug) { - logger.finest("......delete BPMNModel '" + version + "'..."); - } - Collection col = documentService.getDocumentsByType("model"); - for (ItemCollection modelEntity : col) { - // test version... - String oldVersion = modelEntity.getItemValueString("txtname"); - if (version.equals(oldVersion)) { - documentService.remove(modelEntity); + + /** + * This method loads an existing Model Entities from the database. A model + * entity is identified by its name (model version). + * + * @param model + */ + public ItemCollection loadModelEntity(String version) { + + if (version != null && !version.isEmpty()) { + boolean debug = logger.isLoggable(Level.FINE); + if (debug) { + logger.finest("......load BPMNModel Entity '" + version + "'..."); + } + + Collection col = documentService.getDocumentsByType("model"); + for (ItemCollection modelEntity : col) { + // test version... + String currentVersion = modelEntity.getItemValueString("txtname"); + if (version.equals(currentVersion)) { + return modelEntity; + } + } + } else { + logger.severe("deleteModel - invalid model version!"); + throw new InvalidAccessException(InvalidAccessException.INVALID_ID, + "loadModelEntity - invalid model version!"); } - } + return null; - removeModel(version); - } else { - logger.severe("deleteModel - invalid model version!"); - throw new InvalidAccessException(InvalidAccessException.INVALID_ID, - "deleteModel - invalid model version!"); } - } - - /** - * This method loads an existing Model Entities from the database. A model entity is identified by - * its name (model version). - * - * @param model - */ - public ItemCollection loadModelEntity(String version) { - - if (version != null && !version.isEmpty()) { - boolean debug = logger.isLoggable(Level.FINE); - if (debug) { - logger.finest("......load BPMNModel Entity '" + version + "'..."); - } - - Collection col = documentService.getDocumentsByType("model"); - for (ItemCollection modelEntity : col) { - // test version... - String currentVersion = modelEntity.getItemValueString("txtname"); - if (version.equals(currentVersion)) { - return modelEntity; + + /** + * Returns a BPMN DataObject, part of a Task or Event element, by its name + *

+ * DataObjects can be associated in a BPMN Diagram with a Task or an Event + * element + * + * @param bpmnElement + * @return + */ + @SuppressWarnings("unchecked") + public String getDataObject(ItemCollection bpmnElement, String name) { + + List> dataObjects = bpmnElement.getItemValue("dataObjects"); + + if (dataObjects != null && dataObjects.size() > 0) { + for (List dataObject : dataObjects) { + String key = dataObject.get(0); + if (name.equals(key)) { + return dataObject.get(1); + } + } } - } - } else { - logger.severe("deleteModel - invalid model version!"); - throw new InvalidAccessException(InvalidAccessException.INVALID_ID, - "loadModelEntity - invalid model version!"); + // not found! + return null; + } - return null; - - } - - /** - * Returns a BPMN DataObject, part of a Task or Event element, by its name - *

- * DataObjects can be associated in a BPMN Diagram with a Task or an Event element - * - * @param bpmnElement - * @return - */ - @SuppressWarnings("unchecked") - public String getDataObject(ItemCollection bpmnElement, String name) { - - List> dataObjects = bpmnElement.getItemValue("dataObjects"); - - if (dataObjects != null && dataObjects.size() > 0) { - for (List dataObject : dataObjects) { - String key = dataObject.get(0); - if (name.equals(key)) { - return dataObject.get(1); + + /** + * This method returns the modelStore or initialize it if not yet created. + * + * @return + */ + private Map getModelStore() { + if (modelStore == null) { + // create store (sorted map) + modelStore = new TreeMap(); + init(); } - } - } - // not found! - return null; - - } - - /** - * This method returns the modelStore or initialize it if not yet created. - * - * @return - */ - private Map getModelStore() { - if (modelStore == null) { - // create store (sorted map) - modelStore = new TreeMap(); - init(); + return modelStore; } - return modelStore; - } } diff --git a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/ProcessingEvent.java b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/ProcessingEvent.java index 9df2ed614..2cb2061c2 100644 --- a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/ProcessingEvent.java +++ b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/ProcessingEvent.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *

- *  Imixs Workflow 
+/*  
+ *  Imixs-Workflow 
+ *  
  *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
  *  http://www.imixs.com
  *  
@@ -22,18 +22,18 @@
  *      https://github.com/imixs/imixs-workflow
  *  
  *  Contributors:  
- *      Imixs Software Solutions GmbH - initial API and implementation
+ *      Imixs Software Solutions GmbH - Project Management
  *      Ralph Soika - Software Developer
- * 
- *******************************************************************************/ + */ package org.imixs.workflow.engine; import org.imixs.workflow.ItemCollection; /** - * The ProcessingEvent provides a CDI event fired by the WorkflowService EJB. This even can be used - * in a observer pattern of a service EJB to react on the life-cycle of a process instance. + * The ProcessingEvent provides a CDI event fired by the WorkflowService EJB. + * This even can be used in a observer pattern of a service EJB to react on the + * life-cycle of a process instance. *

* The ProcessingEvent defines the following event types: *

    @@ -50,23 +50,23 @@ */ public class ProcessingEvent { - public static final int BEFORE_PROCESS = 1; - public static final int AFTER_PROCESS = 2; + public static final int BEFORE_PROCESS = 1; + public static final int AFTER_PROCESS = 2; - private int eventType; - private ItemCollection document; + private int eventType; + private ItemCollection document; - public ProcessingEvent(ItemCollection document, int eventType) { - this.eventType = eventType; - this.document = document; - } + public ProcessingEvent(ItemCollection document, int eventType) { + this.eventType = eventType; + this.document = document; + } - public int getEventType() { - return eventType; - } + public int getEventType() { + return eventType; + } - public ItemCollection getDocument() { - return document; - } + public ItemCollection getDocument() { + return document; + } } diff --git a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/ReportService.java b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/ReportService.java index e6e4457ad..b162344fe 100644 --- a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/ReportService.java +++ b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/ReportService.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *
    - *  Imixs Workflow 
    +/*  
    + *  Imixs-Workflow 
    + *  
      *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
      *  http://www.imixs.com
      *  
    @@ -22,10 +22,9 @@
      *      https://github.com/imixs/imixs-workflow
      *  
      *  Contributors:  
    - *      Imixs Software Solutions GmbH - initial API and implementation
    + *      Imixs Software Solutions GmbH - Project Management
      *      Ralph Soika - Software Developer
    - * 
    - *******************************************************************************/ + */ package org.imixs.workflow.engine; @@ -65,742 +64,746 @@ import org.imixs.workflow.xml.XSLHandler; /** - * The ReportService supports methods to create, process and find report instances. + * The ReportService supports methods to create, process and find report + * instances. * - * A Report Entity is identified by its name represented by the attribute 'name' So a ReportService - * Implementation should ensure that name is a unique key for the report entity. + * A Report Entity is identified by its name represented by the attribute 'name' + * So a ReportService Implementation should ensure that name is a unique key for + * the report entity. * - * Also each report entity holds a EQL Query in the attribute "txtquery". this eql statement will be - * processed by the processQuery method and should return a collection of entities defined by the - * query. + * Also each report entity holds a EQL Query in the attribute "txtquery". this + * eql statement will be processed by the processQuery method and should return + * a collection of entities defined by the query. * * * @author Ralph Soika * */ -@DeclareRoles({"org.imixs.ACCESSLEVEL.NOACCESS", "org.imixs.ACCESSLEVEL.READERACCESS", - "org.imixs.ACCESSLEVEL.AUTHORACCESS", "org.imixs.ACCESSLEVEL.EDITORACCESS", - "org.imixs.ACCESSLEVEL.MANAGERACCESS"}) -@RolesAllowed({"org.imixs.ACCESSLEVEL.NOACCESS", "org.imixs.ACCESSLEVEL.READERACCESS", - "org.imixs.ACCESSLEVEL.AUTHORACCESS", "org.imixs.ACCESSLEVEL.EDITORACCESS", - "org.imixs.ACCESSLEVEL.MANAGERACCESS"}) +@DeclareRoles({ "org.imixs.ACCESSLEVEL.NOACCESS", "org.imixs.ACCESSLEVEL.READERACCESS", + "org.imixs.ACCESSLEVEL.AUTHORACCESS", "org.imixs.ACCESSLEVEL.EDITORACCESS", + "org.imixs.ACCESSLEVEL.MANAGERACCESS" }) +@RolesAllowed({ "org.imixs.ACCESSLEVEL.NOACCESS", "org.imixs.ACCESSLEVEL.READERACCESS", + "org.imixs.ACCESSLEVEL.AUTHORACCESS", "org.imixs.ACCESSLEVEL.EDITORACCESS", + "org.imixs.ACCESSLEVEL.MANAGERACCESS" }) @Stateless @LocalBean public class ReportService { - private static Logger logger = Logger.getLogger(ReportService.class.getName()); - - @Inject - DocumentService documentService; - - @Inject - WorkflowService workflowService; - - /** - * Returns a Report Entity by its identifier. The identifier can either be the $uniqueId of the - * report or the report name. The method returns null if no report with the given identifier - * exists. - * - * @param reportID - name of the report or its $uniqueId. - * @return ItemCollection representing the Report - */ - public ItemCollection findReport(String reportID) { - - ItemCollection result = null; - // try to load report by uniqueid - result = documentService.load(reportID); - if (result == null) { - // try to search for name - String searchTerm = "(type:\"ReportEntity\" AND txtname:\"" + reportID + "\")"; - Collection col; - try { - col = documentService.find(searchTerm, 1, 0); - } catch (QueryException e) { - logger.severe("findReport - invalid id: " + e.getMessage()); - return null; - } - if (col.size() > 0) { - result = col.iterator().next(); - } + private static Logger logger = Logger.getLogger(ReportService.class.getName()); + + @Inject + DocumentService documentService; + + @Inject + WorkflowService workflowService; + + /** + * Returns a Report Entity by its identifier. The identifier can either be the + * $uniqueId of the report or the report name. The method returns null if no + * report with the given identifier exists. + * + * @param reportID - name of the report or its $uniqueId. + * @return ItemCollection representing the Report + */ + public ItemCollection findReport(String reportID) { + + ItemCollection result = null; + // try to load report by uniqueid + result = documentService.load(reportID); + if (result == null) { + // try to search for name + String searchTerm = "(type:\"ReportEntity\" AND txtname:\"" + reportID + "\")"; + Collection col; + try { + col = documentService.find(searchTerm, 1, 0); + } catch (QueryException e) { + logger.severe("findReport - invalid id: " + e.getMessage()); + return null; + } + if (col.size() > 0) { + result = col.iterator().next(); + } + } + + return result; } - return result; - } - - /** - * Returns a list of all reports sorted by name. - * - * @return list of ItemCollection objects. - */ - public List findAllReports() { - List col = documentService.getDocumentsByType("ReportEntity"); - // sort resultset by name - Collections.sort(col, new ItemCollectionComparator("txtname", true)); - return col; - } - - /** - * updates a Entity Report Object. The Entity representing a report must have at least the - * attributes : txtQuery, numMaxCount, numStartPost, txtName. - * - * txtName is the unique key to be use to get a query. - * - * The method checks if a report with the same key allready exists. If so this report will be - * updated. If no report exists the new report will be created - * - * @param report - * @throws InvalidItemValueException - * @throws AccessDeniedException - * - */ - public void updateReport(ItemCollection aReport) throws AccessDeniedException { - - aReport.replaceItemValue("type", "ReportEntity"); - - // check if Report has a $uniqueid - String sUniqueID = aReport.getItemValueString("$uniqueID"); - // if not try to find report by its name - if ("".equals(sUniqueID)) { - String sReportName = aReport.getItemValueString("txtname"); - // try to find existing Report by name. - ItemCollection oldReport = findReport(sReportName); - if (oldReport != null) { - // old Report exists allready - aReport = updateReport(aReport, oldReport); - } + /** + * Returns a list of all reports sorted by name. + * + * @return list of ItemCollection objects. + */ + public List findAllReports() { + List col = documentService.getDocumentsByType("ReportEntity"); + // sort resultset by name + Collections.sort(col, new ItemCollectionComparator("txtname", true)); + return col; } - documentService.save(aReport); - } - - /** - * Returns the data source defined by a report. - *

    - * The method executes the lucene search query defined by the Report. The values of the returned - * entities will be cloned and formated in case a itemList is provided. - *

    - * The method parses the attribute txtname for a formating expression to format the item value. - * E.g.: - *

    - * {@code - * - * datDateyy-dd-mm - * - * } - *

    - * Optional the lucene search query my contain params which will be replaced by a given param Map: - *

    - * - *

    -   * ($created:{date_from})
    -   * 
    - *

    - * In this example the literal ?{date_from} will be replaced with the given value provided in the - * param map. - *

    - * - * @param reportName - name of the report to be executed - * - * @param startPos - optional start position to query entities - * @param maxcount - optional max count of entities to query - * @param params - optional parameter list to be mapped to the JQPL statement - * @param itemList - optional attribute list of items to be returned - * @return collection of entities - * @throws QueryException - * - */ - @SuppressWarnings("unchecked") - public List getDataSource(ItemCollection reportEntity, int pageSize, - int pageIndex, String sortBy, boolean sortReverse, Map params) - throws QueryException { - - List clonedResult = new ArrayList(); - - long l = System.currentTimeMillis(); - logger.finest("......executeReport: " + reportEntity.getItemValueString("txtname")); - - String query = reportEntity.getItemValueString("txtquery"); - - // replace params in query statement - if (params != null) { - Set keys = params.keySet(); - Iterator iter = keys.iterator(); - while (iter.hasNext()) { - // read key - String sKeyName = iter.next().toString().trim(); - String sParamValue = params.get(sKeyName); - // test if key is contained in query - if (query.indexOf("{" + sKeyName + "}") > -1) { - query = query.replace("{" + sKeyName + "}", sParamValue); - logger.finest("......executeReport set param " + sKeyName + "=" + sParamValue); - } else { - // support old param format - if (query.indexOf("?" + sKeyName) > -1) { - query = query.replace("?" + sKeyName, sParamValue); - logger.warning( - "......query definition in Report '" + reportEntity.getItemValueString("txtname") - + "' is deprecated! Please replace the param '?" + sKeyName + "' with '{" - + sKeyName + "}'"); - } + /** + * updates a Entity Report Object. The Entity representing a report must have at + * least the attributes : txtQuery, numMaxCount, numStartPost, txtName. + * + * txtName is the unique key to be use to get a query. + * + * The method checks if a report with the same key allready exists. If so this + * report will be updated. If no report exists the new report will be created + * + * @param report + * @throws InvalidItemValueException + * @throws AccessDeniedException + * + */ + public void updateReport(ItemCollection aReport) throws AccessDeniedException { + + aReport.replaceItemValue("type", "ReportEntity"); + + // check if Report has a $uniqueid + String sUniqueID = aReport.getItemValueString("$uniqueID"); + // if not try to find report by its name + if ("".equals(sUniqueID)) { + String sReportName = aReport.getItemValueString("txtname"); + // try to find existing Report by name. + ItemCollection oldReport = findReport(sReportName); + if (oldReport != null) { + // old Report exists allready + aReport = updateReport(aReport, oldReport); + } } - } + documentService.save(aReport); } - // now we replace dynamic Date values - query = replaceDateString(query); + /** + * Returns the data source defined by a report. + *

    + * The method executes the lucene search query defined by the Report. The values + * of the returned entities will be cloned and formated in case a itemList is + * provided. + *

    + * The method parses the attribute txtname for a formating expression to format + * the item value. E.g.: + *

    + * {@code + * + * datDateyy-dd-mm + * + * } + *

    + * Optional the lucene search query my contain params which will be replaced by + * a given param Map: + *

    + * + *

    +     * ($created:{date_from})
    +     * 
    + *

    + * In this example the literal ?{date_from} will be replaced with the given + * value provided in the param map. + *

    + * + * @param reportName - name of the report to be executed + * + * @param startPos - optional start position to query entities + * @param maxcount - optional max count of entities to query + * @param params - optional parameter list to be mapped to the JQPL + * statement + * @param itemList - optional attribute list of items to be returned + * @return collection of entities + * @throws QueryException + * + */ + @SuppressWarnings("unchecked") + public List getDataSource(ItemCollection reportEntity, int pageSize, int pageIndex, String sortBy, + boolean sortReverse, Map params) throws QueryException { + + List clonedResult = new ArrayList(); + + long l = System.currentTimeMillis(); + logger.finest("......executeReport: " + reportEntity.getItemValueString("txtname")); + + String query = reportEntity.getItemValueString("txtquery"); + + // replace params in query statement + if (params != null) { + Set keys = params.keySet(); + Iterator iter = keys.iterator(); + while (iter.hasNext()) { + // read key + String sKeyName = iter.next().toString().trim(); + String sParamValue = params.get(sKeyName); + // test if key is contained in query + if (query.indexOf("{" + sKeyName + "}") > -1) { + query = query.replace("{" + sKeyName + "}", sParamValue); + logger.finest("......executeReport set param " + sKeyName + "=" + sParamValue); + } else { + // support old param format + if (query.indexOf("?" + sKeyName) > -1) { + query = query.replace("?" + sKeyName, sParamValue); + logger.warning("......query definition in Report '" + reportEntity.getItemValueString("txtname") + + "' is deprecated! Please replace the param '?" + sKeyName + "' with '{" + sKeyName + + "}'"); + } + } - // execute query - logger.finest("......executeReport query=" + query); - List result = - documentService.find(query, pageSize, pageIndex, sortBy, sortReverse); - - // test if a itemList is provided or defined in the reportEntity... - List> attributes = (List>) reportEntity.getItemValue("attributes"); - List itemNames = new ArrayList(); - for (List attribute : attributes) { - itemNames.add(attribute.get(0)); - } - - // next we iterate over all entities from the result set and clone - // each entity with the given attribute list and format instructions - for (ItemCollection entity : result) { - - // in case _ChildItems are requested the entity will be - // duplicated for each child attribute. - // a child item is identified by the '~' char in the item name - List embeddedChildItems = getEmbeddedChildItems(entity, itemNames); - if (!embeddedChildItems.isEmpty()) { - for (ItemCollection child : embeddedChildItems) { - ItemCollection clone = cloneEntity(child, attributes); - clonedResult.add(clone); + } } - } else { - // default - clone the entity - ItemCollection clone = cloneEntity(entity, attributes); - clonedResult.add(clone); - } - } - logger.fine("...executed report '" + reportEntity.getItemValueString("txtname") + "' in " - + (System.currentTimeMillis() - l) + "ms"); - return clonedResult; - - } - - /** - * Transforms a datasource based on the XSL template from a report into a FileData object. - * - * @param report - the report definition - * @param data - the data source - * @param fileName - * @return FileData object containing the transformed data source. - * @throws JAXBException - * @throws TransformerException - * @throws IOException - */ - public FileData transformDataSource(ItemCollection report, List data, - String fileName) throws JAXBException, TransformerException, IOException { - - String xslTemplate = report.getItemValueString("xsl").trim(); - // execute the transformation based on the report defintion.... - String sContentType = report.getItemValueString("contenttype"); - if ("".equals(sContentType)) { - sContentType = MediaType.TEXT_XML; - } - String encoding = report.getItemValueString("encoding"); - if ("".equals(encoding)) { - // no encoding defined so we default to UTF-8 - encoding = "UTF-8"; - } - byte[] _bytes = null; - // create a ByteArray Output Stream - try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { - XSLHandler.transform(data, xslTemplate, encoding, outputStream); - _bytes = outputStream.toByteArray(); - } - FileData fileData = new FileData(fileName, _bytes, sContentType, null); - - return fileData; - - } - - /** - * This method parses a xml tag and computes a dynamic date by parsing the attributes: - * - * DAY_OF_MONTH - * - * DAY_OF_YEAR - * - * MONTH - * - * YEAR - * - * ADD (FIELD,OFFSET) - * - *

    - * e.g. {@code} - *

    - * results in 1. February of the current year - *

    - * - * {@code} - *

    - * results in 30.November of current year - * - * @param xmlDate - * @return - */ - public Calendar computeDynamicDate(String xmlDate) { - Calendar cal = Calendar.getInstance(); - - Map attributes = XMLParser.findAttributes(xmlDate); - - // test MONTH - if (attributes.containsKey("MONTH")) { - String value = attributes.get("MONTH"); - if ("ACTUAL_MAXIMUM".equalsIgnoreCase(value)) { - // last month of year - cal.set(Calendar.MONTH, cal.getActualMaximum(Calendar.MONTH)); - } else { - cal.set(Calendar.MONTH, Integer.parseInt(value) - 1); - } - } + // now we replace dynamic Date values + query = replaceDateString(query); - // test YEAR - if (attributes.containsKey("YEAR")) { - String value = attributes.get("YEAR"); - cal.set(Calendar.YEAR, Integer.parseInt(value)); + // execute query + logger.finest("......executeReport query=" + query); + List result = documentService.find(query, pageSize, pageIndex, sortBy, sortReverse); - } + // test if a itemList is provided or defined in the reportEntity... + List> attributes = (List>) reportEntity.getItemValue("attributes"); + List itemNames = new ArrayList(); + for (List attribute : attributes) { + itemNames.add(attribute.get(0)); + } - // test DAY_OF_MONTH - if (attributes.containsKey("DAY_OF_MONTH")) { - String value = attributes.get("DAY_OF_MONTH"); - if ("ACTUAL_MAXIMUM".equalsIgnoreCase(value)) { - // last day of month - cal.set(Calendar.DAY_OF_MONTH, cal.getActualMaximum(Calendar.DAY_OF_MONTH)); - } else { - cal.set(Calendar.DAY_OF_MONTH, Integer.parseInt(value)); - } - } + // next we iterate over all entities from the result set and clone + // each entity with the given attribute list and format instructions + for (ItemCollection entity : result) { + + // in case _ChildItems are requested the entity will be + // duplicated for each child attribute. + // a child item is identified by the '~' char in the item name + List embeddedChildItems = getEmbeddedChildItems(entity, itemNames); + if (!embeddedChildItems.isEmpty()) { + for (ItemCollection child : embeddedChildItems) { + ItemCollection clone = cloneEntity(child, attributes); + clonedResult.add(clone); + } + } else { + // default - clone the entity + ItemCollection clone = cloneEntity(entity, attributes); + clonedResult.add(clone); + } + } + logger.fine("...executed report '" + reportEntity.getItemValueString("txtname") + "' in " + + (System.currentTimeMillis() - l) + "ms"); + return clonedResult; - // test DAY_OF_YEAR - if (attributes.containsKey("DAY_OF_YEAR")) { - cal.set(Calendar.DAY_OF_YEAR, Integer.parseInt(attributes.get("DAY_OF_YEAR"))); } - // test ADD - if (attributes.containsKey("ADD")) { - String value = attributes.get("ADD"); - String[] fieldOffset = value.split(","); - - String field = fieldOffset[0]; - int offset = Integer.parseInt(fieldOffset[1]); + /** + * Transforms a datasource based on the XSL template from a report into a + * FileData object. + * + * @param report - the report definition + * @param data - the data source + * @param fileName + * @return FileData object containing the transformed data source. + * @throws JAXBException + * @throws TransformerException + * @throws IOException + */ + public FileData transformDataSource(ItemCollection report, List data, String fileName) + throws JAXBException, TransformerException, IOException { + + String xslTemplate = report.getItemValueString("xsl").trim(); + // execute the transformation based on the report defintion.... + String sContentType = report.getItemValueString("contenttype"); + if ("".equals(sContentType)) { + sContentType = MediaType.TEXT_XML; + } + String encoding = report.getItemValueString("encoding"); + if ("".equals(encoding)) { + // no encoding defined so we default to UTF-8 + encoding = "UTF-8"; + } - if ("MONTH".equalsIgnoreCase(field)) { - cal.add(Calendar.MONTH, offset); - } else if ("DAY_OF_MONTH".equalsIgnoreCase(field)) { - cal.add(Calendar.DAY_OF_MONTH, offset); - } else if ("DAY_OF_YEAR".equalsIgnoreCase(field)) { - cal.add(Calendar.DAY_OF_YEAR, offset); - } + byte[] _bytes = null; + // create a ByteArray Output Stream + try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + XSLHandler.transform(data, xslTemplate, encoding, outputStream); + _bytes = outputStream.toByteArray(); + } + FileData fileData = new FileData(fileName, _bytes, sContentType, null); - } + return fileData; - return cal; - - } - - /** - * This method replaces all occurrences of {@code} tags with the corresponding dynamic date. - * See computeDynamicdate. - * - * @param content - * @return - */ - public String replaceDateString(String content) { - - List dates = XMLParser.findTags(content, "date"); - for (String dateString : dates) { - Calendar cal = computeDynamicDate(dateString); - // convert into lucene format 20020101 - DateFormat f = new SimpleDateFormat("yyyyMMdd"); - // f.setTimeZone(tz); - String dateValue = f.format(cal.getTime()); - content = content.replace(dateString, dateValue); } - return content; - } - - /** - * This method converts a double value into a custom number format including an optional locale. - * - *

    -   * {@code
    -   * 
    -   * "###,###.###", "en_UK", 123456.789
    -   * 
    -   * "EUR #,###,##0.00", "de_DE", 1456.781
    -   * 
    -   * }
    -   * 
    - * - * @param pattern - * @param value - * @return - */ - public String customNumberFormat(String pattern, String locale, double value) { - DecimalFormat formatter = null; - Locale _locale = getLocaleFromString(locale); - // test if we have a locale - if (_locale != null) { - formatter = (DecimalFormat) DecimalFormat.getInstance(getLocaleFromString(locale)); - } else { - formatter = (DecimalFormat) DecimalFormat.getInstance(); - } - formatter.applyPattern(pattern); - String output = formatter.format(value); - - return output; - } - - /** - * This helper method clones a entity with a given format and converter map. - * - * @param formatMap - * @param converterMap - * @param entity - * @return - */ - @SuppressWarnings({"unused", "unchecked"}) - private ItemCollection cloneEntity(ItemCollection entity, List> attributes) { - ItemCollection clone = null; - - // if we have a itemList we clone each entity of the result set - if (attributes != null && attributes.size() > 0) { - clone = new ItemCollection(); - for (List attribute : attributes) { - - String field = attribute.get(0); - String label = "field"; - if (attribute.size() >= 1) { - label = attribute.get(1); + /** + * This method parses a xml tag and computes a dynamic date by parsing + * the attributes: + * + * DAY_OF_MONTH + * + * DAY_OF_YEAR + * + * MONTH + * + * YEAR + * + * ADD (FIELD,OFFSET) + * + *

    + * e.g. {@code} + *

    + * results in 1. February of the current year + *

    + * + * {@code} + *

    + * results in 30.November of current year + * + * @param xmlDate + * @return + */ + public Calendar computeDynamicDate(String xmlDate) { + Calendar cal = Calendar.getInstance(); + + Map attributes = XMLParser.findAttributes(xmlDate); + + // test MONTH + if (attributes.containsKey("MONTH")) { + String value = attributes.get("MONTH"); + if ("ACTUAL_MAXIMUM".equalsIgnoreCase(value)) { + // last month of year + cal.set(Calendar.MONTH, cal.getActualMaximum(Calendar.MONTH)); + } else { + cal.set(Calendar.MONTH, Integer.parseInt(value) - 1); + } } - String convert = ""; - if (attribute.size() >= 2) { - convert = attribute.get(2); + + // test YEAR + if (attributes.containsKey("YEAR")) { + String value = attributes.get("YEAR"); + cal.set(Calendar.YEAR, Integer.parseInt(value)); + } - String format = ""; - if (attribute.size() >= 3) { - format = attribute.get(3); + // test DAY_OF_MONTH + if (attributes.containsKey("DAY_OF_MONTH")) { + String value = attributes.get("DAY_OF_MONTH"); + if ("ACTUAL_MAXIMUM".equalsIgnoreCase(value)) { + // last day of month + cal.set(Calendar.DAY_OF_MONTH, cal.getActualMaximum(Calendar.DAY_OF_MONTH)); + } else { + cal.set(Calendar.DAY_OF_MONTH, Integer.parseInt(value)); + } } - String agregate = ""; - if (attribute.size() >= 4) { - agregate = attribute.get(4); + // test DAY_OF_YEAR + if (attributes.containsKey("DAY_OF_YEAR")) { + cal.set(Calendar.DAY_OF_YEAR, Integer.parseInt(attributes.get("DAY_OF_YEAR"))); } - // first look for converter + // test ADD + if (attributes.containsKey("ADD")) { + String value = attributes.get("ADD"); + String[] fieldOffset = value.split(","); - // did we have a format definition? - List values = entity.getItemValue(field); - if (!convert.isEmpty()) { - values = convertItemValue(entity, field, convert); - } + String field = fieldOffset[0]; + int offset = Integer.parseInt(fieldOffset[1]); - // did we have a format definition? - if (!format.isEmpty()) { - String sLocale = XMLParser.findAttribute(format, "locale"); - // test if we have a XML format tag - List content = XMLParser.findTagValues(format, "format"); - if (content.size() > 0) { - format = content.get(0); - } - // create string array of formated values - List rawValues = values; - values = new ArrayList(); - for (Object rawValue : rawValues) { - values.add(formatObjectValue(rawValue, format, sLocale)); - } + if ("MONTH".equalsIgnoreCase(field)) { + cal.add(Calendar.MONTH, offset); + } else if ("DAY_OF_MONTH".equalsIgnoreCase(field)) { + cal.add(Calendar.DAY_OF_MONTH, offset); + } else if ("DAY_OF_YEAR".equalsIgnoreCase(field)) { + cal.add(Calendar.DAY_OF_YEAR, offset); + } } - clone.replaceItemValue(field, values); - } - } else { - // clone all attributes - clone = (ItemCollection) entity.clone(); + return cal; } - return clone; - } - - /** - * This methode updates the a itemCollection with the attributes supported by another - * itemCollection without the $uniqueid - * - * @param aworkitem - * - */ - @SuppressWarnings("rawtypes") - private ItemCollection updateReport(ItemCollection newReport, ItemCollection oldReport) { - Iterator iter = newReport.getAllItems().entrySet().iterator(); - while (iter.hasNext()) { - Map.Entry mapEntry = (Map.Entry) iter.next(); - String sName = mapEntry.getKey().toString(); - Object o = mapEntry.getValue(); - if (isValidAttributeName(sName)) { - oldReport.replaceItemValue(sName, o); - } - } - return oldReport; - } - - /** - * This method returns true if the attribute name can be updated by a client. Workflow Attributes - * are not valid - * - * @param aName - * @return - */ - private boolean isValidAttributeName(String aName) { - if ("$creator".equalsIgnoreCase(aName)) - return false; - if ("namcreator".equalsIgnoreCase(aName)) - return false; - if ("$created".equalsIgnoreCase(aName)) - return false; - if ("$modified".equalsIgnoreCase(aName)) - return false; - if ("$uniqueID".equalsIgnoreCase(aName)) - return false; - if ("$isAuthor".equalsIgnoreCase(aName)) - return false; - - return true; - - } - - /** - * This helper method test the type of an object and formats the objects value. - * - * If the object if from type Date or Calendar it will be formated unsing the Java - * SimpleDateFormat. - * - * If the object is String, Integer or Double the method tries to format the value into a number - * - * - * - * @param o - * @return - */ - private String formatObjectValue(Object o, String format, String locale) { - String singleValue = ""; - Date dateValue = null; - - // now test the objct type to date - if (o instanceof Date) { - dateValue = (Date) o; + + /** + * This method replaces all occurrences of {@code} tags with the + * corresponding dynamic date. See computeDynamicdate. + * + * @param content + * @return + */ + public String replaceDateString(String content) { + + List dates = XMLParser.findTags(content, "date"); + for (String dateString : dates) { + Calendar cal = computeDynamicDate(dateString); + // convert into lucene format 20020101 + DateFormat f = new SimpleDateFormat("yyyyMMdd"); + // f.setTimeZone(tz); + String dateValue = f.format(cal.getTime()); + content = content.replace(dateString, dateValue); + } + + return content; } - if (o instanceof Calendar) { - Calendar cal = (Calendar) o; - dateValue = cal.getTime(); + /** + * This method converts a double value into a custom number format including an + * optional locale. + * + *
    +     * {@code
    +     * 
    +     * "###,###.###", "en_UK", 123456.789
    +     * 
    +     * "EUR #,###,##0.00", "de_DE", 1456.781
    +     * 
    +     * }
    +     * 
    + * + * @param pattern + * @param value + * @return + */ + public String customNumberFormat(String pattern, String locale, double value) { + DecimalFormat formatter = null; + Locale _locale = getLocaleFromString(locale); + // test if we have a locale + if (_locale != null) { + formatter = (DecimalFormat) DecimalFormat.getInstance(getLocaleFromString(locale)); + } else { + formatter = (DecimalFormat) DecimalFormat.getInstance(); + } + formatter.applyPattern(pattern); + String output = formatter.format(value); + + return output; } - // format date string? - if (dateValue != null) { - if (format != null && !"".equals(format)) { - // format date with provided formater - try { - SimpleDateFormat formatter = null; - if (locale != null && !locale.isEmpty()) { - formatter = new SimpleDateFormat(format, getLocaleFromString(locale)); - } else { - formatter = new SimpleDateFormat(format); - } - singleValue = formatter.format(dateValue); - } catch (Exception ef) { - Logger logger = Logger.getLogger(AbstractPlugin.class.getName()); - logger.warning("ReportService: Invalid format String '" + format + "'"); - logger.warning("ReportService: Can not format value - error: " + ef.getMessage()); - return "" + dateValue; + /** + * This helper method clones a entity with a given format and converter map. + * + * @param formatMap + * @param converterMap + * @param entity + * @return + */ + @SuppressWarnings({ "unused", "unchecked" }) + private ItemCollection cloneEntity(ItemCollection entity, List> attributes) { + ItemCollection clone = null; + + // if we have a itemList we clone each entity of the result set + if (attributes != null && attributes.size() > 0) { + clone = new ItemCollection(); + for (List attribute : attributes) { + + String field = attribute.get(0); + String label = "field"; + if (attribute.size() >= 1) { + label = attribute.get(1); + } + String convert = ""; + if (attribute.size() >= 2) { + convert = attribute.get(2); + } + + String format = ""; + if (attribute.size() >= 3) { + format = attribute.get(3); + } + + String agregate = ""; + if (attribute.size() >= 4) { + agregate = attribute.get(4); + } + + // first look for converter + + // did we have a format definition? + List values = entity.getItemValue(field); + if (!convert.isEmpty()) { + values = convertItemValue(entity, field, convert); + } + + // did we have a format definition? + if (!format.isEmpty()) { + String sLocale = XMLParser.findAttribute(format, "locale"); + // test if we have a XML format tag + List content = XMLParser.findTagValues(format, "format"); + if (content.size() > 0) { + format = content.get(0); + } + // create string array of formated values + List rawValues = values; + values = new ArrayList(); + for (Object rawValue : rawValues) { + values.add(formatObjectValue(rawValue, format, sLocale)); + } + + } + + clone.replaceItemValue(field, values); + } + } else { + // clone all attributes + clone = (ItemCollection) entity.clone(); + } - } else { - // use standard formate short/short - singleValue = - DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT).format(dateValue); - } - - } else { - // test if number formater is provided.... - if (format.contains("#")) { - try { - double d = Double.parseDouble(o.toString()); - singleValue = customNumberFormat(format, locale, d); - } catch (IllegalArgumentException e) { - logger.warning("Format Error (" + format + ") = " + e.getMessage()); - singleValue = "0"; + return clone; + } + + /** + * This methode updates the a itemCollection with the attributes supported by + * another itemCollection without the $uniqueid + * + * @param aworkitem + * + */ + @SuppressWarnings("rawtypes") + private ItemCollection updateReport(ItemCollection newReport, ItemCollection oldReport) { + Iterator iter = newReport.getAllItems().entrySet().iterator(); + while (iter.hasNext()) { + Map.Entry mapEntry = (Map.Entry) iter.next(); + String sName = mapEntry.getKey().toString(); + Object o = mapEntry.getValue(); + if (isValidAttributeName(sName)) { + oldReport.replaceItemValue(sName, o); + } } + return oldReport; + } + + /** + * This method returns true if the attribute name can be updated by a client. + * Workflow Attributes are not valid + * + * @param aName + * @return + */ + private boolean isValidAttributeName(String aName) { + if ("$creator".equalsIgnoreCase(aName)) + return false; + if ("namcreator".equalsIgnoreCase(aName)) + return false; + if ("$created".equalsIgnoreCase(aName)) + return false; + if ("$modified".equalsIgnoreCase(aName)) + return false; + if ("$uniqueID".equalsIgnoreCase(aName)) + return false; + if ("$isAuthor".equalsIgnoreCase(aName)) + return false; + + return true; - } else { - // return object as string - singleValue = o.toString(); - } } - return singleValue; + /** + * This helper method test the type of an object and formats the objects value. + * + * If the object if from type Date or Calendar it will be formated unsing the + * Java SimpleDateFormat. + * + * If the object is String, Integer or Double the method tries to format the + * value into a number + * + * + * + * @param o + * @return + */ + private String formatObjectValue(Object o, String format, String locale) { + String singleValue = ""; + Date dateValue = null; + + // now test the objct type to date + if (o instanceof Date) { + dateValue = (Date) o; + } - } + if (o instanceof Calendar) { + Calendar cal = (Calendar) o; + dateValue = cal.getTime(); + } - /** - * This method converts a single item value into a specified type. If the converter is not - * adaptable a default value will be set. - * - * - * @param o - * @return - */ - @SuppressWarnings({"unchecked", "rawtypes"}) - private List convertItemValue(ItemCollection itemcol, String itemName, String converter) { + // format date string? + if (dateValue != null) { + if (format != null && !"".equals(format)) { + // format date with provided formater + try { + SimpleDateFormat formatter = null; + if (locale != null && !locale.isEmpty()) { + formatter = new SimpleDateFormat(format, getLocaleFromString(locale)); + } else { + formatter = new SimpleDateFormat(format); + } + singleValue = formatter.format(dateValue); + } catch (Exception ef) { + Logger logger = Logger.getLogger(AbstractPlugin.class.getName()); + logger.warning("ReportService: Invalid format String '" + format + "'"); + logger.warning("ReportService: Can not format value - error: " + ef.getMessage()); + return "" + dateValue; + } + } else { + // use standard formate short/short + singleValue = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT).format(dateValue); + } - if (converter == null || converter.isEmpty()) { - return itemcol.getItemValue(itemName); - } + } else { + // test if number formater is provided.... + if (format.contains("#")) { + try { + double d = Double.parseDouble(o.toString()); + singleValue = customNumberFormat(format, locale, d); + } catch (IllegalArgumentException e) { + logger.warning("Format Error (" + format + ") = " + e.getMessage()); + singleValue = "0"; + } + + } else { + // return object as string + singleValue = o.toString(); + } + } - List values = itemcol.getItemValue(itemName); - // if vector is empty we add a dummy null value here! - if (values.size() == 0) { - values.add(null); - } + return singleValue; - // first test if we have a custom converter - List adaptedValueList = null; - if (converter.startsWith("<") && converter.endsWith(">")) { - try { - logger.finest("......converter = " + converter); - // adapt the value list... - adaptedValueList = workflowService.adaptTextList(converter, itemcol); - } catch (PluginException e) { - logger.warning("Unable to adapt text converter: " + converter); - } } - if (adaptedValueList != null) { - values = new ArrayList(); - values.addAll(adaptedValueList); - } + /** + * This method converts a single item value into a specified type. If the + * converter is not adaptable a default value will be set. + * + * + * @param o + * @return + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + private List convertItemValue(ItemCollection itemcol, String itemName, String converter) { + + if (converter == null || converter.isEmpty()) { + return itemcol.getItemValue(itemName); + } - for (int i = 0; i < values.size(); i++) { - Object o = values.get(i); - - if (converter.equalsIgnoreCase("double") || converter.equalsIgnoreCase("xs:decimal")) { - try { - double d = 0; - if (o != null) { - d = Double.parseDouble(o.toString()); - } - values.set(i, d); - } catch (NumberFormatException e) { - values.set(i, new Double(0)); + List values = itemcol.getItemValue(itemName); + // if vector is empty we add a dummy null value here! + if (values.size() == 0) { + values.add(null); } - } - - if (converter.equalsIgnoreCase("integer") || converter.equalsIgnoreCase("xs:int")) { - try { - int d = 0; - if (o != null) { - i = Integer.parseInt(o.toString()); - } - values.set(i, d); - } catch (NumberFormatException e) { - values.set(i, new Integer(0)); + + // first test if we have a custom converter + List adaptedValueList = null; + if (converter.startsWith("<") && converter.endsWith(">")) { + try { + logger.finest("......converter = " + converter); + // adapt the value list... + adaptedValueList = workflowService.adaptTextList(converter, itemcol); + } catch (PluginException e) { + logger.warning("Unable to adapt text converter: " + converter); + } } - } - } - return values; - - } - - /** - * generates a Locale Object form a String - * - * fr_FR , en_US, - * - * @param sLocale - * @return - */ - private static Locale getLocaleFromString(String sLocale) { - Locale locale = null; - - // genreate locale? - if (sLocale != null && !sLocale.isEmpty()) { - // split locale - StringTokenizer stLocale = new StringTokenizer(sLocale, "_"); - if (stLocale.countTokens() == 1) { - // only language variant - String sLang = stLocale.nextToken(); - String sCount = sLang.toUpperCase(); - locale = new Locale(sLang, sCount); - } else { - // language and country - String sLang = stLocale.nextToken(); - String sCount = stLocale.nextToken(); - locale = new Locale(sLang, sCount); - } + if (adaptedValueList != null) { + values = new ArrayList(); + values.addAll(adaptedValueList); + } + + for (int i = 0; i < values.size(); i++) { + Object o = values.get(i); + + if (converter.equalsIgnoreCase("double") || converter.equalsIgnoreCase("xs:decimal")) { + try { + double d = 0; + if (o != null) { + d = Double.parseDouble(o.toString()); + } + values.set(i, d); + } catch (NumberFormatException e) { + values.set(i, new Double(0)); + } + } + + if (converter.equalsIgnoreCase("integer") || converter.equalsIgnoreCase("xs:int")) { + try { + int d = 0; + if (o != null) { + i = Integer.parseInt(o.toString()); + } + values.set(i, d); + } catch (NumberFormatException e) { + values.set(i, new Integer(0)); + } + } + + } + return values; + } - return locale; - } - - /** - * This method returns all embedded child items of a entity. The childItem are identified by a - * fieldname containing a '~'. The left part is the container item (List of Map), the right part - * is the attribute name in the child itemcollection. - * - * @param entity - * @param keySet - * @return - */ - @SuppressWarnings({"unchecked", "rawtypes"}) - private List getEmbeddedChildItems(ItemCollection entity, - List fieldNames) { - List embeddedItemNames = new ArrayList(); - List result = new ArrayList(); - // first find all items containing a child element - for (String field : fieldNames) { - field = field.toLowerCase(); - if (field.contains("~")) { - field = field.substring(0, field.indexOf('~')); - if (!embeddedItemNames.contains(field)) { - embeddedItemNames.add(field); + /** + * generates a Locale Object form a String + * + * fr_FR , en_US, + * + * @param sLocale + * @return + */ + private static Locale getLocaleFromString(String sLocale) { + Locale locale = null; + + // genreate locale? + if (sLocale != null && !sLocale.isEmpty()) { + // split locale + StringTokenizer stLocale = new StringTokenizer(sLocale, "_"); + if (stLocale.countTokens() == 1) { + // only language variant + String sLang = stLocale.nextToken(); + String sCount = sLang.toUpperCase(); + locale = new Locale(sLang, sCount); + } else { + // language and country + String sLang = stLocale.nextToken(); + String sCount = stLocale.nextToken(); + locale = new Locale(sLang, sCount); + } } - } + + return locale; } - if (!embeddedItemNames.isEmpty()) { - for (String field : embeddedItemNames) { - List mapChildItems = entity.getItemValue(field); - // try to convert - for (Object mapOderItem : mapChildItems) { - if (mapOderItem instanceof Map) { - ItemCollection child = new ItemCollection((Map) mapOderItem); - // clone entity and add all map entries - ItemCollection clone = new ItemCollection(entity); - Set childFieldNameList = child.getAllItems().keySet(); - for (String childFieldName : childFieldNameList) { - clone.replaceItemValue(field + "~" + childFieldName, - child.getItemValue(childFieldName)); + + /** + * This method returns all embedded child items of a entity. The childItem are + * identified by a fieldname containing a '~'. The left part is the container + * item (List of Map), the right part is the attribute name in the child + * itemcollection. + * + * @param entity + * @param keySet + * @return + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + private List getEmbeddedChildItems(ItemCollection entity, List fieldNames) { + List embeddedItemNames = new ArrayList(); + List result = new ArrayList(); + // first find all items containing a child element + for (String field : fieldNames) { + field = field.toLowerCase(); + if (field.contains("~")) { + field = field.substring(0, field.indexOf('~')); + if (!embeddedItemNames.contains(field)) { + embeddedItemNames.add(field); + } + } + } + if (!embeddedItemNames.isEmpty()) { + for (String field : embeddedItemNames) { + List mapChildItems = entity.getItemValue(field); + // try to convert + for (Object mapOderItem : mapChildItems) { + if (mapOderItem instanceof Map) { + ItemCollection child = new ItemCollection((Map) mapOderItem); + // clone entity and add all map entries + ItemCollection clone = new ItemCollection(entity); + Set childFieldNameList = child.getAllItems().keySet(); + for (String childFieldName : childFieldNameList) { + clone.replaceItemValue(field + "~" + childFieldName, child.getItemValue(childFieldName)); + } + result.add(clone); + } + } } - result.add(clone); - } } - } + return result; } - return result; - } } diff --git a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/SetupEvent.java b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/SetupEvent.java index f5825d4ea..cfa0e2487 100644 --- a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/SetupEvent.java +++ b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/SetupEvent.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *
    - *  Imixs Workflow 
    +/*  
    + *  Imixs-Workflow 
    + *  
      *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
      *  http://www.imixs.com
      *  
    @@ -22,16 +22,16 @@
      *      https://github.com/imixs/imixs-workflow
      *  
      *  Contributors:  
    - *      Imixs Software Solutions GmbH - initial API and implementation
    + *      Imixs Software Solutions GmbH - Project Management
      *      Ralph Soika - Software Developer
    - * 
    - *******************************************************************************/ + */ package org.imixs.workflow.engine; /** - * The SetupEvent provides a CDI observer pattern. The SetupEvent is fired by the SetupService EJB. - * An event Observer can react on this event to extend the setup routine. + * The SetupEvent provides a CDI observer pattern. The SetupEvent is fired by + * the SetupService EJB. An event Observer can react on this event to extend the + * setup routine. * * * @author Ralph Soika @@ -40,10 +40,9 @@ */ public class SetupEvent { - public SetupEvent() { - super(); - - } + public SetupEvent() { + super(); + } } diff --git a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/SetupService.java b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/SetupService.java index 12963280c..ed25dc1e7 100644 --- a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/SetupService.java +++ b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/SetupService.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *
    - *  Imixs Workflow 
    +/*  
    + *  Imixs-Workflow 
    + *  
      *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
      *  http://www.imixs.com
      *  
    @@ -22,10 +22,9 @@
      *      https://github.com/imixs/imixs-workflow
      *  
      *  Contributors:  
    - *      Imixs Software Solutions GmbH - initial API and implementation
    + *      Imixs Software Solutions GmbH - Project Management
      *      Ralph Soika - Software Developer
    - * 
    - *******************************************************************************/ + */ package org.imixs.workflow.engine; @@ -69,356 +68,354 @@ import org.xml.sax.SAXException; /** - * The SetupService EJB initializes the Imxis-Workflow engine and returns the current status. + * The SetupService EJB initializes the Imxis-Workflow engine and returns the + * current status. *

    - * During startup, the service loads a default model defined by the optional environment variable - * 'MODEL_DEFAULT_DATA'. This variable can point to multiple model resources separated by a ';'. A - * model resource file must have the file extension '.bpmn'. + * During startup, the service loads a default model defined by the optional + * environment variable 'MODEL_DEFAULT_DATA'. This variable can point to + * multiple model resources separated by a ';'. A model resource file must have + * the file extension '.bpmn'. *

    - * The variable can be defined also in the imixs.properties file. In this case the variable is - * named: 'model.default.data'. + * The variable can be defined also in the imixs.properties file. In this case + * the variable is named: 'model.default.data'. *

    - * Optional it is also possible to provide setup workflow initial data in a XML file. + * Optional it is also possible to provide setup workflow initial data in a XML + * file. *

    * Finally the service starts optional registered scheduler services. *

    - * With the method 'getModelCount' the service returns the current status of the workflow engine by - * returning the count of valid workflow models. + * With the method 'getModelCount' the service returns the current status of the + * workflow engine by returning the count of valid workflow models. *

    - * The SetupSerivce has a migration method to migrate old Workflow Schedulers into the new Scheduler - * concept. The method migrateWorkflowScheduler is nust for migration and can be deprecated in - * future releases. + * The SetupSerivce has a migration method to migrate old Workflow Schedulers + * into the new Scheduler concept. The method migrateWorkflowScheduler is nust + * for migration and can be deprecated in future releases. * * @author rsoika * @version 1.0 */ -@DeclareRoles({"org.imixs.ACCESSLEVEL.MANAGERACCESS"}) +@DeclareRoles({ "org.imixs.ACCESSLEVEL.MANAGERACCESS" }) @RunAs("org.imixs.ACCESSLEVEL.MANAGERACCESS") @Startup @Singleton public class SetupService { - public static String SETUP_OK = "OK"; - public static String MODEL_INITIALIZED = "MODEL_INITIALIZED"; + public static String SETUP_OK = "OK"; + public static String MODEL_INITIALIZED = "MODEL_INITIALIZED"; - private static Logger logger = Logger.getLogger(SetupService.class.getName()); + private static Logger logger = Logger.getLogger(SetupService.class.getName()); - @Inject - @ConfigProperty(name = "model.default.data", defaultValue = "") - private String modelDefaultData; + @Inject + @ConfigProperty(name = "model.default.data", defaultValue = "") + private String modelDefaultData; - @Inject - @ConfigProperty(name = "model.default.data.overwrite", defaultValue = "false") - private boolean modelDefaultDataOverwrite; + @Inject + @ConfigProperty(name = "model.default.data.overwrite", defaultValue = "false") + private boolean modelDefaultDataOverwrite; - @Inject - private DocumentService documentService; + @Inject + private DocumentService documentService; - @Inject - private ModelService modelService; + @Inject + private ModelService modelService; - @Inject - private SchedulerService schedulerService; + @Inject + private SchedulerService schedulerService; - @Resource - private javax.ejb.TimerService timerService; + @Resource + private javax.ejb.TimerService timerService; - @Inject - protected Event setupEvents; + @Inject + protected Event setupEvents; + /** + * This method start the system setup during deployment + * + * @throws AccessDeniedException + */ + @PostConstruct + public void startup() { - /** - * This method start the system setup during deployment - * - * @throws AccessDeniedException - */ - @PostConstruct - public void startup() { + logger.info(" ____ "); + logger.info(" / _/_ _ (_)_ __ ___ Workflow"); + logger.info(" _/ // ' \\/ /\\ \\ /(_-< Engine"); + logger.info("/___/_/_/_/_//_\\_\\/___/ V5.1"); + logger.info(""); - logger.info(" ____ "); - logger.info(" / _/_ _ (_)_ __ ___ Workflow"); - logger.info(" _/ // ' \\/ /\\ \\ /(_-< Engine"); - logger.info("/___/_/_/_/_//_\\_\\/___/ V5.1"); - logger.info(""); + logger.info("...initalizing models..."); + + // first we scan for default models + List models = modelService.getVersions(); + if (models.isEmpty() || modelDefaultDataOverwrite == true) { + scanDefaultModels(); + } else { + for (String model : models) { + logger.info("...model: " + model + " ...OK"); + } + } - logger.info("...initalizing models..."); + // Finally fire the SetupEvent. This allows CDI Observers to react on the setup + if (setupEvents != null) { + // create Group Event + SetupEvent setupEvent = new SetupEvent(); + setupEvents.fire(setupEvent); + } else { + logger.warning("Missing CDI support for Event !"); + } - // first we scan for default models - List models = modelService.getVersions(); - if (models.isEmpty() || modelDefaultDataOverwrite == true) { - scanDefaultModels(); - } else { - for (String model : models) { - logger.info("...model: " + model + " ...OK"); - } - } + // migrate old workflow scheduler + migrateWorkflowScheduler(); + // Finally start optional schedulers + logger.info("...initalizing schedulers..."); + schedulerService.startAllSchedulers(); - // Finally fire the SetupEvent. This allows CDI Observers to react on the setup - if (setupEvents != null) { - // create Group Event - SetupEvent setupEvent = new SetupEvent(); - setupEvents.fire(setupEvent); - } else { - logger.warning("Missing CDI support for Event !"); } + /** + * Returns the count of available model versions + * + * @return + */ + public int getModelVersionCount() { + return modelService.getVersions().size(); + } - - // migrate old workflow scheduler - migrateWorkflowScheduler(); - - // Finally start optional schedulers - logger.info("...initalizing schedulers..."); - schedulerService.startAllSchedulers(); - - } - - /** - * Returns the count of available model versions - * - * @return - */ - public int getModelVersionCount() { - return modelService.getVersions().size(); - } - - /** - * Returns the count of available unique model groups - * - * @return - */ - public int getModelGroupCount() { - return modelService.getGroups().size(); - } - - - /** - * This method loads the default model if no models exist in the current instance - * - * @return - status - */ - public void scanDefaultModels() { - logger.finest("......scan default models..."); - // test if we have an environment variable or a property value... - String modelData = modelDefaultData; - - if ("".equals(modelData)) { - // no model data to scan - return; + /** + * Returns the count of available unique model groups + * + * @return + */ + public int getModelGroupCount() { + return modelService.getGroups().size(); } + /** + * This method loads the default model if no models exist in the current + * instance + * + * @return - status + */ + public void scanDefaultModels() { + logger.finest("......scan default models..."); + // test if we have an environment variable or a property value... + String modelData = modelDefaultData; + + if ("".equals(modelData)) { + // no model data to scan + return; + } + + String[] modelResources = modelData.split(";"); + for (String modelResource : modelResources) { + + // try to load this model + + // test if bpmn model? + if (modelResource.endsWith(".bpmn") || modelResource.endsWith(".xml")) { + logger.info("...uploading default model file: '" + modelResource + "'...."); + // if resource starts with '/' then we pickp the file form the filesystem. + // otherwise we load it as a resource bundle. + InputStream inputStream = null; + try { + if (modelResource.startsWith("/")) { + File initialFile = new File(modelResource); + inputStream = new FileInputStream(initialFile); + } else { + inputStream = SetupService.class.getClassLoader().getResourceAsStream(modelResource); + } + // parse model file.... + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + int next; + + next = inputStream.read(); + while (next > -1) { + bos.write(next); + next = inputStream.read(); + } + bos.flush(); + byte[] result = bos.toByteArray(); + + // is BPMN? + if (modelResource.endsWith(".bpmn")) { + BPMNModel model = BPMNParser.parseModel(result, "UTF-8"); + modelService.saveModel(model); + } else { + // XML + importXmlEntityData(result); + } + + // issue #600 return; // MODEL_INITIALIZED; + } catch (IOException | ModelException | ParseException | ParserConfigurationException + | SAXException e) { + throw new RuntimeException( + "Failed to load model configuration: " + e.getMessage() + " check 'model.default.data'", e); + } finally { + if (inputStream != null) { + try { + inputStream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + } else { + logger.severe("Wrong model format: '" + modelResource + "' - expected *.bpmn or *.xml"); + } - String[] modelResources = modelData.split(";"); - for (String modelResource : modelResources) { + } + // SETUP_OK; - // try to load this model + } - // test if bpmn model? - if (modelResource.endsWith(".bpmn") || modelResource.endsWith(".xml")) { - logger.info("...uploading default model file: '" + modelResource + "'...."); - // if resource starts with '/' then we pickp the file form the filesystem. - // otherwise we load it as a resource bundle. - InputStream inputStream = null; + /** + * this method imports an xml entity data stream. This is used to provide model + * uploads during the system setup. The method can also import general entity + * data like configuration data. + * + * @param event + * @throws Exception + */ + public void importXmlEntityData(byte[] filestream) { + XMLDocument entity; + ItemCollection itemCollection; + String sModelVersion = null; + + if (filestream == null) + return; try { - if (modelResource.startsWith("/")) { - File initialFile = new File(modelResource); - inputStream = new FileInputStream(initialFile); - } else { - inputStream = SetupService.class.getClassLoader().getResourceAsStream(modelResource); - } - // parse model file.... - - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - int next; - - next = inputStream.read(); - while (next > -1) { - bos.write(next); - next = inputStream.read(); - } - bos.flush(); - byte[] result = bos.toByteArray(); - - // is BPMN? - if (modelResource.endsWith(".bpmn")) { - BPMNModel model = BPMNParser.parseModel(result, "UTF-8"); - modelService.saveModel(model); - } else { - // XML - importXmlEntityData(result); - } - - // issue #600 return; // MODEL_INITIALIZED; - } catch (IOException | ModelException | ParseException | ParserConfigurationException - | SAXException e) { - throw new RuntimeException("Failed to load model configuration: " + e.getMessage() - + " check 'model.default.data'", e); - } finally { - if (inputStream != null) { + + XMLDataCollection ecol = null; + logger.fine("importXmlEntityData - importModel, verifing file content...."); + + JAXBContext context; + Object jaxbObject = null; + // unmarshall the model file + ByteArrayInputStream input = new ByteArrayInputStream(filestream); try { - inputStream.close(); - } catch (IOException e) { - e.printStackTrace(); + context = JAXBContext.newInstance(XMLDataCollection.class); + Unmarshaller m = context.createUnmarshaller(); + jaxbObject = m.unmarshal(input); + } catch (JAXBException e) { + throw new ModelException(ModelException.INVALID_MODEL, + "error - wrong xml file format - unable to import model file: ", e); + } + if (jaxbObject == null) + throw new ModelException(ModelException.INVALID_MODEL, + "error - wrong xml file format - unable to import model file!"); + + ecol = (XMLDataCollection) jaxbObject; + // import the model entities.... + if (ecol.getDocument().length > 0) { + + Vector vModelVersions = new Vector(); + // first iterrate over all enttity and find if model entries are + // included + for (XMLDocument aentity : ecol.getDocument()) { + itemCollection = XMLDocumentAdapter.putDocument(aentity); + // test if this is a model entry + // (type=WorkflowEnvironmentEntity) + if ("WorkflowEnvironmentEntity".equals(itemCollection.getItemValueString("type")) + && "environment.profile".equals(itemCollection.getItemValueString("txtName"))) { + + sModelVersion = itemCollection.getItemValueString("$ModelVersion"); + if (vModelVersions.indexOf(sModelVersion) == -1) + vModelVersions.add(sModelVersion); + } + } + // now remove old model entries.... + for (String aModelVersion : vModelVersions) { + logger.fine("importXmlEntityData - removing existing configuration for model version '" + + aModelVersion + "'"); + modelService.removeModel(aModelVersion); + } + // save new entities into database and update modelversion..... + for (int i = 0; i < ecol.getDocument().length; i++) { + entity = ecol.getDocument()[i]; + itemCollection = XMLDocumentAdapter.putDocument(entity); + // save entity + documentService.save(itemCollection); + } + + logger.fine("importXmlEntityData - " + ecol.getDocument().length + " entries sucessfull imported"); } - } - } - - } else { - logger.severe("Wrong model format: '" + modelResource + "' - expected *.bpmn or *.xml"); - } - } - // SETUP_OK; - - } - - /** - * this method imports an xml entity data stream. This is used to provide model uploads during the - * system setup. The method can also import general entity data like configuration data. - * - * @param event - * @throws Exception - */ - public void importXmlEntityData(byte[] filestream) { - XMLDocument entity; - ItemCollection itemCollection; - String sModelVersion = null; - - if (filestream == null) - return; - try { - - XMLDataCollection ecol = null; - logger.fine("importXmlEntityData - importModel, verifing file content...."); - - JAXBContext context; - Object jaxbObject = null; - // unmarshall the model file - ByteArrayInputStream input = new ByteArrayInputStream(filestream); - try { - context = JAXBContext.newInstance(XMLDataCollection.class); - Unmarshaller m = context.createUnmarshaller(); - jaxbObject = m.unmarshal(input); - } catch (JAXBException e) { - throw new ModelException(ModelException.INVALID_MODEL, - "error - wrong xml file format - unable to import model file: ", e); - } - if (jaxbObject == null) - throw new ModelException(ModelException.INVALID_MODEL, - "error - wrong xml file format - unable to import model file!"); - - ecol = (XMLDataCollection) jaxbObject; - // import the model entities.... - if (ecol.getDocument().length > 0) { - - Vector vModelVersions = new Vector(); - // first iterrate over all enttity and find if model entries are - // included - for (XMLDocument aentity : ecol.getDocument()) { - itemCollection = XMLDocumentAdapter.putDocument(aentity); - // test if this is a model entry - // (type=WorkflowEnvironmentEntity) - if ("WorkflowEnvironmentEntity".equals(itemCollection.getItemValueString("type")) - && "environment.profile".equals(itemCollection.getItemValueString("txtName"))) { - - sModelVersion = itemCollection.getItemValueString("$ModelVersion"); - if (vModelVersions.indexOf(sModelVersion) == -1) - vModelVersions.add(sModelVersion); - } - } - // now remove old model entries.... - for (String aModelVersion : vModelVersions) { - logger.fine("importXmlEntityData - removing existing configuration for model version '" - + aModelVersion + "'"); - modelService.removeModel(aModelVersion); - } - // save new entities into database and update modelversion..... - for (int i = 0; i < ecol.getDocument().length; i++) { - entity = ecol.getDocument()[i]; - itemCollection = XMLDocumentAdapter.putDocument(entity); - // save entity - documentService.save(itemCollection); + } catch (Exception e) { + e.printStackTrace(); } - logger.fine( - "importXmlEntityData - " + ecol.getDocument().length + " entries sucessfull imported"); - } - - } catch (Exception e) { - e.printStackTrace(); } - } - - /** - * This method migrates the deprecated WorkflowScheduelr configuraiton into the new Imixs - * Scheduler API - */ - public void migrateWorkflowScheduler() { - // lets see if we have an old scheduler configuration.... - - ItemCollection configItemCollection = null; - String searchTerm = "(type:\"configuration\" AND txtname:\"org.imixs.workflow.scheduler\")"; - Collection col; - try { - col = documentService.find(searchTerm, 1, 0); - } catch (QueryException e) { - logger.severe("loadConfiguration - invalid param: " + e.getMessage()); - throw new InvalidAccessException(InvalidAccessException.INVALID_ID, e.getMessage(), e); - } + /** + * This method migrates the deprecated WorkflowScheduelr configuraiton into the + * new Imixs Scheduler API + */ + public void migrateWorkflowScheduler() { + // lets see if we have an old scheduler configuration.... - if (col.size() == 1) { + ItemCollection configItemCollection = null; + String searchTerm = "(type:\"configuration\" AND txtname:\"org.imixs.workflow.scheduler\")"; + Collection col; + try { + col = documentService.find(searchTerm, 1, 0); + } catch (QueryException e) { + logger.severe("loadConfiguration - invalid param: " + e.getMessage()); + throw new InvalidAccessException(InvalidAccessException.INVALID_ID, e.getMessage(), e); + } - configItemCollection = col.iterator().next(); - ItemCollection scheduler = new ItemCollection(); + if (col.size() == 1) { - // create new scheduler?? - if (schedulerService.loadConfiguration(WorkflowScheduler.NAME) == null) { - logger.info("...migrating deprecated workflow scheduler configuration..."); - scheduler.setItemValue("type", SchedulerService.DOCUMENT_TYPE); - scheduler.setItemValue(Scheduler.ITEM_SCHEDULER_DEFINITION, - configItemCollection.getItemValue("txtConfiguration")); - scheduler.setItemValue(Scheduler.ITEM_SCHEDULER_CLASS, WorkflowScheduler.class.getName()); + configItemCollection = col.iterator().next(); + ItemCollection scheduler = new ItemCollection(); - scheduler.setItemValue(Scheduler.ITEM_SCHEDULER_ENABLED, - configItemCollection.getItemValueBoolean("_enabled")); + // create new scheduler?? + if (schedulerService.loadConfiguration(WorkflowScheduler.NAME) == null) { + logger.info("...migrating deprecated workflow scheduler configuration..."); + scheduler.setItemValue("type", SchedulerService.DOCUMENT_TYPE); + scheduler.setItemValue(Scheduler.ITEM_SCHEDULER_DEFINITION, + configItemCollection.getItemValue("txtConfiguration")); + scheduler.setItemValue(Scheduler.ITEM_SCHEDULER_CLASS, WorkflowScheduler.class.getName()); - scheduler.setItemValue("txtname", WorkflowScheduler.NAME); - schedulerService.saveConfiguration(scheduler); + scheduler.setItemValue(Scheduler.ITEM_SCHEDULER_ENABLED, + configItemCollection.getItemValueBoolean("_enabled")); + scheduler.setItemValue("txtname", WorkflowScheduler.NAME); + schedulerService.saveConfiguration(scheduler); - } - Timer oldTimer = this.findTimer(configItemCollection.getUniqueID()); - if (oldTimer != null) { - logger.info("...stopping deprecated workflow scheduler"); - } - logger.info("...deleting deprecated workflow scheduler"); - documentService.remove(configItemCollection); + } + Timer oldTimer = this.findTimer(configItemCollection.getUniqueID()); + if (oldTimer != null) { + logger.info("...stopping deprecated workflow scheduler"); + } + logger.info("...deleting deprecated workflow scheduler"); + documentService.remove(configItemCollection); + + } } - } - - /** - * This method returns a timer for a corresponding id if such a timer object exists. - * - * @param id - * @return Timer - * @throws Exception - */ - Timer findTimer(String id) { - Timer timer = null; - for (Object obj : timerService.getTimers()) { - Timer atimer = (Timer) obj; - String timerID = atimer.getInfo().toString(); - if (id.equals(timerID)) { - if (timer != null) { - logger.severe("more then one timer with id " + id + " was found!"); + /** + * This method returns a timer for a corresponding id if such a timer object + * exists. + * + * @param id + * @return Timer + * @throws Exception + */ + Timer findTimer(String id) { + Timer timer = null; + for (Object obj : timerService.getTimers()) { + Timer atimer = (Timer) obj; + String timerID = atimer.getInfo().toString(); + if (id.equals(timerID)) { + if (timer != null) { + logger.severe("more then one timer with id " + id + " was found!"); + } + timer = atimer; + } } - timer = atimer; - } + return timer; } - return timer; - } } diff --git a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/SimulationService.java b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/SimulationService.java index 440f6fc29..d057d74f7 100644 --- a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/SimulationService.java +++ b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/SimulationService.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *

    - *  Imixs Workflow 
    +/*  
    + *  Imixs-Workflow 
    + *  
      *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
      *  http://www.imixs.com
      *  
    @@ -22,10 +22,9 @@
      *      https://github.com/imixs/imixs-workflow
      *  
      *  Contributors:  
    - *      Imixs Software Solutions GmbH - initial API and implementation
    + *      Imixs Software Solutions GmbH - Project Management
      *      Ralph Soika - Software Developer
    - * 
    - *******************************************************************************/ + */ package org.imixs.workflow.engine; @@ -52,176 +51,176 @@ import org.imixs.workflow.exceptions.ProcessingErrorException; /** - * The SimulationService can be used to simulate a process life cycle without storing any data into - * the database. + * The SimulationService can be used to simulate a process life cycle without + * storing any data into the database. * * @author rsoika * */ -@DeclareRoles({"org.imixs.ACCESSLEVEL.NOACCESS", "org.imixs.ACCESSLEVEL.READERACCESS", - "org.imixs.ACCESSLEVEL.AUTHORACCESS", "org.imixs.ACCESSLEVEL.EDITORACCESS", - "org.imixs.ACCESSLEVEL.MANAGERACCESS"}) -@RolesAllowed({"org.imixs.ACCESSLEVEL.NOACCESS", "org.imixs.ACCESSLEVEL.READERACCESS", - "org.imixs.ACCESSLEVEL.AUTHORACCESS", "org.imixs.ACCESSLEVEL.EDITORACCESS", - "org.imixs.ACCESSLEVEL.MANAGERACCESS"}) +@DeclareRoles({ "org.imixs.ACCESSLEVEL.NOACCESS", "org.imixs.ACCESSLEVEL.READERACCESS", + "org.imixs.ACCESSLEVEL.AUTHORACCESS", "org.imixs.ACCESSLEVEL.EDITORACCESS", + "org.imixs.ACCESSLEVEL.MANAGERACCESS" }) +@RolesAllowed({ "org.imixs.ACCESSLEVEL.NOACCESS", "org.imixs.ACCESSLEVEL.READERACCESS", + "org.imixs.ACCESSLEVEL.AUTHORACCESS", "org.imixs.ACCESSLEVEL.EDITORACCESS", + "org.imixs.ACCESSLEVEL.MANAGERACCESS" }) @Stateless @LocalBean public class SimulationService implements WorkflowContext { - private static Logger logger = Logger.getLogger(SimulationService.class.getName()); - - @Resource - private SessionContext ctx; - - @Inject - protected Event events; - - @Inject - @Any - private Instance plugins; - - @Inject - private ModelService modelService; - - public ModelService getModelService() { - return modelService; - } - - public void setModelService(ModelService modelService) { - this.modelService = modelService; - } - - public SessionContext getCtx() { - return ctx; - } - - public void setCtx(SessionContext ctx) { - this.ctx = ctx; - } - - - /** - * This method simulates a processing life cycle of a process instance without storing any data - * into the database. - * - * @param workitem - the workItem to be processed - * @return updated version of the processed workItem - * @throws AccessDeniedException - thrown if the user has insufficient access to update the - * workItem - * @throws ProcessingErrorException - thrown if the workitem could not be processed by the - * workflowKernel - * @throws PluginException - thrown if processing by a plugin fails - * @throws ModelException - */ - public ItemCollection processWorkItem(final ItemCollection _workitem, final List vPlugins) - throws AccessDeniedException, ProcessingErrorException, PluginException, ModelException { - - ItemCollection workitem = _workitem; - long l = System.currentTimeMillis(); - - if (workitem == null) - throw new ProcessingErrorException(SimulationService.class.getSimpleName(), - ProcessingErrorException.INVALID_WORKITEM, "WorkflowService: error - workitem is null"); - - // fire event - if (events != null) { - events.fire(new ProcessingEvent(workitem, ProcessingEvent.BEFORE_PROCESS)); - } else { - logger.warning("CDI Support is missing - ProcessingEvent will not be fired"); + private static Logger logger = Logger.getLogger(SimulationService.class.getName()); + + @Resource + private SessionContext ctx; + + @Inject + protected Event events; + + @Inject + @Any + private Instance plugins; + + @Inject + private ModelService modelService; + + public ModelService getModelService() { + return modelService; } - // Fetch the current Profile Entity for this version. - WorkflowKernel workflowkernel = new WorkflowKernel(this); - // register plugins defined in the environment.profile .... - if (vPlugins != null && vPlugins.size() > 0) { - for (int i = 0; i < vPlugins.size(); i++) { - String aPluginClassName = vPlugins.get(i); - - Plugin aPlugin = findPluginByName(aPluginClassName); - // aPlugin=null; - if (aPlugin != null) { - // register injected CDI Plugin - logger.fine("register CDI plugin class: " + aPluginClassName + "..."); - workflowkernel.registerPlugin(aPlugin); - } else { - // register plugin by class name - workflowkernel.registerPlugin(aPluginClassName); - } - } + public void setModelService(ModelService modelService) { + this.modelService = modelService; } - // now process the workitem - try { - workitem = workflowkernel.process(workitem); - } catch (PluginException pe) { - // if a plugin exception occurs we roll back the transaction. - logger.severe("processing workitem '" + workitem.getItemValueString(WorkflowKernel.UNIQUEID) - + " failed, rollback transaction..."); - throw pe; + public SessionContext getCtx() { + return ctx; } - logger.fine("workitem '" + workitem.getItemValueString(WorkflowKernel.UNIQUEID) - + "' simulated in " + (System.currentTimeMillis() - l) + "ms"); - // fire event - if (events != null) { - events.fire(new ProcessingEvent(workitem, ProcessingEvent.AFTER_PROCESS)); + public void setCtx(SessionContext ctx) { + this.ctx = ctx; } - // Now fire also events for all split versions..... - List splitWorkitems = workflowkernel.getSplitWorkitems(); - for (ItemCollection splitWorkitemm : splitWorkitems) { - // fire event - if (events != null) { - events.fire(new ProcessingEvent(splitWorkitemm, ProcessingEvent.AFTER_PROCESS)); - } + + /** + * This method simulates a processing life cycle of a process instance without + * storing any data into the database. + * + * @param workitem - the workItem to be processed + * @return updated version of the processed workItem + * @throws AccessDeniedException - thrown if the user has insufficient access + * to update the workItem + * @throws ProcessingErrorException - thrown if the workitem could not be + * processed by the workflowKernel + * @throws PluginException - thrown if processing by a plugin fails + * @throws ModelException + */ + public ItemCollection processWorkItem(final ItemCollection _workitem, final List vPlugins) + throws AccessDeniedException, ProcessingErrorException, PluginException, ModelException { + + ItemCollection workitem = _workitem; + long l = System.currentTimeMillis(); + + if (workitem == null) + throw new ProcessingErrorException(SimulationService.class.getSimpleName(), + ProcessingErrorException.INVALID_WORKITEM, "WorkflowService: error - workitem is null"); + + // fire event + if (events != null) { + events.fire(new ProcessingEvent(workitem, ProcessingEvent.BEFORE_PROCESS)); + } else { + logger.warning("CDI Support is missing - ProcessingEvent will not be fired"); + } + // Fetch the current Profile Entity for this version. + WorkflowKernel workflowkernel = new WorkflowKernel(this); + // register plugins defined in the environment.profile .... + if (vPlugins != null && vPlugins.size() > 0) { + for (int i = 0; i < vPlugins.size(); i++) { + String aPluginClassName = vPlugins.get(i); + + Plugin aPlugin = findPluginByName(aPluginClassName); + // aPlugin=null; + if (aPlugin != null) { + // register injected CDI Plugin + logger.fine("register CDI plugin class: " + aPluginClassName + "..."); + workflowkernel.registerPlugin(aPlugin); + } else { + // register plugin by class name + workflowkernel.registerPlugin(aPluginClassName); + } + + } + } + + // now process the workitem + try { + workitem = workflowkernel.process(workitem); + } catch (PluginException pe) { + // if a plugin exception occurs we roll back the transaction. + logger.severe("processing workitem '" + workitem.getItemValueString(WorkflowKernel.UNIQUEID) + + " failed, rollback transaction..."); + throw pe; + } + logger.fine("workitem '" + workitem.getItemValueString(WorkflowKernel.UNIQUEID) + "' simulated in " + + (System.currentTimeMillis() - l) + "ms"); + + // fire event + if (events != null) { + events.fire(new ProcessingEvent(workitem, ProcessingEvent.AFTER_PROCESS)); + } + // Now fire also events for all split versions..... + List splitWorkitems = workflowkernel.getSplitWorkitems(); + for (ItemCollection splitWorkitemm : splitWorkitems) { + // fire event + if (events != null) { + events.fire(new ProcessingEvent(splitWorkitemm, ProcessingEvent.AFTER_PROCESS)); + } + } + + return workitem; + } - return workitem; - - } - - /** - * This Method returns the modelManager Instance. The current ModelVersion is automatically - * updated during the Method updateProfileEntity which is called from the processWorktiem method. - * - */ - public ModelManager getModelManager() { - return modelService; - } - - /** - * Returns an instance of the EJB session context. - * - * @return - */ - public SessionContext getSessionContext() { - return ctx; - } - - /** - * This method returns a n injected Plugin by name or null if not plugin with the requested class - * name is injected. - * - * @param pluginClassName - * @return plugin class or null if not found - */ - private Plugin findPluginByName(String pluginClassName) { - if (pluginClassName == null || pluginClassName.isEmpty()) - return null; - - if (plugins == null || !plugins.iterator().hasNext()) { - logger.fine("[WorkflowService] no CDI plugins injected"); - return null; + /** + * This Method returns the modelManager Instance. The current ModelVersion is + * automatically updated during the Method updateProfileEntity which is called + * from the processWorktiem method. + * + */ + public ModelManager getModelManager() { + return modelService; } - // iterate over all injected plugins.... - for (Plugin plugin : this.plugins) { - if (plugin.getClass().getName().equals(pluginClassName)) { - logger.fine("[WorkflowService] CDI plugin '" + pluginClassName + "' successful injected"); - return plugin; - } + + /** + * Returns an instance of the EJB session context. + * + * @return + */ + public SessionContext getSessionContext() { + return ctx; } - return null; - } + /** + * This method returns a n injected Plugin by name or null if not plugin with + * the requested class name is injected. + * + * @param pluginClassName + * @return plugin class or null if not found + */ + private Plugin findPluginByName(String pluginClassName) { + if (pluginClassName == null || pluginClassName.isEmpty()) + return null; + + if (plugins == null || !plugins.iterator().hasNext()) { + logger.fine("[WorkflowService] no CDI plugins injected"); + return null; + } + // iterate over all injected plugins.... + for (Plugin plugin : this.plugins) { + if (plugin.getClass().getName().equals(pluginClassName)) { + logger.fine("[WorkflowService] CDI plugin '" + pluginClassName + "' successful injected"); + return plugin; + } + } + + return null; + } } diff --git a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/TextEvent.java b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/TextEvent.java index a9f6ecb4e..bd0c0abb8 100644 --- a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/TextEvent.java +++ b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/TextEvent.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *
    - *  Imixs Workflow 
    +/*  
    + *  Imixs-Workflow 
    + *  
      *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
      *  http://www.imixs.com
      *  
    @@ -22,10 +22,9 @@
      *      https://github.com/imixs/imixs-workflow
      *  
      *  Contributors:  
    - *      Imixs Software Solutions GmbH - initial API and implementation
    + *      Imixs Software Solutions GmbH - Project Management
      *      Ralph Soika - Software Developer
    - * 
    - *******************************************************************************/ + */ package org.imixs.workflow.engine; @@ -34,9 +33,9 @@ import org.imixs.workflow.ItemCollection; /** - * The TextEvent provides a CDI observer pattern. The TextEvent is fired by the WorkflowService EJB - * to adapt a text fragment. An event observer can adapt the text fragment in a given document - * context. + * The TextEvent provides a CDI observer pattern. The TextEvent is fired by the + * WorkflowService EJB to adapt a text fragment. An event observer can adapt the + * text fragment in a given document context. * * @author Ralph Soika * @version 1.0 @@ -44,44 +43,44 @@ */ public class TextEvent { - private ItemCollection document; - private String text; - private List textList; + private ItemCollection document; + private String text; + private List textList; - public TextEvent(String text, ItemCollection document) { - this.text = text; - this.document = document; - } - - public ItemCollection getDocument() { - return document; - } + public TextEvent(String text, ItemCollection document) { + this.text = text; + this.document = document; + } - public String getText() { - // In case we have a textlist return the first entry - if (text == null && textList != null && textList.size() > 0) { - text = textList.get(0); + public ItemCollection getDocument() { + return document; } - return text; - } - public void setText(String text) { - this.text = text; - } + public String getText() { + // In case we have a textlist return the first entry + if (text == null && textList != null && textList.size() > 0) { + text = textList.get(0); + } + return text; + } - public List getTextList() { - // In case we have no textlist return temp list - if (textList == null && text != null) { - textList = new ArrayList(); - textList.add(text); + public void setText(String text) { + this.text = text; } - return textList; - } + public List getTextList() { + // In case we have no textlist return temp list + if (textList == null && text != null) { + textList = new ArrayList(); + textList.add(text); + } + + return textList; + } - public void setTextList(List textList) { + public void setTextList(List textList) { - this.textList = textList; - } + this.textList = textList; + } } diff --git a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/TextForEachAdapter.java b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/TextForEachAdapter.java index 3d9322bff..16f392e66 100644 --- a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/TextForEachAdapter.java +++ b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/TextForEachAdapter.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *
    - *  Imixs Workflow 
    +/*  
    + *  Imixs-Workflow 
    + *  
      *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
      *  http://www.imixs.com
      *  
    @@ -22,10 +22,9 @@
      *      https://github.com/imixs/imixs-workflow
      *  
      *  Contributors:  
    - *      Imixs Software Solutions GmbH - initial API and implementation
    + *      Imixs Software Solutions GmbH - Project Management
      *      Ralph Soika - Software Developer
    - * 
    - *******************************************************************************/ + */ package org.imixs.workflow.engine; @@ -44,8 +43,9 @@ import org.imixs.workflow.util.XMLParser; /** - * The TextForEachAdapter can be used to format text fragments with the 'for-each' tag. The adapter - * will iterate over the value list of a specified item. + * The TextForEachAdapter can be used to format text fragments with the + * 'for-each' tag. The adapter will iterate over the value list of a specified + * item. * *
      * {@code
    @@ -55,9 +55,10 @@
      * }
      * 
    * - * In this example, the for-each block will be executed for each single value of the item '_partid'. - * Within the for-each block it is possible to access the current value of the iteration as also any - * other values of the current document. The result may look like in the following example: + * In this example, the for-each block will be executed for each single value of + * the item '_partid'. Within the for-each block it is possible to access the + * current value of the iteration as also any other values of the current + * document. The result may look like in the following example: *

    * *

    @@ -67,8 +68,9 @@
      * }
      * 
    *

    - * In case the item contains an embedded list of child ItemCollections the content of the for-each - * block will be processed in the context for each embedded ItemCollection: + * In case the item contains an embedded list of child ItemCollections the + * content of the for-each block will be processed in the context for each + * embedded ItemCollection: * *

      * {@code
    @@ -96,80 +98,81 @@
     @Stateless
     public class TextForEachAdapter {
     
    -  private static Logger logger = Logger.getLogger(AbstractPlugin.class.getName());
    +    private static Logger logger = Logger.getLogger(AbstractPlugin.class.getName());
     
    -  @Inject
    -  protected Event textEvents;
    +    @Inject
    +    protected Event textEvents;
     
    -  /**
    -   * This method reacts on CDI events of the type TextEvent and parses a string for xml tag
    -   * . Those tags will be replaced with the corresponding system property value.
    -   * 

    - * The priority of the CDI event is set to (APPLICATION-10) to ensure that the for-each adapter is - * triggered before the TextItemValueAdapter - * - */ - @SuppressWarnings("unchecked") - public void onEvent(@Observes @Priority(Interceptor.Priority.APPLICATION - 10) TextEvent event) { + /** + * This method reacts on CDI events of the type TextEvent and parses a string + * for xml tag . Those tags will be replaced with the corresponding + * system property value. + *

    + * The priority of the CDI event is set to (APPLICATION-10) to ensure that the + * for-each adapter is triggered before the TextItemValueAdapter + * + */ + @SuppressWarnings("unchecked") + public void onEvent(@Observes @Priority(Interceptor.Priority.APPLICATION - 10) TextEvent event) { - String text = event.getText(); - String textResult = ""; - boolean debug = logger.isLoggable(Level.FINE); + String text = event.getText(); + String textResult = ""; + boolean debug = logger.isLoggable(Level.FINE); - List tagList = XMLParser.findNoEmptyTags(text, "for-each"); - if (debug) { - logger.finest("......" + tagList.size() + " tags found"); - } - // test if a tag exists... - for (String tag : tagList) { - // find the item value list... - String itemName = XMLParser.findAttribute(tag, "item"); - String innervalue = XMLParser.findTagValue(tag, "for-each"); - - // next we iterate over all item values and test for each value if the value is - // a basic value or an embedded ItemCollection. - List values = event.getDocument().getItemValue(itemName); - for (Object _value : values) { - ItemCollection _tempDoc = null; - // test if the value defines an embedded ItemCollection.... - if (_value instanceof Map) { - try { - _tempDoc = new ItemCollection((Map>) _value); - } catch (ClassCastException e) { - // embedded value can not be processed - logger.warning("unable to cast embedded map to ItemCollection!"); - continue; - } - } else { - // We treat the value as a normal object and delegate the processing by firing - // a TextEvent. - // Here we need to create a temporary document for processing.... - _tempDoc = new ItemCollection(event.getDocument()); - // replace the for-each item value with the current iteration! - _tempDoc.setItemValue(itemName, _value); + List tagList = XMLParser.findNoEmptyTags(text, "for-each"); + if (debug) { + logger.finest("......" + tagList.size() + " tags found"); } + // test if a tag exists... + for (String tag : tagList) { + // find the item value list... + String itemName = XMLParser.findAttribute(tag, "item"); + String innervalue = XMLParser.findTagValue(tag, "for-each"); - // now we fire a recursive text event to process the content.... - TextEvent _event = new TextEvent(new String(innervalue), _tempDoc); - if (textEvents != null) { - textEvents.fire(_event); - textResult = textResult + _event.getText(); - } else { - logger.warning("CDI Support is missing - TextEvent wil not be fired"); - // here we apply a workaround for junit tests only.... - TextItemValueAdapter tiva = new TextItemValueAdapter(); - tiva.onEvent(_event); - textResult = textResult + _event.getText(); - } - } + // next we iterate over all item values and test for each value if the value is + // a basic value or an embedded ItemCollection. + List values = event.getDocument().getItemValue(itemName); + for (Object _value : values) { + ItemCollection _tempDoc = null; + // test if the value defines an embedded ItemCollection.... + if (_value instanceof Map) { + try { + _tempDoc = new ItemCollection((Map>) _value); + } catch (ClassCastException e) { + // embedded value can not be processed + logger.warning("unable to cast embedded map to ItemCollection!"); + continue; + } + } else { + // We treat the value as a normal object and delegate the processing by firing + // a TextEvent. + // Here we need to create a temporary document for processing.... + _tempDoc = new ItemCollection(event.getDocument()); + // replace the for-each item value with the current iteration! + _tempDoc.setItemValue(itemName, _value); + } - // now replace the tag with the result string - int iStartPos = text.indexOf(tag); - int iEndPos = text.indexOf(tag) + tag.length(); - // now replace the tag with the result string - text = text.substring(0, iStartPos) + textResult + text.substring(iEndPos); + // now we fire a recursive text event to process the content.... + TextEvent _event = new TextEvent(new String(innervalue), _tempDoc); + if (textEvents != null) { + textEvents.fire(_event); + textResult = textResult + _event.getText(); + } else { + logger.warning("CDI Support is missing - TextEvent wil not be fired"); + // here we apply a workaround for junit tests only.... + TextItemValueAdapter tiva = new TextItemValueAdapter(); + tiva.onEvent(_event); + textResult = textResult + _event.getText(); + } + } + + // now replace the tag with the result string + int iStartPos = text.indexOf(tag); + int iEndPos = text.indexOf(tag) + tag.length(); + // now replace the tag with the result string + text = text.substring(0, iStartPos) + textResult + text.substring(iEndPos); + } + event.setText(text); } - event.setText(text); - } } diff --git a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/TextItemValueAdapter.java b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/TextItemValueAdapter.java index 0526f4f59..1bc960b41 100644 --- a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/TextItemValueAdapter.java +++ b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/TextItemValueAdapter.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *
    - *  Imixs Workflow 
    +/*  
    + *  Imixs-Workflow 
    + *  
      *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
      *  http://www.imixs.com
      *  
    @@ -22,10 +22,9 @@
      *      https://github.com/imixs/imixs-workflow
      *  
      *  Contributors:  
    - *      Imixs Software Solutions GmbH - initial API and implementation
    + *      Imixs Software Solutions GmbH - Project Management
      *      Ralph Soika - Software Developer
    - * 
    - *******************************************************************************/ + */ package org.imixs.workflow.engine; @@ -46,7 +45,8 @@ import org.imixs.workflow.util.XMLParser; /** - * The TextItemValueAdapter replaces text fragments with the values of a named Item. + * The TextItemValueAdapter replaces text fragments with the values of a named + * Item. * * @author rsoika * @@ -54,266 +54,269 @@ @Stateless public class TextItemValueAdapter { - private static Logger logger = Logger.getLogger(AbstractPlugin.class.getName()); - - /** - * This method reacts on CDI events of the type TextEvent and parses a string for xml tag - * [{@code}. Those tags will be replaced with the corresponding item value: - *

    - * {@code hello $Creator} - *

    - * Item values can also be formated. e.g. for date/time values: - *

    - * {@code Last access Time= $created} - *

    - * If the itemValue is a multiValue object the single values can be spearated by a separator: - *

    - * {@code Phone List: txtPhones} - * - * - */ - public void onEvent(@Observes TextEvent event) { - boolean debug = logger.isLoggable(Level.FINE); - String text = event.getText(); - ItemCollection documentContext = event.getDocument(); - - String sFormat = ""; - String sSeparator = " "; - String sPosition = null; - if (text == null) - return; - - // lower case into - if (text.contains("")) { - logger.warning("Deprecated tag should be lowercase !"); - text = text.replace("", ""); - } + private static Logger logger = Logger.getLogger(AbstractPlugin.class.getName()); + + /** + * This method reacts on CDI events of the type TextEvent and parses a string + * for xml tag [{@code}. Those tags will be replaced with the + * corresponding item value: + *

    + * {@code hello $Creator} + *

    + * Item values can also be formated. e.g. for date/time values: + *

    + * {@code Last access Time= $created} + *

    + * If the itemValue is a multiValue object the single values can be spearated by + * a separator: + *

    + * {@code Phone List: txtPhones} + * + * + */ + public void onEvent(@Observes TextEvent event) { + boolean debug = logger.isLoggable(Level.FINE); + String text = event.getText(); + ItemCollection documentContext = event.getDocument(); + + String sFormat = ""; + String sSeparator = " "; + String sPosition = null; + if (text == null) + return; + + // lower case into + if (text.contains("")) { + logger.warning("Deprecated tag should be lowercase !"); + text = text.replace("", ""); + } - List tagList = XMLParser.findTags(text, "itemvalue"); - if (debug) { - logger.finest("......" + tagList.size() + " tags found"); - } - // test if a tag exists... - for (String tag : tagList) { - - // next we check if the start tag contains a 'format' attribute - sFormat = XMLParser.findAttribute(tag, "format"); - - // next we check if the start tag contains a 'separator' attribute - sSeparator = XMLParser.findAttribute(tag, "separator"); - - // next we check if the start tag contains a 'position' attribute - sPosition = XMLParser.findAttribute(tag, "position"); - - // extract locale... - Locale locale = null; - String sLocale = XMLParser.findAttribute(tag, "locale"); - if (sLocale != null && !sLocale.isEmpty()) { - // split locale - StringTokenizer stLocale = new StringTokenizer(sLocale, "_"); - if (stLocale.countTokens() == 1) { - // only language variant - String sLang = stLocale.nextToken(); - String sCount = sLang.toUpperCase(); - locale = new Locale(sLang, sCount); - } else { - // language and country - String sLang = stLocale.nextToken(); - String sCount = stLocale.nextToken(); - locale = new Locale(sLang, sCount); + List tagList = XMLParser.findTags(text, "itemvalue"); + if (debug) { + logger.finest("......" + tagList.size() + " tags found"); } - } + // test if a tag exists... + for (String tag : tagList) { + + // next we check if the start tag contains a 'format' attribute + sFormat = XMLParser.findAttribute(tag, "format"); + + // next we check if the start tag contains a 'separator' attribute + sSeparator = XMLParser.findAttribute(tag, "separator"); + + // next we check if the start tag contains a 'position' attribute + sPosition = XMLParser.findAttribute(tag, "position"); + + // extract locale... + Locale locale = null; + String sLocale = XMLParser.findAttribute(tag, "locale"); + if (sLocale != null && !sLocale.isEmpty()) { + // split locale + StringTokenizer stLocale = new StringTokenizer(sLocale, "_"); + if (stLocale.countTokens() == 1) { + // only language variant + String sLang = stLocale.nextToken(); + String sCount = sLang.toUpperCase(); + locale = new Locale(sLang, sCount); + } else { + // language and country + String sLang = stLocale.nextToken(); + String sCount = stLocale.nextToken(); + locale = new Locale(sLang, sCount); + } + } + + // extract Item Value + String sItemValue = XMLParser.findTagValue(tag, "itemvalue"); + + // format field value + List vValue = documentContext.getItemValue(sItemValue); + + String sResult = formatItemValues(vValue, sSeparator, sFormat, locale, sPosition); + + // now replace the tag with the result string + int iStartPos = text.indexOf(tag); + int iEndPos = text.indexOf(tag) + tag.length(); + + text = text.substring(0, iStartPos) + sResult + text.substring(iEndPos); + } + event.setText(text); - // extract Item Value - String sItemValue = XMLParser.findTagValue(tag, "itemvalue"); + } - // format field value - List vValue = documentContext.getItemValue(sItemValue); + /** + * This method returns a formated a string object. + * + * In case a Separator is provided, multiValues will be separated by the + * provided separator. + * + * If no separator is provide, only the first value will returned. + * + * The format and locale attributes can be used to format number and date + * values. + * + */ + public String formatItemValues(List aItem, String aSeparator, String sFormat, Locale locale, String sPosition) { + + StringBuffer sBuffer = new StringBuffer(); + + if (aItem == null || aItem.size() == 0) + return ""; + + // test if a position was defined? + if (sPosition == null || sPosition.isEmpty()) { + // no - we iterate over all... + for (Object aSingleValue : aItem) { + String aValue = formatObjectValue(aSingleValue, sFormat, locale); + sBuffer.append(aValue); + // append delimiter only if a separator is defined + if (aSeparator != null) { + sBuffer.append(aSeparator); + } else { + // no separator, so we can exit with the first value + break; + } + } + } else { + // evaluate position + if ("last".equalsIgnoreCase(sPosition)) { + sBuffer.append(aItem.get(aItem.size() - 1)); + } else { + // default first poistion + sBuffer.append(aItem.get(0)); + } - String sResult = formatItemValues(vValue, sSeparator, sFormat, locale, sPosition); + } - // now replace the tag with the result string - int iStartPos = text.indexOf(tag); - int iEndPos = text.indexOf(tag) + tag.length(); + String sString = sBuffer.toString(); - text = text.substring(0, iStartPos) + sResult + text.substring(iEndPos); - } - event.setText(text); - - } - - /** - * This method returns a formated a string object. - * - * In case a Separator is provided, multiValues will be separated by the provided separator. - * - * If no separator is provide, only the first value will returned. - * - * The format and locale attributes can be used to format number and date values. - * - */ - public String formatItemValues(List aItem, String aSeparator, String sFormat, Locale locale, - String sPosition) { - - StringBuffer sBuffer = new StringBuffer(); - - if (aItem == null || aItem.size() == 0) - return ""; - - // test if a position was defined? - if (sPosition == null || sPosition.isEmpty()) { - // no - we iterate over all... - for (Object aSingleValue : aItem) { - String aValue = formatObjectValue(aSingleValue, sFormat, locale); - sBuffer.append(aValue); - // append delimiter only if a separator is defined - if (aSeparator != null) { - sBuffer.append(aSeparator); - } else { - // no separator, so we can exit with the first value - break; + // cut last separator + if (aSeparator != null && sString.endsWith(aSeparator)) { + sString = sString.substring(0, sString.lastIndexOf(aSeparator)); } - } - } else { - // evaluate position - if ("last".equalsIgnoreCase(sPosition)) { - sBuffer.append(aItem.get(aItem.size() - 1)); - } else { - // default first poistion - sBuffer.append(aItem.get(0)); - } - } - - String sString = sBuffer.toString(); + return sString; - // cut last separator - if (aSeparator != null && sString.endsWith(aSeparator)) { - sString = sString.substring(0, sString.lastIndexOf(aSeparator)); } - return sString; - - } - - /** - * this method formats a string object depending of an attribute type. MultiValues will be - * separated by the provided separator - */ - public String formatItemValues(List aItem, String aSeparator, String sFormat) { - return formatItemValues(aItem, aSeparator, sFormat, null, null); - } - - /** - * this method formats a string object depending of an attribute type. MultiValues will be - * separated by the provided separator - */ - public String formatItemValues(List aItem, String aSeparator, String sFormat, Locale alocale) { - return formatItemValues(aItem, aSeparator, sFormat, alocale, null); - } - - /** - * This method converts a double value into a custom number format including an optional locale. - * - *

    -   * {@code
    -   * 
    -   * "###,###.###", "en_UK", 123456.789
    -   * 
    -   * "EUR #,###,##0.00", "de_DE", 1456.781
    -   * 
    -   * }
    -   * 
    - * - * @param pattern - * @param value - * @return - */ - private String customNumberFormat(String pattern, Locale _locale, double value) { - DecimalFormat formatter = null; - - // test if we have a locale - if (_locale != null) { - formatter = (DecimalFormat) DecimalFormat.getInstance(_locale); - } else { - formatter = (DecimalFormat) DecimalFormat.getInstance(); + /** + * this method formats a string object depending of an attribute type. + * MultiValues will be separated by the provided separator + */ + public String formatItemValues(List aItem, String aSeparator, String sFormat) { + return formatItemValues(aItem, aSeparator, sFormat, null, null); } - formatter.applyPattern(pattern); - String output = formatter.format(value); - - return output; - } - - /** - * This helper method test the type of an object provided by a itemcollection and formats the - * object into a string value. - * - * Only Date Objects will be formated into a modified representation. other objects will be - * returned using the toString() method. - * - * If an optional format is provided this will be used to format date objects. - * - * @param o - * @return - */ - private String formatObjectValue(Object o, String format, Locale locale) { - String singleValue = ""; - Date dateValue = null; - - // now test the objct type to date - if (o instanceof Date) { - dateValue = (Date) o; + + /** + * this method formats a string object depending of an attribute type. + * MultiValues will be separated by the provided separator + */ + public String formatItemValues(List aItem, String aSeparator, String sFormat, Locale alocale) { + return formatItemValues(aItem, aSeparator, sFormat, alocale, null); } - if (o instanceof Calendar) { - Calendar cal = (Calendar) o; - dateValue = cal.getTime(); + /** + * This method converts a double value into a custom number format including an + * optional locale. + * + *
    +     * {@code
    +     * 
    +     * "###,###.###", "en_UK", 123456.789
    +     * 
    +     * "EUR #,###,##0.00", "de_DE", 1456.781
    +     * 
    +     * }
    +     * 
    + * + * @param pattern + * @param value + * @return + */ + private String customNumberFormat(String pattern, Locale _locale, double value) { + DecimalFormat formatter = null; + + // test if we have a locale + if (_locale != null) { + formatter = (DecimalFormat) DecimalFormat.getInstance(_locale); + } else { + formatter = (DecimalFormat) DecimalFormat.getInstance(); + } + formatter.applyPattern(pattern); + String output = formatter.format(value); + + return output; } - // format date string? - if (dateValue != null) { - if (format != null && !"".equals(format)) { - // format date with provided formater - try { - SimpleDateFormat formatter = null; - if (locale != null) { - formatter = new SimpleDateFormat(format, locale); - } else { - formatter = new SimpleDateFormat(format); - } - singleValue = formatter.format(dateValue); - } catch (Exception ef) { - Logger logger = Logger.getLogger(AbstractPlugin.class.getName()); - logger.warning("ReportService: Invalid format String '" + format + "'"); - logger.warning("ReportService: Can not format value - error: " + ef.getMessage()); - return "" + dateValue; + /** + * This helper method test the type of an object provided by a itemcollection + * and formats the object into a string value. + * + * Only Date Objects will be formated into a modified representation. other + * objects will be returned using the toString() method. + * + * If an optional format is provided this will be used to format date objects. + * + * @param o + * @return + */ + private String formatObjectValue(Object o, String format, Locale locale) { + String singleValue = ""; + Date dateValue = null; + + // now test the objct type to date + if (o instanceof Date) { + dateValue = (Date) o; } - } else { - // use standard formate short/short - singleValue = - DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT).format(dateValue); - } - - } else { - // test if number formater is provided.... - if (format != null && format.contains("#")) { - try { - double d = Double.parseDouble(o.toString()); - singleValue = customNumberFormat(format, locale, d); - } catch (IllegalArgumentException e) { - logger.warning("Format Error (" + format + ") = " + e.getMessage()); - singleValue = "0"; + + if (o instanceof Calendar) { + Calendar cal = (Calendar) o; + dateValue = cal.getTime(); } - } else { - // return object as string - singleValue = o.toString(); - } - } + // format date string? + if (dateValue != null) { + if (format != null && !"".equals(format)) { + // format date with provided formater + try { + SimpleDateFormat formatter = null; + if (locale != null) { + formatter = new SimpleDateFormat(format, locale); + } else { + formatter = new SimpleDateFormat(format); + } + singleValue = formatter.format(dateValue); + } catch (Exception ef) { + Logger logger = Logger.getLogger(AbstractPlugin.class.getName()); + logger.warning("ReportService: Invalid format String '" + format + "'"); + logger.warning("ReportService: Can not format value - error: " + ef.getMessage()); + return "" + dateValue; + } + } else { + // use standard formate short/short + singleValue = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT).format(dateValue); + } + + } else { + // test if number formater is provided.... + if (format != null && format.contains("#")) { + try { + double d = Double.parseDouble(o.toString()); + singleValue = customNumberFormat(format, locale, d); + } catch (IllegalArgumentException e) { + logger.warning("Format Error (" + format + ") = " + e.getMessage()); + singleValue = "0"; + } + + } else { + // return object as string + singleValue = o.toString(); + } + } - return singleValue; + return singleValue; - } + } } diff --git a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/TextPropertyValueAdapter.java b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/TextPropertyValueAdapter.java index a555dc2a8..11cc66453 100644 --- a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/TextPropertyValueAdapter.java +++ b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/TextPropertyValueAdapter.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *
    - *  Imixs Workflow 
    +/*  
    + *  Imixs-Workflow 
    + *  
      *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
      *  http://www.imixs.com
      *  
    @@ -22,10 +22,9 @@
      *      https://github.com/imixs/imixs-workflow
      *  
      *  Contributors:  
    - *      Imixs Software Solutions GmbH - initial API and implementation
    + *      Imixs Software Solutions GmbH - Project Management
      *      Ralph Soika - Software Developer
    - * 
    - *******************************************************************************/ + */ package org.imixs.workflow.engine; @@ -45,7 +44,8 @@ import org.imixs.workflow.util.XMLParser; /** - * The TextPropertyValueAdapter replaces text fragments with named system property values. + * The TextPropertyValueAdapter replaces text fragments with named system + * property values. * * @author rsoika * @@ -53,168 +53,169 @@ @Stateless public class TextPropertyValueAdapter { - @Inject - private Config config; - - private static Logger logger = Logger.getLogger(AbstractPlugin.class.getName()); - - /** - * This method reacts on CDI events of the type TextEvent and parses a string for xml tag - * . Those tags will be replaced with the corresponding system property value. - * - * - */ - public void onEvent(@Observes TextEvent event) { - String text = event.getText(); - boolean debug = logger.isLoggable(Level.FINE); - // lower case into - if (text.contains("")) { - logger.warning("Deprecated tag should be lowercase !"); - text = text.replace("", ""); - } + @Inject + private Config config; + + private static Logger logger = Logger.getLogger(AbstractPlugin.class.getName()); + + /** + * This method reacts on CDI events of the type TextEvent and parses a string + * for xml tag . Those tags will be replaced with the + * corresponding system property value. + * + * + */ + public void onEvent(@Observes TextEvent event) { + String text = event.getText(); + boolean debug = logger.isLoggable(Level.FINE); + // lower case into + if (text.contains("")) { + logger.warning("Deprecated tag should be lowercase !"); + text = text.replace("", ""); + } - List tagList = XMLParser.findTags(text, "propertyvalue"); - if (debug) { - logger.finest("......" + tagList.size() + " tags found"); - } - // test if a tag exists... - for (String tag : tagList) { + List tagList = XMLParser.findTags(text, "propertyvalue"); + if (debug) { + logger.finest("......" + tagList.size() + " tags found"); + } + // test if a tag exists... + for (String tag : tagList) { + + // now we have the start and end position of a tag and also the + // start and end pos of the value - // now we have the start and end position of a tag and also the - // start and end pos of the value + // read the property Value + String sPropertyKey = XMLParser.findTagValue(tag, "propertyvalue"); - // read the property Value - String sPropertyKey = XMLParser.findTagValue(tag, "propertyvalue"); + String vValue = ""; + try { + vValue = config.getValue(sPropertyKey, String.class); + } catch (java.util.NoSuchElementException e) { + logger.warning("propertyvalue '" + sPropertyKey + "' is not defined in imixs.properties!"); + vValue = ""; + } - String vValue = ""; - try { - vValue = config.getValue(sPropertyKey, String.class); - } catch (java.util.NoSuchElementException e) { - logger.warning("propertyvalue '" + sPropertyKey + "' is not defined in imixs.properties!"); - vValue = ""; - } + // now replace the tag with the result string + int iStartPos = text.indexOf(tag); + int iEndPos = text.indexOf(tag) + tag.length(); + + // now replace the tag with the result string + text = text.substring(0, iStartPos) + vValue + text.substring(iEndPos); + } - // now replace the tag with the result string - int iStartPos = text.indexOf(tag); - int iEndPos = text.indexOf(tag) + tag.length(); + event.setText(text); - // now replace the tag with the result string - text = text.substring(0, iStartPos) + vValue + text.substring(iEndPos); } - event.setText(text); - - } - - /** - * This method returns a formated a string object. - * - * In case a Separator is provided, multiValues will be separated by the provided separator. - * - * If no separator is provide, only the first value will returned. - * - * The format and locale attributes can be used to format number and date values. - * - */ - public String formatItemValues(List aItem, String aSeparator, String sFormat, Locale locale, - String sPosition) { - - StringBuffer sBuffer = new StringBuffer(); - - if (aItem == null || aItem.size() == 0) - return ""; - - // test if a position was defined? - if (sPosition == null || sPosition.isEmpty()) { - // no - we iterate over all... - for (Object aSingleValue : aItem) { - String aValue = formatObjectValue(aSingleValue, sFormat, locale); - sBuffer.append(aValue); - // append delimiter only if a separator is defined - if (aSeparator != null) { - sBuffer.append(aSeparator); + /** + * This method returns a formated a string object. + * + * In case a Separator is provided, multiValues will be separated by the + * provided separator. + * + * If no separator is provide, only the first value will returned. + * + * The format and locale attributes can be used to format number and date + * values. + * + */ + public String formatItemValues(List aItem, String aSeparator, String sFormat, Locale locale, String sPosition) { + + StringBuffer sBuffer = new StringBuffer(); + + if (aItem == null || aItem.size() == 0) + return ""; + + // test if a position was defined? + if (sPosition == null || sPosition.isEmpty()) { + // no - we iterate over all... + for (Object aSingleValue : aItem) { + String aValue = formatObjectValue(aSingleValue, sFormat, locale); + sBuffer.append(aValue); + // append delimiter only if a separator is defined + if (aSeparator != null) { + sBuffer.append(aSeparator); + } else { + // no separator, so we can exit with the first value + break; + } + } } else { - // no separator, so we can exit with the first value - break; + // evaluate position + if ("last".equalsIgnoreCase(sPosition)) { + sBuffer.append(aItem.get(aItem.size() - 1)); + } else { + // default first poistion + sBuffer.append(aItem.get(0)); + } + } - } - } else { - // evaluate position - if ("last".equalsIgnoreCase(sPosition)) { - sBuffer.append(aItem.get(aItem.size() - 1)); - } else { - // default first poistion - sBuffer.append(aItem.get(0)); - } - } + String sString = sBuffer.toString(); - String sString = sBuffer.toString(); + // cut last separator + if (aSeparator != null && sString.endsWith(aSeparator)) { + sString = sString.substring(0, sString.lastIndexOf(aSeparator)); + } - // cut last separator - if (aSeparator != null && sString.endsWith(aSeparator)) { - sString = sString.substring(0, sString.lastIndexOf(aSeparator)); - } + return sString; - return sString; - - } - - /** - * This helper method test the type of an object provided by a itemcollection and formats the - * object into a string value. - * - * Only Date Objects will be formated into a modified representation. other objects will be - * returned using the toString() method. - * - * If an optional format is provided this will be used to format date objects. - * - * @param o - * @return - */ - private static String formatObjectValue(Object o, String format, Locale locale) { - - Date dateValue = null; - - // now test the objct type to date - if (o instanceof Date) { - dateValue = (Date) o; } - if (o instanceof Calendar) { - Calendar cal = (Calendar) o; - dateValue = cal.getTime(); - } + /** + * This helper method test the type of an object provided by a itemcollection + * and formats the object into a string value. + * + * Only Date Objects will be formated into a modified representation. other + * objects will be returned using the toString() method. + * + * If an optional format is provided this will be used to format date objects. + * + * @param o + * @return + */ + private static String formatObjectValue(Object o, String format, Locale locale) { + + Date dateValue = null; + + // now test the objct type to date + if (o instanceof Date) { + dateValue = (Date) o; + } - // format date string? - if (dateValue != null) { - String singleValue = ""; - if (format != null && !"".equals(format)) { - // format date with provided formater - try { - SimpleDateFormat formatter = null; - if (locale != null) { - formatter = new SimpleDateFormat(format, locale); - } else { - formatter = new SimpleDateFormat(format); - } - singleValue = formatter.format(dateValue); - } catch (Exception ef) { - Logger logger = Logger.getLogger(AbstractPlugin.class.getName()); - logger.warning("AbstractPlugin: Invalid format String '" + format + "'"); - logger.warning("AbstractPlugin: Can not format value - error: " + ef.getMessage()); - return "" + dateValue; + if (o instanceof Calendar) { + Calendar cal = (Calendar) o; + dateValue = cal.getTime(); } - } else - // use standard formate short/short - singleValue = - DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT).format(dateValue); - return singleValue; - } + // format date string? + if (dateValue != null) { + String singleValue = ""; + if (format != null && !"".equals(format)) { + // format date with provided formater + try { + SimpleDateFormat formatter = null; + if (locale != null) { + formatter = new SimpleDateFormat(format, locale); + } else { + formatter = new SimpleDateFormat(format); + } + singleValue = formatter.format(dateValue); + } catch (Exception ef) { + Logger logger = Logger.getLogger(AbstractPlugin.class.getName()); + logger.warning("AbstractPlugin: Invalid format String '" + format + "'"); + logger.warning("AbstractPlugin: Can not format value - error: " + ef.getMessage()); + return "" + dateValue; + } + } else + // use standard formate short/short + singleValue = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT).format(dateValue); + + return singleValue; + } - return o.toString(); - } + return o.toString(); + } } diff --git a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/UserGroupEvent.java b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/UserGroupEvent.java index 85fc10b91..1c1ff9f4c 100644 --- a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/UserGroupEvent.java +++ b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/UserGroupEvent.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *
    - *  Imixs Workflow 
    +/*  
    + *  Imixs-Workflow 
    + *  
      *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
      *  http://www.imixs.com
      *  
    @@ -22,19 +22,18 @@
      *      https://github.com/imixs/imixs-workflow
      *  
      *  Contributors:  
    - *      Imixs Software Solutions GmbH - initial API and implementation
    + *      Imixs Software Solutions GmbH - Project Management
      *      Ralph Soika - Software Developer
    - * 
    - *******************************************************************************/ + */ package org.imixs.workflow.engine; import java.util.List; /** - * The UserGroupEvent provides a CDI observer pattern. The UserGroupEvent is fired by the - * DocumentService EJB. An event Observer can react on this event to extend the current user group - * list. + * The UserGroupEvent provides a CDI observer pattern. The UserGroupEvent is + * fired by the DocumentService EJB. An event Observer can react on this event + * to extend the current user group list. * * * @author Ralph Soika @@ -43,28 +42,28 @@ */ public class UserGroupEvent { - private String userId; - private List groups; + private String userId; + private List groups; - public UserGroupEvent(String userId) { - super(); - this.userId = userId; - } + public UserGroupEvent(String userId) { + super(); + this.userId = userId; + } - public String getUserId() { - return userId; - } + public String getUserId() { + return userId; + } - public void setUserId(String userId) { - this.userId = userId; - } + public void setUserId(String userId) { + this.userId = userId; + } - public void setGroups(List groups) { - this.groups = groups; - } + public void setGroups(List groups) { + this.groups = groups; + } - public List getGroups() { - return groups; - } + public List getGroups() { + return groups; + } } diff --git a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/WorkflowScheduler.java b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/WorkflowScheduler.java index 7450760e4..f840a3ae9 100644 --- a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/WorkflowScheduler.java +++ b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/WorkflowScheduler.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *
    - *  Imixs Workflow 
    +/*  
    + *  Imixs-Workflow 
    + *  
      *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
      *  http://www.imixs.com
      *  
    @@ -22,10 +22,9 @@
      *      https://github.com/imixs/imixs-workflow
      *  
      *  Contributors:  
    - *      Imixs Software Solutions GmbH - initial API and implementation
    + *      Imixs Software Solutions GmbH - Project Management
      *      Ralph Soika - Software Developer
    - * 
    - *******************************************************************************/ + */ package org.imixs.workflow.engine; @@ -50,7 +49,8 @@ import org.imixs.workflow.exceptions.ModelException; /** - * This EJB implements a Imixs Scheduler Interface and scans workitems for scheduled activities. + * This EJB implements a Imixs Scheduler Interface and scans workitems for + * scheduled activities. *

    * The configuration of the scheduler is based on the Imixs Scheduler API. * @@ -59,493 +59,493 @@ */ public class WorkflowScheduler implements Scheduler { - final static public String NAME = "org.imixs.workflow.scheduler"; - - final static public int OFFSET_SECONDS = 0; - final static public int OFFSET_MINUTES = 1; - final static public int OFFSET_HOURS = 2; - final static public int OFFSET_DAYS = 3; - final static public int OFFSET_WORKDAYS = 4; - - private static Logger logger = Logger.getLogger(WorkflowScheduler.class.getName()); - - @Inject - private WorkflowService workflowService; - - @Inject - private DocumentService documentService; - - @Inject - private ModelService modelService; - - @Inject - private SchedulerService schedulerService; - - @Resource - private SessionContext ctx; - - private int iProcessWorkItems = 0; - private List unprocessedIDs = null; - - /** - * This method checks if a workitem (doc) is in due. There are 4 different cases which will be - * compared: The case is determined by the keyScheduledBaseObject of the activity entity - * - * Basis : keyScheduledBaseObject "last process"=1, "last Modification"=2 "Creation"=3 "Field"=4 - * - * The logic is not the best one but it works. So we are open for any kind of improvements - * - * @return true if workitem is is due - */ - public boolean workItemInDue(ItemCollection doc, ItemCollection docActivity) { - try { - int iCompareType = -1; - int iOffsetUnit = -1; - int iOffset = 0; - Date dateTimeCompare = null; - String suniqueid = doc.getItemValueString("$uniqueid"); - String sDelayUnit = docActivity.getItemValueString("keyActivityDelayUnit"); - - try { - iOffsetUnit = Integer.parseInt(sDelayUnit); // 1= min, 2= hours, - // 3=day, 4=workdays - - if (iOffsetUnit < 1 || iOffsetUnit > 4) { - logger.warning("error parsing delay in ActivityEntity " - + docActivity.getItemValueInteger("numProcessID") + "." - + docActivity.getItemValueInteger("numActivityID") - + " : unsuported keyActivityDelayUnit=" + sDelayUnit); - return false; - } + final static public String NAME = "org.imixs.workflow.scheduler"; - } catch (NumberFormatException nfe) { - logger.warning("error parsing delay in ActivityEntity " - + docActivity.getItemValueInteger("numProcessID") + "." - + docActivity.getItemValueInteger("numActivityID") + " :" + nfe.getMessage()); - return false; - } - // get activityDelay from Event - iOffset = docActivity.getItemValueInteger("numActivityDelay"); - - if ("1".equals(sDelayUnit)) - sDelayUnit = "minutes"; - if ("2".equals(sDelayUnit)) - sDelayUnit = "hours"; - if ("3".equals(sDelayUnit)) - sDelayUnit = "days"; - if ("4".equals(sDelayUnit)) - sDelayUnit = "workdays"; - - logger.finest("......" + suniqueid + " offset =" + iOffset + " " + sDelayUnit); - - iCompareType = docActivity.getItemValueInteger("keyScheduledBaseObject"); - - // get current time for compare.... - Date dateTimeNow = Calendar.getInstance().getTime(); - - switch (iCompareType) { - // last process - - case 1: { - logger.finest("......" + suniqueid + ": CompareType = last event"); - - // support deprecated fields $lastProcessingDate and timWorkflowLastAccess - if (!doc.hasItem("$lastEventDate")) { - logger.info("migrating $lasteventdate..."); - if (doc.hasItem("$lastProcessingDate")) { - doc.replaceItemValue("$lastEventDate", doc.getItemValue("$lastProcessingDate")); - } else { - doc.replaceItemValue("$lastEventDate", doc.getItemValue("timWorkflowLastAccess")); + final static public int OFFSET_SECONDS = 0; + final static public int OFFSET_MINUTES = 1; + final static public int OFFSET_HOURS = 2; + final static public int OFFSET_DAYS = 3; + final static public int OFFSET_WORKDAYS = 4; + + private static Logger logger = Logger.getLogger(WorkflowScheduler.class.getName()); + + @Inject + private WorkflowService workflowService; + + @Inject + private DocumentService documentService; + + @Inject + private ModelService modelService; + + @Inject + private SchedulerService schedulerService; + + @Resource + private SessionContext ctx; + + private int iProcessWorkItems = 0; + private List unprocessedIDs = null; + + /** + * This method checks if a workitem (doc) is in due. There are 4 different cases + * which will be compared: The case is determined by the keyScheduledBaseObject + * of the activity entity + * + * Basis : keyScheduledBaseObject "last process"=1, "last Modification"=2 + * "Creation"=3 "Field"=4 + * + * The logic is not the best one but it works. So we are open for any kind of + * improvements + * + * @return true if workitem is is due + */ + public boolean workItemInDue(ItemCollection doc, ItemCollection docActivity) { + try { + int iCompareType = -1; + int iOffsetUnit = -1; + int iOffset = 0; + Date dateTimeCompare = null; + String suniqueid = doc.getItemValueString("$uniqueid"); + String sDelayUnit = docActivity.getItemValueString("keyActivityDelayUnit"); + + try { + iOffsetUnit = Integer.parseInt(sDelayUnit); // 1= min, 2= hours, + // 3=day, 4=workdays + + if (iOffsetUnit < 1 || iOffsetUnit > 4) { + logger.warning( + "error parsing delay in ActivityEntity " + docActivity.getItemValueInteger("numProcessID") + + "." + docActivity.getItemValueInteger("numActivityID") + + " : unsuported keyActivityDelayUnit=" + sDelayUnit); + return false; + } + + } catch (NumberFormatException nfe) { + logger.warning( + "error parsing delay in ActivityEntity " + docActivity.getItemValueInteger("numProcessID") + "." + + docActivity.getItemValueInteger("numActivityID") + " :" + nfe.getMessage()); + return false; + } + // get activityDelay from Event + iOffset = docActivity.getItemValueInteger("numActivityDelay"); + + if ("1".equals(sDelayUnit)) + sDelayUnit = "minutes"; + if ("2".equals(sDelayUnit)) + sDelayUnit = "hours"; + if ("3".equals(sDelayUnit)) + sDelayUnit = "days"; + if ("4".equals(sDelayUnit)) + sDelayUnit = "workdays"; + + logger.finest("......" + suniqueid + " offset =" + iOffset + " " + sDelayUnit); + + iCompareType = docActivity.getItemValueInteger("keyScheduledBaseObject"); + + // get current time for compare.... + Date dateTimeNow = Calendar.getInstance().getTime(); + + switch (iCompareType) { + // last process - + case 1: { + logger.finest("......" + suniqueid + ": CompareType = last event"); + + // support deprecated fields $lastProcessingDate and timWorkflowLastAccess + if (!doc.hasItem("$lastEventDate")) { + logger.info("migrating $lasteventdate..."); + if (doc.hasItem("$lastProcessingDate")) { + doc.replaceItemValue("$lastEventDate", doc.getItemValue("$lastProcessingDate")); + } else { + doc.replaceItemValue("$lastEventDate", doc.getItemValue("timWorkflowLastAccess")); + } + } + dateTimeCompare = doc.getItemValueDate("$lastEventDate"); + if (dateTimeCompare == null) { + logger.warning(suniqueid + ": item '$lastEventDate' is missing!"); + return false; + } + + // compute scheduled time + logger.finest("......" + suniqueid + ": $lastEventDate=" + dateTimeCompare); + dateTimeCompare = adjustBaseDate(dateTimeCompare, iOffsetUnit, iOffset); + if (dateTimeCompare != null) + return dateTimeCompare.before(dateTimeNow); + else + return false; } - } - dateTimeCompare = doc.getItemValueDate("$lastEventDate"); - if (dateTimeCompare == null) { - logger.warning(suniqueid + ": item '$lastEventDate' is missing!"); - return false; - } - - // compute scheduled time - logger.finest("......" + suniqueid + ": $lastEventDate=" + dateTimeCompare); - dateTimeCompare = adjustBaseDate(dateTimeCompare, iOffsetUnit, iOffset); - if (dateTimeCompare != null) - return dateTimeCompare.before(dateTimeNow); - else - return false; - } - // last modification - case 2: { - logger.finest("......" + suniqueid + ": CompareType = last modify"); + // last modification + case 2: { + logger.finest("......" + suniqueid + ": CompareType = last modify"); - dateTimeCompare = doc.getItemValueDate("$modified"); + dateTimeCompare = doc.getItemValueDate("$modified"); - logger.finest("......" + suniqueid + ": modified=" + dateTimeCompare); + logger.finest("......" + suniqueid + ": modified=" + dateTimeCompare); - dateTimeCompare = adjustBaseDate(dateTimeCompare, iOffsetUnit, iOffset); + dateTimeCompare = adjustBaseDate(dateTimeCompare, iOffsetUnit, iOffset); - if (dateTimeCompare != null) - return dateTimeCompare.before(dateTimeNow); - else - return false; - } + if (dateTimeCompare != null) + return dateTimeCompare.before(dateTimeNow); + else + return false; + } - // creation - case 3: { - logger.finest("......" + suniqueid + ": CompareType = creation"); + // creation + case 3: { + logger.finest("......" + suniqueid + ": CompareType = creation"); - dateTimeCompare = doc.getItemValueDate("$created"); - logger.finest("......" + suniqueid + ": doc.getCreated() =" + dateTimeCompare); + dateTimeCompare = doc.getItemValueDate("$created"); + logger.finest("......" + suniqueid + ": doc.getCreated() =" + dateTimeCompare); - // Nein -> Creation date ist masstab - dateTimeCompare = adjustBaseDate(dateTimeCompare, iOffsetUnit, iOffset); + // Nein -> Creation date ist masstab + dateTimeCompare = adjustBaseDate(dateTimeCompare, iOffsetUnit, iOffset); - if (dateTimeCompare != null) - return dateTimeCompare.before(dateTimeNow); - else - return false; - } + if (dateTimeCompare != null) + return dateTimeCompare.before(dateTimeNow); + else + return false; + } - // field - case 4: { - String sNameOfField = docActivity.getItemValueString("keyTimeCompareField"); - logger.finest("......" + suniqueid + ": CompareType = field: '" + sNameOfField + "'"); + // field + case 4: { + String sNameOfField = docActivity.getItemValueString("keyTimeCompareField"); + logger.finest("......" + suniqueid + ": CompareType = field: '" + sNameOfField + "'"); - if (!doc.hasItem(sNameOfField)) { - logger.finest( - "......" + suniqueid + ": CompareType =" + sNameOfField + " no value found!"); - return false; - } + if (!doc.hasItem(sNameOfField)) { + logger.finest("......" + suniqueid + ": CompareType =" + sNameOfField + " no value found!"); + return false; + } - dateTimeCompare = doc.getItemValueDate(sNameOfField); + dateTimeCompare = doc.getItemValueDate(sNameOfField); - logger.finest("......" + suniqueid + ": " + sNameOfField + "=" + dateTimeCompare); + logger.finest("......" + suniqueid + ": " + sNameOfField + "=" + dateTimeCompare); - dateTimeCompare = adjustBaseDate(dateTimeCompare, iOffsetUnit, iOffset); - if (dateTimeCompare != null) { - logger.finest( - "......" + suniqueid + ": Compare " + dateTimeCompare + " <-> " + dateTimeNow); + dateTimeCompare = adjustBaseDate(dateTimeCompare, iOffsetUnit, iOffset); + if (dateTimeCompare != null) { + logger.finest("......" + suniqueid + ": Compare " + dateTimeCompare + " <-> " + dateTimeNow); - if (dateTimeCompare.before(dateTimeNow)) { - logger.finest("......" + suniqueid + " isInDue!"); + if (dateTimeCompare.before(dateTimeNow)) { + logger.finest("......" + suniqueid + " isInDue!"); + } + return dateTimeCompare.before(dateTimeNow); + } else + return false; + } + default: { + logger.warning("Time Base is not defined, verify model!"); + return false; } - return dateTimeCompare.before(dateTimeNow); - } else + } + + } catch (Exception e) { + + e.printStackTrace(); return false; } - default: { - logger.warning("Time Base is not defined, verify model!"); - return false; + + } + + /** + * This method adds workdays (MONDAY - FRIDAY) to a given calendar object. If + * the number of days is negative than this method subtracts the working days + * from the calendar object. + * + * + * @param cal + * @param days + * @return new calendar instance + */ + public Calendar addWorkDays(final Calendar baseDate, final int days) { + Calendar resultDate = null; + Calendar workCal = Calendar.getInstance(); + workCal.setTime(baseDate.getTime()); + + int currentWorkDay = workCal.get(Calendar.DAY_OF_WEEK); + + // test if SATURDAY ? + if (currentWorkDay == Calendar.SATURDAY) { + // move to next FRIDAY + workCal.add(Calendar.DAY_OF_MONTH, (days < 0 ? -1 : +2)); + currentWorkDay = workCal.get(Calendar.DAY_OF_WEEK); + } + // test if SUNDAY ? + if (currentWorkDay == Calendar.SUNDAY) { + // move to next FRIDAY + workCal.add(Calendar.DAY_OF_MONTH, (days < 0 ? -2 : +1)); + currentWorkDay = workCal.get(Calendar.DAY_OF_WEEK); } - } - } catch (Exception e) { + // test if we are in a working week (should be so!) + if (currentWorkDay >= Calendar.MONDAY && currentWorkDay <= Calendar.FRIDAY) { + boolean inCurrentWeek = false; + if (days > 0) + inCurrentWeek = (currentWorkDay + days < 7); + else + inCurrentWeek = (currentWorkDay + days > 1); + + if (inCurrentWeek) { + workCal.add(Calendar.DAY_OF_MONTH, days); + resultDate = workCal; + } else { + int totalDays = 0; + int daysInCurrentWeek = 0; + + // fill up current week. + if (days > 0) { + daysInCurrentWeek = Calendar.SATURDAY - currentWorkDay; + totalDays = daysInCurrentWeek + 2; + } else { + daysInCurrentWeek = -(currentWorkDay - Calendar.SUNDAY); + totalDays = daysInCurrentWeek - 2; + } + + int restTotalDays = days - daysInCurrentWeek; + // next working week... add 2 days for each week. + int x = restTotalDays / 5; + totalDays += restTotalDays + (x * 2); + + workCal.add(Calendar.DAY_OF_MONTH, totalDays); + resultDate = workCal; - e.printStackTrace(); - return false; + } + } + if (resultDate != null) { + logger.finest( + "......addWorkDays (" + baseDate.getTime() + ") + " + days + " = (" + resultDate.getTime() + ")"); + } + return resultDate; } - } - - /** - * This method adds workdays (MONDAY - FRIDAY) to a given calendar object. If the number of days - * is negative than this method subtracts the working days from the calendar object. - * - * - * @param cal - * @param days - * @return new calendar instance - */ - public Calendar addWorkDays(final Calendar baseDate, final int days) { - Calendar resultDate = null; - Calendar workCal = Calendar.getInstance(); - workCal.setTime(baseDate.getTime()); - - int currentWorkDay = workCal.get(Calendar.DAY_OF_WEEK); - - // test if SATURDAY ? - if (currentWorkDay == Calendar.SATURDAY) { - // move to next FRIDAY - workCal.add(Calendar.DAY_OF_MONTH, (days < 0 ? -1 : +2)); - currentWorkDay = workCal.get(Calendar.DAY_OF_WEEK); - } - // test if SUNDAY ? - if (currentWorkDay == Calendar.SUNDAY) { - // move to next FRIDAY - workCal.add(Calendar.DAY_OF_MONTH, (days < 0 ? -2 : +1)); - currentWorkDay = workCal.get(Calendar.DAY_OF_WEEK); - } + /** + * This method process scheduled workitems. The method updates the property + * 'datLastRun' + * + * Because of bug: https://java.net/jira/browse/GLASSFISH-20673 we check the + * imixsDayOfWeek + * + * @param timer + * @throws AccessDeniedException + */ + @Override + public ItemCollection run(ItemCollection configItemCollection) throws SchedulerException { + + /* + * Now we process all scheduled worktitems for each model + */ + iProcessWorkItems = 0; + unprocessedIDs = new ArrayList(); + try { + // get all model versions... + List modelVersions = modelService.getVersions(); + + // sort versions in descending order (issue #482) + Collections.sort(modelVersions, Collections.reverseOrder()); + + for (String version : modelVersions) { + // find scheduled Activities + Collection scheduledEvents = findScheduledEvents(version); + schedulerService.logMessage(version + " (" + scheduledEvents.size() + " scheduled events)", + configItemCollection, null); + // process all workitems for coresponding activities + for (ItemCollection aactivityEntity : scheduledEvents) { + processWorkListByEvent(aactivityEntity, configItemCollection); + } + } - // test if we are in a working week (should be so!) - if (currentWorkDay >= Calendar.MONDAY && currentWorkDay <= Calendar.FRIDAY) { - boolean inCurrentWeek = false; - if (days > 0) - inCurrentWeek = (currentWorkDay + days < 7); - else - inCurrentWeek = (currentWorkDay + days > 1); - - if (inCurrentWeek) { - workCal.add(Calendar.DAY_OF_MONTH, days); - resultDate = workCal; - } else { - int totalDays = 0; - int daysInCurrentWeek = 0; - - // fill up current week. - if (days > 0) { - daysInCurrentWeek = Calendar.SATURDAY - currentWorkDay; - totalDays = daysInCurrentWeek + 2; - } else { - daysInCurrentWeek = -(currentWorkDay - Calendar.SUNDAY); - totalDays = daysInCurrentWeek - 2; + } catch (Exception e) { + logger.severe("Error processing worklist: " + e.getMessage()); + if (logger.isLoggable(Level.FINE)) { + e.printStackTrace(); + } } - int restTotalDays = days - daysInCurrentWeek; - // next working week... add 2 days for each week. - int x = restTotalDays / 5; - totalDays += restTotalDays + (x * 2); + schedulerService.logMessage("================================", configItemCollection, null); + schedulerService.logMessage("... WorkflowScheduler completed.", configItemCollection, null); - workCal.add(Calendar.DAY_OF_MONTH, totalDays); - resultDate = workCal; + schedulerService.logMessage("..." + iProcessWorkItems + " workitems processed", configItemCollection, null); - } + if (unprocessedIDs.size() > 0) { + schedulerService.logWarning(unprocessedIDs.size() + " workitems could not be processed:", + configItemCollection, null); + for (String aid : unprocessedIDs) { + schedulerService.logWarning(" " + aid, configItemCollection, null); + } + } + + // update statistic of last run + configItemCollection.replaceItemValue("numWorkItemsProcessed", iProcessWorkItems); + configItemCollection.replaceItemValue("numWorkItemsUnprocessed", unprocessedIDs.size()); + + return configItemCollection; } - if (resultDate != null) { - logger.finest("......addWorkDays (" + baseDate.getTime() + ") + " + days + " = (" - + resultDate.getTime() + ")"); + + /** + * This method collects all scheduled workflow events. A scheduled workflow + * event is identified by the attribute keyScheduledActivity="1" + * + * The method goes through the latest or a specific Model Version + * + */ + protected Collection findScheduledEvents(String aModelVersion) throws Exception { + Vector vectorActivities = new Vector(); + Collection colProcessList = null; + + // get a complete list of process entities... + colProcessList = modelService.getModel(aModelVersion).findAllTasks(); + for (ItemCollection aprocessentity : colProcessList) { + // select all activities for this process entity... + int processid = aprocessentity.getItemValueInteger("numprocessid"); + logger.finest("......analyse processentity '" + processid + "'"); + Collection aActivityList = modelService.getModel(aModelVersion) + .findAllEventsByTask(processid); + + for (ItemCollection aactivityEntity : aActivityList) { + logger.finest("......analyse acitity '" + aactivityEntity.getItemValueString("txtname") + "'"); + + // check if activity is scheduled + if ("1".equals(aactivityEntity.getItemValueString("keyScheduledActivity"))) + vectorActivities.add(aactivityEntity); + } + } + return vectorActivities; } - return resultDate; - } - - /** - * This method process scheduled workitems. The method updates the property 'datLastRun' - * - * Because of bug: https://java.net/jira/browse/GLASSFISH-20673 we check the imixsDayOfWeek - * - * @param timer - * @throws AccessDeniedException - */ - @Override - public ItemCollection run(ItemCollection configItemCollection) throws SchedulerException { - - /* - * Now we process all scheduled worktitems for each model + + /** + * This method processes all workitems for a specific scheduled event element of + * a workflow model. A scheduled event element can define a selector + * (txtscheduledview). If no selector is defined, the default selector is used: + *

    + * {@code + * ($taskid:"[TASKID]" AND $modelversion:"[MODELVERSION]") + * } + *

    + * In case an old modelversion was deleted, the method tries to migrate to the + * lates model version. (issue #482) + * + * @param event - a event model element + * @throws Exception */ - iProcessWorkItems = 0; - unprocessedIDs = new ArrayList(); - try { - // get all model versions... - List modelVersions = modelService.getVersions(); - - // sort versions in descending order (issue #482) - Collections.sort(modelVersions, Collections.reverseOrder()); - - for (String version : modelVersions) { - // find scheduled Activities - Collection scheduledEvents = findScheduledEvents(version); - schedulerService.logMessage(version + " (" + scheduledEvents.size() + " scheduled events)", - configItemCollection, null); - // process all workitems for coresponding activities - for (ItemCollection aactivityEntity : scheduledEvents) { - processWorkListByEvent(aactivityEntity, configItemCollection); + protected void processWorkListByEvent(ItemCollection event, ItemCollection configItemCollection) throws Exception { + + // get task and event id form the event model entity.... + int taskID = event.getItemValueInteger("numprocessid"); + int eventID = event.getItemValueInteger("numActivityID"); + String modelVersionEvent = event.getItemValueString("$modelversion"); + // find task + ItemCollection taskElement = modelService.getModel(modelVersionEvent).getTask(taskID); + String workflowGroup = taskElement.getItemValueString("txtworkflowgroup"); + + String searchTerm = null; + // test if we have a custom selector + searchTerm = event.getItemValueString("txtscheduledview"); + + if (searchTerm.isEmpty()) { + // build the default selector.... + // searchTerm = "($taskid:\"" + taskID + "\" AND $modelversion:\"" + + // modelVersionEvent + "\")"; + // we are build the default selector based on workflowgroup (see isseu #482).... + searchTerm = "($taskid:\"" + taskID + "\" AND $workflowgroup:\"" + workflowGroup + "\")"; } - } - } catch (Exception e) { - logger.severe("Error processing worklist: " + e.getMessage()); - if (logger.isLoggable(Level.FINE)) { - e.printStackTrace(); - } - } + schedulerService.logMessage("...selector = " + searchTerm + " ...", configItemCollection, null); + Collection worklist = documentService.find(searchTerm, 1000, 0); + logger.finest("......" + worklist.size() + " workitems found"); + for (ItemCollection workitem : worklist) { - schedulerService.logMessage("================================", configItemCollection, null); - schedulerService.logMessage("... WorkflowScheduler completed.", configItemCollection, null); + String type = workitem.getType(); + // skip deleted.... + if (type.endsWith("deleted")) { + continue; + } - schedulerService.logMessage("..." + iProcessWorkItems + " workitems processed", - configItemCollection, null); + // skip $immutable Workitems + if (workitem.getItemValueBoolean("$immutable")) { + continue; + } - if (unprocessedIDs.size() > 0) { - schedulerService.logWarning(unprocessedIDs.size() + " workitems could not be processed:", - configItemCollection, null); - for (String aid : unprocessedIDs) { - schedulerService.logWarning(" " + aid, configItemCollection, null); - } - } + // issue #482 + // If the modelversion did not match the eventModelVersion, than migrate the + // model version... + if (!modelVersionEvent.equals(workitem.getModelVersion())) { + // test if the old model version still exists. + try { + modelService.getModel(workitem.getModelVersion()); + logger.finest("......skip because model version is older than current version..."); + // will be processed in the following loops.. + continue; + } catch (ModelException me) { + // ModelException - we migrate the model ... + logger.warning("...deprecated model version '" + workitem.getModelVersion() + + "' no longer exists -> migrating to new model version '" + modelVersionEvent + "'"); + workitem.model(modelVersionEvent); + } + } - // update statistic of last run - configItemCollection.replaceItemValue("numWorkItemsProcessed", iProcessWorkItems); - configItemCollection.replaceItemValue("numWorkItemsUnprocessed", unprocessedIDs.size()); - - return configItemCollection; - } - - /** - * This method collects all scheduled workflow events. A scheduled workflow event is identified by - * the attribute keyScheduledActivity="1" - * - * The method goes through the latest or a specific Model Version - * - */ - protected Collection findScheduledEvents(String aModelVersion) throws Exception { - Vector vectorActivities = new Vector(); - Collection colProcessList = null; - - // get a complete list of process entities... - colProcessList = modelService.getModel(aModelVersion).findAllTasks(); - for (ItemCollection aprocessentity : colProcessList) { - // select all activities for this process entity... - int processid = aprocessentity.getItemValueInteger("numprocessid"); - logger.finest("......analyse processentity '" + processid + "'"); - Collection aActivityList = - modelService.getModel(aModelVersion).findAllEventsByTask(processid); - - for (ItemCollection aactivityEntity : aActivityList) { - logger.finest( - "......analyse acitity '" + aactivityEntity.getItemValueString("txtname") + "'"); - - // check if activity is scheduled - if ("1".equals(aactivityEntity.getItemValueString("keyScheduledActivity"))) - vectorActivities.add(aactivityEntity); - } - } - return vectorActivities; - } - - /** - * This method processes all workitems for a specific scheduled event element of a workflow model. - * A scheduled event element can define a selector (txtscheduledview). If no selector is defined, - * the default selector is used: - *

    - * {@code - * ($taskid:"[TASKID]" AND $modelversion:"[MODELVERSION]") - * } - *

    - * In case an old modelversion was deleted, the method tries to migrate to the lates model - * version. (issue #482) - * - * @param event - a event model element - * @throws Exception - */ - protected void processWorkListByEvent(ItemCollection event, ItemCollection configItemCollection) - throws Exception { - - // get task and event id form the event model entity.... - int taskID = event.getItemValueInteger("numprocessid"); - int eventID = event.getItemValueInteger("numActivityID"); - String modelVersionEvent = event.getItemValueString("$modelversion"); - // find task - ItemCollection taskElement = modelService.getModel(modelVersionEvent).getTask(taskID); - String workflowGroup = taskElement.getItemValueString("txtworkflowgroup"); - - String searchTerm = null; - // test if we have a custom selector - searchTerm = event.getItemValueString("txtscheduledview"); - - if (searchTerm.isEmpty()) { - // build the default selector.... - // searchTerm = "($taskid:\"" + taskID + "\" AND $modelversion:\"" + - // modelVersionEvent + "\")"; - // we are build the default selector based on workflowgroup (see isseu #482).... - searchTerm = "($taskid:\"" + taskID + "\" AND $workflowgroup:\"" + workflowGroup + "\")"; - } + // verify due date + if (workItemInDue(workitem, event)) { + String sID = workitem.getItemValueString(WorkflowKernel.UNIQUEID); + logger.finest("......document " + sID + "is in due"); + workitem.setEventID(eventID); + try { + logger.finest("......getBusinessObject....."); + // call from new instance because of transaction new... + // see: http://blog.imixs.org/?p=155 + // see: https://www.java.net/node/705304 + workitem = workflowService.processWorkItemByNewTransaction(workitem); + iProcessWorkItems++; + } catch (Exception e) { + logger.warning("error processing workitem: " + sID + " Error=" + e.getMessage()); + if (logger.isLoggable(Level.FINEST)) { + e.printStackTrace(); + } + unprocessedIDs.add(sID); + } + } - schedulerService.logMessage("...selector = " + searchTerm + " ...", configItemCollection, null); - Collection worklist = documentService.find(searchTerm, 1000, 0); - logger.finest("......" + worklist.size() + " workitems found"); - for (ItemCollection workitem : worklist) { - - String type = workitem.getType(); - // skip deleted.... - if (type.endsWith("deleted")) { - continue; - } - - // skip $immutable Workitems - if (workitem.getItemValueBoolean("$immutable")) { - continue; - } - - // issue #482 - // If the modelversion did not match the eventModelVersion, than migrate the - // model version... - if (!modelVersionEvent.equals(workitem.getModelVersion())) { - // test if the old model version still exists. - try { - modelService.getModel(workitem.getModelVersion()); - logger.finest("......skip because model version is older than current version..."); - // will be processed in the following loops.. - continue; - } catch (ModelException me) { - // ModelException - we migrate the model ... - logger.warning("...deprecated model version '" + workitem.getModelVersion() - + "' no longer exists -> migrating to new model version '" + modelVersionEvent + "'"); - workitem.model(modelVersionEvent); } - } + } - // verify due date - if (workItemInDue(workitem, event)) { - String sID = workitem.getItemValueString(WorkflowKernel.UNIQUEID); - logger.finest("......document " + sID + "is in due"); - workitem.setEventID(eventID); - try { - logger.finest("......getBusinessObject....."); - // call from new instance because of transaction new... - // see: http://blog.imixs.org/?p=155 - // see: https://www.java.net/node/705304 - workitem = workflowService.processWorkItemByNewTransaction(workitem); - iProcessWorkItems++; - } catch (Exception e) { - logger.warning("error processing workitem: " + sID + " Error=" + e.getMessage()); - if (logger.isLoggable(Level.FINEST)) { - e.printStackTrace(); - } - unprocessedIDs.add(sID); - } - } + /** + * This method adjusts a given base date for a amount of delay + * + * + * @param baseDate date object to be adjusted + * + * @param offsetUnit - time unit (0=sec, 1=min, 2=hours, 3=days, 4=workdays) + * @param offset offset for adjustment + * @return new date object + */ + private Date adjustBaseDate(Date baseDate, int offsetUnit, int offset) { + if (baseDate != null) { - } - } - - - /** - * This method adjusts a given base date for a amount of delay - * - * - * @param baseDate date object to be adjusted - * - * @param offsetUnit - time unit (0=sec, 1=min, 2=hours, 3=days, 4=workdays) - * @param offset offset for adjustment - * @return new date object - */ - private Date adjustBaseDate(Date baseDate, int offsetUnit, int offset) { - if (baseDate != null) { - - // workdays? - if (offsetUnit == OFFSET_WORKDAYS) { - Calendar baseCal = Calendar.getInstance(); - baseCal.setTime(baseDate); - return addWorkDays(baseCal, offset).getTime(); - - } else { - - // compute offset in seconds... - if (offsetUnit == OFFSET_MINUTES) { - offset *= 60; // min->sec - } else { - if (offsetUnit == OFFSET_HOURS) { - offset *= 3600; // hour->sec - } else { - if (offsetUnit == OFFSET_DAYS) { - offset *= 3600 * 24; // day->sec + // workdays? + if (offsetUnit == OFFSET_WORKDAYS) { + Calendar baseCal = Calendar.getInstance(); + baseCal.setTime(baseDate); + return addWorkDays(baseCal, offset).getTime(); + + } else { + + // compute offset in seconds... + if (offsetUnit == OFFSET_MINUTES) { + offset *= 60; // min->sec + } else { + if (offsetUnit == OFFSET_HOURS) { + offset *= 3600; // hour->sec + } else { + if (offsetUnit == OFFSET_DAYS) { + offset *= 3600 * 24; // day->sec + } + } + } + Calendar calTimeCompare = Calendar.getInstance(); + calTimeCompare.setTime(baseDate); + calTimeCompare.add(Calendar.SECOND, offset); + return calTimeCompare.getTime(); } - } - } - Calendar calTimeCompare = Calendar.getInstance(); - calTimeCompare.setTime(baseDate); - calTimeCompare.add(Calendar.SECOND, offset); - return calTimeCompare.getTime(); - } - } else - return null; - } + } else + return null; + } } diff --git a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/WorkflowSchedulerController.java b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/WorkflowSchedulerController.java index 4d4fb19a4..e8cf31536 100644 --- a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/WorkflowSchedulerController.java +++ b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/WorkflowSchedulerController.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *

    - *  Imixs Workflow 
    +/*  
    + *  Imixs-Workflow 
    + *  
      *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
      *  http://www.imixs.com
      *  
    @@ -22,10 +22,9 @@
      *      https://github.com/imixs/imixs-workflow
      *  
      *  Contributors:  
    - *      Imixs Software Solutions GmbH - initial API and implementation
    + *      Imixs Software Solutions GmbH - Project Management
      *      Ralph Soika - Software Developer
    - * 
    - *******************************************************************************/ + */ package org.imixs.workflow.engine; @@ -34,10 +33,11 @@ import org.imixs.workflow.engine.scheduler.SchedulerController; /** - * The DatevController is used to configure the DatevScheduler. This service is used to generate - * datev export workitems. + * The DatevController is used to configure the DatevScheduler. This service is + * used to generate datev export workitems. *

    - * The Controller creates a configuration entity "type=configuration; txtname=datev". + * The Controller creates a configuration entity "type=configuration; + * txtname=datev". *

    * The following config items are defined: * @@ -56,22 +56,22 @@ @RequestScoped public class WorkflowSchedulerController extends SchedulerController { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 1L; - @Override - public String getName() { - return WorkflowScheduler.NAME; - } + @Override + public String getName() { + return WorkflowScheduler.NAME; + } - /** - * Returns the sepa scheduler class name. This name depends on the _export_type. - * - * There are two export interfaces available - csv and XML - * - */ - @Override - public String getSchedulerClass() { - return WorkflowScheduler.class.getName(); - } + /** + * Returns the sepa scheduler class name. This name depends on the _export_type. + * + * There are two export interfaces available - csv and XML + * + */ + @Override + public String getSchedulerClass() { + return WorkflowScheduler.class.getName(); + } } diff --git a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/WorkflowService.java b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/WorkflowService.java index e5abeb1af..6510412a2 100644 --- a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/WorkflowService.java +++ b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/WorkflowService.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *

    - *  Imixs Workflow 
    +/*  
    + *  Imixs-Workflow 
    + *  
      *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
      *  http://www.imixs.com
      *  
    @@ -22,10 +22,9 @@
      *      https://github.com/imixs/imixs-workflow
      *  
      *  Contributors:  
    - *      Imixs Software Solutions GmbH - initial API and implementation
    + *      Imixs Software Solutions GmbH - Project Management
      *      Ralph Soika - Software Developer
    - * 
    - *******************************************************************************/ + */ package org.imixs.workflow.engine; @@ -72,1097 +71,1116 @@ import org.imixs.workflow.exceptions.QueryException; /** - * The WorkflowService is the Java EE Implementation for the Imixs Workflow Core API. This interface - * acts as a service facade and supports basic methods to create, process and access workitems. The - * Interface extends the core api interface org.imixs.workflow.WorkflowManager with getter methods - * to fetch collections of workitems. + * The WorkflowService is the Java EE Implementation for the Imixs Workflow Core + * API. This interface acts as a service facade and supports basic methods to + * create, process and access workitems. The Interface extends the core api + * interface org.imixs.workflow.WorkflowManager with getter methods to fetch + * collections of workitems. * * @author rsoika * */ -@DeclareRoles({"org.imixs.ACCESSLEVEL.NOACCESS", "org.imixs.ACCESSLEVEL.READERACCESS", - "org.imixs.ACCESSLEVEL.AUTHORACCESS", "org.imixs.ACCESSLEVEL.EDITORACCESS", - "org.imixs.ACCESSLEVEL.MANAGERACCESS"}) -@RolesAllowed({"org.imixs.ACCESSLEVEL.NOACCESS", "org.imixs.ACCESSLEVEL.READERACCESS", - "org.imixs.ACCESSLEVEL.AUTHORACCESS", "org.imixs.ACCESSLEVEL.EDITORACCESS", - "org.imixs.ACCESSLEVEL.MANAGERACCESS"}) +@DeclareRoles({ "org.imixs.ACCESSLEVEL.NOACCESS", "org.imixs.ACCESSLEVEL.READERACCESS", + "org.imixs.ACCESSLEVEL.AUTHORACCESS", "org.imixs.ACCESSLEVEL.EDITORACCESS", + "org.imixs.ACCESSLEVEL.MANAGERACCESS" }) +@RolesAllowed({ "org.imixs.ACCESSLEVEL.NOACCESS", "org.imixs.ACCESSLEVEL.READERACCESS", + "org.imixs.ACCESSLEVEL.AUTHORACCESS", "org.imixs.ACCESSLEVEL.EDITORACCESS", + "org.imixs.ACCESSLEVEL.MANAGERACCESS" }) @Stateless @LocalBean public class WorkflowService implements WorkflowManager, WorkflowContext { - // workitem properties - public static final String UNIQUEIDREF = "$uniqueidref"; - public static final String READACCESS = "$readaccess"; - public static final String WRITEACCESS = "$writeaccess"; - public static final String PARTICIPANTS = "$participants"; - public static final String DEFAULT_TYPE = "workitem"; - - // view properties - public static final int SORT_ORDER_CREATED_DESC = 0; - public static final int SORT_ORDER_CREATED_ASC = 1; - public static final int SORT_ORDER_MODIFIED_DESC = 2; - public static final int SORT_ORDER_MODIFIED_ASC = 3; - - public static final String INVALID_ITEMVALUE_FORMAT = "INVALID_ITEMVALUE_FORMAT"; - public static final String INVALID_ITEM_FORMAT = "INVALID_ITEM_FORMAT"; - - @Inject - @Any - private Instance plugins; - - @Inject - @Any - protected Instance adapters; - - @Inject - DocumentService documentService; - - @Inject - ModelService modelService; - - @Inject - ReportService reportService; - - @Resource - SessionContext ctx; - - @Inject - protected Event processingEvents; - - @Inject - protected Event textEvents; - - private static Logger logger = Logger.getLogger(WorkflowService.class.getName()); - - /** - * This method loads a Workitem with the corresponding uniqueid. - * - */ - public ItemCollection getWorkItem(String uniqueid) { - return documentService.load(uniqueid); - } - - /** - * Returns a collection of workitems containing a '$owner' item belonging to a specified username. - * The '$owner' item can be controlled by the plug-in - * {@code org.imixs.workflow.plugins.OwnerPlugin} - * - * @param name = username for itme '$owner' - if null current username will be used - * @param pageSize = optional page count (default 20) - * @param pageIndex = optional start position - * @param type = defines the type property of the workitems to be returnd. can be null - * @param sortBy -optional field to sort the result - * @param sortReverse - optional sort direction - * - * @return List of workitems - * - */ - public List getWorkListByOwner(String name, String type, int pageSize, - int pageIndex, String sortBy, boolean sortReverse) { - - if (name == null || "".equals(name)) - name = ctx.getCallerPrincipal().getName(); - - String searchTerm = "("; - if (type != null && !"".equals(type)) { - searchTerm += " type:\"" + type + "\" AND "; - } + // workitem properties + public static final String UNIQUEIDREF = "$uniqueidref"; + public static final String READACCESS = "$readaccess"; + public static final String WRITEACCESS = "$writeaccess"; + public static final String PARTICIPANTS = "$participants"; + public static final String DEFAULT_TYPE = "workitem"; - // support deprecated namowner field - searchTerm += " (namowner:\"" + name + "\" OR owner:\"" + name + "\") )"; - try { - return documentService.find(searchTerm, pageSize, pageIndex, sortBy, sortReverse); - } catch (QueryException e) { - logger.severe("getWorkListByOwner - invalid param: " + e.getMessage()); - return null; - } - } - - /** - * Returns a collection of workItems belonging to a specified username. The name is a username or - * role contained in the $WriteAccess attribute of the workItem. - * - * The method returns only workitems the call has sufficient read access for. - * - * @param name = username or role contained in $writeAccess - if null current username will - * be used - * @param pageSize = optional page count (default 20) - * @param pageIndex = optional start position - * @param type = defines the type property of the workitems to be returnd. can be null - * @param sortBy -optional field to sort the result - * @param sortReverse - optional sort direction - * - * @return List of workitems - * - */ - public List getWorkListByAuthor(String name, String type, int pageSize, - int pageIndex, String sortBy, boolean sortReverse) { - - if (name == null || "".equals(name)) - name = ctx.getCallerPrincipal().getName(); - - String searchTerm = "("; - if (type != null && !"".equals(type)) { - searchTerm += " type:\"" + type + "\" AND "; - } - searchTerm += " $writeaccess:\"" + name + "\" )"; + // view properties + public static final int SORT_ORDER_CREATED_DESC = 0; + public static final int SORT_ORDER_CREATED_ASC = 1; + public static final int SORT_ORDER_MODIFIED_DESC = 2; + public static final int SORT_ORDER_MODIFIED_ASC = 3; - try { - return documentService.find(searchTerm, pageSize, pageIndex, sortBy, sortReverse); - } catch (QueryException e) { - logger.severe("getWorkListByAuthor - invalid param: " + e.getMessage()); - return null; - } - } - - /** - * Returns a collection of workitems created by a specified user ($Creator). The behaivor is - * simmilar to the method getWorkList. - * - * - * @param name = username for property $Creator - if null current username will be used - * @param pageSize = optional page count (default 20) - * @param pageIndex = optional start position - * @param type = defines the type property of the workitems to be returnd. can be null - * @param sortBy -optional field to sort the result - * @param sortReverse - optional sort direction - * - * @return List of workitems - * - */ - public List getWorkListByCreator(String name, String type, int pageSize, - int pageIndex, String sortBy, boolean sortReverse) { - - if (name == null || "".equals(name)) - name = ctx.getCallerPrincipal().getName(); - - String searchTerm = "("; - if (type != null && !"".equals(type)) { - searchTerm += " type:\"" + type + "\" AND "; - } - searchTerm += " $creator:\"" + name + "\" )"; - try { - return documentService.find(searchTerm, pageSize, pageIndex, sortBy, sortReverse); - } catch (QueryException e) { - logger.severe("getWorkListByCreator - invalid param: " + e.getMessage()); - return null; + public static final String INVALID_ITEMVALUE_FORMAT = "INVALID_ITEMVALUE_FORMAT"; + public static final String INVALID_ITEM_FORMAT = "INVALID_ITEM_FORMAT"; + + @Inject + @Any + private Instance plugins; + + @Inject + @Any + protected Instance adapters; + + @Inject + DocumentService documentService; + + @Inject + ModelService modelService; + + @Inject + ReportService reportService; + + @Resource + SessionContext ctx; + + @Inject + protected Event processingEvents; + + @Inject + protected Event textEvents; + + private static Logger logger = Logger.getLogger(WorkflowService.class.getName()); + + /** + * This method loads a Workitem with the corresponding uniqueid. + * + */ + public ItemCollection getWorkItem(String uniqueid) { + return documentService.load(uniqueid); } - } - - /** - * Returns a collection of workitems where the current user has a writeAccess. This means the - * either the username or one of the userroles is contained in the $writeaccess property - * - * - * @param pageSize = optional page count (default 20) - * @param pageIndex = optional start position - * @param type = defines the type property of the workitems to be returnd. can be null - * @param sortorder = defines sortorder (SORT_ORDER_CREATED_DESC = 0 SORT_ORDER_CREATED_ASC = 1 - * SORT_ORDER_MODIFIED_DESC = 2 SORT_ORDER_MODIFIED_ASC = 3) - * @param sortBy -optional field to sort the result - * @param sortReverse - optional sort direction - * - * @return List of workitems - * - */ - public List getWorkListByWriteAccess(String type, int pageSize, int pageIndex, - String sortBy, boolean sortReverse) { - StringBuffer nameListBuffer = new StringBuffer(); - - String name = ctx.getCallerPrincipal().getName(); - - // construct nameList. Begin with empty string '' and username - nameListBuffer.append("($writeaccess:\"" + name + "\""); - // now construct role list - - String accessRoles = documentService.getAccessRoles(); - - String roleList = - "org.imixs.ACCESSLEVEL.READERACCESS,org.imixs.ACCESSLEVEL.AUTHORACCESS,org.imixs.ACCESSLEVEL.EDITORACCESS," - + accessRoles; - // add each role the user is in to the name list - StringTokenizer roleListTokens = new StringTokenizer(roleList, ","); - while (roleListTokens.hasMoreTokens()) { - String testRole = roleListTokens.nextToken().trim(); - if (!"".equals(testRole) && ctx.isCallerInRole(testRole)) - nameListBuffer.append(" OR $writeaccess:\"" + testRole + "\""); + + /** + * Returns a collection of workitems containing a '$owner' item belonging to a + * specified username. The '$owner' item can be controlled by the plug-in + * {@code org.imixs.workflow.plugins.OwnerPlugin} + * + * @param name = username for itme '$owner' - if null current username + * will be used + * @param pageSize = optional page count (default 20) + * @param pageIndex = optional start position + * @param type = defines the type property of the workitems to be + * returnd. can be null + * @param sortBy -optional field to sort the result + * @param sortReverse - optional sort direction + * + * @return List of workitems + * + */ + public List getWorkListByOwner(String name, String type, int pageSize, int pageIndex, String sortBy, + boolean sortReverse) { + + if (name == null || "".equals(name)) + name = ctx.getCallerPrincipal().getName(); + + String searchTerm = "("; + if (type != null && !"".equals(type)) { + searchTerm += " type:\"" + type + "\" AND "; + } + + // support deprecated namowner field + searchTerm += " (namowner:\"" + name + "\" OR owner:\"" + name + "\") )"; + try { + return documentService.find(searchTerm, pageSize, pageIndex, sortBy, sortReverse); + } catch (QueryException e) { + logger.severe("getWorkListByOwner - invalid param: " + e.getMessage()); + return null; + } } - nameListBuffer.append(")"); - String searchTerm = "("; - if (type != null && !"".equals(type)) { - searchTerm += " type:\"" + type + "\" AND " + nameListBuffer.toString(); + /** + * Returns a collection of workItems belonging to a specified username. The name + * is a username or role contained in the $WriteAccess attribute of the + * workItem. + * + * The method returns only workitems the call has sufficient read access for. + * + * @param name = username or role contained in $writeAccess - if null + * current username will be used + * @param pageSize = optional page count (default 20) + * @param pageIndex = optional start position + * @param type = defines the type property of the workitems to be + * returnd. can be null + * @param sortBy -optional field to sort the result + * @param sortReverse - optional sort direction + * + * @return List of workitems + * + */ + public List getWorkListByAuthor(String name, String type, int pageSize, int pageIndex, + String sortBy, boolean sortReverse) { + + if (name == null || "".equals(name)) + name = ctx.getCallerPrincipal().getName(); + + String searchTerm = "("; + if (type != null && !"".equals(type)) { + searchTerm += " type:\"" + type + "\" AND "; + } + searchTerm += " $writeaccess:\"" + name + "\" )"; + + try { + return documentService.find(searchTerm, pageSize, pageIndex, sortBy, sortReverse); + } catch (QueryException e) { + logger.severe("getWorkListByAuthor - invalid param: " + e.getMessage()); + return null; + } } - searchTerm += " $writeaccess:\"" + name + "\" )"; - try { - return documentService.find(searchTerm, pageSize, pageIndex, sortBy, sortReverse); - } catch (QueryException e) { - logger.severe("getWorkListByWriteAccess - invalid param: " + e.getMessage()); - return null; + + /** + * Returns a collection of workitems created by a specified user ($Creator). The + * behaivor is simmilar to the method getWorkList. + * + * + * @param name = username for property $Creator - if null current + * username will be used + * @param pageSize = optional page count (default 20) + * @param pageIndex = optional start position + * @param type = defines the type property of the workitems to be + * returnd. can be null + * @param sortBy -optional field to sort the result + * @param sortReverse - optional sort direction + * + * @return List of workitems + * + */ + public List getWorkListByCreator(String name, String type, int pageSize, int pageIndex, + String sortBy, boolean sortReverse) { + + if (name == null || "".equals(name)) + name = ctx.getCallerPrincipal().getName(); + + String searchTerm = "("; + if (type != null && !"".equals(type)) { + searchTerm += " type:\"" + type + "\" AND "; + } + searchTerm += " $creator:\"" + name + "\" )"; + try { + return documentService.find(searchTerm, pageSize, pageIndex, sortBy, sortReverse); + } catch (QueryException e) { + logger.severe("getWorkListByCreator - invalid param: " + e.getMessage()); + return null; + } } - } - - /** - * Returns a list of workitems filtered by the field $workflowgroup - * - * the method supports still also the deprecated field "txtworkflowgroup" - * - * @param name - * @param type - * @param pageSize = optional page count (default 20) - * @param pageIndex = optional start position - * @param sortBy -optional field to sort the result - * @param sortReverse - optional sort direction - * - * @return - */ - - public List getWorkListByGroup(String name, String type, int pageSize, - int pageIndex, String sortBy, boolean sortReverse) { - - String searchTerm = "("; - if (type != null && !"".equals(type)) { - searchTerm += " type:\"" + type + "\" AND "; + + /** + * Returns a collection of workitems where the current user has a writeAccess. + * This means the either the username or one of the userroles is contained in + * the $writeaccess property + * + * + * @param pageSize = optional page count (default 20) + * @param pageIndex = optional start position + * @param type = defines the type property of the workitems to be + * returnd. can be null + * @param sortorder = defines sortorder (SORT_ORDER_CREATED_DESC = 0 + * SORT_ORDER_CREATED_ASC = 1 SORT_ORDER_MODIFIED_DESC = 2 + * SORT_ORDER_MODIFIED_ASC = 3) + * @param sortBy -optional field to sort the result + * @param sortReverse - optional sort direction + * + * @return List of workitems + * + */ + public List getWorkListByWriteAccess(String type, int pageSize, int pageIndex, String sortBy, + boolean sortReverse) { + StringBuffer nameListBuffer = new StringBuffer(); + + String name = ctx.getCallerPrincipal().getName(); + + // construct nameList. Begin with empty string '' and username + nameListBuffer.append("($writeaccess:\"" + name + "\""); + // now construct role list + + String accessRoles = documentService.getAccessRoles(); + + String roleList = "org.imixs.ACCESSLEVEL.READERACCESS,org.imixs.ACCESSLEVEL.AUTHORACCESS,org.imixs.ACCESSLEVEL.EDITORACCESS," + + accessRoles; + // add each role the user is in to the name list + StringTokenizer roleListTokens = new StringTokenizer(roleList, ","); + while (roleListTokens.hasMoreTokens()) { + String testRole = roleListTokens.nextToken().trim(); + if (!"".equals(testRole) && ctx.isCallerInRole(testRole)) + nameListBuffer.append(" OR $writeaccess:\"" + testRole + "\""); + } + nameListBuffer.append(")"); + + String searchTerm = "("; + if (type != null && !"".equals(type)) { + searchTerm += " type:\"" + type + "\" AND " + nameListBuffer.toString(); + } + searchTerm += " $writeaccess:\"" + name + "\" )"; + try { + return documentService.find(searchTerm, pageSize, pageIndex, sortBy, sortReverse); + } catch (QueryException e) { + logger.severe("getWorkListByWriteAccess - invalid param: " + e.getMessage()); + return null; + } } - // we support still the deprecated txtworkflowgroup - searchTerm += " ($workflowgroup:\"" + name + "\" OR txtworkflowgroup:\"" + name + "\") )"; - try { - return documentService.find(searchTerm, pageSize, pageIndex, sortBy, sortReverse); - } catch (QueryException e) { - logger.severe("getWorkListByGroup - invalid param: " + e.getMessage()); - return null; + + /** + * Returns a list of workitems filtered by the field $workflowgroup + * + * the method supports still also the deprecated field "txtworkflowgroup" + * + * @param name + * @param type + * @param pageSize = optional page count (default 20) + * @param pageIndex = optional start position + * @param sortBy -optional field to sort the result + * @param sortReverse - optional sort direction + * + * @return + */ + + public List getWorkListByGroup(String name, String type, int pageSize, int pageIndex, String sortBy, + boolean sortReverse) { + + String searchTerm = "("; + if (type != null && !"".equals(type)) { + searchTerm += " type:\"" + type + "\" AND "; + } + // we support still the deprecated txtworkflowgroup + searchTerm += " ($workflowgroup:\"" + name + "\" OR txtworkflowgroup:\"" + name + "\") )"; + try { + return documentService.find(searchTerm, pageSize, pageIndex, sortBy, sortReverse); + } catch (QueryException e) { + logger.severe("getWorkListByGroup - invalid param: " + e.getMessage()); + return null; + } } - } - - /** - * Returns a collection of workitems belonging to a specified $taskID defined by the workflow - * model. The behaivor is simmilar to the method getWorkList. - * - * @param aID = $taskID for the workitems to be returned. - * @param pageSize = optional page count (default 20) - * @param pageIndex = optional start position - * @param type = defines the type property of the workitems to be returnd. can be null - * @param sortBy -optional field to sort the result - * @param sortReverse - optional sort direction - * - * @return List of workitems - * - */ - public List getWorkListByProcessID(int aid, String type, int pageSize, - int pageIndex, String sortBy, boolean sortReverse) { - - String searchTerm = "("; - if (type != null && !"".equals(type)) { - searchTerm += " type:\"" + type + "\" AND "; + + /** + * Returns a collection of workitems belonging to a specified $taskID defined by + * the workflow model. The behaivor is simmilar to the method getWorkList. + * + * @param aID = $taskID for the workitems to be returned. + * @param pageSize = optional page count (default 20) + * @param pageIndex = optional start position + * @param type = defines the type property of the workitems to be + * returnd. can be null + * @param sortBy -optional field to sort the result + * @param sortReverse - optional sort direction + * + * @return List of workitems + * + */ + public List getWorkListByProcessID(int aid, String type, int pageSize, int pageIndex, String sortBy, + boolean sortReverse) { + + String searchTerm = "("; + if (type != null && !"".equals(type)) { + searchTerm += " type:\"" + type + "\" AND "; + } + // need to be fixed during slow migration issue #384 + searchTerm += " $processid:\"" + aid + "\" )"; + try { + return documentService.find(searchTerm, pageSize, pageIndex, sortBy, sortReverse); + } catch (QueryException e) { + logger.severe("getWorkListByProcessID - invalid param: " + e.getMessage()); + return null; + } } - // need to be fixed during slow migration issue #384 - searchTerm += " $processid:\"" + aid + "\" )"; - try { - return documentService.find(searchTerm, pageSize, pageIndex, sortBy, sortReverse); - } catch (QueryException e) { - logger.severe("getWorkListByProcessID - invalid param: " + e.getMessage()); - return null; + + /** + * Returns a collection of workitems belonging to a specified workitem + * identified by the attribute $UniqueIDRef. + * + * The behaivor of this Mehtod is simmilar to the method getWorkList. + * + * @param aref A unique reference to another workitem inside a database * + * @param pageSize = optional page count (default 20) + * @param pageIndex = optional start position + * @param type = defines the type property of the workitems to be + * returnd. can be null + * @param sortBy -optional field to sort the result + * @param sortReverse - optional sort direction + * + * @return List of workitems + */ + public List getWorkListByRef(String aref, String type, int pageSize, int pageIndex, String sortBy, + boolean sortReverse) { + + String searchTerm = "("; + if (type != null && !"".equals(type)) { + searchTerm += " type:\"" + type + "\" AND "; + } + searchTerm += " $uniqueidref:\"" + aref + "\" )"; + try { + return documentService.find(searchTerm, pageSize, pageIndex, sortBy, sortReverse); + } catch (QueryException e) { + logger.severe("getWorkListByRef - invalid param: " + e.getMessage()); + return null; + } } - } - - /** - * Returns a collection of workitems belonging to a specified workitem identified by the attribute - * $UniqueIDRef. - * - * The behaivor of this Mehtod is simmilar to the method getWorkList. - * - * @param aref A unique reference to another workitem inside a database * - * @param pageSize = optional page count (default 20) - * @param pageIndex = optional start position - * @param type = defines the type property of the workitems to be returnd. can be null - * @param sortBy -optional field to sort the result - * @param sortReverse - optional sort direction - * - * @return List of workitems - */ - public List getWorkListByRef(String aref, String type, int pageSize, - int pageIndex, String sortBy, boolean sortReverse) { - - String searchTerm = "("; - if (type != null && !"".equals(type)) { - searchTerm += " type:\"" + type + "\" AND "; + + /** + * Returns a collection of all workitems belonging to a specified workitem + * identified by the attribute $UniqueIDRef. + * + * @return List of workitems + */ + public List getWorkListByRef(String aref) { + return getWorkListByRef(aref, null, 0, 0, null, false); } - searchTerm += " $uniqueidref:\"" + aref + "\" )"; - try { - return documentService.find(searchTerm, pageSize, pageIndex, sortBy, sortReverse); - } catch (QueryException e) { - logger.severe("getWorkListByRef - invalid param: " + e.getMessage()); - return null; + + /** + * This returns a list of workflow events assigned to a given workitem. The + * method evaluates the events for the current $modelversion and $taskid. The + * result list is filtered by the properties 'keypublicresult' and + * 'keyRestrictedVisibility'. + * + * If the property keyRestrictedVisibility exits the method test if the current + * username is listed in one of the namefields. + * + * If the current user is in the role 'org.imixs.ACCESSLEVEL.MANAGERACCESS' the + * property keyRestrictedVisibility will be ignored. + * + * @see imixs-bpmn + * @param workitem + * @return + * @throws ModelException + */ + @SuppressWarnings("unchecked") + public List getEvents(ItemCollection workitem) throws ModelException { + List result = new ArrayList(); + int processID = workitem.getTaskID(); + // verify if version is valid + Model model = modelService.getModelByWorkitem(workitem); + + List eventList = model.findAllEventsByTask(processID); + + String username = getUserName(); + boolean bManagerAccess = ctx.isCallerInRole(DocumentService.ACCESSLEVEL_MANAGERACCESS); + + // now filter events which are not public (keypublicresult==false) or + // restricted for current user (keyRestrictedVisibility). + for (ItemCollection event : eventList) { + // test keypublicresult==false + + // ad only activities with userControlled != No + if ("0".equals(event.getItemValueString("keypublicresult"))) { + continue; + } + + // test user access level + List readAccessList = event.getItemValue("$readaccess"); + if (!bManagerAccess && !readAccessList.isEmpty()) { + /** + * check read access for current user + */ + boolean accessGranted = false; + // get user name list + List auserNameList = getUserNameList(); + + // check each read access + for (String aReadAccess : readAccessList) { + if (aReadAccess != null && !aReadAccess.isEmpty()) { + if (auserNameList.indexOf(aReadAccess) > -1) { + accessGranted = true; + break; + } + } + } + if (!accessGranted) { + // user has no read access! + continue; + } + } + + // test RestrictedVisibility + List restrictedList = event.getItemValue("keyRestrictedVisibility"); + if (!bManagerAccess && !restrictedList.isEmpty()) { + // test each item for the current user name... + List totalNameList = new ArrayList(); + for (String itemName : restrictedList) { + totalNameList.addAll(workitem.getItemValue(itemName)); + } + // remove null and empty values.... + totalNameList.removeAll(Collections.singleton(null)); + totalNameList.removeAll(Collections.singleton("")); + if (!totalNameList.isEmpty() && !totalNameList.contains(username)) { + // event is not visible for current user! + continue; + } + } + result.add(event); + } + + return result; + } - } - - /** - * Returns a collection of all workitems belonging to a specified workitem identified by the - * attribute $UniqueIDRef. - * - * @return List of workitems - */ - public List getWorkListByRef(String aref) { - return getWorkListByRef(aref, null, 0, 0, null, false); - } - - /** - * This returns a list of workflow events assigned to a given workitem. The method evaluates the - * events for the current $modelversion and $taskid. The result list is filtered by the properties - * 'keypublicresult' and 'keyRestrictedVisibility'. - * - * If the property keyRestrictedVisibility exits the method test if the current username is listed - * in one of the namefields. - * - * If the current user is in the role 'org.imixs.ACCESSLEVEL.MANAGERACCESS' the property - * keyRestrictedVisibility will be ignored. - * - * @see imixs-bpmn - * @param workitem - * @return - * @throws ModelException - */ - @SuppressWarnings("unchecked") - public List getEvents(ItemCollection workitem) throws ModelException { - List result = new ArrayList(); - int processID = workitem.getTaskID(); - // verify if version is valid - Model model = modelService.getModelByWorkitem(workitem); - - List eventList = model.findAllEventsByTask(processID); - - String username = getUserName(); - boolean bManagerAccess = ctx.isCallerInRole(DocumentService.ACCESSLEVEL_MANAGERACCESS); - - // now filter events which are not public (keypublicresult==false) or - // restricted for current user (keyRestrictedVisibility). - for (ItemCollection event : eventList) { - // test keypublicresult==false - - // ad only activities with userControlled != No - if ("0".equals(event.getItemValueString("keypublicresult"))) { - continue; - } - - // test user access level - List readAccessList = event.getItemValue("$readaccess"); - if (!bManagerAccess && !readAccessList.isEmpty()) { - /** - * check read access for current user - */ - boolean accessGranted = false; - // get user name list - List auserNameList = getUserNameList(); - - // check each read access - for (String aReadAccess : readAccessList) { - if (aReadAccess != null && !aReadAccess.isEmpty()) { - if (auserNameList.indexOf(aReadAccess) > -1) { - accessGranted = true; - break; + + /** + * This method processes a workItem by the WorkflowKernel and saves the workitem + * after the processing was finished successful. The workitem have to provide at + * least the properties '$modelversion', '$taskid' and '$eventid' + *

    + * Before the method starts processing the workitem, the method load the current + * instance of the given workitem and compares the property $taskID. If it is + * not equal the method throws an ProcessingErrorException. + *

    + * After the workitem was processed successful, the method verifies the property + * $workitemList. If this property holds a list of entities these entities will + * be saved and the property will be removed automatically. + *

    + * The method provides a observer pattern for plugins to get called during the + * processing phase. + * + * @param workitem - the workItem to be processed + * @return updated version of the processed workItem + * @throws AccessDeniedException - thrown if the user has insufficient access + * to update the workItem + * @throws ProcessingErrorException - thrown if the workitem could not be + * processed by the workflowKernel + * @throws PluginException - thrown if processing by a plugin fails + * @throws ModelException + */ + public ItemCollection processWorkItem(ItemCollection workitem) + throws AccessDeniedException, ProcessingErrorException, PluginException, ModelException { + boolean debug = logger.isLoggable(Level.FINE); + long lStartTime = System.currentTimeMillis(); + + if (workitem == null) + throw new ProcessingErrorException(WorkflowService.class.getSimpleName(), + ProcessingErrorException.INVALID_WORKITEM, "workitem Is Null!"); + + // fire event + if (processingEvents != null) { + processingEvents.fire(new ProcessingEvent(workitem, ProcessingEvent.BEFORE_PROCESS)); + } else { + logger.warning("CDI Support is missing - ProcessingEvents Not Supported!"); + } + // load current instance of this workitem if a unqiueID is provided + if (!workitem.getUniqueID().isEmpty()) { + // try to load the instance + ItemCollection currentInstance = this.getWorkItem(workitem.getUniqueID()); + // Instance successful loaded ? + if (currentInstance != null) { + // test for author access + if (!currentInstance.getItemValueBoolean(DocumentService.ISAUTHOR)) { + throw new AccessDeniedException(AccessDeniedException.OPERATION_NOTALLOWED, "$uniqueid: " + + workitem.getItemValueInteger(WorkflowKernel.UNIQUEID) + " - No Author Access!"); + } + // test if $taskID matches current instance + if (workitem.getTaskID() > 0 && currentInstance.getTaskID() != workitem.getTaskID()) { + throw new ProcessingErrorException(WorkflowService.class.getSimpleName(), + ProcessingErrorException.INVALID_PROCESSID, + "$uniqueid: " + workitem.getItemValueInteger(WorkflowKernel.UNIQUEID) + " - $taskid=" + + workitem.getTaskID() + " Did Not Match Expected $taskid=" + + currentInstance.getTaskID()); + } + // merge workitem into current instance (issue #86, issue #507) + // an instance of this WorkItem still exists! so we update the new + // values.... + workitem.mergeItems(currentInstance.getAllItems()); + + } else { + // In case we have a $UniqueId but did not found an matching workitem + // and the workitem miss a valid model assignment than + // processing is not possible - OPERATION_NOTALLOWED + + if ((workitem.getTaskID() <= 0) || (workitem.getEventID() <= 0) + || (workitem.getModelVersion().isEmpty() && workitem.getWorkflowGroup().isEmpty())) { + // user has no read access -> throw AccessDeniedException + throw new InvalidAccessException(InvalidAccessException.OPERATION_NOTALLOWED, + "$uniqueid: " + workitem.getItemValueInteger(WorkflowKernel.UNIQUEID) + + " - Insufficient Data or Lack Of Permission!"); + } + } - } } - if (!accessGranted) { - // user has no read access! - continue; + + // verify type attribute + if ("".equals(workitem.getType())) { + workitem.replaceItemValue("type", DEFAULT_TYPE); } - } - - // test RestrictedVisibility - List restrictedList = event.getItemValue("keyRestrictedVisibility"); - if (!bManagerAccess && !restrictedList.isEmpty()) { - // test each item for the current user name... - List totalNameList = new ArrayList(); - for (String itemName : restrictedList) { - totalNameList.addAll(workitem.getItemValue(itemName)); + + /* + * Lookup current processEntity. If not available update model to latest + * matching model version + */ + Model model = null; + try { + model = this.getModelManager().getModelByWorkitem(workitem); + } catch (ModelException e) { + throw new ProcessingErrorException(WorkflowService.class.getSimpleName(), + ProcessingErrorException.INVALID_PROCESSID, e.getMessage(), e); } - // remove null and empty values.... - totalNameList.removeAll(Collections.singleton(null)); - totalNameList.removeAll(Collections.singleton("")); - if (!totalNameList.isEmpty() && !totalNameList.contains(username)) { - // event is not visible for current user! - continue; + + WorkflowKernel workflowkernel = new WorkflowKernel(this); + // register plugins... + registerPlugins(workflowkernel, model); + // register adapters..... + registerAdapters(workflowkernel); + // udpate workitem metadata... + updateMetadata(workitem); + + // now process the workitem + try { + long lKernelTime = System.currentTimeMillis(); + workitem = workflowkernel.process(workitem); + if (debug) { + logger.fine("...WorkflowKernel processing time=" + (System.currentTimeMillis() - lKernelTime) + "ms"); + } + } catch (PluginException pe) { + // if a plugin exception occurs we roll back the transaction. + logger.severe("processing workitem '" + workitem.getItemValueString(WorkflowKernel.UNIQUEID) + + " failed, rollback transaction..."); + ctx.setRollbackOnly(); + throw pe; } - } - result.add(event); - } - return result; - - } - - /** - * This method processes a workItem by the WorkflowKernel and saves the workitem after the - * processing was finished successful. The workitem have to provide at least the properties - * '$modelversion', '$taskid' and '$eventid' - *

    - * Before the method starts processing the workitem, the method load the current instance of the - * given workitem and compares the property $taskID. If it is not equal the method throws an - * ProcessingErrorException. - *

    - * After the workitem was processed successful, the method verifies the property $workitemList. If - * this property holds a list of entities these entities will be saved and the property will be - * removed automatically. - *

    - * The method provides a observer pattern for plugins to get called during the processing phase. - * - * @param workitem - the workItem to be processed - * @return updated version of the processed workItem - * @throws AccessDeniedException - thrown if the user has insufficient access to update the - * workItem - * @throws ProcessingErrorException - thrown if the workitem could not be processed by the - * workflowKernel - * @throws PluginException - thrown if processing by a plugin fails - * @throws ModelException - */ - public ItemCollection processWorkItem(ItemCollection workitem) - throws AccessDeniedException, ProcessingErrorException, PluginException, ModelException { - boolean debug = logger.isLoggable(Level.FINE); - long lStartTime = System.currentTimeMillis(); - - if (workitem == null) - throw new ProcessingErrorException(WorkflowService.class.getSimpleName(), - ProcessingErrorException.INVALID_WORKITEM, "workitem Is Null!"); - - // fire event - if (processingEvents != null) { - processingEvents.fire(new ProcessingEvent(workitem, ProcessingEvent.BEFORE_PROCESS)); - } else { - logger.warning("CDI Support is missing - ProcessingEvents Not Supported!"); - } - // load current instance of this workitem if a unqiueID is provided - if (!workitem.getUniqueID().isEmpty()) { - // try to load the instance - ItemCollection currentInstance = this.getWorkItem(workitem.getUniqueID()); - // Instance successful loaded ? - if (currentInstance != null) { - // test for author access - if (!currentInstance.getItemValueBoolean(DocumentService.ISAUTHOR)) { - throw new AccessDeniedException(AccessDeniedException.OPERATION_NOTALLOWED, "$uniqueid: " - + workitem.getItemValueInteger(WorkflowKernel.UNIQUEID) + " - No Author Access!"); + // fire event + if (processingEvents != null) { + processingEvents.fire(new ProcessingEvent(workitem, ProcessingEvent.AFTER_PROCESS)); } - // test if $taskID matches current instance - if (workitem.getTaskID() > 0 && currentInstance.getTaskID() != workitem.getTaskID()) { - throw new ProcessingErrorException(WorkflowService.class.getSimpleName(), - ProcessingErrorException.INVALID_PROCESSID, - "$uniqueid: " + workitem.getItemValueInteger(WorkflowKernel.UNIQUEID) + " - $taskid=" - + workitem.getTaskID() + " Did Not Match Expected $taskid=" - + currentInstance.getTaskID()); + // Now fire also events for all split versions..... + List splitWorkitems = workflowkernel.getSplitWorkitems(); + for (ItemCollection splitWorkitemm : splitWorkitems) { + // fire event + if (processingEvents != null) { + processingEvents.fire(new ProcessingEvent(splitWorkitemm, ProcessingEvent.AFTER_PROCESS)); + } + documentService.save(splitWorkitemm); } - // merge workitem into current instance (issue #86, issue #507) - // an instance of this WorkItem still exists! so we update the new - // values.... - workitem.mergeItems(currentInstance.getAllItems()); - - } else { - // In case we have a $UniqueId but did not found an matching workitem - // and the workitem miss a valid model assignment than - // processing is not possible - OPERATION_NOTALLOWED - - if ((workitem.getTaskID() <= 0) || (workitem.getEventID() <= 0) - || (workitem.getModelVersion().isEmpty() && workitem.getWorkflowGroup().isEmpty())) { - // user has no read access -> throw AccessDeniedException - throw new InvalidAccessException(InvalidAccessException.OPERATION_NOTALLOWED, - "$uniqueid: " + workitem.getItemValueInteger(WorkflowKernel.UNIQUEID) - + " - Insufficient Data or Lack Of Permission!"); + + workitem = documentService.save(workitem); + if (debug) { + logger.fine("...total processing time=" + (System.currentTimeMillis() - lStartTime) + "ms"); } + return workitem; + } - } + /** + * This method processes a workItem based on a given event. + * + * @see method ItemCollection processWorkItem(ItemCollection workitem) + * + * @param workitem - the workItem to be processed + * @param event - event object + * @return updated version of the processed workItem + * @throws AccessDeniedException - thrown if the user has insufficient access + * to update the workItem + * @throws ProcessingErrorException - thrown if the workitem could not be + * processed by the workflowKernel + * @throws PluginException - thrown if processing by a plugin fails + * @throws ModelException + **/ + public ItemCollection processWorkItem(ItemCollection workitem, ItemCollection event) + throws AccessDeniedException, ProcessingErrorException, PluginException, ModelException { + + return processWorkItem(workitem, event.getItemValueInteger("numactivityid")); } - // verify type attribute - if ("".equals(workitem.getType())) { - workitem.replaceItemValue("type", DEFAULT_TYPE); + /** + * This method processes a workItem based on a given event. + * + * @see method ItemCollection processWorkItem(ItemCollection workitem) + * + * @param workitem - the workItem to be processed + * @param event - event object + * @return updated version of the processed workItem + * @throws AccessDeniedException - thrown if the user has insufficient access + * to update the workItem + * @throws ProcessingErrorException - thrown if the workitem could not be + * processed by the workflowKernel + * @throws PluginException - thrown if processing by a plugin fails + * @throws ModelException + **/ + public ItemCollection processWorkItem(ItemCollection workitem, int eventID) + throws AccessDeniedException, ProcessingErrorException, PluginException, ModelException { + + workitem.setEventID(eventID); + return processWorkItem(workitem); } - /* - * Lookup current processEntity. If not available update model to latest matching model version + /** + * This method processes a workitem in a new transaction. + * + * @throws ModelException + * @throws PluginException + * @throws ProcessingErrorException + * @throws AccessDeniedException + * */ - Model model = null; - try { - model = this.getModelManager().getModelByWorkitem(workitem); - } catch (ModelException e) { - throw new ProcessingErrorException(WorkflowService.class.getSimpleName(), - ProcessingErrorException.INVALID_PROCESSID, e.getMessage(), e); + @TransactionAttribute(value = TransactionAttributeType.REQUIRES_NEW) + public ItemCollection processWorkItemByNewTransaction(ItemCollection workitem) + throws AccessDeniedException, ProcessingErrorException, PluginException, ModelException { + boolean debug = logger.isLoggable(Level.FINE); + if (debug) { + logger.finest(" ....processing workitem by by new transaction..."); + } + return processWorkItem(workitem); } - WorkflowKernel workflowkernel = new WorkflowKernel(this); - // register plugins... - registerPlugins(workflowkernel, model); - // register adapters..... - registerAdapters(workflowkernel); - // udpate workitem metadata... - updateMetadata(workitem); - - // now process the workitem - try { - long lKernelTime = System.currentTimeMillis(); - workitem = workflowkernel.process(workitem); - if (debug) { - logger.fine("...WorkflowKernel processing time=" - + (System.currentTimeMillis() - lKernelTime) + "ms"); - } - } catch (PluginException pe) { - // if a plugin exception occurs we roll back the transaction. - logger.severe("processing workitem '" + workitem.getItemValueString(WorkflowKernel.UNIQUEID) - + " failed, rollback transaction..."); - ctx.setRollbackOnly(); - throw pe; + public void removeWorkItem(ItemCollection aworkitem) throws AccessDeniedException { + documentService.remove(aworkitem); } - // fire event - if (processingEvents != null) { - processingEvents.fire(new ProcessingEvent(workitem, ProcessingEvent.AFTER_PROCESS)); - } - // Now fire also events for all split versions..... - List splitWorkitems = workflowkernel.getSplitWorkitems(); - for (ItemCollection splitWorkitemm : splitWorkitems) { - // fire event - if (processingEvents != null) { - processingEvents.fire(new ProcessingEvent(splitWorkitemm, ProcessingEvent.AFTER_PROCESS)); - } - documentService.save(splitWorkitemm); + /** + * This Method returns the modelManager Instance. The current ModelVersion is + * automatically updated during the Method updateProfileEntity which is called + * from the processWorktiem method. + * + */ + public ModelManager getModelManager() { + return modelService; } - workitem = documentService.save(workitem); - if (debug) { - logger.fine("...total processing time=" + (System.currentTimeMillis() - lStartTime) + "ms"); - } - return workitem; - } - - /** - * This method processes a workItem based on a given event. - * - * @see method ItemCollection processWorkItem(ItemCollection workitem) - * - * @param workitem - the workItem to be processed - * @param event - event object - * @return updated version of the processed workItem - * @throws AccessDeniedException - thrown if the user has insufficient access to update the - * workItem - * @throws ProcessingErrorException - thrown if the workitem could not be processed by the - * workflowKernel - * @throws PluginException - thrown if processing by a plugin fails - * @throws ModelException - **/ - public ItemCollection processWorkItem(ItemCollection workitem, ItemCollection event) - throws AccessDeniedException, ProcessingErrorException, PluginException, ModelException { - - return processWorkItem(workitem, event.getItemValueInteger("numactivityid")); - } - - /** - * This method processes a workItem based on a given event. - * - * @see method ItemCollection processWorkItem(ItemCollection workitem) - * - * @param workitem - the workItem to be processed - * @param event - event object - * @return updated version of the processed workItem - * @throws AccessDeniedException - thrown if the user has insufficient access to update the - * workItem - * @throws ProcessingErrorException - thrown if the workitem could not be processed by the - * workflowKernel - * @throws PluginException - thrown if processing by a plugin fails - * @throws ModelException - **/ - public ItemCollection processWorkItem(ItemCollection workitem, int eventID) - throws AccessDeniedException, ProcessingErrorException, PluginException, ModelException { - - workitem.setEventID(eventID); - return processWorkItem(workitem); - } - - /** - * This method processes a workitem in a new transaction. - * - * @throws ModelException - * @throws PluginException - * @throws ProcessingErrorException - * @throws AccessDeniedException - * - */ - @TransactionAttribute(value = TransactionAttributeType.REQUIRES_NEW) - public ItemCollection processWorkItemByNewTransaction(ItemCollection workitem) - throws AccessDeniedException, ProcessingErrorException, PluginException, ModelException { - boolean debug = logger.isLoggable(Level.FINE); - if (debug) { - logger.finest(" ....processing workitem by by new transaction..."); - } - return processWorkItem(workitem); - } - - public void removeWorkItem(ItemCollection aworkitem) throws AccessDeniedException { - documentService.remove(aworkitem); - } - - /** - * This Method returns the modelManager Instance. The current ModelVersion is automatically - * updated during the Method updateProfileEntity which is called from the processWorktiem method. - * - */ - public ModelManager getModelManager() { - return modelService; - } - - /** - * Returns an instance of the EJB session context. - * - * @return - */ - public SessionContext getSessionContext() { - return ctx; - } - - /** - * Returns an instance of the DocumentService EJB. - * - * @return - */ - public DocumentService getDocumentService() { - return documentService; - } - - /** - * Returns an instance of the ReportService EJB. - * - * @return - */ - public ReportService getReportService() { - return reportService; - } - - /** - * Obtain the java.security.Principal that identifies the caller and returns the name of this - * principal. - * - * @return the user name - */ - public String getUserName() { - return ctx.getCallerPrincipal().getName(); - - } - - /** - * Test if the caller has a given security role. - * - * @param rolename - * @return true if user is in role - */ - public boolean isUserInRole(String rolename) { - try { - return ctx.isCallerInRole(rolename); - } catch (Exception e) { - // avoid a exception for a role request which is not defined - return false; + /** + * Returns an instance of the EJB session context. + * + * @return + */ + public SessionContext getSessionContext() { + return ctx; } - } - - /** - * This method returns a list of user names, roles and application groups the caller belongs to. - * - * @return - */ - public List getUserNameList() { - return documentService.getUserNameList(); - } - - /** - * The method adaptText can be called to replace predefined xml tags included in a text with - * custom values. The method fires a CDI event to inform TextAdapterServices to parse and adapt a - * given text fragment. - * - * @param text - * @param documentContext - * @return - * @throws PluginException - */ - public String adaptText(String text, ItemCollection documentContext) throws PluginException { - // fire event - if (textEvents != null) { - TextEvent event = new TextEvent(text, documentContext); - textEvents.fire(event); - text = event.getText(); - } else { - logger.warning("CDI Support is missing - TextEvent wil not be fired"); + + /** + * Returns an instance of the DocumentService EJB. + * + * @return + */ + public DocumentService getDocumentService() { + return documentService; } - return text; - } - - /** - * The method adaptTextList can be called to replace a text with custom values. The method fires a - * CDI event to inform TextAdapterServices to parse and adapt a given text fragment. The method - * expects a textList result. - * - * @param text - * @param documentContext - * @return - * @throws PluginException - */ - public List adaptTextList(String text, ItemCollection documentContext) - throws PluginException { - // fire event - if (textEvents != null) { - TextEvent event = new TextEvent(text, documentContext); - textEvents.fire(event); - return event.getTextList(); - } else { - logger.warning("CDI Support is missing - TextEvent wil not be fired"); + + /** + * Returns an instance of the ReportService EJB. + * + * @return + */ + public ReportService getReportService() { + return reportService; } - // no result return default - List textList = new ArrayList(); - textList.add(text); - return textList; - } - - /** - * The method evaluates the WorkflowResult for a given BPMN event and returns a ItemColleciton - * containing all item definitions. Each item definition of a WorkflowResult contains a name and a - * optional list of additional attributes. The method generates a item for each content element - * and attribute value.
    - * e.g. text
    - * will result in the attributes 'comment' with value 'text' and 'comment.ignore' with the value - * 'true' - * - * Also embedded itemVaues can be resolved (resolveItemValues=true): - * - * - * ABC$uniqueid - * - * - * This example will result in a new item 'somedata' with the $uniqueid prefixed with 'ABC' - * - * @see https://stackoverflow.com/questions/1732348/regex-match-open-tags-except-xhtml-self-contained-tags - * @param event - * @param documentContext - * @param resolveItemValues - if true, itemValue tags will be resolved. - * @return eval itemCollection or null if no items are contained in the workflow result. - * @throws PluginException if the xml structure is invalid - */ - public ItemCollection evalWorkflowResult(ItemCollection event, ItemCollection documentContext, - boolean resolveItemValues) throws PluginException { - boolean debug = logger.isLoggable(Level.FINE); - ItemCollection result = new ItemCollection(); - String workflowResult = event.getItemValueString("txtActivityResult"); - if (workflowResult.trim().isEmpty()) { - return null; + + /** + * Obtain the java.security.Principal that identifies the caller and returns the + * name of this principal. + * + * @return the user name + */ + public String getUserName() { + return ctx.getCallerPrincipal().getName(); + } - // replace dynamic values? - if (resolveItemValues) { - workflowResult = adaptText(workflowResult, documentContext); + + /** + * Test if the caller has a given security role. + * + * @param rolename + * @return true if user is in role + */ + public boolean isUserInRole(String rolename) { + try { + return ctx.isCallerInRole(rolename); + } catch (Exception e) { + // avoid a exception for a role request which is not defined + return false; + } } - // if no getUserNameList() { + return documentService.getUserNameList(); } - // Extract all tags with attributes using regex (including empty tags) - // see also: - // https://stackoverflow.com/questions/1732348/regex-match-open-tags-except-xhtml-self-contained-tags - // (.*?)| - Pattern pattern = Pattern.compile( - "(?s)(?:(\\b(?:\".*?\"|'.*?'|[^>]*?)*>)(?<=/>))|(\\b(?:\".*?\"|'.*?'|[^>]*?)*>)(?))(.*?)())", - Pattern.DOTALL); - - boolean invalidPattern = true; - Matcher matcher = pattern.matcher(workflowResult); - while (matcher.find()) { - invalidPattern = false; - // we expect up to 4 different result groups - // group 0 contains complete item string - // groups 1 or 2 contain the attributes - - String content = ""; - String attributes = matcher.group(1); - if (attributes == null) { - attributes = matcher.group(2); - content = matcher.group(3); - } else { - content = matcher.group(2); - } - - if (content == null) { - content = ""; - } - - // now extract the attributes to verify the item name.. - if (attributes != null && !attributes.isEmpty()) { - // parse attributes... - String spattern = "(\\S+)=[\"']?((?:.(?![\"']?\\s+(?:\\S+)=|[>\"']))+.)[\"']?"; - Pattern attributePattern = Pattern.compile(spattern); - Matcher attributeMatcher = attributePattern.matcher(attributes); - Map attrMap = new HashMap(); - while (attributeMatcher.find()) { - String attrName = attributeMatcher.group(1); // name - String attrValue = attributeMatcher.group(2); // value - attrMap.put(attrName, attrValue); + /** + * The method adaptText can be called to replace predefined xml tags included in + * a text with custom values. The method fires a CDI event to inform + * TextAdapterServices to parse and adapt a given text fragment. + * + * @param text + * @param documentContext + * @return + * @throws PluginException + */ + public String adaptText(String text, ItemCollection documentContext) throws PluginException { + // fire event + if (textEvents != null) { + TextEvent event = new TextEvent(text, documentContext); + textEvents.fire(event); + text = event.getText(); + } else { + logger.warning("CDI Support is missing - TextEvent wil not be fired"); } + return text; + } - String itemName = attrMap.get("name"); - if (itemName == null) { - throw new PluginException(ResultPlugin.class.getSimpleName(), INVALID_ITEM_FORMAT, - " tag contains no name attribute."); + /** + * The method adaptTextList can be called to replace a text with custom values. + * The method fires a CDI event to inform TextAdapterServices to parse and adapt + * a given text fragment. The method expects a textList result. + * + * @param text + * @param documentContext + * @return + * @throws PluginException + */ + public List adaptTextList(String text, ItemCollection documentContext) throws PluginException { + // fire event + if (textEvents != null) { + TextEvent event = new TextEvent(text, documentContext); + textEvents.fire(event); + return event.getTextList(); + } else { + logger.warning("CDI Support is missing - TextEvent wil not be fired"); } + // no result return default + List textList = new ArrayList(); + textList.add(text); + return textList; + } - if (itemName.startsWith("$") || "type".equalsIgnoreCase(itemName)) { - throw new PluginException(ResultPlugin.class.getSimpleName(), INVALID_ITEM_FORMAT, - " tag contains invalid attribute name '" + itemName + "'."); + /** + * The method evaluates the WorkflowResult for a given BPMN event and returns a + * ItemColleciton containing all item definitions. Each item definition of a + * WorkflowResult contains a name and a optional list of additional attributes. + * The method generates a item for each content element and attribute value. + *
    + * e.g. text
    + * will result in the attributes 'comment' with value 'text' and + * 'comment.ignore' with the value 'true' + * + * Also embedded itemVaues can be resolved (resolveItemValues=true): + * + * + * ABC$uniqueid + * + * + * This example will result in a new item 'somedata' with the $uniqueid prefixed + * with 'ABC' + * + * @see https://stackoverflow.com/questions/1732348/regex-match-open-tags-except-xhtml-self-contained-tags + * @param event + * @param documentContext + * @param resolveItemValues - if true, itemValue tags will be resolved. + * @return eval itemCollection or null if no items are contained in the workflow + * result. + * @throws PluginException if the xml structure is invalid + */ + public ItemCollection evalWorkflowResult(ItemCollection event, ItemCollection documentContext, + boolean resolveItemValues) throws PluginException { + boolean debug = logger.isLoggable(Level.FINE); + ItemCollection result = new ItemCollection(); + String workflowResult = event.getItemValueString("txtActivityResult"); + if (workflowResult.trim().isEmpty()) { + return null; + } + // replace dynamic values? + if (resolveItemValues) { + workflowResult = adaptText(workflowResult, documentContext); } - // now add optional attributes if available - for (String attrName : attrMap.keySet()) { - // we need to skip the 'name' attribute - if (!"name".equals(attrName)) { - result.appendItemValue(itemName + "." + attrName, attrMap.get(attrName)); - } + // if no tags with attributes using regex (including empty tags) + // see also: + // https://stackoverflow.com/questions/1732348/regex-match-open-tags-except-xhtml-self-contained-tags + // (.*?)| + Pattern pattern = Pattern.compile( + "(?s)(?:(\\b(?:\".*?\"|'.*?'|[^>]*?)*>)(?<=/>))|(\\b(?:\".*?\"|'.*?'|[^>]*?)*>)(?))(.*?)())", + Pattern.DOTALL); + + boolean invalidPattern = true; + Matcher matcher = pattern.matcher(workflowResult); + while (matcher.find()) { + invalidPattern = false; + // we expect up to 4 different result groups + // group 0 contains complete item string + // groups 1 or 2 contain the attributes + + String content = ""; + String attributes = matcher.group(1); + if (attributes == null) { + attributes = matcher.group(2); + content = matcher.group(3); + } else { + content = matcher.group(2); } - } else if ("double".equalsIgnoreCase(sType)) { - try { - result.appendItemValue(itemName, Double.valueOf(content)); - } catch (NumberFormatException e) { - // append 0 value - result.appendItemValue(itemName, new Double(0)); + + if (content == null) { + content = ""; } - } else if ("date".equalsIgnoreCase(sType)) { - if (content == null || content.isEmpty()) { - // no value available - no op! - if (debug) { - logger.finer("......can not convert empty string into date object"); - } - } else { - // convert content value to date object - try { - if (debug) { - logger.finer("......convert string into date object"); + + // now extract the attributes to verify the item name.. + if (attributes != null && !attributes.isEmpty()) { + // parse attributes... + String spattern = "(\\S+)=[\"']?((?:.(?![\"']?\\s+(?:\\S+)=|[>\"']))+.)[\"']?"; + Pattern attributePattern = Pattern.compile(spattern); + Matcher attributeMatcher = attributePattern.matcher(attributes); + Map attrMap = new HashMap(); + while (attributeMatcher.find()) { + String attrName = attributeMatcher.group(1); // name + String attrValue = attributeMatcher.group(2); // value + attrMap.put(attrName, attrValue); } - Date dateResult = null; - if (sFormat == null || sFormat.isEmpty()) { - // use standard formate short/short - dateResult = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT) - .parse(content); - } else { - // use given formatter (see: TextItemValueAdapter) - DateFormat dateFormat = new SimpleDateFormat(sFormat); - dateResult = dateFormat.parse(content); + + String itemName = attrMap.get("name"); + if (itemName == null) { + throw new PluginException(ResultPlugin.class.getSimpleName(), INVALID_ITEM_FORMAT, + " tag contains no name attribute."); } - result.appendItemValue(itemName, dateResult); - } catch (ParseException e) { - if (debug) { - logger.finer("failed to convert string into date object: " + e.getMessage()); + + if (itemName.startsWith("$") || "type".equalsIgnoreCase(itemName)) { + throw new PluginException(ResultPlugin.class.getSimpleName(), INVALID_ITEM_FORMAT, + " tag contains invalid attribute name '" + itemName + "'."); } - } - } - } else - // no type conversion - result.appendItemValue(itemName, content); - } else { - // no type definition - result.appendItemValue(itemName, content); - } + // now add optional attributes if available + for (String attrName : attrMap.keySet()) { + // we need to skip the 'name' attribute + if (!"name".equals(attrName)) { + result.appendItemValue(itemName + "." + attrName, attrMap.get(attrName)); + } + } - } else { - throw new PluginException(ResultPlugin.class.getSimpleName(), INVALID_ITEM_FORMAT, - " tag contains no name attribute."); + // test if the type attribute was provided to convert content? + String sType = result.getItemValueString(itemName + ".type"); + String sFormat = result.getItemValueString(itemName + ".format"); + if (!sType.isEmpty()) { + // convert content type + if ("boolean".equalsIgnoreCase(sType)) { + result.appendItemValue(itemName, Boolean.valueOf(content)); + } else if ("integer".equalsIgnoreCase(sType)) { + try { + result.appendItemValue(itemName, Integer.valueOf(content)); + } catch (NumberFormatException e) { + // append 0 value + result.appendItemValue(itemName, new Integer(0)); + } + } else if ("double".equalsIgnoreCase(sType)) { + try { + result.appendItemValue(itemName, Double.valueOf(content)); + } catch (NumberFormatException e) { + // append 0 value + result.appendItemValue(itemName, new Double(0)); + } + } else if ("date".equalsIgnoreCase(sType)) { + if (content == null || content.isEmpty()) { + // no value available - no op! + if (debug) { + logger.finer("......can not convert empty string into date object"); + } + } else { + // convert content value to date object + try { + if (debug) { + logger.finer("......convert string into date object"); + } + Date dateResult = null; + if (sFormat == null || sFormat.isEmpty()) { + // use standard formate short/short + dateResult = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT) + .parse(content); + } else { + // use given formatter (see: TextItemValueAdapter) + DateFormat dateFormat = new SimpleDateFormat(sFormat); + dateResult = dateFormat.parse(content); + } + result.appendItemValue(itemName, dateResult); + } catch (ParseException e) { + if (debug) { + logger.finer("failed to convert string into date object: " + e.getMessage()); + } + } + } + + } else + // no type conversion + result.appendItemValue(itemName, content); + } else { + // no type definition + result.appendItemValue(itemName, content); + } - } - } + } else { + throw new PluginException(ResultPlugin.class.getSimpleName(), INVALID_ITEM_FORMAT, + " tag contains no name attribute."); - // test for general invalid format - if (invalidPattern) { - throw new PluginException(ResultPlugin.class.getSimpleName(), INVALID_ITEM_FORMAT, - "invalid tag format in workflowResult: " + workflowResult - + " , expected format is ... "); - } - return result; - } - - /** - * The method evaluates the WorkflowResult of a BPMN event and resolves embedded ItemValues. - * - * * - * ABC$uniqueid - * - * - * This example will result in a new item 'somedata' with the $uniqueid prafixed with 'ABC' - * - * @see evalWorkflowResult(ItemCollection activityEntity, ItemCollection documentContext,boolean - * resolveItemValues) - * @param activityEntity - * @param documentContext - * @return - * @throws PluginException - */ - public ItemCollection evalWorkflowResult(ItemCollection activityEntity, - ItemCollection documentContext) throws PluginException { - return evalWorkflowResult(activityEntity, documentContext, true); - } - - /** - * The method evaluates the next task for a process instance (workitem) based on the current model - * definition. A Workitem must at least provide the properties $TASKID and $EVENTID. - *

    - * During the evaluation life-cycle more than one events can be evaluated. This depends on the - * model definition which can define follow-up-events, split-events and conditional events. - *

    - * The method did not persist the process instance or execute any plugin or adapter classes. - * - * @return Task entity - * @throws PluginException - * @throws ModelException - */ - public ItemCollection evalNextTask(ItemCollection documentContext) - throws PluginException, ModelException { - WorkflowKernel workflowkernel = new WorkflowKernel(this); - - int taskID = workflowkernel.eval(documentContext); - ItemCollection task = - this.getModelManager().getModel(documentContext.getModelVersion()).getTask(taskID); - return task; - // return workflowkernel.findNextTask(documentContext, event); - } - - /** - * This method register all plugin classes listed in the model profile - * - * @throws PluginException - */ - @SuppressWarnings("unchecked") - protected void registerPlugins(WorkflowKernel workflowkernel, Model model) - throws PluginException { - boolean debug = logger.isLoggable(Level.FINE); - // Fetch the current Profile Entity for this version. - ItemCollection profile = model.getDefinition(); - - // register plugins defined in the environment.profile .... - List vPlugins = (List) profile.getItemValue("txtPlugins"); - for (int i = 0; i < vPlugins.size(); i++) { - String aPluginClassName = vPlugins.get(i); - - Plugin aPlugin = findPluginByName(aPluginClassName); - // aPlugin=null; - if (aPlugin != null) { - // register injected CDI Plugin - if (debug) { - logger.finest("......register CDI plugin class: " + aPluginClassName + "..."); + } } - workflowkernel.registerPlugin(aPlugin); - } else { - // register plugin by class name - workflowkernel.registerPlugin(aPluginClassName); - } - } - } - - protected void registerAdapters(WorkflowKernel workflowkernel) { - boolean debug = logger.isLoggable(Level.FINE); - if (debug && (adapters == null || !adapters.iterator().hasNext())) { - logger.finest("......no CDI Adapters injected"); - } else { - // iterate over all injected adapters.... - for (Adapter adapter : this.adapters) { - if (debug) { - logger.finest("......register CDI Adapter class '" + adapter.getClass().getName() + "'"); + + // test for general invalid format + if (invalidPattern) { + throw new PluginException(ResultPlugin.class.getSimpleName(), INVALID_ITEM_FORMAT, + "invalid tag format in workflowResult: " + workflowResult + + " , expected format is ... "); } - workflowkernel.registerAdapter(adapter); - } + return result; } - } - - /** - * This method updates the workitem metadata. The following items will be updated: - * - *

      - *
    • $creator
    • - *
    • $editor
    • - *
    • $lasteditor
    • - *
    • $participants
    • - *
    - *

    - * The method also migrates deprected items. - * - * @param workitem - */ - protected void updateMetadata(ItemCollection workitem) { - - // identify Caller and update CurrentEditor - String nameEditor; - nameEditor = ctx.getCallerPrincipal().getName(); - - // add namCreator if empty - // migrate $creator (Backward compatibility) - if (workitem.getItemValueString("$creator").isEmpty() - && !workitem.getItemValueString("namCreator").isEmpty()) { - workitem.replaceItemValue("$creator", workitem.getItemValue("namCreator")); + + /** + * The method evaluates the WorkflowResult of a BPMN event and resolves embedded + * ItemValues. + * + * * + * ABC$uniqueid + * + * + * This example will result in a new item 'somedata' with the $uniqueid prafixed + * with 'ABC' + * + * @see evalWorkflowResult(ItemCollection activityEntity, ItemCollection + * documentContext,boolean resolveItemValues) + * @param activityEntity + * @param documentContext + * @return + * @throws PluginException + */ + public ItemCollection evalWorkflowResult(ItemCollection activityEntity, ItemCollection documentContext) + throws PluginException { + return evalWorkflowResult(activityEntity, documentContext, true); } - if (workitem.getItemValueString("$creator").isEmpty()) { - workitem.replaceItemValue("$creator", nameEditor); - // support deprecated fieldname - workitem.replaceItemValue("namCreator", nameEditor); + /** + * The method evaluates the next task for a process instance (workitem) based on + * the current model definition. A Workitem must at least provide the properties + * $TASKID and $EVENTID. + *

    + * During the evaluation life-cycle more than one events can be evaluated. This + * depends on the model definition which can define follow-up-events, + * split-events and conditional events. + *

    + * The method did not persist the process instance or execute any plugin or + * adapter classes. + * + * @return Task entity + * @throws PluginException + * @throws ModelException + */ + public ItemCollection evalNextTask(ItemCollection documentContext) throws PluginException, ModelException { + WorkflowKernel workflowkernel = new WorkflowKernel(this); + + int taskID = workflowkernel.eval(documentContext); + ItemCollection task = this.getModelManager().getModel(documentContext.getModelVersion()).getTask(taskID); + return task; + // return workflowkernel.findNextTask(documentContext, event); } - // update namLastEditor only if current editor has changed - if (!nameEditor.equals(workitem.getItemValueString("$editor")) - && !workitem.getItemValueString("$editor").isEmpty()) { - workitem.replaceItemValue("$lasteditor", workitem.getItemValueString("$editor")); - // deprecated - workitem.replaceItemValue("namlasteditor", workitem.getItemValueString("$editor")); + /** + * This method register all plugin classes listed in the model profile + * + * @throws PluginException + */ + @SuppressWarnings("unchecked") + protected void registerPlugins(WorkflowKernel workflowkernel, Model model) throws PluginException { + boolean debug = logger.isLoggable(Level.FINE); + // Fetch the current Profile Entity for this version. + ItemCollection profile = model.getDefinition(); + + // register plugins defined in the environment.profile .... + List vPlugins = (List) profile.getItemValue("txtPlugins"); + for (int i = 0; i < vPlugins.size(); i++) { + String aPluginClassName = vPlugins.get(i); + + Plugin aPlugin = findPluginByName(aPluginClassName); + // aPlugin=null; + if (aPlugin != null) { + // register injected CDI Plugin + if (debug) { + logger.finest("......register CDI plugin class: " + aPluginClassName + "..."); + } + workflowkernel.registerPlugin(aPlugin); + } else { + // register plugin by class name + workflowkernel.registerPlugin(aPluginClassName); + } + } } - // update $editor - workitem.replaceItemValue("$editor", nameEditor); - // deprecated - workitem.replaceItemValue("namcurrenteditor", nameEditor); - } - - /** - * This method returns an injected Plugin by name or null if no plugin with the requested class - * name is injected. - * - * @param pluginClassName - * @return plugin class or null if not found - */ - private Plugin findPluginByName(String pluginClassName) { - if (pluginClassName == null || pluginClassName.isEmpty()) - return null; - boolean debug = logger.isLoggable(Level.FINE); - - if (plugins == null || !plugins.iterator().hasNext()) { - if (debug) { - logger.finest("......no CDI plugins injected"); - } - return null; + protected void registerAdapters(WorkflowKernel workflowkernel) { + boolean debug = logger.isLoggable(Level.FINE); + if (debug && (adapters == null || !adapters.iterator().hasNext())) { + logger.finest("......no CDI Adapters injected"); + } else { + // iterate over all injected adapters.... + for (Adapter adapter : this.adapters) { + if (debug) { + logger.finest("......register CDI Adapter class '" + adapter.getClass().getName() + "'"); + } + workflowkernel.registerAdapter(adapter); + } + } } - // iterate over all injected plugins.... - for (Plugin plugin : this.plugins) { - if (plugin.getClass().getName().equals(pluginClassName)) { - if (debug) { - logger.finest("......CDI plugin '" + pluginClassName + "' successful injected"); + + /** + * This method updates the workitem metadata. The following items will be + * updated: + * + *

      + *
    • $creator
    • + *
    • $editor
    • + *
    • $lasteditor
    • + *
    • $participants
    • + *
    + *

    + * The method also migrates deprected items. + * + * @param workitem + */ + protected void updateMetadata(ItemCollection workitem) { + + // identify Caller and update CurrentEditor + String nameEditor; + nameEditor = ctx.getCallerPrincipal().getName(); + + // add namCreator if empty + // migrate $creator (Backward compatibility) + if (workitem.getItemValueString("$creator").isEmpty() && !workitem.getItemValueString("namCreator").isEmpty()) { + workitem.replaceItemValue("$creator", workitem.getItemValue("namCreator")); + } + + if (workitem.getItemValueString("$creator").isEmpty()) { + workitem.replaceItemValue("$creator", nameEditor); + // support deprecated fieldname + workitem.replaceItemValue("namCreator", nameEditor); + } + + // update namLastEditor only if current editor has changed + if (!nameEditor.equals(workitem.getItemValueString("$editor")) + && !workitem.getItemValueString("$editor").isEmpty()) { + workitem.replaceItemValue("$lasteditor", workitem.getItemValueString("$editor")); + // deprecated + workitem.replaceItemValue("namlasteditor", workitem.getItemValueString("$editor")); } - return plugin; - } + + // update $editor + workitem.replaceItemValue("$editor", nameEditor); + // deprecated + workitem.replaceItemValue("namcurrenteditor", nameEditor); } - return null; - } + /** + * This method returns an injected Plugin by name or null if no plugin with the + * requested class name is injected. + * + * @param pluginClassName + * @return plugin class or null if not found + */ + private Plugin findPluginByName(String pluginClassName) { + if (pluginClassName == null || pluginClassName.isEmpty()) + return null; + boolean debug = logger.isLoggable(Level.FINE); + + if (plugins == null || !plugins.iterator().hasNext()) { + if (debug) { + logger.finest("......no CDI plugins injected"); + } + return null; + } + // iterate over all injected plugins.... + for (Plugin plugin : this.plugins) { + if (plugin.getClass().getName().equals(pluginClassName)) { + if (debug) { + logger.finest("......CDI plugin '" + pluginClassName + "' successful injected"); + } + return plugin; + } + } + + return null; + } } diff --git a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/adapters/AccessAdapter.java b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/adapters/AccessAdapter.java index 989861fc4..728338dcc 100644 --- a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/adapters/AccessAdapter.java +++ b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/adapters/AccessAdapter.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *

    - *  Imixs Workflow 
    +/*  
    + *  Imixs-Workflow 
    + *  
      *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
      *  http://www.imixs.com
      *  
    @@ -22,10 +22,9 @@
      *      https://github.com/imixs/imixs-workflow
      *  
      *  Contributors:  
    - *      Imixs Software Solutions GmbH - initial API and implementation
    + *      Imixs Software Solutions GmbH - Project Management
      *      Ralph Soika - Software Developer
    - * 
    - *******************************************************************************/ + */ package org.imixs.workflow.engine.adapters; @@ -45,20 +44,21 @@ import org.imixs.workflow.exceptions.PluginException; /** - * The AccessAdapter is a generic adapter class responsible to update the ACL of a workitem. The CID - * Bean updates the following Items + * The AccessAdapter is a generic adapter class responsible to update the ACL of + * a workitem. The CID Bean updates the following Items *
      *
    • $writeAccess
    • *
    • $readAccess
    • *
    • $participants
    • *
    *

    - * The read and write access for a workitem can be defined by the BPMN model with the ACL Properties - * of the Imixs-BPMN modeler. + * The read and write access for a workitem can be defined by the BPMN model + * with the ACL Properties of the Imixs-BPMN modeler. *

    * The participants is a computed list of all users who edited this workitem. *

    - * By defining an CDI alternative an application can overwrite the behavior of this bean. + * By defining an CDI alternative an application can overwrite the behavior of + * this bean. * * @author rsoika * @version 1.0.0 @@ -66,282 +66,282 @@ @Named public class AccessAdapter implements GenericAdapter, Serializable { - private static final long serialVersionUID = 1L; - private static Logger logger = Logger.getLogger(AccessAdapter.class.getName()); - - // See CDI Constructor - protected WorkflowService workflowService; - - /** - * Default Constructor - */ - public AccessAdapter() { - super(); - } - - /** - * CDI Constructor to inject WorkflowService - * - * @param workflowService - */ - @Inject - public AccessAdapter(WorkflowService workflowService) { - super(); - this.workflowService = workflowService; - } - - /** - * The Execute method updates the ACL of a process instance based on a given event. - * - */ - @Override - public ItemCollection execute(ItemCollection document, ItemCollection event) - throws AdapterException { - ItemCollection nextTask = null; - // get next process entity - try { - // nextTask = workflowService.evalNextTask(document, event); - nextTask = workflowService.evalNextTask(document); - // in case the event is connected to a followup activity the - // nextProcess can be null! - - updateParticipants(document); - updateACL(document, event, nextTask); - - } catch (ModelException | PluginException e) { - throw new AdapterException(AccessAdapter.class.getSimpleName(), e.getErrorCode(), - e.getMessage()); - } - return null; - } - - public void setWorkflowService(WorkflowService workflowService) { - this.workflowService = workflowService; - - } - - /** - * Update the $PARTICIPANTS. - * - * @param workitem - * @return - */ - @SuppressWarnings("unchecked") - public ItemCollection updateParticipants(ItemCollection workitem) { - - List participants = workitem.getItemValue(WorkflowService.PARTICIPANTS); - String user = workflowService.getUserName(); - if (!participants.contains(user)) { - participants.add(user); - workitem.replaceItemValue(WorkflowService.PARTICIPANTS, participants); - } + private static final long serialVersionUID = 1L; + private static Logger logger = Logger.getLogger(AccessAdapter.class.getName()); + + // See CDI Constructor + protected WorkflowService workflowService; - return workitem; - } - - /** - * This method updates the $readAccess and $writeAccess attributes of a WorkItem depending to the - * configuration of a Activity Entity. - * - * The method evaluates the new model flag keyupdateacl. If 'false' then acl will not be updated. - * - * - */ - @SuppressWarnings({"rawtypes"}) - public ItemCollection updateACL(ItemCollection workitem, ItemCollection event, - ItemCollection nextTask) throws PluginException { - - if (event == null && nextTask == null) { - // no update! - return workitem; + /** + * Default Constructor + */ + public AccessAdapter() { + super(); } - ItemCollection documentContext = workitem; - - - // test update mode of activity and process entity - if true clear the - // existing values. - if ((event == null || event.getItemValueBoolean("keyupdateacl") == false) - && (nextTask == null || nextTask.getItemValueBoolean("keyupdateacl") == false)) { - // no update! - return documentContext; - } else { - // clear existing settings! - documentContext.replaceItemValue(WorkflowService.READACCESS, new Vector()); - documentContext.replaceItemValue(WorkflowService.WRITEACCESS, new Vector()); - - // event settings will not be merged with task settings! - if (event != null && event.getItemValueBoolean("keyupdateacl") == true) { - updateACLByItemCollection(documentContext, event); - } else { - updateACLByItemCollection(documentContext, nextTask); - } + + /** + * CDI Constructor to inject WorkflowService + * + * @param workflowService + */ + @Inject + public AccessAdapter(WorkflowService workflowService) { + super(); + this.workflowService = workflowService; } - return documentContext; - } - - /** - * This method updates the read/write access of a workitem depending on a given model entity The - * model entity should provide the following attributes: - * - * keyupdateacl, namaddreadaccess,keyaddreadfields,keyaddwritefields,namaddwriteaccess - * - * - * The method did not clear the exiting values of $writeAccess and $readAccess - * - * @throws PluginException - */ - @SuppressWarnings({"unchecked", "rawtypes"}) - private void updateACLByItemCollection(ItemCollection documentContext, ItemCollection modelEntity) - throws PluginException { - boolean debug = logger.isLoggable(Level.FINE); - if (modelEntity == null || modelEntity.getItemValueBoolean("keyupdateacl") == false) { - // no update necessary - return; + /** + * The Execute method updates the ACL of a process instance based on a given + * event. + * + */ + @Override + public ItemCollection execute(ItemCollection document, ItemCollection event) throws AdapterException { + ItemCollection nextTask = null; + // get next process entity + try { + // nextTask = workflowService.evalNextTask(document, event); + nextTask = workflowService.evalNextTask(document); + // in case the event is connected to a followup activity the + // nextProcess can be null! + + updateParticipants(document); + updateACL(document, event, nextTask); + + } catch (ModelException | PluginException e) { + throw new AdapterException(AccessAdapter.class.getSimpleName(), e.getErrorCode(), e.getMessage()); + } + return null; } - List vectorAccess; - vectorAccess = documentContext.getItemValue(WorkflowService.READACCESS); - // add roles - mergeRoles(vectorAccess, modelEntity.getItemValue("namaddreadaccess"), documentContext); - // add Mapped Fields - mergeFieldList(documentContext, vectorAccess, modelEntity.getItemValue("keyaddreadfields")); - // clean Vector - vectorAccess = uniqueList(vectorAccess); - - // update accesslist.... - documentContext.replaceItemValue(WorkflowService.READACCESS, vectorAccess); - if ((debug) && (vectorAccess.size() > 0)) { - logger.finest("......[AccessPlugin] ReadAccess:"); - for (int j = 0; j < vectorAccess.size(); j++) - logger.finest(" '" + (String) vectorAccess.get(j) + "'"); + public void setWorkflowService(WorkflowService workflowService) { + this.workflowService = workflowService; + } - // update WriteAccess - vectorAccess = documentContext.getItemValue(WorkflowService.WRITEACCESS); - // add Names - mergeRoles(vectorAccess, modelEntity.getItemValue("namaddwriteaccess"), documentContext); - // add Mapped Fields - mergeFieldList(documentContext, vectorAccess, modelEntity.getItemValue("keyaddwritefields")); - // clean Vector - vectorAccess = uniqueList(vectorAccess); - - // update accesslist.... - documentContext.replaceItemValue(WorkflowService.WRITEACCESS, vectorAccess); - if ((debug) && (vectorAccess.size() > 0)) { - logger.finest("......[AccessPlugin] WriteAccess:"); - for (int j = 0; j < vectorAccess.size(); j++) - logger.finest(" '" + (String) vectorAccess.get(j) + "'"); + /** + * Update the $PARTICIPANTS. + * + * @param workitem + * @return + */ + @SuppressWarnings("unchecked") + public ItemCollection updateParticipants(ItemCollection workitem) { + + List participants = workitem.getItemValue(WorkflowService.PARTICIPANTS); + String user = workflowService.getUserName(); + if (!participants.contains(user)) { + participants.add(user); + workitem.replaceItemValue(WorkflowService.PARTICIPANTS, participants); + } + + return workitem; } - } - - /** - * This method merges the values of fieldList into valueList and test for duplicates. - * - * If an entry of the fieldList is a single key value, than the values to be merged are read from - * the corresponding documentContext property - * - * e.g. 'namTeam' -> maps the values of the documentContext property 'namteam' into the valueList - * - * If an entry of the fieldList is in square brackets, than the comma separated elements are - * mapped into the valueList - * - * e.g. '[user1,user2]' - maps the values 'user1' and 'user2' int the valueList. Also Curly - * brackets are allowed '{user1,user2}' - * - * - * @param valueList - * @param fieldList - */ - @SuppressWarnings({"unchecked", "rawtypes"}) - public void mergeFieldList(ItemCollection documentContext, List valueList, - List fieldList) { - if (valueList == null || fieldList == null) - return; - List values = null; - if (fieldList.size() > 0) { - // iterate over the fieldList - for (String key : fieldList) { - if (key == null) { - continue; + /** + * This method updates the $readAccess and $writeAccess attributes of a WorkItem + * depending to the configuration of a Activity Entity. + * + * The method evaluates the new model flag keyupdateacl. If 'false' then acl + * will not be updated. + * + * + */ + @SuppressWarnings({ "rawtypes" }) + public ItemCollection updateACL(ItemCollection workitem, ItemCollection event, ItemCollection nextTask) + throws PluginException { + + if (event == null && nextTask == null) { + // no update! + return workitem; } - key = key.trim(); - // test if key contains square or curly brackets? - if ((key.startsWith("[") && key.endsWith("]")) - || (key.startsWith("{") && key.endsWith("}"))) { - // extract the value list with regExpression (\s matches any - // white space, The * applies the match zero or more times. - // So \s* means "match any white space zero or more times". - // We look for this before and after the comma.) - values = Arrays.asList(key.substring(1, key.length() - 1).split("\\s*,\\s*")); + ItemCollection documentContext = workitem; + + // test update mode of activity and process entity - if true clear the + // existing values. + if ((event == null || event.getItemValueBoolean("keyupdateacl") == false) + && (nextTask == null || nextTask.getItemValueBoolean("keyupdateacl") == false)) { + // no update! + return documentContext; } else { - // extract value list form documentContext - values = documentContext.getItemValue(key); + // clear existing settings! + documentContext.replaceItemValue(WorkflowService.READACCESS, new Vector()); + documentContext.replaceItemValue(WorkflowService.WRITEACCESS, new Vector()); + + // event settings will not be merged with task settings! + if (event != null && event.getItemValueBoolean("keyupdateacl") == true) { + updateACLByItemCollection(documentContext, event); + } else { + updateACLByItemCollection(documentContext, nextTask); + } + } + + return documentContext; + } + + /** + * This method updates the read/write access of a workitem depending on a given + * model entity The model entity should provide the following attributes: + * + * keyupdateacl, + * namaddreadaccess,keyaddreadfields,keyaddwritefields,namaddwriteaccess + * + * + * The method did not clear the exiting values of $writeAccess and $readAccess + * + * @throws PluginException + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + private void updateACLByItemCollection(ItemCollection documentContext, ItemCollection modelEntity) + throws PluginException { + boolean debug = logger.isLoggable(Level.FINE); + if (modelEntity == null || modelEntity.getItemValueBoolean("keyupdateacl") == false) { + // no update necessary + return; } - // now append the values into p_VectorDestination - if ((values != null) && (values.size() > 0)) { - for (Object o : values) { - // append only if not used - if (valueList.indexOf(o) == -1) - valueList.add(o); - } + + List vectorAccess; + vectorAccess = documentContext.getItemValue(WorkflowService.READACCESS); + // add roles + mergeRoles(vectorAccess, modelEntity.getItemValue("namaddreadaccess"), documentContext); + // add Mapped Fields + mergeFieldList(documentContext, vectorAccess, modelEntity.getItemValue("keyaddreadfields")); + // clean Vector + vectorAccess = uniqueList(vectorAccess); + + // update accesslist.... + documentContext.replaceItemValue(WorkflowService.READACCESS, vectorAccess); + if ((debug) && (vectorAccess.size() > 0)) { + logger.finest("......[AccessPlugin] ReadAccess:"); + for (int j = 0; j < vectorAccess.size(); j++) + logger.finest(" '" + (String) vectorAccess.get(j) + "'"); + } + + // update WriteAccess + vectorAccess = documentContext.getItemValue(WorkflowService.WRITEACCESS); + // add Names + mergeRoles(vectorAccess, modelEntity.getItemValue("namaddwriteaccess"), documentContext); + // add Mapped Fields + mergeFieldList(documentContext, vectorAccess, modelEntity.getItemValue("keyaddwritefields")); + // clean Vector + vectorAccess = uniqueList(vectorAccess); + + // update accesslist.... + documentContext.replaceItemValue(WorkflowService.WRITEACCESS, vectorAccess); + if ((debug) && (vectorAccess.size() > 0)) { + logger.finest("......[AccessPlugin] WriteAccess:"); + for (int j = 0; j < vectorAccess.size(); j++) + logger.finest(" '" + (String) vectorAccess.get(j) + "'"); } - } + } - } + /** + * This method merges the values of fieldList into valueList and test for + * duplicates. + * + * If an entry of the fieldList is a single key value, than the values to be + * merged are read from the corresponding documentContext property + * + * e.g. 'namTeam' -> maps the values of the documentContext property 'namteam' + * into the valueList + * + * If an entry of the fieldList is in square brackets, than the comma separated + * elements are mapped into the valueList + * + * e.g. '[user1,user2]' - maps the values 'user1' and 'user2' int the valueList. + * Also Curly brackets are allowed '{user1,user2}' + * + * + * @param valueList + * @param fieldList + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public void mergeFieldList(ItemCollection documentContext, List valueList, List fieldList) { + if (valueList == null || fieldList == null) + return; + List values = null; + if (fieldList.size() > 0) { + // iterate over the fieldList + for (String key : fieldList) { + if (key == null) { + continue; + } + key = key.trim(); + // test if key contains square or curly brackets? + if ((key.startsWith("[") && key.endsWith("]")) || (key.startsWith("{") && key.endsWith("}"))) { + // extract the value list with regExpression (\s matches any + // white space, The * applies the match zero or more times. + // So \s* means "match any white space zero or more times". + // We look for this before and after the comma.) + values = Arrays.asList(key.substring(1, key.length() - 1).split("\\s*,\\s*")); + } else { + // extract value list form documentContext + values = documentContext.getItemValue(key); + } + // now append the values into p_VectorDestination + if ((values != null) && (values.size() > 0)) { + for (Object o : values) { + // append only if not used + if (valueList.indexOf(o) == -1) + valueList.add(o); + } + } + } + } - /** - * This method removes duplicates and null values from a vector. - * - * @param valueList - list of elements - */ - public List uniqueList(List valueList) { - int iVectorSize = valueList.size(); - Vector cleanedVector = new Vector(); + } - for (int i = 0; i < iVectorSize; i++) { - Object o = valueList.get(i); - if (o == null || cleanedVector.indexOf(o) > -1 || "".equals(o.toString())) - continue; + /** + * This method removes duplicates and null values from a vector. + * + * @param valueList - list of elements + */ + public List uniqueList(List valueList) { + int iVectorSize = valueList.size(); + Vector cleanedVector = new Vector(); + + for (int i = 0; i < iVectorSize; i++) { + Object o = valueList.get(i); + if (o == null || cleanedVector.indexOf(o) > -1 || "".equals(o.toString())) + continue; + + // add unique object + cleanedVector.add(o); + } + valueList = cleanedVector; + // do not work with empty vectors.... + if (valueList.size() == 0) + valueList.add(""); - // add unique object - cleanedVector.add(o); + return valueList; } - valueList = cleanedVector; - // do not work with empty vectors.... - if (valueList.size() == 0) - valueList.add(""); - - return valueList; - } - - /** - * This method merges the role names from a SourceList into a valueList and removes duplicates. - * - * The AddaptText event is fired so a client can adapt a role name. - * - * @param valueList - * @param sourceList - * @throws PluginException - */ - @SuppressWarnings({"rawtypes", "unchecked"}) - public void mergeRoles(List valueList, List sourceList, ItemCollection documentContext) - throws PluginException { - if ((sourceList != null) && (sourceList.size() > 0)) { - for (Object o : sourceList) { - if (valueList.indexOf(o) == -1) { - if (o instanceof String) { - // addapt textList - List adaptedRoles = workflowService.adaptTextList((String) o, documentContext); - valueList.addAll(adaptedRoles);// .add(getWorkflowService().adaptText((String)o, - // documentContext)); - } else { - valueList.add(o); - } + + /** + * This method merges the role names from a SourceList into a valueList and + * removes duplicates. + * + * The AddaptText event is fired so a client can adapt a role name. + * + * @param valueList + * @param sourceList + * @throws PluginException + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + public void mergeRoles(List valueList, List sourceList, ItemCollection documentContext) throws PluginException { + if ((sourceList != null) && (sourceList.size() > 0)) { + for (Object o : sourceList) { + if (valueList.indexOf(o) == -1) { + if (o instanceof String) { + // addapt textList + List adaptedRoles = workflowService.adaptTextList((String) o, documentContext); + valueList.addAll(adaptedRoles);// .add(getWorkflowService().adaptText((String)o, + // documentContext)); + } else { + valueList.add(o); + } + } + } } - } } - } } diff --git a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/adminp/AdminPException.java b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/adminp/AdminPException.java index 33fb02883..81c96b211 100644 --- a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/adminp/AdminPException.java +++ b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/adminp/AdminPException.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *
    - *  Imixs Workflow 
    +/*  
    + *  Imixs-Workflow 
    + *  
      *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
      *  http://www.imixs.com
      *  
    @@ -22,10 +22,9 @@
      *      https://github.com/imixs/imixs-workflow
      *  
      *  Contributors:  
    - *      Imixs Software Solutions GmbH - initial API and implementation
    + *      Imixs Software Solutions GmbH - Project Management
      *      Ralph Soika - Software Developer
    - * 
    - *******************************************************************************/ + */ package org.imixs.workflow.engine.adminp; @@ -39,15 +38,15 @@ */ public class AdminPException extends InvalidAccessException { - private static final long serialVersionUID = 1L; - public static final String INVALID_PARAMS = "INVALID_PARAMS"; + private static final long serialVersionUID = 1L; + public static final String INVALID_PARAMS = "INVALID_PARAMS"; - public AdminPException(String aErrorCode, String message) { - super(aErrorCode, message); - } + public AdminPException(String aErrorCode, String message) { + super(aErrorCode, message); + } - public AdminPException(String aErrorCode, String message, Exception e) { - super(aErrorCode, message, e); - } + public AdminPException(String aErrorCode, String message, Exception e) { + super(aErrorCode, message, e); + } } diff --git a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/adminp/AdminPService.java b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/adminp/AdminPService.java index 56d2060df..c11be0701 100644 --- a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/adminp/AdminPService.java +++ b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/adminp/AdminPService.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *
    - *  Imixs Workflow 
    +/*  
    + *  Imixs-Workflow 
    + *  
      *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
      *  http://www.imixs.com
      *  
    @@ -22,10 +22,9 @@
      *      https://github.com/imixs/imixs-workflow
      *  
      *  Contributors:  
    - *      Imixs Software Solutions GmbH - initial API and implementation
    + *      Imixs Software Solutions GmbH - Project Management
      *      Ralph Soika - Software Developer
    - * 
    - *******************************************************************************/ + */ package org.imixs.workflow.engine.adminp; @@ -50,20 +49,22 @@ import org.imixs.workflow.exceptions.AccessDeniedException; /** - * The AmdinPService provides a mechanism to start long running jobs. Those jobs can be used to - * update workitems in a scheduled batch process. This is called a AdminP-Process. The result of a - * adminp process is documented into an log entity from type='adminp'. The job description is stored - * in the field '$WorkflowSummary'. The current startpos and maxcount are stored in the + * The AmdinPService provides a mechanism to start long running jobs. Those jobs + * can be used to update workitems in a scheduled batch process. This is called + * a AdminP-Process. The result of a adminp process is documented into an log + * entity from type='adminp'. The job description is stored in the field + * '$WorkflowSummary'. The current startpos and maxcount are stored in the * configuration entity in the properties 'numStart' 'numMaxCount' * - * The service provides methods to create and start different types of jobs. The job type is stored - * in the field 'job': + * The service provides methods to create and start different types of jobs. The + * job type is stored in the field 'job': * * RenameUserJob: * - * This job is to replace entries in the fields $WriteAccess, $ReadAccess and owner. An update - * request is stored in a adminp entity containing alll necessary informations. The service starts a - * timer instances for each update process + * This job is to replace entries in the fields $WriteAccess, $ReadAccess and + * owner. An update request is stored in a adminp entity containing alll + * necessary informations. The service starts a timer instances for each update + * process * * * LuceneRebuildIndexJob: @@ -76,292 +77,295 @@ * @author rsoika * */ -@DeclareRoles({"org.imixs.ACCESSLEVEL.MANAGERACCESS"}) +@DeclareRoles({ "org.imixs.ACCESSLEVEL.MANAGERACCESS" }) @Stateless @RunAs("org.imixs.ACCESSLEVEL.MANAGERACCESS") @LocalBean public class AdminPService { - public static final String JOB_RENAME_USER = "RENAME_USER"; - public static final String JOB_REBUILD_INDEX = "JOB_REBUILD_INDEX"; - public static final String JOB_UPGRADE = "UPGRADE"; - public static final String JOB_MIGRATION = "MIGRATION"; - private static final int DEFAULT_INTERVAL = 1; - - @Resource - SessionContext ctx; - - @Resource - javax.ejb.TimerService timerService; - - @Inject - DocumentService documentService; - - @Inject - JobHandlerUpgradeWorkitems jobHandlerUpgradeWorkitems; - - @Inject - JobHandlerRenameUser jobHandlerRenameUser; - - @Inject - JobHandlerRebuildIndex jobHandlerRebuildIndex; - - @Inject - @Any - private Instance jobHandlers; - - @Inject - @Any - private Instance plugins; - - private static Logger logger = Logger.getLogger(AdminPService.class.getName()); - - /** - * This Method starts a new TimerService for a given job. - * - * The method loads configuration from a ItemCollection (timerdescription) with the following - * informations: - * - * datstart - Date Object - * - * datstop - Date Object - * - * numInterval - Integer Object (interval in seconds) - * - * id - String - unique identifier for the schedule Service. - * - * The param 'id' should contain a unique identifier (e.g. the EJB Name) as only one scheduled - * Workflow should run inside a WorkflowInstance. If a timer with the id is already running the - * method stops this timer object first and reschedules the timer. - * - * The method throws an exception if the timerdescription contains invalid attributes or values. - * - * @throws AccessDeniedException - */ - public ItemCollection createJob(ItemCollection adminp) throws AccessDeniedException { - - // set default type - adminp.replaceItemValue("type", "adminp"); - - String jobtype = adminp.getItemValueString("job"); - - // generate new UniqueID... - adminp.replaceItemValue(WorkflowKernel.UNIQUEID, WorkflowKernel.generateUniqueID()); - - // Test interval - in minutes - int interval = adminp.getItemValueInteger("numInterval"); - if (interval <= 0) { - interval = DEFAULT_INTERVAL; - adminp.replaceItemValue("numInterval", Long.valueOf(interval)); - } + public static final String JOB_RENAME_USER = "RENAME_USER"; + public static final String JOB_REBUILD_INDEX = "JOB_REBUILD_INDEX"; + public static final String JOB_UPGRADE = "UPGRADE"; + public static final String JOB_MIGRATION = "MIGRATION"; + private static final int DEFAULT_INTERVAL = 1; + + @Resource + SessionContext ctx; + + @Resource + javax.ejb.TimerService timerService; + + @Inject + DocumentService documentService; + + @Inject + JobHandlerUpgradeWorkitems jobHandlerUpgradeWorkitems; + + @Inject + JobHandlerRenameUser jobHandlerRenameUser; + + @Inject + JobHandlerRebuildIndex jobHandlerRebuildIndex; + + @Inject + @Any + private Instance jobHandlers; + + @Inject + @Any + private Instance plugins; + + private static Logger logger = Logger.getLogger(AdminPService.class.getName()); + + /** + * This Method starts a new TimerService for a given job. + * + * The method loads configuration from a ItemCollection (timerdescription) with + * the following informations: + * + * datstart - Date Object + * + * datstop - Date Object + * + * numInterval - Integer Object (interval in seconds) + * + * id - String - unique identifier for the schedule Service. + * + * The param 'id' should contain a unique identifier (e.g. the EJB Name) as only + * one scheduled Workflow should run inside a WorkflowInstance. If a timer with + * the id is already running the method stops this timer object first and + * reschedules the timer. + * + * The method throws an exception if the timerdescription contains invalid + * attributes or values. + * + * @throws AccessDeniedException + */ + public ItemCollection createJob(ItemCollection adminp) throws AccessDeniedException { + + // set default type + adminp.replaceItemValue("type", "adminp"); + + String jobtype = adminp.getItemValueString("job"); + + // generate new UniqueID... + adminp.replaceItemValue(WorkflowKernel.UNIQUEID, WorkflowKernel.generateUniqueID()); + + // Test interval - in minutes + int interval = adminp.getItemValueInteger("numInterval"); + if (interval <= 0) { + interval = DEFAULT_INTERVAL; + adminp.replaceItemValue("numInterval", Long.valueOf(interval)); + } - // startdatum und enddatum manuell festlegen - Calendar cal = Calendar.getInstance(); - Date terminationDate = cal.getTime(); - cal.add(Calendar.HOUR, 24); - adminp.replaceItemValue("datTerminate", terminationDate); - - // save job document - adminp = documentService.save(adminp); - - // start timer... - Timer timer = timerService.createTimer(terminationDate, (60 * interval * 1000), - adminp.getItemValueString(WorkflowKernel.UNIQUEID)); - - logger.info("Job " + jobtype + " (" + timer.getInfo().toString() + ") started... "); - return adminp; - } - - /** - * Stops a running job and deletes the job configuration. - * - * @param id - * @return - * @throws AccessDeniedException - */ - public void deleteJob(String id) throws AccessDeniedException { - ItemCollection adminp = cancelTimer(id); - if (adminp != null) { - documentService.remove(adminp); - } - } - - /** - * This method processes the timeout event. The method loads the corresponding job description - * (adminp entity) and delegates the processing to the corresponding JobHandler. - * - * @param timer - */ - @Timeout - public void scheduleTimer(javax.ejb.Timer timer) { - String sTimerID = null; - - // Startzeit ermitteln - long lProfiler = System.currentTimeMillis(); - sTimerID = timer.getInfo().toString(); - // load adminp configuration from database - ItemCollection adminp = documentService.load(sTimerID); - try { - - // verify if admin entity still exists - if (adminp == null) { - // configuration was removed - so stop the timer! - logger.info("Process " + sTimerID + " was removed - timer will be canceled"); - // stop timer - timer.cancel(); - // go out! - return; - } - - String job = adminp.getItemValueString("job"); - logger.info("Job " + job + " (" + adminp.getUniqueID() + ") processing..."); - - // boolean jobfound = false; - - // find the corresponding job handler.... - JobHandler jobHandler = null; - if (job.equals(JOB_RENAME_USER)) { - jobHandler = jobHandlerRenameUser; - } - - if (job.equals(JOB_UPGRADE)) { - jobHandler = jobHandlerUpgradeWorkitems; - } - - if (job.equals(JOB_REBUILD_INDEX) || job.equals("REBUILD_LUCENE_INDEX")) { - jobHandler = jobHandlerRebuildIndex; - } - - if (jobHandler == null) { - // try to find the jobHandler by CDI ..... - jobHandler = findJobHandlerByName(job); - } - - // run the job handler... - if (jobHandler != null) { - // update status - adminp.replaceItemValue("$workflowStatus", "PROCESSING"); + // startdatum und enddatum manuell festlegen + Calendar cal = Calendar.getInstance(); + Date terminationDate = cal.getTime(); + cal.add(Calendar.HOUR, 24); + adminp.replaceItemValue("datTerminate", terminationDate); + + // save job document adminp = documentService.save(adminp); - adminp = jobHandler.run(adminp); - if (adminp.getItemValueBoolean("iscompleted")) { - timer.cancel(); - adminp.replaceItemValue("$workflowStatus", "COMPLETED"); - logger.info("Job " + job + " (" + adminp.getUniqueID() + ") completed - timer stopped"); - } else { - adminp.replaceItemValue("$workflowStatus", "WAITING"); - } + // start timer... + Timer timer = timerService.createTimer(terminationDate, (60 * interval * 1000), + adminp.getItemValueString(WorkflowKernel.UNIQUEID)); + + logger.info("Job " + jobtype + " (" + timer.getInfo().toString() + ") started... "); + return adminp; + } - } else { - logger.warning("Unable to start AdminP Job. JobHandler class '" + job + "' not defined!"); - timer.cancel(); - adminp.replaceItemValue("$workflowStatus", "FAILED"); - logger.info("Job " + adminp.getUniqueID() + " - timer stopped"); - } - - } catch (AdminPException e) { - e.printStackTrace(); - // stop timer! - timer.cancel(); - logger.severe("AdminP job '" + sTimerID + "' failed - " + e.getMessage()); - if (adminp != null) { - adminp.replaceItemValue("$workflowStatus", "FAILED"); - adminp.replaceItemValue("errormessage", e.getMessage()); - } - } finally { - // try to update the amdinp document... - try { + /** + * Stops a running job and deletes the job configuration. + * + * @param id + * @return + * @throws AccessDeniedException + */ + public void deleteJob(String id) throws AccessDeniedException { + ItemCollection adminp = cancelTimer(id); if (adminp != null) { - adminp = documentService.save(adminp); - } else { - logger.warning("Unable to update adminp job status - adminp document is null!"); + documentService.remove(adminp); } - } catch (Exception e2) { - logger.warning("Unable to update adminp job status: " + e2.getMessage()); - e2.printStackTrace(); - } } - logger.fine("...timer call finished successfull after " - + ((System.currentTimeMillis()) - lProfiler) + " ms"); - - } - - /** - * This method returns a n injected JobHandler by name or null if no JobHandler with the requested - * class name is injected. - * - * @param jobHandlerClassName - * @return jobHandler class or null if not found - */ - private JobHandler findJobHandlerByName(String jobHandlerClassName) { - if (jobHandlerClassName == null || jobHandlerClassName.isEmpty()) - return null; - - if (jobHandlers == null || !jobHandlers.iterator().hasNext()) { - logger.finest("......no CDI jobHandlers injected"); - return null; - } - // iterate over all injected JobHandlers.... - for (JobHandler jobHandler : this.jobHandlers) { - if (jobHandler.getClass().getName().equals(jobHandlerClassName)) { - logger.finest("......CDI JobHandler '" + jobHandlerClassName + "' successful injected"); - return jobHandler; - } - } + /** + * This method processes the timeout event. The method loads the corresponding + * job description (adminp entity) and delegates the processing to the + * corresponding JobHandler. + * + * @param timer + */ + @Timeout + public void scheduleTimer(javax.ejb.Timer timer) { + String sTimerID = null; + + // Startzeit ermitteln + long lProfiler = System.currentTimeMillis(); + sTimerID = timer.getInfo().toString(); + // load adminp configuration from database + ItemCollection adminp = documentService.load(sTimerID); + try { + + // verify if admin entity still exists + if (adminp == null) { + // configuration was removed - so stop the timer! + logger.info("Process " + sTimerID + " was removed - timer will be canceled"); + // stop timer + timer.cancel(); + // go out! + return; + } + + String job = adminp.getItemValueString("job"); + logger.info("Job " + job + " (" + adminp.getUniqueID() + ") processing..."); + + // boolean jobfound = false; + + // find the corresponding job handler.... + JobHandler jobHandler = null; + if (job.equals(JOB_RENAME_USER)) { + jobHandler = jobHandlerRenameUser; + } + + if (job.equals(JOB_UPGRADE)) { + jobHandler = jobHandlerUpgradeWorkitems; + } + + if (job.equals(JOB_REBUILD_INDEX) || job.equals("REBUILD_LUCENE_INDEX")) { + jobHandler = jobHandlerRebuildIndex; + } + + if (jobHandler == null) { + // try to find the jobHandler by CDI ..... + jobHandler = findJobHandlerByName(job); + } + + // run the job handler... + if (jobHandler != null) { + // update status + adminp.replaceItemValue("$workflowStatus", "PROCESSING"); + adminp = documentService.save(adminp); + + adminp = jobHandler.run(adminp); + if (adminp.getItemValueBoolean("iscompleted")) { + timer.cancel(); + adminp.replaceItemValue("$workflowStatus", "COMPLETED"); + logger.info("Job " + job + " (" + adminp.getUniqueID() + ") completed - timer stopped"); + } else { + adminp.replaceItemValue("$workflowStatus", "WAITING"); + } + + } else { + logger.warning("Unable to start AdminP Job. JobHandler class '" + job + "' not defined!"); + timer.cancel(); + adminp.replaceItemValue("$workflowStatus", "FAILED"); + logger.info("Job " + adminp.getUniqueID() + " - timer stopped"); + } + + } catch (AdminPException e) { + e.printStackTrace(); + // stop timer! + timer.cancel(); + logger.severe("AdminP job '" + sTimerID + "' failed - " + e.getMessage()); + if (adminp != null) { + adminp.replaceItemValue("$workflowStatus", "FAILED"); + adminp.replaceItemValue("errormessage", e.getMessage()); + } + } finally { + // try to update the amdinp document... + try { + if (adminp != null) { + adminp = documentService.save(adminp); + } else { + logger.warning("Unable to update adminp job status - adminp document is null!"); + } + } catch (Exception e2) { + logger.warning("Unable to update adminp job status: " + e2.getMessage()); + e2.printStackTrace(); + } + } + + logger.fine("...timer call finished successfull after " + ((System.currentTimeMillis()) - lProfiler) + " ms"); - return null; - } - - /** - * This method cancels a timer by ID. If a timer configuration exits, the method returns the - * document entity. - * - * @param id - * @return - */ - private ItemCollection cancelTimer(String id) { - - logger.finest("......cancelTimer - id:" + id + " ...."); - ItemCollection adminp = documentService.load(id); - if (adminp == null) { - logger.warning("failed to load timer data ID:" + id + " "); } - // try to cancel an existing timer - Timer timer = this.findTimer(id); - if (timer != null) { - timer.cancel(); - logger.info("cancelTimer - id:" + id + " successful."); - } else { - logger.info("cancelTimer - id:" + id + " failed - timer does no longer exist."); + + /** + * This method returns a n injected JobHandler by name or null if no JobHandler + * with the requested class name is injected. + * + * @param jobHandlerClassName + * @return jobHandler class or null if not found + */ + private JobHandler findJobHandlerByName(String jobHandlerClassName) { + if (jobHandlerClassName == null || jobHandlerClassName.isEmpty()) + return null; + + if (jobHandlers == null || !jobHandlers.iterator().hasNext()) { + logger.finest("......no CDI jobHandlers injected"); + return null; + } + // iterate over all injected JobHandlers.... + for (JobHandler jobHandler : this.jobHandlers) { + if (jobHandler.getClass().getName().equals(jobHandlerClassName)) { + logger.finest("......CDI JobHandler '" + jobHandlerClassName + "' successful injected"); + return jobHandler; + } + } + + return null; } - if (adminp != null) { - adminp.replaceItemValue("txtTimerStatus", "Stopped"); + + /** + * This method cancels a timer by ID. If a timer configuration exits, the method + * returns the document entity. + * + * @param id + * @return + */ + private ItemCollection cancelTimer(String id) { + + logger.finest("......cancelTimer - id:" + id + " ...."); + ItemCollection adminp = documentService.load(id); + if (adminp == null) { + logger.warning("failed to load timer data ID:" + id + " "); + } + // try to cancel an existing timer + Timer timer = this.findTimer(id); + if (timer != null) { + timer.cancel(); + logger.info("cancelTimer - id:" + id + " successful."); + } else { + logger.info("cancelTimer - id:" + id + " failed - timer does no longer exist."); + } + if (adminp != null) { + adminp.replaceItemValue("txtTimerStatus", "Stopped"); + } + return adminp; } - return adminp; - } - - /** - * This method returns a timer for a corresponding id if such a timer object exists. - * - * @param id - * @return Timer - * @throws Exception - */ - private Timer findTimer(String id) { - if (id == null || id.isEmpty()) - return null; - - for (Object obj : timerService.getTimers()) { - Timer timer = (javax.ejb.Timer) obj; - if (timer.getInfo() instanceof String) { - String timerid = timer.getInfo().toString(); - if (id.equals(timerid)) { - return timer; + + /** + * This method returns a timer for a corresponding id if such a timer object + * exists. + * + * @param id + * @return Timer + * @throws Exception + */ + private Timer findTimer(String id) { + if (id == null || id.isEmpty()) + return null; + + for (Object obj : timerService.getTimers()) { + Timer timer = (javax.ejb.Timer) obj; + if (timer.getInfo() instanceof String) { + String timerid = timer.getInfo().toString(); + if (id.equals(timerid)) { + return timer; + } + } } - } + logger.warning("findTimer - id:" + id + " does no longer exist."); + return null; } - logger.warning("findTimer - id:" + id + " does no longer exist."); - return null; - } } diff --git a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/adminp/JobHandler.java b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/adminp/JobHandler.java index c1c11d7c2..ba288b9ab 100644 --- a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/adminp/JobHandler.java +++ b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/adminp/JobHandler.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *
    - *  Imixs Workflow 
    +/*  
    + *  Imixs-Workflow 
    + *  
      *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
      *  http://www.imixs.com
      *  
    @@ -22,10 +22,9 @@
      *      https://github.com/imixs/imixs-workflow
      *  
      *  Contributors:  
    - *      Imixs Software Solutions GmbH - initial API and implementation
    + *      Imixs Software Solutions GmbH - Project Management
      *      Ralph Soika - Software Developer
    - * 
    - *******************************************************************************/ + */ package org.imixs.workflow.engine.adminp; @@ -33,31 +32,32 @@ public interface JobHandler { + public final static String ISCOMPLETED = "iscompleted"; - public final static String ISCOMPLETED = "iscompleted"; - - /** - * Called by the AdminPService. The JobHandler returns the job description with pre defined fields - * to signal the status. - * - * The AdminPService will terminate the job in cases the job is complete. Otherwise the - * AdminPServcie will wait for the next timeout. - *

    - * Fields: - *

      - *
    • type - fixed to value 'adminp'
    • - *
    • job - the job type/name, defined by handler
    • - *
    • $WorkflowStatus - status controlled by AdminP Service
    • - *
    • $WorkflowSummary - summary of job description
    • - *
    • isCompleted - boolean indicates if job is completed - controlled by job handler
    • - *
    - * - * The AdminPService will not call the JobHandler if the job description field 'isCompleted==true' - * - * A JobHandler may throw a AdminPException if something went wrong. - * - * @param job description - * @return updated job description - */ - public ItemCollection run(ItemCollection job) throws AdminPException; + /** + * Called by the AdminPService. The JobHandler returns the job description with + * pre defined fields to signal the status. + * + * The AdminPService will terminate the job in cases the job is complete. + * Otherwise the AdminPServcie will wait for the next timeout. + *

    + * Fields: + *

      + *
    • type - fixed to value 'adminp'
    • + *
    • job - the job type/name, defined by handler
    • + *
    • $WorkflowStatus - status controlled by AdminP Service
    • + *
    • $WorkflowSummary - summary of job description
    • + *
    • isCompleted - boolean indicates if job is completed - controlled by job + * handler
    • + *
    + * + * The AdminPService will not call the JobHandler if the job description field + * 'isCompleted==true' + * + * A JobHandler may throw a AdminPException if something went wrong. + * + * @param job description + * @return updated job description + */ + public ItemCollection run(ItemCollection job) throws AdminPException; } diff --git a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/adminp/JobHandlerRebuildIndex.java b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/adminp/JobHandlerRebuildIndex.java index 1da83c03a..c1695cfde 100644 --- a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/adminp/JobHandlerRebuildIndex.java +++ b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/adminp/JobHandlerRebuildIndex.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *
    - *  Imixs Workflow 
    +/*  
    + *  Imixs-Workflow 
    + *  
      *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
      *  http://www.imixs.com
      *  
    @@ -22,10 +22,9 @@
      *      https://github.com/imixs/imixs-workflow
      *  
      *  Contributors:  
    - *      Imixs Software Solutions GmbH - initial API and implementation
    + *      Imixs Software Solutions GmbH - Project Management
      *      Ralph Soika - Software Developer
    - * 
    - *******************************************************************************/ + */ package org.imixs.workflow.engine.adminp; @@ -61,229 +60,228 @@ * @author rsoika * */ -@DeclareRoles({"org.imixs.ACCESSLEVEL.MANAGERACCESS"}) +@DeclareRoles({ "org.imixs.ACCESSLEVEL.MANAGERACCESS" }) @Stateless @RunAs("org.imixs.ACCESSLEVEL.MANAGERACCESS") @LocalBean public class JobHandlerRebuildIndex implements JobHandler { + private static final String BLOCK_SIZE_DEFAULT = "500"; + private static final String TIMEOUT_DEFAULT = "120"; + @Inject + @ConfigProperty(name = "lucene.rebuild.block_size", defaultValue = BLOCK_SIZE_DEFAULT) + int block_size; - private static final String BLOCK_SIZE_DEFAULT = "500"; - private static final String TIMEOUT_DEFAULT = "120"; + @Inject + @ConfigProperty(name = "lucene.rebuild.time_out", defaultValue = TIMEOUT_DEFAULT) + int time_out; - @Inject - @ConfigProperty(name = "lucene.rebuild.block_size", defaultValue = BLOCK_SIZE_DEFAULT) - int block_size; + private static final int READ_AHEAD = 32; + public final static String ITEM_SYNCPOINT = "syncpoint"; + public final static String ITEM_SYNCDATE = "syncdate"; + public static final String SNAPSHOT_TYPE_PRAFIX = "snapshot-"; - @Inject - @ConfigProperty(name = "lucene.rebuild.time_out", defaultValue = TIMEOUT_DEFAULT) - int time_out; + @PersistenceContext(unitName = "org.imixs.workflow.jpa") + private EntityManager manager; - private static final int READ_AHEAD = 32; - public final static String ITEM_SYNCPOINT = "syncpoint"; - public final static String ITEM_SYNCDATE = "syncdate"; - public static final String SNAPSHOT_TYPE_PRAFIX = "snapshot-"; + @Inject + UpdateService updateService; - @PersistenceContext(unitName = "org.imixs.workflow.jpa") - private EntityManager manager; + private static Logger logger = Logger.getLogger(JobHandlerRebuildIndex.class.getName()); - @Inject - UpdateService updateService; + /** + * This method runs the RebuildLuceneIndexJob. The job starts at creation date + * 1970/01/01 and reads single documents in sequence. + *

    + * After the run method is finished, the properties numIndex, numUpdates and + * numProcessed are updated. + *

    + * The method runs in an isolated new transaction because the method flushes the + * local persistence manager. + * + * @param adminp + * @return true when finished + * @throws AccessDeniedException + * @throws PluginException + */ - private static Logger logger = Logger.getLogger(JobHandlerRebuildIndex.class.getName()); + @Override + @TransactionAttribute(value = TransactionAttributeType.REQUIRES_NEW) + public ItemCollection run(ItemCollection adminp) throws AdminPException { + long lProfiler = System.currentTimeMillis(); + long syncPoint = adminp.getItemValueLong("_syncpoint"); + int totalCount = adminp.getItemValueInteger("numUpdates"); + int blockCount = 0; - /** - * This method runs the RebuildLuceneIndexJob. The job starts at creation date 1970/01/01 and - * reads single documents in sequence. - *

    - * After the run method is finished, the properties numIndex, numUpdates and numProcessed are - * updated. - *

    - * The method runs in an isolated new transaction because the method flushes the local persistence - * manager. - * - * @param adminp - * @return true when finished - * @throws AccessDeniedException - * @throws PluginException - */ + // read blocksize and timeout.... + logger.info("...Job " + AdminPService.JOB_REBUILD_INDEX + " (" + adminp.getUniqueID() + + ") - lucene.rebuild.block_size=" + block_size); + logger.info("...Job " + AdminPService.JOB_REBUILD_INDEX + " (" + adminp.getUniqueID() + + ") - lucene.rebuild.time_out=" + time_out); - @Override - @TransactionAttribute(value = TransactionAttributeType.REQUIRES_NEW) - public ItemCollection run(ItemCollection adminp) throws AdminPException { - long lProfiler = System.currentTimeMillis(); - long syncPoint = adminp.getItemValueLong("_syncpoint"); - int totalCount = adminp.getItemValueInteger("numUpdates"); - int blockCount = 0; + try { + while (true) { + List resultList = new ArrayList(); + List documents = findNextDocumentsBySyncPoint(syncPoint); - // read blocksize and timeout.... - logger.info("...Job " + AdminPService.JOB_REBUILD_INDEX + " (" + adminp.getUniqueID() - + ") - lucene.rebuild.block_size=" + block_size); - logger.info("...Job " + AdminPService.JOB_REBUILD_INDEX + " (" + adminp.getUniqueID() - + ") - lucene.rebuild.time_out=" + time_out); + if (documents != null && documents.size() > 0) { + for (Document doc : documents) { + // update syncpoint + syncPoint = doc.getCreated().getTimeInMillis(); + try { + resultList.add(new ItemCollection(doc.getData())); + } catch (InvalidAccessException e) { + logger.warning("...unable to index document '" + doc.getId() + "' " + e.getMessage()); + } + // detach object! + manager.detach(doc); - try { - while (true) { - List resultList = new ArrayList(); - List documents = findNextDocumentsBySyncPoint(syncPoint); + } - if (documents != null && documents.size() > 0) { - for (Document doc : documents) { - // update syncpoint - syncPoint = doc.getCreated().getTimeInMillis(); - try { - resultList.add(new ItemCollection(doc.getData())); - } catch (InvalidAccessException e) { - logger.warning("...unable to index document '" + doc.getId() + "' " + e.getMessage()); - } - // detach object! - manager.detach(doc); + // update the index + updateService.updateIndex(resultList); + manager.flush(); - } + // update count + totalCount += resultList.size(); + blockCount += resultList.size(); + if (blockCount >= block_size) { + long time = (System.currentTimeMillis() - lProfiler) / 1000; + if (time == 0) { + time = 1; + } + logger.info("...Job " + AdminPService.JOB_REBUILD_INDEX + " (" + adminp.getUniqueID() + + ") - ..." + totalCount + " documents indexed in " + time + " sec. ... "); + blockCount = 0; + } + } else { + // no more documents + manager.flush(); + break; + } - // update the index - updateService.updateIndex(resultList); - manager.flush(); + // suspend job? + long time = (System.currentTimeMillis() - lProfiler) / 1000; + if (time == 0) { + time = 1; + } + if (time > time_out) { // suspend after 2 mintues (default 120).... + logger.info("...Job " + AdminPService.JOB_REBUILD_INDEX + " (" + adminp.getUniqueID() + + ") - suspended: " + totalCount + " documents indexed in " + time + " sec. "); - // update count - totalCount += resultList.size(); - blockCount += resultList.size(); - if (blockCount >= block_size) { - long time = (System.currentTimeMillis() - lProfiler) / 1000; - if (time == 0) { - time = 1; + adminp.replaceItemValue("_syncpoint", syncPoint); + adminp.replaceItemValue(JobHandler.ISCOMPLETED, false); + adminp.replaceItemValue("numUpdates", totalCount); + adminp.replaceItemValue("numProcessed", totalCount); + adminp.replaceItemValue("numLastCount", 0); + return adminp; + } } - logger.info("...Job " + AdminPService.JOB_REBUILD_INDEX + " (" + adminp.getUniqueID() - + ") - ..." + totalCount + " documents indexed in " + time + " sec. ... "); - blockCount = 0; - } - } else { - // no more documents - manager.flush(); - break; + } catch (Exception e) { + // print exception and stop job + logger.severe("...Job " + AdminPService.JOB_REBUILD_INDEX + " (" + adminp.getUniqueID() + ") - failed - " + + e.getMessage() + " last syncpoint " + syncPoint + " - " + totalCount + + " documents reindexed...."); + e.printStackTrace(); + adminp.replaceItemValue(JobHandler.ISCOMPLETED, false); + // update syncpoint + Date syncDate = new Date(syncPoint); + adminp.replaceItemValue("error", e.getMessage()); + adminp.replaceItemValue(ITEM_SYNCPOINT, syncPoint); + adminp.replaceItemValue(ITEM_SYNCDATE, syncDate); + adminp.replaceItemValue("numUpdates", totalCount); + adminp.replaceItemValue("numProcessed", totalCount); + adminp.replaceItemValue("numLastCount", 0); + return adminp; } - // suspend job? + // completed long time = (System.currentTimeMillis() - lProfiler) / 1000; if (time == 0) { - time = 1; + time = 1; } - if (time > time_out) { // suspend after 2 mintues (default 120).... - logger.info("...Job " + AdminPService.JOB_REBUILD_INDEX + " (" + adminp.getUniqueID() - + ") - suspended: " + totalCount + " documents indexed in " + time + " sec. "); + logger.info("...Job " + AdminPService.JOB_REBUILD_INDEX + " (" + adminp.getUniqueID() + ") - Finished: " + + totalCount + " documents indexed in " + time + " sec. "); - adminp.replaceItemValue("_syncpoint", syncPoint); - adminp.replaceItemValue(JobHandler.ISCOMPLETED, false); - adminp.replaceItemValue("numUpdates", totalCount); - adminp.replaceItemValue("numProcessed", totalCount); - adminp.replaceItemValue("numLastCount", 0); - return adminp; - } - } - } catch (Exception e) { - // print exception and stop job - logger.severe("...Job " + AdminPService.JOB_REBUILD_INDEX + " (" + adminp.getUniqueID() - + ") - failed - " + e.getMessage() + " last syncpoint " + syncPoint + " - " + totalCount - + " documents reindexed...."); - e.printStackTrace(); - adminp.replaceItemValue(JobHandler.ISCOMPLETED, false); - // update syncpoint - Date syncDate = new Date(syncPoint); - adminp.replaceItemValue("error", e.getMessage()); - adminp.replaceItemValue(ITEM_SYNCPOINT, syncPoint); - adminp.replaceItemValue(ITEM_SYNCDATE, syncDate); - adminp.replaceItemValue("numUpdates", totalCount); - adminp.replaceItemValue("numProcessed", totalCount); - adminp.replaceItemValue("numLastCount", 0); - return adminp; - } + adminp.replaceItemValue(JobHandler.ISCOMPLETED, true); + adminp.replaceItemValue("numUpdates", totalCount); + adminp.replaceItemValue("numProcessed", totalCount); + adminp.replaceItemValue("numLastCount", 0); + return adminp; - // completed - long time = (System.currentTimeMillis() - lProfiler) / 1000; - if (time == 0) { - time = 1; } - logger.info("...Job " + AdminPService.JOB_REBUILD_INDEX + " (" + adminp.getUniqueID() - + ") - Finished: " + totalCount + " documents indexed in " + time + " sec. "); - - adminp.replaceItemValue(JobHandler.ISCOMPLETED, true); - adminp.replaceItemValue("numUpdates", totalCount); - adminp.replaceItemValue("numProcessed", totalCount); - adminp.replaceItemValue("numLastCount", 0); - return adminp; - } + /** + * Loads the next documents by a given symcpoint (timestamp in milis) compared + * with the created timestamp of a document entity. + *

    + * It is possible that more than one document entities have the same created + * timestamp. For that reason the method returns all documents with the same + * timestamp in a collection. + * + * @param lSyncpoint + * @return a list of documents with the same creation timestamp after the given + * syncpoint. Returns null in case no more documents were found. + */ + @SuppressWarnings("unchecked") + private List findNextDocumentsBySyncPoint(long lSyncpoint) { - /** - * Loads the next documents by a given symcpoint (timestamp in milis) compared with the created - * timestamp of a document entity. - *

    - * It is possible that more than one document entities have the same created timestamp. For that - * reason the method returns all documents with the same timestamp in a collection. - * - * @param lSyncpoint - * @return a list of documents with the same creation timestamp after the given syncpoint. Returns - * null in case no more documents were found. - */ - @SuppressWarnings("unchecked") - private List findNextDocumentsBySyncPoint(long lSyncpoint) { + Date syncpoint = new Date(lSyncpoint); + // ISO date time format: '2016-08-25 01:23:46.0', + DateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); + String query = "SELECT document FROM Document AS document "; + query += " WHERE document.created > '" + isoFormat.format(syncpoint) + "'"; + query += " AND NOT document.type LIKE '" + SNAPSHOT_TYPE_PRAFIX + "%' "; + query += " AND NOT document.type LIKE 'workitemlob%' "; + query += " AND document.type != 'event' "; + query += " ORDER BY document.created ASC"; + Query q = manager.createQuery(query); + q.setFirstResult(0); + q.setMaxResults(READ_AHEAD); + List documentList = q.getResultList(); + if (documentList != null && documentList.size() > 0) { + Document lastDocument = null; + Document nextToLastDocument = null; - Date syncpoint = new Date(lSyncpoint); - // ISO date time format: '2016-08-25 01:23:46.0', - DateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); - String query = "SELECT document FROM Document AS document "; - query += " WHERE document.created > '" + isoFormat.format(syncpoint) + "'"; - query += " AND NOT document.type LIKE '" + SNAPSHOT_TYPE_PRAFIX + "%' "; - query += " AND NOT document.type LIKE 'workitemlob%' "; - query += " AND document.type != 'event' "; - query += " ORDER BY document.created ASC"; - Query q = manager.createQuery(query); - q.setFirstResult(0); - q.setMaxResults(READ_AHEAD); - List documentList = q.getResultList(); - if (documentList != null && documentList.size() > 0) { - Document lastDocument = null; - Document nextToLastDocument = null; + // test if we have two documents with the same creation date (in seldom cases + // possible) + if (documentList.size() == READ_AHEAD) { + lastDocument = documentList.get(READ_AHEAD - 1); + nextToLastDocument = documentList.get(READ_AHEAD - 2); + // now test if we have more than one document with the same timestamp at the end + // of the list + if (lastDocument != null && nextToLastDocument != null + && lastDocument.getCreated().equals(nextToLastDocument.getCreated())) { + logger.finest("......there are more than one document with the same creation timestamp!"); + // lets build a new collection with the duplicated creation timestamp + syncpoint = new Date(lastDocument.getCreated().getTimeInMillis()); + query = "SELECT document FROM Document AS document "; + query += " WHERE document.created = '" + isoFormat.format(syncpoint) + "'"; + query += " AND NOT document.type LIKE '" + SNAPSHOT_TYPE_PRAFIX + "%' "; + query += " AND NOT document.type LIKE 'workitemlob%' "; + query += " AND document.type != 'event' "; + query += " ORDER BY document.created ASC"; + q = manager.createQuery(query); + q.setFirstResult(0); + q.setMaxResults(block_size); + documentList.addAll(q.getResultList()); + return documentList; - // test if we have two documents with the same creation date (in seldom cases - // possible) - if (documentList.size() == READ_AHEAD) { - lastDocument = documentList.get(READ_AHEAD - 1); - nextToLastDocument = documentList.get(READ_AHEAD - 2); - // now test if we have more than one document with the same timestamp at the end - // of the list - if (lastDocument != null && nextToLastDocument != null - && lastDocument.getCreated().equals(nextToLastDocument.getCreated())) { - logger.finest("......there are more than one document with the same creation timestamp!"); - // lets build a new collection with the duplicated creation timestamp - syncpoint = new Date(lastDocument.getCreated().getTimeInMillis()); - query = "SELECT document FROM Document AS document "; - query += " WHERE document.created = '" + isoFormat.format(syncpoint) + "'"; - query += " AND NOT document.type LIKE '" + SNAPSHOT_TYPE_PRAFIX + "%' "; - query += " AND NOT document.type LIKE 'workitemlob%' "; - query += " AND document.type != 'event' "; - query += " ORDER BY document.created ASC"; - q = manager.createQuery(query); - q.setFirstResult(0); - q.setMaxResults(block_size); - documentList.addAll(q.getResultList()); - return documentList; - - } else { - // we found exactly READ_AHEAD documents and the last two ones are not equal - // so we drop the last one of the result to avoid overlapping duplicates in the - // next block. - documentList.remove(lastDocument); - manager.detach(lastDocument); - return documentList; + } else { + // we found exactly READ_AHEAD documents and the last two ones are not equal + // so we drop the last one of the result to avoid overlapping duplicates in the + // next block. + documentList.remove(lastDocument); + manager.detach(lastDocument); + return documentList; + } + } else { + // we are at the end of the list + return documentList; + } } - } else { - // we are at the end of the list - return documentList; - } + return null; } - return null; - } } diff --git a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/adminp/JobHandlerRenameUser.java b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/adminp/JobHandlerRenameUser.java index 4ae8817ad..d23c3989b 100644 --- a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/adminp/JobHandlerRenameUser.java +++ b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/adminp/JobHandlerRenameUser.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *

    - *  Imixs Workflow 
    +/*  
    + *  Imixs-Workflow 
    + *  
      *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
      *  http://www.imixs.com
      *  
    @@ -22,10 +22,9 @@
      *      https://github.com/imixs/imixs-workflow
      *  
      *  Contributors:  
    - *      Imixs Software Solutions GmbH - initial API and implementation
    + *      Imixs Software Solutions GmbH - Project Management
      *      Ralph Soika - Software Developer
    - * 
    - *******************************************************************************/ + */ package org.imixs.workflow.engine.adminp; @@ -54,14 +53,14 @@ import org.imixs.workflow.exceptions.QueryException; /** - * The JobHandlerRenameUser updates the name fields of workitems. A name can be replaced or added. - * The following job attributes are expected: + * The JobHandlerRenameUser updates the name fields of workitems. A name can be + * replaced or added. The following job attributes are expected: * *
      *
    • namFrom - source userID
    • *
    • namTo - target userID
    • - *
    • keyReplace - if true the source UserID will be replaced with the target UserID, otherwise the - * target userID will be added
    • + *
    • keyReplace - if true the source UserID will be replaced with the target + * UserID, otherwise the target userID will be added
    • *
    * * The jobHandler only processes workitems from the type @@ -80,229 +79,229 @@ *
  • namcreator (deprecated)
  • * * - * The attributes $creator can not be replaced. Only an additional userID is placed here. + * The attributes $creator can not be replaced. Only an additional userID is + * placed here. * * @see AdminPService AdminPService for details * @version 1.0 * */ -@DeclareRoles({"org.imixs.ACCESSLEVEL.MANAGERACCESS"}) +@DeclareRoles({ "org.imixs.ACCESSLEVEL.MANAGERACCESS" }) @Stateless @RunAs("org.imixs.ACCESSLEVEL.MANAGERACCESS") @LocalBean public class JobHandlerRenameUser implements JobHandler { - @Resource - SessionContext ctx; - - @Inject - DocumentService documentService; - - - private static final int DEFAULT_COUNT = 100; - private static Logger logger = Logger.getLogger(JobHandlerRenameUser.class.getName()); - - /** - * This method creates a new AdminP Job to rename userId in workitems. - * - * @throws QueryException - * @throws AccessDeniedException - */ - @Override - public ItemCollection run(ItemCollection adminp) throws AdminPException { - long lProfiler = System.currentTimeMillis(); - int iIndex = adminp.getItemValueInteger("numIndex"); - int iBlockSize = adminp.getItemValueInteger("numBlockSize"); - if (iBlockSize <= 0) { - iBlockSize = DEFAULT_COUNT; - adminp.replaceItemValue("numBlockSize", iBlockSize); - } - Date datFilterFrom = adminp.getItemValueDate("datfrom"); - Date datFilterTo = adminp.getItemValueDate("datto"); - - int iUpdates = adminp.getItemValueInteger("numUpdates"); - int iProcessed = adminp.getItemValueInteger("numProcessed"); - String fromUserID = adminp.getItemValueString("namFrom").trim(); - String toUserID = adminp.getItemValueString("namTo").trim(); - boolean replace = adminp.getItemValueBoolean("keyReplace"); - - if (fromUserID.isEmpty() || toUserID.isEmpty()) { - throw new AdminPException(AdminPException.INVALID_PARAMS, - "Invalid job configuration - attributes 'namFrom' or 'namTo' are empty."); - } + @Resource + SessionContext ctx; - // update $WorkflowSummary - String summary = "Rename: " + fromUserID + " -> " + toUserID + " (replace=" + replace + ")"; - logger.info(summary); + @Inject + DocumentService documentService; - adminp.replaceItemValue("$WorkflowSummary", summary); + private static final int DEFAULT_COUNT = 100; + private static Logger logger = Logger.getLogger(JobHandlerRenameUser.class.getName()); - // build search query + /** + * This method creates a new AdminP Job to rename userId in workitems. + * + * @throws QueryException + * @throws AccessDeniedException + */ + @Override + public ItemCollection run(ItemCollection adminp) throws AdminPException { + long lProfiler = System.currentTimeMillis(); + int iIndex = adminp.getItemValueInteger("numIndex"); + int iBlockSize = adminp.getItemValueInteger("numBlockSize"); + if (iBlockSize <= 0) { + iBlockSize = DEFAULT_COUNT; + adminp.replaceItemValue("numBlockSize", iBlockSize); + } + Date datFilterFrom = adminp.getItemValueDate("datfrom"); + Date datFilterTo = adminp.getItemValueDate("datto"); + + int iUpdates = adminp.getItemValueInteger("numUpdates"); + int iProcessed = adminp.getItemValueInteger("numProcessed"); + String fromUserID = adminp.getItemValueString("namFrom").trim(); + String toUserID = adminp.getItemValueString("namTo").trim(); + boolean replace = adminp.getItemValueBoolean("keyReplace"); + + if (fromUserID.isEmpty() || toUserID.isEmpty()) { + throw new AdminPException(AdminPException.INVALID_PARAMS, + "Invalid job configuration - attributes 'namFrom' or 'namTo' are empty."); + } - String typeFilter = adminp.getItemValueString("typelist"); - if (typeFilter.isEmpty()) { - // set default type - typeFilter = "workitem"; - } + // update $WorkflowSummary + String summary = "Rename: " + fromUserID + " -> " + toUserID + " (replace=" + replace + ")"; + logger.info(summary); - String sQuery = "("; - // convert type list into comma separated list - List typeList = Arrays.asList(typeFilter.split("\\s*,\\s*")); - for (String aValue : typeList) { - sQuery += "type:\"" + aValue.trim() + "\" OR "; - } - sQuery = sQuery.substring(0, sQuery.length() - 4); - sQuery += ")"; - // !! We do ignore the creator!! - see issue #350 - sQuery += " AND ($writeaccess:\"" + fromUserID + "\" OR $readaccess:\"" + fromUserID - + "\" OR owner:\"" + fromUserID + "\" OR namowner:\"" + fromUserID + "\")"; - - if (datFilterFrom != null && datFilterTo != null) { - SimpleDateFormat luceneFormat = new SimpleDateFormat("yyyyMMdd"); - sQuery += " AND ($created:[" + luceneFormat.format(datFilterFrom) + " TO " - + luceneFormat.format(datFilterTo) + "])"; - } + adminp.replaceItemValue("$WorkflowSummary", summary); - adminp.replaceItemValue("txtQuery", sQuery); + // build search query - Collection col; - try { - // ASC sorting is important here! - col = documentService.find(sQuery, iBlockSize, iIndex, "$created", false); - } catch (QueryException e) { - throw new InvalidAccessException(InvalidAccessException.INVALID_ID, e.getMessage(), e); - } - int colSize = col.size(); - // check all selected documents - for (ItemCollection entity : col) { - iProcessed++; - // call from new instance because of transaction new... - // see: http://blog.imixs.org/?p=155 - // see: https://www.java.net/node/705304 - boolean result = ctx.getBusinessObject(JobHandlerRenameUser.class) - .updateWorkitemUserIds(entity, fromUserID, toUserID, replace); - if (result == true) { - // inc counter - iUpdates++; - } - } + String typeFilter = adminp.getItemValueString("typelist"); + if (typeFilter.isEmpty()) { + // set default type + typeFilter = "workitem"; + } - // adjust start pos and update count - adminp.replaceItemValue("numUpdates", iUpdates); - adminp.replaceItemValue("numProcessed", iProcessed); - adminp.replaceItemValue("numLastCount", col.size()); - iIndex++; - adminp.replaceItemValue("numIndex", iIndex); + String sQuery = "("; + // convert type list into comma separated list + List typeList = Arrays.asList(typeFilter.split("\\s*,\\s*")); + for (String aValue : typeList) { + sQuery += "type:\"" + aValue.trim() + "\" OR "; + } + sQuery = sQuery.substring(0, sQuery.length() - 4); + sQuery += ")"; + // !! We do ignore the creator!! - see issue #350 + sQuery += " AND ($writeaccess:\"" + fromUserID + "\" OR $readaccess:\"" + fromUserID + "\" OR owner:\"" + + fromUserID + "\" OR namowner:\"" + fromUserID + "\")"; + + if (datFilterFrom != null && datFilterTo != null) { + SimpleDateFormat luceneFormat = new SimpleDateFormat("yyyyMMdd"); + sQuery += " AND ($created:[" + luceneFormat.format(datFilterFrom) + " TO " + + luceneFormat.format(datFilterTo) + "])"; + } - long time = (System.currentTimeMillis() - lProfiler) / 1000; - if (time == 0) { - time = 1; - } + adminp.replaceItemValue("txtQuery", sQuery); - logger.info("Job " + AdminPService.JOB_RENAME_USER + " (" + adminp.getUniqueID() + ") - " - + colSize + " documents processed, " + iUpdates + " updates in " + time - + " sec. (in total: " + iProcessed + " processed, " + iUpdates + " updates)"); + Collection col; + try { + // ASC sorting is important here! + col = documentService.find(sQuery, iBlockSize, iIndex, "$created", false); + } catch (QueryException e) { + throw new InvalidAccessException(InvalidAccessException.INVALID_ID, e.getMessage(), e); + } + int colSize = col.size(); + // check all selected documents + for (ItemCollection entity : col) { + iProcessed++; + // call from new instance because of transaction new... + // see: http://blog.imixs.org/?p=155 + // see: https://www.java.net/node/705304 + boolean result = ctx.getBusinessObject(JobHandlerRenameUser.class).updateWorkitemUserIds(entity, fromUserID, + toUserID, replace); + if (result == true) { + // inc counter + iUpdates++; + } + } - // if colSize " + to + " (replace=" + replace + ")"; - entity.appendItemValue("txtAdminpLog", new Date(System.currentTimeMillis()) + " " + summary); - documentService.save(entity); - logger.finest("......updated: " + entity.getItemValueString(WorkflowKernel.UNIQUEID)); - } - return bUpdate; - } - - /** - * Update the values of a single list. - * - * @param list - * @param from - * @param to - * @param replace - * @return true if the list was modified. - */ - @SuppressWarnings({"rawtypes", "unchecked"}) - private boolean updateList(List list, String from, String to, boolean replace) { - - boolean update = false; - - if (list == null || list.isEmpty()) - return false; - - if (list.contains(from)) { - - if (to != null && !"".equals(to) && !list.contains(to)) { - list.add(to); - update = true; - } - - if (replace) { - while (list.contains(from)) { - list.remove(from); - update = true; - } - } + if (updateList(entity.getItemValue(OwnerPlugin.OWNER), from, to, replace)) + bUpdate = true; + + // support deprecated field + if (updateList(entity.getItemValue("namOwner"), from, to, replace)) + bUpdate = true; + + // !! We do not replace the creator!! - see issue #350 + /* + * if (updateList(entity.getItemValue("$Creator"), from, to, false)) bUpdate = + * true; + */ + if (bUpdate) { + // create log entry.... + String summary = "Rename: " + from + " -> " + to + " (replace=" + replace + ")"; + entity.appendItemValue("txtAdminpLog", new Date(System.currentTimeMillis()) + " " + summary); + documentService.save(entity); + logger.finest("......updated: " + entity.getItemValueString(WorkflowKernel.UNIQUEID)); + } + return bUpdate; } - return update; - } + /** + * Update the values of a single list. + * + * @param list + * @param from + * @param to + * @param replace + * @return true if the list was modified. + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + private boolean updateList(List list, String from, String to, boolean replace) { + + boolean update = false; + + if (list == null || list.isEmpty()) + return false; + + if (list.contains(from)) { + if (to != null && !"".equals(to) && !list.contains(to)) { + list.add(to); + update = true; + } + + if (replace) { + while (list.contains(from)) { + list.remove(from); + update = true; + } + } + + } + + return update; + } } diff --git a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/adminp/JobHandlerUpgradeWorkitems.java b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/adminp/JobHandlerUpgradeWorkitems.java index 52a6b2506..a983beaa5 100644 --- a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/adminp/JobHandlerUpgradeWorkitems.java +++ b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/adminp/JobHandlerUpgradeWorkitems.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *
    - *  Imixs Workflow 
    +/*  
    + *  Imixs-Workflow 
    + *  
      *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
      *  http://www.imixs.com
      *  
    @@ -22,10 +22,9 @@
      *      https://github.com/imixs/imixs-workflow
      *  
      *  Contributors:  
    - *      Imixs Software Solutions GmbH - initial API and implementation
    + *      Imixs Software Solutions GmbH - Project Management
      *      Ralph Soika - Software Developer
    - * 
    - *******************************************************************************/ + */ package org.imixs.workflow.engine.adminp; @@ -59,214 +58,215 @@ * @author rsoika * */ -@DeclareRoles({"org.imixs.ACCESSLEVEL.MANAGERACCESS"}) +@DeclareRoles({ "org.imixs.ACCESSLEVEL.MANAGERACCESS" }) @Stateless @RunAs("org.imixs.ACCESSLEVEL.MANAGERACCESS") @LocalBean public class JobHandlerUpgradeWorkitems implements JobHandler { - private static final int DEFAULT_BLOCK_SIZE = 100; - - @Resource - SessionContext ctx; - - @Inject - DocumentService documentService; - - private static Logger logger = Logger.getLogger(JobHandlerUpgradeWorkitems.class.getName()); - - /** - * This method runs the RebuildLuceneIndexJob. The AdminP job description contains the start - * position (numIndex) and the number of documents to read (numBlockSize). - *

    - * The method updates the index for all affected documents which can be filtered by 'type' and - * '$created'. - *

    - * An existing lucene index must be deleted manually by the administrator. - *

    - * After the run method is finished, the properties numIndex, numUpdates and numProcessed are - * updated. - *

    - * If the number of documents returned from the DocumentService is less the the BlockSize, the - * method returns true to indicate that the Timer should be canceled. - *

    - * The method runs in an isolated new transaction because the method flushes the local persistence - * manager. - * - * @param adminp - * @return true if no more unprocessed documents exist. - * @throws AccessDeniedException - * @throws PluginException - */ - @Override - @TransactionAttribute(value = TransactionAttributeType.REQUIRES_NEW) - public ItemCollection run(ItemCollection adminp) throws AdminPException { - - long lProfiler = System.currentTimeMillis(); - int iIndex = adminp.getItemValueInteger("numIndex"); - int iBlockSize = adminp.getItemValueInteger("numBlockSize"); - - // test if numBlockSize is defined. - if (iBlockSize <= 0) { - // no set default block size. - iBlockSize = DEFAULT_BLOCK_SIZE; - adminp.replaceItemValue("numBlockSize", iBlockSize); - } + private static final int DEFAULT_BLOCK_SIZE = 100; + + @Resource + SessionContext ctx; + + @Inject + DocumentService documentService; + + private static Logger logger = Logger.getLogger(JobHandlerUpgradeWorkitems.class.getName()); + + /** + * This method runs the RebuildLuceneIndexJob. The AdminP job description + * contains the start position (numIndex) and the number of documents to read + * (numBlockSize). + *

    + * The method updates the index for all affected documents which can be filtered + * by 'type' and '$created'. + *

    + * An existing lucene index must be deleted manually by the administrator. + *

    + * After the run method is finished, the properties numIndex, numUpdates and + * numProcessed are updated. + *

    + * If the number of documents returned from the DocumentService is less the the + * BlockSize, the method returns true to indicate that the Timer should be + * canceled. + *

    + * The method runs in an isolated new transaction because the method flushes the + * local persistence manager. + * + * @param adminp + * @return true if no more unprocessed documents exist. + * @throws AccessDeniedException + * @throws PluginException + */ + @Override + @TransactionAttribute(value = TransactionAttributeType.REQUIRES_NEW) + public ItemCollection run(ItemCollection adminp) throws AdminPException { + + long lProfiler = System.currentTimeMillis(); + int iIndex = adminp.getItemValueInteger("numIndex"); + int iBlockSize = adminp.getItemValueInteger("numBlockSize"); + + // test if numBlockSize is defined. + if (iBlockSize <= 0) { + // no set default block size. + iBlockSize = DEFAULT_BLOCK_SIZE; + adminp.replaceItemValue("numBlockSize", iBlockSize); + } - int iUpdates = adminp.getItemValueInteger("numUpdates"); - int iProcessed = adminp.getItemValueInteger("numProcessed"); - - String query = buildQuery(adminp); - logger.finest("......JQPL query: " + query); - adminp.replaceItemValue("txtQuery", query); - - logger.info("... selecting workitems..."); - List workitemList = - documentService.getDocumentsByQuery(query, iIndex, iBlockSize); - int colSize = workitemList.size(); - // Update index - logger.info("Job " + AdminPService.JOB_UPGRADE + " (" + adminp.getUniqueID() + ") - verifeing " - + colSize + " workitems..."); - int iCount = 0; - for (ItemCollection workitem : workitemList) { - // only look into documents with a model version... - if (workitem.hasItem(WorkflowKernel.MODELVERSION)) { - if (upgradeWorkitem(workitem)) { - // update workitem... - logger.info("...upgrade '" + workitem.getUniqueID() + "' ..."); - documentService.saveByNewTransaction(workitem); - iCount++; + int iUpdates = adminp.getItemValueInteger("numUpdates"); + int iProcessed = adminp.getItemValueInteger("numProcessed"); + + String query = buildQuery(adminp); + logger.finest("......JQPL query: " + query); + adminp.replaceItemValue("txtQuery", query); + + logger.info("... selecting workitems..."); + List workitemList = documentService.getDocumentsByQuery(query, iIndex, iBlockSize); + int colSize = workitemList.size(); + // Update index + logger.info("Job " + AdminPService.JOB_UPGRADE + " (" + adminp.getUniqueID() + ") - verifeing " + colSize + + " workitems..."); + int iCount = 0; + for (ItemCollection workitem : workitemList) { + // only look into documents with a model version... + if (workitem.hasItem(WorkflowKernel.MODELVERSION)) { + if (upgradeWorkitem(workitem)) { + // update workitem... + logger.info("...upgrade '" + workitem.getUniqueID() + "' ..."); + documentService.saveByNewTransaction(workitem); + iCount++; + } + } + } + iIndex = iIndex + colSize; + iUpdates = iUpdates + iCount; + iProcessed = iProcessed + colSize; + + // adjust start pos and update count + adminp.replaceItemValue("numUpdates", iUpdates); + adminp.replaceItemValue("numProcessed", iProcessed); + adminp.replaceItemValue("numIndex", iIndex); + + long time = (System.currentTimeMillis() - lProfiler) / 1000; + if (time == 0) { + time = 1; } - } - } - iIndex = iIndex + colSize; - iUpdates = iUpdates + iCount; - iProcessed = iProcessed + colSize; - - // adjust start pos and update count - adminp.replaceItemValue("numUpdates", iUpdates); - adminp.replaceItemValue("numProcessed", iProcessed); - adminp.replaceItemValue("numIndex", iIndex); - - long time = (System.currentTimeMillis() - lProfiler) / 1000; - if (time == 0) { - time = 1; - } - logger.info("Job " + AdminPService.JOB_UPGRADE + " (" + adminp.getUniqueID() + ") - " + colSize - + " documents processed, " + iCount + " updates in " + time + " sec. (in total: " - + iProcessed + " processed, " + iUpdates + " updates)"); + logger.info("Job " + AdminPService.JOB_UPGRADE + " (" + adminp.getUniqueID() + ") - " + colSize + + " documents processed, " + iCount + " updates in " + time + " sec. (in total: " + iProcessed + + " processed, " + iUpdates + " updates)"); - // if colSize typeList = Arrays.asList(typeFilter.split("\\s*,\\s*")); - String sType = ""; - for (String aValue : typeList) { - sType += "'" + aValue.trim() + "',"; - } - sType = sType.substring(0, sType.length() - 1); - query += " AND document.type IN(" + sType + ")"; - bAddAnd = true; - } + /** + * This method builds the query statemetn based on the filter criteria. + * + * @param adminp + * @return + */ + private String buildQuery(ItemCollection adminp) { + Date datFilterFrom = adminp.getItemValueDate("datfrom"); + Date datFilterTo = adminp.getItemValueDate("datto"); + String typeFilter = adminp.getItemValueString("typelist"); + SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd"); + + boolean bAddAnd = false; + String query = "SELECT document FROM Document AS document "; + // ignore lucene event log entries + query += "WHERE document.type NOT IN ('event') "; + // ignore imixs-archive snapshots and deprecated blob + query += "AND document.type NOT LIKE 'snapshot%' "; + query += "AND document.type NOT LIKE 'workitemlob%' "; + + if (typeFilter != null && !typeFilter.isEmpty()) { + // convert type list into comma separated list + List typeList = Arrays.asList(typeFilter.split("\\s*,\\s*")); + String sType = ""; + for (String aValue : typeList) { + sType += "'" + aValue.trim() + "',"; + } + sType = sType.substring(0, sType.length() - 1); + query += " AND document.type IN(" + sType + ")"; + bAddAnd = true; + } - if (datFilterFrom != null) { - if (bAddAnd) { - query += " AND "; - } - query += " document.created>='" + isoFormat.format(datFilterFrom) + "' "; - bAddAnd = true; - } + if (datFilterFrom != null) { + if (bAddAnd) { + query += " AND "; + } + query += " document.created>='" + isoFormat.format(datFilterFrom) + "' "; + bAddAnd = true; + } - if (datFilterTo != null) { - if (bAddAnd) { - query += " AND "; - } - query += " document.created<='" + isoFormat.format(datFilterTo) + "' "; - bAddAnd = true; - } + if (datFilterTo != null) { + if (bAddAnd) { + query += " AND "; + } + query += " document.created<='" + isoFormat.format(datFilterTo) + "' "; + bAddAnd = true; + } - query += " ORDER BY document.created"; + query += " ORDER BY document.created"; - return query; - } + return query; + } } diff --git a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/index/DefaultOperator.java b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/index/DefaultOperator.java index aa5e41f69..e663b9bd6 100644 --- a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/index/DefaultOperator.java +++ b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/index/DefaultOperator.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *

    - *  Imixs Workflow 
    +/*  
    + *  Imixs-Workflow 
    + *  
      *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
      *  http://www.imixs.com
      *  
    @@ -22,13 +22,12 @@
      *      https://github.com/imixs/imixs-workflow
      *  
      *  Contributors:  
    - *      Imixs Software Solutions GmbH - initial API and implementation
    + *      Imixs Software Solutions GmbH - Project Management
      *      Ralph Soika - Software Developer
    - * 
    - *******************************************************************************/ + */ package org.imixs.workflow.engine.index; public enum DefaultOperator { - OR, AND + OR, AND }; diff --git a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/index/SchemaService.java b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/index/SchemaService.java index 7f9d05ae0..d5d8c2ded 100644 --- a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/index/SchemaService.java +++ b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/index/SchemaService.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *
    - *  Imixs Workflow 
    +/*  
    + *  Imixs-Workflow 
    + *  
      *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
      *  http://www.imixs.com
      *  
    @@ -22,10 +22,9 @@
      *      https://github.com/imixs/imixs-workflow
      *  
      *  Contributors:  
    - *      Imixs Software Solutions GmbH - initial API and implementation
    + *      Imixs Software Solutions GmbH - Project Management
      *      Ralph Soika - Software Developer
    - * 
    - *******************************************************************************/ + */ package org.imixs.workflow.engine.index; @@ -67,374 +66,370 @@ @Singleton public class SchemaService { - /* - * index.fields index.fields.analyze index.fields.noanalyze index.fields.store - * - * index.operator index.splitwhitespace - * - * - */ - - public static final String ANONYMOUS = "ANONYMOUS"; - - @Inject - @ConfigProperty(name = "index.fields", defaultValue = "") - private String indexFields; - - @Inject - @ConfigProperty(name = "index.fields.analyze", defaultValue = "") - private String indexFieldsAnalyze; - - @Inject - @ConfigProperty(name = "index.fields.noanalyze", defaultValue = "") - private String indexFieldsNoAnalyze; - - @Inject - @ConfigProperty(name = "index.fields.store", defaultValue = "") - private String indexFieldsStore; - - @Inject - private DocumentService documentService; - - private List fieldList = null; - private List fieldListAnalyze = null; - private List fieldListNoAnalyze = null; - private List fieldListStore = null; - private Set uniqueFieldList = null; - - // default field lists - public static List DEFAULT_SEARCH_FIELD_LIST = - Arrays.asList("$workflowsummary", "$workflowabstract"); - public static List DEFAULT_NOANALYZE_FIELD_LIST = - Arrays.asList("$modelversion", "$taskid", "$processid", "$workitemid", "$uniqueidref", "type", - "$writeaccess", "$modified", "$created", "namcreator", "$creator", "$editor", - "$lasteditor", "$workflowgroup", "$workflowstatus", "txtworkflowgroup", "name", "txtname", - "$owner", "namowner", "txtworkitemref", "$uniqueidsource", "$uniqueidversions", - "$lasttask", "$lastevent", "$lasteventdate"); - public static List DEFAULT_STORE_FIELD_LIST = - Arrays.asList("type", "$taskid", "$writeaccess", "$workflowsummary", "$workflowabstract", - "$workflowgroup", "$workflowstatus", "$modified", "$created", "$lasteventdate", - "$creator", "$editor", "$lasteditor", "$owner", "namowner"); - - private static Logger logger = Logger.getLogger(SchemaService.class.getName()); - - /** - * PostContruct event - The method loads the lucene index properties from the imixs.properties - * file from the classpath. If no properties are defined the method terminates. - * - */ - @PostConstruct - void init() { - boolean debug = logger.isLoggable(Level.FINE); - if (debug) { - logger.finest("......lucene FulltextFieldList=" + indexFields); - logger.finest("......lucene IndexFieldListAnalyze=" + indexFieldsAnalyze); - logger.finest("......lucene IndexFieldListNoAnalyze=" + indexFieldsNoAnalyze); - logger.finest("......lucene IndexFieldListStore=" + indexFieldsStore); - } - // compute the normal search field list - fieldList = new ArrayList(); - // add all entries from the default field list - fieldList.addAll(DEFAULT_SEARCH_FIELD_LIST); - if (indexFields != null && !indexFields.isEmpty()) { - StringTokenizer st = new StringTokenizer(indexFields, ","); - while (st.hasMoreElements()) { - String sName = st.nextToken().toLowerCase().trim(); - // do not add internal fields - if (!"$uniqueid".equals(sName) && !"$readaccess".equals(sName) - && !fieldList.contains(sName)) { - fieldList.add(sName); + /* + * index.fields index.fields.analyze index.fields.noanalyze index.fields.store + * + * index.operator index.splitwhitespace + * + * + */ + + public static final String ANONYMOUS = "ANONYMOUS"; + + @Inject + @ConfigProperty(name = "index.fields", defaultValue = "") + private String indexFields; + + @Inject + @ConfigProperty(name = "index.fields.analyze", defaultValue = "") + private String indexFieldsAnalyze; + + @Inject + @ConfigProperty(name = "index.fields.noanalyze", defaultValue = "") + private String indexFieldsNoAnalyze; + + @Inject + @ConfigProperty(name = "index.fields.store", defaultValue = "") + private String indexFieldsStore; + + @Inject + private DocumentService documentService; + + private List fieldList = null; + private List fieldListAnalyze = null; + private List fieldListNoAnalyze = null; + private List fieldListStore = null; + private Set uniqueFieldList = null; + + // default field lists + public static List DEFAULT_SEARCH_FIELD_LIST = Arrays.asList("$workflowsummary", "$workflowabstract"); + public static List DEFAULT_NOANALYZE_FIELD_LIST = Arrays.asList("$modelversion", "$taskid", "$processid", + "$workitemid", "$uniqueidref", "type", "$writeaccess", "$modified", "$created", "namcreator", "$creator", + "$editor", "$lasteditor", "$workflowgroup", "$workflowstatus", "txtworkflowgroup", "name", "txtname", + "$owner", "namowner", "txtworkitemref", "$uniqueidsource", "$uniqueidversions", "$lasttask", "$lastevent", + "$lasteventdate"); + public static List DEFAULT_STORE_FIELD_LIST = Arrays.asList("type", "$taskid", "$writeaccess", + "$workflowsummary", "$workflowabstract", "$workflowgroup", "$workflowstatus", "$modified", "$created", + "$lasteventdate", "$creator", "$editor", "$lasteditor", "$owner", "namowner"); + + private static Logger logger = Logger.getLogger(SchemaService.class.getName()); + + /** + * PostContruct event - The method loads the lucene index properties from the + * imixs.properties file from the classpath. If no properties are defined the + * method terminates. + * + */ + @PostConstruct + void init() { + boolean debug = logger.isLoggable(Level.FINE); + if (debug) { + logger.finest("......lucene FulltextFieldList=" + indexFields); + logger.finest("......lucene IndexFieldListAnalyze=" + indexFieldsAnalyze); + logger.finest("......lucene IndexFieldListNoAnalyze=" + indexFieldsNoAnalyze); + logger.finest("......lucene IndexFieldListStore=" + indexFieldsStore); + } + // compute the normal search field list + fieldList = new ArrayList(); + // add all entries from the default field list + fieldList.addAll(DEFAULT_SEARCH_FIELD_LIST); + if (indexFields != null && !indexFields.isEmpty()) { + StringTokenizer st = new StringTokenizer(indexFields, ","); + while (st.hasMoreElements()) { + String sName = st.nextToken().toLowerCase().trim(); + // do not add internal fields + if (!"$uniqueid".equals(sName) && !"$readaccess".equals(sName) && !fieldList.contains(sName)) { + fieldList.add(sName); + } + } } - } - } - // next we compute the NOANALYZE field list - fieldListNoAnalyze = new ArrayList(); - // add all entries from the default field list - fieldListNoAnalyze.addAll(DEFAULT_NOANALYZE_FIELD_LIST); - if (indexFieldsNoAnalyze != null && !indexFieldsNoAnalyze.isEmpty()) { - StringTokenizer st = new StringTokenizer(indexFieldsNoAnalyze, ","); - while (st.hasMoreElements()) { - String sName = st.nextToken().toLowerCase().trim(); - // avoid duplicates - if (!"$uniqueid".equals(sName) && !"$readaccess".equals(sName) - && !fieldListNoAnalyze.contains(sName)) { - fieldListNoAnalyze.add(sName); + // next we compute the NOANALYZE field list + fieldListNoAnalyze = new ArrayList(); + // add all entries from the default field list + fieldListNoAnalyze.addAll(DEFAULT_NOANALYZE_FIELD_LIST); + if (indexFieldsNoAnalyze != null && !indexFieldsNoAnalyze.isEmpty()) { + StringTokenizer st = new StringTokenizer(indexFieldsNoAnalyze, ","); + while (st.hasMoreElements()) { + String sName = st.nextToken().toLowerCase().trim(); + // avoid duplicates + if (!"$uniqueid".equals(sName) && !"$readaccess".equals(sName) && !fieldListNoAnalyze.contains(sName)) { + fieldListNoAnalyze.add(sName); + } + } } - } - } - // finally compute Index ANALYZE field list - fieldListAnalyze = new ArrayList(); - if (indexFieldsAnalyze != null && !indexFieldsAnalyze.isEmpty()) { - StringTokenizer st = new StringTokenizer(indexFieldsAnalyze, ","); - while (st.hasMoreElements()) { - String sName = st.nextToken().toLowerCase().trim(); - // Now we need to avoid also duplicates with the NOANALYZE field list ANALYZE - // and NOANALYZE must not be mixed (#560). If we already have a field in the - // NOANALYZE field list we just ignore it! - if (!"$uniqueid".equals(sName) && !"$readaccess".equals(sName) - && !fieldListAnalyze.contains(sName) && !fieldListNoAnalyze.contains(sName)) { - fieldListAnalyze.add(sName); + // finally compute Index ANALYZE field list + fieldListAnalyze = new ArrayList(); + if (indexFieldsAnalyze != null && !indexFieldsAnalyze.isEmpty()) { + StringTokenizer st = new StringTokenizer(indexFieldsAnalyze, ","); + while (st.hasMoreElements()) { + String sName = st.nextToken().toLowerCase().trim(); + // Now we need to avoid also duplicates with the NOANALYZE field list ANALYZE + // and NOANALYZE must not be mixed (#560). If we already have a field in the + // NOANALYZE field list we just ignore it! + if (!"$uniqueid".equals(sName) && !"$readaccess".equals(sName) && !fieldListAnalyze.contains(sName) + && !fieldListNoAnalyze.contains(sName)) { + fieldListAnalyze.add(sName); + } + } + } + + // compute Index field list (Store) + fieldListStore = new ArrayList(); + // add all static default field list + fieldListStore.addAll(DEFAULT_STORE_FIELD_LIST); + if (indexFieldsStore != null && !indexFieldsStore.isEmpty()) { + // add additional field list from imixs.properties + StringTokenizer st = new StringTokenizer(indexFieldsStore, ","); + while (st.hasMoreElements()) { + String sName = st.nextToken().toLowerCase().trim(); + if (!fieldListStore.contains(sName)) + fieldListStore.add(sName); + } } - } - } - // compute Index field list (Store) - fieldListStore = new ArrayList(); - // add all static default field list - fieldListStore.addAll(DEFAULT_STORE_FIELD_LIST); - if (indexFieldsStore != null && !indexFieldsStore.isEmpty()) { - // add additional field list from imixs.properties - StringTokenizer st = new StringTokenizer(indexFieldsStore, ","); - while (st.hasMoreElements()) { - String sName = st.nextToken().toLowerCase().trim(); - if (!fieldListStore.contains(sName)) - fieldListStore.add(sName); - } + // Issue #518: + // In case a field of the STORE field list is not already part of the ANALYZE + // field list add not part of NOANALYZE field list, than we add this field to + // the ANALYZE field list. This is to guaranty that we store the field value in + // any case! + for (String fieldName : fieldListStore) { + if (!fieldListAnalyze.contains(fieldName) && !fieldListNoAnalyze.contains(fieldName)) { + // add this field into he indexFieldListAnalyze + fieldListAnalyze.add(fieldName); + } + } + + // build unique field list containing all field names + uniqueFieldList = new HashSet(); + uniqueFieldList.add(WorkflowKernel.UNIQUEID); + uniqueFieldList.addAll(fieldListStore); + uniqueFieldList.addAll(fieldListAnalyze); + uniqueFieldList.addAll(fieldListNoAnalyze); + } - // Issue #518: - // In case a field of the STORE field list is not already part of the ANALYZE - // field list add not part of NOANALYZE field list, than we add this field to - // the ANALYZE field list. This is to guaranty that we store the field value in - // any case! - for (String fieldName : fieldListStore) { - if (!fieldListAnalyze.contains(fieldName) && !fieldListNoAnalyze.contains(fieldName)) { - // add this field into he indexFieldListAnalyze - fieldListAnalyze.add(fieldName); - } + /** + * Returns the field list defining the default content of the schema. The values + * of those items are only searchable by fulltext search + * + * @return + */ + public List getFieldList() { + return fieldList; } - // build unique field list containing all field names - uniqueFieldList = new HashSet(); - uniqueFieldList.add(WorkflowKernel.UNIQUEID); - uniqueFieldList.addAll(fieldListStore); - uniqueFieldList.addAll(fieldListAnalyze); - uniqueFieldList.addAll(fieldListNoAnalyze); - - } - - /** - * Returns the field list defining the default content of the schema. The values of those items - * are only searchable by fulltext search - * - * @return - */ - public List getFieldList() { - return fieldList; - } - - /** - * Returns the analyzed field list of the schema. The values of those items are searchable by a - * field search. The values are analyzed. - * - * @return - */ - public List getFieldListAnalyze() { - return fieldListAnalyze; - } - - /** - * Returns the no-analyze field list of the schema. The values of those items are searchable by - * field search. The values are not analyzed. - * - * @return - */ - public List getFieldListNoAnalyze() { - return fieldListNoAnalyze; - } - - /** - * Returns the field list of items stored in the index. - * - * @return - */ - public List getFieldListStore() { - return fieldListStore; - } - - /** - * Returns a unique list of all fields part of the index schema. - * - * @return - */ - public Set getUniqueFieldList() { - return uniqueFieldList; - } - - /** - * Returns the Lucene schema configuration - * - * @return - */ - public ItemCollection getConfiguration() { - ItemCollection config = new ItemCollection(); - - config.replaceItemValue("lucence.fulltextFieldList", fieldList); - config.replaceItemValue("lucence.indexFieldListAnalyze", fieldListAnalyze); - config.replaceItemValue("lucence.indexFieldListNoAnalyze", fieldListNoAnalyze); - config.replaceItemValue("lucence.indexFieldListStore", fieldListStore); - - return config; - } - - /** - * Returns the extended search term for a given query. The search term will be extended with a - * users roles to test the read access level of each workitem matching the search term. - * - * @param sSearchTerm - * @return extended search term - * @throws QueryException in case the searchtem is not understandable. - */ - public String getExtendedSearchTerm(String sSearchTerm) throws QueryException { - // test if searchtem is provided - if (sSearchTerm == null || "".equals(sSearchTerm)) { - logger.warning("No search term provided!"); - return ""; + /** + * Returns the analyzed field list of the schema. The values of those items are + * searchable by a field search. The values are analyzed. + * + * @return + */ + public List getFieldListAnalyze() { + return fieldListAnalyze; } - // extend the Search Term if user is not ACCESSLEVEL_MANAGERACCESS - if (!documentService.isUserInRole(DocumentService.ACCESSLEVEL_MANAGERACCESS)) { - // get user names list - List userNameList = documentService.getUserNameList(); - // create search term (always add ANONYMOUS) - String sAccessTerm = "($readaccess:" + ANONYMOUS; - for (String aRole : userNameList) { - if (!"".equals(aRole)) - sAccessTerm += " OR $readaccess:\"" + aRole + "\""; - } - sAccessTerm += ") AND "; - sSearchTerm = sAccessTerm + sSearchTerm; + + /** + * Returns the no-analyze field list of the schema. The values of those items + * are searchable by field search. The values are not analyzed. + * + * @return + */ + public List getFieldListNoAnalyze() { + return fieldListNoAnalyze; } - logger.finest("......lucene final searchTerm=" + sSearchTerm); - - return sSearchTerm; - } - - /** - * This helper method escapes special characters found in a lucene search term. The method can be - * used by clients to prepare a search phrase. - *

    - * Special characters are characters that are part of the lucene query syntax - *

    - * + - && || ! ( ) { } [ ] ^ " ~ * ? : \ / - *

    - * Clients should use the method normalizeSearchTerm() instead of escapeSearchTerm() to prepare a - * user input for a lucene search. - * - * @see normalizeSearchTerm - * @param searchTerm - * @param ignoreBracket - if true brackes will not be escaped. - * @return escaped search term - */ - public String escapeSearchTerm(String searchTerm, boolean ignoreBracket) { - if (searchTerm == null || searchTerm.isEmpty()) { - return searchTerm; + + /** + * Returns the field list of items stored in the index. + * + * @return + */ + public List getFieldListStore() { + return fieldListStore; } - // this is the code from the QueryParser.escape() method without the '*' - // char! - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < searchTerm.length(); i++) { - char c = searchTerm.charAt(i); - // These characters are part of the query syntax and must be escaped - // (ignore brackets!) - if (c == '\\' || c == '+' || c == '-' || c == '!' || c == ':' || c == '^' || c == '[' - || c == ']' || c == '\"' || c == '{' || c == '}' || c == '~' || c == '?' || c == '|' - || c == '&' || c == '/') { - sb.append('\\'); - } - - // escape bracket? - if (!ignoreBracket && (c == '(' || c == ')')) { - sb.append('\\'); - } - - sb.append(c); + /** + * Returns a unique list of all fields part of the index schema. + * + * @return + */ + public Set getUniqueFieldList() { + return uniqueFieldList; } - return sb.toString(); - - } - - public String escapeSearchTerm(String searchTerm) { - return escapeSearchTerm(searchTerm, false); - } - - /** - * This method normalizes a search term. The method can be used by clients to prepare a search - * phrase. The serach term will be lowercased and special characters will be replaced by a blank - * separator - *

    - * e.g. 'europe/berlin' will be normalized to 'europe berlin' - *

    - * In case the searchTerm contains numbers the method escapes special characters instead of - * replacing with a blank: - *

    - * e.g. 'r-555/333' will be converted into 'r\-555\/333' - *

    - * Special characters are characters that are part of the lucene query syntax - *

    - * + - && || ! ( ) { } [ ] ^ " ~ ? : \ / - *

    - * - * @param searchTerm - * @return normalized search term - * - */ - public String normalizeSearchTerm(String searchTerm) { - - if (searchTerm == null) { - return ""; + + /** + * Returns the Lucene schema configuration + * + * @return + */ + public ItemCollection getConfiguration() { + ItemCollection config = new ItemCollection(); + + config.replaceItemValue("lucence.fulltextFieldList", fieldList); + config.replaceItemValue("lucence.indexFieldListAnalyze", fieldListAnalyze); + config.replaceItemValue("lucence.indexFieldListNoAnalyze", fieldListNoAnalyze); + config.replaceItemValue("lucence.indexFieldListStore", fieldListStore); + + return config; } - if (searchTerm.trim().isEmpty()) { - return ""; + + /** + * Returns the extended search term for a given query. The search term will be + * extended with a users roles to test the read access level of each workitem + * matching the search term. + * + * @param sSearchTerm + * @return extended search term + * @throws QueryException in case the searchtem is not understandable. + */ + public String getExtendedSearchTerm(String sSearchTerm) throws QueryException { + // test if searchtem is provided + if (sSearchTerm == null || "".equals(sSearchTerm)) { + logger.warning("No search term provided!"); + return ""; + } + // extend the Search Term if user is not ACCESSLEVEL_MANAGERACCESS + if (!documentService.isUserInRole(DocumentService.ACCESSLEVEL_MANAGERACCESS)) { + // get user names list + List userNameList = documentService.getUserNameList(); + // create search term (always add ANONYMOUS) + String sAccessTerm = "($readaccess:" + ANONYMOUS; + for (String aRole : userNameList) { + if (!"".equals(aRole)) + sAccessTerm += " OR $readaccess:\"" + aRole + "\""; + } + sAccessTerm += ") AND "; + sSearchTerm = sAccessTerm + sSearchTerm; + } + logger.finest("......lucene final searchTerm=" + sSearchTerm); + + return sSearchTerm; } - // lowercase - searchTerm = searchTerm.toLowerCase(); + /** + * This helper method escapes special characters found in a lucene search term. + * The method can be used by clients to prepare a search phrase. + *

    + * Special characters are characters that are part of the lucene query syntax + *

    + * + - && || ! ( ) { } [ ] ^ " ~ * ? : \ / + *

    + * Clients should use the method normalizeSearchTerm() instead of + * escapeSearchTerm() to prepare a user input for a lucene search. + * + * @see normalizeSearchTerm + * @param searchTerm + * @param ignoreBracket - if true brackes will not be escaped. + * @return escaped search term + */ + public String escapeSearchTerm(String searchTerm, boolean ignoreBracket) { + if (searchTerm == null || searchTerm.isEmpty()) { + return searchTerm; + } + + // this is the code from the QueryParser.escape() method without the '*' + // char! + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < searchTerm.length(); i++) { + char c = searchTerm.charAt(i); + // These characters are part of the query syntax and must be escaped + // (ignore brackets!) + if (c == '\\' || c == '+' || c == '-' || c == '!' || c == ':' || c == '^' || c == '[' || c == ']' + || c == '\"' || c == '{' || c == '}' || c == '~' || c == '?' || c == '|' || c == '&' || c == '/') { + sb.append('\\'); + } + + // escape bracket? + if (!ignoreBracket && (c == '(' || c == ')')) { + sb.append('\\'); + } + + sb.append(c); + } + return sb.toString(); - if (containsDigit(searchTerm)) { - return escapeSearchTerm(searchTerm, false); } - // now replace Special Characters with blanks - // + - && || ! ( ) { } [ ] ^ " ~ * ? : \ / - // this is the code from the QueryParser.escape() method without the '*' - // char! - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < searchTerm.length(); i++) { - char c = searchTerm.charAt(i); - // These characters are part of the query syntax and must be escaped - // (ignore brackets!) - if (c == '\\' || c == '+' || c == '-' || c == '!' || c == ':' || c == '^' || c == '[' - || c == ']' || c == '\"' || c == '{' || c == '}' || c == '~' || c == '?' || c == '|' - || c == '&' || c == '/') { - sb.append(' '); - } else { - sb.append(c); - } + public String escapeSearchTerm(String searchTerm) { + return escapeSearchTerm(searchTerm, false); } - return sb.toString(); - } - - /** - * Test if a string contains a number. Seems to be faster than regex. - *

    - * See: https://stackoverflow.com/questions/18590901/check-if-a-string-contains-numbers-java - * - * @param s - * @return - */ - private boolean containsDigit(String s) { - boolean containsDigit = false; - if (s != null && !s.isEmpty()) { - for (char c : s.toCharArray()) { - if (containsDigit = Character.isDigit(c)) { - break; + + /** + * This method normalizes a search term. The method can be used by clients to + * prepare a search phrase. The serach term will be lowercased and special + * characters will be replaced by a blank separator + *

    + * e.g. 'europe/berlin' will be normalized to 'europe berlin' + *

    + * In case the searchTerm contains numbers the method escapes special characters + * instead of replacing with a blank: + *

    + * e.g. 'r-555/333' will be converted into 'r\-555\/333' + *

    + * Special characters are characters that are part of the lucene query syntax + *

    + * + - && || ! ( ) { } [ ] ^ " ~ ? : \ / + *

    + * + * @param searchTerm + * @return normalized search term + * + */ + public String normalizeSearchTerm(String searchTerm) { + + if (searchTerm == null) { + return ""; + } + if (searchTerm.trim().isEmpty()) { + return ""; + } + + // lowercase + searchTerm = searchTerm.toLowerCase(); + + if (containsDigit(searchTerm)) { + return escapeSearchTerm(searchTerm, false); + } + + // now replace Special Characters with blanks + // + - && || ! ( ) { } [ ] ^ " ~ * ? : \ / + // this is the code from the QueryParser.escape() method without the '*' + // char! + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < searchTerm.length(); i++) { + char c = searchTerm.charAt(i); + // These characters are part of the query syntax and must be escaped + // (ignore brackets!) + if (c == '\\' || c == '+' || c == '-' || c == '!' || c == ':' || c == '^' || c == '[' || c == ']' + || c == '\"' || c == '{' || c == '}' || c == '~' || c == '?' || c == '|' || c == '&' || c == '/') { + sb.append(' '); + } else { + sb.append(c); + } + } + return sb.toString(); + } + + /** + * Test if a string contains a number. Seems to be faster than regex. + *

    + * See: + * https://stackoverflow.com/questions/18590901/check-if-a-string-contains-numbers-java + * + * @param s + * @return + */ + private boolean containsDigit(String s) { + boolean containsDigit = false; + if (s != null && !s.isEmpty()) { + for (char c : s.toCharArray()) { + if (containsDigit = Character.isDigit(c)) { + break; + } + } } - } + return containsDigit; } - return containsDigit; - } } diff --git a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/index/SearchService.java b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/index/SearchService.java index 2cb6e2ed0..9a1281802 100644 --- a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/index/SearchService.java +++ b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/index/SearchService.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *

    - *  Imixs Workflow 
    +/*  
    + *  Imixs-Workflow 
    + *  
      *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
      *  http://www.imixs.com
      *  
    @@ -22,10 +22,9 @@
      *      https://github.com/imixs/imixs-workflow
      *  
      *  Contributors:  
    - *      Imixs Software Solutions GmbH - initial API and implementation
    + *      Imixs Software Solutions GmbH - Project Management
      *      Ralph Soika - Software Developer
    - * 
    - *******************************************************************************/ + */ package org.imixs.workflow.engine.index; @@ -35,7 +34,8 @@ import org.imixs.workflow.exceptions.QueryException; /** - * This SearchService defines methods to search workitems or collections of workitems. + * This SearchService defines methods to search workitems or collections of + * workitems. * * @version 1.0 * @author rsoika @@ -43,54 +43,55 @@ @Local public interface SearchService { - public static final int DEFAULT_MAX_SEARCH_RESULT = 9999; // limiting the - // total - // number of hits - public static final int DEFAULT_PAGE_SIZE = 100; // default docs in one page - + public static final int DEFAULT_MAX_SEARCH_RESULT = 9999; // limiting the + // total + // number of hits + public static final int DEFAULT_PAGE_SIZE = 100; // default docs in one page - /** - * Returns a collection of documents matching the provided search term. The term will be extended - * with the current users roles to test the read access level of each workitem matching the search - * term. - *

    - * The optional param 'searchOrder' can be set to force lucene to sort the search result by any - * search order. - *

    - * The optional param 'defaultOperator' can be set to Operator.AND - *

    - * The optional param 'stubs' indicates if the full Imixs Document should be loaded or if only the - * data fields stored in the lucedn index will be return. The later is the faster method but - * returns only document stubs. - * - * @param searchTerm - * @param pageSize - docs per page - * @param pageIndex - page number - * @param sortOrder - optional to sort the result - * @param defaultOperator - optional to change the default search operator - * @param loadStubs - optional indicates of only the lucene document should be returned. - * @return collection of search result - * - * @throws QueryException in case the searchtem is not understandable. - */ - public List search(String searchTerm, int pageSize, int pageIndex, - SortOrder sortOrder, DefaultOperator defaultOperator, boolean loadStubs) - throws QueryException; + /** + * Returns a collection of documents matching the provided search term. The term + * will be extended with the current users roles to test the read access level + * of each workitem matching the search term. + *

    + * The optional param 'searchOrder' can be set to force lucene to sort the + * search result by any search order. + *

    + * The optional param 'defaultOperator' can be set to Operator.AND + *

    + * The optional param 'stubs' indicates if the full Imixs Document should be + * loaded or if only the data fields stored in the lucedn index will be return. + * The later is the faster method but returns only document stubs. + * + * @param searchTerm + * @param pageSize - docs per page + * @param pageIndex - page number + * @param sortOrder - optional to sort the result + * @param defaultOperator - optional to change the default search operator + * @param loadStubs - optional indicates of only the lucene document + * should be returned. + * @return collection of search result + * + * @throws QueryException in case the searchtem is not understandable. + */ + public List search(String searchTerm, int pageSize, int pageIndex, SortOrder sortOrder, + DefaultOperator defaultOperator, boolean loadStubs) throws QueryException; - /** - * Returns the total hits for a given search term from the lucene index. The method did not load - * any data. The provided search term will we extended with a users roles to test the read access - * level of each workitem matching the search term. - * - * The optional param 'maxResult' can be set to overwrite the DEFAULT_MAX_SEARCH_RESULT. - * - * @see search(String, int, int, Sort, Operator) - * - * @param sSearchTerm - * @param maxResult - max search result - * @return total hits of search result - * @throws QueryException in case the searchterm is not understandable. - */ - public int getTotalHits(final String _searchTerm, final int _maxResult, - final DefaultOperator defaultOperator) throws QueryException; + /** + * Returns the total hits for a given search term from the lucene index. The + * method did not load any data. The provided search term will we extended with + * a users roles to test the read access level of each workitem matching the + * search term. + * + * The optional param 'maxResult' can be set to overwrite the + * DEFAULT_MAX_SEARCH_RESULT. + * + * @see search(String, int, int, Sort, Operator) + * + * @param sSearchTerm + * @param maxResult - max search result + * @return total hits of search result + * @throws QueryException in case the searchterm is not understandable. + */ + public int getTotalHits(final String _searchTerm, final int _maxResult, final DefaultOperator defaultOperator) + throws QueryException; } diff --git a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/index/SortOrder.java b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/index/SortOrder.java index 6a1df6b7c..7f80801d4 100644 --- a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/index/SortOrder.java +++ b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/index/SortOrder.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *

    - *  Imixs Workflow 
    +/*  
    + *  Imixs-Workflow 
    + *  
      *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
      *  http://www.imixs.com
      *  
    @@ -22,77 +22,76 @@
      *      https://github.com/imixs/imixs-workflow
      *  
      *  Contributors:  
    - *      Imixs Software Solutions GmbH - initial API and implementation
    + *      Imixs Software Solutions GmbH - Project Management
      *      Ralph Soika - Software Developer
    - * 
    - *******************************************************************************/ + */ package org.imixs.workflow.engine.index; /** - * Stores information about how to sort documents by terms by an individual field. Fields must be - * indexed in order to sort by them. + * Stores information about how to sort documents by terms by an individual + * field. Fields must be indexed in order to sort by them. * * @version 1.0 */ public class SortOrder { - private boolean reverse; - private String field; + private boolean reverse; + private String field; - /** - * Creates a sort, possibly in reverse, with a custom comparison function. - * - * @param field Name of field to sort by; cannot be null. - * @param comparator Returns a comparator for sorting hits. - * @param reverse True if natural order should be reversed. - */ - public SortOrder(String field, boolean reverse) { - this.reverse = reverse; - this.field = field; - } + /** + * Creates a sort, possibly in reverse, with a custom comparison function. + * + * @param field Name of field to sort by; cannot be null. + * @param comparator Returns a comparator for sorting hits. + * @param reverse True if natural order should be reversed. + */ + public SortOrder(String field, boolean reverse) { + this.reverse = reverse; + this.field = field; + } - public boolean isReverse() { - return reverse; - } + public boolean isReverse() { + return reverse; + } - public void setReverse(boolean reverse) { - this.reverse = reverse; - } + public void setReverse(boolean reverse) { + this.reverse = reverse; + } - public String getField() { - return field; - } + public String getField() { + return field; + } - public void setField(String field) { - this.field = field; - } + public void setField(String field) { + this.field = field; + } - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((field == null) ? 0 : field.hashCode()); - result = prime * result + (reverse ? 1231 : 1237); - return result; - } + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((field == null) ? 0 : field.hashCode()); + result = prime * result + (reverse ? 1231 : 1237); + return result; + } - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - SortOrder other = (SortOrder) obj; - if (field == null) { - if (other.field != null) - return false; - } else if (!field.equals(other.field)) - return false; - if (reverse != other.reverse) - return false; - return true; - } + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + SortOrder other = (SortOrder) obj; + if (field == null) { + if (other.field != null) + return false; + } else if (!field.equals(other.field)) + return false; + if (reverse != other.reverse) + return false; + return true; + } } diff --git a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/index/UpdateService.java b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/index/UpdateService.java index 216ecd460..b8a238d27 100644 --- a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/index/UpdateService.java +++ b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/index/UpdateService.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *
    - *  Imixs Workflow 
    +/*  
    + *  Imixs-Workflow 
    + *  
      *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
      *  http://www.imixs.com
      *  
    @@ -22,10 +22,9 @@
      *      https://github.com/imixs/imixs-workflow
      *  
      *  Contributors:  
    - *      Imixs Software Solutions GmbH - initial API and implementation
    + *      Imixs Software Solutions GmbH - Project Management
      *      Ralph Soika - Software Developer
    - * 
    - *******************************************************************************/ + */ package org.imixs.workflow.engine.index; @@ -36,10 +35,11 @@ import org.imixs.workflow.exceptions.IndexException; /** - * The UpdateService defines methods to update the search index. These methods are called by the - * DocuentService. + * The UpdateService defines methods to update the search index. These methods + * are called by the DocuentService. *

    - * The method updateIndex(documents) writes documents immediately into the index. + * The method updateIndex(documents) writes documents immediately into the + * index. *

    * The method updateIndex() updates the search index based on the eventLog. *

    @@ -52,39 +52,37 @@ @Local public interface UpdateService { - // default field lists - public static List DEFAULT_SEARCH_FIELD_LIST = - Arrays.asList("$workflowsummary", "$workflowabstract"); - public static List DEFAULT_NOANALYSE_FIELD_LIST = - Arrays.asList("$modelversion", "$taskid", "$processid", "$workitemid", "$uniqueidref", "type", - "$writeaccess", "$modified", "$created", "namcreator", "$creator", "$editor", - "$lasteditor", "$workflowgroup", "$workflowstatus", "txtworkflowgroup", "name", "txtname", - "$owner", "namowner", "txtworkitemref", "$uniqueidsource", "$uniqueidversions", - "$lasttask", "$lastevent", "$lasteventdate"); - public static List DEFAULT_STORE_FIELD_LIST = - Arrays.asList("type", "$taskid", "$writeaccess", "$workflowsummary", "$workflowabstract", - "$workflowgroup", "$workflowstatus", "$modified", "$created", "$lasteventdate", - "$creator", "$editor", "$lasteditor", "$owner", "namowner"); + // default field lists + public static List DEFAULT_SEARCH_FIELD_LIST = Arrays.asList("$workflowsummary", "$workflowabstract"); + public static List DEFAULT_NOANALYSE_FIELD_LIST = Arrays.asList("$modelversion", "$taskid", "$processid", + "$workitemid", "$uniqueidref", "type", "$writeaccess", "$modified", "$created", "namcreator", "$creator", + "$editor", "$lasteditor", "$workflowgroup", "$workflowstatus", "txtworkflowgroup", "name", "txtname", + "$owner", "namowner", "txtworkitemref", "$uniqueidsource", "$uniqueidversions", "$lasttask", "$lastevent", + "$lasteventdate"); + public static List DEFAULT_STORE_FIELD_LIST = Arrays.asList("type", "$taskid", "$writeaccess", + "$workflowsummary", "$workflowabstract", "$workflowgroup", "$workflowstatus", "$modified", "$created", + "$lasteventdate", "$creator", "$editor", "$lasteditor", "$owner", "namowner"); - /** - * This method adds a collection of documents to the index. The documents are added immediately to - * the index. Calling this method within a running transaction leads to a uncommitted reads in the - * index. For transaction control, it is recommended to use instead the the method - * documentService.addDocumentToIndex() which takes care of uncommitted reads. - *

    - * This method is used by the JobHandlerRebuildIndex only. - * - * @param documents of ItemCollections to be indexed - * @throws IndexException - */ - public void updateIndex(List documents); + /** + * This method adds a collection of documents to the index. The documents are + * added immediately to the index. Calling this method within a running + * transaction leads to a uncommitted reads in the index. For transaction + * control, it is recommended to use instead the the method + * documentService.addDocumentToIndex() which takes care of uncommitted reads. + *

    + * This method is used by the JobHandlerRebuildIndex only. + * + * @param documents of ItemCollections to be indexed + * @throws IndexException + */ + public void updateIndex(List documents); - /** - * This method updates the search index based on the eventLog. Documents are added by the - * DocumentService as events to the EventLogService. This ensures that only committed documents - * are added into the index. - * - * @see DocumentService - */ - public void updateIndex(); + /** + * This method updates the search index based on the eventLog. Documents are + * added by the DocumentService as events to the EventLogService. This ensures + * that only committed documents are added into the index. + * + * @see DocumentService + */ + public void updateIndex(); } diff --git a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/jpa/Document.java b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/jpa/Document.java index 153dbf966..f792b46a2 100644 --- a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/jpa/Document.java +++ b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/jpa/Document.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *

    - *  Imixs Workflow 
    +/*  
    + *  Imixs-Workflow 
    + *  
      *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
      *  http://www.imixs.com
      *  
    @@ -22,10 +22,9 @@
      *      https://github.com/imixs/imixs-workflow
      *  
      *  Contributors:  
    - *      Imixs Software Solutions GmbH - initial API and implementation
    + *      Imixs Software Solutions GmbH - Project Management
      *      Ralph Soika - Software Developer
    - * 
    - *******************************************************************************/ + */ package org.imixs.workflow.engine.jpa; @@ -44,30 +43,32 @@ import org.imixs.workflow.exceptions.InvalidAccessException; /** - * This Document entity bean is a wrapper class for the org.imixs.workflow.ItemCollection which is - * used in all Imixs-Workflow Interfaces. The Document is used by the DocumentService to store - * ItemCollections into a database, using the Java Persistence API. Each Document is added into the - * Lucene index. + * This Document entity bean is a wrapper class for the + * org.imixs.workflow.ItemCollection which is used in all Imixs-Workflow + * Interfaces. The Document is used by the DocumentService to store + * ItemCollections into a database, using the Java Persistence API. Each + * Document is added into the Lucene index. *

    - * A Document contains a universal unique ID to identify the Entity. Also the Document contains the - * following additional properties + * A Document contains a universal unique ID to identify the Entity. Also the + * Document contains the following additional properties *

      *
    • type *
    • created *
    • modified *
    *

    - * The creation time represents the point of time where the Document object was created. The modify - * property represents the point of time when the Document was last modified by the DocumentService. - * The type property is used to categorize documents in a database. If an ItemCollection contains - * the attribute 'type' the value will be automatically mapped to the type property. + * The creation time represents the point of time where the Document object was + * created. The modify property represents the point of time when the Document + * was last modified by the DocumentService. The type property is used to + * categorize documents in a database. If an ItemCollection contains the + * attribute 'type' the value will be automatically mapped to the type property. *

    - * The data attribute is used to hold the ItemCollection data. It is mapped by a OR-Mapper to a - * large object (Lob). + * The data attribute is used to hold the ItemCollection data. It is mapped by a + * OR-Mapper to a large object (Lob). *

    - * A Client should not work directly with an instance of the Document entity. It's recommended to - * use the DocumentService which acts as a session facade to manage instances of ItemCollection - * persisted in a database system. + * A Client should not work directly with an instance of the Document entity. + * It's recommended to use the DocumentService which acts as a session facade to + * manage instances of ItemCollection persisted in a database system. *

    * * @@ -79,153 +80,156 @@ @javax.persistence.Entity public class Document implements java.io.Serializable { - private static final long serialVersionUID = 1L; - private String id; - private Integer version; - private String type; - private Calendar created; - private Calendar modified; - private Map> data; - private boolean pending; - - /** - * A Document will be automatically initialized with a unique id and a creation date. - */ - public Document() { - // Generate a new uniqueId - id = WorkflowKernel.generateUniqueID(); - // Initialize objects - Calendar cal = Calendar.getInstance(); - created = cal; - modified = cal; - } - - /** - * This constructor allows the creation of an Document Instance with a default uniqueID - * - * @param aID - */ - public Document(String aID) { - this(); - if (aID != null && !aID.isEmpty()) { - // overwrite $UNIQUEID - id = aID; - } - } - - /** - * This transient flag indicates if the document was just saved and is still managed by the - * entityManager. In this case the entity may not be detached by other methods during the same - * transaction. See issue #230. - * - * @return save status - */ - @Transient - public boolean isPending() { - return pending; - } - - public void setPending(boolean pandingState) { - pending = pandingState; - } - - /** - * returns the unique identifier for the Entity. - * - * @return universal id - */ - @Id - public String getId() { - return id; - } - - protected void setId(String aID) { - id = aID; - } - - @Version - public Integer getVersion() { - return version; - } - - public void setVersion(Integer version) { - this.version = version; - } - - /** - * returns the type property of the entity instance. This property can be provided by an - * itemColleciton in the attribute 'type'. Values will be case sensitive! - * - * @see org.imixs.workflow.jee.ejb.EntityService - * @return - */ - public String getType() { - return type; - } - - public void setType(String type) { - this.type = type; - } - - /** - * returns the creation point of time. - * - * @return time of creation - */ - @Temporal(TemporalType.TIMESTAMP) - public Calendar getCreated() { - return created; - } - - public void setCreated(Calendar created) { - this.created = created; - } - - /** - * Returns the time of last modification. This attribute is synchronized by the DocumetnService - * with the item '$modified'. - * - * @see setData() - * @return time of modification - */ - @Temporal(TemporalType.TIMESTAMP) - public Calendar getModified() { - return modified; - } - - /** - * Set the time of last modification. This attribute is automatically synchronized with the item - * '$modified'. - */ - public void setModified(Calendar modified) { - this.modified = modified; - } - - /** - * returns the data object part of the Entity represented by a java.util.Map - *

    - * Data is loaded eager because it is read in any case by the DocumentService. - * - * @return Map - */ - @Lob - @Basic(fetch = FetchType.EAGER) - public Map> getData() { - return data; - } - - /** - * sets a data object for this Entity. - *

    - * Note: the modified timestamp will be updated automatically to the current point of time (see - * setModified) independent from the value of the item $modified. The item $modified will be - * updated by the DocumentService on read. - * - * @param data - * @throws InvalidAccessException if $modified is missing - */ - public void setData(Map> itemCol) { - this.data = itemCol; - } + private static final long serialVersionUID = 1L; + private String id; + private Integer version; + private String type; + private Calendar created; + private Calendar modified; + private Map> data; + private boolean pending; + + /** + * A Document will be automatically initialized with a unique id and a creation + * date. + */ + public Document() { + // Generate a new uniqueId + id = WorkflowKernel.generateUniqueID(); + // Initialize objects + Calendar cal = Calendar.getInstance(); + created = cal; + modified = cal; + } + + /** + * This constructor allows the creation of an Document Instance with a default + * uniqueID + * + * @param aID + */ + public Document(String aID) { + this(); + if (aID != null && !aID.isEmpty()) { + // overwrite $UNIQUEID + id = aID; + } + } + + /** + * This transient flag indicates if the document was just saved and is still + * managed by the entityManager. In this case the entity may not be detached by + * other methods during the same transaction. See issue #230. + * + * @return save status + */ + @Transient + public boolean isPending() { + return pending; + } + + public void setPending(boolean pandingState) { + pending = pandingState; + } + + /** + * returns the unique identifier for the Entity. + * + * @return universal id + */ + @Id + public String getId() { + return id; + } + + protected void setId(String aID) { + id = aID; + } + + @Version + public Integer getVersion() { + return version; + } + + public void setVersion(Integer version) { + this.version = version; + } + + /** + * returns the type property of the entity instance. This property can be + * provided by an itemColleciton in the attribute 'type'. Values will be case + * sensitive! + * + * @see org.imixs.workflow.jee.ejb.EntityService + * @return + */ + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + /** + * returns the creation point of time. + * + * @return time of creation + */ + @Temporal(TemporalType.TIMESTAMP) + public Calendar getCreated() { + return created; + } + + public void setCreated(Calendar created) { + this.created = created; + } + + /** + * Returns the time of last modification. This attribute is synchronized by the + * DocumetnService with the item '$modified'. + * + * @see setData() + * @return time of modification + */ + @Temporal(TemporalType.TIMESTAMP) + public Calendar getModified() { + return modified; + } + + /** + * Set the time of last modification. This attribute is automatically + * synchronized with the item '$modified'. + */ + public void setModified(Calendar modified) { + this.modified = modified; + } + + /** + * returns the data object part of the Entity represented by a java.util.Map + *

    + * Data is loaded eager because it is read in any case by the DocumentService. + * + * @return Map + */ + @Lob + @Basic(fetch = FetchType.EAGER) + public Map> getData() { + return data; + } + + /** + * sets a data object for this Entity. + *

    + * Note: the modified timestamp will be updated automatically to the current + * point of time (see setModified) independent from the value of the item + * $modified. The item $modified will be updated by the DocumentService on read. + * + * @param data + * @throws InvalidAccessException if $modified is missing + */ + public void setData(Map> itemCol) { + this.data = itemCol; + } } diff --git a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/jpa/EventLog.java b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/jpa/EventLog.java index e3a18a0d5..050cefadf 100644 --- a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/jpa/EventLog.java +++ b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/jpa/EventLog.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *

    - *  Imixs Workflow 
    +/*  
    + *  Imixs-Workflow 
    + *  
      *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
      *  http://www.imixs.com
      *  
    @@ -22,10 +22,9 @@
      *      https://github.com/imixs/imixs-workflow
      *  
      *  Contributors:  
    - *      Imixs Software Solutions GmbH - initial API and implementation
    + *      Imixs Software Solutions GmbH - Project Management
      *      Ralph Soika - Software Developer
    - * 
    - *******************************************************************************/ + */ package org.imixs.workflow.engine.jpa; @@ -43,11 +42,13 @@ import org.imixs.workflow.exceptions.InvalidAccessException; /** - * The EventLog entity bean is used by the EventLogService to create and access event log entries. - * An EventLog defines a unique event created during the processing life-cycle of a workitem or the - * update life-cycle of a Document entity. + * The EventLog entity bean is used by the EventLogService to create and access + * event log entries. An EventLog defines a unique event created during the + * processing life-cycle of a workitem or the update life-cycle of a Document + * entity. *

    - * An EventLog is an immutable entity. The object contains the following additional properties + * An EventLog is an immutable entity. The object contains the following + * additional properties *

      *
    • id - identifier for the event log entry *
    • ref - the reference id of the corresponding workitem or document entity @@ -56,16 +57,17 @@ *
    • data - an optional data field *
    *

    - * The 'data' attribute of an eventLog is optional and can hold any kind of event specific data - * (e.g. a Mail Message). + * The 'data' attribute of an eventLog is optional and can hold any kind of + * event specific data (e.g. a Mail Message). *

    - * EventLog entities can be created and accessed by the EventLogService. Typically a new EventLog - * entity is created within the same transaction of the main processing or update life cycle. With - * this mechanism a client can be sure that eventLogEntries returned by the EventLogService are - * created during a committed Transaction. + * EventLog entities can be created and accessed by the EventLogService. + * Typically a new EventLog entity is created within the same transaction of the + * main processing or update life cycle. With this mechanism a client can be + * sure that eventLogEntries returned by the EventLogService are created during + * a committed Transaction. *

    - * Note: for the same document reference ($uniqueid) there can exist different eventlog entries. - * Eventlog entries are unique over there internal ID. + * Note: for the same document reference ($uniqueid) there can exist different + * eventlog entries. Eventlog entries are unique over there internal ID. * * @see org.imixs.workflow.engine.EventLogService * @author rsoika @@ -75,174 +77,170 @@ @javax.persistence.Entity public class EventLog implements java.io.Serializable { - private static final long serialVersionUID = 1L; - private String id; - private String topic; - private String ref; - private Integer version; - private Calendar created; - private Map> data; - - - /** - * default constructor for JPA - */ - public EventLog() { - super(); - } - - - - /** - * Creates a new EventLog entity. - * - * @param topic - the event topic - * @param ref - the reference to the associated document entity - * @param data - a optional data list - */ - public EventLog(String topic, String ref, Map> data) { - // Generate a new uniqueId - this.id = WorkflowKernel.generateUniqueID(); - // Initialize objects - Calendar cal = Calendar.getInstance(); - this.created = cal; - this.topic = topic; - this.ref = ref; - this.data = data; - } - - - - /** - * returns the unique identifier for the Entity. - * - * @return universal id - */ - @Id - public String getId() { - return id; - } - - protected void setId(String aID) { - id = aID; - } - - @Version - public Integer getVersion() { - return version; - } - - public void setVersion(Integer version) { - this.version = version; - } - - /** - * returns the topic property of the entity instance. - * - * @return - */ - public String getTopic() { - return topic; - } - - public void setTopic(String topic) { - this.topic = topic; - } - - /** - * returns the reference ID ($uniqueid) of the associated document or workitem instance. - * - * @return - */ - public String getRef() { - return ref; - } - - public void setRef(String ref) { - this.ref = ref; - } - - /** - * returns the creation point of time. - * - * @return time of creation - */ - @Temporal(TemporalType.TIMESTAMP) - public Calendar getCreated() { - return created; - } - - public void setCreated(Calendar created) { - this.created = created; - } - - /** - * returns the data object part of the Entity represented by a java.util.Map - *

    - * Data is loaded eager because it is read in any case by the DocumentService. - * - * @return Map - */ - @Lob - @Basic(fetch = FetchType.EAGER) - public Map> getData() { - return data; - } - - /** - * sets a data object for this Entity. - *

    - * Note: the modified timestamp will be updated automatically to the current point of time (see - * setModified) independent from the value of the item $modified. The item $modified will be - * updated by the DocumentService on read. - * - * @param data - * @throws InvalidAccessException if $modified is missing - */ - public void setData(Map> itemCol) { - this.data = itemCol; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((id == null) ? 0 : id.hashCode()); - result = prime * result + ((ref == null) ? 0 : ref.hashCode()); - result = prime * result + ((topic == null) ? 0 : topic.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - EventLog other = (EventLog) obj; - if (id == null) { - if (other.id != null) - return false; - } else if (!id.equals(other.id)) - return false; - if (ref == null) { - if (other.ref != null) - return false; - } else if (!ref.equals(other.ref)) - return false; - if (topic == null) { - if (other.topic != null) - return false; - } else if (!topic.equals(other.topic)) - return false; - return true; - } - - @Override - public String toString() { - return this.getTopic() + ":" + this.getId(); - } + private static final long serialVersionUID = 1L; + private String id; + private String topic; + private String ref; + private Integer version; + private Calendar created; + private Map> data; + + /** + * default constructor for JPA + */ + public EventLog() { + super(); + } + + /** + * Creates a new EventLog entity. + * + * @param topic - the event topic + * @param ref - the reference to the associated document entity + * @param data - a optional data list + */ + public EventLog(String topic, String ref, Map> data) { + // Generate a new uniqueId + this.id = WorkflowKernel.generateUniqueID(); + // Initialize objects + Calendar cal = Calendar.getInstance(); + this.created = cal; + this.topic = topic; + this.ref = ref; + this.data = data; + } + + /** + * returns the unique identifier for the Entity. + * + * @return universal id + */ + @Id + public String getId() { + return id; + } + + protected void setId(String aID) { + id = aID; + } + + @Version + public Integer getVersion() { + return version; + } + + public void setVersion(Integer version) { + this.version = version; + } + + /** + * returns the topic property of the entity instance. + * + * @return + */ + public String getTopic() { + return topic; + } + + public void setTopic(String topic) { + this.topic = topic; + } + + /** + * returns the reference ID ($uniqueid) of the associated document or workitem + * instance. + * + * @return + */ + public String getRef() { + return ref; + } + + public void setRef(String ref) { + this.ref = ref; + } + + /** + * returns the creation point of time. + * + * @return time of creation + */ + @Temporal(TemporalType.TIMESTAMP) + public Calendar getCreated() { + return created; + } + + public void setCreated(Calendar created) { + this.created = created; + } + + /** + * returns the data object part of the Entity represented by a java.util.Map + *

    + * Data is loaded eager because it is read in any case by the DocumentService. + * + * @return Map + */ + @Lob + @Basic(fetch = FetchType.EAGER) + public Map> getData() { + return data; + } + + /** + * sets a data object for this Entity. + *

    + * Note: the modified timestamp will be updated automatically to the current + * point of time (see setModified) independent from the value of the item + * $modified. The item $modified will be updated by the DocumentService on read. + * + * @param data + * @throws InvalidAccessException if $modified is missing + */ + public void setData(Map> itemCol) { + this.data = itemCol; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((id == null) ? 0 : id.hashCode()); + result = prime * result + ((ref == null) ? 0 : ref.hashCode()); + result = prime * result + ((topic == null) ? 0 : topic.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + EventLog other = (EventLog) obj; + if (id == null) { + if (other.id != null) + return false; + } else if (!id.equals(other.id)) + return false; + if (ref == null) { + if (other.ref != null) + return false; + } else if (!ref.equals(other.ref)) + return false; + if (topic == null) { + if (other.topic != null) + return false; + } else if (!topic.equals(other.topic)) + return false; + return true; + } + + @Override + public String toString() { + return this.getTopic() + ":" + this.getId(); + } } diff --git a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/plugins/AbstractPlugin.java b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/plugins/AbstractPlugin.java index 219d6df67..5f3ffeb38 100644 --- a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/plugins/AbstractPlugin.java +++ b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/plugins/AbstractPlugin.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *

    - *  Imixs Workflow 
    +/*  
    + *  Imixs-Workflow 
    + *  
      *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
      *  http://www.imixs.com
      *  
    @@ -22,10 +22,9 @@
      *      https://github.com/imixs/imixs-workflow
      *  
      *  Contributors:  
    - *      Imixs Software Solutions GmbH - initial API and implementation
    + *      Imixs Software Solutions GmbH - Project Management
      *      Ralph Soika - Software Developer
    - * 
    - *******************************************************************************/ + */ package org.imixs.workflow.engine.plugins; @@ -49,123 +48,121 @@ public abstract class AbstractPlugin implements Plugin { - public static final String INVALID_ITEMVALUE_FORMAT = "INVALID_ITEMVALUE_FORMAT"; - public static final String INVALID_PROPERTYVALUE_FORMAT = "INVALID_PROPERTYVALUE_FORMAT"; - - private WorkflowContext ctx; - private WorkflowService workflowService; - - /** - * Initialize Plugin and get an instance of the EJB Session Context - */ - public void init(WorkflowContext actx) throws PluginException { - ctx = actx; - // get WorkflowService by check for an instance of WorkflowService - if (actx instanceof WorkflowService) { - // yes we are running in a WorkflowService EJB - workflowService = (WorkflowService) actx; - } - } - - @Override - public void close(boolean rollbackTransaction) throws PluginException { - - } - - public WorkflowContext getCtx() { - return ctx; - } - - /** - * Returns an instance of the WorkflowService EJB. - * - * @return - */ - public WorkflowService getWorkflowService() { - return workflowService; - } - - - /** - * This method merges the values of fieldList into valueList and test for duplicates. - * - * If an entry of the fieldList is a single key value, than the values to be merged are read from - * the corresponding documentContext property - * - * e.g. 'namTeam' -> maps the values of the documentContext property 'namteam' into the valueList - * - * If an entry of the fieldList is in square brackets, than the comma separated elements are - * mapped into the valueList - * - * e.g. '[user1,user2]' - maps the values 'user1' and 'user2' int the valueList. Also Curly - * brackets are allowed '{user1,user2}' - * - * - * @param valueList - * @param fieldList - */ - @SuppressWarnings({"unchecked", "rawtypes"}) - public void mergeFieldList(ItemCollection documentContext, List valueList, - List fieldList) { - if (valueList == null || fieldList == null) - return; - List values = null; - if (fieldList.size() > 0) { - // iterate over the fieldList - for (String key : fieldList) { - if (key == null) { - continue; - } - key = key.trim(); - // test if key contains square or curly brackets? - if ((key.startsWith("[") && key.endsWith("]")) - || (key.startsWith("{") && key.endsWith("}"))) { - // extract the value list with regExpression (\s matches any - // white space, The * applies the match zero or more times. - // So \s* means "match any white space zero or more times". - // We look for this before and after the comma.) - values = Arrays.asList(key.substring(1, key.length() - 1).split("\\s*,\\s*")); - } else { - // extract value list form documentContext - values = documentContext.getItemValue(key); + public static final String INVALID_ITEMVALUE_FORMAT = "INVALID_ITEMVALUE_FORMAT"; + public static final String INVALID_PROPERTYVALUE_FORMAT = "INVALID_PROPERTYVALUE_FORMAT"; + + private WorkflowContext ctx; + private WorkflowService workflowService; + + /** + * Initialize Plugin and get an instance of the EJB Session Context + */ + public void init(WorkflowContext actx) throws PluginException { + ctx = actx; + // get WorkflowService by check for an instance of WorkflowService + if (actx instanceof WorkflowService) { + // yes we are running in a WorkflowService EJB + workflowService = (WorkflowService) actx; } - // now append the values into p_VectorDestination - if ((values != null) && (values.size() > 0)) { - for (Object o : values) { - // append only if not used - if (valueList.indexOf(o) == -1) - valueList.add(o); - } - } - } } - } + @Override + public void close(boolean rollbackTransaction) throws PluginException { - /** - * This method removes duplicates and null values from a vector. - * - * @param valueList - list of elements - */ - public List uniqueList(List valueList) { - int iVectorSize = valueList.size(); - Vector cleanedVector = new Vector(); + } - for (int i = 0; i < iVectorSize; i++) { - Object o = valueList.get(i); - if (o == null || cleanedVector.indexOf(o) > -1 || "".equals(o.toString())) - continue; + public WorkflowContext getCtx() { + return ctx; + } - // add unique object - cleanedVector.add(o); + /** + * Returns an instance of the WorkflowService EJB. + * + * @return + */ + public WorkflowService getWorkflowService() { + return workflowService; } - valueList = cleanedVector; - // do not work with empty vectors.... - if (valueList.size() == 0) - valueList.add(""); - return valueList; - } + /** + * This method merges the values of fieldList into valueList and test for + * duplicates. + * + * If an entry of the fieldList is a single key value, than the values to be + * merged are read from the corresponding documentContext property + * + * e.g. 'namTeam' -> maps the values of the documentContext property 'namteam' + * into the valueList + * + * If an entry of the fieldList is in square brackets, than the comma separated + * elements are mapped into the valueList + * + * e.g. '[user1,user2]' - maps the values 'user1' and 'user2' int the valueList. + * Also Curly brackets are allowed '{user1,user2}' + * + * + * @param valueList + * @param fieldList + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public void mergeFieldList(ItemCollection documentContext, List valueList, List fieldList) { + if (valueList == null || fieldList == null) + return; + List values = null; + if (fieldList.size() > 0) { + // iterate over the fieldList + for (String key : fieldList) { + if (key == null) { + continue; + } + key = key.trim(); + // test if key contains square or curly brackets? + if ((key.startsWith("[") && key.endsWith("]")) || (key.startsWith("{") && key.endsWith("}"))) { + // extract the value list with regExpression (\s matches any + // white space, The * applies the match zero or more times. + // So \s* means "match any white space zero or more times". + // We look for this before and after the comma.) + values = Arrays.asList(key.substring(1, key.length() - 1).split("\\s*,\\s*")); + } else { + // extract value list form documentContext + values = documentContext.getItemValue(key); + } + // now append the values into p_VectorDestination + if ((values != null) && (values.size() > 0)) { + for (Object o : values) { + // append only if not used + if (valueList.indexOf(o) == -1) + valueList.add(o); + } + } + } + } + + } + /** + * This method removes duplicates and null values from a vector. + * + * @param valueList - list of elements + */ + public List uniqueList(List valueList) { + int iVectorSize = valueList.size(); + Vector cleanedVector = new Vector(); + + for (int i = 0; i < iVectorSize; i++) { + Object o = valueList.get(i); + if (o == null || cleanedVector.indexOf(o) > -1 || "".equals(o.toString())) + continue; + + // add unique object + cleanedVector.add(o); + } + valueList = cleanedVector; + // do not work with empty vectors.... + if (valueList.size() == 0) + valueList.add(""); + + return valueList; + } } diff --git a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/plugins/AccessPlugin.java b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/plugins/AccessPlugin.java index e0c35d8f4..b78a370f9 100644 --- a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/plugins/AccessPlugin.java +++ b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/plugins/AccessPlugin.java @@ -34,22 +34,20 @@ /** * Deprecated - see PaticipantAdapter. - * + * * @author Ralph Soika * @version 3.0 * @see org.imixs.workflow.WorkflowManager */ @Deprecated public class AccessPlugin extends AbstractPlugin { - private static Logger logger = Logger.getLogger(AccessPlugin.class.getName()); + private static Logger logger = Logger.getLogger(AccessPlugin.class.getName()); - - @Deprecated - public ItemCollection run(ItemCollection adocumentContext, ItemCollection documentActivity) throws PluginException { - - logger.warning("The AccessPlugin is deprecated and can be removed from this model!"); - return adocumentContext; - } + @Deprecated + public ItemCollection run(ItemCollection adocumentContext, ItemCollection documentActivity) throws PluginException { + logger.warning("The AccessPlugin is deprecated and can be removed from this model!"); + return adocumentContext; + } } diff --git a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/plugins/AnalysisPlugin.java b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/plugins/AnalysisPlugin.java index 572450221..40d1e9619 100644 --- a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/plugins/AnalysisPlugin.java +++ b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/plugins/AnalysisPlugin.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *
    - *  Imixs Workflow 
    +/*  
    + *  Imixs-Workflow 
    + *  
      *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
      *  http://www.imixs.com
      *  
    @@ -22,10 +22,9 @@
      *      https://github.com/imixs/imixs-workflow
      *  
      *  Contributors:  
    - *      Imixs Software Solutions GmbH - initial API and implementation
    + *      Imixs Software Solutions GmbH - Project Management
      *      Ralph Soika - Software Developer
    - * 
    - *******************************************************************************/ + */ package org.imixs.workflow.engine.plugins; @@ -37,8 +36,8 @@ import org.imixs.workflow.exceptions.PluginException; /** - * This plugin can be used to measure the time of any phase during a workflow. The plugin can be - * configured by the activity result : + * This plugin can be used to measure the time of any phase during a workflow. + * The plugin can be configured by the activity result : * * Example: * M1 @@ -54,10 +53,11 @@ * * definens a end point named 'M1' * - * The result will be stored into the txtWorkflowActivityLog (comments) and also the Plugin will - * create the following fields: + * The result will be stored into the txtWorkflowActivityLog (comments) and also + * the Plugin will create the following fields: * - * - datMeasurePointStart_M1 : contains the start time points (list latest entry on top!) + * - datMeasurePointStart_M1 : contains the start time points (list latest entry + * on top!) * * - datMeasurePointEnd_M1 : contains the end time points (list) * @@ -69,216 +69,209 @@ * */ public class AnalysisPlugin extends AbstractPlugin { - public static final String INVALID_FORMAT = "INVALID_FORMAT"; - - private static Logger logger = Logger.getLogger(AnalysisPlugin.class.getName()); - - @SuppressWarnings({"unchecked", "rawtypes"}) - @Override - public ItemCollection run(ItemCollection documentContext, ItemCollection documentActivity) - throws PluginException { - - // parse for intem name=measurepoint.... - String sActivityResult = documentActivity.getItemValueString("txtActivityResult"); - List measurePoints = evaluate(sActivityResult, documentContext); - - for (MeasurePoint point : measurePoints) { - - if ("start".equals(point.type)) { - - List valuesStart = documentContext.getItemValue("datMeasurePointStart_" + point.name); - List valuesStop = documentContext.getItemValue("datMeasurePointStop_" + point.name); - - // the length of stop list must be the same as the start list - if (valuesStart.size() != valuesStop.size()) { - logger.warning("[AnalysisPlugin] Wrong measure point '" + point.name - + "' starttime without stoptime! - please check model entry " - + documentActivity.getItemValueInteger("numProcessID") + "." - + documentActivity.getItemValueInteger("numActivityID") - + " measurepoint will be ignored!"); - continue; + public static final String INVALID_FORMAT = "INVALID_FORMAT"; + + private static Logger logger = Logger.getLogger(AnalysisPlugin.class.getName()); + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override + public ItemCollection run(ItemCollection documentContext, ItemCollection documentActivity) throws PluginException { + + // parse for intem name=measurepoint.... + String sActivityResult = documentActivity.getItemValueString("txtActivityResult"); + List measurePoints = evaluate(sActivityResult, documentContext); + + for (MeasurePoint point : measurePoints) { + + if ("start".equals(point.type)) { + + List valuesStart = documentContext.getItemValue("datMeasurePointStart_" + point.name); + List valuesStop = documentContext.getItemValue("datMeasurePointStop_" + point.name); + + // the length of stop list must be the same as the start list + if (valuesStart.size() != valuesStop.size()) { + logger.warning("[AnalysisPlugin] Wrong measure point '" + point.name + + "' starttime without stoptime! - please check model entry " + + documentActivity.getItemValueInteger("numProcessID") + "." + + documentActivity.getItemValueInteger("numActivityID") + " measurepoint will be ignored!"); + continue; + } + + // add new start point + valuesStart.add(0, new Date()); + documentContext.replaceItemValue("datMeasurePointStart_" + point.name, valuesStart); + + } + if ("stop".equals(point.type)) { + List valuesStart = documentContext.getItemValue("datMeasurePointStart_" + point.name); + List valuesStop = documentContext.getItemValue("datMeasurePointStop_" + point.name); + + if (valuesStop.size() != (valuesStart.size() - 1)) { + logger.warning("[AnalysisPlugin] Wrong measure point '" + point.name + + "' stoptime without starttime! - please check model entry " + + documentActivity.getItemValueInteger("numProcessID") + "." + + documentActivity.getItemValueInteger("numActivityID") + " measurepoint will be ignored!"); + continue; + + } + // add new start point + valuesStop.add(0, new Date()); + documentContext.replaceItemValue("datMeasurePointStop_" + point.name, valuesStop); + + // now we add the new time range.... + int numTotal = documentContext.getItemValueInteger("numMeasurePoint_" + point.name); + Date start = (Date) valuesStart.get(0); + Date stop = (Date) valuesStop.get(0); + + long lStart = start.getTime() / 1000; + long lStop = stop.getTime() / 1000; + + numTotal = (int) (numTotal + (lStop - lStart)); + documentContext.replaceItemValue("numMeasurePoint_" + point.name, numTotal); + } } - // add new start point - valuesStart.add(0, new Date()); - documentContext.replaceItemValue("datMeasurePointStart_" + point.name, valuesStart); - - } - if ("stop".equals(point.type)) { - List valuesStart = documentContext.getItemValue("datMeasurePointStart_" + point.name); - List valuesStop = documentContext.getItemValue("datMeasurePointStop_" + point.name); - - if (valuesStop.size() != (valuesStart.size() - 1)) { - logger.warning("[AnalysisPlugin] Wrong measure point '" + point.name - + "' stoptime without starttime! - please check model entry " - + documentActivity.getItemValueInteger("numProcessID") + "." - + documentActivity.getItemValueInteger("numActivityID") - + " measurepoint will be ignored!"); - continue; + return documentContext; + } + /** + * This method parses the a string for xml tag + * xxx. Those tags will result in MeasurePoint + * + * + * + * + * M1 + * + * + * + * + * @return - a list of measure points + * @throws PluginException + * + */ + public List evaluate(String aString, ItemCollection documentContext) throws PluginException { + int iTagStartPos; + int iTagEndPos; + + int iContentStartPos; + int iContentEndPos; + + int iNameStartPos; + int iNameEndPos; + + int iTypeStartPos; + int iTypeEndPos; + + String sName = ""; + String sType = " "; + String sItemValue; + + List result = new ArrayList(); + + if (aString == null || aString.isEmpty()) + return result; + + // test if a tag exists... + while ((iTagStartPos = aString.toLowerCase().indexOf("", iTagStartPos); + + // if no end tag found return string unchanged... + if (iTagEndPos == -1) + throw new PluginException(ResultPlugin.class.getSimpleName(), INVALID_FORMAT, " expected!"); + + // reset pos vars + iContentStartPos = 0; + iContentEndPos = 0; + iNameStartPos = 0; + iNameEndPos = 0; + iTypeStartPos = 0; + iTypeEndPos = 0; + sName = ""; + sType = " "; + sItemValue = ""; + + // so we now search the beginning of the tag content + iContentEndPos = iTagEndPos; + // start pos is the last > before the iContentEndPos + String sTestString = aString.substring(0, iContentEndPos); + iContentStartPos = sTestString.lastIndexOf('>') + 1; + + // if no end tag found return string unchanged... + if (iContentStartPos >= iContentEndPos) + return result; + + iTagEndPos = iTagEndPos + "".length(); + + // now we have the start and end position of a tag and also the + // start and end pos of the value + + // next we check if the start tag contains a 'name' attribute + iNameStartPos = aString.toLowerCase().indexOf("name=", iTagStartPos); + // extract format string if available + // ' can be used instead of " chars! + // e.g.: name='txtName'> or name="txtName"> + if (iNameStartPos > -1 && iNameStartPos < iContentStartPos) { + // replace ' with " before content start pos + String sNamePart = aString.substring(0, iContentStartPos); + sNamePart = sNamePart.replace("'", "\""); + iNameStartPos = sNamePart.indexOf("\"", iNameStartPos) + 1; + iNameEndPos = sNamePart.indexOf("\"", iNameStartPos + 1); + sName = sNamePart.substring(iNameStartPos, iNameEndPos); + sName = sName.toLowerCase(); + } + + // next we check if the start tag contains a 'type' + // attribute + iTypeStartPos = aString.toLowerCase().indexOf("type=", iTagStartPos); + // extract format string if available + if (iTypeStartPos > -1 && iTypeStartPos < iContentStartPos) { + String sTypePart = aString.substring(0, iContentStartPos); + sTypePart = sTypePart.replace("'", "\""); + + iTypeStartPos = sTypePart.indexOf("\"", iTypeStartPos) + 1; + iTypeEndPos = sTypePart.indexOf("\"", iTypeStartPos + 1); + sType = sTypePart.substring(iTypeStartPos, iTypeEndPos); + sType = sType.toLowerCase(); + } + + // extract Item Value + sItemValue = aString.substring(iContentStartPos, iContentEndPos); + + // if name= measurepoint and type=start|stop then we can make a + // measurePoint! + + if ("measurepoint".equals(sName) && ("start".equals(sType) || "stop".equals(sType))) { + MeasurePoint point = new MeasurePoint(sItemValue.toLowerCase(), sType); + result.add(point); + + } + + // now cut the tag form the string + aString = aString.substring(0, iTagStartPos) + "" + aString.substring(iTagEndPos); } - // add new start point - valuesStop.add(0, new Date()); - documentContext.replaceItemValue("datMeasurePointStop_" + point.name, valuesStop); - - // now we add the new time range.... - int numTotal = documentContext.getItemValueInteger("numMeasurePoint_" + point.name); - Date start = (Date) valuesStart.get(0); - Date stop = (Date) valuesStop.get(0); - - long lStart = start.getTime() / 1000; - long lStop = stop.getTime() / 1000; - - numTotal = (int) (numTotal + (lStop - lStart)); - documentContext.replaceItemValue("numMeasurePoint_" + point.name, numTotal); - } - } - return documentContext; - } - - - - /** - * This method parses the a string for xml tag xxx. Those tags will - * result in MeasurePoint - * - * - * - * - * M1 - * - * - * - * - * @return - a list of measure points - * @throws PluginException - * - */ - public List evaluate(String aString, ItemCollection documentContext) - throws PluginException { - int iTagStartPos; - int iTagEndPos; - - int iContentStartPos; - int iContentEndPos; - - int iNameStartPos; - int iNameEndPos; - - int iTypeStartPos; - int iTypeEndPos; - - String sName = ""; - String sType = " "; - String sItemValue; - - List result = new ArrayList(); - - if (aString == null || aString.isEmpty()) - return result; - - // test if a tag exists... - while ((iTagStartPos = aString.toLowerCase().indexOf("", iTagStartPos); - - // if no end tag found return string unchanged... - if (iTagEndPos == -1) - throw new PluginException(ResultPlugin.class.getSimpleName(), INVALID_FORMAT, - " expected!"); - - // reset pos vars - iContentStartPos = 0; - iContentEndPos = 0; - iNameStartPos = 0; - iNameEndPos = 0; - iTypeStartPos = 0; - iTypeEndPos = 0; - sName = ""; - sType = " "; - sItemValue = ""; - - // so we now search the beginning of the tag content - iContentEndPos = iTagEndPos; - // start pos is the last > before the iContentEndPos - String sTestString = aString.substring(0, iContentEndPos); - iContentStartPos = sTestString.lastIndexOf('>') + 1; - - // if no end tag found return string unchanged... - if (iContentStartPos >= iContentEndPos) return result; - - iTagEndPos = iTagEndPos + "".length(); - - // now we have the start and end position of a tag and also the - // start and end pos of the value - - // next we check if the start tag contains a 'name' attribute - iNameStartPos = aString.toLowerCase().indexOf("name=", iTagStartPos); - // extract format string if available - // ' can be used instead of " chars! - // e.g.: name='txtName'> or name="txtName"> - if (iNameStartPos > -1 && iNameStartPos < iContentStartPos) { - // replace ' with " before content start pos - String sNamePart = aString.substring(0, iContentStartPos); - sNamePart = sNamePart.replace("'", "\""); - iNameStartPos = sNamePart.indexOf("\"", iNameStartPos) + 1; - iNameEndPos = sNamePart.indexOf("\"", iNameStartPos + 1); - sName = sNamePart.substring(iNameStartPos, iNameEndPos); - sName = sName.toLowerCase(); - } - - // next we check if the start tag contains a 'type' - // attribute - iTypeStartPos = aString.toLowerCase().indexOf("type=", iTagStartPos); - // extract format string if available - if (iTypeStartPos > -1 && iTypeStartPos < iContentStartPos) { - String sTypePart = aString.substring(0, iContentStartPos); - sTypePart = sTypePart.replace("'", "\""); - - iTypeStartPos = sTypePart.indexOf("\"", iTypeStartPos) + 1; - iTypeEndPos = sTypePart.indexOf("\"", iTypeStartPos + 1); - sType = sTypePart.substring(iTypeStartPos, iTypeEndPos); - sType = sType.toLowerCase(); - } - - // extract Item Value - sItemValue = aString.substring(iContentStartPos, iContentEndPos); - - // if name= measurepoint and type=start|stop then we can make a - // measurePoint! - - if ("measurepoint".equals(sName) && ("start".equals(sType) || "stop".equals(sType))) { - MeasurePoint point = new MeasurePoint(sItemValue.toLowerCase(), sType); - result.add(point); - - } - - // now cut the tag form the string - aString = aString.substring(0, iTagStartPos) + "" + aString.substring(iTagEndPos); } - return result; - } - - /** - * Measure Point - * - * @author rsoika - * - */ - private class MeasurePoint { - - public String name; - public String type;// start|stop + /** + * Measure Point + * + * @author rsoika + * + */ + private class MeasurePoint { + + public String name; + public String type;// start|stop + + public MeasurePoint(String name, String type) { + super(); + this.name = name; + this.type = type; + } - public MeasurePoint(String name, String type) { - super(); - this.name = name; - this.type = type; } - } - } diff --git a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/plugins/ApplicationPlugin.java b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/plugins/ApplicationPlugin.java index 033cf82a1..504c26828 100644 --- a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/plugins/ApplicationPlugin.java +++ b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/plugins/ApplicationPlugin.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *
    - *  Imixs Workflow 
    +/*  
    + *  Imixs-Workflow 
    + *  
      *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
      *  http://www.imixs.com
      *  
    @@ -22,10 +22,9 @@
      *      https://github.com/imixs/imixs-workflow
      *  
      *  Contributors:  
    - *      Imixs Software Solutions GmbH - initial API and implementation
    + *      Imixs Software Solutions GmbH - Project Management
      *      Ralph Soika - Software Developer
    - * 
    - *******************************************************************************/ + */ package org.imixs.workflow.engine.plugins; @@ -44,23 +43,26 @@ *
  • $WorkflowSummary - Summary * * - * These settings can be configured by the imixs modeler on the Application Property Tab on a - * ProcessEntity. + * These settings can be configured by the imixs modeler on the Application + * Property Tab on a ProcessEntity. * - * The Plugin determines the new settings by fetching the next ProcessEntity. The Next ProcessEntity - * is defined by the ActivityEntity attribute 'numNextProcessID' + * The Plugin determines the new settings by fetching the next ProcessEntity. + * The Next ProcessEntity is defined by the ActivityEntity attribute + * 'numNextProcessID' * * * Version 1.1 * - * The Plugin will test if the provided Model supports ExtendedModels. If so the Plugin will fetch - * the next ProcessEntity by the current used modelVersion of the workitem. + * The Plugin will test if the provided Model supports ExtendedModels. If so the + * Plugin will fetch the next ProcessEntity by the current used modelVersion of + * the workitem. * - * Version 1.2 The plugin submits the new settings directly in the run() method, so other plugins - * can access the new properties for further operations + * Version 1.2 The plugin submits the new settings directly in the run() method, + * so other plugins can access the new properties for further operations * http://java.net/jira/browse/IMIXS_WORKFLOW-81 * - * Version 1.3: type, workflowgroup and workfowstatus are handled by the WorkflowKernel + * Version 1.3: type, workflowgroup and workfowstatus are handled by the + * WorkflowKernel * * @author Ralph Soika * @version 1.3 @@ -69,81 +71,78 @@ */ public class ApplicationPlugin extends AbstractPlugin { - - public final static String WORKFLOWABSTRACT = "$workflowabstract"; - public final static String WORKFLOWSUMMARY = "$workflowsummary"; - - - public static final String PROCESS_UNDEFINED = "PROCESS_UNDEFINED"; - - private ItemCollection documentContext; - private String sEditorID; - private String sImageURL; - private String sAbstract; - private String sSummary; - @SuppressWarnings("unused") - private static Logger logger = Logger.getLogger(ApplicationPlugin.class.getName()); - - public ItemCollection run(ItemCollection adocumentContext, ItemCollection adocumentActivity) - throws PluginException { - - documentContext = adocumentContext; - - sEditorID = null; - sImageURL = null; - sAbstract = null; - sSummary = null; - ItemCollection itemColNextProcess = null; - - // get next process entity - try { - // itemColNextProcess = this.getWorkflowService().evalNextTask(adocumentContext, - // adocumentActivity); - itemColNextProcess = this.getWorkflowService().evalNextTask(adocumentContext); - } catch (ModelException e) { - throw new PluginException(ApplicationPlugin.class.getSimpleName(), e.getErrorCode(), - e.getMessage()); - } - - // fetch Editor and Image - sEditorID = itemColNextProcess.getItemValueString("txtEditorID"); - sImageURL = itemColNextProcess.getItemValueString("txtImageURL"); - - // fetch workflow Abstract - sAbstract = itemColNextProcess.getItemValueString("txtworkflowabstract"); - if (!"".equals(sAbstract)) - sAbstract = getWorkflowService().adaptText(sAbstract, documentContext); - - // fetch workflow Abstract - sSummary = itemColNextProcess.getItemValueString("txtworkflowsummary"); - if (!"".equals(sSummary)) - sSummary = getWorkflowService().adaptText(sSummary, documentContext); - - // submit data now into documentcontext - - // set Editor if value is defined - if (sEditorID != null && !"".equals(sEditorID)) - documentContext.replaceItemValue("txtWorkflowEditorID", sEditorID); - - // set ImageURl if one is defined - if (sImageURL != null && !"".equals(sImageURL)) - documentContext.replaceItemValue("txtWorkflowImageURL", sImageURL); - - /* - * We still support the deprecated fields here - see issue #265 can be removed with version - * 4.3.0 - */ - // set Abstract - if (sAbstract != null) { - documentContext.replaceItemValue(WORKFLOWABSTRACT, sAbstract); - documentContext.replaceItemValue("txtworkflowabstract", sAbstract); - } - // set Summary - if (sSummary != null) { - documentContext.replaceItemValue(WORKFLOWSUMMARY, sSummary); - documentContext.replaceItemValue("txtworkflowsummary", sSummary); + public final static String WORKFLOWABSTRACT = "$workflowabstract"; + public final static String WORKFLOWSUMMARY = "$workflowsummary"; + + public static final String PROCESS_UNDEFINED = "PROCESS_UNDEFINED"; + + private ItemCollection documentContext; + private String sEditorID; + private String sImageURL; + private String sAbstract; + private String sSummary; + @SuppressWarnings("unused") + private static Logger logger = Logger.getLogger(ApplicationPlugin.class.getName()); + + public ItemCollection run(ItemCollection adocumentContext, ItemCollection adocumentActivity) + throws PluginException { + + documentContext = adocumentContext; + + sEditorID = null; + sImageURL = null; + sAbstract = null; + sSummary = null; + ItemCollection itemColNextProcess = null; + + // get next process entity + try { + // itemColNextProcess = this.getWorkflowService().evalNextTask(adocumentContext, + // adocumentActivity); + itemColNextProcess = this.getWorkflowService().evalNextTask(adocumentContext); + } catch (ModelException e) { + throw new PluginException(ApplicationPlugin.class.getSimpleName(), e.getErrorCode(), e.getMessage()); + } + + // fetch Editor and Image + sEditorID = itemColNextProcess.getItemValueString("txtEditorID"); + sImageURL = itemColNextProcess.getItemValueString("txtImageURL"); + + // fetch workflow Abstract + sAbstract = itemColNextProcess.getItemValueString("txtworkflowabstract"); + if (!"".equals(sAbstract)) + sAbstract = getWorkflowService().adaptText(sAbstract, documentContext); + + // fetch workflow Abstract + sSummary = itemColNextProcess.getItemValueString("txtworkflowsummary"); + if (!"".equals(sSummary)) + sSummary = getWorkflowService().adaptText(sSummary, documentContext); + + // submit data now into documentcontext + + // set Editor if value is defined + if (sEditorID != null && !"".equals(sEditorID)) + documentContext.replaceItemValue("txtWorkflowEditorID", sEditorID); + + // set ImageURl if one is defined + if (sImageURL != null && !"".equals(sImageURL)) + documentContext.replaceItemValue("txtWorkflowImageURL", sImageURL); + + /* + * We still support the deprecated fields here - see issue #265 can be removed + * with version 4.3.0 + */ + // set Abstract + if (sAbstract != null) { + documentContext.replaceItemValue(WORKFLOWABSTRACT, sAbstract); + documentContext.replaceItemValue("txtworkflowabstract", sAbstract); + } + // set Summary + if (sSummary != null) { + documentContext.replaceItemValue(WORKFLOWSUMMARY, sSummary); + documentContext.replaceItemValue("txtworkflowsummary", sSummary); + } + return documentContext; } - return documentContext; - } } diff --git a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/plugins/ApproverPlugin.java b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/plugins/ApproverPlugin.java index 2ac028d6f..b314847e9 100644 --- a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/plugins/ApproverPlugin.java +++ b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/plugins/ApproverPlugin.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *
    - *  Imixs Workflow 
    +/*  
    + *  Imixs-Workflow 
    + *  
      *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
      *  http://www.imixs.com
      *  
    @@ -22,10 +22,9 @@
      *      https://github.com/imixs/imixs-workflow
      *  
      *  Contributors:  
    - *      Imixs Software Solutions GmbH - initial API and implementation
    + *      Imixs Software Solutions GmbH - Project Management
      *      Ralph Soika - Software Developer
    - * 
    - *******************************************************************************/ + */ package org.imixs.workflow.engine.plugins; @@ -38,8 +37,8 @@ import org.imixs.workflow.exceptions.PluginException; /** - * This plug-in is used to manage multiple users involved in a approver procedure. The list of - * approvers can be declared within the workflow result: + * This plug-in is used to manage multiple users involved in a approver + * procedure. The list of approvers can be declared within the workflow result: *

    * Example: * @@ -49,8 +48,9 @@ * } * * - * The tag value (e.g. 'ReviewTeam') declares the source item holding the users involved in the - * approver procedure. The plugin creates the following items to monitor the approver procedure: + * The tag value (e.g. 'ReviewTeam') declares the source item holding the users + * involved in the approver procedure. The plugin creates the following items to + * monitor the approver procedure: * *

      * {@code
    @@ -59,14 +59,15 @@
      * }
      * 
    * - * If the source item is updated during the approving process, the plugin will add new userIDs if - * these new UserIDs are not yet listed in the item [SOURCEITEMNAME]$ApprovedBy. + * If the source item is updated during the approving process, the plugin will + * add new userIDs if these new UserIDs are not yet listed in the item + * [SOURCEITEMNAME]$ApprovedBy. *

    - * If the attribute 'refresh' is set to true, the list [SOURCEITEMNAME]$Approvers will be updated - * (default is true). + * If the attribute 'refresh' is set to true, the list + * [SOURCEITEMNAME]$Approvers will be updated (default is true). *

    - * If the attribute 'reset' is set to true, the list [SOURCEITEMNAME]$Approvers will be reseted and - * the item [SOURCEITEMNAME]$ApprovedBy will be cleared. + * If the attribute 'reset' is set to true, the list [SOURCEITEMNAME]$Approvers + * will be reseted and the item [SOURCEITEMNAME]$ApprovedBy will be cleared. * * @author rsoika * @version 2.0 @@ -74,147 +75,144 @@ */ public class ApproverPlugin extends AbstractPlugin { - private static Logger logger = Logger.getLogger(ApproverPlugin.class.getName()); - - public static String APPROVEDBY = "$approvedby"; - public static String APPROVERS = "$approvers"; - - private static String EVAL_APPROVEDBY = "approvedby"; - - - /** - * computes the approvedBy and appovers name fields. - * - * - * @throws PluginException - * - **/ - @SuppressWarnings("unchecked") - @Override - public ItemCollection run(ItemCollection workitem, ItemCollection documentActivity) - throws PluginException { - boolean refresh = false; - boolean reset = false; - - ItemCollection evalItemCollection = - this.getWorkflowService().evalWorkflowResult(documentActivity, workitem); - - // test for items with name 'approvedby' - if (evalItemCollection != null && evalItemCollection.hasItem(EVAL_APPROVEDBY)) { - boolean debug = logger.isLoggable(Level.FINE); - - // test refresh - refresh = true; - if ("false".equals(evalItemCollection.getItemValueString(EVAL_APPROVEDBY + ".refresh"))) { - refresh = false; - } - if (debug) { - logger.fine("refresh=" + refresh); - } - // test reset - reset = false; - if ("true".equals(evalItemCollection.getItemValueString(EVAL_APPROVEDBY + ".reset"))) { - reset = true; - } - if (debug) { - logger.fine("reset=" + reset); - } - // 1.) extract the groups definitions - List groups = evalItemCollection.getItemValue(EVAL_APPROVEDBY); - - // 2.) iterate over all definitions - for (String aGroup : groups) { - - // fetch name list... - List nameList = workitem.getItemValue(aGroup); - // remove empty entries... - nameList.removeIf(item -> item == null || "".equals(item)); - // create a new instance of a Vector to avoid setting the - // same vector as reference! We also distinct the List here. - List newAppoverList = nameList.stream().distinct().collect(Collectors.toList()); + private static Logger logger = Logger.getLogger(ApproverPlugin.class.getName()); + + public static String APPROVEDBY = "$approvedby"; + public static String APPROVERS = "$approvers"; + + private static String EVAL_APPROVEDBY = "approvedby"; + + /** + * computes the approvedBy and appovers name fields. + * + * + * @throws PluginException + * + **/ + @SuppressWarnings("unchecked") + @Override + public ItemCollection run(ItemCollection workitem, ItemCollection documentActivity) throws PluginException { + boolean refresh = false; + boolean reset = false; - if (!workitem.hasItem(aGroup + APPROVERS) || reset) { - if (debug) { - logger.fine("creating new approver list: " + aGroup + "=" + newAppoverList); - } - workitem.replaceItemValue(aGroup + APPROVERS, newAppoverList); - workitem.removeItem(aGroup + APPROVEDBY); - } else { - - // refresh approver list..... - if (refresh) { - refreshApprovers(workitem, aGroup); - } - - // 2.) add current approver to approvedBy..... - String currentAppover = getWorkflowService().getUserName(); - List listApprovedBy = workitem.getItemValue(aGroup + APPROVEDBY); - List listApprovers = workitem.getItemValue(aGroup + APPROVERS); - if (debug) { - logger.fine("approved by: " + currentAppover); - } - if (listApprovers.contains(currentAppover) && !listApprovedBy.contains(currentAppover)) { - listApprovers.remove(currentAppover); - listApprovedBy.add(currentAppover); - // remove empty entries... - listApprovers.removeIf(item -> item == null || "".equals(item)); - listApprovedBy.removeIf(item -> item == null || "".equals(item)); - workitem.replaceItemValue(aGroup + APPROVERS, listApprovers); - workitem.replaceItemValue(aGroup + APPROVEDBY, listApprovedBy); + ItemCollection evalItemCollection = this.getWorkflowService().evalWorkflowResult(documentActivity, workitem); + + // test for items with name 'approvedby' + if (evalItemCollection != null && evalItemCollection.hasItem(EVAL_APPROVEDBY)) { + boolean debug = logger.isLoggable(Level.FINE); + + // test refresh + refresh = true; + if ("false".equals(evalItemCollection.getItemValueString(EVAL_APPROVEDBY + ".refresh"))) { + refresh = false; + } + if (debug) { + logger.fine("refresh=" + refresh); + } + // test reset + reset = false; + if ("true".equals(evalItemCollection.getItemValueString(EVAL_APPROVEDBY + ".reset"))) { + reset = true; + } if (debug) { - logger.fine("new list of approvedby: " + aGroup + "=" + listApprovedBy); + logger.fine("reset=" + reset); } - } + // 1.) extract the groups definitions + List groups = evalItemCollection.getItemValue(EVAL_APPROVEDBY); + + // 2.) iterate over all definitions + for (String aGroup : groups) { + + // fetch name list... + List nameList = workitem.getItemValue(aGroup); + // remove empty entries... + nameList.removeIf(item -> item == null || "".equals(item)); + // create a new instance of a Vector to avoid setting the + // same vector as reference! We also distinct the List here. + List newAppoverList = nameList.stream().distinct().collect(Collectors.toList()); + + if (!workitem.hasItem(aGroup + APPROVERS) || reset) { + if (debug) { + logger.fine("creating new approver list: " + aGroup + "=" + newAppoverList); + } + workitem.replaceItemValue(aGroup + APPROVERS, newAppoverList); + workitem.removeItem(aGroup + APPROVEDBY); + } else { + + // refresh approver list..... + if (refresh) { + refreshApprovers(workitem, aGroup); + } + + // 2.) add current approver to approvedBy..... + String currentAppover = getWorkflowService().getUserName(); + List listApprovedBy = workitem.getItemValue(aGroup + APPROVEDBY); + List listApprovers = workitem.getItemValue(aGroup + APPROVERS); + if (debug) { + logger.fine("approved by: " + currentAppover); + } + if (listApprovers.contains(currentAppover) && !listApprovedBy.contains(currentAppover)) { + listApprovers.remove(currentAppover); + listApprovedBy.add(currentAppover); + // remove empty entries... + listApprovers.removeIf(item -> item == null || "".equals(item)); + listApprovedBy.removeIf(item -> item == null || "".equals(item)); + workitem.replaceItemValue(aGroup + APPROVERS, listApprovers); + workitem.replaceItemValue(aGroup + APPROVEDBY, listApprovedBy); + if (debug) { + logger.fine("new list of approvedby: " + aGroup + "=" + listApprovedBy); + } + } + } + } + } - } + return workitem; } - return workitem; - } - - /** - * This method verify if a new member of the existing approvers is available and adds new member - * into the sourceItem$Approvers'. (issue #150) - * - * @param workitem - * @param sourceItem - item name of the source user list - */ - @SuppressWarnings("unchecked") - void refreshApprovers(ItemCollection workitem, String sourceItem) { - boolean debug = logger.isLoggable(Level.FINE); - List nameList = workitem.getItemValue(sourceItem); - // remove empty entries... - nameList.removeIf(item -> item == null || "".equals(item)); - - // create a new instance of a Vector to avoid setting the - // same vector as reference! We also distinct the List here. - List newAppoverList = nameList.stream().distinct().collect(Collectors.toList()); - - // verify if a new member of the existing approvers is available... - // (issue #150) - List listApprovedBy = workitem.getItemValue(sourceItem + APPROVEDBY); - List listApprovers = workitem.getItemValue(sourceItem + APPROVERS); - boolean update = false; - for (String approver : newAppoverList) { - if (!listApprovedBy.contains(approver) && !listApprovers.contains(approver)) { - // add the new member to the existing approver list - if (debug) { - logger.fine("adding new approver to list '" + sourceItem + APPROVERS + "'"); - } - listApprovers.add(approver); + /** + * This method verify if a new member of the existing approvers is available and + * adds new member into the sourceItem$Approvers'. (issue #150) + * + * @param workitem + * @param sourceItem - item name of the source user list + */ + @SuppressWarnings("unchecked") + void refreshApprovers(ItemCollection workitem, String sourceItem) { + boolean debug = logger.isLoggable(Level.FINE); + List nameList = workitem.getItemValue(sourceItem); // remove empty entries... - listApprovers.removeIf(item -> item == null || "".equals(item)); + nameList.removeIf(item -> item == null || "".equals(item)); - update = true; - } - } - if (update) { - if (debug) { - logger.fine("updating approver list '" + sourceItem + APPROVERS + "'"); - } - workitem.replaceItemValue(sourceItem + APPROVERS, listApprovers); + // create a new instance of a Vector to avoid setting the + // same vector as reference! We also distinct the List here. + List newAppoverList = nameList.stream().distinct().collect(Collectors.toList()); + + // verify if a new member of the existing approvers is available... + // (issue #150) + List listApprovedBy = workitem.getItemValue(sourceItem + APPROVEDBY); + List listApprovers = workitem.getItemValue(sourceItem + APPROVERS); + boolean update = false; + for (String approver : newAppoverList) { + if (!listApprovedBy.contains(approver) && !listApprovers.contains(approver)) { + // add the new member to the existing approver list + if (debug) { + logger.fine("adding new approver to list '" + sourceItem + APPROVERS + "'"); + } + listApprovers.add(approver); + // remove empty entries... + listApprovers.removeIf(item -> item == null || "".equals(item)); + + update = true; + } + } + if (update) { + if (debug) { + logger.fine("updating approver list '" + sourceItem + APPROVERS + "'"); + } + workitem.replaceItemValue(sourceItem + APPROVERS, listApprovers); + } } - } } diff --git a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/plugins/DocumentComposerPlugin.java b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/plugins/DocumentComposerPlugin.java index f097a1f0c..6c6247038 100644 --- a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/plugins/DocumentComposerPlugin.java +++ b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/plugins/DocumentComposerPlugin.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *

    - *  Imixs Workflow 
    +/*  
    + *  Imixs-Workflow 
    + *  
      *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
      *  http://www.imixs.com
      *  
    @@ -22,10 +22,9 @@
      *      https://github.com/imixs/imixs-workflow
      *  
      *  Contributors:  
    - *      Imixs Software Solutions GmbH - initial API and implementation
    + *      Imixs Software Solutions GmbH - Project Management
      *      Ralph Soika - Software Developer
    - * 
    - *******************************************************************************/ + */ package org.imixs.workflow.engine.plugins; @@ -45,8 +44,8 @@ import org.imixs.workflow.xml.XSLHandler; /** - * This DocumentComposer Plugin creates html output stored in a item. The DocumentComposer is based - * on BPMN DataObjects assigned to the target task. + * This DocumentComposer Plugin creates html output stored in a item. The + * DocumentComposer is based on BPMN DataObjects assigned to the target task. * *
      *  
    @@ -62,141 +61,134 @@
      */
     public class DocumentComposerPlugin extends AbstractPlugin {
     
    -  public static String ITEM_DOCUMENT_COMPOSER = "document-composer";
    -  public static String INVALID_DATA_OBJECT = "INVALID_DATA_OBJECT";
    -  public static String INVALID_XSL_FORMAT = "INVALID_XSL_FORMAT";
    -
    -  private static Logger logger = Logger.getLogger(DocumentComposerPlugin.class.getName());
    -
    -  @Override
    -  public void init(WorkflowContext actx) throws PluginException {
    -
    -    super.init(actx);
    -
    -  }
    -
    -  /**
    -   * This method adds the attachments of the blob workitem to the MimeMessage
    -   */
    -  @Override
    -  public ItemCollection run(ItemCollection documentContext, ItemCollection documentActivity)
    -      throws PluginException {
    -    long l = System.currentTimeMillis();
    -    // get next process entity
    -    ItemCollection itemColNextProcess = null;
    -    try {
    -      // itemColNextProcess = this.getWorkflowService().evalNextTask(documentContext,
    -      // documentActivity);
    -      itemColNextProcess = this.getWorkflowService().evalNextTask(documentContext);
    -    } catch (ModelException e) {
    -      throw new PluginException(DocumentComposerPlugin.class.getSimpleName(), e.getErrorCode(),
    -          e.getMessage());
    -    }
    +    public static String ITEM_DOCUMENT_COMPOSER = "document-composer";
    +    public static String INVALID_DATA_OBJECT = "INVALID_DATA_OBJECT";
    +    public static String INVALID_XSL_FORMAT = "INVALID_XSL_FORMAT";
     
    -    ItemCollection evalItemCollection =
    -        this.getWorkflowService().evalWorkflowResult(documentActivity, documentContext);
    +    private static Logger logger = Logger.getLogger(DocumentComposerPlugin.class.getName());
     
    -    // find the data object
    -    if (evalItemCollection != null && evalItemCollection.hasItem(ITEM_DOCUMENT_COMPOSER)) {
    +    @Override
    +    public void init(WorkflowContext actx) throws PluginException {
     
    -      String templateName =
    -          evalItemCollection.getItemValueString(ITEM_DOCUMENT_COMPOSER + ".data-object");
    +        super.init(actx);
     
    -      // get the template
    -      List dataObject = findDataObject(templateName, itemColNextProcess);
    -      String template;
    -      if (dataObject != null) {
    -        template = dataObject.get(1);
    -        String outputItem = evalItemCollection.getItemValueString(ITEM_DOCUMENT_COMPOSER);
    -        // process output...
    -        String output = transformXSLTemplate(documentContext, template);
    -        documentContext.replaceItemValue(outputItem, output);
    +    }
     
    -        logger.fine("...composed document in " + (System.currentTimeMillis() - l) + "ms");
    -      }
    +    /**
    +     * This method adds the attachments of the blob workitem to the MimeMessage
    +     */
    +    @Override
    +    public ItemCollection run(ItemCollection documentContext, ItemCollection documentActivity) throws PluginException {
    +        long l = System.currentTimeMillis();
    +        // get next process entity
    +        ItemCollection itemColNextProcess = null;
    +        try {
    +            // itemColNextProcess = this.getWorkflowService().evalNextTask(documentContext,
    +            // documentActivity);
    +            itemColNextProcess = this.getWorkflowService().evalNextTask(documentContext);
    +        } catch (ModelException e) {
    +            throw new PluginException(DocumentComposerPlugin.class.getSimpleName(), e.getErrorCode(), e.getMessage());
    +        }
    +
    +        ItemCollection evalItemCollection = this.getWorkflowService().evalWorkflowResult(documentActivity,
    +                documentContext);
    +
    +        // find the data object
    +        if (evalItemCollection != null && evalItemCollection.hasItem(ITEM_DOCUMENT_COMPOSER)) {
    +
    +            String templateName = evalItemCollection.getItemValueString(ITEM_DOCUMENT_COMPOSER + ".data-object");
    +
    +            // get the template
    +            List dataObject = findDataObject(templateName, itemColNextProcess);
    +            String template;
    +            if (dataObject != null) {
    +                template = dataObject.get(1);
    +                String outputItem = evalItemCollection.getItemValueString(ITEM_DOCUMENT_COMPOSER);
    +                // process output...
    +                String output = transformXSLTemplate(documentContext, template);
    +                documentContext.replaceItemValue(outputItem, output);
    +
    +                logger.fine("...composed document in " + (System.currentTimeMillis() - l) + "ms");
    +            }
    +        }
    +
    +        return documentContext;
         }
     
    -    return documentContext;
    -  }
    -
    -  /**
    -   * Returns the data template for a given tempalte name. If templatename is null or empty, the
    -   * method retruns the first dataobject.
    -   * 
    -   * @param task
    -   * @return
    -   * @throws PluginException
    -   */
    -  private List findDataObject(String objectName, ItemCollection task)
    -      throws PluginException {
    -
    -    @SuppressWarnings("unchecked")
    -    List> dataObjects = task.getItemValue("dataObjects");
    -
    -    // iterate all objects...
    -    for (List dataObj : dataObjects) {
    -      String name = dataObj.get(0);
    -
    -      if (objectName == null || objectName.isEmpty()) {
    -        // return teh first default tempalte
    -        return dataObj;
    -      }
    -      if (objectName.equals(name)) {
    -        return dataObj;
    -      }
    +    /**
    +     * Returns the data template for a given tempalte name. If templatename is null
    +     * or empty, the method retruns the first dataobject.
    +     * 
    +     * @param task
    +     * @return
    +     * @throws PluginException
    +     */
    +    private List findDataObject(String objectName, ItemCollection task) throws PluginException {
    +
    +        @SuppressWarnings("unchecked")
    +        List> dataObjects = task.getItemValue("dataObjects");
    +
    +        // iterate all objects...
    +        for (List dataObj : dataObjects) {
    +            String name = dataObj.get(0);
    +
    +            if (objectName == null || objectName.isEmpty()) {
    +                // return teh first default tempalte
    +                return dataObj;
    +            }
    +            if (objectName.equals(name)) {
    +                return dataObj;
    +            }
    +
    +        }
    +        // no tempalte found
    +        throw new PluginException(this.getClass().getName(), INVALID_DATA_OBJECT,
    +                "dataobject with name '" + objectName + "' not defined in Task " + task.getItemValueInteger("numTask"));
     
         }
    -    // no tempalte found
    -    throw new PluginException(this.getClass().getName(), INVALID_DATA_OBJECT,
    -        "dataobject with name '" + objectName + "' not defined in Task "
    -            + task.getItemValueInteger("numTask"));
    -
    -  }
    -
    -  /**
    -   * This method performs a XSL transformation based on an xslTemplate. The xml source is generated
    -   * form the current document context.
    -   * 
    -   * encoding is set to UTF-8
    -   * 
    -   * @return translated email body
    -   * @throws PluginException
    -   * 
    -   */
    -  public String transformXSLTemplate(ItemCollection documentContext, String xslTemplate)
    -      throws PluginException {
    -    String encoding;
    -    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    -    encoding = "UTF-8";
    -
    -    logger.finest("......transfor mail body based on XSL template....");
    -    // Transform XML per XSL and generate output
    -    XMLDocument xml;
    -    try {
    -      xml = XMLDocumentAdapter.getDocument(documentContext);
    -      StringWriter writer = new StringWriter();
    -
    -      JAXBContext context = JAXBContext.newInstance(XMLDocument.class);
    -      Marshaller m = context.createMarshaller();
    -      m.setProperty("jaxb.encoding", encoding);
    -      m.marshal(xml, writer);
    -
    -      // create a ByteArray Output Stream
    -      XSLHandler.transform(writer.toString(), xslTemplate, encoding, outputStream);
    -      return outputStream.toString(encoding);
    -
    -    } catch (Exception e) {
    -      logger.warning("Error processing XSL template!");
    -      throw new PluginException(this.getClass().getSimpleName(), INVALID_XSL_FORMAT, e.getMessage(),
    -          e);
    -    } finally {
    -      try {
    -        outputStream.close();
    -      } catch (IOException e) {
    -        e.printStackTrace();
    -      }
    -    }
     
    -  }
    +    /**
    +     * This method performs a XSL transformation based on an xslTemplate. The xml
    +     * source is generated form the current document context.
    +     * 
    +     * encoding is set to UTF-8
    +     * 
    +     * @return translated email body
    +     * @throws PluginException
    +     * 
    +     */
    +    public String transformXSLTemplate(ItemCollection documentContext, String xslTemplate) throws PluginException {
    +        String encoding;
    +        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    +        encoding = "UTF-8";
    +
    +        logger.finest("......transfor mail body based on XSL template....");
    +        // Transform XML per XSL and generate output
    +        XMLDocument xml;
    +        try {
    +            xml = XMLDocumentAdapter.getDocument(documentContext);
    +            StringWriter writer = new StringWriter();
    +
    +            JAXBContext context = JAXBContext.newInstance(XMLDocument.class);
    +            Marshaller m = context.createMarshaller();
    +            m.setProperty("jaxb.encoding", encoding);
    +            m.marshal(xml, writer);
    +
    +            // create a ByteArray Output Stream
    +            XSLHandler.transform(writer.toString(), xslTemplate, encoding, outputStream);
    +            return outputStream.toString(encoding);
    +
    +        } catch (Exception e) {
    +            logger.warning("Error processing XSL template!");
    +            throw new PluginException(this.getClass().getSimpleName(), INVALID_XSL_FORMAT, e.getMessage(), e);
    +        } finally {
    +            try {
    +                outputStream.close();
    +            } catch (IOException e) {
    +                e.printStackTrace();
    +            }
    +        }
    +
    +    }
     
     }
    diff --git a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/plugins/HistoryPlugin.java b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/plugins/HistoryPlugin.java
    index 493fc3691..2615b0f1d 100644
    --- a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/plugins/HistoryPlugin.java
    +++ b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/plugins/HistoryPlugin.java
    @@ -1,6 +1,6 @@
    -/*******************************************************************************
    - * 
    - *  Imixs Workflow 
    +/*  
    + *  Imixs-Workflow 
    + *  
      *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
      *  http://www.imixs.com
      *  
    @@ -22,10 +22,9 @@
      *      https://github.com/imixs/imixs-workflow
      *  
      *  Contributors:  
    - *      Imixs Software Solutions GmbH - initial API and implementation
    + *      Imixs Software Solutions GmbH - Project Management
      *      Ralph Soika - Software Developer
    - * 
    - *******************************************************************************/ + */ package org.imixs.workflow.engine.plugins; @@ -43,8 +42,9 @@ import org.imixs.workflow.exceptions.PluginException; /** - * This Plugin creates a history log in the property txtWorkflowHistory. The history log contains a - * list of history entires. Each entry provides the following information: + * This Plugin creates a history log in the property txtWorkflowHistory. The + * history log contains a list of history entires. Each entry provides the + * following information: *
      *
    • date of creation (Date)
    • *
    • comment (String)
    • @@ -52,9 +52,10 @@ *
    * * - * Note: In early versions of this plugin the history entries were stored in a simple string list. - * The date was separated by the char sequence ' : ' from the comment entry. The userId was not - * stored explicit. This plugin converts the old format automatically (see method convertOldFormat) + * Note: In early versions of this plugin the history entries were stored in a + * simple string list. The date was separated by the char sequence ' : ' from + * the comment entry. The userId was not stored explicit. This plugin converts + * the old format automatically (see method convertOldFormat) * * * @@ -64,182 +65,175 @@ */ public class HistoryPlugin extends AbstractPlugin { - private ItemCollection documentContext; - private List> historyList = null; - private static Logger logger = Logger.getLogger(HistoryPlugin.class.getName()); - - /** - * Update the Log entry. - * - * The method tests if the deprecated property 'txtworkflowhistorylogrev' exists. In this case the - * old log format will be transformed into the new format see method convertOldFormat - */ - @SuppressWarnings({"unchecked", "rawtypes"}) - public ItemCollection run(ItemCollection adocumentContext, ItemCollection adocumentActivity) - throws PluginException { - String rtfItemLog; - - documentContext = adocumentContext; - ItemCollection documentActivity = adocumentActivity; - - // convert old format if exists (backward compatibility (< 3.1.1) - if (documentContext.hasItem("txtworkflowhistorylogrev")) { - convertOldFormat(); - documentContext.removeItem("txtworkflowhistorylogrev"); - } + private ItemCollection documentContext; + private List> historyList = null; + private static Logger logger = Logger.getLogger(HistoryPlugin.class.getName()); + + /** + * Update the Log entry. + * + * The method tests if the deprecated property 'txtworkflowhistorylogrev' + * exists. In this case the old log format will be transformed into the new + * format see method convertOldFormat + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public ItemCollection run(ItemCollection adocumentContext, ItemCollection adocumentActivity) + throws PluginException { + String rtfItemLog; + + documentContext = adocumentContext; + ItemCollection documentActivity = adocumentActivity; + + // convert old format if exists (backward compatibility (< 3.1.1) + if (documentContext.hasItem("txtworkflowhistorylogrev")) { + convertOldFormat(); + documentContext.removeItem("txtworkflowhistorylogrev"); + } - // add logtext into history log - rtfItemLog = documentActivity.getItemValueString("rtfresultlog"); - if (rtfItemLog.isEmpty()) - return documentContext; - - rtfItemLog = getWorkflowService().adaptText(rtfItemLog, documentContext); - - List temp = documentContext.getItemValue("txtworkflowhistory"); - historyList = new Vector(); - // clear null and empty values - Iterator i = temp.iterator(); - while (i.hasNext()) { - Object o = i.next(); - if (o instanceof List) - historyList.add((List) o); - } + // add logtext into history log + rtfItemLog = documentActivity.getItemValueString("rtfresultlog"); + if (rtfItemLog.isEmpty()) + return documentContext; + + rtfItemLog = getWorkflowService().adaptText(rtfItemLog, documentContext); + + List temp = documentContext.getItemValue("txtworkflowhistory"); + historyList = new Vector(); + // clear null and empty values + Iterator i = temp.iterator(); + while (i.hasNext()) { + Object o = i.next(); + if (o instanceof List) + historyList.add((List) o); + } - List newEntry = new ArrayList(); - newEntry.add(documentContext.getItemValueDate(WorkflowKernel.LASTEVENTDATE)); - newEntry.add(rtfItemLog); - newEntry.add(this.getWorkflowService().getUserName()); - historyList.add(newEntry); - - // check if maximum length of log is defined - int iMaxLogLength = documentContext.getItemValueInteger("numworkflowhistoryLength"); - if (iMaxLogLength > 0) { - while (historyList.size() > iMaxLogLength) - historyList.remove(0); + List newEntry = new ArrayList(); + newEntry.add(documentContext.getItemValueDate(WorkflowKernel.LASTEVENTDATE)); + newEntry.add(rtfItemLog); + newEntry.add(this.getWorkflowService().getUserName()); + historyList.add(newEntry); + + // check if maximum length of log is defined + int iMaxLogLength = documentContext.getItemValueInteger("numworkflowhistoryLength"); + if (iMaxLogLength > 0) { + while (historyList.size() > iMaxLogLength) + historyList.remove(0); + } + + documentContext.replaceItemValue("txtworkflowhistory", historyList); + + // set timWorkflowLastAccess (Deprecated) + // issue #244 + // documentContext.replaceItemValue("timworkflowlastaccess", new Date()); + return documentContext; } - documentContext.replaceItemValue("txtworkflowhistory", historyList); - - // set timWorkflowLastAccess (Deprecated) - // issue #244 - // documentContext.replaceItemValue("timworkflowlastaccess", new Date()); - return documentContext; - } - - - - /** - * This method converts the old StringList format in the new format with a list of separated - * values: - * - *
      - *
    • date of creation (Date)
    • - *
    • comment (String)
    • - *
    • userID (String)
    • - *
    - * - */ - @SuppressWarnings("unchecked") - protected void convertOldFormat() { - List> newList = new ArrayList>(); - - try { - List oldList = (List) documentContext.getItemValue("txtworkflowhistorylog"); - - for (String oldEntry : oldList) { - if (oldEntry != null && !oldEntry.isEmpty() && oldEntry.indexOf(" : ") > -1) { - String sDate = oldEntry.substring(0, oldEntry.indexOf(" : ")); - String sComment = oldEntry.substring(oldEntry.indexOf(" : ") + 3); - String sUser = ""; - List newEntry = new ArrayList(); - newEntry.add(convertDate(sDate)); - newEntry.add(sComment); - newEntry.add(sUser); - newList.add(newEntry); + /** + * This method converts the old StringList format in the new format with a list + * of separated values: + * + *
      + *
    • date of creation (Date)
    • + *
    • comment (String)
    • + *
    • userID (String)
    • + *
    + * + */ + @SuppressWarnings("unchecked") + protected void convertOldFormat() { + List> newList = new ArrayList>(); + + try { + List oldList = (List) documentContext.getItemValue("txtworkflowhistorylog"); + + for (String oldEntry : oldList) { + if (oldEntry != null && !oldEntry.isEmpty() && oldEntry.indexOf(" : ") > -1) { + String sDate = oldEntry.substring(0, oldEntry.indexOf(" : ")); + String sComment = oldEntry.substring(oldEntry.indexOf(" : ") + 3); + String sUser = ""; + List newEntry = new ArrayList(); + newEntry.add(convertDate(sDate)); + newEntry.add(sComment); + newEntry.add(sUser); + newList.add(newEntry); + } + } + } catch (ClassCastException cce) { + logger.warning("[HistoryPlugin] can not convert txtworkflowhistorylog into new format!"); + logger.warning(cce.getMessage()); } - } - } catch (ClassCastException cce) { - logger.warning("[HistoryPlugin] can not convert txtworkflowhistorylog into new format!"); - logger.warning(cce.getMessage()); - } - documentContext.replaceItemValue("txtworkflowhistory", newList); - } - - /** - * This methd is only used to convert old date formats.... - * - * @param aDateString - * @return - */ - private Date convertDate(String aDateString) { - Date result; - DateFormat df = null; - - try { - df = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, new Locale("de", "DE")); - - result = df.parse(aDateString); - return result; - } catch (ParseException e) { - // no opp + documentContext.replaceItemValue("txtworkflowhistory", newList); } - try { - df = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM, - new Locale("de", "DE")); - result = df.parse(aDateString); - return result; - } catch (ParseException e) { - // no opp - } + /** + * This methd is only used to convert old date formats.... + * + * @param aDateString + * @return + */ + private Date convertDate(String aDateString) { + Date result; + DateFormat df = null; + + try { + df = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, new Locale("de", "DE")); + + result = df.parse(aDateString); + return result; + } catch (ParseException e) { + // no opp + } - try { - df = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, - new Locale("de", "DE")); - result = df.parse(aDateString); - return result; - } catch (ParseException e) { - // no opp - } + try { + df = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM, new Locale("de", "DE")); + result = df.parse(aDateString); + return result; + } catch (ParseException e) { + // no opp + } - try { - df = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.MEDIUM, - new Locale("de", "DE")); - result = df.parse(aDateString); - return result; - } catch (ParseException e) { - // no opp - } + try { + df = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, new Locale("de", "DE")); + result = df.parse(aDateString); + return result; + } catch (ParseException e) { + // no opp + } - try { - df = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT, - new Locale("de", "DE")); - result = df.parse(aDateString); - return result; - } catch (ParseException e) { - // no opp - } + try { + df = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.MEDIUM, new Locale("de", "DE")); + result = df.parse(aDateString); + return result; + } catch (ParseException e) { + // no opp + } - try { - df = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM, - new Locale("de", "DE")); - result = df.parse(aDateString); - return result; - } catch (ParseException e) { - // no opp - } + try { + df = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT, new Locale("de", "DE")); + result = df.parse(aDateString); + return result; + } catch (ParseException e) { + // no opp + } - try { - df = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG, - new Locale("de", "DE")); - result = df.parse(aDateString); - return result; - } catch (ParseException e) { - // no opp - } + try { + df = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM, new Locale("de", "DE")); + result = df.parse(aDateString); + return result; + } catch (ParseException e) { + // no opp + } - return null; - } + try { + df = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG, new Locale("de", "DE")); + result = df.parse(aDateString); + return result; + } catch (ParseException e) { + // no opp + } + + return null; + } } diff --git a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/plugins/IntervalPlugin.java b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/plugins/IntervalPlugin.java index b3eab7fdf..4306b3ec3 100644 --- a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/plugins/IntervalPlugin.java +++ b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/plugins/IntervalPlugin.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *
    - *  Imixs Workflow 
    +/*  
    + *  Imixs-Workflow 
    + *  
      *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
      *  http://www.imixs.com
      *  
    @@ -22,10 +22,9 @@
      *      https://github.com/imixs/imixs-workflow
      *  
      *  Contributors:  
    - *      Imixs Software Solutions GmbH - initial API and implementation
    + *      Imixs Software Solutions GmbH - Project Management
      *      Ralph Soika - Software Developer
    - * 
    - *******************************************************************************/ + */ package org.imixs.workflow.engine.plugins; @@ -38,9 +37,10 @@ import org.imixs.workflow.exceptions.PluginException; /** - * The Imixs Interval Plugin implements an mechanism to adjust a date field of a workitem based on a - * interval description. The interval description is stored in a field with the prafix 'keyinterval' - * followed by the name of an existing date field. See the following example: + * The Imixs Interval Plugin implements an mechanism to adjust a date field of a + * workitem based on a interval description. The interval description is stored + * in a field with the prafix 'keyinterval' followed by the name of an existing + * date field. See the following example: * * * keyIntervalDatDate=monthly @@ -54,93 +54,89 @@ */ public class IntervalPlugin extends AbstractPlugin { - public static final String INVALID_FORMAT = "INVALID_FORMAT"; - - private ItemCollection documentContext; - private static Logger logger = Logger.getLogger(IntervalPlugin.class.getName()); - - /** - * The method paresed for a fields with the prafix 'keyitnerval' - */ - public ItemCollection run(ItemCollection adocumentContext, ItemCollection adocumentActivity) - throws PluginException { - documentContext = adocumentContext; - // test if activity is a schedule activity... - // check if activity is scheduled - if (!"1".equals(adocumentActivity.getItemValueString("keyScheduledActivity"))) { - return documentContext; - } - - - Calendar calNow = Calendar.getInstance(); - - logger.finest("......compute next interval dates for workitem " - + documentContext.getItemValueString(WorkflowKernel.UNIQUEID)); - - Set fieldNames = documentContext.getAllItems().keySet(); - for (String fieldName : fieldNames) { - if (fieldName.toLowerCase().startsWith("keyinterval")) { - String sInterval = documentContext.getItemValueString(fieldName); - - if (sInterval.isEmpty()) - continue; - - sInterval = sInterval.toLowerCase(); - // lookup for a date value - String sDateField = fieldName.substring(11); - if (!sDateField.isEmpty() && documentContext.hasItem(sDateField)) { - Date date = documentContext.getItemValueDate(sDateField); - if (date != null) { - - // verify if date is in the past.... - Calendar calDate = Calendar.getInstance(); - calDate.setTime(date); - if (calNow.after(calDate)) { - logger.finest("......compute next interval for " + sDateField); - - // first set day month and year to now - calDate.set(Calendar.YEAR, calNow.get(Calendar.YEAR)); - calDate.set(Calendar.MONTH, calNow.get(Calendar.MONTH)); - calDate.set(Calendar.DAY_OF_MONTH, calNow.get(Calendar.DAY_OF_MONTH)); - - - // test if interval is a number. In this case - // increase the date of the number of days - try { - int iDays = Integer.parseInt(sInterval); - calDate.add(Calendar.DAY_OF_MONTH, iDays); - } catch (NumberFormatException nfe) { - // check for daily, monthly, yerarliy - - if (sInterval.contains("daily")) { - calDate.add(Calendar.DAY_OF_MONTH, 1); - } - - if (sInterval.contains("weekly")) { - calDate.add(Calendar.DAY_OF_MONTH, 7); - } - - if (sInterval.contains("monthly")) { - calDate.add(Calendar.MONTH, 1); - } + public static final String INVALID_FORMAT = "INVALID_FORMAT"; + + private ItemCollection documentContext; + private static Logger logger = Logger.getLogger(IntervalPlugin.class.getName()); + + /** + * The method paresed for a fields with the prafix 'keyitnerval' + */ + public ItemCollection run(ItemCollection adocumentContext, ItemCollection adocumentActivity) + throws PluginException { + documentContext = adocumentContext; + // test if activity is a schedule activity... + // check if activity is scheduled + if (!"1".equals(adocumentActivity.getItemValueString("keyScheduledActivity"))) { + return documentContext; + } - if (sInterval.contains("yearly")) { - calDate.add(Calendar.YEAR, 1); + Calendar calNow = Calendar.getInstance(); + + logger.finest("......compute next interval dates for workitem " + + documentContext.getItemValueString(WorkflowKernel.UNIQUEID)); + + Set fieldNames = documentContext.getAllItems().keySet(); + for (String fieldName : fieldNames) { + if (fieldName.toLowerCase().startsWith("keyinterval")) { + String sInterval = documentContext.getItemValueString(fieldName); + + if (sInterval.isEmpty()) + continue; + + sInterval = sInterval.toLowerCase(); + // lookup for a date value + String sDateField = fieldName.substring(11); + if (!sDateField.isEmpty() && documentContext.hasItem(sDateField)) { + Date date = documentContext.getItemValueDate(sDateField); + if (date != null) { + + // verify if date is in the past.... + Calendar calDate = Calendar.getInstance(); + calDate.setTime(date); + if (calNow.after(calDate)) { + logger.finest("......compute next interval for " + sDateField); + + // first set day month and year to now + calDate.set(Calendar.YEAR, calNow.get(Calendar.YEAR)); + calDate.set(Calendar.MONTH, calNow.get(Calendar.MONTH)); + calDate.set(Calendar.DAY_OF_MONTH, calNow.get(Calendar.DAY_OF_MONTH)); + + // test if interval is a number. In this case + // increase the date of the number of days + try { + int iDays = Integer.parseInt(sInterval); + calDate.add(Calendar.DAY_OF_MONTH, iDays); + } catch (NumberFormatException nfe) { + // check for daily, monthly, yerarliy + + if (sInterval.contains("daily")) { + calDate.add(Calendar.DAY_OF_MONTH, 1); + } + + if (sInterval.contains("weekly")) { + calDate.add(Calendar.DAY_OF_MONTH, 7); + } + + if (sInterval.contains("monthly")) { + calDate.add(Calendar.MONTH, 1); + } + + if (sInterval.contains("yearly")) { + calDate.add(Calendar.YEAR, 1); + } + } + + documentContext.replaceItemValue(sDateField, calDate.getTime()); + + } + } } - } - - documentContext.replaceItemValue(sDateField, calDate.getTime()); } - } } - } + return documentContext; } - return documentContext; - } - - - } diff --git a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/plugins/LogPlugin.java b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/plugins/LogPlugin.java index b2b81d2cd..a080bcac5 100644 --- a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/plugins/LogPlugin.java +++ b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/plugins/LogPlugin.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *
    - *  Imixs Workflow 
    +/*  
    + *  Imixs-Workflow 
    + *  
      *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
      *  http://www.imixs.com
      *  
    @@ -22,10 +22,9 @@
      *      https://github.com/imixs/imixs-workflow
      *  
      *  Contributors:  
    - *      Imixs Software Solutions GmbH - initial API and implementation
    + *      Imixs Software Solutions GmbH - Project Management
      *      Ralph Soika - Software Developer
    - * 
    - *******************************************************************************/ + */ package org.imixs.workflow.engine.plugins; @@ -34,14 +33,15 @@ import org.imixs.workflow.exceptions.PluginException; /** - * This Pluginmodul cuts the length of the technical log entries generated by the WorkflowKernel: + * This Pluginmodul cuts the length of the technical log entries generated by + * the WorkflowKernel: * * txtWorkflowPluginLog * * txtWorkflowActivityLog * - * The Attribute numWorkflowLogLength indicates the maximum number of entries. if <= 0 no limit is - * set. + * The Attribute numWorkflowLogLength indicates the maximum number of entries. + * if <= 0 no limit is set. * * * @author Ralph Soika @@ -52,31 +52,28 @@ public class LogPlugin extends AbstractPlugin { - /** - * the log entries generated form the kernel will be cut if the attribute numWorkflowLogLength was - * provided - */ - public ItemCollection run(ItemCollection documentContext, ItemCollection adocumentActivity) - throws PluginException { - List vActivityLog; - - vActivityLog = documentContext.getItemValue("txtWorkflowActivityLog"); + /** + * the log entries generated form the kernel will be cut if the attribute + * numWorkflowLogLength was provided + */ + public ItemCollection run(ItemCollection documentContext, ItemCollection adocumentActivity) throws PluginException { + List vActivityLog; - // check if maximum length is defined - int iMaxLogLength = documentContext.getItemValueInteger("numWorkflowLogLength"); - if (iMaxLogLength > 0) { + vActivityLog = documentContext.getItemValue("txtWorkflowActivityLog"); - while (vActivityLog.size() >= iMaxLogLength) { - vActivityLog.remove(0); - } + // check if maximum length is defined + int iMaxLogLength = documentContext.getItemValueInteger("numWorkflowLogLength"); + if (iMaxLogLength > 0) { - // update Log entries now... - documentContext.replaceItemValue("txtWorkflowActivityLog", vActivityLog); - } - - return documentContext; - } + while (vActivityLog.size() >= iMaxLogLength) { + vActivityLog.remove(0); + } + // update Log entries now... + documentContext.replaceItemValue("txtWorkflowActivityLog", vActivityLog); + } + return documentContext; + } } diff --git a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/plugins/MailPlugin.java b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/plugins/MailPlugin.java index a8bc73af0..b693574e2 100644 --- a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/plugins/MailPlugin.java +++ b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/plugins/MailPlugin.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *
    - *  Imixs Workflow 
    +/*  
    + *  Imixs-Workflow 
    + *  
      *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
      *  http://www.imixs.com
      *  
    @@ -22,10 +22,9 @@
      *      https://github.com/imixs/imixs-workflow
      *  
      *  Contributors:  
    - *      Imixs Software Solutions GmbH - initial API and implementation
    + *      Imixs Software Solutions GmbH - Project Management
      *      Ralph Soika - Software Developer
    - * 
    - *******************************************************************************/ + */ package org.imixs.workflow.engine.plugins; @@ -65,639 +64,625 @@ import org.imixs.workflow.xml.XSLHandler; /** - * This plug-in supports a Mail interface to send a email to a list of recipients. The mail content - * can be defined by the corresponding BPMN event. + * This plug-in supports a Mail interface to send a email to a list of + * recipients. The mail content can be defined by the corresponding BPMN event. * * The content of the email can either be plain text or HTML. * - * The plug-in uses a JNID messaging session named 'org.imixs.workflow.mail'. The JNDI resource can - * be customized by the deployment constructor. + * The plug-in uses a JNID messaging session named 'org.imixs.workflow.mail'. + * The JNDI resource can be customized by the deployment constructor. * - * The e-mail message can be canceled by the application or another plug-in by setting the attribute - * keyMailInactive=true + * The e-mail message can be canceled by the application or another plug-in by + * setting the attribute keyMailInactive=true * * @author Ralph Soika * */ public class MailPlugin extends AbstractPlugin { - public static final String ERROR_INVALID_XSL_FORMAT = "INVALID_XSL_FORMAT"; - public static final String ERROR_MAIL_MESSAGE = "ERROR_MAIL_MESSAGE"; - public static final String MAIL_SESSION_NAME = "mail/org.imixs.workflow.mail"; - public static final String CONTENTTYPE_TEXT_PLAIN = "text/plain"; - public static final String CONTENTTYPE_TEXT_HTML = "text/html"; - public static final String INVALID_ADDRESS = "INVALID_ADDRESS"; - - // Mail objects - @Resource(lookup = MAIL_SESSION_NAME) - private Session mailSession; - - @Inject - @ConfigProperty(name = "mail.testRecipients", defaultValue = "") - private String mailTestRecipients; - - @Inject - @ConfigProperty(name = "mail.defaultSender", defaultValue = "") - private String mailDefaultSender; - - @Inject - @ConfigProperty(name = "mail.charSet", defaultValue = "") - private String mailCharSet; - - private MimeMessage mailMessage = null; - private Multipart mimeMultipart = null; - private String charSet = "ISO-8859-1"; - - private boolean bHTMLMail = false; - private static Logger logger = Logger.getLogger(MailPlugin.class.getName()); - - /** - * The run method creates a mailMessage object if recipients are defined by the corresponding BPMN - * event. The mail message will finally be send in the close method. This mechanism avoids that a - * mail is send before all plug-ins were processed correctly. - * - */ - @SuppressWarnings({"rawtypes"}) - public ItemCollection run(ItemCollection documentContext, ItemCollection documentActivity) - throws PluginException { - boolean debug = logger.isLoggable(Level.FINE); - mailMessage = null; - - // check if mail is active? This flag can be set by another plug-in - if (documentActivity.getItemValueBoolean("keyMailInactive") - || "1".equals(documentActivity.getItemValueString("keyMailInactive"))) { - if (debug) { - logger.finest("......keyMailInactive = true - cancel mail message."); - } - return documentContext; - } + public static final String ERROR_INVALID_XSL_FORMAT = "INVALID_XSL_FORMAT"; + public static final String ERROR_MAIL_MESSAGE = "ERROR_MAIL_MESSAGE"; + public static final String MAIL_SESSION_NAME = "mail/org.imixs.workflow.mail"; + public static final String CONTENTTYPE_TEXT_PLAIN = "text/plain"; + public static final String CONTENTTYPE_TEXT_HTML = "text/html"; + public static final String INVALID_ADDRESS = "INVALID_ADDRESS"; + + // Mail objects + @Resource(lookup = MAIL_SESSION_NAME) + private Session mailSession; + + @Inject + @ConfigProperty(name = "mail.testRecipients", defaultValue = "") + private String mailTestRecipients; + + @Inject + @ConfigProperty(name = "mail.defaultSender", defaultValue = "") + private String mailDefaultSender; + + @Inject + @ConfigProperty(name = "mail.charSet", defaultValue = "") + private String mailCharSet; + + private MimeMessage mailMessage = null; + private Multipart mimeMultipart = null; + private String charSet = "ISO-8859-1"; + + private boolean bHTMLMail = false; + private static Logger logger = Logger.getLogger(MailPlugin.class.getName()); + + /** + * The run method creates a mailMessage object if recipients are defined by the + * corresponding BPMN event. The mail message will finally be send in the close + * method. This mechanism avoids that a mail is send before all plug-ins were + * processed correctly. + * + */ + @SuppressWarnings({ "rawtypes" }) + public ItemCollection run(ItemCollection documentContext, ItemCollection documentActivity) throws PluginException { + boolean debug = logger.isLoggable(Level.FINE); + mailMessage = null; + + // check if mail is active? This flag can be set by another plug-in + if (documentActivity.getItemValueBoolean("keyMailInactive") + || "1".equals(documentActivity.getItemValueString("keyMailInactive"))) { + if (debug) { + logger.finest("......keyMailInactive = true - cancel mail message."); + } + return documentContext; + } + + List vectorRecipients = getRecipients(documentContext, documentActivity); + if (vectorRecipients.isEmpty()) { + if (debug) { + logger.finest("......No Receipients defined for this Activity - cancel mail message."); + } + return documentContext; + } + + try { + + // first initialize mail message object + initMailMessage(); + + if (mailMessage == null) { + logger.warning(" mailMessage = null"); + return documentContext; + } + + // set FROM + mailMessage.setFrom(getInternetAddress(getFrom(documentContext, documentActivity))); + + // set Recipient + mailMessage.setRecipients(Message.RecipientType.TO, getInternetAddressArray(vectorRecipients)); + + // build CC + mailMessage.setRecipients(Message.RecipientType.CC, + getInternetAddressArray(getRecipientsCC(documentContext, documentActivity))); + + // build BCC + mailMessage.setRecipients(Message.RecipientType.BCC, + getInternetAddressArray(getRecipientsBCC(documentContext, documentActivity))); + + // replay to? + String sReplyTo = getReplyTo(documentContext, documentActivity); + if ((sReplyTo != null) && (!sReplyTo.isEmpty())) { + InternetAddress[] resplysAdrs = new InternetAddress[1]; + resplysAdrs[0] = getInternetAddress(sReplyTo); + mailMessage.setReplyTo(resplysAdrs); + } + + // set Subject + mailMessage.setSubject(getSubject(documentContext, documentActivity), this.getCharSet()); + + // set Body + String aBodyText = getBody(documentContext, documentActivity); + + // set mailbody + MimeBodyPart messagePart = new MimeBodyPart(); + if (debug) { + logger.finest("......ContentType: '" + getContentType() + "'"); + } + messagePart.setContent(aBodyText, getContentType()); + // append message part + mimeMultipart.addBodyPart(messagePart); + // mimeMulitPart object can be extended from subclases + + } catch (MessagingException e) { + throw new PluginException(MailPlugin.class.getSimpleName(), ERROR_MAIL_MESSAGE, e.getMessage(), e); - List vectorRecipients = getRecipients(documentContext, documentActivity); - if (vectorRecipients.isEmpty()) { - if (debug) { - logger.finest("......No Receipients defined for this Activity - cancel mail message."); - } - return documentContext; + // logger.warning(" run - Warning:" + e.toString()); + // e.printStackTrace(); + // return documentContext; + } + + return documentContext; } - try { - - // first initialize mail message object - initMailMessage(); - - if (mailMessage == null) { - logger.warning(" mailMessage = null"); - return documentContext; - } - - // set FROM - mailMessage.setFrom(getInternetAddress(getFrom(documentContext, documentActivity))); - - // set Recipient - mailMessage.setRecipients(Message.RecipientType.TO, - getInternetAddressArray(vectorRecipients)); - - // build CC - mailMessage.setRecipients(Message.RecipientType.CC, - getInternetAddressArray(getRecipientsCC(documentContext, documentActivity))); - - // build BCC - mailMessage.setRecipients(Message.RecipientType.BCC, - getInternetAddressArray(getRecipientsBCC(documentContext, documentActivity))); - - // replay to? - String sReplyTo = getReplyTo(documentContext, documentActivity); - if ((sReplyTo != null) && (!sReplyTo.isEmpty())) { - InternetAddress[] resplysAdrs = new InternetAddress[1]; - resplysAdrs[0] = getInternetAddress(sReplyTo); - mailMessage.setReplyTo(resplysAdrs); - } - - // set Subject - mailMessage.setSubject(getSubject(documentContext, documentActivity), this.getCharSet()); - - // set Body - String aBodyText = getBody(documentContext, documentActivity); - - // set mailbody - MimeBodyPart messagePart = new MimeBodyPart(); - if (debug) { - logger.finest("......ContentType: '" + getContentType() + "'"); - } - messagePart.setContent(aBodyText, getContentType()); - // append message part - mimeMultipart.addBodyPart(messagePart); - // mimeMulitPart object can be extended from subclases - - } catch (MessagingException e) { - throw new PluginException(MailPlugin.class.getSimpleName(), ERROR_MAIL_MESSAGE, - e.getMessage(), e); - - // logger.warning(" run - Warning:" + e.toString()); - // e.printStackTrace(); - // return documentContext; + /** + * Send the mail if the object 'mailMessage' is not null. + * + * The method lookups the mail session from the session context. + */ + @Override + public void close(boolean rollbackTransaction) throws PluginException { + if (!rollbackTransaction && mailSession != null && mailMessage != null) { + boolean debug = logger.isLoggable(Level.FINE); + // Send the message + try { + + // Check if we are running in a Test MODE + + // test if TestReceipiens are defined... + if (mailTestRecipients != null && !"".equals(mailTestRecipients)) { + List vRecipients = new Vector(); + // split multivalues + StringTokenizer st = new StringTokenizer(mailTestRecipients, ",", false); + while (st.hasMoreElements()) { + vRecipients.add(st.nextToken().trim()); + } + + logger.info("Running in TestMode, forwarding mails to:"); + for (String adr : vRecipients) { + logger.info(" " + adr); + } + try { + getMailMessage().setRecipients(Message.RecipientType.CC, null); + getMailMessage().setRecipients(Message.RecipientType.BCC, null); + getMailMessage().setRecipients(Message.RecipientType.TO, getInternetAddressArray(vRecipients)); + // change subject + String sSubject = getMailMessage().getSubject(); + getMailMessage().setSubject("[TESTMODE] : " + sSubject); + + } catch (MessagingException e) { + throw new PluginException(MailPlugin.class.getSimpleName(), INVALID_ADDRESS, + " unable to set mail recipients: ", e); + } + } + if (debug) { + logger.finest("......sending message..."); + } + mailMessage.setContent(mimeMultipart, getContentType()); + mailMessage.saveChanges(); + + // Issue #452 - optional authentication + // A simple transport.send command did not work if mail host needs a + // authentification. Therefore we use a manual SMTP connection + if (mailSession.getProperty("mail.smtp.password") != null + && !mailSession.getProperty("mail.smtp.password").isEmpty()) { + // create transport object with authentication data + Transport trans = mailSession.getTransport("smtp"); + trans.connect(mailSession.getProperty("mail.smtp.user"), + mailSession.getProperty("mail.smtp.password")); + trans.sendMessage(mailMessage, mailMessage.getAllRecipients()); + trans.close(); + } else { + long l = System.currentTimeMillis(); + // no authentication - so we simple send the mail... + // Transport.send(mailMessage); + // issue #467 + Transport trans = mailSession.getTransport("smtp");// ("smtp"); + trans.connect(); + trans.sendMessage(mailMessage, mailMessage.getAllRecipients()); + trans.close(); + if (debug) { + logger.finest("...mail transfer in " + (System.currentTimeMillis() - l) + "ms"); + } + } + logger.info("...send mail: MessageID=" + mailMessage.getMessageID()); + + } catch (Exception esend) { + logger.warning("close failed with exception: " + esend.toString()); + } + } } - return documentContext; - } - - /** - * Send the mail if the object 'mailMessage' is not null. - * - * The method lookups the mail session from the session context. - */ - @Override - public void close(boolean rollbackTransaction) throws PluginException { - if (!rollbackTransaction && mailSession != null && mailMessage != null) { - boolean debug = logger.isLoggable(Level.FINE); - // Send the message - try { - - // Check if we are running in a Test MODE - - // test if TestReceipiens are defined... - if (mailTestRecipients != null && !"".equals(mailTestRecipients)) { - List vRecipients = new Vector(); - // split multivalues - StringTokenizer st = new StringTokenizer(mailTestRecipients, ",", false); - while (st.hasMoreElements()) { - vRecipients.add(st.nextToken().trim()); - } - - logger.info("Running in TestMode, forwarding mails to:"); - for (String adr : vRecipients) { - logger.info(" " + adr); - } - try { - getMailMessage().setRecipients(Message.RecipientType.CC, null); - getMailMessage().setRecipients(Message.RecipientType.BCC, null); - getMailMessage().setRecipients(Message.RecipientType.TO, - getInternetAddressArray(vRecipients)); - // change subject - String sSubject = getMailMessage().getSubject(); - getMailMessage().setSubject("[TESTMODE] : " + sSubject); - - } catch (MessagingException e) { - throw new PluginException(MailPlugin.class.getSimpleName(), INVALID_ADDRESS, - " unable to set mail recipients: ", e); - } + /** + * Computes the sender name. A sender can be defined by the event property + * 'namMailFrom' or by the system property 'mail.defaultSender'. If no sender is + * defined, the method takes the current username. + * + * This method can be overwritten by subclasses. + * + * @param documentContext + * @param documentActivity + * @return String - mail seder + */ + public String getFrom(ItemCollection documentContext, ItemCollection documentActivity) { + boolean debug = logger.isLoggable(Level.FINE); + // test if namMailReplyToUser is defined by event + String sFrom = documentActivity.getItemValueString("namMailFrom"); + + // if no from was defined by teh event, we test if a default sender is defined + if (sFrom.isEmpty()) { + sFrom = mailDefaultSender; } + // if no default sender take the current username + if (sFrom == null || sFrom.isEmpty()) + sFrom = this.getWorkflowService().getUserName(); if (debug) { - logger.finest("......sending message..."); - } - mailMessage.setContent(mimeMultipart, getContentType()); - mailMessage.saveChanges(); - - // Issue #452 - optional authentication - // A simple transport.send command did not work if mail host needs a - // authentification. Therefore we use a manual SMTP connection - if (mailSession.getProperty("mail.smtp.password") != null - && !mailSession.getProperty("mail.smtp.password").isEmpty()) { - // create transport object with authentication data - Transport trans = mailSession.getTransport("smtp"); - trans.connect(mailSession.getProperty("mail.smtp.user"), - mailSession.getProperty("mail.smtp.password")); - trans.sendMessage(mailMessage, mailMessage.getAllRecipients()); - trans.close(); - } else { - long l = System.currentTimeMillis(); - // no authentication - so we simple send the mail... - // Transport.send(mailMessage); - // issue #467 - Transport trans = mailSession.getTransport("smtp");// ("smtp"); - trans.connect(); - trans.sendMessage(mailMessage, mailMessage.getAllRecipients()); - trans.close(); - if (debug) { - logger.finest("...mail transfer in " + (System.currentTimeMillis() - l) + "ms"); - } + logger.finest("......From: " + sFrom); } - logger.info("...send mail: MessageID=" + mailMessage.getMessageID()); - - } catch (Exception esend) { - logger.warning("close failed with exception: " + esend.toString()); - } - } - } - - /** - * Computes the sender name. A sender can be defined by the event property 'namMailFrom' or by the - * system property 'mail.defaultSender'. If no sender is defined, the method takes the current - * username. - * - * This method can be overwritten by subclasses. - * - * @param documentContext - * @param documentActivity - * @return String - mail seder - */ - public String getFrom(ItemCollection documentContext, ItemCollection documentActivity) { - boolean debug = logger.isLoggable(Level.FINE); - // test if namMailReplyToUser is defined by event - String sFrom = documentActivity.getItemValueString("namMailFrom"); - - // if no from was defined by teh event, we test if a default sender is defined - if (sFrom.isEmpty()) { - sFrom = mailDefaultSender; - } - // if no default sender take the current username - if (sFrom == null || sFrom.isEmpty()) - sFrom = this.getWorkflowService().getUserName(); - if (debug) { - logger.finest("......From: " + sFrom); - } - return sFrom; - } - - /** - * Computes the replyTo address from current workflow activity. This method can be overwritten by - * subclasses. - * - * @param documentContext - * @param documentActivity - * @return String - replyTo address - */ - public String getReplyTo(ItemCollection documentContext, ItemCollection documentActivity) { - String sReplyTo = null; - boolean debug = logger.isLoggable(Level.FINE); - - // check for ReplyTo... - if ("1".equals(documentActivity.getItemValueString("keyMailReplyToCurrentUser"))) - sReplyTo = this.getWorkflowService().getUserName(); - else - sReplyTo = documentActivity.getItemValueString("namMailReplyToUser"); - if (debug) { - logger.finest("......ReplyTo=" + sReplyTo); + return sFrom; } - return sReplyTo; - } - - /** - * Computes the mail subject from the current workflow activity. This method can be overwritten by - * subclasses. - * - * @param documentContext - * @param documentActivity - * @return String - mail subject - * @throws PluginException - */ - public String getSubject(ItemCollection documentContext, ItemCollection documentActivity) - throws PluginException { - boolean debug = logger.isLoggable(Level.FINE); - String subject = getWorkflowService() - .adaptText(documentActivity.getItemValueString("txtMailSubject"), documentContext); - if (debug) { - logger.finest("......Subject: " + subject); + + /** + * Computes the replyTo address from current workflow activity. This method can + * be overwritten by subclasses. + * + * @param documentContext + * @param documentActivity + * @return String - replyTo address + */ + public String getReplyTo(ItemCollection documentContext, ItemCollection documentActivity) { + String sReplyTo = null; + boolean debug = logger.isLoggable(Level.FINE); + + // check for ReplyTo... + if ("1".equals(documentActivity.getItemValueString("keyMailReplyToCurrentUser"))) + sReplyTo = this.getWorkflowService().getUserName(); + else + sReplyTo = documentActivity.getItemValueString("namMailReplyToUser"); + if (debug) { + logger.finest("......ReplyTo=" + sReplyTo); + } + return sReplyTo; } - return subject; - } - - /** - * Computes the mail Recipients from the current workflow activity. This method can be overwritten - * by subclasses. - * - * @param documentContext - * @param documentActivity - * @return String list of Recipients - */ - @SuppressWarnings("unchecked") - public List getRecipients(ItemCollection documentContext, - ItemCollection documentActivity) { - - // build Recipient from Activity ... - List vectorRecipients = (List) documentActivity.getItemValue("namMailReceiver"); - if (vectorRecipients == null) - vectorRecipients = new Vector(); - - // read keyMailReceiverFields (multi value) - // here are the field names defined - mergeFieldList(documentContext, vectorRecipients, - documentActivity.getItemValue("keyMailReceiverFields")); - - // write debug Log - if (logger.isLoggable(Level.FINE)) { - logger.finest("......" + vectorRecipients.size() + " Receipients: "); - for (String rez : vectorRecipients) - logger.finest(" " + rez); + + /** + * Computes the mail subject from the current workflow activity. This method can + * be overwritten by subclasses. + * + * @param documentContext + * @param documentActivity + * @return String - mail subject + * @throws PluginException + */ + public String getSubject(ItemCollection documentContext, ItemCollection documentActivity) throws PluginException { + boolean debug = logger.isLoggable(Level.FINE); + String subject = getWorkflowService().adaptText(documentActivity.getItemValueString("txtMailSubject"), + documentContext); + if (debug) { + logger.finest("......Subject: " + subject); + } + return subject; } - return vectorRecipients; - } - - /** - * Computes the mail RecipientsCC from the current workflow activity. This method can be - * overwritten by subclasses. - * - * @param documentContext - * @param documentActivity - * @return String list of Recipients - */ - @SuppressWarnings("unchecked") - public List getRecipientsCC(ItemCollection documentContext, - ItemCollection documentActivity) { - - // build Recipient Vector from namMailReceiver - List vectorRecipients = - (List) documentActivity.getItemValue("namMailReceiverCC"); - if (vectorRecipients == null) - vectorRecipients = new Vector(); - - // now read keyMailReceiverFieldsCC (multiValue) - mergeFieldList(documentContext, vectorRecipients, - documentActivity.getItemValue("keyMailReceiverFieldsCC")); - - // write debug Log - if (logger.isLoggable(Level.FINE)) { - logger.finest("......" + vectorRecipients.size() + " ReceipientsCC: "); - for (String rez : vectorRecipients) - logger.finest(" " + rez); + /** + * Computes the mail Recipients from the current workflow activity. This method + * can be overwritten by subclasses. + * + * @param documentContext + * @param documentActivity + * @return String list of Recipients + */ + @SuppressWarnings("unchecked") + public List getRecipients(ItemCollection documentContext, ItemCollection documentActivity) { + + // build Recipient from Activity ... + List vectorRecipients = (List) documentActivity.getItemValue("namMailReceiver"); + if (vectorRecipients == null) + vectorRecipients = new Vector(); + + // read keyMailReceiverFields (multi value) + // here are the field names defined + mergeFieldList(documentContext, vectorRecipients, documentActivity.getItemValue("keyMailReceiverFields")); + + // write debug Log + if (logger.isLoggable(Level.FINE)) { + logger.finest("......" + vectorRecipients.size() + " Receipients: "); + for (String rez : vectorRecipients) + logger.finest(" " + rez); + } + + return vectorRecipients; } - return vectorRecipients; - } - - /** - * Computes the mail RecipientsBCC from the current workflow activity. This method can be - * overwritten by subclasses. - * - * @param documentContext - * @param documentActivity - * @return String list of Recipients - */ - @SuppressWarnings("unchecked") - public List getRecipientsBCC(ItemCollection documentContext, - ItemCollection documentActivity) { - - // build Recipient Vector from namMailReceiver - List vectorRecipients = - (List) documentActivity.getItemValue("namMailReceiverBCC"); - if (vectorRecipients == null) - vectorRecipients = new Vector(); - - // now read keyMailReceiverFieldsCC (multiValue) - mergeFieldList(documentContext, vectorRecipients, - documentActivity.getItemValue("keyMailReceiverFieldsBCC")); - - // write debug Log - if (logger.isLoggable(Level.FINE)) { - logger.finest("......" + vectorRecipients.size() + " ReceipientsBCC: "); - for (String rez : vectorRecipients) - logger.finest(" " + rez); + + /** + * Computes the mail RecipientsCC from the current workflow activity. This + * method can be overwritten by subclasses. + * + * @param documentContext + * @param documentActivity + * @return String list of Recipients + */ + @SuppressWarnings("unchecked") + public List getRecipientsCC(ItemCollection documentContext, ItemCollection documentActivity) { + + // build Recipient Vector from namMailReceiver + List vectorRecipients = (List) documentActivity.getItemValue("namMailReceiverCC"); + if (vectorRecipients == null) + vectorRecipients = new Vector(); + + // now read keyMailReceiverFieldsCC (multiValue) + mergeFieldList(documentContext, vectorRecipients, documentActivity.getItemValue("keyMailReceiverFieldsCC")); + + // write debug Log + if (logger.isLoggable(Level.FINE)) { + logger.finest("......" + vectorRecipients.size() + " ReceipientsCC: "); + for (String rez : vectorRecipients) + logger.finest(" " + rez); + } + return vectorRecipients; } - return vectorRecipients; - } - - /** - * Computes the mail body from the current workflow event. The method also updates the internal - * flag HTMLMail to indicate if the mail is send as HTML mail. - * - * In case the content contains a XSL Template, the template will be processed with the current - * document structure. - * - * The method can be overwritten by subclasses. - * - * @param documentContext - * @param documentActivity - * @return String - mail subject - * @throws PluginException - */ - public String getBody(ItemCollection documentContext, ItemCollection documentActivity) - throws PluginException { - // build mail body and replace dynamic values... - String aBodyText = getWorkflowService() - .adaptText(documentActivity.getItemValueString("rtfMailBody"), documentContext); - - // Test if mail body contains HTML content and updates the flag - // 'isHTMLMail'. - String sTestHTML = aBodyText.trim().toLowerCase(); - if (sTestHTML.startsWith(" getRecipientsBCC(ItemCollection documentContext, ItemCollection documentActivity) { + + // build Recipient Vector from namMailReceiver + List vectorRecipients = (List) documentActivity.getItemValue("namMailReceiverBCC"); + if (vectorRecipients == null) + vectorRecipients = new Vector(); + + // now read keyMailReceiverFieldsCC (multiValue) + mergeFieldList(documentContext, vectorRecipients, documentActivity.getItemValue("keyMailReceiverFieldsBCC")); + + // write debug Log + if (logger.isLoggable(Level.FINE)) { + logger.finest("......" + vectorRecipients.size() + " ReceipientsBCC: "); + for (String rez : vectorRecipients) + logger.finest(" " + rez); + } + return vectorRecipients; } - return aBodyText; - - } - - /** - * This method performs a XSL transformation based on the current Mail Body text. The xml source - * is generated form the current document context. - * - * encoding is set to UTF-8 - * - * @return translated email body - * @throws PluginException - * - */ - public String transformXSLBody(ItemCollection documentContext, String xslTemplate) - throws PluginException { - String encoding; - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - encoding = "UTF-8"; - boolean debug = logger.isLoggable(Level.FINE); - - if (debug) { - logger.finest("......transfor mail body based on XSL template...."); + /** + * Computes the mail body from the current workflow event. The method also + * updates the internal flag HTMLMail to indicate if the mail is send as HTML + * mail. + * + * In case the content contains a XSL Template, the template will be processed + * with the current document structure. + * + * The method can be overwritten by subclasses. + * + * @param documentContext + * @param documentActivity + * @return String - mail subject + * @throws PluginException + */ + public String getBody(ItemCollection documentContext, ItemCollection documentActivity) throws PluginException { + // build mail body and replace dynamic values... + String aBodyText = getWorkflowService().adaptText(documentActivity.getItemValueString("rtfMailBody"), + documentContext); + + // Test if mail body contains HTML content and updates the flag + // 'isHTMLMail'. + String sTestHTML = aBodyText.trim().toLowerCase(); + if (sTestHTML.startsWith(" mail session."); + } else { + + // test for property mail.charSet + if (mailCharSet != null && !mailCharSet.isEmpty()) { + setCharSet(mailCharSet); + + } + + // log mail Properties .... + if (debug) { + Properties props = mailSession.getProperties(); + Enumeration enumer = props.keys(); + while (enumer.hasMoreElements()) { + String aKey = enumer.nextElement().toString(); + logger.finest("...... ProperyName= " + aKey); + Object value = props.getProperty(aKey); + if (value == null) + logger.finest("...... PropertyValue=null"); + else + logger.finest("...... PropertyValue= " + props.getProperty(aKey).toString()); + } + } + mailMessage = new MimeMessage(mailSession); + mailMessage.setSentDate(new Date()); + mailMessage.setFrom(); + mimeMultipart = new MimeMultipart(); + } } - if (mailSession == null) { - logger.warning(" Lookup MailSession '" + MAIL_SESSION_NAME + "' failed: "); - logger.warning(" Unable to send mails! Verify server resources -> mail session."); - } else { - - // test for property mail.charSet - if (mailCharSet != null && !mailCharSet.isEmpty()) { - setCharSet(mailCharSet); - - } - - // log mail Properties .... - if (debug) { - Properties props = mailSession.getProperties(); - Enumeration enumer = props.keys(); - while (enumer.hasMoreElements()) { - String aKey = enumer.nextElement().toString(); - logger.finest("...... ProperyName= " + aKey); - Object value = props.getProperty(aKey); - if (value == null) - logger.finest("...... PropertyValue=null"); - else - logger.finest("...... PropertyValue= " + props.getProperty(aKey).toString()); + + /** + * This method creates an InternetAddress from a string. If the string has + * illegal characters like whitespace the string will be surrounded with "". + * + * The method can be overwritten by subclasses to return a different + * mail-address name or lookup a mail attribute in a directory. + * + * @param aAddr string + * @return InternetAddress + * @throws AddressException + */ + public InternetAddress getInternetAddress(String aAddr) throws AddressException { + InternetAddress inetAddr = null; + if (aAddr == null) { + return null; + } + + try { + // surround with "" if space + if (aAddr.indexOf(" ") > -1) + inetAddr = new InternetAddress("\"" + aAddr + "\""); + else + inetAddr = new InternetAddress(aAddr); + + } catch (AddressException ae) { + // return empty address part + ae.printStackTrace(); + return null; } - } - mailMessage = new MimeMessage(mailSession); - mailMessage.setSentDate(new Date()); - mailMessage.setFrom(); - mimeMultipart = new MimeMultipart(); + return inetAddr; } - } - - /** - * This method creates an InternetAddress from a string. If the string has illegal characters like - * whitespace the string will be surrounded with "". - * - * The method can be overwritten by subclasses to return a different mail-address name or lookup a - * mail attribute in a directory. - * - * @param aAddr string - * @return InternetAddress - * @throws AddressException - */ - public InternetAddress getInternetAddress(String aAddr) throws AddressException { - InternetAddress inetAddr = null; - if (aAddr == null) { - return null; + + /** + * This method transforms a vector of E-Mail addresses into an InternetAddress + * Array. Null values will be removed from list + * + * @param String List of adresses + * @return array of InternetAddresses + */ + @SuppressWarnings("rawtypes") + private InternetAddress[] getInternetAddressArray(List aList) { + // set TO Recipient + // store valid addresses into atemp vector to avoid null values + InternetAddress inetAddr = null; + if (aList == null) { + return null; + } + + Vector vReceipsTemp = new Vector(); + for (int i = 0; i < aList.size(); i++) { + try { + inetAddr = getInternetAddress(aList.get(i).toString()); + if (inetAddr != null && !"".equals(inetAddr.getAddress())) + vReceipsTemp.add(inetAddr); + } catch (AddressException e) { + // no todo + } + + } + + // rebuild new InternetAddress array from TempVector... + InternetAddress[] receipsAdrs = new InternetAddress[vReceipsTemp.size()]; + for (int i = 0; i < vReceipsTemp.size(); i++) { + receipsAdrs[i] = (InternetAddress) vReceipsTemp.elementAt(i); + } + return receipsAdrs; } - try { - // surround with "" if space - if (aAddr.indexOf(" ") > -1) - inetAddr = new InternetAddress("\"" + aAddr + "\""); - else - inetAddr = new InternetAddress(aAddr); - - } catch (AddressException ae) { - // return empty address part - ae.printStackTrace(); - return null; + /** + * This method returns the mail session object. + */ + public Session getMailSession() { + return mailSession; } - return inetAddr; - } - - /** - * This method transforms a vector of E-Mail addresses into an InternetAddress Array. Null values - * will be removed from list - * - * @param String List of adresses - * @return array of InternetAddresses - */ - @SuppressWarnings("rawtypes") - private InternetAddress[] getInternetAddressArray(List aList) { - // set TO Recipient - // store valid addresses into atemp vector to avoid null values - InternetAddress inetAddr = null; - if (aList == null) { - return null; + + public Message getMailMessage() { + return mailMessage; } - Vector vReceipsTemp = new Vector(); - for (int i = 0; i < aList.size(); i++) { - try { - inetAddr = getInternetAddress(aList.get(i).toString()); - if (inetAddr != null && !"".equals(inetAddr.getAddress())) - vReceipsTemp.add(inetAddr); - } catch (AddressException e) { - // no todo - } + public Multipart getMultipart() { + return mimeMultipart; + } + /** + * Return true if the mail body contains HTML content. + * + * @return + */ + public boolean isHTMLMail() { + return bHTMLMail; } - // rebuild new InternetAddress array from TempVector... - InternetAddress[] receipsAdrs = new InternetAddress[vReceipsTemp.size()]; - for (int i = 0; i < vReceipsTemp.size(); i++) { - receipsAdrs[i] = (InternetAddress) vReceipsTemp.elementAt(i); + public String getCharSet() { + return charSet; } - return receipsAdrs; - } - - /** - * This method returns the mail session object. - */ - public Session getMailSession() { - return mailSession; - } - - public Message getMailMessage() { - return mailMessage; - } - - public Multipart getMultipart() { - return mimeMultipart; - } - - /** - * Return true if the mail body contains HTML content. - * - * @return - */ - public boolean isHTMLMail() { - return bHTMLMail; - } - - public String getCharSet() { - return charSet; - } - - public void setCharSet(String charSet) { - this.charSet = charSet; - } - - /** - * This method returns a string representing the mail content type. The content type depends on - * the content of the mail body (html or plaintext) and contains optional the character set. - * - * If the mail is a HTML mail then the returned string contains 'text/html' otherwise it will - * contain 'text/plain'. - * - * The content - * - * @return - */ - public String getContentType() { - String sContentType = ""; - if (bHTMLMail) { - sContentType = CONTENTTYPE_TEXT_HTML; - } else { - sContentType = CONTENTTYPE_TEXT_PLAIN; + + public void setCharSet(String charSet) { + this.charSet = charSet; } - if (this.getCharSet() != null && !this.getCharSet().isEmpty()) { - sContentType = sContentType + "; charset=" + this.getCharSet(); + + /** + * This method returns a string representing the mail content type. The content + * type depends on the content of the mail body (html or plaintext) and contains + * optional the character set. + * + * If the mail is a HTML mail then the returned string contains 'text/html' + * otherwise it will contain 'text/plain'. + * + * The content + * + * @return + */ + public String getContentType() { + String sContentType = ""; + if (bHTMLMail) { + sContentType = CONTENTTYPE_TEXT_HTML; + } else { + sContentType = CONTENTTYPE_TEXT_PLAIN; + } + if (this.getCharSet() != null && !this.getCharSet().isEmpty()) { + sContentType = sContentType + "; charset=" + this.getCharSet(); + } + return sContentType; } - return sContentType; - } } diff --git a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/plugins/OwnerPlugin.java b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/plugins/OwnerPlugin.java index e59a8b7bd..283164021 100644 --- a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/plugins/OwnerPlugin.java +++ b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/plugins/OwnerPlugin.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *
    - *  Imixs Workflow 
    +/*  
    + *  Imixs-Workflow 
    + *  
      *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
      *  http://www.imixs.com
      *  
    @@ -22,10 +22,9 @@
      *      https://github.com/imixs/imixs-workflow
      *  
      *  Contributors:  
    - *      Imixs Software Solutions GmbH - initial API and implementation
    + *      Imixs Software Solutions GmbH - Project Management
      *      Ralph Soika - Software Developer
    - * 
    - *******************************************************************************/ + */ package org.imixs.workflow.engine.plugins; @@ -38,9 +37,9 @@ import org.imixs.workflow.exceptions.PluginException; /** - * This plugin implements a ownership control by evaluating the configuration of an BPMN Event - * element. The Plugin updates the WorkItem attribute '$owner' depending on the provided - * information. + * This plugin implements a ownership control by evaluating the configuration of + * an BPMN Event element. The Plugin updates the WorkItem attribute '$owner' + * depending on the provided information. * *

    * These attributes defined in Activity Entity are evaluated by the plugin: @@ -51,9 +50,10 @@ * * * - * NOTE: Models generated with the first version of the Imixs-Workflow Modeler provide a different - * set of attributes. Therefore the plugin implements a fallback method to support deprecated - * models. The fallback method evaluate the following list of attributes defined in Activity Entity: + * NOTE: Models generated with the first version of the Imixs-Workflow Modeler + * provide a different set of attributes. Therefore the plugin implements a + * fallback method to support deprecated models. The fallback method evaluate + * the following list of attributes defined in Activity Entity: *

    * *

      @@ -66,8 +66,8 @@ * * #Issue 133: Extend access plug-in to resolve owner settings in process entity * - * The AccessPlugin also evaluates the ACL settings in the next ProcessEntity which is supported by - * newer versions of the imixs-bpmn modeler. + * The AccessPlugin also evaluates the ACL settings in the next ProcessEntity + * which is supported by newer versions of the imixs-bpmn modeler. * * * @@ -78,128 +78,126 @@ public class OwnerPlugin extends AbstractPlugin { - public final static String OWNER = "$owner"; + public final static String OWNER = "$owner"; - private ItemCollection documentContext; - private ItemCollection documentActivity; - private ItemCollection documentNextProcessEntity; + private ItemCollection documentContext; + private ItemCollection documentActivity; + private ItemCollection documentNextProcessEntity; - private static Logger logger = Logger.getLogger(OwnerPlugin.class.getName()); + private static Logger logger = Logger.getLogger(OwnerPlugin.class.getName()); - /** - * changes the '$owner' item depending to the activityentity or processEntity - * - */ - public ItemCollection run(ItemCollection adocumentContext, ItemCollection adocumentActivity) - throws PluginException { + /** + * changes the '$owner' item depending to the activityentity or processEntity + * + */ + public ItemCollection run(ItemCollection adocumentContext, ItemCollection adocumentActivity) + throws PluginException { - documentContext = adocumentContext; - documentActivity = adocumentActivity; + documentContext = adocumentContext; + documentActivity = adocumentActivity; - // get next process entity - try { - // documentNextProcessEntity = this.getWorkflowService().evalNextTask(adocumentContext, - // adocumentActivity); - documentNextProcessEntity = this.getWorkflowService().evalNextTask(adocumentContext); - } catch (ModelException e) { - throw new PluginException(OwnerPlugin.class.getSimpleName(), e.getErrorCode(), - e.getMessage()); - } + // get next process entity + try { + // documentNextProcessEntity = + // this.getWorkflowService().evalNextTask(adocumentContext, + // adocumentActivity); + documentNextProcessEntity = this.getWorkflowService().evalNextTask(adocumentContext); + } catch (ModelException e) { + throw new PluginException(OwnerPlugin.class.getSimpleName(), e.getErrorCode(), e.getMessage()); + } - // in case the activity is connected to a followup activity the - // nextProcess can be null! - - // test update mode of activity and process entity - if true clear the - // existing values. - if (documentActivity.getItemValueBoolean("keyupdateacl") == false - && (documentNextProcessEntity == null - || documentNextProcessEntity.getItemValueBoolean("keyupdateacl") == false)) { - // no update! - return documentContext; - } else { - // activity settings will not be merged with process entity - // settings! - if (documentActivity.getItemValueBoolean("keyupdateacl") == true) { - updateOwnerByItemCollection(documentActivity); - } else { - updateOwnerByItemCollection(documentNextProcessEntity); - } - } + // in case the activity is connected to a followup activity the + // nextProcess can be null! + + // test update mode of activity and process entity - if true clear the + // existing values. + if (documentActivity.getItemValueBoolean("keyupdateacl") == false && (documentNextProcessEntity == null + || documentNextProcessEntity.getItemValueBoolean("keyupdateacl") == false)) { + // no update! + return documentContext; + } else { + // activity settings will not be merged with process entity + // settings! + if (documentActivity.getItemValueBoolean("keyupdateacl") == true) { + updateOwnerByItemCollection(documentActivity); + } else { + updateOwnerByItemCollection(documentNextProcessEntity); + } + } - return documentContext; - } - - /** - * This method updates the owner of a workitem depending on a given model entity The model entity - * should provide the following attributes: - * - * keyupdateacl, namOwnershipNames,keyOwnershipFields - * - * - * The method did not clear the exiting values of namowner - * - * @throws PluginException - */ - @SuppressWarnings({"unchecked", "rawtypes"}) - private void updateOwnerByItemCollection(ItemCollection modelEntity) throws PluginException { - - if (modelEntity == null || modelEntity.getItemValueBoolean("keyupdateacl") == false) { - // no update necessary - return; + return documentContext; } - List newOwnerList; - newOwnerList = new ArrayList(); - - // add names - mergeRoles(newOwnerList, modelEntity.getItemValue("namOwnershipNames"), documentContext); - // add Mapped Fields - mergeFieldList(documentContext, newOwnerList, modelEntity.getItemValue("keyOwnershipFields")); - // clean Vector - newOwnerList = uniqueList(newOwnerList); - - // update ownerlist.... - documentContext.replaceItemValue(OWNER, newOwnerList); - if ((logger.isLoggable(Level.FINE)) && (newOwnerList.size() > 0)) { - logger.finest("......Owners:"); - for (int j = 0; j < newOwnerList.size(); j++) - logger.finest(" '" + (String) newOwnerList.get(j) + "'"); - } + /** + * This method updates the owner of a workitem depending on a given model entity + * The model entity should provide the following attributes: + * + * keyupdateacl, namOwnershipNames,keyOwnershipFields + * + * + * The method did not clear the exiting values of namowner + * + * @throws PluginException + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + private void updateOwnerByItemCollection(ItemCollection modelEntity) throws PluginException { + + if (modelEntity == null || modelEntity.getItemValueBoolean("keyupdateacl") == false) { + // no update necessary + return; + } + + List newOwnerList; + newOwnerList = new ArrayList(); + + // add names + mergeRoles(newOwnerList, modelEntity.getItemValue("namOwnershipNames"), documentContext); + // add Mapped Fields + mergeFieldList(documentContext, newOwnerList, modelEntity.getItemValue("keyOwnershipFields")); + // clean Vector + newOwnerList = uniqueList(newOwnerList); + + // update ownerlist.... + documentContext.replaceItemValue(OWNER, newOwnerList); + if ((logger.isLoggable(Level.FINE)) && (newOwnerList.size() > 0)) { + logger.finest("......Owners:"); + for (int j = 0; j < newOwnerList.size(); j++) + logger.finest(" '" + (String) newOwnerList.get(j) + "'"); + } + + // we also need to support the deprecated iten name "namOwner" which was + // replaced since version + // 5.0.2 by "owner" + documentContext.replaceItemValue("namOwner", newOwnerList); + } - // we also need to support the deprecated iten name "namOwner" which was replaced since version - // 5.0.2 by "owner" - documentContext.replaceItemValue("namOwner", newOwnerList); - - } - - - /** - * This method merges the role names from a SourceList into a valueList and removes duplicates. - * - * The AddaptText event is fired so a client can adapt a role name. - * - * @param valueList - * @param sourceList - * @throws PluginException - */ - @SuppressWarnings({"rawtypes", "unchecked"}) - public void mergeRoles(List valueList, List sourceList, ItemCollection documentContext) - throws PluginException { - if ((sourceList != null) && (sourceList.size() > 0)) { - for (Object o : sourceList) { - if (valueList.indexOf(o) == -1) { - if (o instanceof String) { - // addapt textList - List adaptedRoles = - this.getWorkflowService().adaptTextList((String) o, documentContext); - valueList.addAll(adaptedRoles);// .add(getWorkflowService().adaptText((String)o, - // documentContext)); - } else { - valueList.add(o); - } + /** + * This method merges the role names from a SourceList into a valueList and + * removes duplicates. + * + * The AddaptText event is fired so a client can adapt a role name. + * + * @param valueList + * @param sourceList + * @throws PluginException + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + public void mergeRoles(List valueList, List sourceList, ItemCollection documentContext) throws PluginException { + if ((sourceList != null) && (sourceList.size() > 0)) { + for (Object o : sourceList) { + if (valueList.indexOf(o) == -1) { + if (o instanceof String) { + // addapt textList + List adaptedRoles = this.getWorkflowService().adaptTextList((String) o, + documentContext); + valueList.addAll(adaptedRoles);// .add(getWorkflowService().adaptText((String)o, + // documentContext)); + } else { + valueList.add(o); + } + } + } } - } } - } } diff --git a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/plugins/ReportPlugin.java b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/plugins/ReportPlugin.java index 25f6c09b5..c62cbc0a8 100644 --- a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/plugins/ReportPlugin.java +++ b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/plugins/ReportPlugin.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *
      - *  Imixs Workflow 
      +/*  
      + *  Imixs-Workflow 
      + *  
        *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
        *  http://www.imixs.com
        *  
      @@ -22,10 +22,9 @@
        *      https://github.com/imixs/imixs-workflow
        *  
        *  Contributors:  
      - *      Imixs Software Solutions GmbH - initial API and implementation
      + *      Imixs Software Solutions GmbH - Project Management
        *      Ralph Soika - Software Developer
      - * 
      - *******************************************************************************/ + */ package org.imixs.workflow.engine.plugins; @@ -44,21 +43,23 @@ import org.imixs.workflow.xml.XSLHandler; /** - * This plug-in executes a Imixs Report definition and stores the result either into the current - * workitem ($file) or into the file system. The corresponding BPMN event provide the following - * properties: + * This plug-in executes a Imixs Report definition and stores the result either + * into the current workitem ($file) or into the file system. The corresponding + * BPMN event provide the following properties: *

      *

        *
      • txtReportName=Name of the Report to be processed - *
      • txtReportFilePath= optional filename or file path the result will be saved + *
      • txtReportFilePath= optional filename or file path the result will be + * saved *
      • txtReportTarget = where the result is saved (0=workitem, 2= disk) *
      *

      * * CHANGES V 2.0 *

      - * In the current version 2.0, only the processed document will be used as the xml input source for - * the XSL transformation. A search query will currently not be evaluated. + * In the current version 2.0, only the processed document will be used as the + * xml input source for the XSL transformation. A search query will currently + * not be evaluated. * * * @author Ralph Soika @@ -67,122 +68,122 @@ public class ReportPlugin extends AbstractPlugin { - public static final String INVALID_CONTEXT = "INVALID_CONTEXT"; - public static final String REPORT_UNDEFINED = "REPORT_UNDEFINED"; - public static final String INVALID_REPORT_DEFINITION = "INVALID_REPORT_DEFINITION"; - - private static Logger logger = Logger.getLogger(ReportPlugin.class.getName()); - - /** - * Executes a report defined defined by the event in the attribute 'txtReportName'. - *

      - * The XML Source used by this method is the XML representation of the current document. The Query - * Statement will not be evaluated - *

      - * - */ - public ItemCollection run(ItemCollection adocumentContext, ItemCollection adocumentActivity) - throws PluginException { - - String reportName = adocumentActivity.getItemValueString("txtReportName"); - String reportFilePath = adocumentActivity.getItemValueString("txtReportFilePath"); - if ("".equals(reportFilePath)) - reportFilePath = reportName; - - // replace dynamic field values - reportFilePath = getWorkflowService().adaptText(reportFilePath, adocumentContext); - - String reportTarget = adocumentActivity.getItemValueString("txtReportTarget"); - - if ("".equals(reportName)) - return adocumentContext; - - ItemCollection itemCol = getWorkflowService().getReportService().findReport(reportName); - if (itemCol == null) { - // report undefined - throw new PluginException(ReportPlugin.class.getSimpleName(), REPORT_UNDEFINED, - "Report '" + reportName + " is undefined", new Object[] {reportName}); - } + public static final String INVALID_CONTEXT = "INVALID_CONTEXT"; + public static final String REPORT_UNDEFINED = "REPORT_UNDEFINED"; + public static final String INVALID_REPORT_DEFINITION = "INVALID_REPORT_DEFINITION"; + + private static Logger logger = Logger.getLogger(ReportPlugin.class.getName()); + + /** + * Executes a report defined defined by the event in the attribute + * 'txtReportName'. + *

      + * The XML Source used by this method is the XML representation of the current + * document. The Query Statement will not be evaluated + *

      + * + */ + public ItemCollection run(ItemCollection adocumentContext, ItemCollection adocumentActivity) + throws PluginException { + + String reportName = adocumentActivity.getItemValueString("txtReportName"); + String reportFilePath = adocumentActivity.getItemValueString("txtReportFilePath"); + if ("".equals(reportFilePath)) + reportFilePath = reportName; + + // replace dynamic field values + reportFilePath = getWorkflowService().adaptText(reportFilePath, adocumentContext); + + String reportTarget = adocumentActivity.getItemValueString("txtReportTarget"); + + if ("".equals(reportName)) + return adocumentContext; + + ItemCollection itemCol = getWorkflowService().getReportService().findReport(reportName); + if (itemCol == null) { + // report undefined + throw new PluginException(ReportPlugin.class.getSimpleName(), REPORT_UNDEFINED, + "Report '" + reportName + " is undefined", new Object[] { reportName }); + } - String xslTemplate = itemCol.getItemValueString("xsl").trim(); - // if no XSL is provided return - if ("".equals(xslTemplate)) - return adocumentContext; - - String sContentType = itemCol.getItemValueString("contenttype"); - if ("".equals(sContentType)) - sContentType = "text/html"; - - String encoding = itemCol.getItemValueString("encoding"); - // no encoding defined so take a default encoding - // (UTF-8) - if ("".equals(encoding)) - encoding = "UTF-8"; - - try { - // TODO : we need to clarify if the method call unescapeXMLContent() is - // necessary - - XMLDocument xml = XMLDocumentAdapter.getDocument(adocumentContext); - StringWriter writer = new StringWriter(); - - JAXBContext context = JAXBContext.newInstance(XMLDataCollection.class); - Marshaller m = context.createMarshaller(); - m.setProperty("jaxb.encoding", encoding); - m.marshal(xml, writer); - - // create a ByteArray Output Stream - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - try { - if ("application/pdf".equals(sContentType.toLowerCase())) { - logger.warning("FOP Transformation is not yet implementd"); - // org.imixs.workflow.jaxrs.ReportRestService.fopTranformation(xmlContentExtended, - // xslTemplate, encoding, - // outputStream); - } else { - XSLHandler.transform(writer.toString(), xslTemplate, encoding, outputStream); + String xslTemplate = itemCol.getItemValueString("xsl").trim(); + // if no XSL is provided return + if ("".equals(xslTemplate)) + return adocumentContext; + + String sContentType = itemCol.getItemValueString("contenttype"); + if ("".equals(sContentType)) + sContentType = "text/html"; + + String encoding = itemCol.getItemValueString("encoding"); + // no encoding defined so take a default encoding + // (UTF-8) + if ("".equals(encoding)) + encoding = "UTF-8"; - } - } finally { - outputStream.close(); - } - - // write to workitem - if (reportTarget.isEmpty() || "0".equals(reportTarget)) { - FileData fileData = - new FileData(reportFilePath, outputStream.toByteArray(), sContentType, null); - adocumentContext.addFileData(fileData); - } - // write to blob - if ("1".equals(reportTarget)) { - logger.warning( - "Writing into BlobWorkitem is no longer supported - please use the DMSPlugin for transfer"); - - } - // write to filesystem - if ("2".equals(reportTarget)) { - FileOutputStream fos = null; try { - fos = new FileOutputStream(reportFilePath); - fos.write(outputStream.toByteArray()); - fos.flush(); - } finally { - if (fos != null) { - fos.close(); - } + // TODO : we need to clarify if the method call unescapeXMLContent() is + // necessary + + XMLDocument xml = XMLDocumentAdapter.getDocument(adocumentContext); + StringWriter writer = new StringWriter(); + + JAXBContext context = JAXBContext.newInstance(XMLDataCollection.class); + Marshaller m = context.createMarshaller(); + m.setProperty("jaxb.encoding", encoding); + m.marshal(xml, writer); + + // create a ByteArray Output Stream + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + try { + if ("application/pdf".equals(sContentType.toLowerCase())) { + logger.warning("FOP Transformation is not yet implementd"); + // org.imixs.workflow.jaxrs.ReportRestService.fopTranformation(xmlContentExtended, + // xslTemplate, encoding, + // outputStream); + } else { + XSLHandler.transform(writer.toString(), xslTemplate, encoding, outputStream); + + } + } finally { + outputStream.close(); + } + + // write to workitem + if (reportTarget.isEmpty() || "0".equals(reportTarget)) { + FileData fileData = new FileData(reportFilePath, outputStream.toByteArray(), sContentType, null); + adocumentContext.addFileData(fileData); + } + // write to blob + if ("1".equals(reportTarget)) { + logger.warning( + "Writing into BlobWorkitem is no longer supported - please use the DMSPlugin for transfer"); + + } + // write to filesystem + if ("2".equals(reportTarget)) { + FileOutputStream fos = null; + try { + fos = new FileOutputStream(reportFilePath); + fos.write(outputStream.toByteArray()); + fos.flush(); + } finally { + if (fos != null) { + fos.close(); + } + } + } + + return adocumentContext; + } catch (Exception e) { + // report undefined + throw new PluginException(ReportPlugin.class.getSimpleName(), INVALID_REPORT_DEFINITION, + "Unable to process report '" + reportName + "' ", new Object[] { reportName }); } - } - - return adocumentContext; - } catch (Exception e) { - // report undefined - throw new PluginException(ReportPlugin.class.getSimpleName(), INVALID_REPORT_DEFINITION, - "Unable to process report '" + reportName + "' ", new Object[] {reportName}); } - } - public void close(int status) throws PluginException { + public void close(int status) throws PluginException { - } + } } diff --git a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/plugins/ResultPlugin.java b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/plugins/ResultPlugin.java index 6d49e7521..73fdabfaf 100644 --- a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/plugins/ResultPlugin.java +++ b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/plugins/ResultPlugin.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *

      - *  Imixs Workflow 
      +/*  
      + *  Imixs-Workflow 
      + *  
        *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
        *  http://www.imixs.com
        *  
      @@ -22,10 +22,9 @@
        *      https://github.com/imixs/imixs-workflow
        *  
        *  Contributors:  
      - *      Imixs Software Solutions GmbH - initial API and implementation
      + *      Imixs Software Solutions GmbH - Project Management
        *      Ralph Soika - Software Developer
      - * 
      - *******************************************************************************/ + */ package org.imixs.workflow.engine.plugins; @@ -33,15 +32,15 @@ import org.imixs.workflow.exceptions.PluginException; /** - * This Plug-In evaluates the result message provided by the Activity property 'txtActivityResult'. - * The value will be parsed for the xml tag 'item' + * This Plug-In evaluates the result message provided by the Activity property + * 'txtActivityResult'. The value will be parsed for the xml tag 'item' * * * value * * - * The provided value will be assigned to the named property. The value can also be evaluated with - * the tag 'itemValue' + * The provided value will be assigned to the named property. The value can also + * be evaluated with the tag 'itemValue' * * * namCreator @@ -56,18 +55,15 @@ */ public class ResultPlugin extends AbstractPlugin { - public ItemCollection run(ItemCollection documentContext, ItemCollection adocumentActivity) - throws PluginException { - // evaluate new items.... - ItemCollection evalItemCollection = - getWorkflowService().evalWorkflowResult(adocumentActivity, documentContext, true); - // copy values - if (evalItemCollection != null) { - documentContext.replaceAllItems(evalItemCollection.getAllItems()); + public ItemCollection run(ItemCollection documentContext, ItemCollection adocumentActivity) throws PluginException { + // evaluate new items.... + ItemCollection evalItemCollection = getWorkflowService().evalWorkflowResult(adocumentActivity, documentContext, + true); + // copy values + if (evalItemCollection != null) { + documentContext.replaceAllItems(evalItemCollection.getAllItems()); + } + return documentContext; } - return documentContext; - } - - } diff --git a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/plugins/RulePlugin.java b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/plugins/RulePlugin.java index 3239cbf31..3f1138e0f 100644 --- a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/plugins/RulePlugin.java +++ b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/plugins/RulePlugin.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *
      - *  Imixs Workflow 
      +/*  
      + *  Imixs-Workflow 
      + *  
        *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
        *  http://www.imixs.com
        *  
      @@ -22,10 +22,9 @@
        *      https://github.com/imixs/imixs-workflow
        *  
        *  Contributors:  
      - *      Imixs Software Solutions GmbH - initial API and implementation
      + *      Imixs Software Solutions GmbH - Project Management
        *      Ralph Soika - Software Developer
      - * 
      - *******************************************************************************/ + */ package org.imixs.workflow.engine.plugins; @@ -38,54 +37,59 @@ import org.imixs.workflow.exceptions.PluginException; /** - * The Imixs Rule Plugin evaluates a business rule provided by the current ActiviyEntity. + * The Imixs Rule Plugin evaluates a business rule provided by the current + * ActiviyEntity. * - * A business rule can be written in any script language supported by the JVM. The Script Language - * is defined by the property 'txtBusinessRuleEngine' from the current Event element. The script is - * defined by the property 'txtBusinessRule'. + * A business rule can be written in any script language supported by the JVM. + * The Script Language is defined by the property 'txtBusinessRuleEngine' from + * the current Event element. The script is defined by the property + * 'txtBusinessRule'. * - * The Script can access all basic item values from the current workItem and also the event by the - * provided JSON objects 'workitem' and 'event'. + * The Script can access all basic item values from the current workItem and + * also the event by the provided JSON objects 'workitem' and 'event'. * * * // test first value of the workitem attribute 'txtname' * var isValid = ('Anna'==workitem.txtname[0]); * * - * A script can add new values for the current workitem by providing the JSON object 'result'. + * A script can add new values for the current workitem by providing the JSON + * object 'result'. * * * var result={ someitem:'Hello World', somenumber:1}; * * - * Also change values of the event object can be made by the script. These changes will be reflected - * back for further processing. + * Also change values of the event object can be made by the script. These + * changes will be reflected back for further processing. * * * // disable mail * event.keymailenabled='0'; * * - * A script can set the variables 'isValid' and 'followUp' to validate a workItem or set a new - * followUp activity. + * A script can set the variables 'isValid' and 'followUp' to validate a + * workItem or set a new followUp activity. * * * result={ isValid:false }; * * - * If the script set the variable 'isValid' to false then the plugin throws a PluginExcpetion. The - * Plugin evaluates the variables 'errorCode' and errorMessage. If these variables are set by the - * Script then the PluginException will be updates with the corresponding errorCode and the - * 'errorMessage' as params[]. If no errorCode is set then the errorCode of the PluginException will - * default to 'VALIDATION_ERROR'. + * If the script set the variable 'isValid' to false then the plugin throws a + * PluginExcpetion. The Plugin evaluates the variables 'errorCode' and + * errorMessage. If these variables are set by the Script then the + * PluginException will be updates with the corresponding errorCode and the + * 'errorMessage' as params[]. If no errorCode is set then the errorCode of the + * PluginException will default to 'VALIDATION_ERROR'. * - * If the script set the variable 'followUp' the follow-up behavior of the current ActivityEntity - * will be updated. + * If the script set the variable 'followUp' the follow-up behavior of the + * current ActivityEntity will be updated. * - * If a script can not be evaluated by the scriptEngin a PluginExcpetion with the errorCode - * 'INVALID_SCRIPT' will be thrown. + * If a script can not be evaluated by the scriptEngin a PluginExcpetion with + * the errorCode 'INVALID_SCRIPT' will be thrown. * - * NOTE: all variable names are case sensitive! All JSON object elements are lower case! + * NOTE: all variable names are case sensitive! All JSON object elements are + * lower case! * * @author Ralph Soika * @version 3.0 @@ -94,201 +98,203 @@ public class RulePlugin extends AbstractPlugin { - public static final String INVALID_SCRIPT = "INVALID_SCRIPT"; - public static final String VALIDATION_ERROR = "VALIDATION_ERROR"; - private static Logger logger = Logger.getLogger(RulePlugin.class.getName()); - - /** - * The run method evaluates a script provided by an activityEntity with the specified - * scriptEngine. - * - * After successful evaluation the method verifies the object value 'isValid'. If isValid is false - * then the method throws a PluginException. In addition the method evaluates the object values - * 'errorCode' and 'errorMessage' which will be part of the PluginException. - * - * If 'isValid' is true or undefined the method evaluates the object value 'followUp'. If a - * followUp value is defined by the script the method update the model follow up definition. - * - * If a script changes properties of the activity entity the method will evaluate these changes - * and update the ItemCollection for further processing. - * - */ - public ItemCollection run(ItemCollection adocumentContext, ItemCollection adocumentActivity) - throws PluginException { - - // test if a business rule is defined - String script = adocumentActivity.getItemValueString("txtBusinessRule"); - if ("".equals(script.trim())) - return adocumentContext; // nothing to do - - String sEngineType = adocumentActivity.getItemValueString("txtBusinessRuleEngine"); - RuleEngine ruleEngine = new RuleEngine(sEngineType); - - ItemCollection result = - ruleEngine.evaluateBusinessRule(script, adocumentContext, adocumentActivity); - - // support deprecated scripts without a 'result' JSON object ... - if (result == null) { - evaluateDeprecatedScript(ruleEngine, adocumentActivity); - } else { - // first we test for the isValid variable - Boolean isValidActivity = true; - // first test result object - if (result.hasItem("isValid")) { - isValidActivity = result.getItemValueBoolean("isValid"); - result.removeItem("isValid"); - } - // if isValid==false then throw a PluginException - if (isValidActivity != null && !isValidActivity) { - // test if a error code is provided! - String sErrorCode = VALIDATION_ERROR; - Object oErrorCode = null; - if (result.hasItem("errorCode")) { - oErrorCode = result.getItemValueString("errorCode"); - result.removeItem("errorCode"); - } - if (oErrorCode != null && oErrorCode instanceof String) { - sErrorCode = oErrorCode.toString(); + public static final String INVALID_SCRIPT = "INVALID_SCRIPT"; + public static final String VALIDATION_ERROR = "VALIDATION_ERROR"; + private static Logger logger = Logger.getLogger(RulePlugin.class.getName()); + + /** + * The run method evaluates a script provided by an activityEntity with the + * specified scriptEngine. + * + * After successful evaluation the method verifies the object value 'isValid'. + * If isValid is false then the method throws a PluginException. In addition the + * method evaluates the object values 'errorCode' and 'errorMessage' which will + * be part of the PluginException. + * + * If 'isValid' is true or undefined the method evaluates the object value + * 'followUp'. If a followUp value is defined by the script the method update + * the model follow up definition. + * + * If a script changes properties of the activity entity the method will + * evaluate these changes and update the ItemCollection for further processing. + * + */ + public ItemCollection run(ItemCollection adocumentContext, ItemCollection adocumentActivity) + throws PluginException { + + // test if a business rule is defined + String script = adocumentActivity.getItemValueString("txtBusinessRule"); + if ("".equals(script.trim())) + return adocumentContext; // nothing to do + + String sEngineType = adocumentActivity.getItemValueString("txtBusinessRuleEngine"); + RuleEngine ruleEngine = new RuleEngine(sEngineType); + + ItemCollection result = ruleEngine.evaluateBusinessRule(script, adocumentContext, adocumentActivity); + + // support deprecated scripts without a 'result' JSON object ... + if (result == null) { + evaluateDeprecatedScript(ruleEngine, adocumentActivity); + } else { + // first we test for the isValid variable + Boolean isValidActivity = true; + // first test result object + if (result.hasItem("isValid")) { + isValidActivity = result.getItemValueBoolean("isValid"); + result.removeItem("isValid"); + } + // if isValid==false then throw a PluginException + if (isValidActivity != null && !isValidActivity) { + // test if a error code is provided! + String sErrorCode = VALIDATION_ERROR; + Object oErrorCode = null; + if (result.hasItem("errorCode")) { + oErrorCode = result.getItemValueString("errorCode"); + result.removeItem("errorCode"); + } + if (oErrorCode != null && oErrorCode instanceof String) { + sErrorCode = oErrorCode.toString(); + } + + // next test for errorMessage (this can be a string or an array + // of strings + Object[] params = null; + if (result.hasItem("errorMessage")) { + params = result.getItemValue("errorMessage").toArray(); + result.removeItem("errorMessage"); + } + // finally we throw the Plugin Exception + throw new PluginException(RulePlugin.class.getName(), sErrorCode, + "BusinessRule: validation failed - ErrorCode=" + sErrorCode, params); + } + + // now test the variable 'followUp' + Object followUp = null; + // first test result object + if (result.hasItem("followUp")) { + followUp = result.getItemValueString("followUp"); + result.removeItem("followUp"); + } + + // If followUp is defined we update now the activityEntity.... + if (followUp != null) { + // try to get double value... + Double d = Double.valueOf(followUp.toString()); + Long followUpActivity = d.longValue(); + if (followUpActivity != null && followUpActivity > 0) { + adocumentActivity.replaceItemValue("keyFollowUp", "1"); + adocumentActivity.replaceItemValue("numNextActivityID", followUpActivity); + } + } + + // if result has item values then we update now the current + // workitem iterate over all entries + + for (Map.Entry> entry : result.getAllItems().entrySet()) { + String itemName = entry.getKey(); + // skip fieldnames starting with '$' + if (!itemName.startsWith("$")) { + logger.finest("......Update item '" + itemName + "'"); + adocumentContext.replaceItemValue(itemName, entry.getValue()); + } + } } - // next test for errorMessage (this can be a string or an array - // of strings - Object[] params = null; - if (result.hasItem("errorMessage")) { - params = result.getItemValue("errorMessage").toArray(); - result.removeItem("errorMessage"); - } - // finally we throw the Plugin Exception - throw new PluginException(RulePlugin.class.getName(), sErrorCode, - "BusinessRule: validation failed - ErrorCode=" + sErrorCode, params); - } - - // now test the variable 'followUp' - Object followUp = null; - // first test result object - if (result.hasItem("followUp")) { - followUp = result.getItemValueString("followUp"); - result.removeItem("followUp"); - } - - // If followUp is defined we update now the activityEntity.... - if (followUp != null) { - // try to get double value... - Double d = Double.valueOf(followUp.toString()); - Long followUpActivity = d.longValue(); - if (followUpActivity != null && followUpActivity > 0) { - adocumentActivity.replaceItemValue("keyFollowUp", "1"); - adocumentActivity.replaceItemValue("numNextActivityID", followUpActivity); - } - } + // Finally update the Activity entity. Values can be provided optional + // by the script variable 'event'... + updateEvent(ruleEngine, adocumentActivity); - // if result has item values then we update now the current - // workitem iterate over all entries + return adocumentContext; - for (Map.Entry> entry : result.getAllItems().entrySet()) { - String itemName = entry.getKey(); - // skip fieldnames starting with '$' - if (!itemName.startsWith("$")) { - logger.finest("......Update item '" + itemName + "'"); - adocumentContext.replaceItemValue(itemName, entry.getValue()); - } - } } - // Finally update the Activity entity. Values can be provided optional - // by the script variable 'event'... - updateEvent(ruleEngine, adocumentActivity); - - return adocumentContext; - - } - - /** - * This method evaluates the script result without a 'result' JSON object. This kind of scripts is - * marked as deprecated. - * - * @param ruleEngine - * @param adocumentActivity - * @throws PluginException - */ - private void evaluateDeprecatedScript(RuleEngine ruleEngine, ItemCollection adocumentActivity) - throws PluginException { - // deprecated evaluation: - logger.warning("Script is deprecated - use JSON object 'result'"); - // first we test for the isValid variable - Boolean isValidActivity = true; - - // if isValid is not provided by result then we look for a - // direct var definition (this is for backward compatibility of - // older scripts) - isValidActivity = (Boolean) ruleEngine.getScriptEngine().get("isValid"); - - // if isValid==false then throw a PluginException.... - if (isValidActivity != null && !isValidActivity) { - // test if a error code is provided! - String sErrorCode = VALIDATION_ERROR; - Object oErrorCode = null; - - // if errorCode is not provided by result then we look for a - // direct var definition (this is for backward compatibility - // of older scripts) - oErrorCode = ruleEngine.getScriptEngine().get("errorCode"); - - if (oErrorCode != null && oErrorCode instanceof String) { - sErrorCode = oErrorCode.toString(); - } - - // next test for errorMessage (this can be a string or an array - // of strings - Object[] params = null; - - params = ruleEngine.evaluateNativeScriptArray("errorMessage"); - - // finally we throw the Plugin Exception - throw new PluginException(RulePlugin.class.getName(), sErrorCode, - "BusinessRule: validation failed - ErrorCode=" + sErrorCode, params); - } + /** + * This method evaluates the script result without a 'result' JSON object. This + * kind of scripts is marked as deprecated. + * + * @param ruleEngine + * @param adocumentActivity + * @throws PluginException + */ + private void evaluateDeprecatedScript(RuleEngine ruleEngine, ItemCollection adocumentActivity) + throws PluginException { + // deprecated evaluation: + logger.warning("Script is deprecated - use JSON object 'result'"); + // first we test for the isValid variable + Boolean isValidActivity = true; + + // if isValid is not provided by result then we look for a + // direct var definition (this is for backward compatibility of + // older scripts) + isValidActivity = (Boolean) ruleEngine.getScriptEngine().get("isValid"); + + // if isValid==false then throw a PluginException.... + if (isValidActivity != null && !isValidActivity) { + // test if a error code is provided! + String sErrorCode = VALIDATION_ERROR; + Object oErrorCode = null; + + // if errorCode is not provided by result then we look for a + // direct var definition (this is for backward compatibility + // of older scripts) + oErrorCode = ruleEngine.getScriptEngine().get("errorCode"); + + if (oErrorCode != null && oErrorCode instanceof String) { + sErrorCode = oErrorCode.toString(); + } + + // next test for errorMessage (this can be a string or an array + // of strings + Object[] params = null; + + params = ruleEngine.evaluateNativeScriptArray("errorMessage"); + + // finally we throw the Plugin Exception + throw new PluginException(RulePlugin.class.getName(), sErrorCode, + "BusinessRule: validation failed - ErrorCode=" + sErrorCode, params); + } - // now test the variable 'followUp' - Object followUp = null; - // first test result object - - // if followUp is not provided by result then we look for a - // direct - // var definition (this is for backward compatibility of older - // scripts) - followUp = ruleEngine.getScriptEngine().get("followUp"); - - // If followUp is defined we update now the activityEntity.... - if (followUp != null) { - // try to get double value... - Double d = Double.valueOf(followUp.toString()); - Long followUpActivity = d.longValue(); - if (followUpActivity != null && followUpActivity > 0) { - adocumentActivity.replaceItemValue("keyFollowUp", "1"); - adocumentActivity.replaceItemValue("numNextActivityID", followUpActivity); - } - } + // now test the variable 'followUp' + Object followUp = null; + // first test result object + + // if followUp is not provided by result then we look for a + // direct + // var definition (this is for backward compatibility of older + // scripts) + followUp = ruleEngine.getScriptEngine().get("followUp"); + + // If followUp is defined we update now the activityEntity.... + if (followUp != null) { + // try to get double value... + Double d = Double.valueOf(followUp.toString()); + Long followUpActivity = d.longValue(); + if (followUpActivity != null && followUpActivity > 0) { + adocumentActivity.replaceItemValue("keyFollowUp", "1"); + adocumentActivity.replaceItemValue("numNextActivityID", followUpActivity); + } + } - } - - /** - * This method injects new properties provided by the script element 'event' into the current - * ActivityEntity. The new value can be used for further processing. - * - * @param engine - * @param event - * @throws ScriptException - */ - private void updateEvent(RuleEngine ruleEngine, ItemCollection event) { - ItemCollection newEvent = ruleEngine.convertScriptVariableToItemCollection("event"); - for (Map.Entry> entry : newEvent.getAllItems().entrySet()) { - String key = entry.getKey(); - List newValue = entry.getValue(); - logger.finest("......update event property " + entry.getKey()); - event.replaceItemValue(key, newValue); } - } + /** + * This method injects new properties provided by the script element 'event' + * into the current ActivityEntity. The new value can be used for further + * processing. + * + * @param engine + * @param event + * @throws ScriptException + */ + private void updateEvent(RuleEngine ruleEngine, ItemCollection event) { + ItemCollection newEvent = ruleEngine.convertScriptVariableToItemCollection("event"); + for (Map.Entry> entry : newEvent.getAllItems().entrySet()) { + String key = entry.getKey(); + List newValue = entry.getValue(); + logger.finest("......update event property " + entry.getKey()); + event.replaceItemValue(key, newValue); + } + + } } diff --git a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/plugins/SplitAndJoinPlugin.java b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/plugins/SplitAndJoinPlugin.java index e6e6898a0..1ece1c091 100644 --- a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/plugins/SplitAndJoinPlugin.java +++ b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/plugins/SplitAndJoinPlugin.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *
      - *  Imixs Workflow 
      +/*  
      + *  Imixs-Workflow 
      + *  
        *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
        *  http://www.imixs.com
        *  
      @@ -22,10 +22,9 @@
        *      https://github.com/imixs/imixs-workflow
        *  
        *  Contributors:  
      - *      Imixs Software Solutions GmbH - initial API and implementation
      + *      Imixs Software Solutions GmbH - Project Management
        *      Ralph Soika - Software Developer
      - * 
      - *******************************************************************************/ + */ package org.imixs.workflow.engine.plugins; @@ -45,22 +44,25 @@ import org.imixs.workflow.util.XMLParser; /** - * The Imixs Split&Join Plugin provides functionality to create and update sub-process instances - * from a workflow event in an origin process. It is also possible to update the origin process from - * the sub-process instance. + * The Imixs Split&Join Plugin provides functionality to create and update + * sub-process instances from a workflow event in an origin process. It is also + * possible to update the origin process from the sub-process instance. * - * The plugin evaluates the txtactivityResult and the items with the following names: + * The plugin evaluates the txtactivityResult and the items with the following + * names: * * subprocess_create = create a new subprocess assigned to the current workitem * - * subprocess_update = update an existing subprocess assigned to the current workitem + * subprocess_update = update an existing subprocess assigned to the current + * workitem * * origin_update = update the origin process assigned to the current workitem * * - * A subprocess will contain the $UniqueID of the origin process stored in the property - * $uniqueidRef. The origin process will contain a link to the subprocess stored in the property - * txtworkitemRef. So both workitems are linked together. + * A subprocess will contain the $UniqueID of the origin process stored in the + * property $uniqueidRef. The origin process will contain a link to the + * subprocess stored in the property txtworkitemRef. So both workitems are + * linked together. * * * @author Ralph Soika @@ -69,475 +71,475 @@ * */ public class SplitAndJoinPlugin extends AbstractPlugin { - public static final String LINK_PROPERTY = "txtworkitemref"; - public static final String INVALID_FORMAT = "INVALID_FORMAT"; - public static final String SUBPROCESS_CREATE = "subprocess_create"; - public static final String SUBPROCESS_UPDATE = "subprocess_update"; - public static final String ORIGIN_UPDATE = "origin_update"; - - private static Logger logger = Logger.getLogger(SplitAndJoinPlugin.class.getName()); - - /** - * The method evaluates the workflow activity result for items with name: - * - * subprocess_create - * - * subprocess_update - * - * origin_update - * - * For each item a corresponding processing cycle will be started. - * - * @throws @throws ProcessingErrorException @throws AccessDeniedException @throws - * - */ - @SuppressWarnings("unchecked") - public ItemCollection run(ItemCollection adocumentContext, ItemCollection adocumentActivity) - throws PluginException, AccessDeniedException, ProcessingErrorException { - boolean debug = logger.isLoggable(Level.FINE); - ItemCollection evalItemCollection = - getWorkflowService().evalWorkflowResult(adocumentActivity, adocumentContext, false); - - if (evalItemCollection == null) - return adocumentContext; - - try { - // 1.) test for items with name subprocess_create and create the - // defined suprocesses - if (evalItemCollection.hasItem(SUBPROCESS_CREATE)) { - if (debug) { - logger.finest("......processing " + SUBPROCESS_CREATE); - } - // extract the create subprocess definitions... - List processValueList = evalItemCollection.getItemValue(SUBPROCESS_CREATE); - createSubprocesses(processValueList, adocumentContext); - } - - // 2.) test for items with name subprocess_update and create the - // defined suprocesses - if (evalItemCollection.hasItem(SUBPROCESS_UPDATE)) { - if (debug) { - logger.finest("......sprocessing " + SUBPROCESS_UPDATE); - } - // extract the create subprocess definitions... - List processValueList = evalItemCollection.getItemValue(SUBPROCESS_UPDATE); - updateSubprocesses(processValueList, adocumentContext); - } - - // 3.) test for items with name origin_update and update the - // origin workitem - if (evalItemCollection.hasItem(ORIGIN_UPDATE)) { - if (debug) { - logger.finest("......processing " + ORIGIN_UPDATE); + public static final String LINK_PROPERTY = "txtworkitemref"; + public static final String INVALID_FORMAT = "INVALID_FORMAT"; + public static final String SUBPROCESS_CREATE = "subprocess_create"; + public static final String SUBPROCESS_UPDATE = "subprocess_update"; + public static final String ORIGIN_UPDATE = "origin_update"; + + private static Logger logger = Logger.getLogger(SplitAndJoinPlugin.class.getName()); + + /** + * The method evaluates the workflow activity result for items with name: + * + * subprocess_create + * + * subprocess_update + * + * origin_update + * + * For each item a corresponding processing cycle will be started. + * + * @throws @throws ProcessingErrorException @throws + * AccessDeniedException @throws + * + */ + @SuppressWarnings("unchecked") + public ItemCollection run(ItemCollection adocumentContext, ItemCollection adocumentActivity) + throws PluginException, AccessDeniedException, ProcessingErrorException { + boolean debug = logger.isLoggable(Level.FINE); + ItemCollection evalItemCollection = getWorkflowService().evalWorkflowResult(adocumentActivity, adocumentContext, + false); + + if (evalItemCollection == null) + return adocumentContext; + + try { + // 1.) test for items with name subprocess_create and create the + // defined suprocesses + if (evalItemCollection.hasItem(SUBPROCESS_CREATE)) { + if (debug) { + logger.finest("......processing " + SUBPROCESS_CREATE); + } + // extract the create subprocess definitions... + List processValueList = evalItemCollection.getItemValue(SUBPROCESS_CREATE); + createSubprocesses(processValueList, adocumentContext); + } + + // 2.) test for items with name subprocess_update and create the + // defined suprocesses + if (evalItemCollection.hasItem(SUBPROCESS_UPDATE)) { + if (debug) { + logger.finest("......sprocessing " + SUBPROCESS_UPDATE); + } + // extract the create subprocess definitions... + List processValueList = evalItemCollection.getItemValue(SUBPROCESS_UPDATE); + updateSubprocesses(processValueList, adocumentContext); + } + + // 3.) test for items with name origin_update and update the + // origin workitem + if (evalItemCollection.hasItem(ORIGIN_UPDATE)) { + if (debug) { + logger.finest("......processing " + ORIGIN_UPDATE); + } + // extract the create subprocess definitions... + String processValue = evalItemCollection.getItemValueString(ORIGIN_UPDATE); + updateOrigin(processValue, adocumentContext); + } + } catch (ModelException e) { + throw new PluginException(e.getErrorContext(), e.getErrorCode(), e.getMessage(), e); + } - // extract the create subprocess definitions... - String processValue = evalItemCollection.getItemValueString(ORIGIN_UPDATE); - updateOrigin(processValue, adocumentContext); - } - } catch (ModelException e) { - throw new PluginException(e.getErrorContext(), e.getErrorCode(), e.getMessage(), e); + return adocumentContext; } - return adocumentContext; - } - - /** - * This method expects a list of Subprocess definitions and create for each definition a new - * subprocess. The reference of the created subprocess will be stored in the property - * txtworkitemRef of the origin workitem - * - * - * The definition is expected in the following format - * - * - * 1.0.0 - * 100 - * 20 - * namTeam,_sub_data - * home - * - * - * - * Both workitems are connected to each other. The subprocess will contain the $UniqueID of the - * origin process stored in the property $uniqueidRef. The origin process will contain a link to - * the subprocess stored in the property txtworkitemRef. - * - * The tag 'action' is optional and allows to overwrite the action result evaluated by the - * ResultPlugin. - * - * @param subProcessDefinitions - * @param originWorkitem - * @throws AccessDeniedException - * @throws ProcessingErrorException - * @throws PluginException - * @throws ModelException - */ - protected void createSubprocesses(final List subProcessDefinitions, - final ItemCollection originWorkitem) - throws AccessDeniedException, ProcessingErrorException, PluginException, ModelException { - - if (subProcessDefinitions == null || subProcessDefinitions.size() == 0) { - // no definition found - return; - } - boolean debug = logger.isLoggable(Level.FINE); - // we iterate over each declaration of a SUBPROCESS_CREATE item.... - for (String processValue : subProcessDefinitions) { - - if (processValue.trim().isEmpty()) { - // no definition - continue; - } - // evaluate the item content (XML format expected here!) - ItemCollection processData = XMLParser.parseItemStructure(processValue); - - if (processData != null) { - // create new process instance - ItemCollection workitemSubProcess = new ItemCollection(); - - // now clone the field list... - copyItemList(processData.getItemValueString("items"), originWorkitem, workitemSubProcess); - - // check model version - String sModelVersion = processData.getItemValueString("modelversion"); - if (sModelVersion.isEmpty()) { - sModelVersion = originWorkitem.getModelVersion(); + /** + * This method expects a list of Subprocess definitions and create for each + * definition a new subprocess. The reference of the created subprocess will be + * stored in the property txtworkitemRef of the origin workitem + * + * + * The definition is expected in the following format + * + * + * 1.0.0 + * 100 + * 20 + * namTeam,_sub_data + * home + * + * + * + * Both workitems are connected to each other. The subprocess will contain the + * $UniqueID of the origin process stored in the property $uniqueidRef. The + * origin process will contain a link to the subprocess stored in the property + * txtworkitemRef. + * + * The tag 'action' is optional and allows to overwrite the action result + * evaluated by the ResultPlugin. + * + * @param subProcessDefinitions + * @param originWorkitem + * @throws AccessDeniedException + * @throws ProcessingErrorException + * @throws PluginException + * @throws ModelException + */ + protected void createSubprocesses(final List subProcessDefinitions, final ItemCollection originWorkitem) + throws AccessDeniedException, ProcessingErrorException, PluginException, ModelException { + + if (subProcessDefinitions == null || subProcessDefinitions.size() == 0) { + // no definition found + return; } - workitemSubProcess.replaceItemValue(WorkflowKernel.MODELVERSION, sModelVersion); + boolean debug = logger.isLoggable(Level.FINE); + // we iterate over each declaration of a SUBPROCESS_CREATE item.... + for (String processValue : subProcessDefinitions) { + + if (processValue.trim().isEmpty()) { + // no definition + continue; + } + // evaluate the item content (XML format expected here!) + ItemCollection processData = XMLParser.parseItemStructure(processValue); + + if (processData != null) { + // create new process instance + ItemCollection workitemSubProcess = new ItemCollection(); + + // now clone the field list... + copyItemList(processData.getItemValueString("items"), originWorkitem, workitemSubProcess); + + // check model version + String sModelVersion = processData.getItemValueString("modelversion"); + if (sModelVersion.isEmpty()) { + sModelVersion = originWorkitem.getModelVersion(); + } + workitemSubProcess.replaceItemValue(WorkflowKernel.MODELVERSION, sModelVersion); + + String task_pattern = processData.getItemValueString("task"); + // support deprecated tag 'processid' (issue #446) + if (task_pattern.isEmpty() && processData.hasItem("processid")) { + task_pattern = processData.getItemValueString("processid"); + logger.warning( + "...subprocess_create uses deprecated tag 'processid' instead of 'task'. Please check your model"); + } + workitemSubProcess.setTaskID(Integer.valueOf(task_pattern)); + + String event_pattern = processData.getItemValueString("event"); + // support deprecated tag 'processid' (issue #446) + if (event_pattern.isEmpty() && processData.hasItem("activityid")) { + event_pattern = processData.getItemValueString("activityid"); + logger.warning( + "...subprocess_create uses deprecated tag 'activityid' instead of 'event'. Please check your model"); + } + workitemSubProcess.setEventID(Integer.valueOf(event_pattern)); + + // add the origin reference + workitemSubProcess.replaceItemValue(WorkflowService.UNIQUEIDREF, originWorkitem.getUniqueID()); + + // process the new subprocess... + workitemSubProcess = getWorkflowService().processWorkItem(workitemSubProcess); + if (debug) { + logger.finest("...... successful created new subprocess."); + } + // finally add the new workitemRef into the origin + // documentContext + addWorkitemRef(workitemSubProcess.getUniqueID(), originWorkitem); + + // test for optional action result.. + if (processData.hasItem("action")) { + String workflowResult = processData.getItemValueString("action"); + if (!workflowResult.isEmpty()) { + workflowResult = getWorkflowService().adaptText(workflowResult, workitemSubProcess); + originWorkitem.replaceItemValue("action", workflowResult); + } + + } + } - String task_pattern = processData.getItemValueString("task"); - // support deprecated tag 'processid' (issue #446) - if (task_pattern.isEmpty() && processData.hasItem("processid")) { - task_pattern = processData.getItemValueString("processid"); - logger.warning( - "...subprocess_create uses deprecated tag 'processid' instead of 'task'. Please check your model"); } - workitemSubProcess.setTaskID(Integer.valueOf(task_pattern)); + } - String event_pattern = processData.getItemValueString("event"); - // support deprecated tag 'processid' (issue #446) - if (event_pattern.isEmpty() && processData.hasItem("activityid")) { - event_pattern = processData.getItemValueString("activityid"); - logger.warning( - "...subprocess_create uses deprecated tag 'activityid' instead of 'event'. Please check your model"); + /** + * This method expects a list of Subprocess definitions and updates each + * matching existing subprocess. + * + * The definition is expected in the following format (were regular expressions + * are allowed) + * + * + * 1.0.0 + * 100 + * 20 + * namTeam,_sub_data + * + * + * Subprocesses and the origin process are connected to each other. The + * subprocess will contain the $UniqueID of the origin process stored in the + * property $uniqueidRef. The origin process will contain a link to the + * subprocess stored in the property txtworkitemRef. + * + * @param subProcessDefinitions + * @param originWorkitem + * @throws AccessDeniedException + * @throws ProcessingErrorException + * @throws PluginException + * @throws ModelException + */ + protected void updateSubprocesses(final List subProcessDefinitions, final ItemCollection originWorkitem) + throws AccessDeniedException, ProcessingErrorException, PluginException, ModelException { + + if (subProcessDefinitions == null || subProcessDefinitions.size() == 0) { + // no definition found + return; } - workitemSubProcess.setEventID(Integer.valueOf(event_pattern)); + boolean debug = logger.isLoggable(Level.FINE); + // we iterate over each declaration of a SUBPROCESS_CREATE item.... + for (String processValue : subProcessDefinitions) { - // add the origin reference - workitemSubProcess.replaceItemValue(WorkflowService.UNIQUEIDREF, - originWorkitem.getUniqueID()); + if (processValue.trim().isEmpty()) { + // no definition + continue; + } + // evaluate the item content (XML format expected here!) + ItemCollection processData = XMLParser.parseItemStructure(processValue); + + if (processData != null) { + // we need to lookup all subprocess instances which are matching + // the process definition + + String model_pattern = processData.getItemValueString("modelversion"); + String task_pattern = processData.getItemValueString("task"); + // support deprecated tag 'processid' (issue #446) + if (task_pattern.isEmpty() && processData.hasItem("processid")) { + task_pattern = processData.getItemValueString("processid"); + logger.warning( + "...subprocess_update uses deprecated tag 'processid' instead of 'task'. Please check your model"); + } + + @SuppressWarnings("unchecked") + List subProcessRefList = originWorkitem.getItemValue(LINK_PROPERTY); + for (String subProcessRef : subProcessRefList) { + ItemCollection workitemSubProcess = this.getWorkflowService().getWorkItem(subProcessRef); + + // test if process matches + String subModelVersion = workitemSubProcess.getModelVersion(); + String subProcessID = "" + workitemSubProcess.getTaskID(); + + if (Pattern.compile(model_pattern).matcher(subModelVersion).find() + && Pattern.compile(task_pattern).matcher(subProcessID).find()) { + if (debug) { + logger.finest("...... subprocess matches criteria."); + } + // now clone the field list... + copyItemList(processData.getItemValueString("items"), originWorkitem, workitemSubProcess); + + String event_pattern = processData.getItemValueString("event"); + // support deprecated tag 'processid' (issue #446) + if (event_pattern.isEmpty() && processData.hasItem("activityid")) { + event_pattern = processData.getItemValueString("activityid"); + logger.warning( + "...subprocess_update uses deprecated tag 'activityid' instead of 'event'. Please check your model"); + } + workitemSubProcess.setEventID(Integer.valueOf(event_pattern)); + // process the exisitng subprocess... + + workitemSubProcess = getWorkflowService().processWorkItem(workitemSubProcess); + + // test for optional action result.. + if (processData.hasItem("action")) { + String workflowResult = processData.getItemValueString("action"); + if (!workflowResult.isEmpty()) { + workflowResult = getWorkflowService().adaptText(workflowResult, workitemSubProcess); + originWorkitem.replaceItemValue("action", workflowResult); + } + } + if (debug) { + logger.finest("...... successful updated subprocess."); + } + } + + // test for optional action result.. + if (processData.hasItem("action")) { + String workflowResult = processData.getItemValueString("action"); + if (!workflowResult.isEmpty()) { + workflowResult = getWorkflowService().adaptText(workflowResult, workitemSubProcess); + originWorkitem.replaceItemValue("action", workflowResult); + } + + } + } - // process the new subprocess... - workitemSubProcess = getWorkflowService().processWorkItem(workitemSubProcess); - if (debug) { - logger.finest("...... successful created new subprocess."); - } - // finally add the new workitemRef into the origin - // documentContext - addWorkitemRef(workitemSubProcess.getUniqueID(), originWorkitem); - - // test for optional action result.. - if (processData.hasItem("action")) { - String workflowResult = processData.getItemValueString("action"); - if (!workflowResult.isEmpty()) { - workflowResult = getWorkflowService().adaptText(workflowResult, workitemSubProcess); - originWorkitem.replaceItemValue("action", workflowResult); - } + } } - } - - } - } - - /** - * This method expects a list of Subprocess definitions and updates each matching existing - * subprocess. - * - * The definition is expected in the following format (were regular expressions are allowed) - * - * - * 1.0.0 - * 100 - * 20 - * namTeam,_sub_data - * - * - * Subprocesses and the origin process are connected to each other. The subprocess will contain - * the $UniqueID of the origin process stored in the property $uniqueidRef. The origin process - * will contain a link to the subprocess stored in the property txtworkitemRef. - * - * @param subProcessDefinitions - * @param originWorkitem - * @throws AccessDeniedException - * @throws ProcessingErrorException - * @throws PluginException - * @throws ModelException - */ - protected void updateSubprocesses(final List subProcessDefinitions, - final ItemCollection originWorkitem) - throws AccessDeniedException, ProcessingErrorException, PluginException, ModelException { - - if (subProcessDefinitions == null || subProcessDefinitions.size() == 0) { - // no definition found - return; } - boolean debug = logger.isLoggable(Level.FINE); - // we iterate over each declaration of a SUBPROCESS_CREATE item.... - for (String processValue : subProcessDefinitions) { - if (processValue.trim().isEmpty()) { - // no definition - continue; - } - // evaluate the item content (XML format expected here!) - ItemCollection processData = XMLParser.parseItemStructure(processValue); + /** + * This method expects a single process definitions to update the origin process + * for a subprocess. The origin workitem will be loaded by the $uniqueidRef + * stored in the subprocess + * + * The processing definition for the origin process is expected in the following + * format + * + * + * 20 + * namTeam,_sub_data + * + * + * + * @param originProcessDefinition + * @param subprocessWorkitem + * @throws AccessDeniedException + * @throws ProcessingErrorException + * @throws PluginException + * @throws ModelException + */ + @SuppressWarnings("unchecked") + protected void updateOrigin(final String originProcessDefinition, final ItemCollection subprocessWorkitem) + throws AccessDeniedException, ProcessingErrorException, PluginException, ModelException { - if (processData != null) { - // we need to lookup all subprocess instances which are matching - // the process definition + ItemCollection originWorkitem = null; + + if (originProcessDefinition == null || originProcessDefinition.isEmpty()) { + // no definition + return; + } + boolean debug = logger.isLoggable(Level.FINE); + + // evaluate the item content (XML format expected here!) + ItemCollection processData = XMLParser.parseItemStructure(originProcessDefinition); String model_pattern = processData.getItemValueString("modelversion"); String task_pattern = processData.getItemValueString("task"); // support deprecated tag 'processid' (issue #446) if (task_pattern.isEmpty() && processData.hasItem("processid")) { - task_pattern = processData.getItemValueString("processid"); - logger.warning( - "...subprocess_update uses deprecated tag 'processid' instead of 'task'. Please check your model"); + task_pattern = processData.getItemValueString("processid"); + logger.warning( + "...origin_update uses deprecated tag 'processid' instead of 'task'. Please check your model"); } + // first we need to lookup the corresponding origin process instance + List refs = subprocessWorkitem.getItemValue(WorkflowService.UNIQUEIDREF); + // iterate over all refs and identify the origin workItem + for (String ref : refs) { + originWorkitem = getWorkflowService().getWorkItem(ref); + if (originWorkitem != null) { + + // test if process matches + String subModelVersion = originWorkitem.getModelVersion(); + String subProcessID = "" + originWorkitem.getTaskID(); + + if (Pattern.compile(model_pattern).matcher(subModelVersion).find() + && Pattern.compile(task_pattern).matcher(subProcessID).find()) { + if (debug) { + logger.finest("...... origin matches criteria."); + } + // process the origin workitem + String event_pattern = processData.getItemValueString("event"); + // support deprecated tag 'processid' (issue #446) + if (event_pattern.isEmpty() && processData.hasItem("activityid")) { + event_pattern = processData.getItemValueString("activityid"); + logger.warning( + "...origin_update uses deprecated tag 'activityid' instead of 'event'. Please check your model"); + } + originWorkitem.setEventID(Integer.valueOf(event_pattern)); + + // now clone the field list... + copyItemList(processData.getItemValueString("items"), subprocessWorkitem, originWorkitem); + + // finally we process the new subprocess... + originWorkitem = getWorkflowService().processWorkItem(originWorkitem); + + // test for optional action result.. + if (processData.hasItem("action")) { + String workflowResult = processData.getItemValueString("action"); + if (!workflowResult.isEmpty()) { + workflowResult = getWorkflowService().adaptText(workflowResult, originWorkitem); + subprocessWorkitem.replaceItemValue("action", workflowResult); + } + + } + if (debug) { + logger.finest("...... successful processed originprocess."); + } + } - @SuppressWarnings("unchecked") - List subProcessRefList = originWorkitem.getItemValue(LINK_PROPERTY); - for (String subProcessRef : subProcessRefList) { - ItemCollection workitemSubProcess = this.getWorkflowService().getWorkItem(subProcessRef); - - // test if process matches - String subModelVersion = workitemSubProcess.getModelVersion(); - String subProcessID = "" + workitemSubProcess.getTaskID(); - - if (Pattern.compile(model_pattern).matcher(subModelVersion).find() - && Pattern.compile(task_pattern).matcher(subProcessID).find()) { - if (debug) { - logger.finest("...... subprocess matches criteria."); - } - // now clone the field list... - copyItemList(processData.getItemValueString("items"), originWorkitem, - workitemSubProcess); - - String event_pattern = processData.getItemValueString("event"); - // support deprecated tag 'processid' (issue #446) - if (event_pattern.isEmpty() && processData.hasItem("activityid")) { - event_pattern = processData.getItemValueString("activityid"); - logger.warning( - "...subprocess_update uses deprecated tag 'activityid' instead of 'event'. Please check your model"); - } - workitemSubProcess.setEventID(Integer.valueOf(event_pattern)); - // process the exisitng subprocess... - - workitemSubProcess = getWorkflowService().processWorkItem(workitemSubProcess); - - // test for optional action result.. - if (processData.hasItem("action")) { - String workflowResult = processData.getItemValueString("action"); - if (!workflowResult.isEmpty()) { - workflowResult = getWorkflowService().adaptText(workflowResult, workitemSubProcess); - originWorkitem.replaceItemValue("action", workflowResult); - } - } - if (debug) { - logger.finest("...... successful updated subprocess."); - } - } - - // test for optional action result.. - if (processData.hasItem("action")) { - String workflowResult = processData.getItemValueString("action"); - if (!workflowResult.isEmpty()) { - workflowResult = getWorkflowService().adaptText(workflowResult, workitemSubProcess); - originWorkitem.replaceItemValue("action", workflowResult); } - } } - } - - } - } - - /** - * This method expects a single process definitions to update the origin process for a subprocess. - * The origin workitem will be loaded by the $uniqueidRef stored in the subprocess - * - * The processing definition for the origin process is expected in the following format - * - * - * 20 - * namTeam,_sub_data - * - * - * - * @param originProcessDefinition - * @param subprocessWorkitem - * @throws AccessDeniedException - * @throws ProcessingErrorException - * @throws PluginException - * @throws ModelException - */ - @SuppressWarnings("unchecked") - protected void updateOrigin(final String originProcessDefinition, - final ItemCollection subprocessWorkitem) - throws AccessDeniedException, ProcessingErrorException, PluginException, ModelException { - - ItemCollection originWorkitem = null; - - if (originProcessDefinition == null || originProcessDefinition.isEmpty()) { - // no definition - return; - } - boolean debug = logger.isLoggable(Level.FINE); - - // evaluate the item content (XML format expected here!) - ItemCollection processData = XMLParser.parseItemStructure(originProcessDefinition); - - String model_pattern = processData.getItemValueString("modelversion"); - String task_pattern = processData.getItemValueString("task"); - // support deprecated tag 'processid' (issue #446) - if (task_pattern.isEmpty() && processData.hasItem("processid")) { - task_pattern = processData.getItemValueString("processid"); - logger.warning( - "...origin_update uses deprecated tag 'processid' instead of 'task'. Please check your model"); } - // first we need to lookup the corresponding origin process instance - List refs = subprocessWorkitem.getItemValue(WorkflowService.UNIQUEIDREF); - // iterate over all refs and identify the origin workItem - for (String ref : refs) { - originWorkitem = getWorkflowService().getWorkItem(ref); - if (originWorkitem != null) { - - // test if process matches - String subModelVersion = originWorkitem.getModelVersion(); - String subProcessID = "" + originWorkitem.getTaskID(); - - if (Pattern.compile(model_pattern).matcher(subModelVersion).find() - && Pattern.compile(task_pattern).matcher(subProcessID).find()) { - if (debug) { - logger.finest("...... origin matches criteria."); - } - // process the origin workitem - String event_pattern = processData.getItemValueString("event"); - // support deprecated tag 'processid' (issue #446) - if (event_pattern.isEmpty() && processData.hasItem("activityid")) { - event_pattern = processData.getItemValueString("activityid"); - logger.warning( - "...origin_update uses deprecated tag 'activityid' instead of 'event'. Please check your model"); - } - originWorkitem.setEventID(Integer.valueOf(event_pattern)); - - // now clone the field list... - copyItemList(processData.getItemValueString("items"), subprocessWorkitem, originWorkitem); - - // finally we process the new subprocess... - originWorkitem = getWorkflowService().processWorkItem(originWorkitem); - - // test for optional action result.. - if (processData.hasItem("action")) { - String workflowResult = processData.getItemValueString("action"); - if (!workflowResult.isEmpty()) { - workflowResult = getWorkflowService().adaptText(workflowResult, originWorkitem); - subprocessWorkitem.replaceItemValue("action", workflowResult); + /** + * This Method copies the fields defined in 'items' into the targetWorkitem. + * Multiple values are separated with comma ','. + *

      + * In case a item name contains '|' the target field name will become the right + * part of the item name. + *

      + * Example: {@code + * txttitle,txtfirstname + * + * txttitle|newitem1,txtfirstname|newitem2 + * + * } + * + *

      + * Optional also reg expressions are supported. In this case mapping of the item + * name is not supported. + *

      + * Example: {@code + * (^artikel$|^invoice$),txtTitel|txtNewTitel + * + * + * } A reg expression must be includes in brackets. + * + */ + protected void copyItemList(String items, ItemCollection source, ItemCollection target) { + // clone the field list... + StringTokenizer st = new StringTokenizer(items, ","); + while (st.hasMoreTokens()) { + String field = st.nextToken().trim(); + + // test if field is a reg ex + if (field.startsWith("(") && field.endsWith(")")) { + Pattern itemPattern = Pattern.compile(field); + Map> map = source.getAllItems(); + for (String itemName : map.keySet()) { + if (itemPattern.matcher(itemName).find()) { + target.replaceItemValue(itemName, source.getItemValue(itemName)); + } + } + + } else { + // default behavior without reg ex + int pos = field.indexOf('|'); + if (pos > -1) { + target.replaceItemValue(field.substring(pos + 1).trim(), + source.getItemValue(field.substring(0, pos).trim())); + } else { + target.replaceItemValue(field, source.getItemValue(field)); + } } - - } - if (debug) { - logger.finest("...... successful processed originprocess."); - } } - - } - } - } - - /** - * This Method copies the fields defined in 'items' into the targetWorkitem. Multiple values are - * separated with comma ','. - *

      - * In case a item name contains '|' the target field name will become the right part of the item - * name. - *

      - * Example: {@code - * txttitle,txtfirstname - * - * txttitle|newitem1,txtfirstname|newitem2 - * - * } - * - *

      - * Optional also reg expressions are supported. In this case mapping of the item name is not - * supported. - *

      - * Example: {@code - * (^artikel$|^invoice$),txtTitel|txtNewTitel - * - * - * } A reg expression must be includes in brackets. - * - */ - protected void copyItemList(String items, ItemCollection source, ItemCollection target) { - // clone the field list... - StringTokenizer st = new StringTokenizer(items, ","); - while (st.hasMoreTokens()) { - String field = st.nextToken().trim(); - - // test if field is a reg ex - if (field.startsWith("(") && field.endsWith(")")) { - Pattern itemPattern = Pattern.compile(field); - Map> map = source.getAllItems(); - for (String itemName : map.keySet()) { - if (itemPattern.matcher(itemName).find()) { - target.replaceItemValue(itemName, source.getItemValue(itemName)); - } + /** + * This methods adds a new workItem reference into a workitem + */ + protected void addWorkitemRef(String aUniqueID, ItemCollection workitem) { + boolean debug = logger.isLoggable(Level.FINE); + if (debug) { + logger.fine("LinkController add workitem reference: " + aUniqueID); } - } else { - // default behavior without reg ex - int pos = field.indexOf('|'); - if (pos > -1) { - target.replaceItemValue(field.substring(pos + 1).trim(), - source.getItemValue(field.substring(0, pos).trim())); - } else { - target.replaceItemValue(field, source.getItemValue(field)); - } - } - } - } - - /** - * This methods adds a new workItem reference into a workitem - */ - protected void addWorkitemRef(String aUniqueID, ItemCollection workitem) { - boolean debug = logger.isLoggable(Level.FINE); - if (debug) { - logger.fine("LinkController add workitem reference: " + aUniqueID); - } + @SuppressWarnings("unchecked") + List refList = workitem.getItemValue(LINK_PROPERTY); - @SuppressWarnings("unchecked") - List refList = workitem.getItemValue(LINK_PROPERTY); + // clear empty entry if set + if (refList.size() == 1 && "".equals(refList.get(0))) + refList.remove(0); - // clear empty entry if set - if (refList.size() == 1 && "".equals(refList.get(0))) - refList.remove(0); + // test if not yet a member of + if (refList.indexOf(aUniqueID) == -1) { + refList.add(aUniqueID); + workitem.replaceItemValue(LINK_PROPERTY, refList); + } - // test if not yet a member of - if (refList.indexOf(aUniqueID) == -1) { - refList.add(aUniqueID); - workitem.replaceItemValue(LINK_PROPERTY, refList); } - - } } diff --git a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/plugins/VersionPlugin.java b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/plugins/VersionPlugin.java index 20455f2a3..971005fc3 100644 --- a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/plugins/VersionPlugin.java +++ b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/plugins/VersionPlugin.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *

      - *  Imixs Workflow 
      +/*  
      + *  Imixs-Workflow 
      + *  
        *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
        *  http://www.imixs.com
        *  
      @@ -22,10 +22,9 @@
        *      https://github.com/imixs/imixs-workflow
        *  
        *  Contributors:  
      - *      Imixs Software Solutions GmbH - initial API and implementation
      + *      Imixs Software Solutions GmbH - Project Management
        *      Ralph Soika - Software Developer
      - * 
      - *******************************************************************************/ + */ package org.imixs.workflow.engine.plugins; @@ -40,33 +39,38 @@ import org.imixs.workflow.exceptions.QueryException; /** - * This plugin handles the creation and management of versions from an existing workitem. inside the - * Imix JEE Workflow. The creation or modificatin of a version is defined by the workflowmodel. The - * plugin can generate new versions (e.g. creating a version of a master version) and also - * converting a existing version into a master version. + * This plugin handles the creation and management of versions from an existing + * workitem. inside the Imix JEE Workflow. The creation or modificatin of a + * version is defined by the workflowmodel. The plugin can generate new versions + * (e.g. creating a version of a master version) and also converting a existing + * version into a master version. *

      - * The Plugin depends on the org.imixs.workflow.jee.ejb.WorkflowManager. So the Plugin can not be - * used in other implementations. + * The Plugin depends on the org.imixs.workflow.jee.ejb.WorkflowManager. So the + * Plugin can not be used in other implementations. *

      - * The creation and management of a new version is defined by the workflow model (See Imixs Modeler) - * There are currently two modes supported (provided by the activity property 'keyVersion') + * The creation and management of a new version is defined by the workflow model + * (See Imixs Modeler) There are currently two modes supported (provided by the + * activity property 'keyVersion') *

      - * mode=1 indicates that the plugin should create a new version of the current workitem. The two - * workitems are identically except the attributes $uniqueid and $workitemidRef. The attribute - * workitemidRef points to the $uniqueid form the source workitem. So the availability of this - * property indicates that the workitem is a version of source workitem with this $uniqueid. The - * source workitem has typically no $workitemidRef attribute. The Source Workitem is also named - * Master Version. After the new version is created the plugin processes the version with the - * activity provided by the model (numVersionActivityID) if provided by the model. + * mode=1 indicates that the plugin should create a new version of the current + * workitem. The two workitems are identically except the attributes $uniqueid + * and $workitemidRef. The attribute workitemidRef points to the $uniqueid form + * the source workitem. So the availability of this property indicates that the + * workitem is a version of source workitem with this $uniqueid. The source + * workitem has typically no $workitemidRef attribute. The Source Workitem is + * also named Master Version. After the new version is created the plugin + * processes the version with the activity provided by the model + * (numVersionActivityID) if provided by the model. *

      - * 2=indicates that the plugin should convert a existing version into a Master Version. This means - * that the $workitemIDRef will be nulled. An existing Master Version will be processed by the - * activity provided by the model (numVersionActivityID). Also the $workitemidRef will be set to the - * current $workitemID. + * 2=indicates that the plugin should convert a existing version into a Master + * Version. This means that the $workitemIDRef will be nulled. An existing + * Master Version will be processed by the activity provided by the model + * (numVersionActivityID). Also the $workitemidRef will be set to the current + * $workitemID. *

      - * If an error occured during the workflow process this plugin will throw a new ejbexception in the - * close() method to cancel the current transaction. So no changes will be saved by the ejb - * container + * If an error occured during the workflow process this plugin will throw a new + * ejbexception in the close() method to cancel the current transaction. So no + * changes will be saved by the ejb container * * @see org.imixs.workflow.jee.ejb.WorkflowManager * @author Ralph Soika @@ -75,167 +79,166 @@ public class VersionPlugin extends AbstractPlugin { - public static final String INVALID_CONTEXT = "INVALID_CONTEXT"; - public static final String INVALID_WORKITEM = "INVALID_WORKITEM"; - - private String versionMode = ""; - private int versionActivityID = -1; - private ItemCollection version = null; - private ItemCollection documentContext = null; - - private static final String PROCESSING_VERSION_ATTRIBUTE = "$processingversion"; - private static Logger logger = Logger.getLogger(VersionPlugin.class.getName()); - - public ItemCollection getVersion() { - return version; - } - - public void setVersion(ItemCollection version) { - this.version = version; - } - - /** - * creates a new version or converts a version into the MasterVersion depending to the activity - * attribute "keyVersion" provided by the workflow model. - * - * If a new version is created and be processed the plugin adds the attribute '_versionprocessing' - * = true to indicate this situation for other plugins. Other plugins can determine the - * $processingversion mode by calling the method isProcssingVersion(). The attribute will be - * removed after a version was processed. - * - * @throws PluginException - */ - public ItemCollection run(ItemCollection adocumentContext, ItemCollection adocumentActivity) - throws PluginException { - boolean debug = logger.isLoggable(Level.FINE); - documentContext = adocumentContext; - - // determine mode to manage version - versionMode = adocumentActivity.getItemValueString("keyVersion"); - versionActivityID = adocumentActivity.getItemValueInteger("numVersionActivityID"); - try { - - // handle different version modes - // 1 = create a new Version from current workitem - if ("1".equals(versionMode)) { - - // copy workitem - - version = createVersion(documentContext); - if (debug) { - logger.fine("...... new version created"); - } - // check if workitem should be processed - if (versionActivityID > 0) { - version.replaceItemValue("$ActivityID", versionActivityID); - version.replaceItemValue(PROCESSING_VERSION_ATTRIBUTE, true); - version = getWorkflowService().processWorkItem(version); - - } else { - // no processing - simply save workitem - version = getWorkflowService().getDocumentService().save(version); - } - return documentContext; + public static final String INVALID_CONTEXT = "INVALID_CONTEXT"; + public static final String INVALID_WORKITEM = "INVALID_WORKITEM"; + + private String versionMode = ""; + private int versionActivityID = -1; + private ItemCollection version = null; + private ItemCollection documentContext = null; + + private static final String PROCESSING_VERSION_ATTRIBUTE = "$processingversion"; + private static Logger logger = Logger.getLogger(VersionPlugin.class.getName()); + + public ItemCollection getVersion() { + return version; + } + + public void setVersion(ItemCollection version) { + this.version = version; + } - } - - // convert to Master Version - if ("2".equals(adocumentActivity.getItemValueString("keyVersion"))) { - - /* - * this code iterates over all existing workitems with the same $workitemid and fixes lost - * parent workitemRefIDs. - */ - String sworkitemID = documentContext.getItemValueString("$WorkItemID"); - - String searchTerm = "($workitemid:\"" + sworkitemID + "\")"; - - Collection col = - getWorkflowService().getDocumentService().find(searchTerm, 0, -1); - // now search master version... - for (ItemCollection aVersion : col) { - String sWorkitemRef = aVersion.getItemValueString("$workitemIDRef"); - if ("".equals(sWorkitemRef)) { - // Master version found! - // convert now this workitem into a Version from the - // current workitem - String id = documentContext.getItemValueString("$uniqueID"); - aVersion.replaceItemValue("$WorkItemIDRef", id); - // process version? - // check if worktiem should be processed - if (versionActivityID > 0) { - aVersion.replaceItemValue("$ActivityID", versionActivityID); - aVersion = getWorkflowService().processWorkItem(aVersion); - } else { - // no processing - simply save workitem - aVersion = getWorkflowService().getDocumentService().save(aVersion); + /** + * creates a new version or converts a version into the MasterVersion depending + * to the activity attribute "keyVersion" provided by the workflow model. + * + * If a new version is created and be processed the plugin adds the attribute + * '_versionprocessing' = true to indicate this situation for other plugins. + * Other plugins can determine the $processingversion mode by calling the method + * isProcssingVersion(). The attribute will be removed after a version was + * processed. + * + * @throws PluginException + */ + public ItemCollection run(ItemCollection adocumentContext, ItemCollection adocumentActivity) + throws PluginException { + boolean debug = logger.isLoggable(Level.FINE); + documentContext = adocumentContext; + + // determine mode to manage version + versionMode = adocumentActivity.getItemValueString("keyVersion"); + versionActivityID = adocumentActivity.getItemValueInteger("numVersionActivityID"); + try { + + // handle different version modes + // 1 = create a new Version from current workitem + if ("1".equals(versionMode)) { + + // copy workitem + + version = createVersion(documentContext); + if (debug) { + logger.fine("...... new version created"); + } + // check if workitem should be processed + if (versionActivityID > 0) { + version.replaceItemValue("$ActivityID", versionActivityID); + version.replaceItemValue(PROCESSING_VERSION_ATTRIBUTE, true); + version = getWorkflowService().processWorkItem(version); + + } else { + // no processing - simply save workitem + version = getWorkflowService().getDocumentService().save(version); + } + return documentContext; + + } + + // convert to Master Version + if ("2".equals(adocumentActivity.getItemValueString("keyVersion"))) { + + /* + * this code iterates over all existing workitems with the same $workitemid and + * fixes lost parent workitemRefIDs. + */ + String sworkitemID = documentContext.getItemValueString("$WorkItemID"); + + String searchTerm = "($workitemid:\"" + sworkitemID + "\")"; + + Collection col = getWorkflowService().getDocumentService().find(searchTerm, 0, -1); + // now search master version... + for (ItemCollection aVersion : col) { + String sWorkitemRef = aVersion.getItemValueString("$workitemIDRef"); + if ("".equals(sWorkitemRef)) { + // Master version found! + // convert now this workitem into a Version from the + // current workitem + String id = documentContext.getItemValueString("$uniqueID"); + aVersion.replaceItemValue("$WorkItemIDRef", id); + // process version? + // check if worktiem should be processed + if (versionActivityID > 0) { + aVersion.replaceItemValue("$ActivityID", versionActivityID); + aVersion = getWorkflowService().processWorkItem(aVersion); + } else { + // no processing - simply save workitem + aVersion = getWorkflowService().getDocumentService().save(aVersion); + } + version = aVersion; + } + } + // now remove workitemIDRef from current version + documentContext.removeItem("$WorkItemIDRef"); } - version = aVersion; - } + + } catch (AccessDeniedException e) { + throw new PluginException(e.getErrorContext(), e.getErrorCode(), e.getMessage(), e); + } catch (ProcessingErrorException e) { + throw new PluginException(e.getErrorContext(), e.getErrorCode(), e.getMessage(), e); + } catch (QueryException e) { + throw new PluginException(e.getErrorContext(), e.getErrorCode(), e.getMessage(), e); + } catch (ModelException e) { + throw new PluginException(e.getErrorContext(), e.getErrorCode(), e.getMessage(), e); } - // now remove workitemIDRef from current version - documentContext.removeItem("$WorkItemIDRef"); - } - - } catch (AccessDeniedException e) { - throw new PluginException(e.getErrorContext(), e.getErrorCode(), e.getMessage(), e); - } catch (ProcessingErrorException e) { - throw new PluginException(e.getErrorContext(), e.getErrorCode(), e.getMessage(), e); - } catch (QueryException e) { - throw new PluginException(e.getErrorContext(), e.getErrorCode(), e.getMessage(), e); - } catch (ModelException e) { - throw new PluginException(e.getErrorContext(), e.getErrorCode(), e.getMessage(), e); + + // removes the attribute $processingversion which is set to 'true' + // during the processing of a new version + documentContext.removeItem(PROCESSING_VERSION_ATTRIBUTE); + return documentContext; + } + + /** + * returns true in case a new version is created based on the currentWorkitem + * + * @param documentContext + * @param documentActivity + * @return + */ + public static boolean isProcssingVersion(ItemCollection adocumentContext) { + return adocumentContext.getItemValueBoolean(PROCESSING_VERSION_ATTRIBUTE); } - // removes the attribute $processingversion which is set to 'true' - // during the processing of a new version - documentContext.removeItem(PROCESSING_VERSION_ATTRIBUTE); - return documentContext; - } - - - - /** - * returns true in case a new version is created based on the currentWorkitem - * - * @param documentContext - * @param documentActivity - * @return - */ - public static boolean isProcssingVersion(ItemCollection adocumentContext) { - return adocumentContext.getItemValueBoolean(PROCESSING_VERSION_ATTRIBUTE); - } - - /** - * This method creates a new instance of a exiting workitem. The method did not save the - * workitem!. The method can be subclassed to modify the new created version. - * - * The new property $WorkitemIDRef will be added which points to the $uniqueID of the - * sourceWorkitem. - * - * @param sourceItemCollection the ItemCollection which should be versioned - * @return new version of the source ItemCollection - * - * @throws PluginException - * @throws Exception - */ - public ItemCollection createVersion(ItemCollection sourceItemCollection) throws PluginException { - ItemCollection itemColNewVersion = new ItemCollection(); - - itemColNewVersion.replaceAllItems(sourceItemCollection.getAllItems()); - - String id = sourceItemCollection.getItemValueString("$uniqueid"); - if ("".equals(id)) - throw new PluginException(VersionPlugin.class.getSimpleName(), INVALID_WORKITEM, - "Error - unable to create a version from a new workitem!"); - // remove $Uniqueid to force the generation of a new Entity Instance. - itemColNewVersion.getAllItems().remove("$uniqueid"); - - // update $WorkItemIDRef to current worktiemID - itemColNewVersion.replaceItemValue("$WorkItemIDRef", id); - - return itemColNewVersion; - - } + /** + * This method creates a new instance of a exiting workitem. The method did not + * save the workitem!. The method can be subclassed to modify the new created + * version. + * + * The new property $WorkitemIDRef will be added which points to the $uniqueID + * of the sourceWorkitem. + * + * @param sourceItemCollection the ItemCollection which should be versioned + * @return new version of the source ItemCollection + * + * @throws PluginException + * @throws Exception + */ + public ItemCollection createVersion(ItemCollection sourceItemCollection) throws PluginException { + ItemCollection itemColNewVersion = new ItemCollection(); + + itemColNewVersion.replaceAllItems(sourceItemCollection.getAllItems()); + + String id = sourceItemCollection.getItemValueString("$uniqueid"); + if ("".equals(id)) + throw new PluginException(VersionPlugin.class.getSimpleName(), INVALID_WORKITEM, + "Error - unable to create a version from a new workitem!"); + // remove $Uniqueid to force the generation of a new Entity Instance. + itemColNewVersion.getAllItems().remove("$uniqueid"); + + // update $WorkItemIDRef to current worktiemID + itemColNewVersion.replaceItemValue("$WorkItemIDRef", id); + + return itemColNewVersion; + + } } diff --git a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/scheduler/Scheduler.java b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/scheduler/Scheduler.java index e27ffc24e..19c2abcbe 100644 --- a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/scheduler/Scheduler.java +++ b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/scheduler/Scheduler.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *

      - *  Imixs Workflow 
      +/*  
      + *  Imixs-Workflow 
      + *  
        *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
        *  http://www.imixs.com
        *  
      @@ -22,17 +22,17 @@
        *      https://github.com/imixs/imixs-workflow
        *  
        *  Contributors:  
      - *      Imixs Software Solutions GmbH - initial API and implementation
      + *      Imixs Software Solutions GmbH - Project Management
        *      Ralph Soika - Software Developer
      - * 
      - *******************************************************************************/ + */ package org.imixs.workflow.engine.scheduler; import org.imixs.workflow.ItemCollection; /** - * This interface is used to implement a Scheduler managed by the SchedulerService. + * This interface is used to implement a Scheduler managed by the + * SchedulerService. * * @see SchedulerService * @author rsoika @@ -40,39 +40,41 @@ */ public interface Scheduler { - public final static String ITEM_SCHEDULER_NAME = "txtname"; - public final static String ITEM_SCHEDULER_ENABLED = "_scheduler_enabled"; - public final static String ITEM_SCHEDULER_STATUS = "_scheduler_status"; - public final static String ITEM_SCHEDULER_CLASS = "_scheduler_class"; - public final static String ITEM_SCHEDULER_DEFINITION = "_scheduler_definition"; - public static final String ITEM_ERRORMESSAGE = "_scheduler_errormessage"; - public static final String ITEM_LOGMESSAGE = "_scheduler_logmessage"; - + public final static String ITEM_SCHEDULER_NAME = "txtname"; + public final static String ITEM_SCHEDULER_ENABLED = "_scheduler_enabled"; + public final static String ITEM_SCHEDULER_STATUS = "_scheduler_status"; + public final static String ITEM_SCHEDULER_CLASS = "_scheduler_class"; + public final static String ITEM_SCHEDULER_DEFINITION = "_scheduler_definition"; + public static final String ITEM_ERRORMESSAGE = "_scheduler_errormessage"; + public static final String ITEM_LOGMESSAGE = "_scheduler_logmessage"; - /** - * The run method is called by the ScheduelrService during a timer timeout event. The - * SchedulerService provides a configuration object containing information for the processor of a - * concrete implementation: - *
        - *
      • type - fixed to value 'scheduler'
      • - *
      • _scheduler_definition - the chron/calendar definition for the Java EE timer service.
      • - *
      • _scheduler_enabled - boolean indicates if the scheduler is enabled/disabled
      • - *
      • _scheduler_class - class name of the scheduler implementation
      • - *
      • _scheduler_log - optional log information generated by the scheduler implementation - *
      - * Beside these standard attributes a scheduler configuration can contain additional application - * specific information. - *

      - * After the run method is finished the scheduelrService will save the scheduler configuration if - * a configuration object is returned. In case of an exception the Timer service will be canceled. - * To cancel the timer programmatically, an implementation must set the item _scheduler_enabled to - * 'false'. - *

      - * To start or stop the timer service the methods start() and stop() from the SchedulerService can - * be called. - * - * @param scheduler the scheduler configuration - * @return updated scheduler configuration - */ - public ItemCollection run(ItemCollection job) throws SchedulerException; + /** + * The run method is called by the ScheduelrService during a timer timeout + * event. The SchedulerService provides a configuration object containing + * information for the processor of a concrete implementation: + *

        + *
      • type - fixed to value 'scheduler'
      • + *
      • _scheduler_definition - the chron/calendar definition for the Java EE + * timer service.
      • + *
      • _scheduler_enabled - boolean indicates if the scheduler is + * enabled/disabled
      • + *
      • _scheduler_class - class name of the scheduler implementation
      • + *
      • _scheduler_log - optional log information generated by the scheduler + * implementation + *
      + * Beside these standard attributes a scheduler configuration can contain + * additional application specific information. + *

      + * After the run method is finished the scheduelrService will save the scheduler + * configuration if a configuration object is returned. In case of an exception + * the Timer service will be canceled. To cancel the timer programmatically, an + * implementation must set the item _scheduler_enabled to 'false'. + *

      + * To start or stop the timer service the methods start() and stop() from the + * SchedulerService can be called. + * + * @param scheduler the scheduler configuration + * @return updated scheduler configuration + */ + public ItemCollection run(ItemCollection job) throws SchedulerException; } diff --git a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/scheduler/SchedulerConfigurationService.java b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/scheduler/SchedulerConfigurationService.java index 9770732f6..788e6471d 100644 --- a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/scheduler/SchedulerConfigurationService.java +++ b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/scheduler/SchedulerConfigurationService.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *

      - *  Imixs Workflow 
      +/*  
      + *  Imixs-Workflow 
      + *  
        *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
        *  http://www.imixs.com
        *  
      @@ -22,10 +22,9 @@
        *      https://github.com/imixs/imixs-workflow
        *  
        *  Contributors:  
      - *      Imixs Software Solutions GmbH - initial API and implementation
      + *      Imixs Software Solutions GmbH - Project Management
        *      Ralph Soika - Software Developer
      - * 
      - *******************************************************************************/ + */ package org.imixs.workflow.engine.scheduler; @@ -43,9 +42,9 @@ import org.imixs.workflow.engine.DocumentService; /** - * This SchedulerSaveService is used to save configurations in a new transaction. The service is - * only called by the SchedulerService in case a scheduler throws a SchedulerException or a - * RuntimeExcepiton. + * This SchedulerSaveService is used to save configurations in a new + * transaction. The service is only called by the SchedulerService in case a + * scheduler throws a SchedulerException or a RuntimeExcepiton. * * @see SchedulerService for details * @author rsoika @@ -53,27 +52,27 @@ */ @Stateless @LocalBean -@DeclareRoles({"org.imixs.ACCESSLEVEL.MANAGERACCESS"}) +@DeclareRoles({ "org.imixs.ACCESSLEVEL.MANAGERACCESS" }) @RunAs("org.imixs.ACCESSLEVEL.MANAGERACCESS") public class SchedulerConfigurationService { - @Resource - SessionContext ctx; + @Resource + SessionContext ctx; - @Inject - DocumentService documentService; + @Inject + DocumentService documentService; - private static Logger logger = Logger.getLogger(SchedulerConfigurationService.class.getName()); + private static Logger logger = Logger.getLogger(SchedulerConfigurationService.class.getName()); - /** - * This method saves a configuration in a new transaction. This is needed case of a runtime - * exception - * - */ - @TransactionAttribute(value = TransactionAttributeType.REQUIRES_NEW) - public void storeConfigurationInNewTransaction(ItemCollection config) { - logger.finest(" ....saving scheduler configuration by new transaciton..."); - config.removeItem("$version"); - config = documentService.save(config); - } + /** + * This method saves a configuration in a new transaction. This is needed case + * of a runtime exception + * + */ + @TransactionAttribute(value = TransactionAttributeType.REQUIRES_NEW) + public void storeConfigurationInNewTransaction(ItemCollection config) { + logger.finest(" ....saving scheduler configuration by new transaciton..."); + config.removeItem("$version"); + config = documentService.save(config); + } } diff --git a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/scheduler/SchedulerController.java b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/scheduler/SchedulerController.java index 69e5b5830..de38df031 100644 --- a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/scheduler/SchedulerController.java +++ b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/scheduler/SchedulerController.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *
      - *  Imixs Workflow 
      +/*  
      + *  Imixs-Workflow 
      + *  
        *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
        *  http://www.imixs.com
        *  
      @@ -22,10 +22,9 @@
        *      https://github.com/imixs/imixs-workflow
        *  
        *  Contributors:  
      - *      Imixs Software Solutions GmbH - initial API and implementation
      + *      Imixs Software Solutions GmbH - Project Management
        *      Ralph Soika - Software Developer
      - * 
      - *******************************************************************************/ + */ package org.imixs.workflow.engine.scheduler; @@ -43,10 +42,12 @@ import org.imixs.workflow.exceptions.AccessDeniedException; /** - * The SchedulerController is a front-end controller to start and stop schedulers. A scheduler - * configuration is defined by the item type="scheduler" and the item name. + * The SchedulerController is a front-end controller to start and stop + * schedulers. A scheduler configuration is defined by the item type="scheduler" + * and the item name. *

      - * The class can be subclassed to add specific data to the scheduler configuration. + * The class can be subclassed to add specific data to the scheduler + * configuration. * * @author rsoika * @version 1.0 @@ -56,125 +57,125 @@ @RequestScoped public class SchedulerController implements Serializable { - private ItemCollection configuration = null; - private String name; - private String schedulerClass; + private ItemCollection configuration = null; + private String name; + private String schedulerClass; - @Inject - private SchedulerService schedulerService; + @Inject + private SchedulerService schedulerService; - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 1L; - private static Logger logger = Logger.getLogger(SchedulerController.class.getName()); + private static Logger logger = Logger.getLogger(SchedulerController.class.getName()); - /** - * This method load the config entity after postContstruct. If no Entity exists than the - * ConfigService EJB creates a new config entity. - * - */ - @PostConstruct - public void init() { - configuration = schedulerService.loadConfiguration(getName()); - } + /** + * This method load the config entity after postContstruct. If no Entity exists + * than the ConfigService EJB creates a new config entity. + * + */ + @PostConstruct + public void init() { + configuration = schedulerService.loadConfiguration(getName()); + } - public String getName() { - return name; - } + public String getName() { + return name; + } - public void setName(String name) { - this.name = name; - } + public void setName(String name) { + this.name = name; + } - public String getSchedulerClass() { - return schedulerClass; - } + public String getSchedulerClass() { + return schedulerClass; + } - public void setSchedulerClass(String schedulerClass) { - this.schedulerClass = schedulerClass; - } + public void setSchedulerClass(String schedulerClass) { + this.schedulerClass = schedulerClass; + } - public ItemCollection getConfiguration() { - if (configuration == null) { - configuration = new ItemCollection(); - configuration.setItemValue(Scheduler.ITEM_SCHEDULER_NAME, getName()); + public ItemCollection getConfiguration() { + if (configuration == null) { + configuration = new ItemCollection(); + configuration.setItemValue(Scheduler.ITEM_SCHEDULER_NAME, getName()); + } + return configuration; } - return configuration; - } - - public void setConfiguration(ItemCollection configuration) { - this.configuration = configuration; - } - - /** - * Saves the current scheduler configuration. - */ - public void saveConfiguration() { - configuration.setItemValue(Scheduler.ITEM_SCHEDULER_CLASS, getSchedulerClass()); - schedulerService.saveConfiguration(getConfiguration()); - } - - /** - * This method updates the scheduler configuration with the current timer information - * - */ - public void refresh() { - configuration = schedulerService.loadConfiguration(getName()); - } - - public SchedulerService getSchedulerService() { - return schedulerService; - } - - /** - * starts the timer service - * - * @return - * @throws ParseException - * @throws AccessDeniedException - * @throws Exception - */ - public void startScheduler() throws AccessDeniedException, ParseException { - configuration = schedulerService.start(getConfiguration()); - schedulerService.saveConfiguration(configuration); - } - - public void stopScheduler() { - configuration = schedulerService.stop(getConfiguration()); - schedulerService.saveConfiguration(configuration); - } - - public void restartScheduler(ActionEvent event) throws Exception { - stopScheduler(); - startScheduler(); - } - - /** - * - * converts time (in milliseconds) to human-readable format "hh:mm:ss" - * - * @return - */ - public String millisToShortDHMS(int duration) { - boolean debug = logger.isLoggable(Level.FINE); - if (debug) { - logger.finest("......confert ms " + duration); + + public void setConfiguration(ItemCollection configuration) { + this.configuration = configuration; } - String res = ""; - long days = TimeUnit.MILLISECONDS.toDays(duration); - long hours = TimeUnit.MILLISECONDS.toHours(duration) - - TimeUnit.DAYS.toHours(TimeUnit.MILLISECONDS.toDays(duration)); - long minutes = TimeUnit.MILLISECONDS.toMinutes(duration) - - TimeUnit.HOURS.toMinutes(TimeUnit.MILLISECONDS.toHours(duration)); - long seconds = TimeUnit.MILLISECONDS.toSeconds(duration) - - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(duration)); - if (days == 0) { - res = String.format("%d hours, %d minutes, %d seconds", hours, minutes, seconds); - } else { - res = - String.format("%d days, %d hours, %d minutes, %d seconds", days, hours, minutes, seconds); + + /** + * Saves the current scheduler configuration. + */ + public void saveConfiguration() { + configuration.setItemValue(Scheduler.ITEM_SCHEDULER_CLASS, getSchedulerClass()); + schedulerService.saveConfiguration(getConfiguration()); + } + + /** + * This method updates the scheduler configuration with the current timer + * information + * + */ + public void refresh() { + configuration = schedulerService.loadConfiguration(getName()); } - return res; - } + public SchedulerService getSchedulerService() { + return schedulerService; + } + + /** + * starts the timer service + * + * @return + * @throws ParseException + * @throws AccessDeniedException + * @throws Exception + */ + public void startScheduler() throws AccessDeniedException, ParseException { + configuration = schedulerService.start(getConfiguration()); + schedulerService.saveConfiguration(configuration); + } + + public void stopScheduler() { + configuration = schedulerService.stop(getConfiguration()); + schedulerService.saveConfiguration(configuration); + } + + public void restartScheduler(ActionEvent event) throws Exception { + stopScheduler(); + startScheduler(); + } + + /** + * + * converts time (in milliseconds) to human-readable format "hh:mm:ss" + * + * @return + */ + public String millisToShortDHMS(int duration) { + boolean debug = logger.isLoggable(Level.FINE); + if (debug) { + logger.finest("......confert ms " + duration); + } + String res = ""; + long days = TimeUnit.MILLISECONDS.toDays(duration); + long hours = TimeUnit.MILLISECONDS.toHours(duration) + - TimeUnit.DAYS.toHours(TimeUnit.MILLISECONDS.toDays(duration)); + long minutes = TimeUnit.MILLISECONDS.toMinutes(duration) + - TimeUnit.HOURS.toMinutes(TimeUnit.MILLISECONDS.toHours(duration)); + long seconds = TimeUnit.MILLISECONDS.toSeconds(duration) + - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(duration)); + if (days == 0) { + res = String.format("%d hours, %d minutes, %d seconds", hours, minutes, seconds); + } else { + res = String.format("%d days, %d hours, %d minutes, %d seconds", days, hours, minutes, seconds); + } + return res; + + } } diff --git a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/scheduler/SchedulerException.java b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/scheduler/SchedulerException.java index e54666b40..2b806dff7 100644 --- a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/scheduler/SchedulerException.java +++ b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/scheduler/SchedulerException.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *

      - *  Imixs Workflow 
      +/*  
      + *  Imixs-Workflow 
      + *  
        *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
        *  http://www.imixs.com
        *  
      @@ -22,10 +22,9 @@
        *      https://github.com/imixs/imixs-workflow
        *  
        *  Contributors:  
      - *      Imixs Software Solutions GmbH - initial API and implementation
      + *      Imixs Software Solutions GmbH - Project Management
        *      Ralph Soika - Software Developer
      - * 
      - *******************************************************************************/ + */ package org.imixs.workflow.engine.scheduler; @@ -40,35 +39,33 @@ */ public class SchedulerException extends WorkflowException { - private static final long serialVersionUID = 1L; - - public static final String INVALID_MODELVERSION = "INVALID_MODELVERSION"; - public static final String INVALID_WORKITEM = "INVALID_WORKITEM"; - public static final String INVALID_PROCESSID = "INVALID_PROCESSID"; - - protected String errorContext = "UNDEFINED"; - protected String errorCode = "UNDEFINED"; + private static final long serialVersionUID = 1L; + public static final String INVALID_MODELVERSION = "INVALID_MODELVERSION"; + public static final String INVALID_WORKITEM = "INVALID_WORKITEM"; + public static final String INVALID_PROCESSID = "INVALID_PROCESSID"; - public SchedulerException(String aErrorCode, String message) { - super(aErrorCode, message); - } + protected String errorContext = "UNDEFINED"; + protected String errorCode = "UNDEFINED"; - public SchedulerException(String aErrorContext, String aErrorCode, String message) { - super(aErrorContext, aErrorCode, message); - } + public SchedulerException(String aErrorCode, String message) { + super(aErrorCode, message); + } - public SchedulerException(String aErrorContext, String aErrorCode, String message, Exception e) { - super(aErrorContext, aErrorCode, message, e); - } + public SchedulerException(String aErrorContext, String aErrorCode, String message) { + super(aErrorContext, aErrorCode, message); + } + public SchedulerException(String aErrorContext, String aErrorCode, String message, Exception e) { + super(aErrorContext, aErrorCode, message, e); + } - public SchedulerException(String aErrorCode, String message, Exception e) { - super(aErrorCode, message, e); - } + public SchedulerException(String aErrorCode, String message, Exception e) { + super(aErrorCode, message, e); + } - public void setErrorCode(String errorCode) { - this.errorCode = errorCode; - } + public void setErrorCode(String errorCode) { + this.errorCode = errorCode; + } } diff --git a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/scheduler/SchedulerService.java b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/scheduler/SchedulerService.java index 5be5fe416..33fa030b9 100644 --- a/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/scheduler/SchedulerService.java +++ b/imixs-workflow-engine/src/main/java/org/imixs/workflow/engine/scheduler/SchedulerService.java @@ -1,6 +1,6 @@ -/******************************************************************************* - *
      - *  Imixs Workflow 
      +/*  
      + *  Imixs-Workflow 
      + *  
        *  Copyright (C) 2001-2020 Imixs Software Solutions GmbH,  
        *  http://www.imixs.com
        *  
      @@ -22,10 +22,9 @@
        *      https://github.com/imixs/imixs-workflow
        *  
        *  Contributors:  
      - *      Imixs Software Solutions GmbH - initial API and implementation
      + *      Imixs Software Solutions GmbH - Project Management
        *      Ralph Soika - Software Developer
      - * 
      - *******************************************************************************/ + */ package org.imixs.workflow.engine.scheduler; @@ -57,31 +56,36 @@ import org.imixs.workflow.exceptions.QueryException; /** - * The SchedulerService EJB can be used to start, monitor and stop custom scheduler implementation. - * A Scheduler Implementation must implement the Interface - * "org.imixs.workflow.engine.scheduler.Scheduler". + * The SchedulerService EJB can be used to start, monitor and stop custom + * scheduler implementation. A Scheduler Implementation must implement the + * Interface "org.imixs.workflow.engine.scheduler.Scheduler". *

      - * A scheduler definition is stored in a document with the type "scheduler". The document can - * provide concrete information to process the timer event. + * A scheduler definition is stored in a document with the type "scheduler". The + * document can provide concrete information to process the timer event. *

      - * The TimerService can be started using the method start(). The Methods findTimerDescription and - * findAllTimerDescriptions are used to lookup enabled and running service instances. + * The TimerService can be started using the method start(). The Methods + * findTimerDescription and findAllTimerDescriptions are used to lookup enabled + * and running service instances. *

      - * Each Method expects or generates a TimerDescription Object. This object is an instance of a - * ItemCollection. To create a new timer the ItemCollection should contain the following attributes: + * Each Method expects or generates a TimerDescription Object. This object is an + * instance of a ItemCollection. To create a new timer the ItemCollection should + * contain the following attributes: *

      *

        *
      • type - fixed to value 'scheduler'
      • - *
      • _scheduler_definition - the chron/calendar definition for the Java EE timer service.
      • - *
      • _scheduler_enabled - boolean indicates if the scheduler is enabled/disabled
      • + *
      • _scheduler_definition - the chron/calendar definition for the Java EE + * timer service.
      • + *
      • _scheduler_enabled - boolean indicates if the scheduler is + * enabled/disabled
      • *
      • _scheduler_class - class name of the scheduler implementation
      • *
      • _scheduelr_log - optional log information *
      *

      - * the following additional attributes are generated by the finder methods and can be used by an - * application to verfiy the status of a running instance: + * the following additional attributes are generated by the finder methods and + * can be used by an application to verfiy the status of a running instance: *

        - *
      • nextTimeout - Next Timeout - pint of time when the service will be scheduled
      • + *
      • nextTimeout - Next Timeout - pint of time when the service will be + * scheduled
      • *
      • timeRemaining - Timeout in milliseconds
      • *
      • statusmessage - text message
      • *
      @@ -91,524 +95,516 @@ */ @Stateless @LocalBean -@DeclareRoles({"org.imixs.ACCESSLEVEL.MANAGERACCESS"}) +@DeclareRoles({ "org.imixs.ACCESSLEVEL.MANAGERACCESS" }) @RunAs("org.imixs.ACCESSLEVEL.MANAGERACCESS") public class SchedulerService { - public static final String DOCUMENT_TYPE = "scheduler"; - - @Resource - SessionContext ctx; - - @Inject - DocumentService documentService; - - @Resource - javax.ejb.TimerService timerService; - - @Inject - SchedulerConfigurationService schedulerSaveService; - - @Inject - @Any - private Instance schedulerHandlers; - - private static Logger logger = Logger.getLogger(SchedulerService.class.getName()); - - /** - * Loads the scheduler configuration entity by name. The method returns null if no scheduler - * configuration exits. - * - * @return - */ - public ItemCollection loadConfiguration(String name) { - try { - // support deprecated txtname attribure - String sQuery = "(type:\"" + DOCUMENT_TYPE + "\" AND (name:\"" + name + "\" OR txtname:\"" - + name + "\" ) )"; - Collection col = documentService.find(sQuery, 1, 0); - // check if we found a scheduler configuration - if (col.size() > 0) { - ItemCollection configuration = col.iterator().next(); - // refresh timer details - updateTimerDetails(configuration); - return configuration; - } - } catch (QueryException e1) { - e1.printStackTrace(); - } - return null; - } - - /** - * This method saves the scheduler configuration. The method ensures that the following properties - * are set to default. - *
        - *
      • type
      • - *
      • name
      • - *
      • $writeAccess
      • - *
      • $readAccess
      • - *
      - * The method also updates the timer details of a running timer. - * - * @return - * @throws AccessDeniedException - */ - public ItemCollection saveConfiguration(ItemCollection configItemCollection) { - - // validate and migrate deprecated 'txtname' field - String name = configItemCollection.getItemValueString("name"); - if (name.isEmpty()) { - name = configItemCollection.getItemValueString("txtname"); - configItemCollection.replaceItemValue("name", name); - } - if (name == null || name.isEmpty()) { - throw new InvalidAccessException(SchedulerService.class.getName(), - SchedulerException.INVALID_WORKITEM, - " scheduler configuraiton must contain the item 'name'"); + public static final String DOCUMENT_TYPE = "scheduler"; + + @Resource + SessionContext ctx; + + @Inject + DocumentService documentService; + + @Resource + javax.ejb.TimerService timerService; + + @Inject + SchedulerConfigurationService schedulerSaveService; + + @Inject + @Any + private Instance schedulerHandlers; + + private static Logger logger = Logger.getLogger(SchedulerService.class.getName()); + + /** + * Loads the scheduler configuration entity by name. The method returns null if + * no scheduler configuration exits. + * + * @return + */ + public ItemCollection loadConfiguration(String name) { + try { + // support deprecated txtname attribure + String sQuery = "(type:\"" + DOCUMENT_TYPE + "\" AND (name:\"" + name + "\" OR txtname:\"" + name + + "\" ) )"; + Collection col = documentService.find(sQuery, 1, 0); + // check if we found a scheduler configuration + if (col.size() > 0) { + ItemCollection configuration = col.iterator().next(); + // refresh timer details + updateTimerDetails(configuration); + return configuration; + } + } catch (QueryException e1) { + e1.printStackTrace(); + } + return null; } - // update write and read access - configItemCollection.replaceItemValue("type", DOCUMENT_TYPE); - configItemCollection.replaceItemValue("$writeAccess", "org.imixs.ACCESSLEVEL.MANAGERACCESS"); - configItemCollection.replaceItemValue("$readAccess", "org.imixs.ACCESSLEVEL.MANAGERACCESS"); - - // refesh timer details - updateTimerDetails(configItemCollection); - // save entity in new transaction - configItemCollection = documentService.save(configItemCollection); - - return configItemCollection; - } - - /** - * Starts a new Timer for the scheduler defined by the Configuration. - *

      - * The Timer can be started based on a Calendar setting stored in the property - * _scheduler_definition. - *

      - * The $UniqueID of the configuration entity is the id of the timer to be controlled. - *

      - * The method throws an exception if the configuration entity contains invalid attributes or - * values. - *

      - * After the timer was started the configuration is updated with the latest statusmessage. The - * item _schedueler_enabled will be set to 'true'. - *

      - * The method returns the updated configuration. The configuration will not be saved! - * - * @param configuration - scheduler configuration - * @return updated configuration - * @throws AccessDeniedException - * @throws ParseException - */ - public ItemCollection start(ItemCollection configuration) - throws AccessDeniedException, ParseException { - Timer timer = null; - if (configuration == null) - return null; - - String id = configuration.getUniqueID(); - // try to cancel an existing timer for this workflowinstance - timer = findTimer(id); - if (timer != null) { - try { - timer.cancel(); - timer = null; - } catch (Exception e) { - logger - .warning("...failed to stop existing timer for '" + configuration.getUniqueID() + "'!"); - throw new InvalidAccessException(SchedulerService.class.getName(), - SchedulerException.INVALID_WORKITEM, " failed to cancle existing timer!"); - } - } + /** + * This method saves the scheduler configuration. The method ensures that the + * following properties are set to default. + *

        + *
      • type
      • + *
      • name
      • + *
      • $writeAccess
      • + *
      • $readAccess
      • + *
      + * The method also updates the timer details of a running timer. + * + * @return + * @throws AccessDeniedException + */ + public ItemCollection saveConfiguration(ItemCollection configItemCollection) { + + // validate and migrate deprecated 'txtname' field + String name = configItemCollection.getItemValueString("name"); + if (name.isEmpty()) { + name = configItemCollection.getItemValueString("txtname"); + configItemCollection.replaceItemValue("name", name); + } + if (name == null || name.isEmpty()) { + throw new InvalidAccessException(SchedulerService.class.getName(), SchedulerException.INVALID_WORKITEM, + " scheduler configuraiton must contain the item 'name'"); + } + + // update write and read access + configItemCollection.replaceItemValue("type", DOCUMENT_TYPE); + configItemCollection.replaceItemValue("$writeAccess", "org.imixs.ACCESSLEVEL.MANAGERACCESS"); + configItemCollection.replaceItemValue("$readAccess", "org.imixs.ACCESSLEVEL.MANAGERACCESS"); - logger.info("...Scheduler Service " + configuration.getUniqueID() + " will be started..."); - String schedulerDescription = - configuration.getItemValueString(Scheduler.ITEM_SCHEDULER_DEFINITION); + // refesh timer details + updateTimerDetails(configItemCollection); + // save entity in new transaction + configItemCollection = documentService.save(configItemCollection); - if (!schedulerDescription.isEmpty()) { - // New timer will be started on calendar confiugration - timer = createTimerOnCalendar(configuration); + return configItemCollection; } - // start and set statusmessage - if (timer != null) { - - Calendar calNow = Calendar.getInstance(); - SimpleDateFormat dateFormatDE = new SimpleDateFormat("dd.MM.yy hh:mm:ss"); - String msg = "started at " + dateFormatDE.format(calNow.getTime()) + " by " - + ctx.getCallerPrincipal().getName(); - configuration.replaceItemValue(Scheduler.ITEM_SCHEDULER_STATUS, msg); - logger.info("...Scheduler Service " + id + " (" + configuration.getItemValueString("Name") - + ") successfull started."); + + /** + * Starts a new Timer for the scheduler defined by the Configuration. + *

      + * The Timer can be started based on a Calendar setting stored in the property + * _scheduler_definition. + *

      + * The $UniqueID of the configuration entity is the id of the timer to be + * controlled. + *

      + * The method throws an exception if the configuration entity contains invalid + * attributes or values. + *

      + * After the timer was started the configuration is updated with the latest + * statusmessage. The item _schedueler_enabled will be set to 'true'. + *

      + * The method returns the updated configuration. The configuration will not be + * saved! + * + * @param configuration - scheduler configuration + * @return updated configuration + * @throws AccessDeniedException + * @throws ParseException + */ + public ItemCollection start(ItemCollection configuration) throws AccessDeniedException, ParseException { + Timer timer = null; + if (configuration == null) + return null; + + String id = configuration.getUniqueID(); + // try to cancel an existing timer for this workflowinstance + timer = findTimer(id); + if (timer != null) { + try { + timer.cancel(); + timer = null; + } catch (Exception e) { + logger.warning("...failed to stop existing timer for '" + configuration.getUniqueID() + "'!"); + throw new InvalidAccessException(SchedulerService.class.getName(), SchedulerException.INVALID_WORKITEM, + " failed to cancle existing timer!"); + } + } + + logger.info("...Scheduler Service " + configuration.getUniqueID() + " will be started..."); + String schedulerDescription = configuration.getItemValueString(Scheduler.ITEM_SCHEDULER_DEFINITION); + + if (!schedulerDescription.isEmpty()) { + // New timer will be started on calendar confiugration + timer = createTimerOnCalendar(configuration); + } + // start and set statusmessage + if (timer != null) { + + Calendar calNow = Calendar.getInstance(); + SimpleDateFormat dateFormatDE = new SimpleDateFormat("dd.MM.yy hh:mm:ss"); + String msg = "started at " + dateFormatDE.format(calNow.getTime()) + " by " + + ctx.getCallerPrincipal().getName(); + configuration.replaceItemValue(Scheduler.ITEM_SCHEDULER_STATUS, msg); + logger.info("...Scheduler Service " + id + " (" + configuration.getItemValueString("Name") + + ") successfull started."); + } + configuration.replaceItemValue(Scheduler.ITEM_SCHEDULER_ENABLED, true); + // clear logs... + configuration.replaceItemValue(Scheduler.ITEM_ERRORMESSAGE, ""); + configuration.replaceItemValue(Scheduler.ITEM_LOGMESSAGE, ""); + + return configuration; } - configuration.replaceItemValue(Scheduler.ITEM_SCHEDULER_ENABLED, true); - // clear logs... - configuration.replaceItemValue(Scheduler.ITEM_ERRORMESSAGE, ""); - configuration.replaceItemValue(Scheduler.ITEM_LOGMESSAGE, ""); - - return configuration; - } - - /** - * Cancels a running timer instance. After cancel a timer the corresponding timerDescripton - * (ItemCollection) is no longer valid. - *

      - * The method returns the current configuration. The configuration will not be saved! - * - * - */ - public ItemCollection stop(ItemCollection configuration) { - Timer timer = findTimer(configuration.getUniqueID()); - return stop(configuration, timer); - - } - - public ItemCollection stop(ItemCollection configuration, Timer timer) { - if (timer != null) { - try { - timer.cancel(); - } catch (Exception e) { - logger.info("...failed to stop timer for '" + configuration.getUniqueID() + "'!"); - } - - // update status message - Calendar calNow = Calendar.getInstance(); - SimpleDateFormat dateFormatDE = new SimpleDateFormat("dd.MM.yy hh:mm:ss"); - - String message = "stopped at " + dateFormatDE.format(calNow.getTime()); - String name = ctx.getCallerPrincipal().getName(); - if (name != null && !name.isEmpty() && !"anonymous".equals(name)) { - message += " by " + name; - } - configuration.replaceItemValue(Scheduler.ITEM_SCHEDULER_STATUS, message); - - logger.info("... scheduler " + configuration.getItemValueString("Name") + " stopped: " - + configuration.getUniqueID()); - } else { - String msg = "stopped"; - configuration.replaceItemValue(Scheduler.ITEM_SCHEDULER_STATUS, msg); + + /** + * Cancels a running timer instance. After cancel a timer the corresponding + * timerDescripton (ItemCollection) is no longer valid. + *

      + * The method returns the current configuration. The configuration will not be + * saved! + * + * + */ + public ItemCollection stop(ItemCollection configuration) { + Timer timer = findTimer(configuration.getUniqueID()); + return stop(configuration, timer); } - configuration.removeItem("nextTimeout"); - configuration.removeItem("timeRemaining"); - configuration.replaceItemValue(Scheduler.ITEM_SCHEDULER_ENABLED, false); - Calendar cal = Calendar.getInstance(); - configuration.appendItemValue(Scheduler.ITEM_LOGMESSAGE, "Stopped: " + cal.getTime()); - return configuration; - } - - /** - * This method will start all schedulers which are not yet started. The method is called for - * example by the SchedulerStartupServlet. - * - */ - public void startAllSchedulers() { - logger.info("...starting Imixs Schedulers...."); - try { - String sQuery = "(type:\"" + SchedulerService.DOCUMENT_TYPE + "\" )"; - Collection col = documentService.find(sQuery, 101, 0); - if (col.size() > 100) { - // Issue #568 - we do not support more than 100 jobs in parallel! - logger.severe( - "More than 100 waiting scheduler jobs found but a maximum of 100 jobs will be started in parallel. Please report this issue to the imixs-workflow project!"); - } - // check if we found a scheduler configuration - for (ItemCollection schedulerConfig : col) { - // is timmer running? - if (schedulerConfig != null - && schedulerConfig.getItemValueBoolean(Scheduler.ITEM_SCHEDULER_ENABLED)) { - try { - - if (findTimer(schedulerConfig.getUniqueID()) == null) { - start(schedulerConfig); - } else { - logger.info( - "...Scheduler Service " + schedulerConfig.getUniqueID() + " already running. "); + + public ItemCollection stop(ItemCollection configuration, Timer timer) { + if (timer != null) { + try { + timer.cancel(); + } catch (Exception e) { + logger.info("...failed to stop timer for '" + configuration.getUniqueID() + "'!"); + } + + // update status message + Calendar calNow = Calendar.getInstance(); + SimpleDateFormat dateFormatDE = new SimpleDateFormat("dd.MM.yy hh:mm:ss"); + + String message = "stopped at " + dateFormatDE.format(calNow.getTime()); + String name = ctx.getCallerPrincipal().getName(); + if (name != null && !name.isEmpty() && !"anonymous".equals(name)) { + message += " by " + name; } - } catch (Exception e) { - logger.severe("...start of Scheduler Service " + schedulerConfig.getUniqueID() - + " failed! - " + e.getMessage()); - e.printStackTrace(); - } + configuration.replaceItemValue(Scheduler.ITEM_SCHEDULER_STATUS, message); + + logger.info("... scheduler " + configuration.getItemValueString("Name") + " stopped: " + + configuration.getUniqueID()); } else { - logger - .info("...Scheduler Service " + schedulerConfig.getUniqueID() + " is not enabled. "); + String msg = "stopped"; + configuration.replaceItemValue(Scheduler.ITEM_SCHEDULER_STATUS, msg); + } - } - } catch (QueryException e1) { - e1.printStackTrace(); - } - } - - /** - * This method returns a timer for a corresponding id if such a timer object exists. - * - * @param id - * @return Timer - * @throws Exception - */ - public Timer findTimer(String id) { - for (Object obj : timerService.getTimers()) { - Timer timer = (javax.ejb.Timer) obj; - if (id.equals(timer.getInfo())) { - return timer; - } - } - return null; - } - - /** - * Updates the timer details of a running timer service. The method updates the properties - * netxtTimeout and timeRemaining and store them into the timer configuration. - * - * @param configuration - the current scheduler configuration to be updated. - */ - public void updateTimerDetails(ItemCollection configuration) { - if (configuration == null) - return;// configuration; - String id = configuration.getUniqueID(); - Timer timer; - try { - timer = this.findTimer(id); - if (timer != null) { - // load current timer details - configuration.replaceItemValue("nextTimeout", timer.getNextTimeout()); - configuration.replaceItemValue("timeRemaining", timer.getTimeRemaining()); - } else { configuration.removeItem("nextTimeout"); configuration.removeItem("timeRemaining"); - } - } catch (Exception e) { - logger.warning("unable to updateTimerDetails: " + e.getMessage()); - configuration.removeItem("nextTimeout"); - configuration.removeItem("timeRemaining"); + configuration.replaceItemValue(Scheduler.ITEM_SCHEDULER_ENABLED, false); + Calendar cal = Calendar.getInstance(); + configuration.appendItemValue(Scheduler.ITEM_LOGMESSAGE, "Stopped: " + cal.getTime()); + return configuration; } - } - + /** + * This method will start all schedulers which are not yet started. The method + * is called for example by the SchedulerStartupServlet. + * + */ + public void startAllSchedulers() { + logger.info("...starting Imixs Schedulers...."); + try { + String sQuery = "(type:\"" + SchedulerService.DOCUMENT_TYPE + "\" )"; + Collection col = documentService.find(sQuery, 101, 0); + if (col.size() > 100) { + // Issue #568 - we do not support more than 100 jobs in parallel! + logger.severe( + "More than 100 waiting scheduler jobs found but a maximum of 100 jobs will be started in parallel. Please report this issue to the imixs-workflow project!"); + } + // check if we found a scheduler configuration + for (ItemCollection schedulerConfig : col) { + // is timmer running? + if (schedulerConfig != null && schedulerConfig.getItemValueBoolean(Scheduler.ITEM_SCHEDULER_ENABLED)) { + try { + + if (findTimer(schedulerConfig.getUniqueID()) == null) { + start(schedulerConfig); + } else { + logger.info("...Scheduler Service " + schedulerConfig.getUniqueID() + " already running. "); + } + } catch (Exception e) { + logger.severe("...start of Scheduler Service " + schedulerConfig.getUniqueID() + " failed! - " + + e.getMessage()); + e.printStackTrace(); + } + } else { + logger.info("...Scheduler Service " + schedulerConfig.getUniqueID() + " is not enabled. "); + } + } + } catch (QueryException e1) { + e1.printStackTrace(); + } + } - /** - * Creates a new log entry stored in the item _scheduler_log. The log can be writen optional to - * the scheduler configuration and a workitem. - * - * @param message - * @param configuration - */ - public void logMessage(String message, ItemCollection configuration, ItemCollection workitem) { - if (configuration != null) { - configuration.appendItemValue(Scheduler.ITEM_LOGMESSAGE, message); + /** + * This method returns a timer for a corresponding id if such a timer object + * exists. + * + * @param id + * @return Timer + * @throws Exception + */ + public Timer findTimer(String id) { + for (Object obj : timerService.getTimers()) { + Timer timer = (javax.ejb.Timer) obj; + if (id.equals(timer.getInfo())) { + return timer; + } + } + return null; } - if (workitem != null) { - workitem.appendItemValue(Scheduler.ITEM_LOGMESSAGE, message); + + /** + * Updates the timer details of a running timer service. The method updates the + * properties netxtTimeout and timeRemaining and store them into the timer + * configuration. + * + * @param configuration - the current scheduler configuration to be updated. + */ + public void updateTimerDetails(ItemCollection configuration) { + if (configuration == null) + return;// configuration; + String id = configuration.getUniqueID(); + Timer timer; + try { + timer = this.findTimer(id); + if (timer != null) { + // load current timer details + configuration.replaceItemValue("nextTimeout", timer.getNextTimeout()); + configuration.replaceItemValue("timeRemaining", timer.getTimeRemaining()); + } else { + configuration.removeItem("nextTimeout"); + configuration.removeItem("timeRemaining"); + } + } catch (Exception e) { + logger.warning("unable to updateTimerDetails: " + e.getMessage()); + configuration.removeItem("nextTimeout"); + configuration.removeItem("timeRemaining"); + } } - logger.info(message); + /** + * Creates a new log entry stored in the item _scheduler_log. The log can be + * writen optional to the scheduler configuration and a workitem. + * + * @param message + * @param configuration + */ + public void logMessage(String message, ItemCollection configuration, ItemCollection workitem) { + if (configuration != null) { + configuration.appendItemValue(Scheduler.ITEM_LOGMESSAGE, message); + } + if (workitem != null) { + workitem.appendItemValue(Scheduler.ITEM_LOGMESSAGE, message); + } - } + logger.info(message); - /** - * Creates a new log entry stored in the item _scheduler_log. The log can be writen optional to - * the scheduler configuration and a workitem. - * - * @param message - * @param configuration - */ - public void logWarning(String message, ItemCollection configuration, ItemCollection workitem) { - if (configuration != null) { - configuration.appendItemValue(Scheduler.ITEM_LOGMESSAGE, message); - } - if (workitem != null) { - workitem.appendItemValue(Scheduler.ITEM_LOGMESSAGE, message); } - logger.warning(message); - - } + /** + * Creates a new log entry stored in the item _scheduler_log. The log can be + * writen optional to the scheduler configuration and a workitem. + * + * @param message + * @param configuration + */ + public void logWarning(String message, ItemCollection configuration, ItemCollection workitem) { + if (configuration != null) { + configuration.appendItemValue(Scheduler.ITEM_LOGMESSAGE, message); + } + if (workitem != null) { + workitem.appendItemValue(Scheduler.ITEM_LOGMESSAGE, message); + } + logger.warning(message); - /** - * This method returns a n injected JobHandler by name or null if no JobHandler with the requested - * class name is injected. - * - * @param jobHandlerClassName - * @return jobHandler class or null if not found - */ - protected Scheduler findSchedulerByName(String schedulerClassName) { - if (schedulerClassName == null || schedulerClassName.isEmpty()) { - return null; } - boolean debug = logger.isLoggable(Level.FINE); - if (schedulerHandlers == null || !schedulerHandlers.iterator().hasNext()) { - if (debug) { - logger.finest("......no CDI schedulers injected"); - } - return null; - } + /** + * This method returns a n injected JobHandler by name or null if no JobHandler + * with the requested class name is injected. + * + * @param jobHandlerClassName + * @return jobHandler class or null if not found + */ + protected Scheduler findSchedulerByName(String schedulerClassName) { + if (schedulerClassName == null || schedulerClassName.isEmpty()) { + return null; + } + boolean debug = logger.isLoggable(Level.FINE); - logger.finest("......injecting CDI Scheduler '" + schedulerClassName + "'..."); - // iterate over all injected JobHandlers.... - for (Scheduler scheduler : this.schedulerHandlers) { - if (scheduler.getClass().getName().equals(schedulerClassName)) { - if (debug) { - logger - .finest("......CDI Scheduler class '" + schedulerClassName + "' successful injected"); + if (schedulerHandlers == null || !schedulerHandlers.iterator().hasNext()) { + if (debug) { + logger.finest("......no CDI schedulers injected"); + } + return null; } - return scheduler; - } - } - return null; - } - - /** - * This is the method which processes the timeout event depending on the running timer settings. - * The method calls the abstract method 'process' which need to be implemented by a subclass. - * - * @param timer - * @throws Exception - * @throws QueryException - */ - @Timeout - protected void onTimeout(javax.ejb.Timer timer) { - String errorMes = ""; - // start time.... - long lProfiler = System.currentTimeMillis(); - String id = timer.getInfo().toString(); - ItemCollection configuration = documentService.load(id); - - if (configuration == null) { - logger.severe( - "...failed to load scheduler configuration for current timer. Timer will be stopped..."); - timer.cancel(); - return; + logger.finest("......injecting CDI Scheduler '" + schedulerClassName + "'..."); + // iterate over all injected JobHandlers.... + for (Scheduler scheduler : this.schedulerHandlers) { + if (scheduler.getClass().getName().equals(schedulerClassName)) { + if (debug) { + logger.finest("......CDI Scheduler class '" + schedulerClassName + "' successful injected"); + } + return scheduler; + } + } + + return null; } - try { - // ...start processing - String schedulerClassName = configuration.getItemValueString(Scheduler.ITEM_SCHEDULER_CLASS); + /** + * This is the method which processes the timeout event depending on the running + * timer settings. The method calls the abstract method 'process' which need to + * be implemented by a subclass. + * + * @param timer + * @throws Exception + * @throws QueryException + */ + @Timeout + protected void onTimeout(javax.ejb.Timer timer) { + String errorMes = ""; + // start time.... + long lProfiler = System.currentTimeMillis(); + String id = timer.getInfo().toString(); + ItemCollection configuration = documentService.load(id); + + if (configuration == null) { + logger.severe("...failed to load scheduler configuration for current timer. Timer will be stopped..."); + timer.cancel(); + return; + } + + try { + // ...start processing + String schedulerClassName = configuration.getItemValueString(Scheduler.ITEM_SCHEDULER_CLASS); + + Scheduler scheduler = findSchedulerByName(schedulerClassName); + if (scheduler != null) { + logger.info("...run scheduler '" + id + "' scheduler class='" + schedulerClassName + "'...."); + Calendar cal = Calendar.getInstance(); + configuration.replaceItemValue(Scheduler.ITEM_LOGMESSAGE, "Started: " + cal.getTime()); + + configuration = scheduler.run(configuration); + logger.info("...run scheduler '" + id + "' finished in: " + ((System.currentTimeMillis()) - lProfiler) + + " ms"); + cal = Calendar.getInstance(); + configuration.appendItemValue(Scheduler.ITEM_LOGMESSAGE, "Finished: " + cal.getTime()); + if (configuration.getItemValueBoolean(Scheduler.ITEM_SCHEDULER_ENABLED) == false) { + logger.info("...scheduler '" + id + "' disabled -> timer will be stopped..."); + stop(configuration); + } + } else { + errorMes = "Scheduler class='" + schedulerClassName + "' not found!"; + logger.warning("...scheduler '" + id + "' scheduler class='" + schedulerClassName + + "' not found, timer will be stopped..."); + configuration.setItemValue(Scheduler.ITEM_SCHEDULER_ENABLED, false); - Scheduler scheduler = findSchedulerByName(schedulerClassName); - if (scheduler != null) { - logger - .info("...run scheduler '" + id + "' scheduler class='" + schedulerClassName + "'...."); - Calendar cal = Calendar.getInstance(); - configuration.replaceItemValue(Scheduler.ITEM_LOGMESSAGE, "Started: " + cal.getTime()); - - configuration = scheduler.run(configuration); - logger.info("...run scheduler '" + id + "' finished in: " - + ((System.currentTimeMillis()) - lProfiler) + " ms"); - cal = Calendar.getInstance(); - configuration.appendItemValue(Scheduler.ITEM_LOGMESSAGE, "Finished: " + cal.getTime()); - if (configuration.getItemValueBoolean(Scheduler.ITEM_SCHEDULER_ENABLED) == false) { - logger.info("...scheduler '" + id + "' disabled -> timer will be stopped..."); - stop(configuration); + stop(configuration); + } + } catch (RuntimeException | SchedulerException e) { + // in case of an exception we did not cancel the Timer service + if (logger.isLoggable(Level.FINEST)) { + e.printStackTrace(); + } + errorMes = e.getMessage(); + logger.severe("Scheduler '" + id + "' failed: " + errorMes); + + configuration.appendItemValue(Scheduler.ITEM_LOGMESSAGE, "Error: " + errorMes); + + configuration = stop(configuration, timer); + } finally { + // Save statistic in configuration + if (configuration != null) { + configuration.replaceItemValue(Scheduler.ITEM_ERRORMESSAGE, errorMes); + schedulerSaveService.storeConfigurationInNewTransaction(configuration); + + } } - } else { - errorMes = "Scheduler class='" + schedulerClassName + "' not found!"; - logger.warning("...scheduler '" + id + "' scheduler class='" + schedulerClassName - + "' not found, timer will be stopped..."); - configuration.setItemValue(Scheduler.ITEM_SCHEDULER_ENABLED, false); - - stop(configuration); - } - } catch (RuntimeException | SchedulerException e) { - // in case of an exception we did not cancel the Timer service - if (logger.isLoggable(Level.FINEST)) { - e.printStackTrace(); - } - errorMes = e.getMessage(); - logger.severe("Scheduler '" + id + "' failed: " + errorMes); - - configuration.appendItemValue(Scheduler.ITEM_LOGMESSAGE, "Error: " + errorMes); - - configuration = stop(configuration, timer); - } finally { - // Save statistic in configuration - if (configuration != null) { - configuration.replaceItemValue(Scheduler.ITEM_ERRORMESSAGE, errorMes); - schedulerSaveService.storeConfigurationInNewTransaction(configuration); - - } } - } - - /** - * Create a calendar-based timer based on a input schedule expression. The expression will be - * parsed by this method. - * - * Example: - * second=0 - * minute=0 - * hour=* - * dayOfWeek= - * dayOfMonth= - * month= - * year=* - * - * - * @param sConfiguation - * @return - * @throws ParseException - */ - protected Timer createTimerOnCalendar(ItemCollection configItemCollection) throws ParseException { - - TimerConfig timerConfig = new TimerConfig(); - timerConfig.setInfo(configItemCollection.getUniqueID()); - - ScheduleExpression scheduerExpression = new ScheduleExpression(); - - @SuppressWarnings("unchecked") - List calendarConfiguation = - configItemCollection.getItemValue(Scheduler.ITEM_SCHEDULER_DEFINITION); - // try to parse the configuration list.... - for (String confgEntry : calendarConfiguation) { - - if (confgEntry.startsWith("second=")) { - scheduerExpression.second(confgEntry.substring(confgEntry.indexOf('=') + 1)); - } - if (confgEntry.startsWith("minute=")) { - scheduerExpression.minute(confgEntry.substring(confgEntry.indexOf('=') + 1)); - } - if (confgEntry.startsWith("hour=")) { - scheduerExpression.hour(confgEntry.substring(confgEntry.indexOf('=') + 1)); - } - if (confgEntry.startsWith("dayOfWeek=")) { - scheduerExpression.dayOfWeek(confgEntry.substring(confgEntry.indexOf('=') + 1)); - } - if (confgEntry.startsWith("dayOfMonth=")) { - scheduerExpression.dayOfMonth(confgEntry.substring(confgEntry.indexOf('=') + 1)); - } - if (confgEntry.startsWith("month=")) { - scheduerExpression.month(confgEntry.substring(confgEntry.indexOf('=') + 1)); - } - if (confgEntry.startsWith("year=")) { - scheduerExpression.year(confgEntry.substring(confgEntry.indexOf('=') + 1)); - } - if (confgEntry.startsWith("timezone=")) { - scheduerExpression.timezone(confgEntry.substring(confgEntry.indexOf('=') + 1)); - } - - /* Start date */ - if (confgEntry.startsWith("start=")) { - SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd"); - Date convertedDate = dateFormat.parse(confgEntry.substring(confgEntry.indexOf('=') + 1)); - scheduerExpression.start(convertedDate); - } - - /* End date */ - if (confgEntry.startsWith("end=")) { - SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd"); - Date convertedDate = dateFormat.parse(confgEntry.substring(confgEntry.indexOf('=') + 1)); - scheduerExpression.end(convertedDate); - } - } + /** + * Create a calendar-based timer based on a input schedule expression. The + * expression will be parsed by this method. + * + * Example: + * second=0 + * minute=0 + * hour=* + * dayOfWeek= + * dayOfMonth= + * month= + * year=* + * + * + * @param sConfiguation + * @return + * @throws ParseException + */ + protected Timer createTimerOnCalendar(ItemCollection configItemCollection) throws ParseException { + + TimerConfig timerConfig = new TimerConfig(); + timerConfig.setInfo(configItemCollection.getUniqueID()); + + ScheduleExpression scheduerExpression = new ScheduleExpression(); + + @SuppressWarnings("unchecked") + List calendarConfiguation = configItemCollection.getItemValue(Scheduler.ITEM_SCHEDULER_DEFINITION); + // try to parse the configuration list.... + for (String confgEntry : calendarConfiguation) { + + if (confgEntry.startsWith("second=")) { + scheduerExpression.second(confgEntry.substring(confgEntry.indexOf('=') + 1)); + } + if (confgEntry.startsWith("minute=")) { + scheduerExpression.minute(confgEntry.substring(confgEntry.indexOf('=') + 1)); + } + if (confgEntry.startsWith("hour=")) { + scheduerExpression.hour(confgEntry.substring(confgEntry.indexOf('=') + 1)); + } + if (confgEntry.startsWith("dayOfWeek=")) { + scheduerExpression.dayOfWeek(confgEntry.substring(confgEntry.indexOf('=') + 1)); + } + if (confgEntry.startsWith("dayOfMonth=")) { + scheduerExpression.dayOfMonth(confgEntry.substring(confgEntry.indexOf('=') + 1)); + } + if (confgEntry.startsWith("month=")) { + scheduerExpression.month(confgEntry.substring(confgEntry.indexOf('=') + 1)); + } + if (confgEntry.startsWith("year=")) { + scheduerExpression.year(confgEntry.substring(confgEntry.indexOf('=') + 1)); + } + if (confgEntry.startsWith("timezone=")) { + scheduerExpression.timezone(confgEntry.substring(confgEntry.indexOf('=') + 1)); + } + + /* Start date */ + if (confgEntry.startsWith("start=")) { + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd"); + Date convertedDate = dateFormat.parse(confgEntry.substring(confgEntry.indexOf('=') + 1)); + scheduerExpression.start(convertedDate); + } + + /* End date */ + if (confgEntry.startsWith("end=")) { + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd"); + Date convertedDate = dateFormat.parse(confgEntry.substring(confgEntry.indexOf('=') + 1)); + scheduerExpression.end(convertedDate); + } - Timer timer = timerService.createCalendarTimer(scheduerExpression, timerConfig); + } - return timer; + Timer timer = timerService.createCalendarTimer(scheduerExpression, timerConfig); - } + return timer; + + } } diff --git a/src/site/markdown/contributing.md b/src/site/markdown/contributing.md index 2a1197404..add0f3432 100644 --- a/src/site/markdown/contributing.md +++ b/src/site/markdown/contributing.md @@ -6,10 +6,12 @@ Imixs-Workflow is an open source project and we sincerely invite you to particip ## Coding Guidelines -The code style used within the Imixs-Workflow project is based on the _[Google Java Style Guide](https://google.github.io/styleguide/javaguide.html)_: +The code style used within the Imixs-Workflow project is based on the Java Conventions with the following details: -- indentation -> 2 Spaces -- line length -> 100 +- tab policy = Spaces only +- indentation = 4 Spaces (do not use tabs) +- tab size = 4 columns +- line length = 120 Within this project we use Eclipse as the main development IDE. You can import the file 'imixs-code-style.xml' into the Eclipse IDE to get the actual formating rules (_Preferences -> Java-Code-Style Formatter Profile_).