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

import static org.hamcrest.core.Is.is;
import static org.hamcrest.core.IsNot.not;
import static org.hamcrest.core.IsNull.notNullValue;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import javax.jcr.Node;
import javax.jcr.PathNotFoundException;
import javax.jcr.Property;
import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
import javax.jcr.Value;
import javax.jcr.nodetype.ConstraintViolationException;
import javax.jcr.nodetype.NoSuchNodeTypeException;
import javax.jcr.nodetype.NodeDefinitionTemplate;
import javax.jcr.nodetype.NodeTypeDefinition;
import javax.jcr.nodetype.NodeTypeManager;
import javax.jcr.nodetype.NodeTypeTemplate;
import javax.jcr.nodetype.PropertyDefinitionTemplate;
import javax.jcr.version.OnParentVersionAction;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.modeshape.common.FixFor;

public class MixinTest extends SingleUseAbstractTest {

    /*
     * Declares the following node types:
     * mixinTypeA with child node nodeA and property propertyA
     * mixinTypeB with child node nodeB and property propertyB
     * mixinTypeC with child node nodeA and property propertyB
     * mixinTypeWithAutoCreatedProperty with auto-created property propertyB
     * mixinTypeWithAutoCreatedChildNode with auto-created child node nodeB
     * mixinTypeC with child node nodeA and property propertyB
     * primaryTypeA with child node nodeA and property propertyA
     * mixinProtected with child node childProtected and property propertyProtected
     */
    static final String MIXIN_TYPE_A = "mixinTypeA";
    static final String MIXIN_TYPE_B = "mixinTypeB";
    static final String MIXIN_TYPE_C = "mixinTypeC";
    static final String BASE_PRIMARY_TYPE = "baseType";
    static final String MIXIN_TYPE_WITH_AUTO_PROP = "mixinTypeWithAutoCreatedProperty";
    static final String MIXIN_TYPE_WITH_AUTO_CHILD = "mixinTypeWithAutoCreatedChildNode";
    static final String PRIMARY_TYPE_A = "primaryTypeA";
    static final String MIXIN_PROTECTED = "mixinProtected";

    static final String PROPERTY_A = "propertyA";
    static final String PROPERTY_B = "propertyB";
    static final String PROPERTY_PROTECTED = "propertyProtected";

    static final String CHILD_NODE_A = "nodeA";
    static final String CHILD_NODE_B = "nodeB";
    static final String CHILD_PROTECTED = "childProtected";

    @Override
    @Before
    public void beforeEach() throws Exception {
        super.beforeEach();
        session.getWorkspace().getNodeTypeManager().registerNodeTypes(getTestTypes(), true);
    }

    @After
    public void after() throws Exception {
        if (session != null && session.isLive()) {
            session.logout();
        }
    }

    @Test( expected = IllegalArgumentException.class )
    public void shouldNotAllowNullMixinTypeName() throws Exception {
        session.getRootNode().addNode("a", PRIMARY_TYPE_A);
        session.save();

        Node rootNode = session.getRootNode();
        Node nodeA = rootNode.getNode("a");

        nodeA.canAddMixin(null);
    }

    @Test( expected = IllegalArgumentException.class )
    public void shouldNotAllowEmptyMixinTypeName() throws Exception {
        session.getRootNode().addNode("a", PRIMARY_TYPE_A);
        session.save();

        Node rootNode = session.getRootNode();
        Node nodeA = rootNode.getNode("a");

        nodeA.canAddMixin("");
    }

    @Test( expected = NoSuchNodeTypeException.class )
    public void shouldNotAllowInvalidMixinTypeName() throws Exception {
        session.getRootNode().addNode("a", PRIMARY_TYPE_A);
        session.save();

        Node rootNode = session.getRootNode();
        Node nodeA = rootNode.getNode("a");

        nodeA.canAddMixin("foo");
    }

    @Test
    public void shouldNotAllowAddingMixinToProtectedNodes() throws Exception {
        Node rootNode = session.getRootNode();
        Node systemNode = rootNode.getNode("jcr:system");

        assertThat(systemNode.getDefinition().isProtected(), is(true));
        assertThat(systemNode.canAddMixin("mix:versionable"), is(false));
    }

