/*
 * File   : $Source: /usr/local/cvs/opencms/test/org/opencms/xml/content/TestCmsXmlContentWithVfs.java,v $
 * Date   : $Date: 2008-02-27 12:05:51 $
 * Version: $Revision: 1.50 $
 *
 * This library is part of OpenCms -
 * the Open Source Content Management System
 *
 * Copyright (c) 2002 - 2008 Alkacon Software GmbH (http://www.alkacon.com)
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * For further information about Alkacon Software GmbH, please see the
 * company website: http://www.alkacon.com
 *
 * For further information about OpenCms, please see the
 * project website: http://www.opencms.org
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package org.opencms.xml.content;

import org.opencms.file.CmsFile;
import org.opencms.file.CmsObject;
import org.opencms.file.CmsProperty;
import org.opencms.file.CmsPropertyDefinition;
import org.opencms.file.CmsResource;
import org.opencms.file.CmsUser;
import org.opencms.i18n.CmsEncoder;
import org.opencms.i18n.CmsMessages;
import org.opencms.main.OpenCms;
import org.opencms.relations.CmsLink;
import org.opencms.relations.CmsRelation;
import org.opencms.relations.CmsRelationFilter;
import org.opencms.relations.CmsRelationType;
import org.opencms.staticexport.CmsLinkTable;
import org.opencms.test.OpenCmsTestCase;
import org.opencms.test.OpenCmsTestProperties;
import org.opencms.util.CmsFileUtil;
import org.opencms.widgets.CmsCheckboxWidget;
import org.opencms.widgets.CmsHtmlWidget;
import org.opencms.widgets.I_CmsWidget;
import org.opencms.xml.CmsXmlContentDefinition;
import org.opencms.xml.CmsXmlEntityResolver;
import org.opencms.xml.CmsXmlUtils;
import org.opencms.xml.types.CmsXmlHtmlValue;
import org.opencms.xml.types.CmsXmlNestedContentDefinition;
import org.opencms.xml.types.CmsXmlStringValue;
import org.opencms.xml.types.CmsXmlVarLinkValue;
import org.opencms.xml.types.CmsXmlVfsFileValue;
import org.opencms.xml.types.I_CmsXmlContentValue;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;

import junit.extensions.TestSetup;
import junit.framework.Test;
import junit.framework.TestSuite;

/**
 * Tests the OpenCms XML contents with real VFS operations.<p>
 *
 * @author Alexander Kandzior 
 * @version $Revision: 1.50 $
 */
public class TestCmsXmlContentWithVfs extends OpenCmsTestCase {

    private static final String SCHEMA_SYSTEM_ID_2 = "http://www.opencms.org/test2.xsd";
    private static final String SCHEMA_SYSTEM_ID_3 = "http://www.opencms.org/test3.xsd";
    private static final String SCHEMA_SYSTEM_ID_3B = "http://www.opencms.org/test3b.xsd";
    private static final String SCHEMA_SYSTEM_ID_4 = "http://www.opencms.org/test4.xsd";
    private static final String SCHEMA_SYSTEM_ID_4B = "http://www.opencms.org/test4b.xsd";
    private static final String SCHEMA_SYSTEM_ID_5 = "http://www.opencms.org/test5.xsd";
    private static final String SCHEMA_SYSTEM_ID_6 = "http://www.opencms.org/test6.xsd";
    private static final String SCHEMA_SYSTEM_ID_7 = "http://www.opencms.org/test7.xsd";
    private static final String SCHEMA_SYSTEM_ID_8 = "http://www.opencms.org/test8.xsd";
    private static final String SCHEMA_SYSTEM_ID_9 = "http://www.opencms.org/test9.xsd";
    
    /**
     * Default JUnit constructor.<p>
     * 
     * @param arg0 JUnit parameters
     */
    public TestCmsXmlContentWithVfs(String arg0) {

        super(arg0);
    }

    /**
     * Test suite for this test class.<p>
     * 
     * @return the test suite
     */
    public static Test suite() {

        OpenCmsTestProperties.initialize(org.opencms.test.AllTests.TEST_PROPERTIES_PATH);

        TestSuite suite = new TestSuite();
        suite.setName(TestCmsXmlContentWithVfs.class.getName());

        suite.addTest(new TestCmsXmlContentWithVfs("testAutoXsd"));
        suite.addTest(new TestCmsXmlContentWithVfs("testAddRemoveElements"));
        suite.addTest(new TestCmsXmlContentWithVfs("testContentHandler"));
        suite.addTest(new TestCmsXmlContentWithVfs("testDefaultOnCreation"));
        suite.addTest(new TestCmsXmlContentWithVfs("testDefaultOnCreationWithNested"));
        suite.addTest(new TestCmsXmlContentWithVfs("testDefaultNested"));
        suite.addTest(new TestCmsXmlContentWithVfs("testNestedSchema"));
        suite.addTest(new TestCmsXmlContentWithVfs("testAddRemoveNestedElements"));
        suite.addTest(new TestCmsXmlContentWithVfs("testAccessNestedElements"));
        suite.addTest(new TestCmsXmlContentWithVfs("testValueIndex"));
        suite.addTest(new TestCmsXmlContentWithVfs("testLayoutWidgetMapping"));
        suite.addTest(new TestCmsXmlContentWithVfs("testLinkResolver"));
        suite.addTest(new TestCmsXmlContentWithVfs("testVarLinkResolver"));
        suite.addTest(new TestCmsXmlContentWithVfs("testEmptyLocale"));
        suite.addTest(new TestCmsXmlContentWithVfs("testCopyMoveRemoveLocale"));
        suite.addTest(new TestCmsXmlContentWithVfs("testValidation"));
        suite.addTest(new TestCmsXmlContentWithVfs("testValidationExtended"));
        suite.addTest(new TestCmsXmlContentWithVfs("testValidationLocale"));
        suite.addTest(new TestCmsXmlContentWithVfs("testMappings"));
        suite.addTest(new TestCmsXmlContentWithVfs("testMappingsWithManyLocales"));
        suite.addTest(new TestCmsXmlContentWithVfs("testMappingsOfNestedContent"));
        suite.addTest(new TestCmsXmlContentWithVfs("testMappingsAsList"));
        suite.addTest(new TestCmsXmlContentWithVfs("testResourceBundle"));
        suite.addTest(new TestCmsXmlContentWithVfs("testMacros"));
        suite.addTest(new TestCmsXmlContentWithVfs("testAddFileReference"));
        suite.addTest(new TestCmsXmlContentWithVfs("testXmlContentCreate"));

        TestSetup wrapper = new TestSetup(suite) {

            protected void setUp() {

                setupOpenCms("simpletest", "/sites/default/");
            }

            protected void tearDown() {

                removeOpenCms();
            }
        };

        return wrapper;
    }

    /**
     * Test accessing elements in nested schemas.<p>
     * 
     * @throws Exception in case something goes wrong
     */
    public void testAccessNestedElements() throws Exception {

        CmsObject cms = getCmsObject();
        echo("Testing access to nested schema values in XML content");

        CmsXmlEntityResolver resolver = new CmsXmlEntityResolver(cms);

        String content;
        CmsXmlContent xmlcontent;

        // unmarshal content definition
        content = CmsFileUtil.readFile("org/opencms/xml/content/xmlcontent-definition-7.xsd", CmsEncoder.ENCODING_UTF_8);
        // store content definition in entitiy resolver
        CmsXmlEntityResolver.cacheSystemId(SCHEMA_SYSTEM_ID_7, content.getBytes(CmsEncoder.ENCODING_UTF_8));

        // now read the XML content
        content = CmsFileUtil.readFile("org/opencms/xml/content/xmlcontent-7.xml", CmsEncoder.ENCODING_UTF_8);
        xmlcontent = CmsXmlContentFactory.unmarshal(content, CmsEncoder.ENCODING_UTF_8, resolver);

        // validate the XML structure
        xmlcontent.validateXmlStructure(resolver);

        // add a deep cascaded node
        xmlcontent.addValue(cms, "DeepCascade", Locale.ENGLISH, 0);
        CmsXmlContentValueSequence level0Sequence = xmlcontent.getValueSequence("DeepCascade[1]", Locale.ENGLISH);
        assertEquals(1, level0Sequence.getElementCount());

        // validate the XML structure
        xmlcontent.validateXmlStructure(resolver);

        // check nested cascade level 1
        CmsXmlContentValueSequence level1Sequence = xmlcontent.getValueSequence(
            "DeepCascade[1]/Cascade",
            Locale.ENGLISH);
        assertEquals(1, level1Sequence.getElementCount());

        // check nested cascade level 2
        CmsXmlContentValueSequence level2Sequence = xmlcontent.getValueSequence(
            "DeepCascade[1]/Cascade[1]/VfsLink",
            Locale.ENGLISH);
        assertEquals(1, level2Sequence.getElementCount());

        // now append an element to the nested element 
        level1Sequence.addValue(cms, 1);
        assertEquals(2, level1Sequence.getElementCount());

        // validate the XML structure
        xmlcontent.validateXmlStructure(resolver);

        // output the current document
        System.out.println(xmlcontent.toString());
        // re-create the document
        xmlcontent = CmsXmlContentFactory.unmarshal(xmlcontent.toString(), CmsEncoder.ENCODING_UTF_8, resolver);
        // validate the XML structure
        xmlcontent.validateXmlStructure(resolver);

        level0Sequence = xmlcontent.getValueSequence("DeepCascade[1]", Locale.ENGLISH);
        assertEquals(1, level0Sequence.getElementCount());

        // check nested cascade level 1
        level1Sequence = xmlcontent.getValueSequence("DeepCascade[1]/Cascade", Locale.ENGLISH);
        assertEquals(2, level1Sequence.getElementCount());

        // check nested cascade level 2 (for the NEW element)
        level2Sequence = xmlcontent.getValueSequence("DeepCascade[1]/Cascade[2]/VfsLink", Locale.ENGLISH);
        assertEquals(1, level2Sequence.getElementCount());

        // add some values to the level 2 sequence
        level2Sequence.addValue(cms, 0);
        level2Sequence.addValue(cms, 2);
        level2Sequence.addValue(cms, 1);
        assertEquals(4, level2Sequence.getElementCount());

        // output the current document
        System.out.println(xmlcontent.toString());
        // re-create the document
        xmlcontent = CmsXmlContentFactory.unmarshal(xmlcontent.toString(), CmsEncoder.ENCODING_UTF_8, resolver);

        // validate the XML structure
        xmlcontent.validateXmlStructure(resolver);

        // check nested cascade level 2 (for the NEW element)
        level2Sequence = xmlcontent.getValueSequence("DeepCascade[1]/Cascade[2]/VfsLink", Locale.ENGLISH);
        assertEquals(4, level2Sequence.getElementCount());

        // now add an optional, deep nested node that has no current value
        level2Sequence = xmlcontent.getValueSequence("DeepCascade[1]/Cascade[2]/Option", Locale.ENGLISH);
        assertEquals(0, level2Sequence.getElementCount());
        level2Sequence.addValue(cms, 0);
        level2Sequence.addValue(cms, 1);
        assertEquals(2, level2Sequence.getElementCount());

        // output the current document
        System.out.println(xmlcontent.toString());
        // re-create the document
        xmlcontent = CmsXmlContentFactory.unmarshal(xmlcontent.toString(), CmsEncoder.ENCODING_UTF_8, resolver);

        // validate the XML structure
        xmlcontent.validateXmlStructure(resolver);

        level2Sequence = xmlcontent.getValueSequence("DeepCascade[1]/Cascade[2]/Option", Locale.ENGLISH);
        assertEquals(2, level2Sequence.getElementCount());

        // now remove the deep cascaded sequence and create a new one
        level0Sequence = xmlcontent.getValueSequence("DeepCascade", Locale.ENGLISH);
        assertEquals(1, level0Sequence.getElementCount());
        level0Sequence.removeValue(0);
        assertEquals(0, level0Sequence.getElementCount());

        // output the current document
        System.out.println(xmlcontent.toString());
        // re-create the document
        xmlcontent = CmsXmlContentFactory.unmarshal(xmlcontent.toString(), CmsEncoder.ENCODING_UTF_8, resolver);

        // validate the XML structure
        xmlcontent.validateXmlStructure(resolver);

        level0Sequence = xmlcontent.getValueSequence("DeepCascade", Locale.ENGLISH);
        assertEquals(0, level0Sequence.getElementCount());

        // add a new value for the deep cascade
        level0Sequence.addValue(cms, 0);
        assertEquals(1, level0Sequence.getElementCount());

        // output the current document
        System.out.println(xmlcontent.toString());
        // re-create the document
        xmlcontent = CmsXmlContentFactory.unmarshal(xmlcontent.toString(), CmsEncoder.ENCODING_UTF_8, resolver);

        // validate the XML structure
        xmlcontent.validateXmlStructure(resolver);

        level0Sequence = xmlcontent.getValueSequence("DeepCascade", Locale.ENGLISH);
        assertEquals(1, level0Sequence.getElementCount());
    }

