/* * Copyright 2014 Avanza Bank AB * * Licensed 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 com.avanza.astrix.config; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Manages the life-cycle of each object created by Astrix. Each object created will be cached. * * When the object is created (upon cache-miss) the object will be created using the ObjectFactory * and then the object will be initialized by invoking all @PostConstruct annotated methods. * * When the ObjectCache is destroyed all objects in the cache will be destroyed by invoking * all @PreDestroy annotated methods on every instance in the cache. * * @author Elias Lindholm (elilin) * */ final class ObjectCache { private static final Logger logger = LoggerFactory.getLogger(ObjectCache.class); private final ConcurrentMap<Object, Object> instanceById = new ConcurrentHashMap<>(); private final ConcurrentMap<Object, Lock> lockedObjects = new ConcurrentHashMap<>(); @SuppressWarnings("unchecked") public <T> T getInstance(Object objectId, ObjectFactory<T> factory) { T object = (T) this.instanceById.get(objectId); if (object != null) { return object; } return create(objectId, factory); } public void destroy() { for (Object object : this.instanceById.values()) { destroy(object); } } public static void destroy(Object object) { List<Method> methods = getMethodsAnnotatedWith(object.getClass(), PreDestroy.class); for (Method m : methods) { try { m.invoke(object); } catch (Exception e) { logger.error(String.format("Failed to invoke destroy method. methodName=%s objectType=%s", m.getName(), object.getClass().getName()), e); } } } public static void init(Object object) { List<Method> methods = getMethodsAnnotatedWith(object.getClass(), PostConstruct.class); for (Method m : methods) { try { m.invoke(object); } catch (Exception e) { logger.error(String.format("Failed to invoke init method. methodName=%s objectType=%s", m.getName(), object.getClass().getName()), e); } } } @SuppressWarnings("unchecked") private <T> T create(Object id, ObjectFactory<T> objectFactory) { Lock lock = new ReentrantLock(); Lock existingLock = lockedObjects.putIfAbsent(id, lock); if (existingLock != null) { // Another thread is already crating object lock = existingLock; } lock.lock(); try { T object = (T) this.instanceById.get(id); if (object != null) { // Another thread created instance return object; } // Create instance T instance; try { instance = objectFactory.create(); init(instance); this.instanceById.put(id, instance); return instance; } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new RuntimeException("Failed to create instance of: " + id); } } finally { lock.unlock(); /* * If object was created on current thread, then remove the lock. * If another thread created the object, this will have no effect. */ lockedObjects.remove(id); } } public <T> T create(Object id, final T instance) { return create(id, new ObjectFactory<T>() { @Override public T create() throws Exception { return instance; } }); } public interface ObjectFactory<T> { T create() throws Exception; } private static List<Method> getMethodsAnnotatedWith(final Class<?> type, final Class<? extends Annotation> annotation) { final List<Method> methods = new ArrayList<Method>(); for (Method method : type.getMethods()) { if (method.isAnnotationPresent(annotation)) { methods.add(method); } } return methods; } }