All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.nmdp.service.common.domain.ConfigurationModule Maven / Gradle / Ivy

There is a newer version: 2
Show newest version
/*

    service-common  Common libraries and utilities for services modules.
    Copyright (c) 2014-2015 National Marrow Donor Program (NMDP)
    
    This library is free software; you can redistribute it and/or modify it
    under the terms of the GNU Lesser General Public License as published
    by the Free Software Foundation; either version 3 of the License, or (at
    your option) any later version.
    
    This library is distributed in the hope that it will be useful, but WITHOUT
    ANY WARRANTY; with out even the implied warranty of MERCHANTABILITY or
    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
    License for more details.
    
    You should have received a copy of the GNU Lesser General Public License
    along with this library;  if not, write to the Free Software Foundation,
    Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA.
    
    > http://www.gnu.org/licenses/lgpl.html

*/

package org.nmdp.service.common.domain;

import static com.google.common.base.Preconditions.checkNotNull;

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Stack;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Function;
import com.google.common.collect.Collections2;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.Sets;
import com.google.common.collect.Sets.SetView;
import com.google.inject.AbstractModule;
import com.google.inject.BindingAnnotation;

/**
 * Configuration module.
 */
public class ConfigurationModule extends AbstractModule {

	final List config;
	final Set> annos;
	Logger log = LoggerFactory.getLogger(getClass());
	Stack seen = new Stack();

    /**
     * Create a new configuration module with the specified configuration objects.
     *
     * @param config variable number of configuration objects, must not be null
     */
	public ConfigurationModule(Object... config) {
            checkNotNull(config);
		this.config = Collections.unmodifiableList(Arrays.asList(config));
		this.annos = Collections.unmodifiableSet(findConfigAnnotations(config));
	}

	@Override
	protected void configure() {
		for (Object o : config) {
			try {
				bindObjectConfig(o);
			} catch (Exception e) {
				binder().addError(e);
			}
		}
	}
	
	private Set> findConfigAnnotations(Object... config) {
		final Set> annos = new HashSet>();
		for (Object o : config) {
			if (o instanceof Class) {
				Class clazz = (Class)o;
				if (clazz.isAnnotation() && clazz.getAnnotation(BindingAnnotation.class) != null) {
					annos.add((Class) clazz);
				}
				annos.addAll(findConfigAnnotations(clazz.getDeclaredClasses()));
			}
		}
		return annos;
	}
	
	
	private static final Set> stopClasses = new HashSet<>(Arrays.asList(
			Boolean.class, Short.class, Integer.class, Long.class, 
			Float.class, Double.class, String.class, Class.class));

	private void bindObjectConfig(Object value, Annotation... bindAnnos) throws Exception {
		if (null == value || value instanceof Class || seen.contains(value)) return;
		seen.push(value);
		log.trace("stack size: " + seen.size());
		try {
			Class clazz = value.getClass();
			if (null != bindAnnos && bindAnnos.length > 0) {
				Set> bindAnnoClasses = FluentIterable.from(Arrays.asList(bindAnnos))
						.transform(new Function>() {
							@Override public Class apply(Annotation anno) {
								return anno.annotationType();
							}})
						.toSet();
				SetView> matchedAnnos = Sets.intersection(annos, Sets.newHashSet(bindAnnoClasses));
				for (Class anno : matchedAnnos) {
					log.info("binding property " + anno.getSimpleName() + ": " +  value);
					bind(clazz).annotatedWith(anno).toInstance(clazz.cast(value));
				}
			}
			if (stopClasses.contains(value.getClass())) return;
			bindBeanProperties(value);
			bindJavaFields(value);
		} finally {
			seen.pop();
		}
	}
	
	private void bindBeanProperties(Object o) throws Exception {
		// bind config in bean properties
		BeanInfo cbi = Introspector.getBeanInfo(o.getClass(), Object.class);
		PropertyDescriptor[] pda = cbi.getPropertyDescriptors();
		if (null != pda) {
			for (PropertyDescriptor pd : pda) {
				Class pt = pd.getPropertyType();
				Method pr = pd.getReadMethod(); 
				if (null == pr) continue;
				Object pv = pr.invoke(o, null);
				if (null != pv) {
					Annotation[] pas = pr.getAnnotations();
					log.trace("scanning bean property: " + pd.getName() + " (" + pd.getPropertyType() + ")");
					bindObjectConfig(pv, pas);
				}
			}
		}
	}

	private void bindJavaFields(Object o) throws Exception {
		Field[] pfa = o.getClass().getDeclaredFields(); // no inherited types considered
		for (Field pf : pfa) {
			Object pv = null;
			try { pv = pf.get(o); } catch (Exception e) {}
			if (null != pv) {
				Annotation[] pas = pf.getAnnotations();
				log.trace("scanning java field: " + pf.getName() + " (" + pf.getType() + ")");
				bindObjectConfig(pv, pas);
			}
		}
	}
	
	private Class[] annoToClass(Annotation... anno) {
		Class[] annoClasses = new Class[anno.length]; 
		int i = 0;
		for (Annotation a : anno) {
			annoClasses[i++] = a.annotationType();
		}
		return annoClasses;
	}
	
}