/*
* Copyright 2014 The Sculptor Project Team, including the original
* author or authors.
*
* 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 org.sculptor.framework.event.annotation;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.sculptor.framework.event.EventBus;
import org.sculptor.framework.event.EventSubscriber;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanNotOfRequiredTypeException;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.StringUtils;
/**
* For all subscriber beans (implementations of the {@link EventSubscriber}
* interface marked with the {@link Subscribe} annotation) the specified topic
* is subscribed from the selected {@link EventBus}.
*/
public class SubscribeBeanPostProcessor implements DestructionAwareBeanPostProcessor, Ordered, BeanFactoryAware {
private static final Logger LOG = LoggerFactory.getLogger(SubscribeBeanPostProcessor.class);
private ListableBeanFactory beanFactory;
private int order = Ordered.LOWEST_PRECEDENCE - 1;
private final Map<String, Subscribe> subscribers = new ConcurrentHashMap<>(64);
/**
* Creates a map of subscriber bean names with the corresponding
* {@link Subscribe} annotations which is retrived from the <b>unproxied</b>
* bean.
*/
@Override
public Object postProcessBeforeInitialization(final Object bean, final String beanName) throws BeansException {
if (bean instanceof EventSubscriber) {
Subscribe annotation = getAnnotation(bean.getClass(), beanName);
if (annotation != null) {
subscribers.put(beanName, annotation);
}
}
return bean;
}
/**
* Subscribes the <b>proxied</b> subscriber bean to the corresponding
* {@link EventBus}.
*/
@Override
public Object postProcessAfterInitialization(final Object bean, final String beanName) throws BeansException {
if (subscribers.containsKey(beanName)) {
Subscribe annotation = subscribers.get(beanName);
EventBus eventBus = getEventBus(annotation.eventBus(), beanName);
LOG.debug("Subscribing the event listener '{}' to event bus '{}' with topic '{}", beanName,
annotation.eventBus(), annotation.topic());
eventBus.subscribe(annotation.topic(), (EventSubscriber) bean);
}
return bean;
}
/**
* Unsubscribes the subscriber bean from the corresponding {@link EventBus}.
*/
@Override
public void postProcessBeforeDestruction(final Object bean, final String beanName) throws BeansException {
if (subscribers.containsKey(beanName)) {
Subscribe annotation = getAnnotation(bean.getClass(), beanName);
LOG.debug("Unsubscribing the event listener '{}' from event bus '{}' with topic '{}", beanName,
annotation.eventBus(), annotation.topic());
try {
EventBus eventBus = getEventBus(annotation.eventBus(), beanName);
eventBus.unsubscribe(annotation.topic(), (EventSubscriber) bean);
} catch (Exception e) {
LOG.error("Unsubscribing the event listener '{}' failed", beanName, e);
} finally {
subscribers.remove(beanName);
}
}
}
protected Subscribe getAnnotation(Class<?> beanClass, String beanName) {
Subscribe annotation = AnnotationUtils.findAnnotation(beanClass, Subscribe.class);
if (annotation != null) {
if (StringUtils.isEmpty(annotation.topic())) {
throw new RuntimeException("No topic specified in event subscriber '" + beanName + "'");
}
if (StringUtils.isEmpty(annotation.eventBus())) {
throw new RuntimeException("No event bus specified in event subscriber '" + beanName + "'");
}
}
return annotation;
}
protected EventBus getEventBus(String eventBusName, String subscriberName) {
try {
return beanFactory.getBean(eventBusName, EventBus.class);
} catch (NoSuchBeanDefinitionException e) {
throw new RuntimeException("Event bus '" + eventBusName + "' specified in event subscriber '"
+ subscriberName + "' is not available");
} catch (BeanNotOfRequiredTypeException e) {
throw new RuntimeException("Event bus '" + eventBusName + "' specified in event subscriber '"
+ subscriberName + "' is not of type '" + EventBus.class + "''");
}
}
@Override
public int getOrder() {
return order;
}
/**
* Processing order of this BeanPostProcessor, use last. Default is
* <code>Ordered.LOWEST_PRECEDENCE - 1</code>.
*/
public void setOrder(int order) {
this.order = order;
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
if (!(beanFactory instanceof ListableBeanFactory)) {
throw new IllegalArgumentException("Expected instance of 'ListableBeanFactory' but got '"
+ beanFactory.getClass() + "'");
}
this.beanFactory = (ListableBeanFactory) beanFactory;
}
}