    /**
     * Test adding a file reference value to an existing xmlcontent.<p>
     * 
     * @throws Exception in case something goes wrong
     */
    public void testAddFileReference() throws Exception {

        CmsObject cms = getCmsObject();
        echo("Testing adding a file reference value to an existing xmlcontent");

        String filename = "/xmlcontent/article_0001.html";
        String filename2 = "/xmlcontent/article_0002.html";
        CmsXmlEntityResolver resolver = new CmsXmlEntityResolver(cms);
        // now read the XML content
        CmsFile file = cms.readFile(filename);
        // needs to be written to assure link id correctness
        cms.lockResource(filename);
        file = cms.writeFile(file);
        String content = new String(file.getContents());
        CmsXmlContent xmlcontent = CmsXmlContentFactory.unmarshal(content, CmsEncoder.ENCODING_UTF_8, resolver);

        xmlcontent.addValue(cms, "Homepage", Locale.ENGLISH, 0);
        CmsXmlVfsFileValue value = (CmsXmlVfsFileValue)xmlcontent.getValue("Homepage", Locale.ENGLISH);
        value.setStringValue(cms, filename + "?a=b&c=d#e");
        // validate the XML structure
        xmlcontent.validateXmlStructure(resolver);

        CmsLink link = value.getLink(cms);
        assertEquals(link.getTarget(), cms.getRequestContext().addSiteRoot(filename));
        assertTrue(link.isInternal());
        assertEquals(link.getQuery(), "a=b&c=d");
        assertEquals(link.getAnchor(), "e");
        assertEquals(link.getStructureId(), file.getStructureId());
        assertEquals(value.getStringValue(cms), filename + "?a=b&c=d#e");

        CmsResource res2 = cms.readResource(filename2);
        List links = cms.getRelationsForResource(filename, CmsRelationFilter.TARGETS);
        assertEquals(links.size(), 1);
        assertRelation(new CmsRelation(file, res2, CmsRelationType.HYPERLINK), (CmsRelation)links.get(0));

        file.setContents(xmlcontent.toString().getBytes());
        cms.lockResource(filename);
        cms.writeFile(file);

        links = cms.getRelationsForResource(filename, CmsRelationFilter.TARGETS);
        assertEquals(links.size(), 2);
        assertRelation(new CmsRelation(file, file, CmsRelationType.XML_WEAK), (CmsRelation)links.get(0));
        assertRelation(new CmsRelation(file, res2, CmsRelationType.HYPERLINK), (CmsRelation)links.get(1));

        links = cms.getRelationsForResource(filename, CmsRelationFilter.TARGETS.filterType(CmsRelationType.XML_WEAK));
        assertEquals(links.size(), 1);
        assertRelation(new CmsRelation(file, file, CmsRelationType.XML_WEAK), (CmsRelation)links.get(0));

        links = cms.getRelationsForResource(filename, CmsRelationFilter.TARGETS.filterType(CmsRelationType.HYPERLINK));
        assertEquals(links.size(), 1);
        assertRelation(new CmsRelation(file, res2, CmsRelationType.HYPERLINK), (CmsRelation)links.get(0));

        file = cms.readFile(filename);
        content = new String(file.getContents());
        xmlcontent = CmsXmlContentFactory.unmarshal(content, CmsEncoder.ENCODING_UTF_8, resolver);

        CmsXmlHtmlValue value2 = (CmsXmlHtmlValue)xmlcontent.getValue("Text", Locale.ENGLISH, 0);
        link = value2.getLinkTable().getLink("link0");
        assertEquals(link.getTarget(), res2.getRootPath());
        assertTrue(link.isInternal());
        assertNull(link.getQuery());
        assertNull(link.getAnchor());
        assertEquals(link.getStructureId(), res2.getStructureId());
    }

    /**
     * Test adding and removing elements from an XML content.<p>
     * 
     * @throws Exception in case something goes wrong
     */
    public void testAddRemoveElements() throws Exception {

        CmsObject cms = getCmsObject();
        echo("Testing adding and removing elements from an XML content");

        CmsXmlEntityResolver resolver = new CmsXmlEntityResolver(cms);

        String content;
        CmsXmlContent xmlcontent;

        // unmarshal content definition
        content = CmsFileUtil.readFile("org/opencms/xml/content/xmlcontent-definition-6.xsd", CmsEncoder.ENCODING_UTF_8);
        // store content definition in entitiy resolver
        CmsXmlEntityResolver.cacheSystemId(SCHEMA_SYSTEM_ID_6, content.getBytes(CmsEncoder.ENCODING_UTF_8));

        // now read the XML content
        content = CmsFileUtil.readFile("org/opencms/xml/content/xmlcontent-6.xml", CmsEncoder.ENCODING_UTF_8);
        xmlcontent = CmsXmlContentFactory.unmarshal(content, CmsEncoder.ENCODING_UTF_8, resolver);

        CmsXmlContentValueSequence titleSequence;

        titleSequence = xmlcontent.getValueSequence("Title", Locale.ENGLISH);
        assertEquals("Title", titleSequence.getElementName());
        assertEquals(1, titleSequence.getElementCount());
        assertEquals(1, titleSequence.getMinOccurs());
        assertEquals(5, titleSequence.getMaxOccurs());
        assertEquals("This is just a modification test", titleSequence.getValue(0).getStringValue(cms));

        CmsXmlStringValue newValue;

        newValue = (CmsXmlStringValue)titleSequence.addValue(cms, 0);
        assertEquals(2, titleSequence.getElementCount());
        assertEquals(newValue, titleSequence.getValue(0));
        newValue.setStringValue(cms, "This is another Value!");

        // now re-create the XML content from the XML document
        content = xmlcontent.toString();
        System.out.println(content);
        xmlcontent = CmsXmlContentFactory.unmarshal(content, CmsEncoder.ENCODING_UTF_8, resolver);

        // ensure the document structure is as expected
        titleSequence = xmlcontent.getValueSequence("Title", Locale.ENGLISH);
        assertEquals("Title", titleSequence.getElementName());
        assertEquals(2, titleSequence.getElementCount());
        assertEquals(1, titleSequence.getMinOccurs());
        assertEquals(5, titleSequence.getMaxOccurs());
        assertEquals("This is another Value!", titleSequence.getValue(0).getStringValue(cms));
        assertEquals("This is just a modification test", titleSequence.getValue(1).getStringValue(cms));

        // add an element at the last position
        newValue = (CmsXmlStringValue)titleSequence.addValue(cms, 2);
        newValue.setStringValue(cms, "This is the last value.");
        assertEquals(newValue, titleSequence.getValue(2));
        // add another element at the 2nd position
        newValue = (CmsXmlStringValue)titleSequence.addValue(cms, 1);
        newValue.setStringValue(cms, "This is the 2nd value.");
        assertEquals(newValue, titleSequence.getValue(1));
        assertEquals(4, titleSequence.getElementCount());

        // now re-create the XML content from the XML document
        content = xmlcontent.toString();
        System.out.println(content);
        xmlcontent = CmsXmlContentFactory.unmarshal(content, CmsEncoder.ENCODING_UTF_8, resolver);

        // ensure the document structure is as expected
        titleSequence = xmlcontent.getValueSequence("Title", Locale.ENGLISH);
        assertEquals("Title", titleSequence.getElementName());
        assertEquals(4, titleSequence.getElementCount());
        assertEquals(1, titleSequence.getMinOccurs());
        assertEquals(5, titleSequence.getMaxOccurs());
        assertEquals("This is another Value!", titleSequence.getValue(0).getStringValue(cms));
        assertEquals("This is the 2nd value.", titleSequence.getValue(1).getStringValue(cms));
        assertEquals("This is just a modification test", titleSequence.getValue(2).getStringValue(cms));
        assertEquals("This is the last value.", titleSequence.getValue(3).getStringValue(cms));

        // now the optional element
        CmsXmlContentValueSequence optionSequence;

        optionSequence = xmlcontent.getValueSequence("Option", Locale.ENGLISH);
        assertEquals("Option", optionSequence.getElementName());
        assertEquals(0, optionSequence.getElementCount());
        assertEquals(0, optionSequence.getMinOccurs());
        assertEquals(2, optionSequence.getMaxOccurs());

        // add an element for the optional element
        newValue = (CmsXmlStringValue)optionSequence.addValue(cms, 0);
        newValue.setStringValue(cms, "Optional value 1");
        assertEquals(newValue, optionSequence.getValue(0));
        // add another element
        newValue = (CmsXmlStringValue)optionSequence.addValue(cms, 0);
        newValue.setStringValue(cms, "Optional value 0");
        assertEquals(newValue, optionSequence.getValue(0));
        assertEquals(2, optionSequence.getElementCount());

        // now re-create the XML content from the XML document
        content = xmlcontent.toString();
        System.out.println(content);
        xmlcontent = CmsXmlContentFactory.unmarshal(content, CmsEncoder.ENCODING_UTF_8, resolver);

        optionSequence = xmlcontent.getValueSequence("Option", Locale.ENGLISH);
        assertEquals("Option", optionSequence.getElementName());
        assertEquals(2, optionSequence.getElementCount());
        assertEquals(0, optionSequence.getMinOccurs());
        assertEquals(2, optionSequence.getMaxOccurs());

        assertEquals("Optional value 0", optionSequence.getValue(0).getStringValue(cms));
        assertEquals("Optional value 1", optionSequence.getValue(1).getStringValue(cms));

        optionSequence.removeValue(1);
        assertEquals(1, optionSequence.getElementCount());
        assertEquals("Optional value 0", optionSequence.getValue(0).getStringValue(cms));

        optionSequence.removeValue(0);
        assertEquals(0, optionSequence.getElementCount());

        // now re-create the XML content from the XML document
        content = xmlcontent.toString();
        System.out.println(content);
        xmlcontent = CmsXmlContentFactory.unmarshal(content, CmsEncoder.ENCODING_UTF_8, resolver);

        titleSequence = xmlcontent.getValueSequence("Title", Locale.ENGLISH);
        assertEquals(4, titleSequence.getElementCount());

        titleSequence.removeValue(0);
        titleSequence.removeValue(2);
        assertEquals(2, titleSequence.getElementCount());
        assertEquals("This is the 2nd value.", titleSequence.getValue(0).getStringValue(cms));
        assertEquals("This is just a modification test", titleSequence.getValue(1).getStringValue(cms));

        // now re-create the XML content from the XML document
        content = xmlcontent.toString();
        System.out.println(content);
    }

