/*
 * 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
 * 
 *   http://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.
 */


#ifndef PAGESPEED_SYSTEM_SYSTEM_CACHES_H_
#define PAGESPEED_SYSTEM_SYSTEM_CACHES_H_

#include <map>
#include <vector>

#include "pagespeed/kernel/base/basictypes.h"
#include "pagespeed/kernel/base/cache_interface.h"
#include "pagespeed/kernel/base/md5_hasher.h"
#include "pagespeed/kernel/base/message_handler.h"
#include "pagespeed/kernel/base/scoped_ptr.h"
#include "pagespeed/kernel/base/string.h"
#include "pagespeed/kernel/base/string_util.h"
#include "pagespeed/kernel/sharedmem/shared_mem_cache.h"
#include "pagespeed/system/redis_cache.h"
#include "pagespeed/system/system_rewrite_options.h"

namespace net_instaweb {

class AbstractSharedMem;
class AprMemCache;
class NamedLockManager;
class QueuedWorkerPool;
class RewriteDriverFactory;
class ServerContext;
class SlowWorker;
class Statistics;
class SystemCachePath;

// Helps manage setup of cache backends provided by the PSOL library
// (LRU, File, Memcached, and shared memory metadata), as well as named lock
// managers. The expectation is that the RewriteDriverFactory for the server
// will invoke this class's methods in appropriate spots.
//
// It is also expected that the RootInit() method will be called during server
// setup before the server launches any additional processes, and ChildInit()
// will be called on any child process handling requests. If the server
// is single-process, both methods should be called.
//
// Keep in mind, however, that when fork() is involved a process may
// effectively see both calls, in which case the 'ChildInit' call would
// come second and override the previous root status.
class SystemCaches {
 public:
  // CacheStats prefixes.
  static const char kMemcachedAsync[];
  static const char kMemcachedBlocking[];
  static const char kRedisAsync[];
  static const char kRedisBlocking[];
  static const char kShmCache[];

  static const char kDefaultSharedMemoryPath[];

  enum StatFlags {
    kDefaultStatFlags = 0,
    kGlobalView = 1,
    kIncludeMemcached = 2,
    kIncludeRedis = 4
  };

  // Registers all statistics the cache backends may use.
  static void InitStats(Statistics* statistics);

  // thread_limit is an estimate of number of threads that may access the
  // cache at the same time. Does not take ownership of shm_runtime.
  SystemCaches(RewriteDriverFactory* factory,
               AbstractSharedMem* shm_runtime,
               int thread_limit);

  // Note that you must call ShutDown() before this is deleted.
  ~SystemCaches();

  bool is_root_process() const { return is_root_process_; }

  // Note: RegisterConfig must be called for all relevant configurations
  // before calling RootInit()
  void RegisterConfig(SystemRewriteOptions* config);
  void RootInit();
  void ChildInit();

  // Tries to block all asynchronous cache activity, causing lookups to
  // fail, to help quicker shutdown. Not 100% guaranteed to work, as not
  // all backends implement it.
  void StopCacheActivity();

  // Actually stops some of the work threads, and queues up deferred deletion of
  // various objects on the RewriteDriverFactory.
  void ShutDown(MessageHandler* message_handler);

  // Configures server_context's caches based on its configuration.
  void SetupCaches(ServerContext* server_context, bool enable_property_cache);

  // Creates & registers a shared memory metadata cache segment with given
  // name and size.
  //
  // Returns whether successful or not, and if not, *error_msg will contain
  // an error message.  Meant to be called from config parsing.
  bool CreateShmMetadataCache(
      StringPiece name, int64 size_kb, GoogleString* error_msg);

  // Returns, perhaps creating it, an appropriate named manager for this config
  // (potentially sharing with others as appropriate).
  NamedLockManager* GetLockManager(SystemRewriteOptions* config);

  // Print out stats appropriate for the given flags combination.
  void PrintCacheStats(StatFlags flags, GoogleString* out);

  // For cases where the thread limit isn't known at construction time, call
  // set_thread_limit() before calling any other methods.
  void set_thread_limit(int thread_limit) { thread_limit_ = thread_limit; }

  // Finds a Cache for the file_cache_path in the config.  If none exists,
  // creates one, using all the other parameters in the SystemRewriteOptions.
  // If multiple calls are made to get a file-cache with the same path, but
  // with different cleaning parameters, the parameters are merged based
  // on these rules:
  //   1. An explicitly configured option is selected over a default without
  //      warning.
  //   2. When there are two explicit settings, the higher size is picked,
  //      but the lower time-interval is picked.  A warning is issued to
  //      the server log, as this situation should be resolved by the server
  //      administrator.
  SystemCachePath* GetCache(SystemRewriteOptions* config);

 private:
  typedef SharedMemCache<64> MetadataShmCache;
  struct MetadataShmCacheInfo {
    MetadataShmCacheInfo()
        : cache_to_use(NULL), cache_backend(NULL), initialized(false) {}

    // Note that the fields may be NULL if e.g. initialization failed.
    CacheInterface* cache_to_use;  // may be CacheStats or such.
    GoogleString segment;
    MetadataShmCache* cache_backend;
    bool initialized;  // This is needed since in some scenarios we may
                       // not end up as far as calling ->Initialize() before
                       // we get shutdown.
  };

  struct ExternalCacheInterfaces {
    ExternalCacheInterfaces() : async(NULL), blocking(NULL) {}
    CacheInterface* async;
    CacheInterface* blocking;
  };

