/**
 * Copyright (C) 2010 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.xcmis.spi.tck;

import static org.junit.Assert.fail;

import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.xcmis.spi.CmisConstants;
import org.xcmis.spi.ConstraintException;
import org.xcmis.spi.ItemsTree;
import org.xcmis.spi.NotSupportedException;
import org.xcmis.spi.model.AccessControlEntry;
import org.xcmis.spi.model.AccessControlPropagation;
import org.xcmis.spi.model.CapabilityACL;
import org.xcmis.spi.model.TypeDefinition;
import org.xcmis.spi.utils.CmisUtils;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class ACLTest extends BaseTest
{
   private static String testRootFolderId;

   private static TypeDefinition controllableAclType;

   private static TypeDefinition notControllableAclType;

   private static String controllableAclObject;

   private static String notControllableAclObject;

   private static String principal = "root";

   @BeforeClass
   public static void start() throws Exception
   {
      testRootFolderId = createFolder(rootFolderID, CmisConstants.FOLDER, "acl_testroot", null, null, null);

      List<ItemsTree<TypeDefinition>> allTypes = connection.getTypeDescendants(null, -1, true);
      controllableAclType = getControllableAclType(allTypes);
      notControllableAclType = getNotControllableAclType(allTypes);
      if (controllableAclType != null)
      {
         switch (controllableAclType.getBaseId())
         {
            case DOCUMENT :
               controllableAclObject =
                  createDocument(testRootFolderId, controllableAclType.getId(),
                     generateName(controllableAclType, null), null, null, null, null, null);
               break;
            case FOLDER :
               controllableAclObject =
                  createFolder(testRootFolderId, controllableAclType.getId(), generateName(controllableAclType, null),
                     null, null, null);
               break;
            case POLICY :
               controllableAclObject =
                  createPolicy(testRootFolderId, controllableAclType.getId(), generateName(controllableAclType, null),
                     null, null, null, null);
               break;
            case RELATIONSHIP :
               String sourceId =
                  createDocument(testRootFolderId, CmisConstants.DOCUMENT, generateName(connection
                     .getTypeDefinition(CmisConstants.DOCUMENT), null), null, null, null, null, null);
               String targetId =
                  createDocument(testRootFolderId, CmisConstants.DOCUMENT, generateName(connection
                     .getTypeDefinition(CmisConstants.DOCUMENT), null), null, null, null, null, null);
               controllableAclObject =
                  createRelationship(controllableAclType.getId(), generateName(controllableAclType, null), sourceId,
                     targetId, null, null, null);
               break;
         }
      }
      if (notControllableAclType != null)
      {
         switch (notControllableAclType.getBaseId())
         {
            case DOCUMENT :
               notControllableAclObject =
                  createDocument(testRootFolderId, notControllableAclType.getId(), generateName(notControllableAclType,
                     null), null, null, null, null, null);
               break;
            case FOLDER :
               notControllableAclObject =
                  createFolder(testRootFolderId, notControllableAclType.getId(), generateName(notControllableAclType,
                     null), null, null, null);
               break;
            case POLICY :
               notControllableAclObject =
                  createPolicy(testRootFolderId, notControllableAclType.getId(), generateName(notControllableAclType,
                     null), null, null, null, null);
               break;
            case RELATIONSHIP :
               String sourceId =
                  createDocument(testRootFolderId, CmisConstants.DOCUMENT, generateName(connection
                     .getTypeDefinition(CmisConstants.DOCUMENT), null), null, null, null, null, null);
               String targetId =
                  createDocument(testRootFolderId, CmisConstants.DOCUMENT, generateName(connection
                     .getTypeDefinition(CmisConstants.DOCUMENT), null), null, null, null, null, null);
               notControllableAclObject =
                  createRelationship(notControllableAclType.getId(), generateName(notControllableAclType, null),
                     sourceId, targetId, null, null, null);
               break;
         }
      }
      System.out.println("Running ACL Service tests");
   }

   @AfterClass
   public static void stop() throws Exception
   {
      if (testRootFolderId != null)
      {
         clear(testRootFolderId);
      }
   }

   /**
    * 2.2.10.2 Adds or removes the given ACEs to or from the ACL of document or
    * folder object.
    * 
    * @throws Exception
    */
   @Test
   public void testApplyACL_Add() throws Exception
   {
      if (controllableAclObject == null)
      {
         return;
      }
      if (capabilities.getCapabilityACL() == CapabilityACL.MANAGE)
      {
         List<AccessControlEntry> acl = createACL(principal, "cmis:write");
         try
         {
            connection.applyACL(controllableAclObject, acl, null, aclCapability.getPropagation());
            List<AccessControlEntry> actualACL = connection.getACL(controllableAclObject, false);
            validateACL(acl);
            checkACL(acl, actualACL);
         }
         finally
         {
            // Restore previous ACL.
            connection.applyACL(controllableAclObject, null, acl, aclCapability.getPropagation());
         }
      }
   }

   /**
    * 2.2.10.2.3 At least one of the specified values for permission in ANY of
    * the ACEs does not match ANY of the permissionNames as returned by
    * getACLCapability and is not a CMIS Basic permission
    * 
    * @throws Exception
    */
   @Test
   public void testApplyACL_ConstraintException_ACLNotMatch() throws Exception
   {
      if (notControllableAclObject == null)
      {
         return;
      }
      if (capabilities.getCapabilityACL() == CapabilityACL.MANAGE)
      {
         List<AccessControlEntry> acl = createACL(principal, "cmis:unknown");
         try
         {
            connection.applyACL(notControllableAclObject, acl, null, aclCapability.getPropagation());
            fail("ConstraintException must be thrown since type is not controllable by ACL.");
         }
         catch (ConstraintException e)
         {
         }
      }
   }

   /**
    * 2.2.10.2.3 The value for ACLPropagation does not match the values as
    * returned via getACLCapabilities.
    * 
    * @throws Exception
    */
   @Test
   public void testApplyACL_ConstraintException_ACLPropagation() throws Exception
   {
      if (controllableAclObject == null)
      {
         return;
      }
      if (capabilities.getCapabilityACL() == CapabilityACL.MANAGE)
      {
         int l = AccessControlPropagation.values().length;
         AccessControlPropagation propagation = aclCapability.getPropagation();
         // Propagation which is not supported.
         int ord = propagation.ordinal();
         int p = ord == l - 1 ? ord - 1 : ord + 1;
         AccessControlPropagation propagation1 = AccessControlPropagation.values()[p];
         if (propagation != AccessControlPropagation.REPOSITORYDETERMINED)
         {
            try
            {
               connection.applyACL(controllableAclObject, createACL(principal, "cmis:write"), null, propagation1);
            }
            catch (ConstraintException e)
            {
            }
         }
      }
   }

   /**
    * 2.2.10.2.3 The specified object's Object-Type definition's attribute for
    * controllableACL is FALSE.
    * 
    * @throws Exception
    */
   @Test
   public void testApplyACL_ConstraintException_NotControllable() throws Exception
   {
      if (notControllableAclObject == null)
      {
         return;
      }
      if (capabilities.getCapabilityACL() == CapabilityACL.MANAGE)
      {
         List<AccessControlEntry> acl = createACL(principal, "cmis:write");
         try
         {
            connection.applyACL(notControllableAclObject, acl, null, aclCapability.getPropagation());
            fail("ConstraintException must be thrown since type is not controllable by ACL.");
         }
         catch (ConstraintException e)
         {
         }
      }
   }

   /**
    * Managing of ACL is not supported but discovering may be supported.
    * 
    * @throws Exception
    */
   @Test
   public void testApplyACL_NotSupportedException() throws Exception
   {
      // If managing is not supported then try to apply ACL to any type and
      // expect for org.xcmis.spi.NotSupportedException.
      if (controllableAclObject != null && capabilities.getCapabilityACL() != CapabilityACL.MANAGE)
      {
         List<AccessControlEntry> acl = createACL(principal, "cmis:write");
         try
         {
            connection.applyACL(controllableAclObject, acl, null, aclCapability.getPropagation());
            fail("NotSupportedException must be thrown since managing of ACL is not supported.");
         }
         catch (NotSupportedException e)
         {
         }
      }
   }

   /**
    * 2.2.10.1 Get the ACL currently applied to the specified document or folder
    * object.
    * 
    * @throws Exception
    */
   @Test
   public void testGetACL() throws Exception
   {
      if (capabilities.getCapabilityACL() == CapabilityACL.NONE || controllableAclObject == null)
      {
         return;
      }

      List<AccessControlEntry> actualACL = connection.getACL(controllableAclObject, false);
      if (actualACL.size() > 0)
      {
         // May contains some ACEs which are inherited from parent or assigned by repository itself.
         Map<String, Set<String>> m1 = new HashMap<String, Set<String>>();
         CmisUtils.addAclToPermissionMap(m1, actualACL);
         validateACL(actualACL);
      }
      else if (capabilities.getCapabilityACL() == CapabilityACL.MANAGE)
      {
         // If capability is MANAGE then try add and retrieve ACL.
         List<AccessControlEntry> acl = createACL(principal, "cmis:write");
         try
         {
            connection.applyACL(controllableAclObject, acl, null, aclCapability.getPropagation());
            actualACL = connection.getACL(controllableAclObject, false);
            validateACL(actualACL);
            checkACL(acl, actualACL);
         }
         finally
         {
            // Restore previous ACL.
            connection.applyACL(controllableAclObject, null, acl, aclCapability.getPropagation());
         }
      }
   }

}
