Skip to content

Commit

Permalink
impl, refactoring, docu
Browse files Browse the repository at this point in the history
Issue #254
  • Loading branch information
rsoika committed May 24, 2023
1 parent 02066e2 commit ea11857
Show file tree
Hide file tree
Showing 7 changed files with 322 additions and 174 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@
********************************************************************************/
package org.openbpmn.extensions;

import java.nio.file.Path;

import org.openbpmn.bpmn.BPMNModel;
import org.openbpmn.bpmn.elements.core.BPMNElement;

/**
* The BPMNModelExtension is an extension point which allows you adapt the load
Expand All @@ -36,14 +39,29 @@ public interface BPMNModelExtension {
* An injected ModelState already holds the generated GModel Tree.
*
* @param model - the BPMNModel
* @param path - the file path the model is loaded from
*/
void onLoad(final BPMNModel model);
void onLoad(final BPMNModel model, final Path path);

/**
* Called before the BPMNModel is stored to disk.
*
* @param model - the BPMNModel
* @param path - the file path the model is loaded from
*/
void onSave(final BPMNModel model);
void onSave(final BPMNModel model, final Path path);

/**
* Returns the priority of this action handler. The priority is used to derive
* the execution order if multiple extension handlers should execute the same
* {@link BPMNElement}. The default priority is `0` and the priority is sorted
* descending. This means handlers with a priority > 0 are executed before
* handlers with a default priority and handlers with a priority < 0 are
* executed afterwards.
*
* @return the priority as integer.
*/
default int getPriority() {
return 0;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
/********************************************************************************
* Copyright (c) 2022 Imixs Software Solutions GmbH and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
package org.openbpmn.extensions.model;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.logging.Logger;

import org.openbpmn.bpmn.BPMNModel;
import org.openbpmn.bpmn.ModelNotification;
import org.openbpmn.extensions.BPMNModelExtension;
import org.w3c.dom.CDATASection;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
* The TimerEventDefinitionExtension is responsible to read and update optional
* TimerEventDefinitions from the BPMN model. The Extension builds a custom
* property section named 'Timers' shown a list of all TimerEventDefinitions
* define in a Event.
*
* @author rsoika
*/
public class FileLinkExtension implements BPMNModelExtension {
protected static Logger logger = Logger.getLogger(FileLinkExtension.class.getName());

@Override
public int getPriority() {
return 101;
}

/**
* This method is called during the load method from the
* BPMNModelFactory. The method resolves all elements with the attribute
* 'open-bpmn:file-link'
* e.g.
* <bpmn2:script id="script_1" open-bpmn:file-link="file://imixs.script.js">
* <![CDATA[file://imixs.script.js]]></bpmn2:script>
*
* The method compares the content of the corresponding file. In case of a
* mismatch we assume that the file content is actual and so we
* mark the model immediately as dirty.
*
* The method marks the model as dirty if linked file content has changed
*
* @param path - file path
* @return boolean - true if the linked file content has changed.
*/

@Override
public void onLoad(BPMNModel model, Path path) {
// Resolve the parent path
Path parent = path.getParent();
long l = System.currentTimeMillis();

// Find all elements with the attribute "x"
NodeList elements = model.getDoc().getElementsByTagName("*");
for (int i = 0; i < elements.getLength(); i++) {
Element element = (Element) elements.item(i);
if (element.hasAttribute("open-bpmn:file-link")) {
String fileLinkRelative = element.getAttribute("open-bpmn:file-link");
String fileLink = fileLinkRelative.substring(6);
fileLink = parent + fileLink;
Path pathLinkFileContent = Paths.get(fileLink);
try {
// read content...
logger.fine(element.getNodeName() + " has attribute open-bpmn:file-link: "
+ fileLink);

byte[] bytes = Files.readAllBytes(pathLinkFileContent);
String fileData = new String(bytes, StandardCharsets.UTF_8);
// Now we compare the content with the content of the CDATA Tag. If not equal we
// update the file!! Because we assume the .bpmn file is always right.
String bpmnContent = getElementContent(element);

if (!bpmnContent.equals(fileData)) {
logger.fine(
"File content of open-bpmn:file-link '" + fileLink + "' updated.");
// mark model as dirty
model.setDirty(true);
model.getNotifications().add(new ModelNotification(ModelNotification.Severity.WARNING,
"Linked file content was updated!",
"The linked file resource '" + fileLink
+ "' was updated. Model file should be saved!"));
}

// Now replace the content with the filename
while (element.hasChildNodes()) {
element.removeChild(element.getFirstChild());
}
// create new cdata section for this child node and add the content....
CDATASection cdataSection = model.getDoc().createCDATASection(fileLinkRelative);
element.appendChild(cdataSection);

} catch (IOException e) {
logger.warning(
"Failed to read content of open-bpmn:file-link '" + fileLink + "' : " + e.getMessage());
model.getNotifications().add(new ModelNotification(ModelNotification.Severity.WARNING,
"Failed to read linked file content '" + fileLinkRelative + "' !",
"Failed to read content of open-bpmn:file-link '" + fileLinkRelative + "' in element "
+ element.getAttribute("id")));
}
}
}
logger.fine("...resolveFileLinksOnLoad took " + (System.currentTimeMillis() - l) + "ms");
}

/**
* Helper method that gets the content of an element and supports an optional
* CDATA node
*
* @param element
* @return
*/
private String getElementContent(Element element) {
// search CDATA node
NodeList childNodes = element.getChildNodes();
for (int i = 0; i < childNodes.getLength(); i++) {
Node node = childNodes.item(i);
if (node instanceof CDATASection) {
return node.getNodeValue();
} else {
// normal text node
return node.getTextContent();
}
}
return "";
}

/**
* This helper method is called during the save method. The method resolves all
* elements with the attribute 'open-bpmn:file-link'
* e.g.
* <bpmn2:script id="script_1" open-bpmn:file-link="file://imixs.script.js">
* <![CDATA[file://imixs.script.js]]></bpmn2:script>
*
* The method opens the corresponding file and replaces the element content.
* If no file was found, the method prints a warning.
*/
@Override
public void onSave(BPMNModel model, final Path path) {
// Resolve the parent path
Path parent = path.getParent();
long l = System.currentTimeMillis();

// Find all elements with the attribute "x"
NodeList elements = model.getDoc().getElementsByTagName("*");
for (int i = 0; i < elements.getLength(); i++) {
Element element = (Element) elements.item(i);
if (element.hasAttribute("open-bpmn:file-link")) {
String fileLink = element.getAttribute("open-bpmn:file-link");
fileLink = fileLink.substring(6);
fileLink = parent + fileLink;
Path pathLinkFileContent = Paths.get(fileLink);
try {
// read content...
logger.fine(element.getNodeName() + " has attribute open-bpmn:file-link: "
+ fileLink);

byte[] bytes = Files.readAllBytes(pathLinkFileContent);
String fileData = new String(bytes, StandardCharsets.UTF_8);

// remove old sub_child nodes of this childNode...
while (element.hasChildNodes()) {
element.removeChild(element.getFirstChild());
}
// create new cdata section for this child node and add the content....
CDATASection cdataSection = model.getDoc().createCDATASection(fileData);
element.appendChild(cdataSection);

} catch (IOException e) {
// We do not create the file here and print just a warning. This is because
// if the user has activated autosave mode files will be created even if the
// user does not expect it.
// Files.createFile(pathLinkFileContent);
String message = "Missing resource open-bpmn:file-link '" + fileLink + "'!";
logger.warning(message);
model.getNotifications().add(new ModelNotification(ModelNotification.Severity.WARNING, message,
"The linked file resource '" + fileLink + "' does not exist!"));

}

}
}
logger.fine("...resolveFileLinksOnSave took " + (System.currentTimeMillis() - l) + "ms");

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import org.eclipse.glsp.server.operations.OperationHandler;
import org.openbpmn.extensions.BPMNCreateExtensionHandler;
import org.openbpmn.extensions.BPMNElementExtension;
import org.openbpmn.extensions.BPMNModelExtension;
import org.openbpmn.extensions.elements.ConditionalEventDefinitionExtension;
import org.openbpmn.extensions.elements.DefaultBPMNDataObjectExtension;
import org.openbpmn.extensions.elements.DefaultBPMNDefinitionsExtension;
Expand All @@ -47,6 +48,7 @@
import org.openbpmn.extensions.elements.LinkEventDefinitionExtension;
import org.openbpmn.extensions.elements.SignalEventDefinitionExtension;
import org.openbpmn.extensions.elements.TimerEventDefinitionExtension;
import org.openbpmn.extensions.model.FileLinkExtension;
import org.openbpmn.glsp.elements.data.BPMNApplyEditLabelOperationHandler;
import org.openbpmn.glsp.elements.data.BPMNCreateDataObjectHandler;
import org.openbpmn.glsp.elements.data.BPMNCreateMessageHandler;
Expand Down Expand Up @@ -89,7 +91,8 @@ public class BPMNDiagramModule extends DiagramModule {
@SuppressWarnings("unused")
private static Logger logger = Logger.getLogger(BPMNDiagramModule.class.getName());

private Multibinder<BPMNElementExtension> bpmnExtensionBinder;
private Multibinder<BPMNElementExtension> bpmnElementExtensionBinder;
private Multibinder<BPMNModelExtension> bpmnModelExtensionBinder;

@Override
protected Class<? extends GModelState> bindGModelState() {
Expand Down Expand Up @@ -204,8 +207,10 @@ protected void configureClientActions(MultiBinding<Action> binding) {
protected void configureAdditionals() {
super.configureAdditionals();
// create the BPMNExtension binder
bpmnExtensionBinder = Multibinder.newSetBinder(binder(), BPMNElementExtension.class);
configureBPMNExtensions(bpmnExtensionBinder);
bpmnElementExtensionBinder = Multibinder.newSetBinder(binder(), BPMNElementExtension.class);
configureBPMNElementExtensions(bpmnElementExtensionBinder);
bpmnModelExtensionBinder = Multibinder.newSetBinder(binder(), BPMNModelExtension.class);
configureBPMNModelExtensions(bpmnModelExtensionBinder);

}

Expand Down Expand Up @@ -248,14 +253,14 @@ public String getDiagramType() {
}

/**
* This method adds the BPMN default extensions
* This method adds the BPMN default element extensions
* <p>
* Overwrite this method to add custom BPMN Extensions
*
* @param binding
*/
public void configureBPMNExtensions(final Multibinder<BPMNElementExtension> binding) {
// bind BPMN default extensions
public void configureBPMNElementExtensions(final Multibinder<BPMNElementExtension> binding) {
// bind BPMN default element extensions
binding.addBinding().to(DefaultBPMNDefinitionsExtension.class);
binding.addBinding().to(DefaultBPMNEventExtension.class);
binding.addBinding().to(DefaultBPMNTaskExtension.class);
Expand All @@ -272,4 +277,18 @@ public void configureBPMNExtensions(final Multibinder<BPMNElementExtension> bind
binding.addBinding().to(LinkEventDefinitionExtension.class);

}

/**
* This method adds the BPMN default model extensions
* <p>
* Overwrite this method to add custom BPMN Extensions
*
* @param binding
*/
public void configureBPMNModelExtensions(final Multibinder<BPMNModelExtension> binding) {

// bind BPMN default model extensions
binding.addBinding().to(FileLinkExtension.class);

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,13 @@
import static org.eclipse.glsp.server.types.GLSPServerException.getOrThrow;

import java.io.File;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
Expand All @@ -34,6 +40,7 @@
import org.openbpmn.bpmn.BPMNModel;
import org.openbpmn.bpmn.exceptions.BPMNModelException;
import org.openbpmn.bpmn.util.BPMNModelFactory;
import org.openbpmn.extensions.BPMNModelExtension;
import org.openbpmn.glsp.BPMNDiagramConfiguration;
import org.openbpmn.glsp.utils.BPMNActionUtil;

Expand All @@ -58,6 +65,9 @@ public class BPMNSourceModelStorage implements SourceModelStorage {
@Inject
protected ActionDispatcher actionDispatcher;

@Inject
protected Set<BPMNModelExtension> extensions;

/**
* Loads a source model into the modelState.
*/
Expand All @@ -81,6 +91,19 @@ public void loadSourceModel(final RequestModelAction action) {
BPMNModel model;
try {
model = BPMNModelFactory.read(file);

// Apply BPMNModelExtensions
if (extensions != null) {
// sort extensions by priority
List<BPMNModelExtension> sortedExtensions = new ArrayList<>();
sortedExtensions.addAll(extensions);
Comparator<BPMNModelExtension> byPriority = Comparator.comparing(BPMNModelExtension::getPriority);
Collections.sort(sortedExtensions, byPriority);
for (BPMNModelExtension extension : sortedExtensions) {
extension.onLoad(model, Paths.get(file.getPath()));
}
}

// we store the BPMN meta model into the modelState
modelState.setBpmnModel(model);
// if the model is dirty (because linked-file content has change) we send a
Expand Down Expand Up @@ -140,6 +163,19 @@ public void saveSourceModel(final SaveModelAction action) {
logger.warn("saveSourceModel to : " + file);

BPMNModel model = modelState.getBpmnModel();

// Apply BPMNModelExtensions
if (extensions != null) {
// sort extensions by priority
List<BPMNModelExtension> sortedExtensions = new ArrayList<>();
sortedExtensions.addAll(extensions);
Comparator<BPMNModelExtension> byPriority = Comparator.comparing(BPMNModelExtension::getPriority);
Collections.sort(sortedExtensions, byPriority);
for (BPMNModelExtension extension : sortedExtensions) {
extension.onSave(model, Paths.get(file.getPath()));
}
}

model.save(file);

// process all model notifications...
Expand Down
Loading

0 comments on commit ea11857

Please sign in to comment.