org.onehippo.cms7.autoexport.AutoExportTest.java Source code

Java tutorial

Introduction

Here is the source code for org.onehippo.cms7.autoexport.AutoExportTest.java

Source

/*
 *  Copyright 2011-2013 Hippo B.V. (http://www.onehippo.com)
 * 
 *  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.onehippo.cms7.autoexport;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.Map;

import javax.jcr.NamespaceRegistry;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.PathNotFoundException;
import javax.jcr.RepositoryException;
import javax.jcr.nodetype.NoSuchNodeTypeException;
import javax.jcr.nodetype.NodeTypeManager;
import javax.jcr.nodetype.NodeTypeTemplate;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.custommonkey.xmlunit.Diff;
import org.custommonkey.xmlunit.Difference;
import org.custommonkey.xmlunit.DifferenceListener;
import org.custommonkey.xmlunit.ElementQualifier;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.onehippo.repository.testutils.RepositoryTestCase;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;

import junit.framework.Assert;
import static org.custommonkey.xmlunit.DifferenceConstants.ATTR_SEQUENCE_ID;
import static org.custommonkey.xmlunit.DifferenceConstants.CHILD_NODELIST_SEQUENCE_ID;
import static org.custommonkey.xmlunit.DifferenceConstants.COMMENT_VALUE_ID;
import static org.custommonkey.xmlunit.DifferenceConstants.TEXT_VALUE_ID;
import static org.hippoecm.repository.api.HippoNodeType.CONFIGURATION_PATH;
import static org.hippoecm.repository.api.HippoNodeType.INITIALIZE_PATH;
import static org.hippoecm.repository.api.HippoNodeType.NT_RESOURCEBUNDLE;
import static org.hippoecm.repository.api.HippoNodeType.NT_RESOURCEBUNDLES;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.onehippo.cms7.autoexport.Constants.CONFIG_ENABLED_PROPERTY_NAME;
import static org.onehippo.cms7.autoexport.Constants.CONFIG_NODE_PATH;

/**
 * Test for {@link AutoExportModule}
 */
@Ignore
public class AutoExportTest extends RepositoryTestCase {

    private static final Logger log = LoggerFactory.getLogger("org.onehippo.cms7.autoexport.test");
    private static final long TEN_SECONDS = 15 * 1000;

    private String testHome;
    private String projectBase;

    // /export-test
    private Node testRoot;

    @Before
    @Override
    public void setUp() throws Exception {
        // Where are we?
        final String resource = AutoExportTest.class.getResource("/autoexporttest/simple/hippoecm-extension.xml")
                .getFile();
        int idx = resource.indexOf("/target/test-classes/autoexporttest/simple/hippoecm-extension.xml");
        String moduleHome = resource.substring(0, idx);

        testHome = URLDecoder.decode(moduleHome + "/target/test-classes/autoexporttest", "UTF-8");
        projectBase = URLDecoder.decode(moduleHome + "/target/autoexporttest", "UTF-8");

        assertTrue(new File(testHome).exists());

        // remove results from previous invocation
        // if we do this on teardown we can't inspect
        // results manually
        File projectBaseDir = new File(this.projectBase);
        if (projectBaseDir.exists()) {
            log.debug("deleting project base dir: " + projectBaseDir.getPath());
            FileUtils.deleteDirectory(projectBaseDir);
        }
        System.setProperty("project.basedir", this.projectBase);

        // startup the repository
        super.setUp(true);

        // remove imported nodes
        Node root = session.getNode("/");
        for (NodeIterator iter = root.getNodes("et:*"); iter.hasNext();) {
            Node node = iter.nextNode();
            log.debug("removing node " + node.getPath());
            node.remove();
        }
        for (NodeIterator iter = root.getNode(CONFIGURATION_PATH).getNode(INITIALIZE_PATH).getNodes("et-*"); iter
                .hasNext();) {
            Node node = iter.nextNode();
            log.debug("removing node " + node.getPath());
            node.remove();
        }
        NodeTypeManager manager = session.getWorkspace().getNodeTypeManager();
        try {
            manager.unregisterNodeType("et:example");
        } catch (NoSuchNodeTypeException e) {
        }
        try {
            manager.unregisterNodeType("et:example2");
        } catch (NoSuchNodeTypeException e) {
        }

        session.save();
        testRoot = session.getRootNode();

    }

