content: /*****************************************************************
   *   Licensed to the Apache Software Foundation (ASF) under one
   *  or more contributor license agreements.  See the NOTICE file
   *  distributed with this work for additional information
   *  regarding copyright ownership.  The ASF licenses this file
   *  to you under the Apache License, Version 2.0 (the
   *  "License"); you may not use this file except in compliance
   *  with the License.  You may obtain a copy of the License at
   *
   *    https://www.apache.org/licenses/LICENSE-2.0
   *
   *  Unless required by applicable law or agreed to in writing,
   *  software distributed under the License is distributed on an
   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
   *  KIND, either express or implied.  See the License for the
   *  specific language governing permissions and limitations
   *  under the License.
   ****************************************************************/
  
  package org.apache.cayenne.jcache;
  
  import org.apache.cayenne.cache.QueryCache;
  import org.apache.cayenne.cache.QueryCacheEntryFactory;
  import org.apache.cayenne.di.BeforeScopeEnd;
  import org.apache.cayenne.di.Inject;
  import org.apache.cayenne.query.QueryMetadata;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  import javax.cache.Cache;
  import javax.cache.CacheException;
  import javax.cache.CacheManager;
  import java.util.Collections;
  import java.util.List;
  import java.util.Objects;
  import java.util.Set;
  import java.util.concurrent.ConcurrentHashMap;
  
  /**
   * @since 4.0
   */
  public class JCacheQueryCache implements QueryCache {
  
      private static final Logger LOGGER = LoggerFactory.getLogger(QueryCache.class);
  
      @Inject
      protected CacheManager cacheManager;
  
      @Inject
      protected JCacheConfigurationFactory configurationFactory;
  
      private Set<String> seenCacheNames = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());
  
      @Override
      public List get(QueryMetadata metadata) {
          String key = Objects.requireNonNull(metadata.getCacheKey());
          Cache<String, List> cache = createIfAbsent(metadata);
  
          return cache.get(key);
      }
  
      @Override
      public List get(QueryMetadata metadata, QueryCacheEntryFactory factory) {
          String key = Objects.requireNonNull(metadata.getCacheKey());
          Cache<String, List> cache = createIfAbsent(metadata);
  
          List<?> result = cache.get(key);
          return result != null
                  ? result
                  : cache.invoke(key, new JCacheEntryLoader(factory));
      }
  
      @Override
      public void put(QueryMetadata metadata, List results) {
          String key = Objects.requireNonNull(metadata.getCacheKey());
          Cache<String, List> cache = createIfAbsent(metadata);
  
          cache.put(key, results);
      }
  
      @Override
      public void remove(String key) {
          if (key != null) {
              for (String cache : cacheManager.getCacheNames()) {
                  getCache(cache).remove(key);
              }
          }
      }
  
      @Override
      public void removeGroup(String groupKey) {
          Cache<String, List> cache = getCache(groupKey);
          if (cache != null) {
              cache.clear();
          }
      }
  
      @Override
      public void removeGroup(String groupKey, Class<?> keyType, Class<?> valueType) {
          Cache cache = cacheManager.getCache(groupKey, keyType, valueType);
          if (cache != null) {
              cache.clear();
          }
      }
  
      /**
       * @deprecated since 4.0 . Note that this method should not be relied upon to clear caches consistently. It only
       * operates on a subset of caches that were created by this object, and ignores any preconfigured caches.
       */
      @Deprecated
      @Override
      public void clear() {
          for (String name : seenCacheNames) {
              getCache(name).clear();
          }
      }
  
      protected Cache<String, List> createIfAbsent(QueryMetadata metadata) {
          return createIfAbsent(cacheName(metadata));
      }
  
      @SuppressWarnings("unchecked")
      protected Cache<String, List> createIfAbsent(String cacheName) {
  
          Cache<String, List> cache = getCache(cacheName);
          if (cache == null) {
  
              try {
                  cache = createCache(cacheName);
              } catch (CacheException e) {
                  // someone else just created this cache?
                  cache = getCache(cacheName);
                  if (cache == null) {
                      // giving up... the error was about something else...
                      throw e;
                  }
              }
  
              seenCacheNames.add(cacheName);
          }
  
          return cache;
      }
  
      protected Cache createCache(String cacheName) {
          // Cache creation here can lead to a memory leak, see CAY-2642 for details.
          LOGGER.warn("Creating a new JCache entry '{}'. It will be unlimited by default, and that can lead to greater memory usage or even leak. " +
                  "This entry could be configured by JCache provider-specific configuration.", cacheName);
          return cacheManager.createCache(cacheName, configurationFactory.create(cacheName));
      }
  
      protected Cache<String, List> getCache(String name) {
          return cacheManager.getCache(name);
      }
  
      protected String cacheName(QueryMetadata metadata) {
  
          String cacheGroup = metadata.getCacheGroup();
          if (cacheGroup != null) {
              return cacheGroup;
          }
  
          // no explicit cache group
          return JCacheConstants.DEFAULT_CACHE_NAME;
      }
  
      @BeforeScopeEnd
      public void shutdown() {
          cacheManager.close();
      }
  }
  