/*
 * Hibernate, Relational Persistence for Idiomatic Java
 *
 * License: GNU Lesser General Public License (LGPL), version 2.1 or later.
 * See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
 */
package org.hibernate.userguide.caching;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.cache.configuration.MutableConfiguration;
import javax.persistence.Cacheable;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Version;

import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.cache.jcache.JCacheHelper;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;

import org.junit.Test;

import org.jboss.logging.Logger;

import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;


/**
 * @author Vlad Mihalcea
 */
public class NonStrictReadWriteCacheTest extends BaseEntityManagerFunctionalTestCase {

	@Override
	public void buildEntityManagerFactory() {
		JCacheHelper.locateStandardCacheManager().createCache(
				"hibernate.test.org.hibernate.userguide.caching.NonStrictReadWriteCacheTest$Person",
				new MutableConfiguration<>()
		);
		JCacheHelper.locateStandardCacheManager().createCache(
				"hibernate.test.org.hibernate.userguide.caching.NonStrictReadWriteCacheTest$Phone",
				new MutableConfiguration<>()
		);
		JCacheHelper.locateStandardCacheManager().createCache(
				"hibernate.test.org.hibernate.userguide.caching.NonStrictReadWriteCacheTest$Person.phones",
				new MutableConfiguration<>()
		);

		super.buildEntityManagerFactory();
	}

	@Override
    protected Class<?>[] getAnnotatedClasses() {
        return new Class<?>[] {
            Person.class,
			Phone.class
        };
    }

    @Override
    @SuppressWarnings( "unchecked" )
    protected void addConfigOptions(Map options) {
        options.put( AvailableSettings.USE_SECOND_LEVEL_CACHE, Boolean.TRUE.toString() );
        options.put( AvailableSettings.CACHE_REGION_FACTORY, "jcache" );
    }

    @Test
    public void testCache() {
        doInJPA( this::entityManagerFactory, entityManager -> {
			Person person = new Person();
            entityManager.persist( person );
			Phone home = new Phone( "123-456-7890" );
			Phone office = new Phone( "098-765-4321" );
			person.addPhone( home );
			person.addPhone( office );
        });
		doInJPA( this::entityManagerFactory, entityManager -> {
			Person person = entityManager.find( Person.class, 1L );
			person.getPhones().size();
		});
		doInJPA( this::entityManagerFactory, entityManager -> {
			log.info( "Log collection from cache" );
			//tag::caching-collection-example[]
			Person person = entityManager.find( Person.class, 1L );
			person.getPhones().size();
			//end::caching-collection-example[]
		});
        doInJPA( this::entityManagerFactory, entityManager -> {
			log.info( "Load from cache" );
            entityManager.find( Person.class, 1L ).getPhones().size();
        });
    }


    @Entity(name = "Person")
	@Cacheable
    @org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
    public static class Person {

        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        private Long id;

        private String name;

		//tag::caching-collection-mapping-example[]
		@OneToMany(mappedBy = "person", cascade = CascadeType.ALL)
		@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
		private List<Phone> phones = new ArrayList<>(  );
		//end::caching-collection-mapping-example[]

        @Version
        private int version;

        public Person() {}

        public Person(String name) {
            this.name = name;
        }

        public Long getId() {
            return id;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

		public List<Phone> getPhones() {
			return phones;
		}

		public void addPhone(Phone phone) {
			phones.add( phone );
			phone.setPerson( this );
		}
	}

	//tag::caching-entity-mapping-example[]
	@Entity(name = "Phone")
	@Cacheable
	@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
	public static class Phone {

		@Id
		@GeneratedValue
		private Long id;

		private String mobile;

		@ManyToOne
		private Person person;

		@Version
		private int version;

		//Getters and setters are omitted for brevity

	//end::caching-entity-mapping-example[]

		public Phone() {}

		public Phone(String mobile) {
			this.mobile = mobile;
		}

		public Long getId() {
			return id;
		}

		public String getMobile() {
			return mobile;
		}

		public Person getPerson() {
			return person;
		}

		public void setPerson(Person person) {
			this.person = person;
		}
	//tag::caching-entity-mapping-example[]
	}
	//end::caching-entity-mapping-example[]
}
