<commit_before>/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * 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 com.celements.store;

import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;

import org.python.google.common.base.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xwiki.cache.Cache;
import org.xwiki.cache.CacheException;
import org.xwiki.cache.CacheFactory;
import org.xwiki.cache.CacheManager;
import org.xwiki.cache.config.CacheConfiguration;
import org.xwiki.cache.eviction.EntryEvictionConfiguration;
import org.xwiki.cache.eviction.LRUEvictionConfiguration;
import org.xwiki.component.annotation.Component;
import org.xwiki.component.annotation.Requirement;
import org.xwiki.component.manager.ComponentLookupException;
import org.xwiki.configuration.ConfigurationSource;
import org.xwiki.context.Execution;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.model.reference.EntityReferenceSerializer;
import org.xwiki.query.QueryManager;

import com.xpn.xwiki.XWikiContext;
import com.xpn.xwiki.XWikiException;
import com.xpn.xwiki.doc.XWikiDocument;
import com.xpn.xwiki.doc.XWikiLink;
import com.xpn.xwiki.doc.XWikiLock;
import com.xpn.xwiki.objects.classes.BaseClass;
import com.xpn.xwiki.store.XWikiCacheStoreInterface;
import com.xpn.xwiki.store.XWikiStoreInterface;
import com.xpn.xwiki.web.Utils;

/**
 * A proxy store implementation that caches Documents when they are first fetched and subsequently
 * return them from a
 * cache. It delegates all write and search operations to an underlying store without doing any
 * caching on them.
 *
 * @version $Id$
 */
@Component(DocumentCacheStore.COMPONENT_NAME)
public class DocumentCacheStore implements XWikiCacheStoreInterface {

  private final static Logger LOGGER = LoggerFactory.getLogger(DocumentCacheStore.class);
  private final static Logger LOGGER_DL = LoggerFactory.getLogger(DocumentLoader.class);

  public static final String COMPONENT_NAME = "DocumentCacheStore";

  public static final String PARAM_DOC_CACHE_CAPACITY = "xwiki.store.cache.capacity";
  public static final String PARAM_EXIST_CACHE_CAPACITY = "xwiki.store.cache.pageexistcapacity";
  public static final String BACKING_STORE_STRATEGY = "celements.store.cache.storeStrategy";

  @Requirement("xwikiproperties")
  private ConfigurationSource config;

  @Requirement
  private CacheManager cacheManager;

  @Requirement
  private Execution execution;

  @Requirement("default")
  private EntityReferenceSerializer<String> serializerDefault;

  /**
   * Lazy initialized according to backing store strategy configuration.
   * The store field is immutable in the following way:
   * even though it could be initialized in multiple threads because of missing memory visibility,
   * it always results in the same value. Thus a thread could not tell if it was multiple times
   * initialized. Hence no volatile modificator is needed.
   */
  private XWikiStoreInterface store;

  /**
   * CAUTION: Lazy initialized of cache thus volatile is needed.
   */
  private volatile Cache<XWikiDocument> docCache;

  /**
   * CAUTION: Lazy initialized of cache thus volatile is needed.
   */
  private volatile Cache<Boolean> existCache;

  private final ConcurrentMap<String, DocumentLoader> documentLoaderMap = new ConcurrentHashMap<>();

  private XWikiContext getContext() {
    return (XWikiContext) execution.getContext().getProperty(XWikiContext.EXECUTIONCONTEXT_KEY);
  }

  void initalize() {
    if ((this.docCache == null) || (this.existCache == null)) {
      synchronized (this) {
        try {
          if (this.docCache == null) {
            initializeDocCache();
          }
          if (this.existCache == null) {
            initializeExistCache();
          }
        } catch (CacheException cacheExp) {
          LOGGER.error("Failed to initialize document cache.", cacheExp);
          throw new RuntimeException("FATAL: Failed to initialize document cache.", cacheExp);
        }
      }
    }
  }

  private void initializeExistCache() throws CacheException {
    CacheConfiguration config = new CacheConfiguration();
    config.setConfigurationId("xwiki.store.pageexistcache");
    LRUEvictionConfiguration lru = new LRUEvictionConfiguration();
    lru.setMaxEntries(getExistCacheCapacity());
    config.put(EntryEvictionConfiguration.CONFIGURATIONID, lru);
    existCache = getCacheFactory().newCache(config);
  }