    /**
     * Test adding and removing elements from an XML content, including nested elements.<p>
     * 
     * @throws Exception in case something goes wrong
     */
    public void testAddRemoveNestedElements() throws Exception {

        CmsObject cms = getCmsObject();
        echo("Testing adding and removing nested elements from an XML content");

        CmsXmlEntityResolver resolver = new CmsXmlEntityResolver(cms);

        String content;
        CmsXmlContent xmlcontent;

        // unmarshal content definition
        content = CmsFileUtil.readFile("org/opencms/xml/content/xmlcontent-definition-7.xsd", CmsEncoder.ENCODING_UTF_8);
        // store content definition in entitiy resolver
        CmsXmlEntityResolver.cacheSystemId(SCHEMA_SYSTEM_ID_7, content.getBytes(CmsEncoder.ENCODING_UTF_8));

        // now read the XML content
        content = CmsFileUtil.readFile("org/opencms/xml/content/xmlcontent-7.xml", CmsEncoder.ENCODING_UTF_8);
        xmlcontent = CmsXmlContentFactory.unmarshal(content, CmsEncoder.ENCODING_UTF_8, resolver);

        CmsXmlContentValueSequence nestedSequence;

        nestedSequence = xmlcontent.getValueSequence("Cascade", Locale.ENGLISH);
        assertEquals(1, nestedSequence.getElementCount());
        I_CmsXmlContentValue newValue;
        newValue = nestedSequence.addValue(cms, 0);
        assertNotNull(newValue);
        assertFalse(newValue.isSimpleType());
        assertEquals(CmsXmlNestedContentDefinition.class.getName(), newValue.getClass().getName());

        // re-create the XML content
        content = xmlcontent.toString();
        System.out.println(content);
        xmlcontent = CmsXmlContentFactory.unmarshal(content, CmsEncoder.ENCODING_UTF_8, resolver);
        // validate the XML structure
        xmlcontent.validateXmlStructure(resolver);

        nestedSequence = xmlcontent.getValueSequence("Cascade", Locale.ENGLISH);
        assertEquals(2, nestedSequence.getElementCount());

        CmsXmlContentValueSequence deepNestedSequence;
        deepNestedSequence = xmlcontent.getValueSequence("DeepCascade", Locale.ENGLISH);
        assertEquals(0, deepNestedSequence.getElementCount());

        newValue = deepNestedSequence.addValue(cms, 0);
        assertNotNull(newValue);
        assertFalse(newValue.isSimpleType());
        assertEquals(CmsXmlNestedContentDefinition.class.getName(), newValue.getClass().getName());

        // re-create the XML content
        content = xmlcontent.toString();
        System.out.println(content);
        xmlcontent = CmsXmlContentFactory.unmarshal(content, CmsEncoder.ENCODING_UTF_8, resolver);
        // validate the XML structure
        xmlcontent.validateXmlStructure(resolver);

        deepNestedSequence = xmlcontent.getValueSequence("DeepCascade", Locale.ENGLISH);
        assertEquals(1, deepNestedSequence.getElementCount());

        nestedSequence = xmlcontent.getValueSequence("Cascade", Locale.ENGLISH);
        assertEquals(2, nestedSequence.getElementCount());

        nestedSequence.removeValue(1);
        deepNestedSequence.removeValue(0);

        // re-create the XML content
        content = xmlcontent.toString();
        System.out.println(content);
        xmlcontent = CmsXmlContentFactory.unmarshal(content, CmsEncoder.ENCODING_UTF_8, resolver);
        // validate the XML structure
        xmlcontent.validateXmlStructure(resolver);

        deepNestedSequence = xmlcontent.getValueSequence("DeepCascade", Locale.ENGLISH);
        assertEquals(0, deepNestedSequence.getElementCount());

        nestedSequence = xmlcontent.getValueSequence("Cascade", Locale.ENGLISH);
        assertEquals(1, nestedSequence.getElementCount());
    }

    /**
     * Tests creation of the automatic XML schema XSD.
     * 
     * @throws Exception in case something goes wrong
     */
    public void testAutoXsd() throws Exception {

        org.opencms.xml.CmsXmlEntityResolver resolver = new org.opencms.xml.CmsXmlEntityResolver(null);
        org.xml.sax.InputSource source = resolver.resolveEntity(
            null,
            org.opencms.xml.CmsXmlContentDefinition.XSD_INCLUDE_OPENCMS);
        byte[] bytes = org.opencms.util.CmsFileUtil.readFully(source.getByteStream());
        String string = org.opencms.i18n.CmsEncoder.createString(bytes, "UTF-8");
        System.out.println(string);
    }

    /**
     * Test using a different XML content handler then the default handler.<p>
     * 
     * @throws Exception in case something goes wrong
     */
    public void testContentHandler() throws Exception {

        CmsObject cms = getCmsObject();
        echo("Testing individual content handler for XML content");

        CmsXmlEntityResolver resolver = new CmsXmlEntityResolver(cms);

        String content;

        // unmarshal content definition
        content = CmsFileUtil.readFile("org/opencms/xml/content/xmlcontent-definition-3.xsd", CmsEncoder.ENCODING_UTF_8);
        CmsXmlContentDefinition definition = CmsXmlContentDefinition.unmarshal(content, SCHEMA_SYSTEM_ID_3, resolver);
        // store content definition in entitiy resolver
        CmsXmlEntityResolver.cacheSystemId(SCHEMA_SYSTEM_ID_3, content.getBytes(CmsEncoder.ENCODING_UTF_8));

        // now create the XML content
        content = CmsFileUtil.readFile("org/opencms/xml/content/xmlcontent-3.xml", CmsEncoder.ENCODING_UTF_8);
        CmsXmlContent xmlcontent = CmsXmlContentFactory.unmarshal(content, CmsEncoder.ENCODING_UTF_8, resolver);

        assertTrue(xmlcontent.hasValue("Html", Locale.ENGLISH));
        assertTrue(xmlcontent.hasValue("VfsLink", Locale.ENGLISH));
        assertSame(definition.getContentHandler().getClass().getName(), TestXmlContentHandler.class.getName());
    }

    /**
     * Tests locale copy, move and remove operation on an XML content.<p>
     * 
     * @throws Exception if the test fails
     */
    public void testCopyMoveRemoveLocale() throws Exception {

        CmsObject cms = getCmsObject();
        echo("Testing locale copy, move and remove operation on an XML content");

        CmsXmlEntityResolver resolver = new CmsXmlEntityResolver(cms);

        String iso = "ISO-8859-1";

        String content;
        CmsXmlContent xmlcontent;

        // unmarshal content definition
        content = CmsFileUtil.readFile("org/opencms/xml/content/xmlcontent-definition-8.xsd", CmsEncoder.ENCODING_UTF_8);
        // store content definition in entitiy resolver
        CmsXmlEntityResolver.cacheSystemId(SCHEMA_SYSTEM_ID_8, content.getBytes(iso));

        // read an existing (empty) XML content with just one locale node
        content = CmsFileUtil.readFile("org/opencms/xml/content/xmlcontent-8.xml", iso);
        xmlcontent = CmsXmlContentFactory.unmarshal(content, iso, resolver);
        // validate the XML structure
        xmlcontent.validateXmlStructure(resolver);
        List locales = xmlcontent.getLocales();
        assertEquals(1, locales.size());
        assertEquals(Locale.ENGLISH, locales.get(0));

        xmlcontent.copyLocale(Locale.ENGLISH, Locale.GERMANY);
        assertEquals(2, xmlcontent.getLocales().size());
        assertTrue(xmlcontent.hasLocale(Locale.ENGLISH));
        assertTrue(xmlcontent.hasLocale(Locale.GERMANY));

        // validate the XML structure
        xmlcontent.validateXmlStructure(resolver);

        xmlcontent.moveLocale(Locale.GERMANY, Locale.FRENCH);
        assertEquals(2, xmlcontent.getLocales().size());
        assertTrue(xmlcontent.hasLocale(Locale.ENGLISH));
        assertTrue(xmlcontent.hasLocale(Locale.FRENCH));
        assertFalse(xmlcontent.hasLocale(Locale.GERMANY));

        // validate the XML structure
        xmlcontent.validateXmlStructure(resolver);

        xmlcontent.removeLocale(Locale.ENGLISH);
        assertEquals(1, xmlcontent.getLocales().size());
        assertTrue(xmlcontent.hasLocale(Locale.FRENCH));
        assertFalse(xmlcontent.hasLocale(Locale.ENGLISH));
        assertFalse(xmlcontent.hasLocale(Locale.GERMANY));
        assertEquals(Locale.FRENCH, xmlcontent.getLocales().get(0));

        // validate the XML structure
        xmlcontent.validateXmlStructure(resolver);
    }

    /**
     * Test default values in the appinfo node using a nested XML content schema.<p>
     * 
     * @throws Exception in case something goes wrong
     */
    public void testDefaultNested() throws Exception {

        CmsObject cms = getCmsObject();
        echo("Testing for default values in nested XML content schemas");

        CmsXmlEntityResolver resolver = new CmsXmlEntityResolver(cms);

        String content;

        // unmarshal content definition
        content = CmsFileUtil.readFile("org/opencms/xml/content/xmlcontent-definition-4.xsd", CmsEncoder.ENCODING_UTF_8);
        // store content definition in entitiy resolver
        CmsXmlEntityResolver.cacheSystemId(SCHEMA_SYSTEM_ID_4, content.getBytes(CmsEncoder.ENCODING_UTF_8));

        // now create the XML content
        content = CmsFileUtil.readFile("org/opencms/xml/content/xmlcontent-4.xml", CmsEncoder.ENCODING_UTF_8);
        CmsXmlContent xmlcontent = CmsXmlContentFactory.unmarshal(content, CmsEncoder.ENCODING_UTF_8, resolver);
        System.out.println(xmlcontent.toString());

        // validate the XML structure
        xmlcontent.validateXmlStructure(resolver);

        I_CmsXmlContentValue value1;

        value1 = xmlcontent.addValue(cms, "Title", Locale.ENGLISH, 0);
        assertEquals("Test", value1.getStringValue(cms));

        value1 = xmlcontent.addValue(cms, "Cascade[1]/Option", Locale.ENGLISH, 0);
        assertEquals("Default value from the XML", value1.getStringValue(cms));

        // check exact default mappings for nested content
        value1 = xmlcontent.addValue(cms, "Cascade[1]/Option", Locale.ENGLISH, 1);
        assertEquals("Default value from outer content definition", value1.getStringValue(cms));

        // check generic default mappings for nested content
        value1 = xmlcontent.addValue(cms, "Cascade[1]/VfsLink", Locale.ENGLISH, 1);
        assertEquals("/default/for/all/from/outer.txt", value1.getStringValue(cms));

        value1 = xmlcontent.addValue(cms, "Cascade[1]/VfsLink", Locale.ENGLISH, 2);
        assertEquals("/default/for/all/from/outer.txt", value1.getStringValue(cms));

    }

    /**
     * Test default values after a new XML content has been created.<p>
     * 
     * @throws Exception in case the test fails
     */
    public void testDefaultOnCreation() throws Exception {

        CmsObject cms = getCmsObject();
        echo("Testing default values when creating an XML content resource");

        CmsXmlEntityResolver resolver = new CmsXmlEntityResolver(cms);

        // create a new xml content article
        String xmlContentFile = "/xmlcontent/article_0005.html";
        cms.createResource(xmlContentFile, OpenCmsTestCase.ARTICLE_TYPEID);

        CmsFile file = cms.readFile(xmlContentFile);
        String content = new String(file.getContents(), CmsEncoder.ENCODING_UTF_8);
        CmsXmlContent xmlcontent = CmsXmlContentFactory.unmarshal(content, CmsEncoder.ENCODING_UTF_8, resolver);

        String value = xmlcontent.getStringValue(cms, "Title", Locale.ENGLISH);
        assertEquals("Default title value", value);

        value = xmlcontent.getStringValue(cms, "Release", Locale.ENGLISH);
        assertEquals("1114525380000", value);

        value = xmlcontent.getStringValue(cms, "Author", Locale.ENGLISH);
        assertEquals("This is the Description", value);
    }

    /**
     * Test default values in the appinfo node using a nested XML content schema when creating a new content.<p>
     * 
     * The nested content definition must be non-optional, and must have non-optional element.<p>
     * 
     * @throws Exception in case something goes wrong
     */
    public void testDefaultOnCreationWithNested() throws Exception {

        CmsObject cms = getCmsObject();
        echo("Testing for default values in nested XML content schemas when creating a new content");

        CmsXmlEntityResolver resolver = new CmsXmlEntityResolver(cms);

        String content;

        // unmarshal content definitions
        content = CmsFileUtil.readFile(
            "org/opencms/xml/content/xmlcontent-definition-3b.xsd",
            CmsEncoder.ENCODING_UTF_8);
        // store content definition in entitiy resolver
        CmsXmlEntityResolver.cacheSystemId(SCHEMA_SYSTEM_ID_3B, content.getBytes(CmsEncoder.ENCODING_UTF_8));
        content = CmsFileUtil.readFile(
            "org/opencms/xml/content/xmlcontent-definition-4b.xsd",
            CmsEncoder.ENCODING_UTF_8);
        // store content definition in entitiy resolver
        CmsXmlEntityResolver.cacheSystemId(SCHEMA_SYSTEM_ID_4B, content.getBytes(CmsEncoder.ENCODING_UTF_8));

        // create the content definition
        CmsXmlContentDefinition cd = CmsXmlContentDefinition.unmarshal(content, SCHEMA_SYSTEM_ID_4B, resolver);

        CmsXmlContent xmlcontent = CmsXmlContentFactory.createDocument(cms, Locale.ENGLISH, content, cd);

        String value = xmlcontent.getStringValue(cms, "Title", Locale.ENGLISH);
        assertEquals("Test", value);

        value = xmlcontent.getStringValue(cms, "Cascade/Option", Locale.ENGLISH);
        assertEquals("Default value from outer content definition", value);

        value = xmlcontent.getStringValue(cms, "Cascade/Option[2]", Locale.ENGLISH);
        assertEquals("Default value from outer content definition (for option node 2)", value);

        value = xmlcontent.getStringValue(cms, "Cascade/VfsLink", Locale.ENGLISH);
        assertEquals("/default/for/all/from/outer.txt", value);
    }

