org.compass.spring.support.CompassContextBeanPostProcessor.java Source code

Java tutorial

Introduction

Here is the source code for org.compass.spring.support.CompassContextBeanPostProcessor.java

Source

/*
 * Copyright 2004-2009 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.compass.spring.support;

import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.compass.core.Compass;
import org.compass.core.CompassContext;
import org.compass.core.CompassSession;
import org.compass.core.spi.InternalCompass;
import org.compass.core.spi.InternalCompassSession;
import org.compass.core.support.session.CompassSessionTransactionalProxy;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.util.ReflectionUtils;

/**
 * BeanPostProcessor that processes {@link org.compass.core.CompassContext}
 * annotation for injection of Compass interfaces. Any such annotated fields
 * or methods in any Spring-managed object will automatically be injected.
 * <p/>
 * Will inject either a {@link org.compass.core.Compass} or {@link org.compass.core.CompassSession} instances.
 *
 * @author kimchy
 */
public class CompassContextBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter
        implements ApplicationContextAware {

    protected final Log logger = LogFactory.getLog(getClass());

    private ApplicationContext applicationContext;

    private Map<Class<?>, List<AnnotatedMember>> classMetadata = new HashMap<Class<?>, List<AnnotatedMember>>();

    private Map<String, Compass> compassesByName;

    private Compass uniqueCompass;

    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    /**
     * Lazily initialize compass map.
     */
    private synchronized void initMapsIfNecessary() {
        if (this.compassesByName == null) {
            this.compassesByName = new HashMap<String, Compass>();
            // Look for named Compasses
            String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext,
                    Compass.class);
            for (String emfName : beanNames) {
                Compass compass = (Compass) this.applicationContext.getBean(emfName);
                compassesByName.put(((InternalCompass) compass).getName(), compass);
            }

            if (this.compassesByName.isEmpty()) {
                if (beanNames.length == 1) {
                    this.uniqueCompass = (Compass) this.applicationContext.getBean(beanNames[0]);
                }
            } else if (this.compassesByName.size() == 1) {
                this.uniqueCompass = this.compassesByName.values().iterator().next();
            }

            if (this.compassesByName.isEmpty() && this.uniqueCompass == null) {
                logger.warn("No named compass instances defined and not exactly one anonymous one: cannot inject");
            }
        }
    }

    /**
     * Find a Compass with the given name in the current
     * application context
     *
     * @param compassName name of the EntityManagerFactory
     * @return the EntityManagerFactory or throw NoSuchBeanDefinitionException
     * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException if there is no such EntityManagerFactory
     *                                       in the context
     */
    protected Compass findEntityManagerFactoryByName(String compassName) throws NoSuchBeanDefinitionException {

        initMapsIfNecessary();
        if (compassName == null || "".equals(compassName)) {
            if (this.uniqueCompass != null) {
                return this.uniqueCompass;
            } else {
                throw new NoSuchBeanDefinitionException("No Compass name given and factory contains several");
            }
        }
        Compass namedCompass = this.compassesByName.get(compassName);
        if (namedCompass == null) {
            throw new NoSuchBeanDefinitionException("No Compass found for name [" + compassName + "]");
        }
        return namedCompass;
    }

    @Override
    public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
        List<AnnotatedMember> metadata = findClassMetadata(bean.getClass());
        for (AnnotatedMember member : metadata) {
            member.inject(bean);
        }
        return true;
    }

    private synchronized List<AnnotatedMember> findClassMetadata(Class<? extends Object> clazz) {
        List<AnnotatedMember> metadata = this.classMetadata.get(clazz);
        if (metadata == null) {
            final List<AnnotatedMember> newMetadata = new LinkedList<AnnotatedMember>();

            ReflectionUtils.doWithFields(clazz, new ReflectionUtils.FieldCallback() {
                public void doWith(Field f) {
                    addIfPresent(newMetadata, f);
                }
            });

            // TODO is it correct to walk up the hierarchy for methods? Otherwise inheritance
            // is implied? CL to resolve
            ReflectionUtils.doWithMethods(clazz, new ReflectionUtils.MethodCallback() {
                public void doWith(Method m) {
                    addIfPresent(newMetadata, m);
                }
            });

            metadata = newMetadata;
            this.classMetadata.put(clazz, metadata);
        }
        return metadata;
    }

    private void addIfPresent(List<AnnotatedMember> metadata, AccessibleObject ao) {
        CompassContext compassContext = ao.getAnnotation(CompassContext.class);
        if (compassContext != null) {
            metadata.add(new AnnotatedMember(compassContext.name(), ao));
        }
    }

    /**
     * Class representing injection information about an annotated field
     * or setter method.
     */
    private class AnnotatedMember {

        private final String name;

        private final AccessibleObject member;

        public AnnotatedMember(String name, AccessibleObject member) {
            this.name = name;
            this.member = member;

            // Validate member type
            Class<?> memberType = getMemberType();
            if (!(Compass.class.isAssignableFrom(memberType)
                    || CompassSession.class.isAssignableFrom(memberType))) {
                throw new IllegalArgumentException("Cannot inject " + member + ": not a supported Compass type");
            }
        }

        public void inject(Object instance) {
            Object value = resolve();
            try {
                if (!this.member.isAccessible()) {
                    this.member.setAccessible(true);
                }
                if (this.member instanceof Field) {
                    ((Field) this.member).set(instance, value);
                } else if (this.member instanceof Method) {
                    ((Method) this.member).invoke(instance, value);
                } else {
                    throw new IllegalArgumentException(
                            "Cannot inject unknown AccessibleObject type " + this.member);
                }
            } catch (IllegalAccessException ex) {
                throw new IllegalArgumentException("Cannot inject member " + this.member, ex);
            } catch (InvocationTargetException ex) {
                // Method threw an exception
                throw new IllegalArgumentException(
                        "Attempt to inject setter method " + this.member + " resulted in an exception", ex);
            }
        }

        /**
         * Return the type of the member, whether it's a field or a method.
         */
        public Class<?> getMemberType() {
            if (member instanceof Field) {
                return ((Field) member).getType();
            } else if (member instanceof Method) {
                Method setter = (Method) member;
                if (setter.getParameterTypes().length != 1) {
                    throw new IllegalArgumentException("Supposed setter " + this.member
                            + " must have 1 argument, not " + setter.getParameterTypes().length);
                }
                return setter.getParameterTypes()[0];
            } else {
                throw new IllegalArgumentException("Unknown AccessibleObject type " + this.member.getClass()
                        + "; Can only inject settermethods or fields");
            }
        }

        /**
         * Resolve the object against the application context.
         */
        protected Object resolve() {
            // Resolves to Compass or CompassSession.
            Compass compass = findEntityManagerFactoryByName(this.name);
            if (Compass.class.isAssignableFrom(getMemberType())) {
                if (!getMemberType().isInstance(compass)) {
                    throw new IllegalArgumentException(
                            "Cannot inject " + this.member + " with Compass [" + this.name + "]: type mismatch");
                }
                return compass;
            } else {
                // We need to inject aa CompassSession.
                return Proxy.newProxyInstance(CompassContextBeanPostProcessor.class.getClassLoader(),
                        new Class[] { InternalCompassSession.class },
                        new CompassSessionTransactionalProxy(compass));

            }
        }
    }

}