  private CacheFactory getCacheFactory() {
    try {
      return cacheManager.getCacheFactory();
    } catch (ComponentLookupException exp) {
      throw new RuntimeException("Failed to get cache factory component", exp);
    }
  }

  private int getExistCacheCapacity() {
    int existCacheCapacity = 10000;
    String existsCapacity = getContext().getWiki().Param(PARAM_EXIST_CACHE_CAPACITY);
    if (existsCapacity != null) {
      try {
        existCacheCapacity = Integer.parseInt(existsCapacity);
      } catch (NumberFormatException exp) {
        LOGGER.warn("Failed to read '{}' using default '{}'", PARAM_EXIST_CACHE_CAPACITY,
            existCacheCapacity, exp);
      }
    }
    return existCacheCapacity;
  }

  private void initializeDocCache() throws CacheException {
    CacheConfiguration config = new CacheConfiguration();
    config.setConfigurationId("xwiki.store.pagecache");
    LRUEvictionConfiguration lru = new LRUEvictionConfiguration();
    lru.setMaxEntries(getDocCacheCapacity());
    config.put(EntryEvictionConfiguration.CONFIGURATIONID, lru);
    docCache = getCacheFactory().newCache(config);
  }

  private int getDocCacheCapacity() {
    int docCacheCapacity = 100;
    String capacity = getContext().getWiki().Param(PARAM_DOC_CACHE_CAPACITY);
    if (capacity != null) {
      try {
        docCacheCapacity = Integer.parseInt(capacity);
      } catch (NumberFormatException exp) {
        LOGGER.warn("Failed to read xwiki.store.cache.capacity using default '{}'",
            docCacheCapacity, exp);
      }
    }
    return docCacheCapacity;
  }

  @Override<commit_msg>adding exists cache capacity config check<commit_after>/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * 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 com.celements.store;

import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;

import org.python.google.common.base.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xwiki.cache.Cache;
import org.xwiki.cache.CacheException;
import org.xwiki.cache.CacheFactory;
import org.xwiki.cache.CacheManager;
import org.xwiki.cache.config.CacheConfiguration;
import org.xwiki.cache.eviction.EntryEvictionConfiguration;
import org.xwiki.cache.eviction.LRUEvictionConfiguration;
import org.xwiki.component.annotation.Component;
import org.xwiki.component.annotation.Requirement;
import org.xwiki.component.manager.ComponentLookupException;
import org.xwiki.configuration.ConfigurationSource;
import org.xwiki.context.Execution;
import org.xwiki.model.reference.DocumentReference;
import org.xwiki.model.reference.EntityReferenceSerializer;
import org.xwiki.query.QueryManager;

import com.xpn.xwiki.XWikiContext;
import com.xpn.xwiki.XWikiException;
import com.xpn.xwiki.doc.XWikiDocument;
import com.xpn.xwiki.doc.XWikiLink;
import com.xpn.xwiki.doc.XWikiLock;
import com.xpn.xwiki.objects.classes.BaseClass;
import com.xpn.xwiki.store.XWikiCacheStoreInterface;
import com.xpn.xwiki.store.XWikiStoreInterface;
import com.xpn.xwiki.web.Utils;

/**
 * A proxy store implementation that caches Documents when they are first fetched and subsequently
 * return them from a
 * cache. It delegates all write and search operations to an underlying store without doing any
 * caching on them.
 *
 * @version $Id$
 */
@Component(DocumentCacheStore.COMPONENT_NAME)
public class DocumentCacheStore implements XWikiCacheStoreInterface {

  private final static Logger LOGGER = LoggerFactory.getLogger(DocumentCacheStore.class);
  private final static Logger LOGGER_DL = LoggerFactory.getLogger(DocumentLoader.class);

  public static final String COMPONENT_NAME = "DocumentCacheStore";

  public static final String PARAM_DOC_CACHE_CAPACITY = "xwiki.store.cache.capacity";
  public static final String PARAM_EXIST_CACHE_CAPACITY = "xwiki.store.cache.pageexistcapacity";
  public static final String BACKING_STORE_STRATEGY = "celements.store.cache.storeStrategy";

  @Requirement("xwikiproperties")
  private ConfigurationSource config;

  @Requirement
  private CacheManager cacheManager;

  @Requirement
  private Execution execution;

  @Requirement("default")
  private EntityReferenceSerializer<String> serializerDefault;