    /**
     * Tests the Locale settings of XMLContents with only optional elements and no element present.<p>
     * 
     * @throws Exception if the test fails
     */
    public void testEmptyLocale() throws Exception {

        CmsObject cms = getCmsObject();
        echo("Testing handling of empty locale nodes in XML content");

        CmsXmlEntityResolver resolver = new CmsXmlEntityResolver(cms);

        String iso = "ISO-8859-1";

        String content;
        CmsXmlContent xmlcontent;

        // unmarshal content definition
        content = CmsFileUtil.readFile("org/opencms/xml/content/xmlcontent-definition-8.xsd", CmsEncoder.ENCODING_UTF_8);
        // store content definition in entitiy resolver
        CmsXmlEntityResolver.cacheSystemId(SCHEMA_SYSTEM_ID_8, content.getBytes(iso));
        CmsXmlContentDefinition cd1 = CmsXmlContentDefinition.unmarshal(content, SCHEMA_SYSTEM_ID_8, resolver);

        // read an existing (empty) XML content with just one locale node
        content = CmsFileUtil.readFile("org/opencms/xml/content/xmlcontent-8.xml", iso);
        xmlcontent = CmsXmlContentFactory.unmarshal(content, iso, resolver);
        // validate the XML structure
        xmlcontent.validateXmlStructure(resolver);
        List locales = xmlcontent.getLocales();
        assertEquals(1, locales.size());
        assertEquals(Locale.ENGLISH, locales.get(0));

        // create a fresh XML content based on the schema and try again  
        xmlcontent = CmsXmlContentFactory.createDocument(null, Locale.ENGLISH, CmsEncoder.ENCODING_UTF_8, cd1);
        xmlcontent.validateXmlStructure(resolver);

        locales = xmlcontent.getLocales();
        assertEquals(1, locales.size());
        assertEquals(Locale.ENGLISH, locales.get(0));
    }

    /**
     * Test using the GUI widget mapping appinfo nodes.<p>
     * 
     * @throws Exception in case something goes wrong
     */
    public void testLayoutWidgetMapping() throws Exception {

        CmsObject cms = getCmsObject();
        echo("Testing mapping of the XML content GUI to different widgets");

        CmsXmlEntityResolver resolver = new CmsXmlEntityResolver(cms);

        String content;

        // unmarshal content definition
        content = CmsFileUtil.readFile("org/opencms/xml/content/xmlcontent-definition-5.xsd", CmsEncoder.ENCODING_UTF_8);
        CmsXmlContentDefinition definition = CmsXmlContentDefinition.unmarshal(content, SCHEMA_SYSTEM_ID_5, resolver);
        // store content definition in entitiy resolver
        CmsXmlEntityResolver.cacheSystemId(SCHEMA_SYSTEM_ID_5, content.getBytes(CmsEncoder.ENCODING_UTF_8));

        // now read the XML content
        content = CmsFileUtil.readFile("org/opencms/xml/content/xmlcontent-5.xml", CmsEncoder.ENCODING_UTF_8);
        CmsXmlContent xmlcontent = CmsXmlContentFactory.unmarshal(content, CmsEncoder.ENCODING_UTF_8, resolver);

        // validate the XML structure
        xmlcontent.validateXmlStructure(resolver);

        I_CmsWidget widget;
        I_CmsXmlContentHandler handler = definition.getContentHandler();

        // make sure the selected widgets are of the configured "non-standard" type
        widget = handler.getWidget(xmlcontent.getValue("Title", Locale.ENGLISH));
        assertNotNull(widget);
        assertEquals(CmsCheckboxWidget.class.getName(), widget.getClass().getName());
        assertEquals("Configuration for Title", handler.getConfiguration(xmlcontent.getValue("Title", Locale.ENGLISH)));

        // make sure the alias name works
        widget = handler.getWidget(xmlcontent.getValue("Test", Locale.ENGLISH));
        assertNotNull(widget);
        assertEquals(CmsHtmlWidget.class.getName(), widget.getClass().getName());
        assertEquals("Configuration for Test", handler.getConfiguration(xmlcontent.getValue("Test", Locale.ENGLISH)));

        // make sure the custom class name works
        widget = handler.getWidget(xmlcontent.getValue("Toast", Locale.ENGLISH));
        assertNotNull(widget);
        assertEquals(TestCustomInputWidgetImpl.class.getName(), widget.getClass().getName());
        assertEquals("Configuration for Toast", handler.getConfiguration(xmlcontent.getValue("Toast", Locale.ENGLISH)));
        // custom widget configuration has extended the handler String
        assertEquals("Configuration for Toast[some addition here]", widget.getConfiguration());
    }

    /**
     * Test resolving the links from an XML content.<p>
     * 
     * @throws Exception in case something goes wrong
     */
    public void testLinkResolver() throws Exception {

        CmsObject cms = getCmsObject();
        echo("Testing link resolver for XML content");

        CmsXmlEntityResolver resolver = new CmsXmlEntityResolver(cms);

        String content;

        // unmarshal content definition
        content = CmsFileUtil.readFile("org/opencms/xml/content/xmlcontent-definition-2.xsd", CmsEncoder.ENCODING_UTF_8);
        CmsXmlContentDefinition definition = CmsXmlContentDefinition.unmarshal(content, SCHEMA_SYSTEM_ID_2, resolver);
        // store content definition in entitiy resolver
        CmsXmlEntityResolver.cacheSystemId(SCHEMA_SYSTEM_ID_2, content.getBytes(CmsEncoder.ENCODING_UTF_8));

        // now create the XML content
        content = CmsFileUtil.readFile("org/opencms/xml/content/xmlcontent-2.xml", CmsEncoder.ENCODING_UTF_8);
        CmsXmlContent xmlcontent = CmsXmlContentFactory.unmarshal(content, CmsEncoder.ENCODING_UTF_8, resolver);

        assertTrue(xmlcontent.hasValue("Html", Locale.ENGLISH));
        assertTrue(xmlcontent.hasValue("VfsLink", Locale.ENGLISH));
        assertSame(definition.getContentHandler().getClass().getName(), CmsDefaultXmlContentHandler.class.getName());

        CmsXmlHtmlValue htmlValue = (CmsXmlHtmlValue)xmlcontent.getValue("Html", Locale.ENGLISH);
        CmsXmlVfsFileValue vfsValue = (CmsXmlVfsFileValue)xmlcontent.getValue("VfsLink", Locale.ENGLISH);

        // must set the value again to ensure link table is properly initialized
        htmlValue.setStringValue(cms, htmlValue.getStringValue(cms));
        vfsValue.setStringValue(cms, vfsValue.getStringValue(cms));

        Iterator i;
        CmsLinkTable table;

        String retranslatedOutput = htmlValue.getStringValue(cms);
        assertEquals("Incorrect links in resulting output", "<a href=\"http://www.alkacon.com\">Alkacon</a>\n"
            + "<a href=\"/data/opencms/index.html\">Index page</a>\n"
            + "<a href=\"/data/opencms/folder1/index.html?a=b&amp;c=d#anchor\">Index page</a>\n"
            + "<a href=\"/data/opencms/folder1/index.html?a2=b2&amp;c2=d2\">Index page with unescaped ampersand</a>",
        // note that the & in the links appear correctly escaped here
            retranslatedOutput.trim());

        table = htmlValue.getLinkTable();
        assertEquals(4, table.size());

        i = table.iterator();
        int result = 0;
        while (i.hasNext()) {
            // iterate all links and check if the required values are found
            CmsLink link = (CmsLink)i.next();
            if (link.getTarget().equals("/sites/default/index.html") && link.isInternal()) {
                result++;
            } else if (link.getTarget().equals("http://www.alkacon.com") && !link.isInternal()) {
                result++;
            } else if (link.getTarget().equals("/sites/default/folder1/index.html")
                && link.getQuery().equals("a=b&c=d") // at this point the & in the link should be unescaped
                && link.getAnchor().equals("anchor")
                && link.isInternal()) {
                result++;
            } else if (link.getTarget().equals("/sites/default/folder1/index.html")
                && link.getQuery().equals("a2=b2&c2=d2") // at this point the & in the link should be unescaped
                && link.isInternal()) {
                result++;
            }
        }

        assertEquals(4, result);

        CmsLink link = vfsValue.getLink(cms);
        assertEquals("/sites/default/index.html", link.getTarget());
        assertTrue(link.isInternal());
        assertEquals("/index.html", vfsValue.getStringValue(cms));
    }

    /**
     * Test resolving a {@link CmsXmlVarLinkValue} in an XML content.<p>
     * 
     * @throws Exception in case something goes wrong
     */
    public void testVarLinkResolver() throws Exception {

        CmsObject cms = getCmsObject();
        echo("Testing link CmsXmlVarLinkValue in an XML content");

        CmsXmlEntityResolver resolver = new CmsXmlEntityResolver(cms);

        String content;

        // unmarshal content definition
        content = CmsFileUtil.readFile(
            "org/opencms/xml/content/xmlcontent-VarLink-definition-1.xsd",
            CmsEncoder.ENCODING_UTF_8);
        String schemaId = "http://www.opencms.org/testVarLink1.xsd";
        CmsXmlContentDefinition definition = CmsXmlContentDefinition.unmarshal(content, schemaId, resolver);
        // store content definition in entitiy resolver
        CmsXmlEntityResolver.cacheSystemId(schemaId, content.getBytes(CmsEncoder.ENCODING_UTF_8));

        // now create the XML content
        content = CmsFileUtil.readFile("org/opencms/xml/content/xmlcontent-VarLink-1.xml", CmsEncoder.ENCODING_UTF_8);
        CmsXmlContent xmlcontent = CmsXmlContentFactory.unmarshal(content, CmsEncoder.ENCODING_UTF_8, resolver);

        assertTrue(xmlcontent.hasValue("VfsLink", Locale.ENGLISH));
        assertTrue(xmlcontent.hasValue("VarLink", Locale.ENGLISH));
        assertSame(definition.getContentHandler().getClass().getName(), CmsDefaultXmlContentHandler.class.getName());

        CmsXmlVfsFileValue vfsValue = (CmsXmlVfsFileValue)xmlcontent.getValue("VfsLink", Locale.ENGLISH);
        CmsXmlVarLinkValue varValue1 = (CmsXmlVarLinkValue)xmlcontent.getValue("VarLink", Locale.ENGLISH, 0);
        CmsXmlVarLinkValue varValue2 = (CmsXmlVarLinkValue)xmlcontent.getValue("VarLink", Locale.ENGLISH, 1);

        // make sure the XML unmarshals as expected
        CmsLink link = vfsValue.getLink(cms);
        assertEquals("/sites/default/index.html", link.getTarget());
        assertTrue(link.isInternal());
        assertEquals("/index.html", vfsValue.getStringValue(cms));

        CmsLink varLink1 = varValue1.getLink(cms);
        assertEquals("/sites/default/index.html", varLink1.getTarget());
        assertTrue(varLink1.isInternal());
        assertEquals("/index.html", varValue1.getStringValue(cms));

        CmsLink varLink2 = varValue2.getLink(cms);
        assertEquals("http://www.alkacon.com", varLink2.getTarget());
        assertFalse(varLink2.isInternal());

        // now set some VarLinks with different types of targets

        // simple external link
        CmsXmlVarLinkValue varVal;
        CmsLink varLink;

        varVal = (CmsXmlVarLinkValue)xmlcontent.addValue(cms, "VarLink", Locale.ENGLISH, 2);
        varVal.setStringValue(cms, "http://www.opencms.org");
        varLink = varVal.getLink(cms);
        assertEquals("http://www.opencms.org", varLink.getTarget());
        assertFalse(varLink.isInternal());

        // internal link to an existing file
        varVal = (CmsXmlVarLinkValue)xmlcontent.addValue(cms, "VarLink", Locale.ENGLISH, 3);
        varVal.setStringValue(cms, "/folder1/page1.html");
        varLink = varVal.getLink(cms);
        assertEquals("/sites/default/folder1/page1.html", varLink.getTarget());
        assertTrue(varLink.isInternal());
        assertEquals("/folder1/page1.html", varVal.getStringValue(cms));

        // internal link to a not existing file
        varVal = (CmsXmlVarLinkValue)xmlcontent.addValue(cms, "VarLink", Locale.ENGLISH, 4);
        varVal.setStringValue(cms, "/folder_notexist/page_i_dont_exist.html");
        varLink = varVal.getLink(cms);
        assertEquals("/sites/default/folder_notexist/page_i_dont_exist.html", varLink.getTarget());
        assertTrue(varLink.isInternal());
        assertEquals("/folder_notexist/page_i_dont_exist.html", varVal.getStringValue(cms));

        // internal link using the server prefix to an existing file
        varVal = (CmsXmlVarLinkValue)xmlcontent.addValue(cms, "VarLink", Locale.ENGLISH, 5);
        varVal.setStringValue(cms, "http://localhost:8080/folder1/page1.html");
        varLink = varVal.getLink(cms);
        assertEquals("/sites/default/folder1/page1.html", varLink.getTarget());
        assertTrue(varLink.isInternal());
        assertEquals("/folder1/page1.html", varVal.getStringValue(cms));

        // internal link using the server prefix to a not existing file
        varVal = (CmsXmlVarLinkValue)xmlcontent.addValue(cms, "VarLink", Locale.ENGLISH, 6);
        varVal.setStringValue(cms, "http://localhost:8080/folder_notexist/page_i_dont_exist.html");
        varLink = varVal.getLink(cms);
        assertEquals("/sites/default/folder_notexist/page_i_dont_exist.html", varLink.getTarget());
        assertTrue(varLink.isInternal());
        assertEquals("/folder_notexist/page_i_dont_exist.html", varVal.getStringValue(cms));

        // output the XML content after modifications
        echo("XML Content after VarLink modification:");
        echo(xmlcontent.toString());
        echo("-----------------");

        // create the content definition
        CmsXmlContentDefinition cd = CmsXmlContentDefinition.unmarshal(content, schemaId, resolver);
        CmsXmlContent newContent = CmsXmlContentFactory.createDocument(
            cms,
            Locale.ENGLISH,
            CmsEncoder.ENCODING_UTF_8,
            cd);

        echo("New XML Content for VarLink:");
        echo(newContent.toString());
        echo("-----------------");

        // validate the XML of the created XML content
        xmlcontent.validateXmlStructure(resolver);

        // validate the XML of the created XML content
        newContent.validateXmlStructure(resolver);
    }

