/*
 * JBoss, Home of Professional Open Source
 * Copyright 2009 Red Hat Inc. and/or its affiliates and other
 * contributors as indicated by the @author tags. All rights reserved.
 * 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.infinispan.loaders;

import org.infinispan.config.ConfigurationBeanVisitor;
import org.infinispan.config.ConfigurationDoc;
import org.infinispan.config.ConfigurationDocRef;
import org.infinispan.loaders.decorators.AsyncStoreConfig;
import org.infinispan.loaders.decorators.SingletonStoreConfig;
import org.infinispan.util.TypedProperties;
import org.infinispan.util.Util;

import javax.xml.bind.annotation.*;

/**
 * Configures {@link AbstractCacheStore}. This allows you to tune a number of characteristics of the
 * {@link AbstractCacheStore}.
 * <p/>
 * <ul>
 * <li><tt>purgeSynchronously</tt> - whether
 * {@link org.infinispan.loaders.CacheStore#purgeExpired()} calls happen synchronously or not. By
 * default, this is set to <tt>false</tt>.</li>
 * <li><tt>purgerThreads</tt> - number of threads to use when purging. Defaults to <tt>1</tt> if
 * <tt>purgeSynchronously</tt> is <tt>true</tt>, ignored if <tt>false</tt>.</li>
 * </ul>
 * 
 * 
 * 
 * @author Mircea.Markus@jboss.com
 * @author Vladimir Blagojevic
 * @since 4.0
 * 
 * @see <a href="../../../config.html#ce_loaders_loader">Configuration reference</a>
 */
@XmlAccessorType(XmlAccessType.PUBLIC_MEMBER)
@XmlType(propOrder= {})
@ConfigurationDoc(name="loader",desc="Responsible for loading/storing cache data from/to an external source.")
@SuppressWarnings("boxing")
public class AbstractCacheStoreConfig extends AbstractCacheLoaderConfig implements CacheStoreConfig {

   /** The serialVersionUID */
   private static final long serialVersionUID = 4607371052771122893L;
   
   @ConfigurationDocRef(bean=AbstractCacheStoreConfig.class,targetElement="setIgnoreModifications")
   protected Boolean ignoreModifications = false;

   @ConfigurationDocRef(bean=AbstractCacheStoreConfig.class,targetElement="setFetchPersistentState")
   protected Boolean fetchPersistentState = false;

   @ConfigurationDocRef(bean=AbstractCacheStoreConfig.class,targetElement="setPurgeOnStartup")
   protected Boolean purgeOnStartup = false;

   @ConfigurationDocRef(bean=AbstractCacheStoreConfig.class,targetElement="setPurgeSynchronously")
   protected Boolean purgeSynchronously = false;

   @ConfigurationDocRef(bean=AbstractCacheStoreConfig.class,targetElement="setPurgerThreads")
   protected Integer purgerThreads = 1;

   protected SingletonStoreConfig singletonStore = new SingletonStoreConfig();

   protected AsyncStoreConfig async = new AsyncStoreConfig();
   

   @Override
   public AsyncStoreConfig asyncStore() {
      async.setEnabled(true);
      async.setCacheStoreConfig(this);
      return async;
   }

   @Override
   public SingletonStoreConfig singletonStore() {
      singletonStore.setSingletonStoreEnabled(true);
      singletonStore.setCacheStoreConfig(this);
      return singletonStore;
   }

   @Override
   @XmlAttribute
   public Boolean isPurgeSynchronously() {
      return purgeSynchronously;
   }

   @Override
   @XmlAttribute
   public Integer getPurgerThreads() {
      return purgerThreads;
   }
   
   @XmlElement(name="properties")
   public TypedProperties getTypedProperties(){
      return properties;      
   }
   
   public void setTypedProperties (TypedProperties tp){
      this.properties = tp;
   }

   /**
    * If true, CacheStore#purgeExpired() call will be done synchronously
    *
    * @param purgeSynchronously
    */
   @Override
   public void setPurgeSynchronously(Boolean purgeSynchronously) {
      testImmutability("purgeSynchronously");
      this.purgeSynchronously = purgeSynchronously;
   }
   