    @Test
    public void shouldAllowAddingMixinIfNoConflict() throws Exception {
        session.getRootNode().addNode("a", PRIMARY_TYPE_A).addMixin("mix:referenceable");
        session.save();

        Node rootNode = session.getRootNode();
        Node nodeA = rootNode.getNode("a");

        assertThat(nodeA.canAddMixin(MIXIN_TYPE_B), is(true));
    }

    @Test
    public void shouldAllowAddingMixinIfPrimaryTypeConflicts() throws Exception {
        session.getRootNode().addNode("a", PRIMARY_TYPE_A).addMixin("mix:referenceable");
        session.save();

        Node rootNode = session.getRootNode();
        Node nodeA = rootNode.getNode("a");

        assertThat(nodeA.canAddMixin(MIXIN_TYPE_C), is(true));
    }

    @Test
    public void shouldAllowAddingMixinIfMixinTypeConflicts() throws Exception {
        session.getRootNode().addNode("a", PRIMARY_TYPE_A).addMixin(MIXIN_TYPE_B);
        session.save();

        Node rootNode = session.getRootNode();
        Node nodeA = rootNode.getNode("a");

        assertThat(nodeA.canAddMixin(MIXIN_TYPE_C), is(true));
    }

    @Test
    public void shouldAutoCreateAutoCreatedPropertiesOnAddition() throws Exception {
        session.getRootNode().addNode("a");
        session.save();

        Node rootNode = session.getRootNode();
        Node nodeA = rootNode.getNode("a");

        assertThat(nodeA.canAddMixin(MIXIN_TYPE_WITH_AUTO_PROP), is(true));
        nodeA.addMixin(MIXIN_TYPE_WITH_AUTO_PROP);
        Property prop = nodeA.getProperty(PROPERTY_B);

        assertThat(prop, is(notNullValue()));
        assertThat(prop.getLong(), is(10L));
    }

    @Test
    public void shouldAutoCreateAutoCreatedChildNodesOnAddition() throws Exception {
        session.getRootNode().addNode("a");
        session.save();

        Node rootNode = session.getRootNode();
        Node nodeA = rootNode.getNode("a");

        assertThat(nodeA.canAddMixin(MIXIN_TYPE_WITH_AUTO_CHILD), is(true));
        nodeA.addMixin(MIXIN_TYPE_WITH_AUTO_CHILD);
        Node childNode = nodeA.getNode(CHILD_NODE_B);

        assertThat(childNode, is(notNullValue()));
        assertThat(childNode.getPrimaryNodeType().getName(), is("nt:unstructured"));
    }

    @Test( expected = ConstraintViolationException.class )
    public void shouldNotAllowAdditionIfResidualPropertyConflicts() throws Exception {
        session.getRootNode().addNode("a", "nt:unstructured");
        session.save();

        Node rootNode = session.getRootNode();
        Node nodeA = rootNode.getNode("a");
        Property b = nodeA.setProperty(PROPERTY_B, "Not a boolean");
        assertThat(b.getType(), is(not(PropertyType.UNDEFINED))); // see JavaDoc on javax.jcr.PropertyType.UNDEFINED

        assertThat(nodeA.canAddMixin(MIXIN_TYPE_WITH_AUTO_PROP), is(false));
        nodeA.addMixin(MIXIN_TYPE_WITH_AUTO_PROP);
    }

    @Test
    public void shouldAllowAdditionIfResidualPropertyDoesNotConflict() throws Exception {
        session.getRootNode().addNode("a", "nt:unstructured");
        session.save();

        Node rootNode = session.getRootNode();
        Node nodeA = rootNode.getNode("a");
        nodeA.setProperty(PROPERTY_B, 10L);

        assertThat(nodeA.canAddMixin(MIXIN_TYPE_WITH_AUTO_PROP), is(true));
        nodeA.addMixin(MIXIN_TYPE_WITH_AUTO_PROP);
    }

    @Test( expected = ConstraintViolationException.class )
    public void shouldNotAllowAdditionIfResidualChildNodeConflicts() throws Exception {
        session.getRootNode().addNode("a", "nt:unstructured").addNode(CHILD_NODE_B, "nt:base");
        session.save();

        Node rootNode = session.getRootNode();
        Node nodeA = rootNode.getNode("a");

        assertThat(nodeA.canAddMixin(MIXIN_TYPE_WITH_AUTO_CHILD), is(false));
        nodeA.addMixin(MIXIN_TYPE_WITH_AUTO_CHILD);
    }