    @Override
    @After
    public void tearDown() throws Exception {
        removeNode("/et:simple");
        removeNode("/et:foo");
        System.clearProperty("project.basedir");
        super.tearDown();
    }

    @Test
    public void testAddNode() throws Exception {
        String[] content = { "/et:simple", "et:node" };
        build(session, content);
        session.save();
        waitForAutoExport();
        checkExportedFiles("simple");
        assertTrue(hasInitializeItemNode("et-simple"));
    }

    @Test
    public void testAddDeepNode() throws Exception {
        String[] content = { "/et:foo", "et:node", "/et:foo/et:foo", "et:node", "/et:foo/et:foo/et:foo",
                "et:node" };
        build(session, content);
        session.save();
        waitForAutoExport();
        checkExportedFiles("deep");
        assertTrue(hasInitializeItemNode("et-foo"));
        assertTrue(hasInitializeItemNode("et-foo-et-foo-et-foo"));
    }

    @Test
    public void testAddAndRemoveNode() throws Exception {
        // case: add and remove a child node
        Node node0 = testRoot.addNode("et:simple", "et:node");
        Node node1 = node0.addNode("et:simple", "et:node");
        session.save();
        waitForAutoExport();
        node1.remove();
        session.save();
        waitForAutoExport();
        checkExportedFiles("simple");
        // case: add and remove a context node
        // (should add and remove content resource instruction)
        Node node2 = testRoot.addNode("et:simple2", "et:node");
        session.save();
        waitForAutoExport();
        node2.remove();
        session.save();
        waitForAutoExport();
        // result should be the same
        checkExportedFiles("simple");
    }

    @Test
    public void testMoveNode() throws Exception {
        // create /export-test/et:tobemoved and persist 
        testRoot.addNode("et:tobemoved", "et:node");
        session.save();
        waitForAutoExport();
        // move
        session.move("/et:tobemoved", "/et:simple");
        session.save();
        waitForAutoExport();
        checkExportedFiles("simple");
        //        assertTrue(!hasInitializeItemNode("et-tobemoved"));
    }

    @Test
    public void testMoveDeepNode() throws Exception {
        Node node = testRoot.addNode("et:bar", "et:node");
        Node sub = node.addNode("et:foo", "et:node");
        sub.addNode("et:foo", "et:node");
        session.save();
        waitForAutoExport();
        // move
        session.move("/et:bar", "/et:foo");
        session.save();
        waitForAutoExport();
        checkExportedFiles("deep");
    }

    //    @Test
    //    public void testMoveDeepOverRemovedNode() throws Exception {
    //        Node node = testRoot.addNode("et:foo", "et:node");
    //        Node sub = node.addNode("et:foo", "et:node");
    //        sub.addNode("et:foo", "et:node");
    //        node = testRoot.addNode("et:bar", "et:node");
    //        sub = node.addNode("et:foo", "et:node");
    //        sub.addNode("et:foo", "et:node");
    //        super.session.save();
    //        Thread.sleep(TEN_SECONDS);
    //        // move
    //        super.session.removeItem("/et:foo");
    //        super.session.move("/et:bar", "/et:foo");
    //        super.session.save();
    //        Thread.sleep(TEN_SECONDS);
    //        checkExportedFiles("deep");
    //    }

    @Test
    public void testAddNamespace() throws Exception {
        NamespaceRegistry registry = super.session.getWorkspace().getNamespaceRegistry();
        registry.registerNamespace("etx", "http://hippoecm.org/etx/nt/1.1");
        // we need to add and remove a node here in order for the jcr event listener to be called
        Node node = testRoot.addNode("et:simple", "et:node");
        session.save();
        node.remove();
        session.save();
        waitForAutoExport();
        checkExportedFiles("namespace");
        assertTrue(hasInitializeItemNode("etx"));
    }

