/*
 * Copyright (C) 2009 eXo Platform SAS.
 *
 * This 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 software 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.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.exoplatform.services.jcr.api.writing;

import org.exoplatform.services.jcr.JcrAPIBaseTest;
import org.exoplatform.services.jcr.core.WorkspaceContainerFacade;
import org.exoplatform.services.jcr.dataflow.ItemState;
import org.exoplatform.services.jcr.dataflow.ItemStateChangesLog;
import org.exoplatform.services.jcr.dataflow.TransactionChangesLog;
import org.exoplatform.services.jcr.dataflow.persistent.ItemsPersistenceListener;
import org.exoplatform.services.jcr.impl.core.SessionImpl;
import org.exoplatform.services.jcr.impl.core.value.NameValue;
import org.exoplatform.services.jcr.impl.dataflow.persistent.CacheableWorkspaceDataManager;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.util.GregorianCalendar;

import javax.jcr.ItemExistsException;
import javax.jcr.Node;
import javax.jcr.Property;
import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.Value;
import javax.jcr.ValueFormatException;
import javax.jcr.nodetype.ConstraintViolationException;
import javax.jcr.version.OnParentVersionAction;

/**
 * Created by The eXo Platform SAS.
 * 
 * @author <a href="mailto:geaz@users.sourceforge.net">Gennady Azarenkov</a>
 * @version $Id: TestSetProperty.java 14508 2008-05-20 10:07:45Z ksm $
 */
public class TestSetProperty extends JcrAPIBaseTest implements ItemsPersistenceListener
{

   static protected String TEST_MULTIVALUED = "testMultivalued";

   protected Node testMultivalued = null;

   private TransactionChangesLog cLog;

   public void setUp() throws Exception
   {
      super.setUp();

      WorkspaceContainerFacade wsc = repository.getWorkspaceContainer(session.getWorkspace().getName());
      CacheableWorkspaceDataManager dm =
         (CacheableWorkspaceDataManager)wsc.getComponent(CacheableWorkspaceDataManager.class);
      dm.addItemPersistenceListener(this);
   }

   public void initRepository() throws RepositoryException
   {
      Node root = session.getRootNode();
      Node propDef = root.addNode("propertyDefNode", "nt:propertyDefinition");
      propDef.setProperty("jcr:name", valueFactory.createValue("test", PropertyType.NAME));

      propDef.setProperty("jcr:autoCreated", false);
      propDef.setProperty("jcr:mandatory", false);
      propDef.setProperty("jcr:onParentVersion", OnParentVersionAction.ACTIONNAME_COPY);
      propDef.setProperty("jcr:protected", false);
      propDef.setProperty("jcr:requiredType", PropertyType.TYPENAME_STRING.toUpperCase());
      propDef.setProperty("jcr:multiple", false);
      // Unknown Property Type. Should set something!
      Value[] defVals = {session.getValueFactory().createValue("testString")};
      propDef.setProperty("jcr:defaultValues", defVals);

      Node childNodeDefNode = root.addNode("childNodeDefNode", "nt:childNodeDefinition");
      childNodeDefNode.setProperty("jcr:name", valueFactory.createValue("test"), PropertyType.NAME);
      childNodeDefNode.setProperty("jcr:autoCreated", false);
      childNodeDefNode.setProperty("jcr:mandatory", false);
      childNodeDefNode.setProperty("jcr:onParentVersion", OnParentVersionAction.ACTIONNAME_COPY);
      childNodeDefNode.setProperty("jcr:protected", false);
      childNodeDefNode.setProperty("jcr:requiredPrimaryTypes", new NameValue[]{(NameValue)valueFactory.createValue(
         "nt:base", PropertyType.NAME)});
      childNodeDefNode.setProperty("jcr:sameNameSiblings", false);

      root.addNode("unstructured", "nt:unstructured");

      testMultivalued = root.addNode(TEST_MULTIVALUED);

      session.save();
   }

   public void tearDown() throws Exception
   {

      try
      {
         // testMultivalued.getSession().refresh(false);
         testMultivalued.remove();
         testMultivalued.getSession().save();
      }
      catch (RepositoryException e)
      {
         log.error("Error delete '" + TEST_MULTIVALUED + "' node", e);
      }

      session = (SessionImpl)repository.login(credentials, WORKSPACE);
      Node root = session.getRootNode();
      root.getNode("unstructured").remove();

      // session.getItem("/propertyDefNode").remove();
      root.getNode("propertyDefNode").remove();
      root.getNode("childNodeDefNode").remove();
      session.save();
      
      // Unregister the listener in order to make it available to the GC
      WorkspaceContainerFacade wsc = repository.getWorkspaceContainer(session.getWorkspace().getName());
      CacheableWorkspaceDataManager dm =
         (CacheableWorkspaceDataManager)wsc.getComponent(CacheableWorkspaceDataManager.class);
      dm.removeItemPersistenceListener(this);
      
      super.tearDown();
   }

