org.activiti.engine.test.api.repository.diagram.ProcessDiagramRetrievalTest.java Source code

Java tutorial

Introduction

Here is the source code for org.activiti.engine.test.api.repository.diagram.ProcessDiagramRetrievalTest.java

Source

/* Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.activiti.engine.test.api.repository.diagram;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.io.BufferedOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import org.activiti.engine.RepositoryService;
import org.activiti.engine.impl.bpmn.diagram.ProcessDiagramLayoutFactory;
import org.activiti.engine.repository.DiagramElement;
import org.activiti.engine.repository.DiagramLayout;
import org.activiti.engine.repository.DiagramNode;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.repository.ProcessDefinitionQuery;
import org.activiti.engine.test.ActivitiRule;
import org.apache.commons.io.FileUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;

/**
 * Tests process model and diagram retrieval features of the
 * {@link RepositoryService}.
 * 
 * This test generates HTML code containing the positions and dimensions of all
 * elements in a BPMN process and compares that to HTML files stored in
 * 'src/test/resources/org/activiti/engine/test/api/repository/diagram'.
 * 
 * If the expected HTML code needs to be changed due to changes in
 * {@link ProcessDiagramLayoutFactory}, the files can be regenerated by running the
 * test while {@link ProcessDiagramRetrievalTest#OVERWRITE_EXPECTED_HTML_FILES}
 * is set to true.
 * 
 * @author Falko Menge
 */
@RunWith(Parameterized.class)
public class ProcessDiagramRetrievalTest {

    /**
     * Set this to true and run the tests to regenerate the HTML files located in
     * src/test/resources/org/activiti/engine/test/api/repository/diagram, which
     * contain expected values for the HTML code generated by test cases.
     */
    private static final boolean OVERWRITE_EXPECTED_HTML_FILES = false;

    @Rule
    public ActivitiRule activitiRule = new ActivitiRule();

    /**
     * Provides a list of parameters for
     * {@link ProcessDiagramRetrievalTest#ProcessDiagramRetrievalTest(String, String, String, String)}
     */
    @Parameters
    public static Collection<Object[]> data() {
        return Arrays.asList(new Object[][] {
                { "testStartEventWithNegativeCoordinates", ".bpmn", ".png",
                        "sid-61D1FC47-8031-4834-A9B4-84158E73F7B9" },
                { "testStartAndEndEventWithNegativeCoordinates", ".bpmn", ".png",
                        "sid-61D1FC47-8031-4834-A9B4-84158E73F7B9" },
                { "testProcessWithTask", ".bpmn", ".png", "sid-1E142B16-AFAF-429E-A441-D1232CFBD560" },
                { "testProcessFromCamundaFoxDesigner", ".bpmn", ".png", "UserTask_1" },
                { "testProcessFromCamundaFoxDesigner", ".bpmn", ".jpg", "UserTask_1" },
                { "testProcessFromActivitiDesigner", ".bpmn20.xml", ".png",
                        "Send_rejection_notification_via_email__3" },
                { "testSequenceFlowOutOfBounds", ".bpmn", ".png", "sid-61D1FC47-8031-4834-A9B4-84158E73F7B9" },
                { "testProcessFromAdonis", ".bpmn", ".png", "_16615" },
                { "testProcessFromIboPrometheus", ".bpmn", ".png", "ibo-5784efbe-35ac-44bc-bcbe-4c18a2f23d5d" },
                { "testProcessFromIboPrometheus", ".bpmn", ".jpg", "ibo-5784efbe-35ac-44bc-bcbe-4c18a2f23d5d" },
                { "testInvoiceProcessCamundaFoxDesigner", ".bpmn20.xml", ".jpg", "Rechnung_freigeben_125" },
                { "testInvoiceProcessSignavio", ".bpmn", ".png", "Freigebenden_zuordnen_143" },
                { "testInvoiceProcessFromBusinessProcessIncubator", ".bpmn", ".png", "Rechnung_kl_ren_148" },
                { "testProcessFromYaoqiang", ".bpmn", ".png", "_3" }, });
    }

