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

org.apache.camel.guice.support.GuiceyFruitModule Maven / Gradle / Ivy

There is a newer version: 2.25.4
Show newest version
/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.apache.camel.guice.support;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.inject.AbstractModule;
import com.google.inject.Key;
import com.google.inject.MembersInjector;
import com.google.inject.Provider;
import com.google.inject.ProvisionException;
import com.google.inject.TypeLiteral;
import com.google.inject.binder.LinkedBindingBuilder;
import com.google.inject.matcher.AbstractMatcher;
import com.google.inject.name.Names;
import com.google.inject.spi.InjectionListener;
import com.google.inject.spi.TypeEncounter;
import com.google.inject.spi.TypeListener;

import org.apache.camel.guice.inject.Configures;
import org.apache.camel.guice.support.internal.MethodKey;

import static com.google.inject.matcher.Matchers.any;
import static org.apache.camel.guice.support.EncounterProvider.encounterProvider;


/**
 * Adds some new helper methods to the base Guice module
 * 
 * @version
 */
@SuppressWarnings({ "rawtypes", "unchecked" })
public abstract class GuiceyFruitModule extends AbstractModule {

    protected void configure() {
        // lets find all of the configures methods
        List configureMethods = getConfiguresMethods();
        if (!configureMethods.isEmpty()) {
            final GuiceyFruitModule moduleInstance = this;
            final Class moduleType = getClass();
            TypeLiteral type = TypeLiteral
                    .get(moduleType);

            for (final Method method : configureMethods) {
                int size = method.getParameterTypes().length;
                if (size == 0) {
                    throw new ProvisionException(
                            "No arguments on @Configures method " + method);
                } else if (size > 1) {
                    throw new ProvisionException("Too many arguments " + size
                            + " on @Configures method " + method);
                }
                final Class paramType = getParameterType(type, method, 0);

                bindListener(new AbstractMatcher>() {
                    public boolean matches(TypeLiteral typeLiteral) {
                        return typeLiteral.getRawType().equals(paramType);
                    }
                }, new TypeListener() {
                    public  void hear(TypeLiteral injectableType,
                            TypeEncounter encounter) {
                        encounter.register(new MembersInjector() {
                            public void injectMembers(I injectee) {
                                // lets invoke the configures method
                                try {
                                    method.setAccessible(true);
                                    method.invoke(moduleInstance, injectee);
                                } catch (IllegalAccessException e) {
                                    throw new ProvisionException(
                                            "Failed to invoke @Configures method "
                                                    + method + ". Reason: " + e,
                                            e);
                                } catch (InvocationTargetException ie) {
                                    Throwable e = ie.getTargetException();
                                    throw new ProvisionException(
                                            "Failed to invoke @Configures method "
                                                    + method + ". Reason: " + e,
                                            e);
                                }
                            }
                        });
                    }
                });
            }
        }
    }

    private List getConfiguresMethods() {
        List answer = Lists.newArrayList();
        List list = Reflectors.getAllMethods(getClass());
        for (Method method : list) {
            if (method.getAnnotation(Configures.class) != null) {
                answer.add(method);
            }
        }
        return answer;
    }

    /**
     * Binds a post injection hook method annotated with the given annotation to
     * the given method handler.
     */
    protected  void bindMethodHandler(
            final Class annotationType, final MethodHandler methodHandler) {

        bindMethodHandler(annotationType, encounterProvider(methodHandler));
    }

    /**
     * Binds a post injection hook method annotated with the given annotation to
     * the given method handler.
     */
    protected  void bindMethodHandler(
            final Class annotationType,
            final Key methodHandlerKey) {

        bindMethodHandler(annotationType, encounterProvider(methodHandlerKey));
    }

    /**
     * Binds a post injection hook method annotated with the given annotation to
     * the given method handler.
     */
    protected  void bindMethodHandler(
            final Class annotationType,
            final Class methodHandlerType) {

        bindMethodHandler(annotationType, encounterProvider(methodHandlerType));
    }

    private  void bindMethodHandler(
            final Class annotationType,
            final EncounterProvider encounterProvider) {

        bindListener(any(), new TypeListener() {
            public  void hear(TypeLiteral injectableType,
                    TypeEncounter encounter) {
                Class type = injectableType.getRawType();
                Method[] methods = type.getDeclaredMethods();
                for (final Method method : methods) {
                    final A annotation = method.getAnnotation(annotationType);
                    if (annotation != null) {
                        final Provider provider = encounterProvider
                                .get(encounter);

                        encounter.register(new InjectionListener() {
                            public void afterInjection(I injectee) {

                                MethodHandler methodHandler = provider.get();
                                try {
                                    methodHandler.afterInjection(injectee,
                                            annotation, method);
                                } catch (InvocationTargetException ie) {
                                    Throwable e = ie.getTargetException();
                                    throw new ProvisionException(
                                            e.getMessage(), e);
                                } catch (IllegalAccessException e) {
                                    throw new ProvisionException(
                                            e.getMessage(), e);
                                }
                            }
                        });
                    }
                }
            }
        });
    }