    @Test
    public void testAddNodetype() throws Exception {
        NodeTypeManager ntm = super.session.getWorkspace().getNodeTypeManager();
        NodeTypeTemplate template = ntm.createNodeTypeTemplate();
        template.setName("et:example");
        ntm.registerNodeType(template, false);
        super.session.save();
        waitForAutoExport();
        checkExportedFiles("nodetype");
    }

    @Test
    public void testAddNodetypes() throws Exception {
        NodeTypeManager ntm = super.session.getWorkspace().getNodeTypeManager();
        NodeTypeTemplate template = ntm.createNodeTypeTemplate();
        template.setName("et:example");
        ntm.registerNodeType(template, false);
        super.session.save();
        waitForAutoExport();
        // adding a nodetype in the same namespace should result
        // in the cnd for this namespace to be updated
        NodeTypeTemplate template2 = ntm.createNodeTypeTemplate();
        template2.setName("et:example2");
        ntm.registerNodeType(template2, false);
        super.session.save();
        waitForAutoExport();
        checkExportedFiles("nodetypes");
    }

    @Test
    public void testBasicDeltaXML() throws Exception {
        disableExport();
        Node node = testRoot.addNode("et:foo");
        node.setProperty("baz", "baz");
        session.save();
        enableExport();
        node.addNode("et:bar");
        node.setProperty("baz", "baz");
        node.setProperty("quz", "quz");
        session.save();
        waitForAutoExport();
        checkExportedFiles("basicdelta");
    }

    @Test
    public void testNestedDeltaXML() throws Exception {
        disableExport();
        Node node = testRoot.addNode("et:foo");
        session.save();
        enableExport();
        node.addNode("et:bar").addNode("et:baz");
        session.save();
        waitForAutoExport();
        checkExportedFiles("nesteddelta");
    }

    @Test
    public void testTranslations() throws Exception {
        disableExport();
        // create start configuration
        Node translations = session.getNode("/hippo:configuration/hippo:translations");
        final Node foo = translations.addNode("foo", NT_RESOURCEBUNDLES);
        final Node en = foo.addNode("en", NT_RESOURCEBUNDLE);
        en.setProperty("bar", "test");
        en.setProperty("quz", "quux");
        final Node nl = foo.addNode("nl", NT_RESOURCEBUNDLE);
        nl.setProperty("bar", "test");
        session.save();
        Thread.sleep(1000);
        enableExport();
        // change the configuration
        en.setProperty("bar", "baz");
        session.save();
        waitForAutoExport();
        checkExportedFiles("translations");
    }

    @Test
    public void testExcludeUuidPaths() throws Exception {
        String[] content = { "/et:foo", "et:node", "jcr:mixinTypes", "mix:referenceable", "/et:foo/et:foo",
                "et:node", "/et:foo/et:foo/et:foo", "et:node", "jcr:mixinTypes", "mix:referenceable", };
        build(session, content);
        session.save();
        waitForAutoExport();
        checkExportedFiles("filteruuids");
    }

    private void waitForAutoExport() throws RepositoryException {
        try {
            Thread.sleep(TEN_SECONDS);
        } catch (InterruptedException ignore) {
        }
    }

    private void checkExportedFiles(String testCase) throws Exception {
        Map<String, Reader> changes = new HashMap<String, Reader>();
        createReadersForExportedFiles(new File(projectBase + "/content/src/main/resources"),
                new File(projectBase + "/content/src/main/resources"), changes);
        Map<String, Reader> expected = new HashMap<String, Reader>();
        createReadersForExportedFiles(new File(testHome, testCase), new File(testHome, testCase), expected);
        assertEquals(expected, changes);
    }

