/*
 * JBoss, Home of Professional Open Source
 * Copyright 2005, JBoss Inc., and individual contributors as indicated
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * 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.jboss.test.security.acl;

import java.util.ArrayList;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;

import junit.framework.TestCase;

import org.jboss.security.acl.ACL;
import org.jboss.security.acl.ACLEntry;
import org.jboss.security.acl.ACLEntryImpl;
import org.jboss.security.acl.ACLImpl;
import org.jboss.security.acl.BasicACLPermission;
import org.jboss.security.acl.CompositeACLPermission;
import org.jboss.security.acl.Util;
import org.jboss.security.identity.plugins.IdentityFactory;
import org.junit.Ignore;

/**
 * <p>
 * This {@code TestCase} tests the functionality of the persistence layer added to the {@code ACL} implementation
 * classes. It uses an in-memory hsql test database, so there is no need to perform any special database cleanup in case
 * one of the tests fail. Every time the tests are run a clean new database is used.
 * </p>
 * 
 * @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a>
 */
public class PersistenceTestCase extends TestCase
{
   private EntityManagerFactory entityManagerFactory;

   private EntityManager entityManager;

   private List<ACLEntryImpl> persistedEntries;

   private List<ACLImpl> persistedACLs;

   /*
    * (non-Javadoc)
    * 
    * @see junit.framework.TestCase#setUp()
    */
   @Override
   protected void setUp() throws Exception
   {
      this.entityManagerFactory = Persistence.createEntityManagerFactory("ACL");
      this.entityManager = entityManagerFactory.createEntityManager();
      this.persistedACLs = new ArrayList<ACLImpl>();
      this.persistedEntries = new ArrayList<ACLEntryImpl>();

      // create the test entries.
      this.persistedEntries
            .add(new ACLEntryImpl(BasicACLPermission.READ, IdentityFactory.createIdentity("Identity-1")));
      this.persistedEntries.add(new ACLEntryImpl(new CompositeACLPermission(BasicACLPermission.CREATE,
            BasicACLPermission.READ), IdentityFactory.createIdentity("Identity-2")));
      this.persistedEntries.add(new ACLEntryImpl(new CompositeACLPermission(BasicACLPermission.values()),
            IdentityFactory.createIdentity("Identity-3")));

      // create the test acls.
      this.persistedACLs.add(new ACLImpl(new TestResource(100, "Resource-1")));
      this.persistedACLs.add(new ACLImpl(new TestResource(200, "Resource-2"), new ArrayList<ACLEntry>(
            this.persistedEntries)));

      // persist everything.
      for (ACL acl : this.persistedACLs)
         this.persistEntity(acl);
   }

   /*
    * (non-Javadoc)
    * 
    * @see junit.framework.TestCase#tearDown()
    */
   @Override
   protected void tearDown() throws Exception
   {
      // remove the persisted entities - removing the acl also removes the entries.
      for (ACLImpl acl : this.persistedACLs)
      {
         // re-attach the acl before removing.
         ACLImpl attachedACL = this.entityManager.find(ACLImpl.class, acl.getACLId());
         this.removeEntity(attachedACL);
      }

      // assert the acls have been removed.
      for (ACLImpl acl : this.persistedACLs)
         assertNull(this.entityManager.find(ACLImpl.class, acl.getACLId()));

      // assert the entries have been also removed.
      for (ACLEntryImpl entry : this.persistedEntries)
         assertNull(this.entityManager.find(ACLEntryImpl.class, entry.getACLEntryId()));

      if (this.entityManager != null)
         entityManager.close();
      this.entityManagerFactory.close();
   }

   /**
    * <p>
    * Tests persisting the {@code ACLEntry} objects to a database.
    * </p>
    * 
    * @throws Exception if an error occurs when running the test.
    */
   public void testPersistACLEntry() throws Exception
   {
      // assert the entries have been created by checking if the auto-generated id has been set on each entry.
      assertTrue("Entry1 id value has not been generated", this.persistedEntries.get(0).getACLEntryId() > 0);
      assertTrue("Entry2 id value has not been generated", this.persistedEntries.get(1).getACLEntryId() > 0);
      assertTrue("Entry3 id value has not been generated", this.persistedEntries.get(2).getACLEntryId() > 0);

   }