   public void testSetPropertyNameValue() throws RepositoryException
   {
      Node root = session.getRootNode();
      Node node = root.getNode("propertyDefNode");
      // Node node = (Node)session.getItem("/propertyDefNode");

      try
      {
         node.setProperty("jcr:multiple", valueFactory.createValue(20l));
         fail("exception should have been thrown");
      }
      catch (ValueFormatException e)
      {
      }
      session.refresh(false);
   }

   public void testSetPropertyNameValueType() throws RepositoryException
   {
      Node root = session.getRootNode();
      Node node = root.getNode("propertyDefNode");

      session.refresh(false);
      node.setProperty("jcr:defaultValues", new Value[]{valueFactory.createValue(10l)}); // ,
      // PropertyType
      // .LONG
      assertEquals(PropertyType.LONG, node.getProperty("jcr:defaultValues").getValues()[0].getType());
      assertEquals(10, node.getProperty("jcr:defaultValues").getValues()[0].getLong());
      node.save();
      session = (SessionImpl)repository.login(credentials, WORKSPACE);
      node = session.getRootNode().getNode("propertyDefNode");
      assertEquals(10, node.getProperty("jcr:defaultValues").getValues()[0].getLong());
   }

   public void testSetPropertyNameValuesType() throws RepositoryException
   {
      Node root = session.getRootNode();
      Node node = root.getNode("childNodeDefNode");
      Value[] values = {session.getValueFactory().createValue("not"), session.getValueFactory().createValue("in")};

      // it converts to required !
      // node.setProperty("jcr:requiredPrimaryTypes", values, PropertyType.LONG);
      node.setProperty("jcr:requiredPrimaryTypes", values, PropertyType.NAME);

      try
      {
         Property prop = node.setProperty("jcr:onParentVersion", values, PropertyType.STRING);
         fail("exception should have been thrown " + prop.getString());
      }
      catch (ValueFormatException e)
      {
      }

      Value[] nameValues =
         {valueFactory.createValue("jcr:unstructured", PropertyType.NAME),
            valueFactory.createValue("jcr:base", PropertyType.NAME)};
      node.setProperty("jcr:requiredPrimaryTypes", nameValues, PropertyType.NAME);
      node.save();

      session = (SessionImpl)repository.login(credentials, WORKSPACE);
      node = session.getRootNode().getNode("childNodeDefNode");
      assertEquals(2, node.getProperty("jcr:requiredPrimaryTypes").getValues().length);
   }

   public void testSetPropertyNameStringValueType() throws RepositoryException
   {
      Node root = session.getRootNode();
      Node node = root.getNode("propertyDefNode");

      session.refresh(false);

      node.setProperty("jcr:defaultValues", new Value[]{valueFactory.createValue((long)10)});
      assertEquals(PropertyType.LONG, node.getProperty("jcr:defaultValues").getValues()[0].getType());
      assertEquals(10, node.getProperty("jcr:defaultValues").getValues()[0].getLong());
      node.save();
      session = (SessionImpl)repository.login(credentials, WORKSPACE);
      node = session.getRootNode().getNode("propertyDefNode");
      assertEquals(10, node.getProperty("jcr:defaultValues").getValues()[0].getLong());
   }

   public void testSetPropertyNameStringValuesType() throws RepositoryException
   {
      Node root = session.getRootNode();
      Node node = root.getNode("childNodeDefNode");
      String[] values = {"not", "in"};
      try
      {
         // it converts to required !
         node.setProperty("jcr:requiredPrimaryTypes", values, PropertyType.LONG);
      }
      catch (ValueFormatException e)
      {
      }
      try
      {
         node.setProperty("jcr:onParentVersion", values, PropertyType.STRING);
         fail("exception should have been thrown");
      }
      catch (ValueFormatException e)
      {
      }

      Value[] nameValues =
         {valueFactory.createValue("jcr:unstructured", PropertyType.NAME),
            valueFactory.createValue("jcr:base", PropertyType.NAME)};
      node.setProperty("jcr:requiredPrimaryTypes", nameValues, PropertyType.NAME);
      node.save();

      session = (SessionImpl)repository.login(credentials, WORKSPACE);
      node = session.getRootNode().getNode("childNodeDefNode");
      assertEquals(2, node.getProperty("jcr:requiredPrimaryTypes").getValues().length);
   }