    @Test
    public void shouldAllowAdditionIfResidualChildNodeDoesNotConflict() throws Exception {
        session.getRootNode().addNode("a", "nt:unstructured").addNode(CHILD_NODE_B, "nt:unstructured");
        session.save();

        Node rootNode = session.getRootNode();
        Node nodeA = rootNode.getNode("a");

        assertThat(nodeA.canAddMixin(MIXIN_TYPE_WITH_AUTO_CHILD), is(true));
        nodeA.addMixin(MIXIN_TYPE_WITH_AUTO_CHILD);
        session.save();

        Node newRootNode = session.getRootNode();
        Node newNodeA = newRootNode.getNode("a");

        Node newNodeB = newNodeA.getNode(CHILD_NODE_B);

        assertThat(newNodeA.isNodeType(MIXIN_TYPE_WITH_AUTO_CHILD), is(true));
        assertThat(newNodeB.isNodeType("nt:unstructured"), is(true));
        assertThat(newNodeB, is(notNullValue()));

        // Uncomment this to see the problem with the session cache not refreshing the immutable info for /a after a save
        // assertThat(newNodeB.getDefinition().getDeclaringNodeType().getName(), is(MIXIN_TYPE_B));

    }

    @Test
    public void shouldAllowSettingNewPropertyAfterAddingMixin() throws Exception {
        session.getRootNode().addNode("a", PRIMARY_TYPE_A).addMixin("mix:referenceable");
        session.save();

        Node rootNode = session.getRootNode();
        Node nodeA = rootNode.getNode("a");

        assertThat(nodeA.canAddMixin(MIXIN_TYPE_B), is(true));
        nodeA.addMixin(MIXIN_TYPE_B);

        nodeA.setProperty(PROPERTY_B, "some string");
        session.save();

        rootNode = session.getRootNode();
        nodeA = rootNode.getNode("a");
        Property propB = nodeA.getProperty(PROPERTY_B);

        assertThat(propB, is(notNullValue()));
        assertThat(propB.getValue().getString(), is("some string"));
    }

    @Test
    public void shouldAllowAddingNewChildNodeAfterAddingMixin() throws Exception {
        session.getRootNode().addNode("a", PRIMARY_TYPE_A).addMixin("mix:referenceable");
        session.save();

        Node rootNode = session.getRootNode();
        Node nodeA = rootNode.getNode("a");

        assertThat(nodeA.canAddMixin(MIXIN_TYPE_B), is(true));
        nodeA.addMixin(MIXIN_TYPE_B);

        nodeA.addNode(CHILD_NODE_B);
        session.save();

        Node newRootNode = session.getRootNode();
        Node newNodeA = newRootNode.getNode("a");
        Node newNodeB = newNodeA.getNode(CHILD_NODE_B);

        assertThat(newNodeB, is(notNullValue()));
        assertThat(newNodeB.getDefinition().getDeclaringNodeType().getName(), is(MIXIN_TYPE_B));
    }

    @Test
    public void shouldAllowRemovalIfNoConflict() throws Exception {
        Node a = session.getRootNode().addNode("a");
        a.addMixin(MIXIN_TYPE_B);
        a.addNode(CHILD_NODE_B);
        session.save();

        Node rootNode = session.getRootNode();
        Node nodeA = rootNode.getNode("a");
        nodeA.removeMixin(MIXIN_TYPE_B);
        session.save();

        rootNode = session.getRootNode();
        nodeA = rootNode.getNode("a");

        assertThat(nodeA.getMixinNodeTypes().length, is(0));
    }

    @Test
    public void shouldAllowRemovalIfExistingPropertyWouldHaveDefinition() throws Exception {
        Node a = session.getRootNode().addNode("a", "nt:unstructured");
        a.addMixin(MIXIN_TYPE_B);
        a.setProperty(PROPERTY_B, true);
        session.save();

        Node rootNode = session.getRootNode();
        Node nodeA = rootNode.getNode("a");
        nodeA.removeMixin(MIXIN_TYPE_B);
        session.save();

        rootNode = session.getRootNode();
        nodeA = rootNode.getNode("a");

        assertThat(nodeA.getMixinNodeTypes().length, is(0));
    }