    private final String xmlFileName;
    private final String imageFileName;
    private final String highlightedActivityId;
    private RepositoryService repositoryService;
    private String deploymentId;

    private ProcessDefinitionQuery processDefinitionQuery;

    public ProcessDiagramRetrievalTest(String modelName, String xmlFileExtension, String imageFileExtension,
            String highlightedActivityId) {
        this.xmlFileName = modelName + xmlFileExtension;
        this.imageFileName = modelName + imageFileExtension;
        this.highlightedActivityId = highlightedActivityId;
    }

    @Before
    public void setup() {
        repositoryService = activitiRule.getRepositoryService();
        deploymentId = repositoryService.createDeployment()
                .addClasspathResource("org/activiti/engine/test/api/repository/diagram/" + xmlFileName)
                .addClasspathResource("org/activiti/engine/test/api/repository/diagram/" + imageFileName).deploy()
                .getId();
        processDefinitionQuery = repositoryService.createProcessDefinitionQuery();
    }

    @After
    public void teardown() {
        repositoryService.deleteDeployment(deploymentId, true);
    }

    /**
     * Tests {@link RepositoryService#getProcessModel(String)}.
     */
    @Test
    public void testGetProcessModel() throws Exception {
        if (1 == processDefinitionQuery.count()) {
            ProcessDefinition processDefinition = processDefinitionQuery.singleResult();
            InputStream expectedStream = new FileInputStream(
                    "src/test/resources/org/activiti/engine/test/api/repository/diagram/" + xmlFileName);
            InputStream actualStream = repositoryService.getProcessModel(processDefinition.getId());
            assertTrue(isEqual(expectedStream, actualStream));
        } else {
            // some test diagrams do not contain executable processes
            // and are therefore ignored by the engine
        }
    }

    /**
     * Tests {@link RepositoryService#getProcessDiagram(String)}.
     */
    @Test
    public void testGetProcessDiagram() throws Exception {
        if (1 == processDefinitionQuery.count()) {
            ProcessDefinition processDefinition = processDefinitionQuery.singleResult();
            InputStream expectedStream = new FileInputStream(
                    "src/test/resources/org/activiti/engine/test/api/repository/diagram/" + imageFileName);
            InputStream actualStream = repositoryService.getProcessDiagram(processDefinition.getId());
            //      writeToFile(repositoryService.getProcessDiagram(processDefinition.getId()),
            //              new File("src/test/resources/org/activiti/engine/test/api/repository/diagram/" + imageFileName + ".actual.png"));
            assertTrue(isEqual(expectedStream, actualStream));
        } else {
            // some test diagrams do not contain executable processes
            // and are therefore ignored by the engine
        }
    }

    /**
     * Tests {@link RepositoryService#getProcessDiagramLayout(String)} and
     * {@link ProcessDiagramLayoutFactory#getProcessDiagramLayout(InputStream, InputStream)}.
     */
    @Test
    public void testGetProcessDiagramLayout() throws Exception {
        DiagramLayout processDiagramLayout;
        if (1 == processDefinitionQuery.count()) {
            ProcessDefinition processDefinition = processDefinitionQuery.singleResult();
            assertNotNull(processDefinition);
            processDiagramLayout = repositoryService.getProcessDiagramLayout(processDefinition.getId());
        } else {
            // some test diagrams do not contain executable processes
            // and are therefore ignored by the engine
            InputStream bpmnXmlStream = new FileInputStream(
                    "src/test/resources/org/activiti/engine/test/api/repository/diagram/" + xmlFileName);
            InputStream imageStream = new FileInputStream(
                    "src/test/resources/org/activiti/engine/test/api/repository/diagram/" + imageFileName);
            assertNotNull(bpmnXmlStream);
            assertNotNull(imageStream);
            processDiagramLayout = new ProcessDiagramLayoutFactory().getProcessDiagramLayout(bpmnXmlStream,
                    imageStream);
        }
        assertLayoutCorrect(processDiagramLayout);
    }

