/*
 * 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.lock;

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

import javax.jcr.lock.LockException;
import javax.jcr.Session;
import javax.jcr.Node;
import javax.jcr.Property;
import javax.jcr.Repository;
import javax.jcr.RepositoryException;
import javax.jcr.Value;
import javax.jcr.PropertyType;
import javax.jcr.PathNotFoundException;
import java.util.Calendar;
import java.io.ByteArrayInputStream;


/**
 * <code>SetValueLockExceptionTest</code> Tests throwing of a LockException for the
 * Property.setValue() methods in case the parentNode of the given property is locked.
 *
 */
public class SetValueLockExceptionTest extends AbstractJCRTest {

    private Node testNode;

    private static final String binaryProp = "binaryProp";
    private static final String booleanProp = "booleanProp";
    private static final String dateProp = "dateProp";
    private static final String doubleProp = "doubleProp";
    private static final String longProp = "longProp";
    private static final String referenceProp = "referenceProp";
    private static final String stringProp = "stringProp";
    private static final String multiStringProp ="multiStringProp";

    private static final boolean booleanValue = false;
    private Calendar dateValue = null;
    private static final double doubleValue = 3.1414926;
    private static final long longValue = Long.MAX_VALUE;
    private Node referenceNode = null;
    private static final String stringValue = "a string";
    private byte[] binaryValue = null;
    private String[] multiString = {"one", "two", "three"};

    // types for the different method signatures of Property.setValue
    private static final int TYPE_VALUE = 20;
    private static final int TYPE_MULTIVAL = 21;
    private static final int TYPE_MULTSTRING = 22;
    private static int[] types = {PropertyType.DATE, PropertyType.DOUBLE, PropertyType.LONG,
                                  PropertyType.REFERENCE, PropertyType.STRING,
                                  PropertyType.BINARY, PropertyType.BOOLEAN, TYPE_VALUE,
                                  TYPE_MULTIVAL, TYPE_MULTSTRING};


    /**
     * Check if Locking is supported and if yes setup a lockable node with properties
     * each one for the possible values passed to Property.setValue .
     * and
     * @throws Exception
     */
    public void setUp() throws Exception {
        super.setUp();
        if (!isSupported(Repository.OPTION_LOCKING_SUPPORTED)) {
            throw new NotExecutableException("SetValueLockExceptionTest "
                    + "not executable: Locking not supported");
        }
        else {
            // add a lockable node
            testNode = testRootNode.addNode(nodeName1, testNodeType);
            ensureMixinType(testNode, mixLockable);

            // add properties
            dateValue = Calendar.getInstance();
            referenceNode = createReferenceableNode(nodeName2);
            binaryValue = createRandomString(10).getBytes();

            ByteArrayInputStream in = new ByteArrayInputStream(binaryValue);
            ensureCanSetProperty(testNode, binaryProp, PropertyType.BINARY, false);
            testNode.setProperty(binaryProp, in);
            ensureCanSetProperty(testNode, booleanProp, PropertyType.BOOLEAN, false);
            testNode.setProperty(booleanProp, booleanValue);
            ensureCanSetProperty(testNode, dateProp, PropertyType.DATE, false);
            testNode.setProperty(dateProp, dateValue);
            ensureCanSetProperty(testNode, doubleProp, PropertyType.DOUBLE, false);
            testNode.setProperty(doubleProp, doubleValue);
            ensureCanSetProperty(testNode, longProp, PropertyType.LONG, false);
            testNode.setProperty(longProp, longValue);
            if (referenceNode != null) {
                ensureCanSetProperty(testNode, referenceProp, PropertyType.REFERENCE, false);
                testNode.setProperty(referenceProp, referenceNode);
            }
            ensureCanSetProperty(testNode, stringProp, PropertyType.STRING, false);
            testNode.setProperty(stringProp, stringValue);
            ensureCanSetProperty(testNode, multiStringProp, PropertyType.STRING, true);
            testNode.setProperty(multiStringProp, multiString);
            testRootNode.getSession().save();
        }
    }

    public void tearDown() throws Exception {
        if (testNode.holdsLock()) {
            testNode.unlock();
        }
        testNode = null;
        referenceNode = null;
        superuser.save();
        super.tearDown();
    }

    /**
     * Tests if a LockException is thrown if a value is added to a property of a locked node.
     *
     * @param type The possible argument types.
     * @throws RepositoryException
     */
    public void doTestSetValueLockException(int type)
            throws RepositoryException {

        // lock if not yet locked
        if (!testNode.holdsLock()) {
            testNode.lock(false, false);
            superuser.save();
        }

        // another session
        Session session = getHelper().getReadWriteSession();
        try {
            Node node = (Node) session.getItem(testNode.getPath());
            Property prop = null;
            switch (type) {
                case PropertyType.BINARY:
                    ByteArrayInputStream in = new ByteArrayInputStream(binaryValue);
                    prop = node.getProperty(binaryProp);
                    prop.setValue(in);
                    break;

                case PropertyType.BOOLEAN:
                    prop = node.getProperty(booleanProp);
                    prop.setValue(booleanValue);
                    break;

                case PropertyType.DATE:
                    prop = node.getProperty(dateProp);
                    prop.setValue(dateValue);
                    break;

                case PropertyType.DOUBLE:
                    prop = node.getProperty(doubleProp);
                    prop.setValue(doubleValue);
                    break;

                case PropertyType.LONG:
                    prop = node.getProperty(longProp);
                    prop.setValue(longValue);
                    break;

                case PropertyType.REFERENCE:
                    prop = node.getProperty(referenceProp);
                    if (referenceNode != null) {
                        prop.setValue(referenceNode);
                    }
                    break;

               case PropertyType.STRING:
                    prop = node.getProperty(stringProp);
                    prop.setValue(stringValue);
                    break;

               case TYPE_VALUE:
                    prop = node.getProperty(stringProp);
                    Value value = session.getValueFactory().createValue(stringValue);
                    prop.setValue(value);
                    break;

                case TYPE_MULTIVAL:
                    prop = node.getProperty(multiStringProp);
                    Value[] values = {  session.getValueFactory().createValue(stringValue),
                                        session.getValueFactory().createValue(stringValue),
                                        session.getValueFactory().createValue(stringValue) };
                    prop.setValue(values);
                    break;

               case TYPE_MULTSTRING:
                    prop = node.getProperty(multiStringProp);
                    String[] strVals = {stringValue, stringValue, stringValue};
                    prop.setValue(strVals);
                    break;
            }
            session.save();
            fail("Property.setValue should throw a LockException "
                    + "if the parent node holds a Lock.");
        } catch (LockException le) {
            // ok
        } finally {
            session.logout();
        }
    }

    /**
     * Performs the test for all argument types.
     * @throws RepositoryException
     */
    public void testSetValueLockException() throws RepositoryException {
       for (int i = 0; i < types.length; i++) {
            doTestSetValueLockException(types[i]);
        }
    }

    /**
     * Create a referenceable node under the testRootNode
     * or null if it is not possible to create one.
     * @param name
     * @throws RepositoryException
     */
    public Node createReferenceableNode(String name) throws RepositoryException {
        // remove a yet existing node at the target
        try {
            Node node = testRootNode.getNode(name);
            node.remove();
            superuser.save();
        } catch (PathNotFoundException pnfe) {
            // ok
        }
        // a referenceable node
        Node n1 = testRootNode.addNode(name, testNodeType);
        if (n1.canAddMixin(mixReferenceable)) {
            n1.addMixin(mixReferenceable);
            // make sure jcr:uuid is available
            superuser.save();
            return n1;
        }
        else {
            return null;
        }
    }
}