    /**
     * Binds a custom injection point for a given injection annotation to the
     * annotation member provider so that occurrences of the annotation on
     * fields and methods with a single parameter will be injected by Guice
     * after the constructor and @Inject have been processed.
     * 
     * @param annotationType
     *            the annotation class used to define the injection point
     * @param annotationMemberProviderKey
     *            the key of the annotation member provider which can be
     *            instantiated and injected by guice
     * @param 
     *            the annotation type used as the injection point
     */
    protected  void bindAnnotationInjector(
            Class annotationType,
            Key annotationMemberProviderKey) {

        bindAnnotationInjector(annotationType,
                encounterProvider(annotationMemberProviderKey));
    }

    /**
     * Binds a custom injection point for a given injection annotation to the
     * annotation member provider so that occurrences of the annotation on
     * fields and methods with a single parameter will be injected by Guice
     * after the constructor and @Inject have been processed.
     * 
     * @param annotationType
     *            the annotation class used to define the injection point
     * @param annotationMemberProvider
     *            the annotation member provider which can be instantiated and
     *            injected by guice
     * @param 
     *            the annotation type used as the injection point
     */
    protected  void bindAnnotationInjector(
            Class annotationType,
            AnnotationMemberProvider annotationMemberProvider) {

        bindAnnotationInjector(annotationType,
                encounterProvider(annotationMemberProvider));
    }

    /**
     * Binds a custom injection point for a given injection annotation to the
     * annotation member provider so that occurrences of the annotation on
     * fields and methods with a single parameter will be injected by Guice
     * after the constructor and @Inject have been processed.
     * 
     * @param annotationType
     *            the annotation class used to define the injection point
     * @param annotationMemberProviderType
     *            the type of the annotation member provider which can be
     *            instantiated and injected by guice
     * @param 
     *            the annotation type used as the injection point
     */
    protected  void bindAnnotationInjector(
            Class annotationType,
            Class annotationMemberProviderType) {

        bindAnnotationInjector(annotationType,
                encounterProvider(annotationMemberProviderType));
    }

    private  void bindAnnotationInjector(
            final Class annotationType,
            final EncounterProvider memberProviderProvider) {

        bindListener(any(), new TypeListener() {
            Provider providerProvider;

            public  void hear(TypeLiteral injectableType,
                    TypeEncounter encounter) {

                Set boundFields = Sets.newHashSet();
                Map boundMethods = Maps.newHashMap();

                TypeLiteral startType = injectableType;
                while (true) {
                    Class type = startType.getRawType();
                    if (type == Object.class) {
                        break;
                    }

                    Field[] fields = type.getDeclaredFields();
                    for (Field field : fields) {
                        if (boundFields.add(field)) {
                            bindAnnotationInjectorToField(encounter, startType,
                                    field);
                        }
                    }

                    Method[] methods = type.getDeclaredMethods();
                    for (final Method method : methods) {
                        MethodKey key = new MethodKey(method);
                        if (boundMethods.get(key) == null) {
                            boundMethods.put(key, method);
                            bindAnnotationInjectionToMember(encounter,
                                    startType, method);
                        }
                    }

                    Class supertype = type.getSuperclass();
                    if (supertype == Object.class) {
                        break;
                    }
                    startType = startType.getSupertype(supertype);
                }
            }

            protected  void bindAnnotationInjectionToMember(
                    final TypeEncounter encounter,
                    final TypeLiteral type, final Method method) {
                // TODO lets exclude methods with @Inject?
                final A annotation = method.getAnnotation(annotationType);
                if (annotation != null) {
                    if (providerProvider == null) {
                        providerProvider = memberProviderProvider
                                .get(encounter);
                    }

                    encounter.register(new MembersInjector() {
                        public void injectMembers(I injectee) {
                            AnnotationMemberProvider provider = providerProvider
                                    .get();

                            int size = method.getParameterTypes().length;
                            Object[] values = new Object[size];
                            for (int i = 0; i < size; i++) {
                                Class paramType = getParameterType(type,
                                        method, i);
                                Object value = provider.provide(annotation,
                                        type, method, paramType, i);
                                checkInjectedValueType(value, paramType,
                                        encounter);

                                // if we have a null value then assume the
                                // injection point cannot be satisfied
                                // which is the spring @Autowired way of doing
                                // things
                                if (value == null
                                        && !provider.isNullParameterAllowed(
                                                annotation, method, paramType,
                                                i)) {
                                    return;
                                }
                                values[i] = value;
                            }
                            try {
                                method.setAccessible(true);
                                method.invoke(injectee, values);
                            } catch (IllegalAccessException e) {
                                throw new ProvisionException(
                                        "Failed to inject method " + method
                                                + ". Reason: " + e, e);
                            } catch (InvocationTargetException ie) {
                                Throwable e = ie.getTargetException();
                                throw new ProvisionException(
                                        "Failed to inject method " + method
                                                + ". Reason: " + e, e);
                            }
                        }
                    });
                }
            }

            protected  void bindAnnotationInjectorToField(
                    final TypeEncounter encounter,
                    final TypeLiteral type, final Field field) {
                // TODO lets exclude fields with @Inject?
                final A annotation = field.getAnnotation(annotationType);
                if (annotation != null) {
                    if (providerProvider == null) {
                        providerProvider = memberProviderProvider
                                .get(encounter);
                    }

                    encounter.register(new InjectionListener() {
                        public void afterInjection(I injectee) {
                            AnnotationMemberProvider provider = providerProvider
                                    .get();
                            Object value = provider.provide(annotation, type,
                                    field);
                            checkInjectedValueType(value, field.getType(),
                                    encounter);

                            try {
                                field.setAccessible(true);
                                field.set(injectee, value);
                            } catch (IllegalAccessException e) {
                                throw new ProvisionException(
                                        "Failed to inject field " + field
                                                + ". Reason: " + e, e);
                            }
                        }
                    });
                }
            }
        });
    }