    private void createReadersForExportedFiles(File basedir, File file, Map<String, Reader> readers)
            throws IOException {
        if (file.isDirectory()) {
            for (File child : file.listFiles()) {
                createReadersForExportedFiles(basedir, child, readers);
            }
        } else if (file.getName().endsWith(".xml") || file.getName().endsWith(".cnd")
                || file.getName().endsWith(".json")) {
            Reader reader = new FileReader(file);
            String path = file.getPath().substring(basedir.getPath().length() + 1);
            readers.put(path, reader);
        }
    }

    private void assertEquals(Map<String, Reader> expected, Map<String, Reader> changes)
            throws IOException, SAXException {
        Assert.assertEquals(expected.size(), changes.size());
        for (String file : expected.keySet()) {
            log.debug("Comparing file " + file);
            assertNotNull(changes.get(file));
            if (file.endsWith(".xml")) {
                // compare the xml
                Diff d = new Diff(expected.get(file), changes.get(file));
                d.overrideDifferenceListener(new IgnoreTextDifferenceListener());
                d.overrideElementQualifier(new MyElementQualifier());
                assertTrue("File " + file + " has unexpected contents, diff:\n" + d.toString(), d.similar());
            } else {
                String exported = IOUtils.toString(changes.get(file));
                String expectation = IOUtils.toString(expected.get(file));
                //                System.out.println("exported = " + exported);
                //                System.out.println("expected = " + expectation);
                Assert.assertEquals(expectation, exported);
            }
        }
    }

    // not interested in differences in text node values
    private static class IgnoreTextDifferenceListener implements DifferenceListener {
        private boolean isIgnoredDifference(Difference difference) {
            if (difference.getId() == TEXT_VALUE_ID) {
                // ignore all differences in text nodes other than within element sv:value
                Element parentNode = (Element) difference.getTestNodeDetail().getNode().getParentNode();
                if (parentNode.getNodeName().equals("sv:value")) {
                    // also, when it is a value of a creation date property, ignore it as well
                    return ((Element) parentNode.getParentNode()).getAttribute("sv:name").equals("jcr:created");
                }
                return true;
            }
            if (difference.getId() == COMMENT_VALUE_ID) {
                return true;
            }
            if (difference.getId() == ATTR_SEQUENCE_ID) {
                return true;
            }
            if (difference.getId() == CHILD_NODELIST_SEQUENCE_ID) {
                return true;
            }
            return false;
        }

        @Override
        public int differenceFound(Difference difference) {
            if (isIgnoredDifference(difference)) {
                return RETURN_IGNORE_DIFFERENCE_NODES_SIMILAR;
            } else {
                log.debug("difference: " + difference);
                return RETURN_ACCEPT_DIFFERENCE;
            }
        }

        @Override
        public void skippedComparison(org.w3c.dom.Node node, org.w3c.dom.Node node1) {
        }
    }

    private static class MyElementQualifier implements ElementQualifier {
        @Override
        public boolean qualifyForComparison(Element control, Element test) {
            if (control.getNodeName().equals("sv:property") && test.getNodeName().equals("sv:property")) {
                return control.getAttribute("sv:name").equals(test.getAttribute("sv:name"));
            }
            if (control.getNodeName().equals("sv:node") && test.getNodeName().equals("sv:node")) {
                return control.getAttribute("sv:name").equals(test.getAttribute("sv:name"));
            }
            return (control.getNodeName().equals("sv:value") && test.getNodeName().equals("sv:value"));
        }
    }

    private void disableExport() {
        try {
            session.getNode(CONFIG_NODE_PATH).setProperty(CONFIG_ENABLED_PROPERTY_NAME, false);
        } catch (RepositoryException e) {
            log.error("Failed to disable export.", e);
        }
    }

    private void enableExport() {
        try {
            session.getNode(CONFIG_NODE_PATH).setProperty(CONFIG_ENABLED_PROPERTY_NAME, true);
        } catch (RepositoryException e) {
            log.error("Failed to disable export.", e);
        }
    }

    private boolean hasInitializeItemNode(String name) throws RepositoryException {
        try {
            return session.getRootNode().getNode(CONFIGURATION_PATH).getNode(INITIALIZE_PATH).hasNode(name);
        } catch (PathNotFoundException e) {
            return false;
        }
    }
}