/*
* Copyright (c) 2008-2017 Nelson Carpentier, Jakub Białek
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
package com.google.code.ssm;
import java.io.IOException;
import java.lang.reflect.Proxy;
import java.net.InetSocketAddress;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import lombok.Getter;
import lombok.Setter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.Assert;
import com.google.code.ssm.aop.CacheBase;
import com.google.code.ssm.api.AnnotationConstants;
import com.google.code.ssm.api.format.SerializationType;
import com.google.code.ssm.config.AddressChangeListener;
import com.google.code.ssm.config.AddressChangeNotifier;
import com.google.code.ssm.config.AddressProvider;
import com.google.code.ssm.mapper.JsonObjectMapper;
import com.google.code.ssm.providers.CacheClient;
import com.google.code.ssm.providers.CacheClientFactory;
import com.google.code.ssm.providers.CacheConfiguration;
import com.google.code.ssm.providers.CacheTranscoder;
import com.google.code.ssm.transcoders.JavaTranscoder;
import com.google.code.ssm.transcoders.JsonTranscoder;
/**
* Creates cache using provider factory and connection configuration.
*
* @author Nelson Carpentier
* @author Jakub Białek
*
*/
@Getter
public class CacheFactory implements AddressChangeListener, FactoryBean<Cache>, InitializingBean, DisposableBean {
private static final Logger LOGGER = LoggerFactory.getLogger(CacheFactory.class);
@Setter
private CacheConfiguration configuration = new CacheConfiguration();
@Setter
private AddressProvider addressProvider;
@Setter
private CacheClientFactory cacheClientFactory;
@Setter
private String cacheName = AnnotationConstants.DEFAULT_CACHE_NAME;
@Setter
private Collection<String> cacheAliases = Collections.emptyList();
private Cache cache;
@Setter
private AddressChangeNotifier addressChangeNotifier;
@Setter
private SerializationType defaultSerializationType = SerializationType.PROVIDER;
@Setter
private JsonTranscoder jsonTranscoder;
@Setter
private JavaTranscoder javaTranscoder;
@Setter
private CacheTranscoder customTranscoder;
@Setter
private boolean initializeTranscoders = true;
@Autowired(required = false)
private CacheBase cacheBase;
@Override
public void afterPropertiesSet() throws Exception {
Assert.notNull(configuration, "'configuration' cannot be null");
Assert.notNull(addressProvider, "'addressProvider' is required and cannot be null");
Assert.notNull(cacheClientFactory, "'cacheClientFactory' is required and cannot be null");
Assert.notNull(cacheName, "'cacheName' cannot be null");
Assert.notNull(defaultSerializationType, "'defaultSerializationType' cannot be null");
if (initializeTranscoders) {
if (jsonTranscoder == null) {
jsonTranscoder = new JsonTranscoder(new JsonObjectMapper());
}
if (javaTranscoder == null) {
javaTranscoder = new JavaTranscoder();
}
}
validateTranscoder(SerializationType.JSON, jsonTranscoder, "jsonTranscoder");
validateTranscoder(SerializationType.JAVA, javaTranscoder, "javaTranscoder");
validateTranscoder(SerializationType.CUSTOM, customTranscoder, "customTranscoder");
if (addressChangeNotifier != null) {
addressChangeNotifier.setAddressChangeListener(this);
}
}
@Override
public Cache getObject() throws Exception {
return (cache != null) ? cache : createCache();
}
@Override
public Class<?> getObjectType() {
return Cache.class;
}
@Override
public boolean isSingleton() {
return true;
}
@Override
public void destroy() throws Exception {
if (cache != null) {
LOGGER.info("Shutdowning cache {}", cacheName);
cache.shutdown();
}
}
@Override
public void changeAddresses(final List<InetSocketAddress> addresses) {
if (isCacheDisabled()) {
LOGGER.warn("Cache {} is disabled", cacheName);
return;
}
if (!(cache instanceof CacheImpl)) {
LOGGER.warn("This client doesn't support changing memcached addresses on the fly");
return;
}
try {
LOGGER.info("Creating new memcached client for cache {} with new addresses: {}", cacheName, addresses);
CacheClient memcacheClient = createClient(addresses);
LOGGER.info("New memcached client for cache {} was created with addresses: {}", cacheName, addresses);
((CacheImpl)cache).changeCacheClient(memcacheClient);
} catch (IOException e) {
if (LOGGER.isErrorEnabled()) {
LOGGER.error(String.format("Cannot change memcached client to new one with addresses %s", addresses), e);
}
}
}
/**
* Only one cache is created.
*
* @return cache
* @throws IOException
*/
protected Cache createCache() throws IOException {
// this factory creates only one single cache and return it if someone invoked this method twice or
// more
if (cache != null) {
throw new IllegalStateException(String.format("This factory has already created memcached client for cache %s", cacheName));
}
if (isCacheDisabled()) {
LOGGER.warn("Cache {} is disabled", cacheName);
cache = (Cache) Proxy.newProxyInstance(Cache.class.getClassLoader(), new Class[] { Cache.class },
new DisabledCacheInvocationHandler(cacheName, cacheAliases));
return cache;
}
if (configuration == null) {
throw new RuntimeException(String.format("The MemcachedConnectionBean for cache %s must be defined!", cacheName));
}
List<InetSocketAddress> addrs = addressProvider.getAddresses();
cache = new CacheImpl(cacheName, cacheAliases, createClient(addrs), defaultSerializationType, jsonTranscoder, javaTranscoder,
customTranscoder, new CacheProperties(configuration.isUseNameAsKeyPrefix(), configuration.getKeyPrefixSeparator()));
return cache;
}
boolean isCacheDisabled() {
return cacheBase != null && cacheBase.isCacheDisabled();
}
private CacheClient createClient(final List<InetSocketAddress> addrs) throws IOException {
if (addrs == null || addrs.isEmpty()) {
throw new IllegalArgumentException(String.format("No memcached addresses specified for cache %s", cacheName));
}
return cacheClientFactory.create(addrs, configuration);
}
private void validateTranscoder(final SerializationType serializationType, final CacheTranscoder cacheTranscoder,
final String transcoderName) {
if (defaultSerializationType == serializationType) {
Assert.notNull(cacheTranscoder,
String.format("'%s' cannot be null if default serialization type is set to %s", transcoderName, serializationType));
}
}
}