    protected Class getParameterType(TypeLiteral type, Method method,
            int i) {
        Class[] parameterTypes = method.getParameterTypes();
        List> list = type.getParameterTypes(method);
        TypeLiteral typeLiteral = list.get(i);

        Class paramType = typeLiteral.getRawType();
        if (paramType == Object.class || paramType.isArray()
                && paramType.getComponentType() == Object.class) {
            // if the TypeLiteral ninja doesn't work, lets fall back to the
            // actual type
            paramType = parameterTypes[i];
        }
        return paramType;
    }

    /*
     * protected void bindCloseHook() { bindListener(any(), new Listener() {
     * public  void hear(InjectableType injectableType, Encounter
     * encounter) { encounter.registerPostInjectListener(new
     * InjectionListener() { public void afterInjection(I injectee) {
     * 
     * } }); } }); }
     */

    /**
     * Returns true if the value to be injected is of the correct type otherwise
     * an error is raised on the encounter and false is returned
     */
    protected  void checkInjectedValueType(Object value, Class type,
            TypeEncounter encounter) {
        // TODO check the type
    }

    /**
     * A helper method to bind the given type with the binding annotation.
     * 
     * This allows you to replace this code
     *  bind(Key.get(MyType.class, SomeAnnotation.class))
     * 
     * 
     * with this  bind(KMyType.class, SomeAnnotation.class) 
     */
    protected  LinkedBindingBuilder bind(Class type,
            Class annotationType) {
        return bind(Key.get(type, annotationType));
    }

    /**
     * A helper method to bind the given type with the binding annotation.
     * 
     * This allows you to replace this code
     *  bind(Key.get(MyType.class, someAnnotation))
     * 
     * 
     * with this  bind(KMyType.class, someAnnotation) 
     */
    protected  LinkedBindingBuilder bind(Class type,
            Annotation annotation) {
        return bind(Key.get(type, annotation));
    }

    /**
     * A helper method to bind the given type with the
     * {@link com.google.inject.name.Named} annotation of the given text value.
     * 
     * This allows you to replace this code
     *  bind(Key.get(MyType.class, Names.named("myName")))
     * 
     * 
     * with this  bind(KMyType.class, "myName") 
     */
    protected  LinkedBindingBuilder bind(Class type, String namedText) {
        return bind(type, Names.named(namedText));
    }

    /**
     * A helper method which binds a named instance to a key defined by the
     * given name and the instances type. So this method is short hand for
     * 
     *  bind(instance.getClass(), name).toInstance(instance); 
     */
    protected  void bindInstance(String name, T instance) {
        // TODO not sure the generics ninja to avoid this cast
        Class aClass = (Class) instance.getClass();
        bind(aClass, name).toInstance(instance);
    }
}