   /**
    * <p>
    * Tests searching for the persisted {@code ACLEntry} objects.
    * </p>
    * 
    * @throws Exception if an error occurs when running the test.
    */
   public void testSearchACLEntry() throws Exception
   {
      // clear the entity manager so that it goes to the database when searching for entries.
      this.entityManager.clear();

      // load the entries from the database using their primary key and validate them.
      for (ACLEntryImpl entry : this.persistedEntries)
      {
         ACLEntryImpl loadedEntry = this.entityManager.find(ACLEntryImpl.class, entry.getACLEntryId());
         assertNotNull("Entry could not be retrieved by primary key", loadedEntry);
         assertEquals(entry, loadedEntry);
      }

      // execute some queries and validate the results.
      ACLEntryImpl entry = this.persistedEntries.get(1);
      ACLEntryImpl queryResult = (ACLEntryImpl) this.entityManager.createQuery(
            "SELECT e FROM ACLEntryImpl e WHERE e.identityOrRole LIKE '"
                  + entry.getIdentityOrRole() + "'").getSingleResult();
      assertNotNull("Entry2 could not be retrieved by it's identity", queryResult);
      assertEquals(entry, queryResult);

      entry = this.persistedEntries.get(0);
      queryResult = (ACLEntryImpl) this.entityManager.createQuery(
            "SELECT e FROM ACLEntryImpl e WHERE e.bitMask = " + BasicACLPermission.READ.getMaskValue())
            .getSingleResult();
      assertNotNull("Entry1 could not be retrieved by it's bitmask value", queryResult);
      assertEquals(entry, queryResult);
   }

   /**
    * <p>
    * Tests persisting the {@code ACL} objects to a database.
    * </p>
    * 
    * @throws Exception if an error occurs when running the test.
    */
   public void testPersistACL() throws Exception
   {
      // assert the ACLs and their associated entries have been persisted
      for (ACLImpl acl : this.persistedACLs)
      {
         assertTrue("ACL id value has not been generated", acl.getACLId() > 0);
         for (ACLEntry entry : acl.getEntries())
            assertTrue("ACL entry has not been persisted", ((ACLEntryImpl) entry).getACLEntryId() > 0);
      }
   }

   /**
    * <p>
    * Tests searching for the persisted {@code ACL} objects.
    * </p>
    * 
    * @throws Exception if an error occurs when running the test.
    */
   public void testSearchACL() throws Exception
   {
      // clear the entity manager's cache.
      this.entityManager.clear();

      // load the ACLs from the database using their primary key and validate them.
      ACLImpl loadedACL1 = this.entityManager.find(ACLImpl.class, this.persistedACLs.get(0).getACLId());
      assertNotNull("ACL1 could not be retrieved", loadedACL1);
      assertEquals("Loaded ACL contains unexpected number of entries", 0, loadedACL1.getEntries().size());
      assertNull(loadedACL1.getResource());

      ACLImpl loadedACL2 = this.entityManager.find(ACLImpl.class, this.persistedACLs.get(1).getACLId());
      assertNotNull("ACL2 could not be retrieved", loadedACL2);
      assertEquals("Loaded ACL contains unexpected number of entries", 3, loadedACL2.getEntries().size());
      assertTrue(loadedACL2.getEntries().contains(this.persistedEntries.get(0)));
      assertTrue(loadedACL2.getEntries().contains(this.persistedEntries.get(1)));
      assertTrue(loadedACL2.getEntries().contains(this.persistedEntries.get(2)));
      assertNull(loadedACL2.getResource());

      // find the ACLs using the resource and validate the result.
      ACLImpl acl = this.persistedACLs.get(0);
      ACLImpl queryResult = (ACLImpl) this.entityManager.createQuery(
            "SELECT a FROM ACLImpl a WHERE a.resourceAsString LIKE '" + Util.getResourceAsString(acl.getResource())
                  + "'").getSingleResult();
      assertNotNull("ACL1 could not be retrieved by it's resource", queryResult);
      assertEquals("Queried ACL id does not match the expected id", acl.getACLId(), queryResult.getACLId());
      assertEquals("Queried ACL contains unexpected number of entries", 0, queryResult.getEntries().size());

      acl = this.persistedACLs.get(1);
      queryResult = (ACLImpl) this.entityManager.createQuery(
            "SELECT a FROM ACLImpl a WHERE a.resourceAsString LIKE '" + Util.getResourceAsString(acl.getResource())
                  + "'").getSingleResult();
      assertNotNull("ACL2 could not be retrieved by it's resource", queryResult);
      assertEquals("Queried ACL id does not match the expected id", acl.getACLId(), queryResult.getACLId());
      assertEquals("Queried ACL contains unexpected number of entries", 3, queryResult.getEntries().size());
      assertTrue(queryResult.getEntries().contains(this.persistedEntries.get(0)));
      assertTrue(queryResult.getEntries().contains(this.persistedEntries.get(1)));
      assertTrue(queryResult.getEntries().contains(this.persistedEntries.get(2)));

   }