    /**
     * Tests the macros in messages and default values.<p>
     * 
     * @throws Exception in case something goes wrong
     */
    public void testMacros() throws Exception {

        CmsObject cms = getCmsObject();
        echo("Testing macros in the XML content");

        CmsUser admin = cms.getRequestContext().currentUser();
        admin.setFirstname("Hans");
        admin.setLastname("Mustermann");
        admin.setEmail("hans.mustermann@germany.de");
        admin.setAddress("Heidestraße 17, München");
        cms.writeUser(admin);

        CmsXmlEntityResolver resolver = new CmsXmlEntityResolver(cms);

        String content;
        CmsXmlContent xmlcontent;

        // unmarshal content definition
        content = CmsFileUtil.readFile("org/opencms/xml/content/xmlcontent-definition-9.xsd", CmsEncoder.ENCODING_UTF_8);
        // store content definition in entitiy resolver
        CmsXmlEntityResolver.cacheSystemId(SCHEMA_SYSTEM_ID_9, content.getBytes(CmsEncoder.ENCODING_UTF_8));

        // now read the XML content
        content = CmsFileUtil.readFile("org/opencms/xml/content/xmlcontent-9.xml", CmsEncoder.ENCODING_UTF_8);
        xmlcontent = CmsXmlContentFactory.unmarshal(content, CmsEncoder.ENCODING_UTF_8, resolver);

        CmsXmlContentErrorHandler errorHandler;

        I_CmsXmlContentValue value1;

        value1 = xmlcontent.getValue("Test", Locale.ENGLISH);
        value1.setStringValue(cms, "This produces a warning!");

        errorHandler = xmlcontent.validate(cms);
        assertFalse(errorHandler.hasErrors());
        assertTrue(errorHandler.hasWarnings());

        value1.setStringValue(cms, "This produces a warning and an error!");

        errorHandler = xmlcontent.validate(cms);
        assertTrue(errorHandler.hasErrors());
        assertTrue(errorHandler.hasWarnings());
        assertEquals(1, errorHandler.getErrors().size());
        assertEquals(1, errorHandler.getWarnings().size());

        value1 = xmlcontent.getValue("Toast", Locale.ENGLISH);
        value1.setStringValue(cms, "This produces a warning but no error!");

        errorHandler = xmlcontent.validate(cms);
        assertTrue(errorHandler.hasErrors());
        assertTrue(errorHandler.hasWarnings());
        assertEquals(1, errorHandler.getErrors(Locale.ENGLISH).size());
        assertEquals(2, errorHandler.getWarnings(Locale.ENGLISH).size());

        value1 = xmlcontent.addValue(cms, "Option", Locale.ENGLISH, 0);
        assertEquals(
            "The author is: Hans Mustermann (Admin), Heidestraße 17, München - hans.mustermann@germany.de",
            value1.getStringValue(cms));

        value1 = xmlcontent.addValue(cms, "Option", Locale.GERMAN, 0);
        assertEquals(
            "Der Autor ist: Hans Mustermann (Admin), Heidestraße 17, München - hans.mustermann@germany.de",
            value1.getStringValue(cms));

        // output the current document
        System.out.println(xmlcontent.toString());
        // re-create the document
        xmlcontent = CmsXmlContentFactory.unmarshal(xmlcontent.toString(), CmsEncoder.ENCODING_UTF_8, resolver);
        // validate the XML structure
        xmlcontent.validateXmlStructure(resolver);

        errorHandler = xmlcontent.validate(cms);
        assertTrue(errorHandler.hasErrors());
        assertTrue(errorHandler.hasWarnings());
        assertEquals(1, errorHandler.getErrors(Locale.ENGLISH).size());
        assertEquals(2, errorHandler.getWarnings(Locale.ENGLISH).size());
    }

    /**
     * Tests the element mappings from the appinfo node.<p>
     * 
     * @throws Exception in case something goes wrong
     */
    public void testMappings() throws Exception {

        CmsObject cms = getCmsObject();
        echo("Testing mapping of values in the XML content");

        CmsXmlEntityResolver resolver = new CmsXmlEntityResolver(cms);

        String content;
        CmsXmlContent xmlcontent;

        // unmarshal content definition
        content = CmsFileUtil.readFile("org/opencms/xml/content/xmlcontent-definition-8.xsd", CmsEncoder.ENCODING_UTF_8);
        // store content definition in entitiy resolver
        CmsXmlEntityResolver.cacheSystemId(SCHEMA_SYSTEM_ID_8, content.getBytes(CmsEncoder.ENCODING_ISO_8859_1));

        // now read the XML content
        content = CmsFileUtil.readFile("org/opencms/xml/content/xmlcontent-8.xml", CmsEncoder.ENCODING_ISO_8859_1);
        xmlcontent = CmsXmlContentFactory.unmarshal(content, CmsEncoder.ENCODING_ISO_8859_1, resolver);
        // validate the XML structure
        xmlcontent.validateXmlStructure(resolver);

        String resourcename = "/mappingtext.html";
        // create a file in the VFS with this content (required for mappings to work)
        cms.createResource(
            resourcename,
            OpenCms.getResourceManager().getResourceType("xmlcontent").getTypeId(),
            content.getBytes(CmsEncoder.ENCODING_ISO_8859_1),
            Collections.EMPTY_LIST);

        CmsFile file = cms.readFile(resourcename);
        xmlcontent = CmsXmlContentFactory.unmarshal(cms, file);

        CmsProperty titleProperty = cms.readPropertyObject(resourcename, CmsPropertyDefinition.PROPERTY_TITLE, false);
        assertSame(titleProperty, CmsProperty.getNullProperty());

        CmsProperty localeProperty = cms.readPropertyObject(resourcename, CmsPropertyDefinition.PROPERTY_LOCALE, false);
        assertSame(localeProperty, CmsProperty.getNullProperty());

        CmsProperty navImageProperty = cms.readPropertyObject(
            resourcename,
            CmsPropertyDefinition.PROPERTY_NAVIMAGE,
            false);
        assertSame(navImageProperty, CmsProperty.getNullProperty());

        CmsProperty navInfoProperty = cms.readPropertyObject(
            resourcename,
            CmsPropertyDefinition.PROPERTY_NAVINFO,
            false);
        assertSame(navInfoProperty, CmsProperty.getNullProperty());

        String titleStr = "This must be the Title";
        String navImageStr = "This is the String with xpath String[2]";
        String navInfoStr = "Here we have the String with xpath String[3]";

        I_CmsXmlContentValue value;
        value = xmlcontent.addValue(cms, "String", Locale.ENGLISH, 0);
        value.setStringValue(cms, titleStr);

        // set values for Title[2] and Title[3]
        CmsXmlContentValueSequence seq = xmlcontent.getValueSequence("String", Locale.ENGLISH);
        assertEquals(1, seq.getElementCount());
        value = seq.addValue(cms, 1);
        value.setStringValue(cms, navImageStr);
        value = seq.addValue(cms, 2);
        value.setStringValue(cms, navInfoStr);

        String localeStr = "en";
        value = xmlcontent.addValue(cms, "Locale", Locale.ENGLISH, 0);
        value.setStringValue(cms, localeStr);

        file.setContents(xmlcontent.toString().getBytes(CmsEncoder.ENCODING_ISO_8859_1));
        cms.writeFile(file);

        titleProperty = cms.readPropertyObject(resourcename, CmsPropertyDefinition.PROPERTY_TITLE, false);
        assertEquals(titleStr, titleProperty.getValue());
        assertEquals(titleStr, titleProperty.getStructureValue());
        assertNull(titleProperty.getResourceValue());

        // check multiple mappings
        CmsProperty myTitleProperty = cms.readPropertyObject(resourcename, "MyTitle", false);
        assertEquals(titleStr, myTitleProperty.getValue());
        assertEquals(titleStr, myTitleProperty.getStructureValue());
        assertNull(myTitleProperty.getResourceValue());

        navImageProperty = cms.readPropertyObject(resourcename, CmsPropertyDefinition.PROPERTY_NAVIMAGE, false);
        assertEquals(navImageStr, navImageProperty.getValue());
        assertEquals(navImageStr, navImageProperty.getResourceValue());
        assertNull(navImageProperty.getStructureValue());

        navInfoProperty = cms.readPropertyObject(resourcename, CmsPropertyDefinition.PROPERTY_NAVINFO, false);
        assertEquals(navInfoStr, navInfoProperty.getValue());
        assertEquals(navInfoStr, navInfoProperty.getStructureValue());
        assertNull(navInfoProperty.getResourceValue());

        localeProperty = cms.readPropertyObject(resourcename, CmsPropertyDefinition.PROPERTY_LOCALE, false);
        assertEquals(localeStr, localeProperty.getValue());
        assertEquals(localeStr, localeProperty.getResourceValue());
        assertNull(localeProperty.getStructureValue());
    }