   /**
    * If true, CacheStore#purgeExpired() call will be done synchronously
    * 
    * @param purgeSynchronously
    */
   @Override
   public CacheStoreConfig purgeSynchronously(Boolean purgeSynchronously) {
      testImmutability("purgeSynchronously");
      this.purgeSynchronously = purgeSynchronously;
      return this;
   }

   /**
    * The number of threads to use when purging asynchronously.
    * 
    * @param purgerThreads
    * @deprecated use {@link #purgerThreads(Integer)} instead
   */
   @Deprecated
   public void setPurgerThreads(Integer purgerThreads) {
      testImmutability("purgerThreads");
      this.purgerThreads = purgerThreads;
   }

   @Override
   public CacheStoreConfig purgerThreads(Integer purgerThreads) {
      setPurgerThreads(purgerThreads);
      return this;
   }

   @Override
   @XmlAttribute
   public Boolean isFetchPersistentState() {
      return fetchPersistentState;
   }

   /**
    * If true, fetch persistent state when joining a cluster. If multiple cache stores are chained,
    * only one of them can have this property enabled. Persistent state transfer with a shared cache
    * store does not make sense, as the same persistent store that provides the data will just end
    * up receiving it. Therefore, if a shared cache store is used, the cache will not allow a
    * persistent state transfer even if a cache store has this property set to true. Finally,
    * setting it to true only makes sense if in a clustered environment, and only 'replication' and
    * 'invalidation' cluster modes are supported.
    * 
    * 
    * @param fetchPersistentState
    */
   @Override
   public void setFetchPersistentState(Boolean fetchPersistentState) {
      testImmutability("fetchPersistentState");
      this.fetchPersistentState = fetchPersistentState;
   }
   
   /**
    * If true, fetch persistent state when joining a cluster. If multiple cache stores are chained,
    * only one of them can have this property enabled. Persistent state transfer with a shared cache
    * store does not make sense, as the same persistent store that provides the data will just end
    * up receiving it. Therefore, if a shared cache store is used, the cache will not allow a
    * persistent state transfer even if a cache store has this property set to true. Finally,
    * setting it to true only makes sense if in a clustered environment, and only 'replication' and
    * 'invalidation' cluster modes are supported.
    * 
    * 
    * @param fetchPersistentState
    */
   @Override
   public CacheStoreConfig fetchPersistentState(Boolean fetchPersistentState) {
      testImmutability("fetchPersistentState");
      this.fetchPersistentState = fetchPersistentState;
      return this;
   }

   /**
    * If true, any operation that modifies the cache (put, remove, clear, store...etc) won't be
    * applied to the cache store. This means that the cache store could become out of sync with the
    * cache.
    * 
    * @param ignoreModifications
    */
   @Override
   public void setIgnoreModifications(Boolean ignoreModifications) {
      testImmutability("ignoreModifications");
      this.ignoreModifications = ignoreModifications;    
   }
   
   /**
    * If true, any operation that modifies the cache (put, remove, clear, store...etc) won't be
    * applied to the cache store. This means that the cache store could become out of sync with the
    * cache.
    * 
    * @param ignoreModifications
    */
   @Override
   public CacheStoreConfig ignoreModifications(Boolean ignoreModifications) {
      testImmutability("ignoreModifications");
      this.ignoreModifications = ignoreModifications;
      return this;
   }

   @Override
   @XmlAttribute
   public Boolean isIgnoreModifications() {
      return ignoreModifications;
   }
   
   @Override
   @XmlAttribute
   public Boolean isPurgeOnStartup() {
      return purgeOnStartup;
   }

   /**
    * 
    * If true, purges this cache store when it starts up.
    * 
    * @param purgeOnStartup
    */
   @Override
   public CacheStoreConfig purgeOnStartup(Boolean purgeOnStartup) {
      testImmutability("purgeOnStartup");
      this.purgeOnStartup = purgeOnStartup;
      return this;
   }
   
