/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.apache.jackrabbit.test.api;

import javax.jcr.Node;
import javax.jcr.PathNotFoundException;
import javax.jcr.Property;
import javax.jcr.Repository;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.lock.LockException;
import javax.jcr.nodetype.ConstraintViolationException;
import javax.jcr.nodetype.NoSuchNodeTypeException;
import javax.jcr.version.VersionException;

import org.apache.jackrabbit.test.AbstractJCRTest;
import org.apache.jackrabbit.test.NotExecutableException;

/**
 * <code>NodeRemoveMixinTest</code> contains the test cases for the method
 * <code>Node.removeMixin(String)</code>.
 *
 * @test
 * @sources NodeRemoveMixinTest.java
 * @executeClass org.apache.jackrabbit.test.api.NodeRemoveMixinTest
 * @keywords level2
 */
public class NodeRemoveMixinTest extends AbstractJCRTest {

    /**
     * Tests if <code>Node.removeMixin(String mixinName)</code> removes the
     * requested mixin properly
     */
    public void testRemoveSuccessfully()
            throws NotExecutableException, RepositoryException {

        Session session = testRootNode.getSession();
        Node node = testRootNode.addNode(nodeName1, testNodeType);
        String mixinName = NodeMixinUtil.getAddableMixinName(session, node);

        if (mixinName == null) {
            throw new NotExecutableException("No testable mixin node type found");
        }

        node.addMixin(mixinName);
        testRootNode.getSession().save();

        try {
            node.removeMixin(mixinName);
        } catch (ConstraintViolationException e) {
            /**
             * In some implementations it may not be possible to remove mixin node
             * types (short of removing the node itself). In these cases this
             * method will throw a ConstraintViolationException.
             */
            throw new NotExecutableException("Repository does not support remove of mixin.");
        }

        // test if mixin is removed from property jcr:mixinTypes immediately
        // it is implementation-specific, if the property is removed completely
        // or set to an empty array when removing the last mixin type
        try {
            Property mixinProps = node.getProperty(jcrMixinTypes);

            // getValues() returns an empty array
            assertTrue("Node.removeMixin(String mixinName) did not remove mixin from " +
                    "property " + jcrMixinTypes + ".",
                    mixinProps.getValues().length == 0);
        } catch (PathNotFoundException e) {
            // success (property jcr:mixinTypes has been completely removed)
        }

        // it is implementation-specific if a removed mixin isn't available
        // before or after save therefore save before further tests
        testRootNode.getSession().save();

        // test if removed mixin isn't available anymore by node.getMixinNodeTypes()
        assertTrue("removeMixin(String mixinName) did not remove mixin.",
                node.getMixinNodeTypes().length == 0);
    }

    /**
     * Tests if <code>Node.removeMixin(String mixinName)</code> throws a
     * NoSuchNodeTypeException <code>Node</code> does not have assigned the
     * requested mixin
     */
    public void testNotAssigned()
            throws NotExecutableException, RepositoryException {

        Session session = testRootNode.getSession();
        Node node = testRootNode.addNode(nodeName1, testNodeType);
        String mixinName = NodeMixinUtil.getAddableMixinName(session, node);

        if (mixinName == null) {
            throw new NotExecutableException("No testable mixin node type found");
        }

        node.addMixin(mixinName);
        testRootNode.getSession().save();


        String notAssignedMixin = NodeMixinUtil.getNotAssignedMixinName(session, node);
        if (notAssignedMixin == null) {
            throw new NotExecutableException("No testable mixin node type found");
        }

        try {
            node.removeMixin(notAssignedMixin);
            fail("Node.removeMixin(String mixinName) must throw a " +
                    "NoSuchNodeTypeException if Node does not have the " +
                    "specified mixin.");
        } catch (NoSuchNodeTypeException e) {
            // success
        }
    }

    /**
     * Tests if <code>Node.removeMixin(String mixinName)</code> throws a
     * <code>LockException</code> if <code>Node</code> is locked.
     * <p>
     * The test creates a node <code>nodeName1</code> of type
     * <code>testNodeType</code> under <code>testRoot</code>, adds a mixin and
     * then locks the node with the superuser session. Then the test tries to
     * remove the before added mixin readWrite <code>Session</code>.
     */
    public void testLocked()
            throws ConstraintViolationException, NotExecutableException, RepositoryException {

        Session session = testRootNode.getSession();

        if (!isSupported(Repository.OPTION_LOCKING_SUPPORTED)) {
            throw new NotExecutableException("Locking is not supported.");
        }

        // create a node that is lockable
        Node node = testRootNode.addNode(nodeName1, testNodeType);
        // or try to make it lockable if it is not
        ensureMixinType(node, mixLockable);
        testRootNode.getSession().save();

        String mixinName = NodeMixinUtil.getAddableMixinName(session, node);
        if (mixinName == null) {
            throw new NotExecutableException("No testable mixin node type found");
        }

        node.addMixin(mixinName);
        testRootNode.getSession().save();

        // remove first slash of path to get rel path to root
        String pathRelToRoot = node.getPath().substring(1);

        // access node through another session to lock it
        Session session2 = getHelper().getSuperuserSession();
        try {
            Node node2 = session2.getRootNode().getNode(pathRelToRoot);
            node2.lock(true, true);

            try {
                // remove mixin on locked node must throw either directly upon
                // removeMixin or upon save.
                node.removeMixin(mixinName);
                node.save();
                fail("Node.removeMixin(String mixinName) must throw a " +
                        "LockException if the node is locked.");
            } catch (LockException e) {
                // success
            }

            // unlock to remove node at tearDown()
            node2.unlock();
        } finally {
            session2.logout();
        }
    }

    /**
     * Tests if <code>Node.removeMixin(String mixinName)</code> throws a
     * <code>VersionException</code> if <code>Node</code> is checked-in
     * <p>
     * The test creates a node <code>nodeName1</code> of type
     * <code>testNodeType</code> under <code>testRoot</code>, adds a mixin and
     * then checks it in. Then the test tries to remove the added.
     */
    public void testCheckedIn()
            throws ConstraintViolationException, NotExecutableException, RepositoryException {

        Session session = testRootNode.getSession();

        if (!isSupported(Repository.OPTION_VERSIONING_SUPPORTED)) {
            throw new NotExecutableException("Versioning is not supported.");
        }

        // create a node that is versionable
        Node node = testRootNode.addNode(nodeName1, testNodeType);
        // or try to make it versionable if it is not
        ensureMixinType(node, mixVersionable);
        testRootNode.getSession().save();

        String mixinName = NodeMixinUtil.getAddableMixinName(session, node);
        if (mixinName == null || node.isNodeType(mixinName)) {
            throw new NotExecutableException("No testable mixin node type found");
        }

        node.addMixin(mixinName);
        testRootNode.getSession().save();
        node.checkin();

        try {
            node.removeMixin(mixinName);
            fail("Node.removeMixin(String mixinName) must throw a " +
                    "VersionException if the node is checked-in.");
        } catch (VersionException e) {
            // success
        }
    }
}