   public void testSetPropertyMultivaluedString() throws RepositoryException
   {
      String[] values = {"binary string 1", "binary string 2"};
      Property mvp1 = null;
      try
      {
         mvp1 = testMultivalued.setProperty("Multivalued Property", values, PropertyType.BINARY);
         testMultivalued.save();
      }
      catch (ValueFormatException e)
      {
         fail("Can't add 'Multivalued Property'. Error: " + e.getMessage());
      }
      try
      {
         assertTrue("'Multivalued Property' must have size 2", mvp1.getLengths().length == 2);
      }
      catch (RepositoryException e)
      {
         fail("Error of 'Multivalued Property' length reading. Error: " + e.getMessage());
      }

      SessionImpl newSession = (SessionImpl)repository.login(credentials, WORKSPACE);
      Node test = (Node)newSession.getItem(testMultivalued.getPath());
      assertEquals("Node '" + TEST_MULTIVALUED + "' must have values length 2", 2, test.getProperty(
         "Multivalued Property").getValues().length);
      test = newSession.getRootNode().getNode(TEST_MULTIVALUED);
      assertEquals("Node '" + TEST_MULTIVALUED + "' must have values length 2", 2, test.getProperty(
         "Multivalued Property").getValues().length);
   }

   public void testSetPropertyMultivaluedBinary() throws RepositoryException
   {
      Value[] values =
         {valueFactory.createValue(new ByteArrayInputStream("binary string 1".getBytes())),
            valueFactory.createValue(new ByteArrayInputStream("binary string 2".getBytes()))};
      Property mvp1 = null;
      try
      {
         mvp1 = testMultivalued.setProperty("Multivalued Property", values, PropertyType.BINARY);
         testMultivalued.save();
      }
      catch (ValueFormatException e)
      {
         fail("Can't add 'Multivalued Property'. Error: " + e.getMessage());
      }
      try
      {
         assertTrue("'Multivalued Property' must have size 2", mvp1.getValues().length == 2);
      }
      catch (RepositoryException e)
      {
         fail("Error of 'Multivalued Property' length reading. Error: " + e.getMessage());
      }

      SessionImpl newSession = (SessionImpl)repository.login(credentials, WORKSPACE);
      Node test = (Node)newSession.getItem(testMultivalued.getPath());
      assertEquals("Node '" + TEST_MULTIVALUED + "' must have values length 2", 2, test.getProperty(
         "Multivalued Property").getValues().length);
      test = newSession.getRootNode().getNode(TEST_MULTIVALUED);
      assertEquals("Node '" + TEST_MULTIVALUED + "' must have values length 2", 2, test.getProperty(
         "Multivalued Property").getValues().length);
   }

   public void testSetPropertyNameTypedValue() throws RepositoryException
   {
      Node root = session.getRootNode();
      Node node = root.getNode("propertyDefNode");

      node.setProperty("jcr:defaultValues", new Value[]{valueFactory.createValue("default")});
      node.setProperty("jcr:defaultValues", new Value[]{valueFactory.createValue(new ByteArrayInputStream(new String(
         "default").getBytes()))});
      node.setProperty("jcr:defaultValues", new Value[]{valueFactory.createValue(true)});
      node.setProperty("jcr:defaultValues", new Value[]{valueFactory.createValue(new GregorianCalendar())});
      node.setProperty("jcr:defaultValues", new Value[]{valueFactory.createValue(20D)});
      node.setProperty("jcr:defaultValues", new Value[]{valueFactory.createValue(20L)});

      try
      {
         node.setProperty("jcr:multiple", 20D);
         fail("exception should have been thrown");
      }
      catch (ValueFormatException e)
      {
      }

      try
      {
         node.setProperty("jcr:versionStorage", 20D);
         fail("exception should have been thrown");
      }
      catch (ConstraintViolationException e)
      {
      }

      try
      {
         node.setProperty("jcr:versionStorage", valueFactory.createValue(20L), PropertyType.LONG);
         fail("exception should have been thrown");
      }
      catch (ConstraintViolationException e)
      {
      }

      try
      {
         node.setProperty("jcr:versionStorage", "20", PropertyType.LONG);
         fail("exception should have been thrown");
      }
      catch (ConstraintViolationException e)
      {
      }
   }