   /**
    * 
    * If true, purges this cache store when it starts up.
    * 
    * @param purgeOnStartup
    */
   @Override
   public void setPurgeOnStartup(Boolean purgeOnStartup) {
      testImmutability("purgeOnStartup");
      this.purgeOnStartup = purgeOnStartup;
   }

   @Override
   @XmlElement(name="singletonStore")
   public SingletonStoreConfig getSingletonStoreConfig() {
      return singletonStore;
   }

   @Override
   public void setSingletonStoreConfig(SingletonStoreConfig singletonStoreConfig) {
      testImmutability("singletonStore");
      this.singletonStore = singletonStoreConfig;
   }

   @Override
   @XmlElement(name="async")
   public AsyncStoreConfig getAsyncStoreConfig() {
      return async;      
   }

   @Override
   public void setAsyncStoreConfig(AsyncStoreConfig asyncStoreConfig) {
      testImmutability("async");
      this.async = asyncStoreConfig;      
   }
   
   @Override
   public void accept(ConfigurationBeanVisitor v) {
      singletonStore.accept(v);
      async.accept(v);
      v.visitCacheLoaderConfig(this);
   }

   @Override
   public boolean equals(Object obj) {
      if (super.equals(obj)) {
         if (!(obj instanceof AbstractCacheStoreConfig)) return false;
         AbstractCacheStoreConfig i = (AbstractCacheStoreConfig) obj;
         return equalsExcludingProperties(i);
      }
      return false;
   }

   protected boolean equalsExcludingProperties(Object obj) {
      AbstractCacheStoreConfig other = (AbstractCacheStoreConfig) obj;

      return Util.safeEquals(this.cacheLoaderClassName, other.cacheLoaderClassName)
            && (this.ignoreModifications.equals(other.ignoreModifications))
            && (this.fetchPersistentState.equals(other.fetchPersistentState))
            && Util.safeEquals(this.singletonStore, other.singletonStore)
            && Util.safeEquals(this.async, other.async)
            && Util.safeEquals(this.purgeSynchronously, other.purgeSynchronously)
            && Util.safeEquals(this.purgerThreads, other.purgerThreads);
   }

   @Override
   public int hashCode() {
      return 31 * hashCodeExcludingProperties() + (properties == null ? 0 : properties.hashCode());
   }

   protected int hashCodeExcludingProperties() {
      int result = 17;
      result = 31 * result + (cacheLoaderClassName == null ? 0 : cacheLoaderClassName.hashCode());
      result = 31 * result + (ignoreModifications ? 0 : 1);
      result = 31 * result + (fetchPersistentState ? 0 : 1);
      result = 31 * result + (singletonStore == null ? 0 : singletonStore.hashCode());
      result = 31 * result + (async == null ? 0 : async.hashCode());
      result = 31 * result + (purgeOnStartup ? 0 : 1);
      result = 31 * result + (purgerThreads);
      return result;
   }

   @Override
   public String toString() {
      return new StringBuilder().append(getClass().getSimpleName()).append("{").append("className='").append(cacheLoaderClassName).append('\'')
            .append(", ignoreModifications=").append(ignoreModifications)
            .append(", fetchPersistentState=").append(fetchPersistentState)
            .append(", properties=").append(properties)
            .append(", purgeOnStartup=").append(purgeOnStartup).append("},")
            .append(", singletonStore{").append(singletonStore).append('}')
            .append(", async{").append(async).append('}')
            .append(", purgeSynchronously{").append(purgeSynchronously).append('}')
            .append(", purgerThreads{").append(purgerThreads).append('}')
            .toString();
   }

   @Override
   public AbstractCacheStoreConfig clone() {
      AbstractCacheStoreConfig clone = (AbstractCacheStoreConfig) super.clone();
      if (singletonStore != null) clone.setSingletonStoreConfig(singletonStore.clone());
      if (async != null) clone.setAsyncStoreConfig(async.clone());
      return clone;
   }
}