    @Test( expected = ConstraintViolationException.class )
    public void shouldNotAllowRemovalIfExistingPropertyWouldHaveNoDefinition() throws Exception {
        Node a = session.getRootNode().addNode("a", BASE_PRIMARY_TYPE);
        a.addMixin(MIXIN_TYPE_B);
        a.setProperty(PROPERTY_B, "true");
        session.save();

        Node rootNode = session.getRootNode();
        Node nodeA = rootNode.getNode("a");
        nodeA.removeMixin(MIXIN_TYPE_B);
        session.save();
    }

    @Test
    public void shouldAllowRemovalIfExistingChildNodeWouldHaveDefinition() throws Exception {
        Node a = session.getRootNode().addNode("a", "nt:unstructured");
        a.addMixin(MIXIN_TYPE_B);
        a.addNode(CHILD_NODE_B);
        session.save();

        Node rootNode = session.getRootNode();
        Node nodeA = rootNode.getNode("a");
        nodeA.removeMixin(MIXIN_TYPE_B);
    }

    @Test( expected = ConstraintViolationException.class )
    public void shouldNotAllowRemovalIfExistingChildNodeWouldHaveNoDefinition() throws Exception {
        Node a = session.getRootNode().addNode("a", BASE_PRIMARY_TYPE);
        a.addMixin(MIXIN_TYPE_B);
        a.addNode(CHILD_NODE_B);
        session.save();

        Node rootNode = session.getRootNode();
        Node nodeA = rootNode.getNode("a");
        nodeA.removeMixin(MIXIN_TYPE_B);
    }

    @Test
    @FixFor( "MODE-1912" )
    public void shouldRemoveProtectedChildrenAndPropertiesWhenRemovingMixin() throws Exception {
        Node protectedNode = session.getRootNode().addNode("protected", BASE_PRIMARY_TYPE);
        protectedNode.addMixin(MIXIN_PROTECTED);
        protectedNode.addNode(CHILD_PROTECTED);
        protectedNode.setProperty(PROPERTY_PROTECTED, "a value");

        session.save();

        //change the item definitions to protected and run the validations
        NodeTypeManager mgr = session.getWorkspace().getNodeTypeManager();
        NodeTypeTemplate mixinProtected = createMixinProtected(mgr, true);
        mgr.registerNodeType(mixinProtected, true);

        String childPath = "/protected/" + CHILD_PROTECTED;
        String propertyPath = "/protected/" + PROPERTY_PROTECTED;

        protectedNode = session.getNode("/protected");
        assertNotNull(session.getNode(childPath));
        assertNotNull(session.getProperty(propertyPath));
        protectedNode.removeMixin(MIXIN_PROTECTED);
        session.save();

        try {
            session.getNode(childPath);
            fail("Protected child should have been removed");
        } catch (PathNotFoundException path) {
            //expected
        }

        try {
            session.getProperty(propertyPath);
            fail("Protected property should have been removed");
        } catch (PathNotFoundException path) {
            //expected
        }
    }

    @Test
    @FixFor( "MODE-1949")
    public void shouldNotAllowMixinRemovalIfPropertiesDefinedInParentMixinArePresent() throws Exception {
        Node folder = session.getNode("/").addNode("folder", "nt:folder");
        //add the publishArea mixin which inherits from mix:title
        folder.addMixin("mode:publishArea");
        folder.setProperty("jcr:title", "title");
        folder.setProperty("jcr:description", "description");
        session.save();

        try {
            folder.removeMixin("mode:publishArea");
            session.save();
            fail("The mixin should not be removed because there are still properties defined from the parent mixin");
        } catch (ConstraintViolationException e) {
            //expected
        }

        folder.setProperty("jcr:title", (Value)null);
        session.save();

        try {
            folder.removeMixin("mode:publishArea");
            session.save();
            fail("The mixin should not be removed because there are still properties defined from the parent mixin");
        } catch (ConstraintViolationException e) {
            //expected
        }

        folder.setProperty("jcr:description", (Value)null);
        session.save();
        folder.removeMixin("mode:publishArea");
        session.save();
    }