   /**
    * <p>
    * Tests updating the persisted {@code ACL} objects.
    * </p>
    * 
    * @throws Exception if an error occurs when running the test.
    */
   public void testUpdateACL() throws Exception
   {
      // add some entries to the acls and remove one of the existing entries from ACL2.
      ACLEntryImpl entry4 = new ACLEntryImpl(BasicACLPermission.CREATE, IdentityFactory.createIdentity("Identity-4"));
      ACLEntryImpl entry5 = new ACLEntryImpl(new CompositeACLPermission(BasicACLPermission.CREATE,
            BasicACLPermission.DELETE), IdentityFactory.createIdentity("Identity-5"));
      ACLEntryImpl entry6 = new ACLEntryImpl(new CompositeACLPermission(BasicACLPermission.values()), IdentityFactory
            .createIdentity("Identity-6"));

      ACLImpl acl1 = null;
      ACLImpl acl2 = null;
      EntityTransaction transaction = this.entityManager.getTransaction();
      transaction.begin();
      try
      {
         acl1 = this.entityManager.merge(this.persistedACLs.get(0));
         acl1.addEntry(entry4);
         acl1.addEntry(entry5);

         acl2 = this.entityManager.merge(this.persistedACLs.get(1));
         acl2.addEntry(entry6);
         acl2.removeEntry(this.persistedEntries.get(0));
         transaction.commit();
      }
      catch (RuntimeException re)
      {
         re.printStackTrace();
         transaction.rollback();
      }

      // add the new entries to the persisted entries collection.
      this.persistedEntries.add(entry4);
      this.persistedEntries.add(entry5);
      this.persistedEntries.add(entry6);

      // clear the entity manager's cache.
      this.entityManager.clear();

      // load the ACLs again and validate the changes.
      ACLImpl loadedACL1 = this.entityManager.find(ACLImpl.class, acl1.getACLId());
      assertNotNull("ACL1 could not be retrieved", loadedACL1);
      assertEquals("Loaded ACL contains unexpected number of entries", 2, loadedACL1.getEntries().size());
      assertTrue(loadedACL1.getEntries().contains(entry4));
      assertTrue(loadedACL1.getEntries().contains(entry5));

      ACLImpl loadedACL2 = this.entityManager.find(ACLImpl.class, acl2.getACLId());
      assertNotNull("ACL2 could not be retrieved", loadedACL2);
      assertEquals("Loaded AC2 contains unexpected number of entries", 3, loadedACL2.getEntries().size());
      assertFalse(loadedACL2.getEntries().contains(this.persistedEntries.get(0)));
      assertTrue(loadedACL2.getEntries().contains(this.persistedEntries.get(1)));
      assertTrue(loadedACL2.getEntries().contains(this.persistedEntries.get(2)));
      assertTrue(loadedACL2.getEntries().contains(entry6));
   }

   /**
    * <p>
    * Persists the specified entity to the database.
    * </p>
    * 
    * @param entity an {@code Object} representing the entity to be persisted.
    */
   private void persistEntity(Object entity)
   {
      EntityTransaction transaction = this.entityManager.getTransaction();
      transaction.begin();
      try
      {
         this.entityManager.persist(entity);
         transaction.commit();
      }
      catch (RuntimeException re)
      {
         re.printStackTrace();
         transaction.rollback();
      }
   }

   /**
    * <p>
    * Removes the specified entity from the database.
    * </p>
    * 
    * @param entity an {@code Object} representing the entity to be removed.
    */
   private void removeEntity(Object entity)
   {
      EntityTransaction transaction = this.entityManager.getTransaction();
      transaction.begin();
      try
      {
         this.entityManager.remove(entity);
         transaction.commit();
      }
      catch (RuntimeException re)
      {
         re.printStackTrace();
         transaction.rollback();
      }
   }
}
