package com.ryantenney.zookeeper.spring; import static org.springframework.util.ReflectionUtils.*; import java.lang.ref.WeakReference; import java.lang.reflect.Field; import java.lang.reflect.Method; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.api.GetDataBuilder; import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.Watcher; import org.apache.zookeeper.KeeperException.ConnectionLossException; import org.apache.zookeeper.KeeperException.NoNodeException; import org.apache.zookeeper.Watcher.Event.EventType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.aop.support.AopUtils; import org.springframework.beans.BeansException; import org.springframework.beans.SimpleTypeConverter; import org.springframework.beans.TypeConverter; import org.springframework.beans.TypeMismatchException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.core.MethodParameter; import org.springframework.core.Ordered; import org.springframework.core.PriorityOrdered; import org.springframework.util.ReflectionUtils.FieldCallback; import org.springframework.util.ReflectionUtils.MethodCallback; public class ZooKeeperAnnotationBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware, PriorityOrdered { private static final Logger LOG = LoggerFactory.getLogger(ZooKeeperAnnotationBeanPostProcessor.class); private static final FieldFilter FIELD_FILTER = new FieldFilter() { @Override public boolean matches(Field field) { return COPYABLE_FIELDS.matches(field) && field.isAnnotationPresent(ZooKeeper.class); } }; private static final MethodFilter METHOD_FILTER = new MethodFilter() { @Override public boolean matches(Method method) { return USER_DECLARED_METHODS.matches(method) && method.isAnnotationPresent(ZooKeeper.class); } }; private int order = Ordered.LOWEST_PRECEDENCE - 2; private CuratorFramework curatorFramework; private TypeConverter typeConverter; public void setCuratorFramework(CuratorFramework curator) { this.curatorFramework = curator; } @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { if (beanFactory instanceof ConfigurableBeanFactory) { typeConverter = ((ConfigurableBeanFactory) beanFactory).getTypeConverter(); } if (typeConverter == null) { typeConverter = new SimpleTypeConverter(); } } @Override public Object postProcessBeforeInitialization(final Object bean, String beanName) throws BeansException { Class<?> targetClass = AopUtils.getTargetClass(bean); doWithFields(targetClass, new FieldCallback() { @Override public void doWith(final Field field) throws IllegalArgumentException, IllegalAccessException { makeAccessible(field); String path = field.getAnnotation(ZooKeeper.class).value(); final Class<?> requiredType = field.getType(); lookup(path, new Setter(bean) { public void setValue(Object value) { setField(field, getTarget(), convert(value, requiredType, null)); } }); } }, FIELD_FILTER); doWithMethods(targetClass, new MethodCallback() { @Override public void doWith(final Method method) throws IllegalArgumentException, IllegalAccessException { makeAccessible(method); String path = method.getAnnotation(ZooKeeper.class).value(); final MethodParameter param = MethodParameter.forMethodOrConstructor(method, 0); final Class<?> requiredType = param.getParameterType(); lookup(path, new Setter(bean) { public void setValue(Object value) { invokeMethod(method, getTarget(), convert(value, requiredType, param)); } }); } }, METHOD_FILTER); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { return bean; } protected void lookup(final String path, final Setter setter) { lookup(path, setter, new Watcher() { @Override public void process(WatchedEvent event) { // check that path and event.getPath match? LOG.info("Watcher for '{}' received watched event: {}", path, event); if (event.getType() == EventType.NodeDataChanged) { lookup(path, setter, this); } } }); } protected void lookup(String path, Setter setter, Watcher watcher) { try { GetDataBuilder getDataBuilder = curatorFramework.getData(); if (setter.isValid()) { getDataBuilder.usingWatcher(watcher); } byte[] data = getDataBuilder.forPath(path); setter.setValue(new String(data, "UTF-8")); } catch (NoNodeException ex) { // TODO } catch (ConnectionLossException ex) { // TODO } catch (Exception ex) { rethrowRuntimeException(ex); } } protected Object convert(Object value, Class<?> requiredType, MethodParameter param) { if (typeConverter != null) { return typeConverter.convertIfNecessary(value, requiredType, param); } else { if (requiredType.isInstance(value)) { return requiredType.cast(value); } else { throw new TypeMismatchException(value, requiredType); } } } @Override public int getOrder() { return order; } public void setOrder(final int order) { this.order = order; } private static abstract class Setter { protected final WeakReference<Object> target; public Setter(final Object target) { this.target = new WeakReference<Object>(target); } public boolean isValid() { return this.target.get() != null; } public Object getTarget() { return target.get(); } public abstract void setValue(Object value); } }