    protected NodeTypeDefinition[] getTestTypes() throws RepositoryException {
        NodeTypeManager mgr = session.getWorkspace().getNodeTypeManager();

        NodeTypeTemplate basePrimaryType = createBasePrimaryType(mgr);
        NodeTypeTemplate mixinTypeA = createMixinA(mgr);
        NodeTypeTemplate mixinTypeB = createMixinB(mgr);
        NodeTypeTemplate mixinTypeC = createMixinC(mgr);
        NodeTypeTemplate mixinTypeWithAutoChild = createMixinWithAutoChild(mgr);
        NodeTypeTemplate mixinTypeWithAutoProperty = createMixinWithAutoProperty(mgr);
        NodeTypeTemplate primaryTypeA = createPrimaryTypeA(mgr);
        //create the items on mixin protected as "un-protected" first, so we can create children & properties
        NodeTypeTemplate mixinProtected = createMixinProtected(mgr, false);

        return new NodeTypeDefinition[] { basePrimaryType, mixinTypeA, mixinTypeB, mixinTypeC, mixinTypeWithAutoChild,
                mixinTypeWithAutoProperty, primaryTypeA, mixinProtected};
    }

    private NodeTypeTemplate createBasePrimaryType( NodeTypeManager mgr ) throws RepositoryException {
        NodeTypeTemplate basePrimaryType = mgr.createNodeTypeTemplate();
        basePrimaryType.setName(BASE_PRIMARY_TYPE);
        return basePrimaryType;
    }

    @SuppressWarnings( {"unchecked"} )
    private NodeTypeTemplate createMixinWithAutoProperty( NodeTypeManager mgr ) throws RepositoryException {
        NodeTypeTemplate mixinTypeWithAutoProperty = mgr.createNodeTypeTemplate();
        mixinTypeWithAutoProperty.setName("mixinTypeWithAutoCreatedProperty");
        mixinTypeWithAutoProperty.setMixin(true);

        PropertyDefinitionTemplate propertyB = mgr.createPropertyDefinitionTemplate();
        propertyB.setName(PROPERTY_B);
        propertyB.setMandatory(true);
        propertyB.setAutoCreated(true);
        propertyB.setOnParentVersion(OnParentVersionAction.IGNORE);
        propertyB.setRequiredType(PropertyType.LONG);
        propertyB.setDefaultValues(new Value[] {session.getValueFactory().createValue("10")});
        mixinTypeWithAutoProperty.getPropertyDefinitionTemplates().add(propertyB);
        return mixinTypeWithAutoProperty;
    }

    @SuppressWarnings( {"unchecked"} )
    private NodeTypeTemplate createMixinWithAutoChild( NodeTypeManager mgr ) throws RepositoryException {
        NodeTypeTemplate mixinTypeWithAutoChild = mgr.createNodeTypeTemplate();
        mixinTypeWithAutoChild.setName("mixinTypeWithAutoCreatedChildNode");
        mixinTypeWithAutoChild.setMixin(true);

        NodeDefinitionTemplate childNodeB = mgr.createNodeDefinitionTemplate();
        childNodeB.setName(CHILD_NODE_B);
        childNodeB.setOnParentVersion(OnParentVersionAction.IGNORE);
        childNodeB.setMandatory(true);
        childNodeB.setAutoCreated(true);
        childNodeB.setDefaultPrimaryTypeName("nt:unstructured");
        childNodeB.setRequiredPrimaryTypeNames(new String[] {"nt:unstructured"});
        mixinTypeWithAutoChild.getNodeDefinitionTemplates().add(childNodeB);
        return mixinTypeWithAutoChild;
    }

    @SuppressWarnings( {"unchecked"} )
    private NodeTypeTemplate createPrimaryTypeA( NodeTypeManager mgr ) throws RepositoryException {
        NodeTypeTemplate primaryTypeA = mgr.createNodeTypeTemplate();
        primaryTypeA.setName(PRIMARY_TYPE_A);

        NodeDefinitionTemplate childNodeA = mgr.createNodeDefinitionTemplate();
        childNodeA.setName(CHILD_NODE_A);
        childNodeA.setOnParentVersion(OnParentVersionAction.IGNORE);
        primaryTypeA.getNodeDefinitionTemplates().add(childNodeA);

        PropertyDefinitionTemplate propertyA = mgr.createPropertyDefinitionTemplate();
        propertyA.setName(PROPERTY_A);
        propertyA.setOnParentVersion(OnParentVersionAction.IGNORE);
        propertyA.setRequiredType(PropertyType.STRING);
        primaryTypeA.getPropertyDefinitionTemplates().add(propertyA);
        return primaryTypeA;
    }

