/** * 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.memcached; import static java.util.Objects.requireNonNull; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; import java.util.function.Consumer; import org.jooby.Env; import org.jooby.Jooby; import org.jooby.Session; import org.jooby.internal.memcached.MemcachedClientProvider; import com.google.inject.Binder; import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; import net.spy.memcached.AddrUtil; import net.spy.memcached.ConnectionFactoryBuilder; import net.spy.memcached.ConnectionFactoryBuilder.Locator; import net.spy.memcached.ConnectionFactoryBuilder.Protocol; import net.spy.memcached.FailureMode; import net.spy.memcached.MemcachedClient; import net.spy.memcached.compat.log.SLF4JLogger; import net.spy.memcached.metrics.MetricType; /** * <h1>memcached module</h1> * <p> * Provides memcached access via <a * href="https://github.com/dustin/java-memcached-client">SpyMemcached</a> * </p> * * <h2>exposes</h2> * <ul> * <li>A {@link MemcachedClient} service</li> * </ul> * * <h2>usage</h2> * * <pre> * memcached.server = "localhost:11211" * </pre> * * <pre> * { * use(new SpyMemcached()); * * get("/", req {@literal ->} { * MemcachedClient client = req.require(MemcachedClient.class); * client.set("foo", 60, "bar"); * return client.get("foo"); * }); * } * </pre> * * <h2>configuration</h2> * <p> * It is done via <code>.conf</code> file: * </p> * * <pre> * memcached.protocol = binary * </pre> * * <p> * or programmatically: * </p> * * <pre> * { * use(new SpyMemcached() * .doWith(builder {@literal ->} { * builder.setProtocol(Protocol.BINARY); * }) * ); * } * </pre> * * <p> * This module comes with a {@link Session.Store} too. See {@link SpySessionStore}. * </p> * * @author edgar * @since 0.7.0 */ public class SpyMemcached implements Jooby.Module { static { System.setProperty("net.spy.log.LoggerImpl", SLF4JLogger.class.getName()); } private BiConsumer<ConnectionFactoryBuilder, Config> configurer; /** * Creates a new {@link SpyMemcached} module. */ public SpyMemcached() { } @SuppressWarnings("unchecked") @Override public void configure(final Env env, final Config conf, final Binder binder) { Config $memcached = conf.getConfig("memcached") .withFallback(conf.getConfig("memcached")); ConnectionFactoryBuilder builder = newConnectionFactoryBuilder($memcached); if (configurer != null) { configurer.accept(builder, conf); } List<String> servers = new ArrayList<>(); Object $servers = conf.getAnyRef("memcached.server"); if ($servers instanceof List) { servers.addAll((Collection<? extends String>) $servers); } else { servers.add($servers.toString()); } MemcachedClientProvider provider = new MemcachedClientProvider( builder, AddrUtil.getAddresses(servers), $memcached.getDuration("shutdownTimeout", TimeUnit.MILLISECONDS)); env.onStop(provider::destroy); binder .bind(MemcachedClient.class) .toProvider(provider) .asEagerSingleton(); } /** * Configure a {@link ConnectionFactoryBuilder} programmatically. * * @param configurer A configure callback. * @return This module. */ public SpyMemcached doWith(final BiConsumer<ConnectionFactoryBuilder, Config> configurer) { this.configurer = requireNonNull(configurer, "Configurer callback is required."); return this; } /** * Configure a {@link ConnectionFactoryBuilder} programmatically. * * @param configurer A configure callback. * @return This module. */ public SpyMemcached doWith(final Consumer<ConnectionFactoryBuilder> configurer) { requireNonNull(configurer, "Configurer callback is required."); return doWith((b, c) -> configurer.accept(b)); } @Override public Config config() { return ConfigFactory.parseResources(getClass(), "memcached.conf"); } private ConnectionFactoryBuilder newConnectionFactoryBuilder(final Config conf) { ConnectionFactoryBuilder builder = new ConnectionFactoryBuilder(); ifset(conf, "authWaitTime", path -> builder .setAuthWaitTime(conf.getDuration(path, TimeUnit.MILLISECONDS))); ifset(conf, "daemon", path -> builder.setDaemon(conf.getBoolean(path))); ifset(conf, "enableMetrics", path -> builder .setEnableMetrics(enumFor(conf.getString(path), MetricType.values()))); ifset(conf, "failureMode", path -> builder .setFailureMode(enumFor(conf.getString(path), FailureMode.values()))); ifset(conf, "locator", path -> builder .setLocatorType(enumFor(conf.getString(path), Locator.values()))); ifset(conf, "maxReconnectDelay", path -> builder .setMaxReconnectDelay(conf.getDuration(path, TimeUnit.SECONDS))); ifset(conf, "opQueueMaxBlockTime", path -> builder .setOpQueueMaxBlockTime(conf.getDuration(path, TimeUnit.MILLISECONDS))); ifset(conf, "opTimeout", path -> builder .setOpTimeout(conf.getDuration(path, TimeUnit.MILLISECONDS))); ifset(conf, "protocol", path -> builder .setProtocol(enumFor(conf.getString(path), Protocol.values()))); ifset(conf, "readBufferSize", path -> builder .setReadBufferSize(conf.getInt(path))); ifset(conf, "shouldOptimize", path -> builder .setShouldOptimize(conf.getBoolean(path))); ifset(conf, "timeoutExceptionThreshold", path -> builder .setTimeoutExceptionThreshold(conf.getInt(path))); ifset(conf, "useNagleAlgorithm", path -> builder .setUseNagleAlgorithm(conf.getBoolean(path))); return builder; } private void ifset(final Config conf, final String path, final Consumer<String> setter) { if (conf.hasPath(path)) { setter.accept(path); } } private <E extends Enum<E>> E enumFor(final String value, final E[] values) { for (E it : values) { if (it.name().equalsIgnoreCase(value)) { return it; } } throw new IllegalArgumentException("Invalid value: " + value); } }