  // Given a blocking cache, prepares a fully functional ExternalCacheInterfaces
  // with both blocking and async versions. Async version is obtained by
  // wrapping blocking cache in AsyncCache with given worker pool.
  // If pool is NULL, this wrapping is omitted, effictively yielding a blocking
  // cache instead of async (this is used for compatibility with
  // MemcachedThreads 0 config option). Async version is then wrapped in
  // CacheBatcher. The value of batcher_max_parallel_lookups will be used to
  // override the batcher's max_parallel_lookups. If you don't want to override
  // it, pass in -1.
  //
  // Each cache is also wrapped in CacheStatistics with given name. All newly
  // created wrappers are owned by SystemCaches.
  ExternalCacheInterfaces ConstructExternalCacheInterfacesFromBlocking(
      CacheInterface* backend, QueuedWorkerPool* pool,
      int batcher_max_parallel_lookups, const char* async_stats_name,
      const char* blocking_stats_name);

  // Constructs external cache interfaces for a configuration. Both blocking
  // and (potentially) non-blocking interfaces are constructed, and given
  // separate stats. The returned interfaces are owned by SystemCaches, and must
  // not be freed by the caller.
  //
  // The corresponding external cache should be enabled in the config.
  ExternalCacheInterfaces NewMemcached(SystemRewriteOptions* config);
  ExternalCacheInterfaces NewRedis(SystemRewriteOptions* config);

  // Either constructs a new external cache (memcached/redis) based on
  // configuration or retrieves it from external_caches_map_ if it was already
  // constructed. Note that not all objects are stored in the map, some may be
  // created on each individual run (see impl for details).
  ExternalCacheInterfaces NewExternalCache(SystemRewriteOptions* config);

  // Returns any shared memory metadata cache configured for the given name, or
  // NULL.
  MetadataShmCacheInfo* LookupShmMetadataCache(const GoogleString& name);

  // Returns the shared metadata cache explicitly configured for this config if
  // it exists, otherwise return the default one, creating it if necessary.
  // Returns NULL if shared memory isn't supported or if the default cache is
  // disabled and this server context didn't explicitly configure its own.
  MetadataShmCacheInfo* GetShmMetadataCacheOrDefault(
      SystemRewriteOptions* config);

  // Establishes common cohorts for the property cache.
  void SetupPcacheCohorts(ServerContext* server_context,
                          bool enable_property_cache);

  scoped_ptr<SlowWorker> slow_worker_;

  RewriteDriverFactory* factory_;
  AbstractSharedMem* shared_mem_runtime_;
  int thread_limit_;
  bool is_root_process_;
  bool was_shut_down_;

  // File-Caches are expensive.  Just allocate one per distinct file-cache path.
  // At the moment there is no consistency checking for other parameters.  Note
  // that the LRUCache is instantiated inside the SystemCachePath, so we get a
  // new LRUCache for each distinct file-cache path.  Also note that only the
  // file-cache path is used as the key in this map.  Other parameters changed,
  // such as lru cache size or file cache clean interval, are taken from the
  // first file-cache found configured to one address.
  //
  // TODO(jmarantz): Consider instantiating one LRUCache per process.
  typedef std::map<GoogleString, SystemCachePath*> PathCacheMap;
  PathCacheMap path_cache_map_;

  // The QueuedWorkerPool for async cache-gets is shared among all memcached
  // connections. We have similar pool for Redis.
  // TODO(yeputons): consider reducing to a single pool. Potential problem: if
  // both memcached and Redis are enabled and one of them goes down, it blocks
  // requests to both servers. Actually, we have that problem already if there
  // different vhosts use different external cache servers.
  scoped_ptr<QueuedWorkerPool> memcached_pool_;
  scoped_ptr<QueuedWorkerPool> redis_pool_;

  // Explicit lists of AprMemCache/RedisCache instances are stored individually,
  // as they require extra treatment during startup and shutdown.
  // TODO(yeputons): consider reducing to a single vector when these classes
  // have common base class. Potential problem: users may want to enable
  // statistics for only memcached or only Redis (see kIncludeMemcached flag).
  std::vector<AprMemCache*> memcache_servers_;
  std::vector<RedisCache*> redis_servers_;

  // As each external cache object typically holds a TCP connection, we do not
  // want to allocate one per vhost (there can be tens of thousands of vhosts).
  // So, we create only one object per each external cache configuration and
  // store them in a map. See GetExternalCache() for details.
  //
  // All ExternalCacheInterfaces pairs stored already include (depending on
  // options) instances of CacheBatcher, AsyncCache and CacheStats.
  typedef std::map<GoogleString, ExternalCacheInterfaces> ExternalCachesMap;
  ExternalCachesMap external_caches_map_;

  // Map of any shared memory metadata caches we have + their CacheStats
  // wrappers. These are named explicitly to make configuration comprehensible.
  typedef std::map<GoogleString, MetadataShmCacheInfo*> MetadataShmCacheMap;

  // Note that entries here may be NULL in cases of config errors.
  MetadataShmCacheMap metadata_shm_caches_;

  MD5Hasher cache_hasher_;

  bool default_shm_metadata_cache_creation_failed_;

  DISALLOW_COPY_AND_ASSIGN(SystemCaches);
};

}  // namespace net_instaweb

#endif  // PAGESPEED_SYSTEM_SYSTEM_CACHES_H_