    @SuppressWarnings( {"unchecked"} )
    private NodeTypeTemplate createMixinC( NodeTypeManager mgr ) throws RepositoryException {
        NodeTypeTemplate mixinTypeC = mgr.createNodeTypeTemplate();
        mixinTypeC.setName(MIXIN_TYPE_C);
        mixinTypeC.setMixin(true);

        PropertyDefinitionTemplate propertyB = mgr.createPropertyDefinitionTemplate();
        propertyB.setName(PROPERTY_B);
        propertyB.setOnParentVersion(OnParentVersionAction.IGNORE);
        propertyB.setRequiredType(PropertyType.STRING);
        mixinTypeC.getPropertyDefinitionTemplates().add(propertyB);
        return mixinTypeC;
    }

    @SuppressWarnings( {"unchecked"} )
    private NodeTypeTemplate createMixinB( NodeTypeManager mgr ) throws RepositoryException {
        NodeTypeTemplate mixinTypeB = mgr.createNodeTypeTemplate();
        mixinTypeB.setName(MIXIN_TYPE_B);
        mixinTypeB.setMixin(true);

        NodeDefinitionTemplate childNodeB = mgr.createNodeDefinitionTemplate();
        childNodeB.setName(CHILD_NODE_B);
        childNodeB.setDefaultPrimaryTypeName("nt:base");
        childNodeB.setOnParentVersion(OnParentVersionAction.IGNORE);
        mixinTypeB.getNodeDefinitionTemplates().add(childNodeB);

        PropertyDefinitionTemplate propertyB = mgr.createPropertyDefinitionTemplate();
        propertyB.setName(PROPERTY_B);
        propertyB.setOnParentVersion(OnParentVersionAction.IGNORE);
        propertyB.setRequiredType(PropertyType.BINARY);
        mixinTypeB.getPropertyDefinitionTemplates().add(propertyB);

        NodeDefinitionTemplate childNodeA = mgr.createNodeDefinitionTemplate();
        childNodeA.setName(CHILD_NODE_A);
        childNodeA.setOnParentVersion(OnParentVersionAction.IGNORE);
        mixinTypeB.getNodeDefinitionTemplates().add(childNodeA);
        return mixinTypeB;
    }

    @SuppressWarnings( {"unchecked"} )
    private NodeTypeTemplate createMixinProtected( NodeTypeManager mgr, boolean makeItemsProtected ) throws RepositoryException {
        NodeTypeTemplate mixinProtected = mgr.createNodeTypeTemplate();
        mixinProtected.setName(MIXIN_PROTECTED);
        mixinProtected.setMixin(true);

        NodeDefinitionTemplate childProtected = mgr.createNodeDefinitionTemplate();
        childProtected.setName(CHILD_PROTECTED);
        childProtected.setProtected(makeItemsProtected);
        childProtected.setDefaultPrimaryTypeName("nt:base");
        childProtected.setOnParentVersion(OnParentVersionAction.IGNORE);
        mixinProtected.getNodeDefinitionTemplates().add(childProtected);

        PropertyDefinitionTemplate propertyProtected = mgr.createPropertyDefinitionTemplate();
        propertyProtected.setProtected(makeItemsProtected);
        propertyProtected.setName(PROPERTY_PROTECTED);
        propertyProtected.setOnParentVersion(OnParentVersionAction.IGNORE);
        propertyProtected.setRequiredType(PropertyType.STRING);
        mixinProtected.getPropertyDefinitionTemplates().add(propertyProtected);

        return mixinProtected;
    }

    @SuppressWarnings( {"unchecked"} )
    private NodeTypeTemplate createMixinA( NodeTypeManager mgr ) throws RepositoryException {
        NodeTypeTemplate mixinTypeA = mgr.createNodeTypeTemplate();
        mixinTypeA.setName(MIXIN_TYPE_A);
        mixinTypeA.setMixin(true);

        NodeDefinitionTemplate childNodeA = mgr.createNodeDefinitionTemplate();
        childNodeA.setName(CHILD_NODE_A);
        childNodeA.setOnParentVersion(OnParentVersionAction.IGNORE);
        mixinTypeA.getNodeDefinitionTemplates().add(childNodeA);

        PropertyDefinitionTemplate propertyA = mgr.createPropertyDefinitionTemplate();
        propertyA.setName(PROPERTY_A);
        propertyA.setOnParentVersion(OnParentVersionAction.IGNORE);
        propertyA.setRequiredType(PropertyType.STRING);
        mixinTypeA.getPropertyDefinitionTemplates().add(propertyA);
        return mixinTypeA;
    }

}
