/** * 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. */ package org.jooby.ehcache; import static java.util.Objects.requireNonNull; import java.util.Map.Entry; import java.util.function.BiConsumer; import java.util.function.Consumer; import org.jooby.Env; import org.jooby.Jooby; import org.jooby.internal.ehcache.CacheConfigurationBuilder; import org.jooby.internal.ehcache.ConfigurationBuilder; import com.google.inject.Binder; import com.google.inject.name.Names; import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; import com.typesafe.config.ConfigObject; import com.typesafe.config.ConfigValue; import net.sf.ehcache.CacheManager; import net.sf.ehcache.Ehcache; import net.sf.ehcache.config.Configuration; /** * <h1>ehcache module</h1> * <p> * Provides advanced cache features via <a href="http://ehcache.org">Ehcache</a> * </p> * * <h2>exposes</h2> * <ul> * <li>A {@link CacheManager}</li> * <li>Direct access to {@link Ehcache} instances</li> * </ul> * * <h2>usage</h2> * * <pre> * { * use(new Eh()); * * get("/", req {@literal ->} { * CacheManager cm = req.require(CacheManager.class); * // work with cm * * Ehcache ehcache = req.require(Ehcache.class); * // work with ehcache * }); * } * </pre> * <p> * <a href="http://ehcache.org">Ehcache</a> can be fully configured from your <code>.conf</code> * file and/or programmatically but for the time being there is no support for <code>xml</code>. * </p> * * <h2>caches</h2> * <p> * Caches are configured via <code>.conf</code> like almost everything in {@link Jooby}. * </p> * * <pre> * ehcache.cache.mycache { * eternal = true * } * </pre> * * Later, we can access to <code>mycache</code> with: * * <pre> * { * get("/", req {@literal ->} { * Ehcache mycache = req.require(Ehcache.class); * }); * } * </pre> * * <h3>multiple caches</h3> * <p> * Multiple caches are also possible: * </p> * * <pre> * ehcache.cache.cache1 { * maxEntriesLocalHeap = 100 * eternal = true * } * * ehcache.cache.cache2 { * maxEntriesLocalHeap = 100 * eternal = true * } * </pre> * * Later, we can access to our caches with: * * <pre> * { * get("/", req {@literal ->} { * Ehcache cache1 = req.require("cache1", Ehcache.class); * // .. * Ehcache cache2 = req.require("cache2", Ehcache.class); * // .. * }); * } * </pre> * * <h3>cache inheritance</h3> * <p> * Previous examples, show how to configure two or more caches, but it is also possible to inherit * cache configuration using the <code>default</code> cache: * </p> * * <pre> * * ehcache.cache.default { * maxEntriesLocalHeap = 100 * eternal = true * } * * ehcache.cache.cache1 { * eternal = false * } * * ehcache.cache.cache2 { * maxEntriesLocalHeap = 1000 * } * </pre> * * <p> * Here <code>cache1</code> and <code>cache2</code> will inherited their properties from the * <code>default</code> cache. * * Please note the <code>default</code> cache works as a template and isn't a real/usable cache. * </p> * * <h2>session store</h2> * <p> * This module provides an {@link EhSessionStore}. In order to use the {@link EhSessionStore} all * you have to do is define a <code>session</code> cache: * </p> * * <pre> * ehcache.cache.session { * # cache will expire after 30 minutes of inactivity * timeToIdle = 30m * } * </pre> * * And then register the {@link EhSessionStore}: * * <pre> * { * session(EhSessionStore.class); * } * </pre> * * <h2>configuration</h2> * <p> * Configuration is done in one of two ways: 1) via <code>.conf</code>; or 2) * <code>programmatically:</code>. * </p> * * <h3>via .conf file</h3> * * <pre> * * ehcache { * * defaultTransactionTimeout = 1m * * dynamicConfig = true * * maxBytesLocalDisk = 1k * * maxBytesLocalHeap = 1k * * maxBytesLocalOffHeap = 1m * * monitor = off * * # just one event listener * cacheManagerEventListenerFactory { * class = MyCacheEventListenerFactory * p1 = "v1" * p2 = true * } * * # or multiple event listeners * cacheManagerEventListenerFactory { * listener1 { * class = MyCacheEventListenerFactory1 * p1 = "v1" * p2 = true * } * listener2 { * class = MyCacheEventListenerFactory2 * } * } * * diskStore.path = ${application.tmpdir}${file.separator}ehcache * * # etc... * } * * </pre> * * <h3>programmatically</h3> * * <pre> * { * use(new Eh().doWith(conf {@literal ->} { * conf.setDefaultTransactionTimeoutInSeconds(120); * // etc... * })); * } * </pre> * * @author edgar * @since 0.6.0 */ public class Eh implements Jooby.Module { /** * Configure callback. */ private BiConsumer<Configuration, Config> configurer; @Override public void configure(final Env env, final Config config, final Binder binder) { Config ehcache = config.getConfig("ehcache"); Config caches = ehcache.getConfig("cache"); Config defcache = caches.getConfig("default"); Configuration ehconfig = new ConfigurationBuilder().build(ehcache); ehconfig.setDefaultCacheConfiguration(new CacheConfigurationBuilder("default").build(defcache)); Config userCaches = caches.withoutPath("default"); for (Entry<String, ConfigValue> userCache : userCaches.root().entrySet()) { ConfigValue value = userCache.getValue(); String cname = userCache.getKey(); Config ccache = ConfigFactory.empty(); if (value instanceof ConfigObject) { ccache = ((ConfigObject) value).toConfig(); } ehconfig.addCache(new CacheConfigurationBuilder(cname).build(ccache.withFallback(defcache))); } if (configurer != null) { configurer.accept(ehconfig, config); } CacheManager cm = CacheManager.newInstance(ehconfig); binder.bind(CacheManager.class).toInstance(cm); env.onStop(cm::shutdown); String[] names = cm.getCacheNames(); if (names.length == 1) { // just one cache, bind it without name binder.bind(Ehcache.class).toInstance(cm.getEhcache(names[0])); } // now bind each cache using it's name for (String name : cm.getCacheNames()) { binder.bind(Ehcache.class).annotatedWith(Names.named(name)).toInstance(cm.getEhcache(name)); } } /** * Configure callback to manipulate a {@link Configuration} programmatically. * * @param configurer A configure callback. * @return This {@link Eh} cache. */ public Eh doWith(final BiConsumer<Configuration, Config> configurer) { this.configurer = requireNonNull(configurer, "Configurer callback is required."); return this; } /** * Configure callback to manipulate a {@link Configuration} programmatically. * * @param configurer A configure callback. * @return This {@link Eh} cache. */ public Eh doWith(final Consumer<Configuration> configurer) { requireNonNull(configurer, "Configurer callback is required."); return doWith((ehconf, conf) -> configurer.accept(ehconf)); } @Override public Config config() { return ConfigFactory.parseResources(getClass(), "ehcache.conf"); } }