    /**
     * Tests element mappings fom XML content to a property list.<p>
     * 
     * @throws Exception if the test fails
     */
    public void testMappingsAsList() throws Exception {

        CmsObject cms = getCmsObject();
        echo("Testing element mappings fom XML content to a property list");

        CmsXmlEntityResolver resolver = new CmsXmlEntityResolver(cms);

        String content;
        CmsXmlContent xmlcontent;

        // please note: XML schema 8 already in the cache from previous tests

        // now read the XML content
        content = CmsFileUtil.readFile("org/opencms/xml/content/xmlcontent-8.xml", CmsEncoder.ENCODING_ISO_8859_1);
        xmlcontent = CmsXmlContentFactory.unmarshal(content, CmsEncoder.ENCODING_ISO_8859_1, resolver);
        // validate the XML structure
        xmlcontent.validateXmlStructure(resolver);

        String resourcename = "/mappinglist.html";
        // create a file in the VFS with this content (required for mappings to work)
        cms.createResource(
            resourcename,
            OpenCms.getResourceManager().getResourceType("xmlcontent").getTypeId(),
            content.getBytes(CmsEncoder.ENCODING_ISO_8859_1),
            Collections.EMPTY_LIST);

        CmsFile file = cms.readFile(resourcename);
        xmlcontent = CmsXmlContentFactory.unmarshal(cms, file);

        CmsProperty prop;
        prop = cms.readPropertyObject(resourcename, CmsPropertyDefinition.PROPERTY_DESCRIPTION, false);
        assertSame(prop, CmsProperty.getNullProperty());

        I_CmsXmlContentValue value;
        CmsXmlContentValueSequence seq = xmlcontent.getValueSequence("VfsFile", Locale.ENGLISH);
        assertEquals(0, seq.getElementCount());

        String res1 = "/index.html";
        String res2 = "/xmlcontent/";
        String res3 = "/xmlcontent/article_0001.html";
        String res4 = "/folder1/index.html";

        String sr = cms.getRequestContext().getSiteRoot();
        String propValue = sr + res1 + "|" + sr + res2 + "|" + sr + res3 + "|" + sr + res4;

        value = seq.addValue(cms, 0);
        value.setStringValue(cms, res1);
        value = seq.addValue(cms, 1);
        value.setStringValue(cms, res2);
        value = seq.addValue(cms, 2);
        value.setStringValue(cms, res3);
        value = seq.addValue(cms, 3);
        value.setStringValue(cms, res4);

        assertEquals(4, seq.getElementCount());
        // validate the XML structure
        xmlcontent.validateXmlStructure(resolver);

        file.setContents(xmlcontent.toString().getBytes(CmsEncoder.ENCODING_ISO_8859_1));
        cms.writeFile(file);

        // check for written property values as list
        prop = cms.readPropertyObject(resourcename, CmsPropertyDefinition.PROPERTY_DESCRIPTION, false);
        List list = prop.getValueList();
        assertNotNull(list);
        assertEquals(4, list.size());
        assertEquals(sr + res1, list.get(0));
        assertEquals(sr + res2, list.get(1));
        assertEquals(sr + res3, list.get(2));
        assertEquals(sr + res4, list.get(3));
        assertEquals(propValue, prop.getValue());
        assertEquals(propValue, prop.getStructureValue());
        assertNull(prop.getResourceValue());

        CmsProperty prop2;
        prop2 = cms.readPropertyObject(resourcename, CmsPropertyDefinition.PROPERTY_KEYWORDS, false);
        assertSame(prop2, CmsProperty.getNullProperty());

        I_CmsXmlContentValue value2;
        CmsXmlContentValueSequence seq2 = xmlcontent.getValueSequence("VfsFile2", Locale.ENGLISH);
        assertEquals(0, seq2.getElementCount());

        value2 = seq2.addValue(cms, 0);
        value2.setStringValue(cms, res1);
        value2 = seq2.addValue(cms, 1);
        value2.setStringValue(cms, res2);
        value2 = seq2.addValue(cms, 2);
        value2.setStringValue(cms, res3);
        value2 = seq2.addValue(cms, 3);
        value2.setStringValue(cms, res4);

        assertEquals(4, seq2.getElementCount());
        // validate the XML structure
        xmlcontent.validateXmlStructure(resolver);

        file.setContents(xmlcontent.toString().getBytes(CmsEncoder.ENCODING_ISO_8859_1));
        cms.writeFile(file);

        // check for written property values as list
        prop2 = cms.readPropertyObject(resourcename, CmsPropertyDefinition.PROPERTY_KEYWORDS, false);
        List list2 = prop.getValueList();
        assertNotNull(list2);
        assertEquals(4, list2.size());
        assertEquals(sr + res1, list2.get(0));
        assertEquals(sr + res2, list2.get(1));
        assertEquals(sr + res3, list2.get(2));
        assertEquals(sr + res4, list2.get(3));
        assertEquals(propValue, prop2.getValue());
        assertEquals(propValue, prop2.getResourceValue());
        assertNull(prop2.getStructureValue());

        CmsProperty prop3;
        prop3 = cms.readPropertyObject(resourcename, CmsPropertyDefinition.PROPERTY_NAVTEXT, false);
        assertSame(prop3, CmsProperty.getNullProperty());

        I_CmsXmlContentValue value3;
        CmsXmlContentValueSequence seq3 = xmlcontent.getValueSequence("VfsFile3", Locale.ENGLISH);
        assertEquals(0, seq3.getElementCount());

        value3 = seq3.addValue(cms, 0);
        value3.setStringValue(cms, res1);
        value3 = seq3.addValue(cms, 1);
        value3.setStringValue(cms, res2);
        value3 = seq3.addValue(cms, 2);
        value3.setStringValue(cms, res3);
        value3 = seq3.addValue(cms, 3);
        value3.setStringValue(cms, res4);

        assertEquals(4, seq3.getElementCount());
        // validate the XML structure
        xmlcontent.validateXmlStructure(resolver);

        file.setContents(xmlcontent.toString().getBytes(CmsEncoder.ENCODING_ISO_8859_1));
        cms.writeFile(file);

        // check for written property values as list
        prop3 = cms.readPropertyObject(resourcename, CmsPropertyDefinition.PROPERTY_NAVTEXT, false);
        List list3 = prop.getValueList();
        assertNotNull(list3);
        assertEquals(4, list3.size());
        assertEquals(sr + res1, list3.get(0));
        assertEquals(sr + res2, list3.get(1));
        assertEquals(sr + res3, list3.get(2));
        assertEquals(sr + res4, list3.get(3));
        assertEquals(propValue, prop3.getValue());
        assertEquals(propValue, prop3.getStructureValue());
        assertNull(prop3.getResourceValue());
    }

    /**
     * Tests the element mappings from the appinfo node for nested XML content.<p>
     * 
     * @throws Exception in case something goes wrong
     */
    public void testMappingsOfNestedContent() throws Exception {

        CmsObject cms = getCmsObject();
        echo("Testing mapping of values in the XML content with nested elements");

        CmsXmlEntityResolver resolver = new CmsXmlEntityResolver(cms);

        String content;
        CmsXmlContent xmlcontent;

        // unmarshal content definition
        content = CmsFileUtil.readFile("org/opencms/xml/content/xmlcontent-definition-7.xsd", CmsEncoder.ENCODING_UTF_8);
        // store content definition in entitiy resolver
        CmsXmlEntityResolver.cacheSystemId(SCHEMA_SYSTEM_ID_7, content.getBytes(CmsEncoder.ENCODING_UTF_8));

        // now read the XML content
        content = CmsFileUtil.readFile("org/opencms/xml/content/xmlcontent-7.xml", CmsEncoder.ENCODING_UTF_8);
        xmlcontent = CmsXmlContentFactory.unmarshal(content, CmsEncoder.ENCODING_UTF_8, resolver);

        String resourcename = "/mappingtest_nested.html";
        // create a file in the VFS with this content (required for mappings to work)
        cms.createResource(
            resourcename,
            OpenCms.getResourceManager().getResourceType("xmlcontent").getTypeId(),
            content.getBytes(CmsEncoder.ENCODING_ISO_8859_1),
            Collections.EMPTY_LIST);

        CmsFile file = cms.readFile(resourcename);
        xmlcontent = CmsXmlContentFactory.unmarshal(cms, file);

        CmsProperty titleProperty;
        titleProperty = cms.readPropertyObject(resourcename, CmsPropertyDefinition.PROPERTY_TITLE, false);
        assertSame(titleProperty, CmsProperty.getNullProperty());

        String titleStr = "This must be the Title (not nested)";
        I_CmsXmlContentValue value;
        value = xmlcontent.getValue("Test", Locale.ENGLISH);
        assertEquals(value.getStringValue(cms), "Another Test");
        value.setStringValue(cms, titleStr);

        String descStr = "This must be the Description (which IS nested)";
        value = xmlcontent.getValue("Cascade/Toast", Locale.ENGLISH);
        assertEquals(value.getStringValue(cms), "Toast");
        value.setStringValue(cms, descStr);

        file.setContents(xmlcontent.toString().getBytes(CmsEncoder.ENCODING_ISO_8859_1));
        cms.writeFile(file);

        titleProperty = cms.readPropertyObject(resourcename, CmsPropertyDefinition.PROPERTY_TITLE, false);
        assertEquals(titleStr, titleProperty.getValue());
        titleProperty = cms.readPropertyObject(resourcename, CmsPropertyDefinition.PROPERTY_DESCRIPTION, false);
        assertEquals(descStr, titleProperty.getValue());
    }

    /**
     * Tests the element mappings from the appinfo node if there is more then one locale.<p>
     * 
     * @throws Exception in case something goes wrong
     */
    public void testMappingsWithManyLocales() throws Exception {

        CmsObject cms = getCmsObject();
        echo("Testing mapping of values in the XML content with locales");

        CmsXmlEntityResolver resolver = new CmsXmlEntityResolver(cms);

        String content;
        CmsXmlContent xmlcontent;

        // unmarshal content definition
        content = CmsFileUtil.readFile("org/opencms/xml/content/xmlcontent-definition-8.xsd", CmsEncoder.ENCODING_UTF_8);
        // store content definition in entitiy resolver
        CmsXmlEntityResolver.cacheSystemId(SCHEMA_SYSTEM_ID_8, content.getBytes(CmsEncoder.ENCODING_ISO_8859_1));

        // now read the XML content
        content = CmsFileUtil.readFile("org/opencms/xml/content/xmlcontent-8.xml", CmsEncoder.ENCODING_ISO_8859_1);
        xmlcontent = CmsXmlContentFactory.unmarshal(content, CmsEncoder.ENCODING_ISO_8859_1, resolver);
        // validate the XML structure
        xmlcontent.validateXmlStructure(resolver);

        // create "en" property
        List properties = new ArrayList();
        properties.add(new CmsProperty(CmsPropertyDefinition.PROPERTY_LOCALE, Locale.ENGLISH.toString(), null));

        String resourcenameEn = "/mappingtext_en.html";
        String resourcenameDe = "/mappingtext_de.html";
        // create a file in the VFS with this content (required for mappings to work)
        cms.createResource(
            resourcenameEn,
            OpenCms.getResourceManager().getResourceType("xmlcontent").getTypeId(),
            content.getBytes(CmsEncoder.ENCODING_ISO_8859_1),
            properties);

        // copy the resource as a sibling to "de"
        cms.copyResource(resourcenameEn, resourcenameDe, CmsResource.COPY_AS_SIBLING);
        // now lock the "DE" sibling
        cms.changeLock(resourcenameDe);
        // add the "DE" locale property to the german version
        cms.writePropertyObject(resourcenameDe, new CmsProperty(
            CmsPropertyDefinition.PROPERTY_LOCALE,
            Locale.GERMAN.toString(),
            null));

        CmsFile file = cms.readFile(resourcenameDe);
        xmlcontent = CmsXmlContentFactory.unmarshal(cms, file);

        xmlcontent.addLocale(cms, Locale.GERMAN);
        if (!xmlcontent.hasLocale(Locale.ENGLISH)) {
            xmlcontent.addLocale(cms, Locale.ENGLISH);
        }

        // add EN property
        String titleStrEn = "This must be the Title in EN";
        I_CmsXmlContentValue value;
        value = xmlcontent.addValue(cms, "String", Locale.ENGLISH, 0);
        value.setStringValue(cms, titleStrEn);
        // add EN release date
        long timeEN = System.currentTimeMillis() - 1000;
        value = xmlcontent.addValue(cms, "DateTime", Locale.ENGLISH, 0);
        value.setStringValue(cms, "" + timeEN);

        // add DE property
        String titleStrDe = "Das ist der Title in DE";
        value = xmlcontent.addValue(cms, "String", Locale.GERMAN, 0);
        value.setStringValue(cms, titleStrDe);
        // add DE release date
        long timeDE = System.currentTimeMillis() - 5000;
        value = xmlcontent.addValue(cms, "DateTime", Locale.GERMAN, 0);
        value.setStringValue(cms, "" + timeDE);

        file.setContents(xmlcontent.toString().getBytes(CmsEncoder.ENCODING_ISO_8859_1));
        cms.writeFile(file);
        // finally unlock the resource
        cms.unlockResource(resourcenameDe);

        // now check if the properties have been assigned as required to the locales
        CmsProperty titlePropertyEn = cms.readPropertyObject(
            resourcenameEn,
            CmsPropertyDefinition.PROPERTY_TITLE,
            false);
        assertEquals(titleStrEn, titlePropertyEn.getValue());
        assertDateReleased(cms, resourcenameEn, timeEN);

        CmsProperty titlePropertyDe = cms.readPropertyObject(
            resourcenameDe,
            CmsPropertyDefinition.PROPERTY_TITLE,
            false);
        assertEquals(titleStrDe, titlePropertyDe.getValue());
        assertDateReleased(cms, resourcenameDe, timeDE);
    }

