001: /*
002: * Copyright 2004-2006 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.compass.spring.support;
018:
019: import java.lang.reflect.AccessibleObject;
020: import java.lang.reflect.Field;
021: import java.lang.reflect.InvocationTargetException;
022: import java.lang.reflect.Method;
023: import java.lang.reflect.Proxy;
024: import java.util.HashMap;
025: import java.util.LinkedList;
026: import java.util.List;
027: import java.util.Map;
028:
029: import org.apache.commons.logging.Log;
030: import org.apache.commons.logging.LogFactory;
031: import org.compass.core.Compass;
032: import org.compass.core.CompassContext;
033: import org.compass.core.CompassSession;
034: import org.compass.core.spi.InternalCompass;
035: import org.compass.core.spi.InternalCompassSession;
036: import org.compass.core.support.session.CompassSessionTransactionalProxy;
037: import org.springframework.beans.BeansException;
038: import org.springframework.beans.factory.BeanFactoryUtils;
039: import org.springframework.beans.factory.NoSuchBeanDefinitionException;
040: import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter;
041: import org.springframework.context.ApplicationContext;
042: import org.springframework.context.ApplicationContextAware;
043: import org.springframework.util.ReflectionUtils;
044:
045: /**
046: * BeanPostProcessor that processes {@link org.compass.core.CompassContext}
047: * annotation for injection of Compass interfaces. Any such annotated fields
048: * or methods in any Spring-managed object will automatically be injected.
049: * <p/>
050: * Will inject either a {@link Compass} or {@link CompassSession} instances.
051: *
052: * @author kimchy
053: */
054: public class CompassContextBeanPostProcessor extends
055: InstantiationAwareBeanPostProcessorAdapter implements
056: ApplicationContextAware {
057:
058: protected final Log logger = LogFactory.getLog(getClass());
059:
060: private ApplicationContext applicationContext;
061:
062: private Map<Class<?>, List<AnnotatedMember>> classMetadata = new HashMap<Class<?>, List<AnnotatedMember>>();
063:
064: private Map<String, Compass> compassesByName;
065:
066: private Compass uniqueCompass;
067:
068: public void setApplicationContext(
069: ApplicationContext applicationContext) {
070: this .applicationContext = applicationContext;
071: }
072:
073: /**
074: * Lazily initialize compass map.
075: */
076: private synchronized void initMapsIfNecessary() {
077: if (this .compassesByName == null) {
078: this .compassesByName = new HashMap<String, Compass>();
079: // Look for named Compasses
080: String[] beanNames = BeanFactoryUtils
081: .beanNamesForTypeIncludingAncestors(
082: applicationContext, Compass.class);
083: for (String emfName : beanNames) {
084: Compass compass = (Compass) this .applicationContext
085: .getBean(emfName);
086: compassesByName.put(((InternalCompass) compass)
087: .getName(), compass);
088: }
089:
090: if (this .compassesByName.isEmpty()) {
091: if (beanNames.length == 1) {
092: this .uniqueCompass = (Compass) this .applicationContext
093: .getBean(beanNames[0]);
094: }
095: } else if (this .compassesByName.size() == 1) {
096: this .uniqueCompass = this .compassesByName.values()
097: .iterator().next();
098: }
099:
100: if (this .compassesByName.isEmpty()
101: && this .uniqueCompass == null) {
102: logger
103: .warn("No named compass instances defined and not exactly one anonymous one: cannot inject");
104: }
105: }
106: }
107:
108: /**
109: * Find a Compass with the given name in the current
110: * application context
111: *
112: * @param compassName name of the EntityManagerFactory
113: * @return the EntityManagerFactory or throw NoSuchBeanDefinitionException
114: * @throws NoSuchBeanDefinitionException if there is no such EntityManagerFactory
115: * in the context
116: */
117: protected Compass findEntityManagerFactoryByName(String compassName)
118: throws NoSuchBeanDefinitionException {
119:
120: initMapsIfNecessary();
121: if (compassName == null || "".equals(compassName)) {
122: if (this .uniqueCompass != null) {
123: return this .uniqueCompass;
124: } else {
125: throw new NoSuchBeanDefinitionException(
126: "No Compass name given and factory contains several");
127: }
128: }
129: Compass namedCompass = this .compassesByName.get(compassName);
130: if (namedCompass == null) {
131: throw new NoSuchBeanDefinitionException(
132: "No Compass found for name [" + compassName + "]");
133: }
134: return namedCompass;
135: }
136:
137: @Override
138: public boolean postProcessAfterInstantiation(Object bean,
139: String beanName) throws BeansException {
140: List<AnnotatedMember> metadata = findClassMetadata(bean
141: .getClass());
142: for (AnnotatedMember member : metadata) {
143: member.inject(bean);
144: }
145: return true;
146: }
147:
148: private synchronized List<AnnotatedMember> findClassMetadata(
149: Class<? extends Object> clazz) {
150: List<AnnotatedMember> metadata = this .classMetadata.get(clazz);
151: if (metadata == null) {
152: final List<AnnotatedMember> newMetadata = new LinkedList<AnnotatedMember>();
153:
154: ReflectionUtils.doWithFields(clazz,
155: new ReflectionUtils.FieldCallback() {
156: public void doWith(Field f) {
157: addIfPresent(newMetadata, f);
158: }
159: });
160:
161: // TODO is it correct to walk up the hierarchy for methods? Otherwise inheritance
162: // is implied? CL to resolve
163: ReflectionUtils.doWithMethods(clazz,
164: new ReflectionUtils.MethodCallback() {
165: public void doWith(Method m) {
166: addIfPresent(newMetadata, m);
167: }
168: });
169:
170: metadata = newMetadata;
171: this .classMetadata.put(clazz, metadata);
172: }
173: return metadata;
174: }
175:
176: private void addIfPresent(List<AnnotatedMember> metadata,
177: AccessibleObject ao) {
178: CompassContext compassContext = ao
179: .getAnnotation(CompassContext.class);
180: if (compassContext != null) {
181: metadata
182: .add(new AnnotatedMember(compassContext.name(), ao));
183: }
184: }
185:
186: /**
187: * Class representing injection information about an annotated field
188: * or setter method.
189: */
190: private class AnnotatedMember {
191:
192: private final String name;
193:
194: private final AccessibleObject member;
195:
196: public AnnotatedMember(String name, AccessibleObject member) {
197: this .name = name;
198: this .member = member;
199:
200: // Validate member type
201: Class<?> memberType = getMemberType();
202: if (!(Compass.class.isAssignableFrom(memberType) || CompassSession.class
203: .isAssignableFrom(memberType))) {
204: throw new IllegalArgumentException("Cannot inject "
205: + member + ": not a supported Compass type");
206: }
207: }
208:
209: public void inject(Object instance) {
210: Object value = resolve();
211: try {
212: if (!this .member.isAccessible()) {
213: this .member.setAccessible(true);
214: }
215: if (this .member instanceof Field) {
216: ((Field) this .member).set(instance, value);
217: } else if (this .member instanceof Method) {
218: ((Method) this .member).invoke(instance, value);
219: } else {
220: throw new IllegalArgumentException(
221: "Cannot inject unknown AccessibleObject type "
222: + this .member);
223: }
224: } catch (IllegalAccessException ex) {
225: throw new IllegalArgumentException(
226: "Cannot inject member " + this .member, ex);
227: } catch (InvocationTargetException ex) {
228: // Method threw an exception
229: throw new IllegalArgumentException(
230: "Attempt to inject setter method "
231: + this .member
232: + " resulted in an exception", ex);
233: }
234: }
235:
236: /**
237: * Return the type of the member, whether it's a field or a method.
238: */
239: public Class<?> getMemberType() {
240: if (member instanceof Field) {
241: return ((Field) member).getType();
242: } else if (member instanceof Method) {
243: Method setter = (Method) member;
244: if (setter.getParameterTypes().length != 1) {
245: throw new IllegalArgumentException(
246: "Supposed setter " + this .member
247: + " must have 1 argument, not "
248: + setter.getParameterTypes().length);
249: }
250: return setter.getParameterTypes()[0];
251: } else {
252: throw new IllegalArgumentException(
253: "Unknown AccessibleObject type "
254: + this .member.getClass()
255: + "; Can only inject settermethods or fields");
256: }
257: }
258:
259: /**
260: * Resolve the object against the application context.
261: */
262: protected Object resolve() {
263: // Resolves to Compass or CompassSession.
264: Compass compass = findEntityManagerFactoryByName(this .name);
265: if (Compass.class.isAssignableFrom(getMemberType())) {
266: if (!getMemberType().isInstance(compass)) {
267: throw new IllegalArgumentException("Cannot inject "
268: + this .member + " with Compass ["
269: + this .name + "]: type mismatch");
270: }
271: return compass;
272: } else {
273: // We need to inject aa CompassSession.
274: return Proxy.newProxyInstance(
275: CompassContextBeanPostProcessor.class
276: .getClassLoader(),
277: new Class[] { InternalCompassSession.class },
278: new CompassSessionTransactionalProxy(compass));
279:
280: }
281: }
282: }
283:
284: }
|