    private void assertLayoutCorrect(DiagramLayout processDiagramLayout) throws IOException {
        String html = generateHtmlCode(imageFileName, processDiagramLayout, highlightedActivityId);

        File htmlFile = new File(
                "src/test/resources/org/activiti/engine/test/api/repository/diagram/" + imageFileName + ".html");
        if (OVERWRITE_EXPECTED_HTML_FILES) {
            FileUtils.writeStringToFile(htmlFile, html);
            fail("The assertions of this test only work if ProcessDiagramRetrievalTest#OVERWRITE_EXPECTED_HTML_FILES is set to false.");
        }
        assertEquals(FileUtils.readFileToString(htmlFile).replace("\r", ""), html); // remove carriage returns in case the files have been fetched via Git on Windows
    }

    private static String generateHtmlCode(String imageUrl, DiagramLayout processDiagramLayout,
            String highlightedActivityId) {
        StringBuilder html = new StringBuilder();
        html.append("<!DOCTYPE html>\n");
        html.append("<html>\n");
        html.append("  <head>\n");
        html.append("    <style type=\"text/css\"><!--\n");
        html.append("      .BPMNElement {\n");
        html.append("        position: absolute;\n");
        html.append("        border: 2px dashed lightBlue;\n");
        html.append("        border-radius: 5px; -moz-border-radius: 5px;\n");
        html.append("      }\n");
        if (highlightedActivityId != null && highlightedActivityId.length() > 0) {
            html.append("      #" + highlightedActivityId + " {border: 2px solid red;}\n");
        }
        html.append("    --></style>");
        html.append("  </head>\n");
        html.append("  <body>\n");
        html.append("    <div style=\"position: relative\">\n");
        html.append("      <img src=\"" + imageUrl + "\" />\n");

        List<DiagramNode> nodes = processDiagramLayout.getNodes();
        Collections.sort(nodes, new DiagramElementComparator());

        for (DiagramNode node : nodes) {
            html.append("      <div");
            html.append(" class=\"BPMNElement\"");
            html.append(" id=\"" + node.getId() + "\"");
            html.append(" style=\"");
            html.append(" left: " + (int) (node.getX() - 2) + "px;");
            html.append(" top: " + (int) (node.getY() - 2) + "px;");
            html.append(" width: " + node.getWidth().intValue() + "px;");
            html.append(" height: " + node.getHeight().intValue() + "px;\"></div>\n");
        }
        html.append("    </div>\n");
        html.append("  </body>\n");
        html.append("</html>");
        return html.toString();
    }

    private static boolean isEqual(InputStream stream1, InputStream stream2) throws IOException {

        ReadableByteChannel channel1 = Channels.newChannel(stream1);
        ReadableByteChannel channel2 = Channels.newChannel(stream2);

        ByteBuffer buffer1 = ByteBuffer.allocateDirect(1024);
        ByteBuffer buffer2 = ByteBuffer.allocateDirect(1024);

        try {
            while (true) {

                int bytesReadFromStream1 = channel1.read(buffer1);
                int bytesReadFromStream2 = channel2.read(buffer2);

                if (bytesReadFromStream1 == -1 || bytesReadFromStream2 == -1)
                    return bytesReadFromStream1 == bytesReadFromStream2;

                buffer1.flip();
                buffer2.flip();

                for (int i = 0; i < Math.min(bytesReadFromStream1, bytesReadFromStream2); i++)
                    if (buffer1.get() != buffer2.get())
                        return false;

                buffer1.compact();
                buffer2.compact();
            }

        } finally {
            if (stream1 != null)
                stream1.close();
            if (stream2 != null)
                stream2.close();
        }
    }

    /**
     * Might be used for debugging {@link ProcessDiagramRetrievalTest#testGetProcessDiagram()}.
     */
    @SuppressWarnings("unused")
    private static void writeToFile(InputStream is, File file) throws Exception {
        DataOutputStream out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file)));
        int c;
        while ((c = is.read()) != -1) {
            out.writeByte(c);
        }
        is.close();
        out.close();
    }

    static class DiagramElementComparator implements Comparator<DiagramElement> {

        public int compare(DiagramElement a, DiagramElement b) {
            return a.getId().compareTo(b.getId());
        }

        // Don't need it here
        public boolean equals(Object obj) {
            return this == obj;
        }

    }

}