    /**
     * Test using a nested XML content schema.<p>
     * 
     * @throws Exception in case something goes wrong
     */
    public void testNestedSchema() throws Exception {

        CmsObject cms = getCmsObject();
        echo("Testing for nested XML content schemas");

        CmsXmlEntityResolver resolver = new CmsXmlEntityResolver(cms);

        String content;

        // unmarshal content definition
        content = CmsFileUtil.readFile("org/opencms/xml/content/xmlcontent-definition-4.xsd", CmsEncoder.ENCODING_UTF_8);
        CmsXmlContentDefinition definition = CmsXmlContentDefinition.unmarshal(content, SCHEMA_SYSTEM_ID_4, resolver);
        // store content definition in entitiy resolver
        CmsXmlEntityResolver.cacheSystemId(SCHEMA_SYSTEM_ID_4, content.getBytes(CmsEncoder.ENCODING_UTF_8));

        // now create the XML content
        content = CmsFileUtil.readFile("org/opencms/xml/content/xmlcontent-4.xml", CmsEncoder.ENCODING_UTF_8);
        CmsXmlContent xmlcontent = CmsXmlContentFactory.unmarshal(content, CmsEncoder.ENCODING_UTF_8, resolver);
        System.out.println(xmlcontent.toString());

        // validate the XML structure
        xmlcontent.validateXmlStructure(resolver);

        assertTrue(xmlcontent.hasValue("Title", Locale.ENGLISH));
        assertTrue(xmlcontent.hasValue("Title[1]", Locale.ENGLISH));
        assertTrue(xmlcontent.hasValue("Cascade[1]/Html[1]", Locale.ENGLISH));
        assertTrue(xmlcontent.hasValue("Cascade[1]/VfsLink[1]", Locale.ENGLISH));
        assertTrue(xmlcontent.hasValue("Cascade[1]/VfsLink[2]", Locale.ENGLISH));
        assertTrue(xmlcontent.hasValue("Cascade[2]/Html[1]", Locale.ENGLISH));
        assertTrue(xmlcontent.hasValue("Cascade[2]/VfsLink[1]", Locale.ENGLISH));
        assertTrue(xmlcontent.hasValue("Cascade[2]/Html", Locale.ENGLISH));
        assertTrue(xmlcontent.hasValue("Cascade[2]/VfsLink", Locale.ENGLISH));

        assertTrue(xmlcontent.hasValue("Cascade", Locale.ENGLISH));
        assertTrue(xmlcontent.hasValue("Cascade[1]", Locale.ENGLISH));
        assertTrue(xmlcontent.hasValue("Cascade[2]", Locale.ENGLISH));

        assertTrue(xmlcontent.hasValue("Cascade/Html", Locale.ENGLISH));
        assertTrue(xmlcontent.hasValue("Cascade/Html[1]", Locale.ENGLISH));
        assertTrue(xmlcontent.hasValue("Cascade/VfsLink", Locale.ENGLISH));
        assertTrue(xmlcontent.hasValue("Cascade/VfsLink[1]", Locale.ENGLISH));
        assertTrue(xmlcontent.hasValue("Cascade/VfsLink[2]", Locale.ENGLISH));

        // ensure Xpath index is based on 1, not 0
        assertFalse(xmlcontent.hasValue("Title[0]", Locale.ENGLISH));
        assertFalse(xmlcontent.hasValue("Cascade[0]", Locale.ENGLISH));

        I_CmsXmlContentValue value1;
        I_CmsXmlContentValue value2;

        value1 = xmlcontent.getValue("Title", Locale.ENGLISH);
        value2 = xmlcontent.getValue("Title[1]", Locale.ENGLISH);
        assertSame(value1, value2);

        value2 = xmlcontent.getValue("Title", Locale.ENGLISH, 0);
        assertSame(value1, value2);

        String xpath = "Cascade[1]/VfsLink[2]";
        value1 = xmlcontent.getValue(xpath, Locale.ENGLISH);
        assertEquals(xpath, value1.getPath());

        xpath = "Title[1]";
        value1 = xmlcontent.getValue(xpath, Locale.ENGLISH);
        assertEquals(xpath, value1.getPath());

        xpath = "Cascade/Html";
        value1 = xmlcontent.getValue(xpath, Locale.ENGLISH);
        assertEquals(CmsXmlUtils.createXpath(xpath, 1), value1.getPath());

        xpath = "Cascade";
        value1 = xmlcontent.getValue(xpath, Locale.ENGLISH);
        assertEquals(CmsXmlUtils.createXpath(xpath, 1), value1.getPath());

        assertSame(definition.getContentHandler().getClass().getName(), TestXmlContentHandler.class.getName());
    }

    /**
     * Test if the resource bundle in the schema definition is properly initialized.<p>
     * 
     * @throws Exception in case something goes wrong
     */
    public void testResourceBundle() throws Exception {

        CmsObject cms = getCmsObject();
        echo("Testing using different resource bundles in content handler for XML content");

        CmsXmlEntityResolver resolver = new CmsXmlEntityResolver(cms);

        String content;
        CmsXmlContentDefinition definition;

        // unmarshal content definition
        content = CmsFileUtil.readFile("org/opencms/xml/content/xmlcontent-definition-3.xsd", CmsEncoder.ENCODING_UTF_8);
        definition = CmsXmlContentDefinition.unmarshal(content, SCHEMA_SYSTEM_ID_3, resolver);

        I_CmsXmlContentHandler contentHandler;

        contentHandler = definition.getContentHandler();
        assertSame(definition.getContentHandler().getClass().getName(), TestXmlContentHandler.class.getName());
        assertNull(contentHandler.getMessages(Locale.ENGLISH));

        // unmarshal content definition
        content = CmsFileUtil.readFile("org/opencms/xml/content/xmlcontent-definition-8.xsd", CmsEncoder.ENCODING_UTF_8);
        definition = CmsXmlContentDefinition.unmarshal(content, SCHEMA_SYSTEM_ID_8, resolver);

        contentHandler = definition.getContentHandler();
        assertSame(definition.getContentHandler().getClass().getName(), CmsDefaultXmlContentHandler.class.getName());

        CmsMessages messages = contentHandler.getMessages(Locale.ENGLISH);
        assertNotNull(messages);
        assertEquals(
            "The following errors occurred when validating the form:",
            messages.key(org.opencms.xml.content.Messages.GUI_EDITOR_XMLCONTENT_VALIDATION_ERROR_TITLE_0));
    }

    /**
     * Test the validation of the value elements.<p>
     * 
     * @throws Exception in case something goes wrong
     */
    public void testValidation() throws Exception {

        CmsObject cms = getCmsObject();
        echo("Testing the validation for values in the XML content");

        CmsXmlEntityResolver resolver = new CmsXmlEntityResolver(cms);

        String content;
        CmsXmlContent xmlcontent;

        // unmarshal content definition
        content = CmsFileUtil.readFile("org/opencms/xml/content/xmlcontent-definition-7.xsd", CmsEncoder.ENCODING_UTF_8);
        // store content definition in entitiy resolver
        CmsXmlEntityResolver.cacheSystemId(SCHEMA_SYSTEM_ID_7, content.getBytes(CmsEncoder.ENCODING_UTF_8));

        // now read the XML content
        content = CmsFileUtil.readFile("org/opencms/xml/content/xmlcontent-7.xml", CmsEncoder.ENCODING_UTF_8);
        xmlcontent = CmsXmlContentFactory.unmarshal(content, CmsEncoder.ENCODING_UTF_8, resolver);

        // add 2 deep cascaded nodes
        xmlcontent.addValue(cms, "DeepCascade", Locale.ENGLISH, 0);
        xmlcontent.addValue(cms, "DeepCascade", Locale.ENGLISH, 1);
        xmlcontent.addLocale(cms, Locale.GERMAN);
        xmlcontent.addValue(cms, "DeepCascade", Locale.GERMAN, 0);
        // output the current document
        System.out.println(xmlcontent.toString());

        CmsXmlContentErrorHandler errorHandler;

        // perform a first validation - the must be no errors or warnings reported
        errorHandler = xmlcontent.validate(cms);
        assertFalse(errorHandler.hasErrors());
        assertFalse(errorHandler.hasWarnings());

        I_CmsXmlContentValue value1;

        value1 = xmlcontent.getValue("Test", Locale.ENGLISH);
        value1.setStringValue(cms, "This produces a warning!");

        errorHandler = xmlcontent.validate(cms);
        assertFalse(errorHandler.hasErrors());
        assertTrue(errorHandler.hasWarnings());

        value1.setStringValue(cms, "This produces a warning and an error!");

        errorHandler = xmlcontent.validate(cms);
        assertTrue(errorHandler.hasErrors());
        assertTrue(errorHandler.hasWarnings());
        assertEquals(1, errorHandler.getErrors().size());
        assertEquals(1, errorHandler.getWarnings().size());

        value1 = xmlcontent.getValue("Toast", Locale.ENGLISH);
        value1.setStringValue(cms, "This produces a warning but no error!");

        errorHandler = xmlcontent.validate(cms);
        assertTrue(errorHandler.hasErrors());
        assertTrue(errorHandler.hasWarnings());
        assertEquals(1, errorHandler.getErrors(Locale.ENGLISH).size());
        assertEquals(2, errorHandler.getWarnings(Locale.ENGLISH).size());

        value1 = xmlcontent.addValue(cms, "Option", Locale.ENGLISH, 0);
        assertEquals("Default value from the appinfos", value1.getStringValue(cms));

        // output the current document
        System.out.println(xmlcontent.toString());
        // re-create the document
        xmlcontent = CmsXmlContentFactory.unmarshal(xmlcontent.toString(), CmsEncoder.ENCODING_UTF_8, resolver);
        // validate the XML structure
        xmlcontent.validateXmlStructure(resolver);

        value1 = xmlcontent.getValue("DeepCascade[1]/Cascade[1]/VfsLink", Locale.ENGLISH);
        value1.setStringValue(cms, "/system/workplace/warning");

        value1 = xmlcontent.getValue("DeepCascade[1]/Cascade[1]/Html", Locale.ENGLISH);
        value1.setStringValue(cms, "This HTML contains an error!");

        value1 = xmlcontent.addValue(cms, "DeepCascade[1]/Cascade[1]/Option", Locale.ENGLISH, 0);
        assertEquals("Default value from the XML", value1.getStringValue(cms));

        // output the current document
        System.out.println(xmlcontent.toString());
        // re-create the document
        xmlcontent = CmsXmlContentFactory.unmarshal(xmlcontent.toString(), CmsEncoder.ENCODING_UTF_8, resolver);
        // validate the XML structure
        xmlcontent.validateXmlStructure(resolver);

        errorHandler = xmlcontent.validate(cms);
        assertTrue(errorHandler.hasErrors());
        assertTrue(errorHandler.hasWarnings());
        assertEquals(3, errorHandler.getErrors(Locale.ENGLISH).size());
        assertEquals(2, errorHandler.getWarnings(Locale.ENGLISH).size());
    }

