/* * Copyright [2014] [Christian Loehnert, krampenschiesser@gmail.com] * 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 de.ks.activity.context; import de.ks.activity.executor.ActivityExecutor; import de.ks.activity.executor.ActivityJavaFXExecutor; import de.ks.eventsystem.bus.EventBus; import org.apache.commons.lang3.tuple.Pair; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.enterprise.context.spi.Context; import javax.enterprise.context.spi.Contextual; import javax.enterprise.context.spi.CreationalContext; import javax.enterprise.inject.spi.Bean; import javax.enterprise.inject.spi.BeanManager; import javax.enterprise.inject.spi.CDI; import java.lang.annotation.Annotation; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.ReentrantReadWriteLock; /** * */ public class ActivityContext implements Context { private static final Logger log = LoggerFactory.getLogger(ActivityContext.class); private static final AtomicBoolean registeredWithEventBus = new AtomicBoolean(); protected final ConcurrentHashMap<String, ActivityHolder> activities = new ConcurrentHashMap<>(); protected volatile String currentActivity = null; private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true); private final BeanManager beanManager; public static void start(String id) { CDI<Object> cdi = CDI.current(); ActivityContext context = (ActivityContext) cdi.getBeanManager().getContext(ActivityScoped.class); context.startActivity(id); if (!registeredWithEventBus.get()) { EventBus eventBus = cdi.select(EventBus.class).get(); ActivityExecutor activityExecutor = cdi.select(ActivityExecutor.class).get(); ActivityJavaFXExecutor fxExecutor = cdi.select(ActivityJavaFXExecutor.class).get(); eventBus.setExecutorService(activityExecutor); eventBus.setExecutorService(fxExecutor); } } public static void stop(String id) { ActivityContext context = (ActivityContext) CDI.current().getBeanManager().getContext(ActivityScoped.class); context.stopActivity(id); } public static void cleanup(String id) { ActivityContext context = (ActivityContext) CDI.current().getBeanManager().getContext(ActivityScoped.class); context.cleanupSingleActivity(id); } public static void stopAll() { ActivityContext context = (ActivityContext) CDI.current().getBeanManager().getContext(ActivityScoped.class); context.cleanupAllActivities(); } public ActivityContext(BeanManager mgr) { beanManager = mgr; } @Override public Class<? extends Annotation> getScope() { return ActivityScoped.class; } @Override public <T> T get(Contextual<T> contextual) { if (contextual instanceof Bean) { Bean bean = (Bean) contextual; Pair<String, Class<?>> key = getKey(bean); lock.readLock().lock(); try { StoredBean storedBean = activities.get(key.getLeft()).getStoredBean(key.getRight()); if (storedBean != null) { return storedBean.getInstance(); } } finally { lock.readLock().unlock(); } } return null; } @SuppressWarnings("unchecked") @Override public <T> T get(Contextual<T> contextual, CreationalContext<T> creationalContext) { if (contextual instanceof Bean) { Bean bean = (Bean) contextual; Pair<String, Class<?>> key = getKey(bean); lock.writeLock().lock(); try { Object o = bean.create(creationalContext); StoredBean storedBean = new StoredBean(bean, creationalContext, o); activities.get(key.getLeft()).put(key.getRight(), storedBean); return (T) o; } finally { lock.writeLock().unlock(); } } return null; } @Override public boolean isActive() { return true; } protected Pair<String, Class<?>> getKey(Bean<?> bean) { Class<?> beanClass = bean.getBeanClass(); Annotation annotation = beanClass.getAnnotation(ActivityScoped.class); if (annotation == null) {//might be a producer method, only warn String msg = "Unable to retrieve " + ActivityScoped.class.getName() + " from " + beanClass; if (bean.getClass().getName().contains("Producer")) { log.trace(msg); } else { log.warn(msg); } } if (currentActivity == null) { throw new IllegalStateException("No activity currently active!"); } return Pair.of(currentActivity, beanClass); } public void cleanupSingleActivity(String id) { lock.writeLock().lock(); try { ActivityHolder activityHolder = activities.remove(id); log.debug("Cleanup activity {}", activityHolder.getId()); Set<Map.Entry<Class<?>, StoredBean>> entries = activityHolder.objectStore.entrySet(); for (Iterator<Map.Entry<Class<?>, StoredBean>> iterator = entries.iterator(); iterator.hasNext(); ) { Map.Entry<Class<?>, StoredBean> next = iterator.next(); next.getValue().destroy(); iterator.remove(); } } finally { lock.writeLock().unlock(); } } public void cleanupSingleBean(Class<?> clazz) { lock.writeLock().lock(); try { ActivityHolder activityHolder = activities.get(getCurrentActivity()); StoredBean storedBean = activityHolder.objectStore.get(clazz); if (storedBean != null) { storedBean.destroy(); activityHolder.objectStore.remove(clazz); log.debug("Cleaned up bean {} of activity {}", clazz.getName(), activityHolder.getId()); } } finally { lock.writeLock().unlock(); } } public ActivityHolder startActivity(String id) { lock.writeLock().lock(); try { currentActivity = id; if (activities.containsKey(id)) { log.debug("Resuming activity {}", id); return activities.get(id); } else { ActivityHolder holder = new ActivityHolder(id); this.activities.put(id, holder); log.debug("Started activity {}", holder.getId()); return holder; } } finally { lock.writeLock().unlock(); } } public void stopActivity(String id) { lock.writeLock().lock(); try { ActivityHolder activityHolder = activities.get(id); if (activityHolder == null) { log.warn("Activity {} is already stopped", id); return; } int count = activityHolder.getCount().decrementAndGet(); if (count == 0) { cleanupSingleActivity(id); currentActivity = null; log.debug("Stopped activity {}", activityHolder.getId()); } else { log.debug("Don't stop activity {} because of {} holders.", activityHolder.getId(), count); } } finally { lock.writeLock().unlock(); } } public void cleanupAllActivities() { log.debug("Cleanup all activities."); for (String id : activities.keySet()) { ActivityHolder activityHolder = activities.get(id); if (activityHolder == null) { log.error("No activity active in thread {}", Thread.currentThread().getName()); throw new IllegalStateException("No activity active in thread " + Thread.currentThread().getName()); } if (multipleThreadsActive(activityHolder)) { log.warn("There are still {} other threads holding a reference to this activity, cleanup not allowed", activityHolder.getCount().get() - 1); } waitForOtherThreads(activityHolder); cleanupSingleActivity(id); } lock.writeLock().lock(); try { this.activities.clear(); } finally { lock.writeLock().unlock(); } } private void waitForOtherThreads(ActivityHolder activityHolder) { while (multipleThreadsActive(activityHolder)) { try { Thread.sleep(100); } catch (InterruptedException e) { log.error("Interrupted", e); } } } private boolean multipleThreadsActive(ActivityHolder activityHolder) { return activityHolder.getCount().get() > 1; } public ActivityHolder getHolder() { if (currentActivity == null) { throw new RuntimeException("No activity active in current thread!"); } return activities.get(currentActivity); } public String getCurrentActivity() { return currentActivity; } public boolean hasCurrentActivity() { return currentActivity != null; } }