/** * 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 v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * 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.List; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.eclipse.bpmn2.Bpmn2Factory; import org.eclipse.bpmn2.Bpmn2Package; import org.eclipse.bpmn2.Category; import org.eclipse.bpmn2.CategoryValue; import org.eclipse.bpmn2.Collaboration; import org.eclipse.bpmn2.ConditionalEventDefinition; import org.eclipse.bpmn2.ConversationLink; import org.eclipse.bpmn2.Definitions; import org.eclipse.bpmn2.DocumentRoot; import org.eclipse.bpmn2.Documentation; import org.eclipse.bpmn2.FlowElement; import org.eclipse.bpmn2.FormalExpression; import org.eclipse.bpmn2.Import; import org.eclipse.bpmn2.ItemDefinition; import org.eclipse.bpmn2.Lane; import org.eclipse.bpmn2.LaneSet; import org.eclipse.bpmn2.Participant; import org.eclipse.bpmn2.Process; import org.eclipse.bpmn2.RootElement; import org.eclipse.bpmn2.ScriptTask; import org.eclipse.bpmn2.Task; import org.eclipse.bpmn2.util.NamespaceHelper; 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.EcoreFactory; import org.eclipse.emf.ecore.EcorePackage; import org.eclipse.emf.ecore.InternalEObject; import org.eclipse.emf.ecore.impl.DynamicEObjectImpl; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.xmi.ClassNotFoundException; import org.eclipse.emf.ecore.xmi.FeatureNotFoundException; import org.eclipse.emf.ecore.xml.type.SimpleAnyType; import org.eclipse.emf.ecore.xml.type.XMLTypeFactory; import org.eclipse.emf.ecore.xml.type.XMLTypePackage; import org.junit.Before; import org.junit.Test; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; /** * Tests serialization as XML. * @author Henning Heitkoetter * */ public class XMLSerializationTest extends Bpmn2SerializationTest { protected Definitions model; /** * Prepares a test run by initializing all fields. * * A basic BPMN2 model is created in {@link #model}, thereby initializing the BPMN2 package. */ @Before public void setUpModel() { model = TestHelper.initBasicModel("urn:tns1"); } /** * The extension for all files that are created. * @return File extension, i.e. {@code "bpmn2"}. */ @Override protected String getFileExtension() { return EXTENSION_BPMN2_XML; } @Override protected String getSubDirectory() { return "xml"; } /** * Checks if serialization works at all. * @throws Exception */ @Test public void testBasicSerialization() throws Exception { Resource res = saveAndLoadModel("basic", model); assertTrue("Resource loaded with errors", res.getErrors().isEmpty()); checkBasicSerialization(res); } /** * Performs the actual checks if the basic serialization succeeded. * * Can be overriden by subclasses. * @param res */ protected void checkBasicSerialization(Resource res) { EObject root = res.getContents().get(0); if (root instanceof DocumentRoot) { DocumentRoot docRoot = (DocumentRoot) root; assertTrue("Namespace prefix bpmn2 not present", docRoot.getXMLNSPrefixMap() .containsKey(Bpmn2Package.eNS_PREFIX)); String NS_URI_EXPECTED = Bpmn2Package.eNS_URI.endsWith("-XMI") ? NamespaceHelper .xmiToXsdNamespaceUri(Bpmn2Package.eNS_URI) : Bpmn2Package.eNS_URI; assertEquals("Namespace URI of prefix bpmn2", NS_URI_EXPECTED, docRoot .getXMLNSPrefixMap().get(Bpmn2Package.eNS_PREFIX)); assertNotNull("No definitions object in doc root", docRoot.getDefinitions()); } else fail("Root element is not DocumentRoot"); } /** * Tests if an ID is generated upon save if necessary. * @throws Exception */ @Test public void testIdSerialization() throws Exception { Collaboration c = Bpmn2Factory.eINSTANCE.createCollaboration(); c.setName("collab1"); Process p = Bpmn2Factory.eINSTANCE.createProcess(); p.setDefinitionalCollaborationRef(c); model.getRootElements().add(c); model.getRootElements().add(p); Resource res = saveAndLoadModel("idOK", model); Definitions d = TestHelper.getRootDefinitionElement(res); // Technically, only collab1 needs to have an ID, because it is referenced by another element for (RootElement cur : d.getRootElements()) if (cur instanceof Collaboration && ((Collaboration) cur).getName().equals("collab1")) { assertNotNull( "No id generated for element \"collab1\", although it is referenced by another element", cur.getId()); break; } } /** * Asserts that no ID is generated for elements that don't have a corresponding feature. * @throws IOException */ @Test public void testNoIDForImport() throws IOException { model.getImports().add(Bpmn2Factory.eINSTANCE.createImport()); try { saveAndLoadModel("noIDForImport", model); } catch (WrappedException e) { if (e.exception() instanceof FeatureNotFoundException) { FeatureNotFoundException fnfe = ((FeatureNotFoundException) e.exception()); if (fnfe.getName().equals("id")) { fail("ID was generated for an import element (Import does not have an ID feature)"); } } else throw e; } } @Test public void testIDAlreadySet() throws Exception { model.setId("id1"); Resource res = null; try { res = saveAndLoadModel("idAlreadySet", model); } catch (WrappedException e) { if (e.exception() instanceof SAXParseException) fail("Duplicate attribute 'id'."); else throw e; } assertEquals("id1", TestHelper.getRootDefinitionElement(res).getId()); } @Test public void testDocumentationText() throws Exception { final String docId = "doc1"; final String docText = "Documentation text"; Process p = Bpmn2Factory.eINSTANCE.createProcess(); p.setName("Name"); Documentation doc = Bpmn2Factory.eINSTANCE.createDocumentation(); doc.setText(docText); doc.setId(docId); p.getDocumentation().add(doc); model.getRootElements().add(p); Resource res = saveAndLoadModel("documentationText", model); EObject docLoaded = res.getEObject(docId); assertTrue(docLoaded instanceof Documentation); assertEquals(docText, ((Documentation) docLoaded).getText()); checkSerializationDocText(res); } protected void checkSerializationDocText(Resource res) throws SAXException, IOException, ParserConfigurationException { DocumentBuilderFactory fact = DocumentBuilderFactory.newInstance(); Document xml = fact.newDocumentBuilder().parse(new File(res.getURI().toFileString())); Node docNode = xml.getElementsByTagName("bpmn2:documentation").item(0); assertNull("Documentation has an attribute 'text' (invalid acc. to XML schema)", docNode .getAttributes().getNamedItem("text")); } @Test public void testScript() throws Exception { final String scriptId = "st1"; final String scriptContent = "Script content"; Process p = Bpmn2Factory.eINSTANCE.createProcess(); p.setName("Name"); ScriptTask st = Bpmn2Factory.eINSTANCE.createScriptTask(); st.setId(scriptId); st.setName("Name"); st.setScript(scriptContent); st.setScriptFormat("Script format"); p.getFlowElements().add(st); model.getRootElements().add(p); Resource res = saveAndLoadModel("scriptContent", model); EObject stLoaded = res.getEObject(scriptId); assertTrue(stLoaded instanceof ScriptTask); assertEquals(scriptContent, ((ScriptTask) stLoaded).getScript()); checkSerializationScriptContent(res); } protected void checkSerializationScriptContent(Resource res) throws SAXException, IOException, ParserConfigurationException { DocumentBuilderFactory fact = DocumentBuilderFactory.newInstance(); Document xml = fact.newDocumentBuilder().parse(new File(res.getURI().toFileString())); Node scriptNode = xml.getElementsByTagName("bpmn2:scriptTask").item(0); assertNull("ScriptTask has an attribute 'script' (invalid acc. to XML schema)", scriptNode .getAttributes().getNamedItem("script")); } @Test public void testFormalExpressionBody() throws Exception { final String feId = "fe1"; final String feBody = "${script}"; ConditionalEventDefinition ced = Bpmn2Factory.eINSTANCE.createConditionalEventDefinition(); FormalExpression fe = Bpmn2Factory.eINSTANCE.createFormalExpression(); fe.setBody(feBody); fe.setId(feId); ced.setCondition(fe); model.getRootElements().add(ced); Resource res = saveAndLoadModel("formalExpBody", model); EObject docLoaded = res.getEObject(feId); assertTrue(docLoaded instanceof FormalExpression); assertEquals(feBody, ((FormalExpression) docLoaded).getBody()); checkSerializationFormalExpBody(res); } protected void checkSerializationFormalExpBody(Resource res) throws SAXException, IOException, ParserConfigurationException { DocumentBuilderFactory fact = DocumentBuilderFactory.newInstance(); Document xml = fact.newDocumentBuilder().parse(new File(res.getURI().toFileString())); Node docNode = xml.getElementsByTagName("bpmn2:condition").item(0); assertNull("FormalExpression has an attribute 'body' (invalid acc. to XML schema)", docNode .getAttributes().getNamedItem("body")); } @Test public void testItemDefinitionXSD() throws Exception { /* * TODO: This test FAILS currently (XSD&XMI). * XSD-EMF does not support lookup by ID - wrong element (whole schema) resolved. * We need to handle this to not lose the original reference. */ testItemDefinition("res/DataDefinitions.xsd", "http://www.example.org/Messages", "reqForQuot", "xsd"); } @Test public void testItemDefinitionBPMN() throws Exception { // should resolve to Definitions element testItemDefinition("res/basic.bpmn2", "urn:basic", "_DqjcgKCwEd-AVfJ3FSSMwg", "bpmn"); } @Test public void testItemDefinitionElement() throws Exception { // does not exist, should be handled gracefully testItemDefinition("test.abc", "urn:test", "id", "element"); } /** * Tests the structureRef feature of ItemDefinition. * * @param location Location of the file in which the structure element is located. * @param namespace Namespace for the import element pointing to the file. * @param id ID of the structure. * @param name Name of the test, used in the filename of the resource. * @throws Exception */ public void testItemDefinition(String location, String namespace, String id, String name) throws Exception { Import xsdImport = Bpmn2Factory.eINSTANCE.createImport(); xsdImport.setNamespace(namespace); xsdImport.setLocation("../../" + location); // Relative to resource during test run. model.getImports().add(xsdImport); ItemDefinition xsdItem = Bpmn2Factory.eINSTANCE.createItemDefinition(); final String xsdItemId = "xsdItemID"; xsdItem.setId(xsdItemId); InternalEObject value = new DynamicEObjectImpl(); final URI uri = URI.createURI(location + "#" + id); value.eSetProxyURI(uri); xsdItem.setStructureRef(value); model.getRootElements().add(xsdItem); Resource res = saveAndLoadModel("itemDef_" + name, model); ItemDefinition xsdItemNew = (ItemDefinition) res.getEObject(xsdItemId); checkStructureRef(uri, xsdItemNew); } /** * Checks if the structureRef feature of itemDef points (after resolution) to the given URI. * @param uriExpected The expected URI. * @param itemDef The Item Definition. */ protected void checkStructureRef(final URI uriExpected, ItemDefinition itemDef) { final InternalEObject xsdStructure = (InternalEObject) itemDef.getStructureRef(); assertNotNull(xsdStructure); if (xsdStructure.eIsProxy()) assertTrue(String.format("Proxy and expected URI differ (expected: %s, actual: %s)", uriExpected, xsdStructure.eProxyURI()), xsdStructure.eProxyURI().toString() .endsWith(uriExpected.toString())); else { final Resource res = xsdStructure.eResource(); URI actual = res.getURI().appendFragment(res.getURIFragment(xsdStructure)); assertTrue(String.format("Actual and expected URI differ (expected: %s, actual: %s)", uriExpected, actual), actual.toString().endsWith(uriExpected.toString())); } } @Test public void testOppositeReferenceCategoryValue() throws Exception { Category cat = Bpmn2Factory.eINSTANCE.createCategory(); CategoryValue catValue = Bpmn2Factory.eINSTANCE.createCategoryValue(); cat.getCategoryValue().add(catValue); model.getRootElements().add(cat); Process proc = Bpmn2Factory.eINSTANCE.createProcess(); Task task = Bpmn2Factory.eINSTANCE.createTask(); proc.getFlowElements().add(task); model.getRootElements().add(proc); task.getCategoryValueRef().add(catValue); saveAndLoadModel("oppositeRefCategoryValue", model); List<FlowElement> result = null; try { result = catValue.getCategorizedFlowElements(); } catch (UnsupportedOperationException e) { fail("getCategorizedFlowElements not implemented"); } assertTrue("Task not found in list", result.contains(task)); } @Test public void testOppositeReferenceInteractionNode() throws Exception { Collaboration collab = Bpmn2Factory.eINSTANCE.createCollaboration(); Participant part1 = Bpmn2Factory.eINSTANCE.createParticipant(); collab.getParticipants().add(part1); Participant part2 = Bpmn2Factory.eINSTANCE.createParticipant(); collab.getParticipants().add(part2); model.getRootElements().add(collab); ConversationLink link1 = Bpmn2Factory.eINSTANCE.createConversationLink(); link1.setSourceRef(part1); link1.setTargetRef(part2); collab.getConversationLinks().add(link1); ConversationLink link2 = Bpmn2Factory.eINSTANCE.createConversationLink(); link2.setTargetRef(part1); link2.setSourceRef(part2); collab.getConversationLinks().add(link2); saveAndLoadModel("oppositeRefInteractionNode", model); try { List<ConversationLink> tmp = part1.getIncomingConversationLinks(); assertTrue(tmp.size() == 1 && tmp.contains(link2)); tmp = part1.getOutgoingConversationLinks(); assertTrue(tmp.size() == 1 && tmp.contains(link1)); tmp = part2.getIncomingConversationLinks(); assertTrue(tmp.size() == 1 && tmp.contains(link1)); tmp = part2.getOutgoingConversationLinks(); assertTrue(tmp.size() == 1 && tmp.contains(link2)); } catch (UnsupportedOperationException e) { fail("getIncoming/OutgoingConversationLinks not implemented"); } } @Test public void testIDReferenceToAbstractType() throws Exception { Process p = Bpmn2Factory.eINSTANCE.createProcess(); model.getRootElements().add(p); Task t = Bpmn2Factory.eINSTANCE.createTask(); p.getFlowElements().add(t); LaneSet ls = Bpmn2Factory.eINSTANCE.createLaneSet(); p.getLaneSets().add(ls); Lane l = Bpmn2Factory.eINSTANCE.createLane(); String laneId = "laneId"; l.setId(laneId); ls.getLanes().add(l); l.getFlowNodeRefs().add(t); try { Resource res = saveAndLoadModel("idRefToAbstract", model); Lane lNew = (Lane) res.getEObject(laneId); assertTrue(lNew.getFlowNodeRefs().size() > 0); assertFalse(lNew.getFlowNodeRefs().get(0).eIsProxy()); assertEquals(t.getId(), lNew.getFlowNodeRefs().get(0).getId()); } 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; } } }