    /**
     * Extended test for the validation of the value elements.<p>
     * 
     * @throws Exception in case something goes wrong
     */
    public void testValidationExtended() throws Exception {

        CmsObject cms = getCmsObject();
        echo("Extended test for the validation of values in the XML content");

        CmsXmlEntityResolver resolver = new CmsXmlEntityResolver(cms);

        String content;
        CmsXmlContent xmlcontent;

        // unmarshal content definition
        content = CmsFileUtil.readFile("org/opencms/xml/content/xmlcontent-definition-8.xsd", CmsEncoder.ENCODING_UTF_8);
        // store content definition in entitiy resolver
        CmsXmlEntityResolver.cacheSystemId(SCHEMA_SYSTEM_ID_8, content.getBytes(CmsEncoder.ENCODING_UTF_8));

        // now read the XML content
        content = CmsFileUtil.readFile("org/opencms/xml/content/xmlcontent-8.xml", CmsEncoder.ENCODING_UTF_8);
        xmlcontent = CmsXmlContentFactory.unmarshal(content, CmsEncoder.ENCODING_UTF_8, resolver);
        // validate the XML structure
        xmlcontent.validateXmlStructure(resolver);

        CmsXmlContentValueSequence sequence;
        I_CmsXmlContentValue value;

        sequence = xmlcontent.getValueSequence("String", Locale.ENGLISH);
        value = sequence.addValue(cms, 0);
        value.setStringValue(cms, "This is a String that contains an error and a warning!");

        // validate the XML structure (no error caused here)
        xmlcontent.validateXmlStructure(resolver);

        CmsXmlContentErrorHandler errorHandler;

        errorHandler = xmlcontent.validate(cms);
        assertTrue(errorHandler.hasErrors());
        assertTrue(errorHandler.hasWarnings());
        assertTrue(errorHandler.hasErrors(Locale.ENGLISH));
        assertFalse(errorHandler.hasErrors(Locale.GERMAN));
        assertFalse(errorHandler.hasErrors(Locale.FRENCH));
        assertEquals(1, errorHandler.getErrors().size());
        assertEquals(1, errorHandler.getErrors(Locale.ENGLISH).size());
        assertEquals(1, errorHandler.getWarnings(Locale.ENGLISH).size());

        value.setStringValue(cms, "This is a nice String");
        errorHandler = xmlcontent.validate(cms);
        assertFalse(errorHandler.hasErrors());
        assertFalse(errorHandler.hasWarnings());

        sequence = xmlcontent.getValueSequence("DateTime", Locale.ENGLISH);
        value = sequence.addValue(cms, 0);
        value.setStringValue(cms, "invalid!");

        boolean error = true;
        try {
            xmlcontent.validateXmlStructure(resolver);
        } catch (Exception e) {
            error = false;
        }
        if (error) {
            fail("Invalid value was possible for DateTime");
        }

        errorHandler = xmlcontent.validate(cms);
        assertTrue(errorHandler.hasErrors());
        assertFalse(errorHandler.hasWarnings());
        assertEquals(1, errorHandler.getErrors(Locale.ENGLISH).size());

        value.setStringValue(cms, String.valueOf(System.currentTimeMillis()));
        xmlcontent.validateXmlStructure(resolver);
        errorHandler = xmlcontent.validate(cms);
        assertFalse(errorHandler.hasErrors());
        assertFalse(errorHandler.hasWarnings());

        sequence = xmlcontent.getValueSequence("Color", Locale.ENGLISH);
        value = sequence.addValue(cms, 0);
        value.setStringValue(cms, "invalid!");

        error = true;
        try {
            xmlcontent.validateXmlStructure(resolver);
        } catch (Exception e) {
            error = false;
        }
        if (error) {
            fail("Invalid value was possible for Color");
        }

        errorHandler = xmlcontent.validate(cms);
        assertTrue(errorHandler.hasErrors());
        assertFalse(errorHandler.hasWarnings());
        assertEquals(1, errorHandler.getErrors(Locale.ENGLISH).size());

        value.setStringValue(cms, "#fff");
        xmlcontent.validateXmlStructure(resolver);
        errorHandler = xmlcontent.validate(cms);
        assertTrue(errorHandler.hasErrors());
        assertFalse(errorHandler.hasWarnings());
        assertEquals(1, errorHandler.getErrors(Locale.ENGLISH).size());

        // test custom error message
        assertEquals("A valid HTML color value (e.g. #ffffff) is required", errorHandler.getErrors(Locale.ENGLISH).get(
            value.getPath()));

        value.setStringValue(cms, "#ffffff");
        xmlcontent.validateXmlStructure(resolver);
        errorHandler = xmlcontent.validate(cms);
        assertFalse(errorHandler.hasErrors());
        assertFalse(errorHandler.hasWarnings());

        sequence = xmlcontent.getValueSequence("Locale", Locale.ENGLISH);
        value = sequence.addValue(cms, 0);
        value.setStringValue(cms, "invalid!");

        error = true;
        try {
            xmlcontent.validateXmlStructure(resolver);
        } catch (Exception e) {
            error = false;
        }
        if (error) {
            fail("Invalid value was possible for Locale");
        }

        errorHandler = xmlcontent.validate(cms);
        assertTrue(errorHandler.hasErrors());
        assertFalse(errorHandler.hasWarnings());
        assertEquals(1, errorHandler.getErrors(Locale.ENGLISH).size());

        value.setStringValue(cms, Locale.GERMANY.toString());
        xmlcontent.validateXmlStructure(resolver);
        errorHandler = xmlcontent.validate(cms);
        assertTrue(errorHandler.hasErrors());
        assertFalse(errorHandler.hasWarnings());
        assertEquals(1, errorHandler.getErrors(Locale.ENGLISH).size());

        value.setStringValue(cms, Locale.GERMAN.toString());
        xmlcontent.validateXmlStructure(resolver);
        errorHandler = xmlcontent.validate(cms);
        assertFalse(errorHandler.hasErrors());
        assertFalse(errorHandler.hasWarnings());

        // output the current document
        System.out.println(xmlcontent.toString());
    }

    /**
     * Test for the validation with different locales.<p>
     * 
     * @throws Exception in case something goes wrong
     */
    public void testValidationLocale() throws Exception {

        CmsObject cms = getCmsObject();
        echo("Test for the validation of multiple locale values in the XML content");

        CmsXmlEntityResolver resolver = new CmsXmlEntityResolver(cms);

        String content;
        CmsXmlContent xmlcontent;

        // unmarshal content definition
        content = CmsFileUtil.readFile("org/opencms/xml/content/xmlcontent-definition-8.xsd", CmsEncoder.ENCODING_UTF_8);
        // store content definition in entitiy resolver
        CmsXmlEntityResolver.cacheSystemId(SCHEMA_SYSTEM_ID_8, content.getBytes(CmsEncoder.ENCODING_UTF_8));

        // now read the XML content
        content = CmsFileUtil.readFile("org/opencms/xml/content/xmlcontent-8.xml", CmsEncoder.ENCODING_UTF_8);
        xmlcontent = CmsXmlContentFactory.unmarshal(content, CmsEncoder.ENCODING_UTF_8, resolver);
        // validate the XML structure
        xmlcontent.validateXmlStructure(resolver);

        CmsXmlContentValueSequence sequence;
        I_CmsXmlContentValue value;

        xmlcontent.addLocale(cms, Locale.GERMAN);

        sequence = xmlcontent.getValueSequence("String", Locale.ENGLISH);
        value = sequence.addValue(cms, 0);
        value.setStringValue(cms, "This is a String that contains an error and a warning!");

        sequence = xmlcontent.getValueSequence("String", Locale.GERMAN);
        value = sequence.addValue(cms, 0);
        value.setStringValue(cms, "Dieser String enthällt einen Fehler (English: 'error') und eine Warnung!");

        // validate the XML structure (no error caused here)
        xmlcontent.validateXmlStructure(resolver);

        CmsXmlContentErrorHandler errorHandler;

        errorHandler = xmlcontent.validate(cms);
        assertTrue(errorHandler.hasErrors());
        assertTrue(errorHandler.hasWarnings());
        assertTrue(errorHandler.hasErrors(Locale.ENGLISH));
        assertTrue(errorHandler.hasErrors(Locale.GERMAN));
        assertFalse(errorHandler.hasErrors(Locale.FRENCH));
        assertEquals(2, errorHandler.getErrors().size());
        assertEquals(1, errorHandler.getErrors(Locale.ENGLISH).size());
        assertEquals(1, errorHandler.getWarnings(Locale.ENGLISH).size());
        assertEquals(1, errorHandler.getErrors(Locale.GERMAN).size());
        assertEquals(1, errorHandler.getWarnings(Locale.GERMAN).size());

        // output the current document
        System.out.println(xmlcontent.toString());
    }

    /**
     * Test the index of the value elements.<p>
     * 
     * @throws Exception in case something goes wrong
     */
    public void testValueIndex() throws Exception {

        CmsObject cms = getCmsObject();
        echo("Testing the value index for nodes in the XML content");

        CmsXmlEntityResolver resolver = new CmsXmlEntityResolver(cms);

        String content;
        CmsXmlContent xmlcontent;

        // unmarshal content definition
        content = CmsFileUtil.readFile("org/opencms/xml/content/xmlcontent-definition-7.xsd", CmsEncoder.ENCODING_UTF_8);
        // store content definition in entitiy resolver
        CmsXmlEntityResolver.cacheSystemId(SCHEMA_SYSTEM_ID_7, content.getBytes(CmsEncoder.ENCODING_UTF_8));

        // now read the XML content
        content = CmsFileUtil.readFile("org/opencms/xml/content/xmlcontent-7.xml", CmsEncoder.ENCODING_UTF_8);
        xmlcontent = CmsXmlContentFactory.unmarshal(content, CmsEncoder.ENCODING_UTF_8, resolver);

        // "fill up" the XML content with some values
        CmsXmlContentValueSequence toastSequence = xmlcontent.getValueSequence("Toast", Locale.ENGLISH);

        for (int i = 0; i < 2; i++) {
            I_CmsXmlContentValue value = toastSequence.addValue(cms, 0);
            value.setStringValue(cms, "Added toast value " + i);
        }

        // output the current document
        System.out.println(xmlcontent.toString());

        assertEquals(toastSequence.getElementCount(), 3);
        for (int i = 0; i < 3; i++) {
            I_CmsXmlContentValue value = toastSequence.getValue(i);
            assertEquals(i, value.getIndex());
        }

        // test min / max occurs values for value instances
        I_CmsXmlContentValue toastValue = toastSequence.getValue(1);
        assertEquals(1, toastValue.getMinOccurs());
        assertEquals(3, toastValue.getMaxOccurs());

        // check content handlers for nested elements
        I_CmsXmlContentValue value1 = xmlcontent.getValue("Test", Locale.ENGLISH);
        assertSame(
            TestXmlContentHandler.class.getName(),
            value1.getContentDefinition().getContentHandler().getClass().getName());

        value1 = xmlcontent.getValue("Cascade", Locale.ENGLISH);
        assertSame(
            TestXmlContentHandler.class.getName(),
            value1.getContentDefinition().getContentHandler().getClass().getName());

        value1 = xmlcontent.getValue("Cascade/Title", Locale.ENGLISH);
        assertSame(
            CmsDefaultXmlContentHandler.class.getName(),
            value1.getContentDefinition().getContentHandler().getClass().getName());
    }

    /**
     * Tests creating a XMl page with the API.<p>
     * 
     * @throws Exception in case something goes wrong
     */
    public void testXmlContentCreate() throws Exception {

        CmsObject cms = getCmsObject();
        echo("Testing creation of an XML Content:\n");
        CmsXmlEntityResolver resolver = new CmsXmlEntityResolver(cms);

        String filename = "xmlcontent.html";
        CmsResource res = cms.createResource(filename, OpenCmsTestCase.ARTICLE_TYPEID);
        CmsFile file = cms.readFile(res);
        String content = new String(file.getContents(), CmsEncoder.ENCODING_UTF_8);
        CmsXmlContent xmlcontent = CmsXmlContentFactory.unmarshal(content, CmsEncoder.ENCODING_UTF_8, resolver);
        assertTrue(xmlcontent.hasLocale(Locale.ENGLISH));
        try {
            xmlcontent.addLocale(cms, Locale.ENGLISH);
            fail("where is the default locale!?");
        } catch (Exception e) {
            // should fail
        }
        xmlcontent.getValue("Author", Locale.ENGLISH).setStringValue(cms, "Alkacon Software GmbH");
        file.setContents(xmlcontent.marshal());
        cms.writeFile(file);
    }
}