001: /*
002: * Copyright 2002-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.springframework.transaction.interceptor;
018:
019: import java.lang.reflect.Method;
020: import java.util.ArrayList;
021: import java.util.HashMap;
022: import java.util.Iterator;
023: import java.util.List;
024: import java.util.Map;
025:
026: import org.apache.commons.logging.Log;
027: import org.apache.commons.logging.LogFactory;
028:
029: import org.springframework.beans.factory.BeanClassLoaderAware;
030: import org.springframework.beans.factory.InitializingBean;
031: import org.springframework.util.Assert;
032: import org.springframework.util.ClassUtils;
033: import org.springframework.util.ObjectUtils;
034: import org.springframework.util.PatternMatchUtils;
035:
036: /**
037: * Simple {@link TransactionAttributeSource} implementation that
038: * allows attributes to be stored per method in a {@link Map}.
039: *
040: * @author Rod Johnson
041: * @author Juergen Hoeller
042: * @since 24.04.2003
043: * @see #isMatch
044: * @see NameMatchTransactionAttributeSource
045: */
046: public class MethodMapTransactionAttributeSource implements
047: TransactionAttributeSource, BeanClassLoaderAware,
048: InitializingBean {
049:
050: /** Logger available to subclasses */
051: protected final Log logger = LogFactory.getLog(getClass());
052:
053: /** Map from method name to attribute value */
054: private Map methodMap;
055:
056: private ClassLoader beanClassLoader = ClassUtils
057: .getDefaultClassLoader();
058:
059: private boolean eagerlyInitialized = false;
060:
061: private boolean initialized = false;
062:
063: /** Map from Method to TransactionAttribute */
064: private final Map transactionAttributeMap = new HashMap();
065:
066: /** Map from Method to name pattern used for registration */
067: private final Map methodNameMap = new HashMap();
068:
069: /**
070: * Set a name/attribute map, consisting of "FQCN.method" method names
071: * (e.g. "com.mycompany.mycode.MyClass.myMethod") and
072: * {@link TransactionAttribute} instances (or Strings to be converted
073: * to <code>TransactionAttribute</code> instances).
074: * <p>Intended for configuration via setter injection, typically within
075: * a Spring bean factory. Relies on {@link #afterPropertiesSet()}
076: * being called afterwards.
077: * @param methodMap said {@link Map} from method name to attribute value
078: * @see TransactionAttribute
079: * @see TransactionAttributeEditor
080: */
081: public void setMethodMap(Map methodMap) {
082: this .methodMap = methodMap;
083: }
084:
085: public void setBeanClassLoader(ClassLoader beanClassLoader) {
086: this .beanClassLoader = beanClassLoader;
087: }
088:
089: /**
090: * Eagerly initializes the specified
091: * {@link #setMethodMap(java.util.Map) "methodMap"}, if any.
092: * @see #initMethodMap(java.util.Map)
093: */
094: public void afterPropertiesSet() {
095: initMethodMap(this .methodMap);
096: this .eagerlyInitialized = true;
097: this .initialized = true;
098: }
099:
100: /**
101: * Initialize the specified {@link #setMethodMap(java.util.Map) "methodMap"}, if any.
102: * @param methodMap Map from method names to <code>TransactionAttribute</code> instances
103: * (or Strings to be converted to <code>TransactionAttribute</code> instances)
104: * @see #setMethodMap
105: */
106: protected void initMethodMap(Map methodMap) {
107: if (methodMap != null) {
108: Iterator it = methodMap.entrySet().iterator();
109: while (it.hasNext()) {
110: Map.Entry entry = (Map.Entry) it.next();
111: Object key = entry.getKey();
112: if (!(key instanceof String)) {
113: throw new IllegalArgumentException(
114: "Invalid method map key [" + key
115: + "]: only Strings allowed");
116: }
117: Object value = entry.getValue();
118: // Check whether we need to convert from String to TransactionAttribute.
119: TransactionAttribute attr = null;
120: if (value instanceof TransactionAttribute) {
121: attr = (TransactionAttribute) value;
122: } else if (value instanceof String) {
123: TransactionAttributeEditor editor = new TransactionAttributeEditor();
124: editor.setAsText((String) value);
125: attr = (TransactionAttribute) editor.getValue();
126: } else {
127: throw new IllegalArgumentException("Value ["
128: + value + "] is neither of type ["
129: + TransactionAttribute.class.getName()
130: + "] nor a String");
131: }
132: addTransactionalMethod((String) key, attr);
133: }
134: }
135: }
136:
137: /**
138: * Add an attribute for a transactional method.
139: * <p>Method names can end or start with "*" for matching multiple methods.
140: * @param name class and method name, separated by a dot
141: * @param attr attribute associated with the method
142: * @throws IllegalArgumentException in case of an invalid name
143: */
144: public void addTransactionalMethod(String name,
145: TransactionAttribute attr) {
146: Assert.notNull(name, "Name must not be null");
147: int lastDotIndex = name.lastIndexOf(".");
148: if (lastDotIndex == -1) {
149: throw new IllegalArgumentException(
150: "'"
151: + name
152: + "' is not a valid method name: format is FQN.methodName");
153: }
154: String className = name.substring(0, lastDotIndex);
155: String methodName = name.substring(lastDotIndex + 1);
156: Class clazz = ClassUtils.resolveClassName(className,
157: this .beanClassLoader);
158: addTransactionalMethod(clazz, methodName, attr);
159: }
160:
161: /**
162: * Add an attribute for a transactional method.
163: * Method names can end or start with "*" for matching multiple methods.
164: * @param clazz target interface or class
165: * @param mappedName mapped method name
166: * @param attr attribute associated with the method
167: */
168: public void addTransactionalMethod(Class clazz, String mappedName,
169: TransactionAttribute attr) {
170: Assert.notNull(clazz, "Class must not be null");
171: Assert.notNull(mappedName, "Mapped name must not be null");
172: String name = clazz.getName() + '.' + mappedName;
173:
174: // TODO address method overloading? At present this will
175: // simply match all methods that have the given name.
176: // Consider EJB syntax (int, String) etc.?
177: Method[] methods = clazz.getDeclaredMethods();
178: List matchingMethods = new ArrayList();
179: for (int i = 0; i < methods.length; i++) {
180: if (isMatch(methods[i].getName(), mappedName)) {
181: matchingMethods.add(methods[i]);
182: }
183: }
184: if (matchingMethods.isEmpty()) {
185: throw new IllegalArgumentException("Couldn't find method '"
186: + mappedName + "' on class [" + clazz.getName()
187: + "]");
188: }
189:
190: // register all matching methods
191: for (Iterator it = matchingMethods.iterator(); it.hasNext();) {
192: Method method = (Method) it.next();
193: String regMethodName = (String) this .methodNameMap
194: .get(method);
195: if (regMethodName == null
196: || (!regMethodName.equals(name) && regMethodName
197: .length() <= name.length())) {
198: // No already registered method name, or more specific
199: // method name specification now -> (re-)register method.
200: if (logger.isDebugEnabled() && regMethodName != null) {
201: logger
202: .debug("Replacing attribute for transactional method ["
203: + method
204: + "]: current name '"
205: + name
206: + "' is more specific than '"
207: + regMethodName + "'");
208: }
209: this .methodNameMap.put(method, name);
210: addTransactionalMethod(method, attr);
211: } else {
212: if (logger.isDebugEnabled() && regMethodName != null) {
213: logger
214: .debug("Keeping attribute for transactional method ["
215: + method
216: + "]: current name '"
217: + name
218: + "' is not more specific than '"
219: + regMethodName + "'");
220: }
221: }
222: }
223: }
224:
225: /**
226: * Add an attribute for a transactional method.
227: * @param method the method
228: * @param attr attribute associated with the method
229: */
230: public void addTransactionalMethod(Method method,
231: TransactionAttribute attr) {
232: Assert.notNull(method, "Method must not be null");
233: Assert.notNull(attr, "TransactionAttribute must not be null");
234: if (logger.isDebugEnabled()) {
235: logger.debug("Adding transactional method [" + method
236: + "] with attribute [" + attr + "]");
237: }
238: this .transactionAttributeMap.put(method, attr);
239: }
240:
241: /**
242: * Return if the given method name matches the mapped name.
243: * <p>The default implementation checks for "xxx*", "*xxx" and "*xxx*"
244: * matches, as well as direct equality.
245: * @param methodName the method name of the class
246: * @param mappedName the name in the descriptor
247: * @return if the names match
248: * @see org.springframework.util.PatternMatchUtils#simpleMatch(String, String)
249: */
250: protected boolean isMatch(String methodName, String mappedName) {
251: return PatternMatchUtils.simpleMatch(mappedName, methodName);
252: }
253:
254: public TransactionAttribute getTransactionAttribute(Method method,
255: Class targetClass) {
256: if (this .eagerlyInitialized) {
257: return (TransactionAttribute) this .transactionAttributeMap
258: .get(method);
259: } else {
260: synchronized (this .transactionAttributeMap) {
261: if (!this .initialized) {
262: initMethodMap(this .methodMap);
263: this .initialized = true;
264: }
265: return (TransactionAttribute) this .transactionAttributeMap
266: .get(method);
267: }
268: }
269: }
270:
271: public boolean equals(Object other) {
272: if (this == other) {
273: return true;
274: }
275: if (!(other instanceof MethodMapTransactionAttributeSource)) {
276: return false;
277: }
278: MethodMapTransactionAttributeSource otherTas = (MethodMapTransactionAttributeSource) other;
279: return ObjectUtils.nullSafeEquals(this .methodMap,
280: otherTas.methodMap);
281: }
282:
283: public int hashCode() {
284: return MethodMapTransactionAttributeSource.class.hashCode();
285: }
286:
287: public String toString() {
288: return getClass().getName() + ": " + this.methodMap;
289: }
290:
291: }
|