001: /*
002: * Copyright (c) 2002-2006 by OpenSymphony
003: * All rights reserved.
004: */
005: package com.opensymphony.xwork;
006:
007: import com.opensymphony.util.ClassLoaderUtil;
008: import com.opensymphony.xwork.config.ConfigurationException;
009: import com.opensymphony.xwork.config.entities.ActionConfig;
010: import com.opensymphony.xwork.config.entities.InterceptorConfig;
011: import com.opensymphony.xwork.config.entities.ResultConfig;
012: import com.opensymphony.xwork.interceptor.Interceptor;
013: import com.opensymphony.xwork.util.OgnlUtil;
014: import com.opensymphony.xwork.util.XWorkContinuationConfig;
015: import com.opensymphony.xwork.validator.Validator;
016: import com.uwyn.rife.continuations.ContinuationConfig;
017: import com.uwyn.rife.continuations.ContinuationInstrumentor;
018: import com.uwyn.rife.continuations.util.ClassByteUtil;
019:
020: import java.io.IOException;
021: import java.util.HashMap;
022: import java.util.Map;
023:
024: import org.apache.commons.logging.Log;
025: import org.apache.commons.logging.LogFactory;
026:
027: /**
028: * ObjectFactory is responsible for building the core framework objects. Users may register their own implementation of
029: * the ObjectFactory to control instantiation of these Objects.
030: * <p/>
031: * This default implementation uses the {@link #buildBean(Class,java.util.Map) buildBean} method to create all classes
032: * (interceptors, actions, results, etc).
033: *
034: * @author Jason Carreira
035: */
036: public class ObjectFactory {
037: private static final Log LOG = LogFactory
038: .getLog(ObjectFactory.class);
039:
040: private static ContinuationsClassLoader ccl;
041: private static ObjectFactory FACTORY = new ObjectFactory();
042: private static String continuationPackage;
043:
044: public static void setContinuationPackage(String continuationPackage) {
045: ContinuationConfig.setInstance(new XWorkContinuationConfig());
046: ObjectFactory.continuationPackage = continuationPackage;
047: ObjectFactory.ccl = new ContinuationsClassLoader(
048: continuationPackage, Thread.currentThread()
049: .getContextClassLoader());
050: }
051:
052: public static String getContinuationPackage() {
053: return continuationPackage;
054: }
055:
056: protected ObjectFactory() {
057: }
058:
059: public static void setObjectFactory(ObjectFactory factory) {
060: FACTORY = factory;
061: }
062:
063: public static ObjectFactory getObjectFactory() {
064: return FACTORY;
065: }
066:
067: /**
068: * Allows for ObjectFactory implementations that support
069: * Actions without no-arg constructors.
070: *
071: * @return true if no-arg constructor is required, false otherwise
072: */
073: public boolean isNoArgConstructorRequired() {
074: return true;
075: }
076:
077: /**
078: * Utility method to obtain the class matched to className. Caches look ups so that subsequent
079: * lookups will be faster.
080: *
081: * @param className The fully qualified name of the class to return
082: * @return The class itself
083: * @throws ClassNotFoundException
084: */
085: public Class getClassInstance(String className)
086: throws ClassNotFoundException {
087: if (ccl != null) {
088: return ccl.loadClass(className);
089: }
090:
091: return ClassLoaderUtil.loadClass(className, this .getClass());
092: }
093:
094: /**
095: * Build an instance of the action class to handle a web request
096: * @param actionName the name the action configuration is set up with in the configuration
097: * @param namespace the namespace the action is configured in
098: * @param config the action configuration found in the config for the actionName / namespace
099: * @param extraContext a Map of extra context which uses the same keys as the {@link com.opensymphony.xwork.ActionContext}
100: * @return instance of the action class to handle a web request
101: * @throws Exception
102: */
103: public Object buildAction(String actionName, String namespace,
104: ActionConfig config, Map extraContext) throws Exception {
105: return buildBean(config.getClassName(), extraContext);
106: }
107:
108: /**
109: * Build a generic Java object of the given type.
110: *
111: * @param clazz the type of Object to build
112: * @param extraContext a Map of extra context which uses the same keys as the {@link com.opensymphony.xwork.ActionContext}
113: */
114: public Object buildBean(Class clazz, Map extraContext)
115: throws Exception {
116: return clazz.newInstance();
117: }
118:
119: /**
120: * Build a generic Java object of the given type.
121: *
122: * @param className the type of Object to build
123: * @param extraContext a Map of extra context which uses the same keys as the {@link com.opensymphony.xwork.ActionContext}
124: */
125: public Object buildBean(String className, Map extraContext)
126: throws Exception {
127: Class clazz = getClassInstance(className);
128:
129: return buildBean(clazz, extraContext);
130: }
131:
132: /**
133: * Builds an Interceptor from the InterceptorConfig and the Map of
134: * parameters from the interceptor reference. Implementations of this method
135: * should ensure that the Interceptor is parameterized with both the
136: * parameters from the Interceptor config and the interceptor ref Map (the
137: * interceptor ref params take precedence), and that the Interceptor.init()
138: * method is called on the Interceptor instance before it is returned.
139: *
140: * @param interceptorConfig the InterceptorConfig from the configuration
141: * @param interceptorRefParams a Map of params provided in the Interceptor reference in the
142: * Action mapping or InterceptorStack definition
143: */
144: public Interceptor buildInterceptor(
145: InterceptorConfig interceptorConfig,
146: Map interceptorRefParams) throws ConfigurationException {
147: String interceptorClassName = interceptorConfig.getClassName();
148: Map this InterceptorClassParams = interceptorConfig.getParams();
149: Map params = (this InterceptorClassParams == null) ? new HashMap()
150: : new HashMap(this InterceptorClassParams);
151: params.putAll(interceptorRefParams);
152:
153: String message;
154: Throwable cause;
155:
156: try {
157: // interceptor instances are long-lived and used across user sessions, so don't try to pass in any extra context
158: Interceptor interceptor = (Interceptor) buildBean(
159: interceptorClassName, null);
160: OgnlUtil.setProperties(params, interceptor);
161: interceptor.init();
162:
163: return interceptor;
164: } catch (InstantiationException e) {
165: cause = e;
166: message = "Unable to instantiate an instance of Interceptor class ["
167: + interceptorClassName + "].";
168: } catch (IllegalAccessException e) {
169: cause = e;
170: message = "IllegalAccessException while attempting to instantiate an instance of Interceptor class ["
171: + interceptorClassName + "].";
172: } catch (ClassCastException e) {
173: cause = e;
174: message = "Class ["
175: + interceptorClassName
176: + "] does not implement com.opensymphony.xwork.interceptor.Interceptor";
177: } catch (Exception e) {
178: cause = e;
179: message = "Caught Exception while registering Interceptor class "
180: + interceptorClassName;
181: } catch (NoClassDefFoundError e) {
182: cause = e;
183: message = "Could not load class "
184: + interceptorClassName
185: + ". Perhaps it exists but certain dependencies are not available?";
186: }
187:
188: throw new ConfigurationException(message, cause,
189: interceptorConfig);
190: }
191:
192: /**
193: * Build a Result using the type in the ResultConfig and set the parameters in the ResultConfig.
194: *
195: * @param resultConfig the ResultConfig found for the action with the result code returned
196: * @param extraContext a Map of extra context which uses the same keys as the {@link com.opensymphony.xwork.ActionContext}
197: */
198: public Result buildResult(ResultConfig resultConfig,
199: Map extraContext) throws Exception {
200: String resultClassName = resultConfig.getClassName();
201: Result result = null;
202:
203: if (resultClassName != null) {
204: result = (Result) buildBean(resultClassName, extraContext);
205: OgnlUtil.setProperties(resultConfig.getParams(), result,
206: extraContext);
207: }
208:
209: return result;
210: }
211:
212: /**
213: * Build a Validator of the given type and set the parameters on it
214: *
215: * @param className the type of Validator to build
216: * @param params property name -> value Map to set onto the Validator instance
217: * @param extraContext a Map of extra context which uses the same keys as the {@link com.opensymphony.xwork.ActionContext}
218: */
219: public Validator buildValidator(String className, Map params,
220: Map extraContext) throws Exception {
221: Validator validator = (Validator) buildBean(className, null);
222: OgnlUtil.setProperties(params, validator);
223:
224: return validator;
225: }
226:
227: static class ContinuationsClassLoader extends ClassLoader {
228: private String base;
229: private ClassLoader parent;
230:
231: public ContinuationsClassLoader(String base, ClassLoader parent) {
232: super (parent);
233: this .base = base;
234: this .parent = parent;
235: }
236:
237: public Class loadClass(String name)
238: throws ClassNotFoundException {
239: if (validName(name)) {
240: Class clazz = findLoadedClass(name);
241: if (clazz == null) {
242: try {
243: byte[] bytes = ClassByteUtil.getBytes(name,
244: parent);
245: if (bytes == null) {
246: throw new XworkException(
247: "Continuation error: no bytes loaded for class '"
248: + name + "'");
249: }
250:
251: byte[] resume_bytes = null;
252: try {
253: resume_bytes = ContinuationInstrumentor
254: .instrument(bytes, name, false);
255: } catch (ClassNotFoundException e) {
256: // this can happen when the Rife Continuations code gets broken (there are bugs in it still, ya know!)
257: // rather than making a big deal, we'll quietly log this and move on
258: // when more people are using continuations, perhaps we'll raise the log level
259: LOG
260: .debug(
261: "Error instrumenting with RIFE/Continuations, "
262: + "loading class normally without continuation support",
263: e);
264: }
265:
266: if (resume_bytes == null) {
267: return parent.loadClass(name);
268: } else {
269: return defineClass(name, resume_bytes, 0,
270: resume_bytes.length);
271: }
272: } catch (IOException e) {
273: throw new XworkException("Continuation error",
274: e);
275: }
276: } else {
277: return clazz;
278: }
279: } else {
280: return parent.loadClass(name);
281: }
282: }
283:
284: private boolean validName(String name) {
285: return name.startsWith(base + ".");
286: }
287: }
288: }
|