blob: 8b5375b05e7610117e69b4d56f58efc872042d40 [file] [log] [blame]
/**
* Copyright (c) 2010 Henning Heitkoetter.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Henning Heitkoetter - initial API and implementation
*/
package org.eclipse.bpmn2.tests;
import static org.junit.Assert.*;
import java.io.File;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.xml.parsers.DocumentBuilderFactory;
import org.eclipse.bpmn2.BaseElement;
import org.eclipse.bpmn2.Bpmn2Factory;
import org.eclipse.bpmn2.Collaboration;
import org.eclipse.bpmn2.Definitions;
import org.eclipse.bpmn2.DocumentRoot;
import org.eclipse.bpmn2.EventDefinition;
import org.eclipse.bpmn2.Import;
import org.eclipse.bpmn2.LinkEventDefinition;
import org.eclipse.bpmn2.Process;
import org.eclipse.bpmn2.StartEvent;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.common.util.WrappedException;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.xmi.ClassNotFoundException;
import org.junit.Before;
import org.junit.Test;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
/**
* Tests our QName (de)resolution mechanism both for intra- and inter-model references.
*
* This test uses XML serialization (no QNames in XMI).
* @author Henning Heitkoetter
*
*/
public class QNameReferenceTest extends Bpmn2SerializationTest {
protected Definitions A_model, B_model;
protected Collaboration A_collab, B_collab;
protected Process A_process, B_process;
protected Resource A_resource, B_resource;
@Before
public void setUpModels() {
A_model = TestHelper.initBasicModel("urn:modelA");
B_model = TestHelper.initBasicModel("urn:modelB");
A_collab = Bpmn2Factory.eINSTANCE.createCollaboration();
A_model.getRootElements().add(A_collab);
A_process = Bpmn2Factory.eINSTANCE.createProcess();
A_model.getRootElements().add(A_process);
B_collab = Bpmn2Factory.eINSTANCE.createCollaboration();
B_model.getRootElements().add(B_collab);
B_process = Bpmn2Factory.eINSTANCE.createProcess();
B_model.getRootElements().add(B_process);
}
@Override
protected String getFileExtension() {
return EXTENSION_BPMN2_XML;
}
@Override
protected String getSubDirectory() {
return "qname";
}
/**
* Equivalent to {@link #saveAndLoadModels(String, boolean) saveAndLoadModels(name, false)}
*/
protected void saveAndLoadModels(String name) throws IOException {
saveAndLoadModels(name, false);
}
/**
* Equivalent to {@link #saveAndLoadModels(String, String, boolean, String) saveAndLoadModels(name"_A", name"_B", absolute, ...)}
*/
protected void saveAndLoadModels(String name, boolean absolute) throws IOException {
saveAndLoadModels(name + "_A", name + "_B", absolute, name + "_B." + getFileExtension());
}
/**
* Saves both models (A and B). Creates a import element in A pointing to B.
* Loads the created resources into {@link #A_resource} and {@link #B_resource}, respectively.
*
* @param nameA Part of the file name for model A that is used to identify different tests.
* @param nameB Part of the file name for model B.
* @param absolute <code>true</code>, if absolute URIs should be used to construct the resources.
* @param importLocation The value for the location attribute of the import element created (should be the location
* of B relative to A).
* @throws IOException
*/
protected void saveAndLoadModels(String nameA, String nameB, boolean absolute,
String importLocation) throws IOException {
Map<URI, EObject> uriToContentMap = new LinkedHashMap<URI, EObject>();
URI A_fileUri = getCompletePathURI(nameA, absolute);
URI B_fileUri = getCompletePathURI(nameB, absolute);
uriToContentMap.put(A_fileUri, A_model);
uriToContentMap.put(B_fileUri, B_model);
Import importBintoA = Bpmn2Factory.eINSTANCE.createImport();
importBintoA.setImportType("http://www.omg.org/spec/BPMN/20100524/MODEL");
importBintoA.setLocation(importLocation != null ? importLocation : B_fileUri.toString());
importBintoA.setNamespace("urn:modelB");
A_model.getImports().add(importBintoA);
TestHelper.createResourcesWithContent(uriToContentMap);
createdFiles.add(A_fileUri);
createdFiles.add(B_fileUri);
A_resource = TestHelper.getResource(A_fileUri);
B_resource = TestHelper.getResource(B_fileUri);
}
/**
* Asserts that obj can be resolved and resolves to expected.
* @param message Message if obj is a proxy (not resolved).
* @param obj The obj to check.
* @param expected obj is expected to be equivalent to this parameter, as
* determined by their {@linkplain BaseElement#getId() ID}.
*/
protected static void assertResolvesTo(String message, BaseElement obj, BaseElement expected) {
assertFalse(message, obj.eIsProxy());
assertEquals(expected.getId(), obj.getId());
}
@Test
public void testIntraModelReferencesRelative() throws Exception {
testIntraModelReferences(false);
}
@Test
public void testIntraModelReferencesAbsolute() throws Exception {
testIntraModelReferences(true);
}
/**
* Test proxy-resolving references (i.e. potentially cross-file references) when
* they point to objects within the same file.
* @param absolute Determines if the model is saved and loaded with an absolute or relative URI.
* @throws IOException
*/
public void testIntraModelReferences(boolean absolute) throws IOException {
// single @ XML attribute
A_process.setDefinitionalCollaborationRef(A_collab);
// many @ XML attribute - no cross-file reference available
// single @ XML element
LinkEventDefinition A_led1, A_led2;
A_led1 = Bpmn2Factory.eINSTANCE.createLinkEventDefinition();
A_model.getRootElements().add(A_led1);
A_led2 = Bpmn2Factory.eINSTANCE.createLinkEventDefinition();
A_model.getRootElements().add(A_led2);
A_led1.setTarget(A_led2);
// many @ XML element
Process A_process2 = Bpmn2Factory.eINSTANCE.createProcess();
A_model.getRootElements().add(A_process2);
A_process.getSupports().add(A_process2);
Resource res = saveAndLoadModel("intra" + (absolute ? "Abs" : "Rel"), A_model, absolute);
Process A_processNew = (Process) res.getEObject(A_process.getId());
LinkEventDefinition A_led1New = (LinkEventDefinition) res.getEObject(A_led1.getId());
assertResolvesTo("Proxy could not be resolved (single attr)",
A_processNew.getDefinitionalCollaborationRef(), A_collab);
assertResolvesTo("Proxy could not be resolved (single elem)", A_led1New.getTarget(), A_led2);
assertResolvesTo("Proxy could not be resolved (many elem)",
A_processNew.getSupports().get(0), A_process2);
}
/**
* Test a proxy-resolving intra-file reference with an abstract type.
* @throws Exception
*/
@Test
public void testReferenceToAbstractType() throws Exception {
EventDefinition A_eventDef = Bpmn2Factory.eINSTANCE.createSignalEventDefinition();
A_model.getRootElements().add(A_eventDef);
StartEvent A_event = Bpmn2Factory.eINSTANCE.createStartEvent();
A_event.getEventDefinitionRefs().add(A_eventDef);
A_process.getFlowElements().add(A_event);
try {
Resource res = saveAndLoadModel("toAbstract", A_model);
StartEvent A_eventNew = (StartEvent) res.getEObject(A_event.getId());
assertResolvesTo("Proxy could not be resolved (abstract)", A_eventNew
.getEventDefinitionRefs().get(0), A_eventDef);
} catch (WrappedException e) {
if (e.exception() instanceof ClassNotFoundException)
fail("Class EventDefinition was recognized as abstract.");
else
throw e;
} catch (IllegalArgumentException e) {
// different error in Eclipse 3.4
if (e.getMessage().endsWith("not a valid classifier"))
fail("Class EventDefinition was recognized as abstract.");
else
throw e;
}
}
@Test
public void testInterModelReferencesRelative() throws IOException {
testInterModelReferences(false);
}
@Test
public void testInterModelReferencesAbsolute() throws IOException {
testInterModelReferences(true);
}
/**
* Test cross-file references.
* @param absolute Determines if the models are saved and loaded with absolute or relative URIs.
* @throws IOException
*/
public void testInterModelReferences(boolean absolute) throws IOException {
// single @ XML attribute
A_process.setDefinitionalCollaborationRef(B_collab);
// many @ XML attribute - no cross-file reference available
// single @ XML element
LinkEventDefinition A_led, B_led;
A_led = Bpmn2Factory.eINSTANCE.createLinkEventDefinition();
A_model.getRootElements().add(A_led);
B_led = Bpmn2Factory.eINSTANCE.createLinkEventDefinition();
B_model.getRootElements().add(B_led);
A_led.setTarget(B_led);
// many @ XML element
A_process.getSupports().add(B_process);
saveAndLoadModels("inter" + (absolute ? "Abs" : "Rel"), absolute);
Process A_processNew = (Process) A_resource.getEObject(A_process.getId());
LinkEventDefinition A_ledNew = (LinkEventDefinition) A_resource.getEObject(A_led.getId());
assertResolvesTo("Proxy could not be resolved (single attr)",
A_processNew.getDefinitionalCollaborationRef(), B_collab);
assertResolvesTo("Proxy could not be resolved (single elem)", A_ledNew.getTarget(), B_led);
assertResolvesTo("Proxy could not be resolved (many elem)",
A_processNew.getSupports().get(0), B_process);
}
@Test
public void testQNameSerialization() throws Exception {
A_process.setId("A_process_id");
A_process.setDefinitionalCollaborationRef(A_collab);
A_process.getSupports().add(B_process);
saveAndLoadModels("serialization");
DocumentBuilderFactory fact = DocumentBuilderFactory.newInstance();
Document xml = fact.newDocumentBuilder()
.parse(new File(A_resource.getURI().toFileString()));
Node pNode = xml.getElementsByTagName("bpmn2:process").item(0);
String ref = pNode.getAttributes().getNamedItem("definitionalCollaborationRef")
.getNodeValue();
String prefixLocal = ref.indexOf(":") == -1 ? "" : ref.split(":")[0];
assertEquals("The prefix for local references is not mapped to the target namespace.",
A_model.getTargetNamespace(), ((DocumentRoot) A_resource.getContents().get(0))
.getXMLNSPrefixMap().get(prefixLocal));
ref = pNode.getChildNodes().item(1).getTextContent();
String prefixModelB = ref.indexOf(":") == -1 ? "" : ref.split(":")[0];
assertEquals("The prefix for references to model b is not mapped to its target namespace.",
B_model.getTargetNamespace(), ((DocumentRoot) A_resource.getContents().get(0))
.getXMLNSPrefixMap().get(prefixModelB));
}
/**
* Tests the standard case for the mapping of prefixes to namespaces:
* - the default namespace is set to the target namespace and local references do not use a prefix
* - cross-file references use a prefix associated with an imported namespace
*/
@Test
public void testPrefixNamespaceMapping() {
testQNameResolve("normal", true);
}
/**
* Tests the inverse case for the mapping of prefixes to namespaces:
* - an explicit prefix is associated with the target namespace and used in local references
* - the default namespace point to an imported namespace and cross-file reference do not use a prefix
*/
@Test
public void testPrefixNamespaceMappingInverse() {
testQNameResolve("inverse", true);
}
/**
* Tests the special case for the mapping of prefixes to namespaces:
* - neither a default namespace nor a target namespace are defined, unprefixed QNames thus point to local elements
*/
@Test
public void testPrefixNamespaceMappingNoNs() {
testQNameResolve("noNs", false);
}
/**
* Tests the special case for the mapping of prefixes to namespaces:
* - no default namespace but a target namespace is defined, unprefixed QNames are invalid (strict interpretation)
* - chosen fallback behavior: unprefixed QNames should be interpreted as references to local elements
*/
@Test
public void testPrefixNamespaceMappingNoDefaultButTarget() {
testQNameResolve("noDefaultButTarget", false);
}
/**
* Tests the special case for the mapping of prefixes to namespaces:
* - the default namespace is neither the target namespace nor mapped by an import element
* - strict interpretation: unprefixed QNames can not be resolved
* - chosen fallback behavior (?): unprefixed QNames should be interpreted as references to local elements
* -- guideline: treat unprefixed QNames as local (guess), unless default namespace is mapped by import element
*/
@Test
public void testPrefixNamespaceMappingDefaultNotResolvable() {
testQNameResolve("defaultNotResolvable", true);
}
/**
* Tests if QNames are correctly resolved in the specified test case.
* @param testCase Identifier of the test case, pointing to a BPMN2 file
* under 'res/qname_prefixNs_<i>testCase</i>.bpmn2' (with defined content).
* @param crossFile <code>true</code>, if this test case features a cross-file reference
* to be checked (Process.supports).
*/
protected void testQNameResolve(String testCase, boolean crossFile) {
Resource res = TestHelper.getResource(URI.createFileURI("res/qname_prefixNs_" + testCase
+ ".bpmn2"));
Process p = (Process) res.getEObject("proc1Id");
assertFalse("Resolution of local reference failed, test case: " + testCase, p
.getDefinitionalCollaborationRef().eIsProxy());
if (crossFile)
assertFalse("Resolution of cross-file reference failed, test case: " + testCase, p
.getSupports().get(0).eIsProxy());
}
/**
* Tests the automated generation of prefixes in case multiple imports would use the same prefix (as
* automatically generated from the namespace).
* @throws Exception
*/
@Test
public void testPrefixClash() throws Exception {
Definitions C_model = TestHelper.initBasicModel("urn:path:modelB"); // same ns ending as B_model
Process C_process = Bpmn2Factory.eINSTANCE.createProcess();
C_model.getRootElements().add(C_process);
Resource C_resource = saveAndLoadModel("prefixClash_C", C_model);
Import importCintoA = Bpmn2Factory.eINSTANCE.createImport();
importCintoA.setImportType("http://www.omg.org/spec/BPMN/20100524/MODEL");
importCintoA.setLocation("prefixClash_C.bpmn2");
importCintoA.setNamespace(C_model.getTargetNamespace());
A_model.getImports().add(importCintoA);
A_process.getSupports().add(B_process);
A_process.getSupports().add(C_process);
saveAndLoadModels("prefixClash");
Process A_processNew = (Process) A_resource.getEObject(A_process.getId());
assertResolvesTo("Reference to element of model B could not be resolved", A_processNew
.getSupports().get(0), B_process);
assertResolvesTo("Reference to element of model C could not be resolved", A_processNew
.getSupports().get(1), C_process);
}
/*
* Tests with different scenarios for the location attribute of import elements
*/
@Test
public void testImportLocationSubfolderRelative() throws Exception {
testImportLocationSubfolder(false);
}
@Test
public void testImportLocationSubfolderAbsolute() throws Exception {
testImportLocationSubfolder(true);
}
/**
* A --> folder/B
*/
public void testImportLocationSubfolder(boolean absolute) throws Exception {
String suffix = absolute ? "_Abs" : "_Rel";
testImportLocation("importSub_A" + suffix, "sub/importSub_B" + suffix, absolute,
"sub/importSub_B" + suffix + ".bpmn2");
}
/**
* Redundant path segments in import location ("./")
*/
@Test
public void testImportRedundant() throws Exception {
testImportLocation("importRedundant_A", "importRedundant_B", false,
"./importRedundant_B.bpmn2");
}
/**
* Redundant path segments in resource uri ("./")
*/
@Test
public void testImportRedundantResource() throws Exception {
testImportLocation("importRedundant_A", "./importRedundant_B", false,
"importRedundant_B.bpmn2");
}
@Test
public void testImportLocationParentFolderRelative() throws Exception {
testImportLocationParentFolder(false);
}
@Test
public void testImportLocationParentFolderAbsolute() throws Exception {
testImportLocationParentFolder(true);
}
/**
* A --> ../B
*/
public void testImportLocationParentFolder(boolean absolute) throws Exception {
String suffix = absolute ? "_Abs" : "_Rel";
testImportLocation("sub/importParent_A" + suffix, "importParent_B" + suffix, absolute,
"../importParent_B" + suffix + ".bpmn2");
}
@Test
public void testImportLocationNestedFolderRelative() throws Exception {
testImportLocationNestedFolder(false);
}
@Test
public void testImportLocationNestedFolderAbsolute() throws Exception {
testImportLocationNestedFolder(true);
}
/**
* A --> ../folder/B
*/
public void testImportLocationNestedFolder(boolean absolute) throws Exception {
String suffix = absolute ? "_Abs" : "_Rel";
testImportLocation("subA/importNested_A" + suffix, "subB/importNested_B" + suffix,
absolute, "../subB/importNested_B" + suffix + ".bpmn2");
}
@Test
public void testImportLocationCompletePathRelative() throws Exception {
testImportLocationCompletePath(false);
}
@Test
public void testImportLocationCompletePathAbsolute() throws Exception {
testImportLocationCompletePath(true);
}
/**
* A --> /absolute/Path/To/B
*/
public void testImportLocationCompletePath(boolean absolute) throws Exception {
String suffix = absolute ? "_Abs" : "_Rel";
testImportLocation("importComplete_A" + suffix, "importComplete_B" + suffix, absolute,
getCompletePathURI("importComplete_B" + suffix, true).toString()); // TODO: toString (file:/E:/path/to/B.bpmn2) or toFileString (E:\path\to\B.bpmn2) ??
}
public void testImportLocation(String nameA, String nameB, boolean absolute,
String importLocation) throws Exception {
A_process.setDefinitionalCollaborationRef(B_collab);
saveAndLoadModels(nameA, nameB, absolute, importLocation);
Process A_processNew = (Process) A_resource.getEObject(A_process.getId());
assertResolvesTo("Proxy could not be resolved, import location: " + importLocation,
A_processNew.getDefinitionalCollaborationRef(), B_collab);
}
@Test
public void testImportElementCreation() throws Exception {
A_process.setDefinitionalCollaborationRef(B_collab);
String importLocation = "sub/importElementCreation_B";
B_resource = saveAndLoadModel(importLocation, B_model);
A_resource = saveAndLoadModel("importElementCreation_A", A_model);
Definitions A_modelNew = (Definitions) A_resource.getEObject(A_model.getId());
assertFalse("No import element was generated", A_modelNew.getImports().isEmpty());
Import newImport = A_modelNew.getImports().get(0);
assertEquals("Wrong namespace of import element", B_model.getTargetNamespace(),
newImport.getNamespace());
assertEquals("Wrong relative location of import element", importLocation + "."
+ getFileExtension(), newImport.getLocation());
Process A_processNew = (Process) A_resource.getEObject(A_process.getId());
assertResolvesTo("Proxy could not be resolved (wrong import location?)",
A_processNew.getDefinitionalCollaborationRef(), B_collab);
}
}