  /**
   * Lazy initialized according to backing store strategy configuration.
   * The store field is immutable in the following way:
   * even though it could be initialized in multiple threads because of missing memory visibility,
   * it always results in the same value. Thus a thread could not tell if it was multiple times
   * initialized. Hence no volatile modificator is needed.
   */
  private XWikiStoreInterface store;

  /**
   * CAUTION: Lazy initialized of cache thus volatile is needed.
   */
  private volatile Cache<XWikiDocument> docCache;

  /**
   * CAUTION: Lazy initialized of cache thus volatile is needed.
   */
  private volatile Cache<Boolean> existCache;

  private final ConcurrentMap<String, DocumentLoader> documentLoaderMap = new ConcurrentHashMap<>();

  private XWikiContext getContext() {
    return (XWikiContext) execution.getContext().getProperty(XWikiContext.EXECUTIONCONTEXT_KEY);
  }

  void initalize() {
    if ((this.docCache == null) || (this.existCache == null)) {
      synchronized (this) {
        try {
          if (this.docCache == null) {
            initializeDocCache();
          }
          if (this.existCache == null) {
            initializeExistCache();
          }
        } catch (CacheException cacheExp) {
          LOGGER.error("Failed to initialize document cache.", cacheExp);
          throw new RuntimeException("FATAL: Failed to initialize document cache.", cacheExp);
        }
      }
    }
  }

  private void initializeExistCache() throws CacheException {
    CacheConfiguration config = new CacheConfiguration();
    config.setConfigurationId("xwiki.store.pageexistcache");
    LRUEvictionConfiguration lru = new LRUEvictionConfiguration();
    lru.setMaxEntries(getExistCacheCapacity());
    config.put(EntryEvictionConfiguration.CONFIGURATIONID, lru);
    existCache = getCacheFactory().newCache(config);
  }

  private CacheFactory getCacheFactory() {
    try {
      return cacheManager.getCacheFactory();
    } catch (ComponentLookupException exp) {
      throw new RuntimeException("Failed to get cache factory component", exp);
    }
  }

  private int getExistCacheCapacity() {
    int existCacheCapacity = 10000;
    String existsCapacity = getContext().getWiki().Param(PARAM_EXIST_CACHE_CAPACITY);
    if (existsCapacity != null) {
      try {
        existCacheCapacity = Integer.parseInt(existsCapacity);
      } catch (NumberFormatException exp) {
        LOGGER.warn("Failed to read '{}' using default '{}'", PARAM_EXIST_CACHE_CAPACITY,
            existCacheCapacity, exp);
      }
    }
    int docCacheCapacity = getDocCacheCapacity();
    if (existCacheCapacity < docCacheCapacity) {
      LOGGER.warn("WARNING: document exists cache capacity is smaller configured than docCache "
          + "capacity. Ignoring exists cache configuration '{}' and using doc cache capacity '{}'"
          + " instead.", existCacheCapacity, docCacheCapacity);
      existCacheCapacity = docCacheCapacity;
    }
    return existCacheCapacity;
  }

  private void initializeDocCache() throws CacheException {
    CacheConfiguration config = new CacheConfiguration();
    config.setConfigurationId("xwiki.store.pagecache");
    LRUEvictionConfiguration lru = new LRUEvictionConfiguration();
    lru.setMaxEntries(getDocCacheCapacity());
    config.put(EntryEvictionConfiguration.CONFIGURATIONID, lru);
    docCache = getCacheFactory().newCache(config);
  }

  private int getDocCacheCapacity() {
    int docCacheCapacity = 100;
    String capacity = getContext().getWiki().Param(PARAM_DOC_CACHE_CAPACITY);
    if (capacity != null) {
      try {
        docCacheCapacity = Integer.parseInt(capacity);
      } catch (NumberFormatException exp) {
        LOGGER.warn("Failed to read xwiki.store.cache.capacity using default '{}'",
            docCacheCapacity, exp);
      }
    }
    return docCacheCapacity;
  }

  @Override
{'fraction_non_alphanumeric': 0.08650519031141868, 'fraction_numerical': 0.0031833910034602076, 'mean_word_length': 4.203817068779258, 'pattern_counts': {'":': 0, '<': 14, '<?xml version=': 0, '>': 13, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5322621', 'n_tokens_mistral': 3986, 'n_tokens_neox': 3908, 'n_words': 1129}