   public void testSetPathProperty() throws RepositoryException
   {
      Node root = session.getRootNode();
      Node node1 = root.addNode("node1", "nt:unstructured");
      node1.setProperty("pathValue", valueFactory.createValue("/root-node/node_1", PropertyType.PATH));
      assertNotNull(session.getItem("/node1/pathValue"));
      assertEquals("/root-node/node_1", ((Property)session.getItem("/node1/pathValue")).getString());
      root.save();
      assertNotNull(session.getItem("/node1/pathValue"));
      assertEquals("/root-node/node_1", ((Property)session.getItem("/node1/pathValue")).getString());
      node1.remove();
      root.save();
      // node1.save();//impossible
   }

   public void testInvalidItemStateException() throws RepositoryException
   {
      Property p = session.getRootNode().setProperty("sameProperty", "test");

      Session session2 = repository.login(credentials, "ws");
      Property p2 = session2.getRootNode().setProperty("sameProperty", "test");
      session.save();

      try
      {
         session2.save();
         fail("InvalidItemStateException should have been thrown");
      }
      catch (ItemExistsException e)
      {
      }

   }

   public void testSetPropertySeveralTime() throws Exception
   {

      Node node = root.addNode("testNode");

      File tmpFile1 = createBLOBTempFile(250);
      node.setProperty("testProp", new FileInputStream(tmpFile1));

      File tmpFile2 = createBLOBTempFile(500);
      node.setProperty("testProp", new FileInputStream(tmpFile2));

      File tmpFile3 = createBLOBTempFile(1000);
      node.setProperty("testProp", new FileInputStream(tmpFile3));

      session.save();

      File tempFiles[] = new File[3];
      tempFiles[0] = tmpFile1;
      tempFiles[1] = tmpFile2;
      tempFiles[2] = tmpFile3;

      for (int i = 2; i < cLog.getAllStates().size(); i++)
      {
         ItemState item = cLog.getAllStates().get(i);
         // TODO doesn't pass with FileTree VS, ok with CAS if contents different
         //compareStream(((PropertyData) item.getData()).getValues().get(0).getAsStream(),
         //              new FileInputStream(tempFiles[i - 2]));
      }
   }

   public void testCheckNSetProperty() throws Exception
   {
      Node node = root.addNode("testCheckNSetProperty");
      session.save();
      if (!node.hasNode("foo"))
      {
         node.setProperty("foo", "foo");
      }
      if (!node.hasProperty("foo2"))
      {
         node.setProperty("foo2", "foo");
      }
      session.save();
   }

   public void testCheckNSetPropertyWithRefresh() throws Exception
   {
      Node node = root.addNode("testCheckNSetPropertyWithRefresh");
      session.save();
      if (!node.hasNode("foo"))
      {
         node.setProperty("foo", "foo");
      }
      if (!node.hasProperty("foo2"))
      {
         node.setProperty("foo2", "foo");
      }
      session.refresh(true);
      session.save();
   }

   public void testEmptyReference() throws Exception
   {
      Node node = root.addNode("testEmptyReference", "nt:folder");
      node.addMixin("exo:testEmptyReference");
      session.save();
      try
      {
         node.setProperty("exo:emptyRef", "");
         fail("A ValueFormatException was expected");
      }
      catch (ValueFormatException e)
      {
         // expected
      }
      try
      {
         node.setProperty("exo:emptyRefs", new String[]{""});
         fail("A ValueFormatException was expected");
      }
      catch (ValueFormatException e)
      {
         // expected
      }
      Node ref = root.addNode("testEmptyReferenceRefNode");
      ref.addMixin("mix:referenceable");
      session.save();

      node.setProperty("exo:emptyRef", ref);
      node.setProperty("exo:emptyRefs", new String[]{ref.getUUID()});

      session.save();

      node.setProperty("exo:emptyRef", ref.getUUID());

      session.save();
      try
      {
         node.setProperty("exo:emptyRef", "");
         fail("A ValueFormatException was expected");
      }
      catch (ValueFormatException e)
      {
         // expected
         node.setProperty("exo:emptyRef", (String)null);
      }
      try
      {
         node.setProperty("exo:emptyRefs", new String[]{""});
         fail("A ValueFormatException was expected");
      }
      catch (ValueFormatException e)
      {
         // expected
         node.setProperty("exo:emptyRef", (String[])null);
      }
      session.save();
   }

   public void onSaveItems(ItemStateChangesLog itemStates)
   {
      cLog = (TransactionChangesLog)itemStates;
   }

   /**
    * {@inheritDoc}
    */
   public boolean isTXAware()
   {
      return true;
   }
}
