001: package com.icesoft.util;
002:
003: import org.apache.commons.logging.Log;
004: import org.apache.commons.logging.LogFactory;
005:
006: import java.lang.reflect.Method;
007: import java.lang.reflect.Field;
008: import java.util.StringTokenizer;
009:
010: import javax.faces.event.PhaseListener;
011: import javax.faces.lifecycle.Lifecycle;
012: import javax.faces.context.FacesContext;
013:
014: /**
015: * @author ICEsoft Technologies, Inc.
016: *
017: * Purpose of this class is to localize Seam Introspection code
018: * in one place, and get rid of the variables cluttering up a few
019: * of the ICEfaces classes
020: *
021: * Jun 2007 - removed reference to ConversationIsLongRunningParameter
022: * since seam1.3.0.ALPHA has removed all reference to it in Manager Class
023: *
024: */
025: public class SeamUtilities {
026:
027: private static final Log log = LogFactory
028: .getLog(SeamUtilities.class);
029:
030: // Seam vars
031:
032: private static Class seamManagerClass;
033:
034: private static Class[] seamClassArgs = new Class[0];
035: private static Object[] seamInstanceArgs = new Object[0];
036: private static Class[] seamGetEncodeMethodArgs = { String.class,
037: String.class };
038: private static Object[] seamEncodeMethodArgs = new Object[2];
039:
040: private static Object[] seamMethodNoArgs = new Object[0];
041:
042: private static Method seamConversationIdMethodInstance;
043: private static Method seamLongRunningMethodInstance;
044: private static Method seamAppendConversationMethodInstance;
045: private static Method seamInstanceMethod;
046: private static Method seamPageContextGetPrefixInstance;
047:
048: // The method for getting the conversationId parameter name
049: private static Method seamConversationIdParameterMethod;
050:
051: // The method for getting the parent conversationId parameter name
052: // private static Method seamParentConversationIdParameterMethod;
053:
054: // the method for getting the longRunningConversation parameter name
055: // private static Method seamLongRunningConversationParameterMethod;
056:
057: // private static Method seamBeforeRedirectMethodInstance;
058:
059: private static Object pageContextInstance;
060:
061: // This is just convenience, to avoid rebuilding the String
062: private static String conversationIdParameter;
063: private static String conversationParentParameter = "parentConversationId";
064: // private static String conversationLongRunningParameter;
065:
066: // since seam1.3.0Alpha has api changes, detect which version we are using
067: private static String seamVersion = "none";
068: private static Method seamVersionMethod;
069:
070: private static String SPRING_CLASS_NAME = "org.springframework.webflow.executor.jsf.FlowVariableResolver";
071:
072: private static boolean isSpringLoaded;
073:
074: static {
075: loadSeamEnvironment();
076: loadSpringEnvironment();
077: }
078:
079: /**
080: * Utility method to determine if the Seam classes can be loaded.
081: *
082: * @return true if Seam classes can be loaded
083: */
084: public static boolean isSeamEnvironment() {
085: return seamManagerClass != null;
086: }
087:
088: /**
089: * Utility method to determine if D2DSeamFaceletViewHandler requires
090: * SeamExpressionFactory Class
091: * @return false if Seam version 1.3.0.ALPHA
092: * false otherwise
093: */
094: public static boolean requiresSeamExpressionFactory() {
095: return (seamVersion.startsWith("1.2.1"));
096: }
097:
098: /**
099: * Called on a redirect to invoke any Seam redirection code. Seam uses
100: * the sendRedirect method to preserve temporary conversations for the
101: * duration of redirects. ICEfaces does not call this method, so this
102: * method attempts to call the same Seam code introspectively. Seam will
103: * encode the <code>conversationId</code> to the end of the argument URI.
104: *
105: * @param uri the redirect URI to redirect to, before the
106: * conversationId is encoded
107: * @return the URI, with the conversationId if Seam is detected
108: */
109: public static String encodeSeamConversationId(String uri,
110: String viewId) {
111:
112: // If Seam's not loaded, no changes necessary
113: if (!isSeamEnvironment()) {
114: return uri;
115: }
116:
117: String cleanedUrl = uri;
118:
119: if (conversationIdParameter == null) {
120: getConversationIdParameterName();
121: }
122:
123: // IF the URI already contains a conversationId, the isLongRunning parameter, or the
124: // parentConversationId parameter, strip it out, and start again.
125:
126: // Maybe all of this has changed. This used to be necessary because the
127: //
128:
129: if (log.isTraceEnabled()) {
130: log.trace("SeamConversationURLParam: "
131: + conversationIdParameter);
132: }
133: StringTokenizer st = new StringTokenizer(uri, "?&");
134: StringBuffer builder = new StringBuffer();
135:
136: String token;
137: boolean first = true;
138: while (st.hasMoreTokens()) {
139: token = st.nextToken();
140: if ((token.indexOf(conversationIdParameter) > -1)
141: || (token.indexOf(conversationParentParameter) > -1)
142: ||
143: // (token.indexOf(conversationLongRunningParameter) > -1) ||
144: token.indexOf("rvn") > -1) {
145: continue;
146: }
147: builder.append(token);
148:
149: if (st.hasMoreTokens()) {
150: if (first) {
151: builder.append('?');
152: first = false;
153: } else {
154: builder.append('&');
155: }
156: }
157: }
158:
159: if (builder.length() > 0) {
160: cleanedUrl = builder.toString();
161: }
162: // The manager instance is a singleton, but it's continuously destroyed
163: // after each request and thus must be re-obtained during each redirect.
164: try {
165:
166: // Get the singleton instance of the Seam Manager each time through
167: Object seamManagerInstance = seamInstanceMethod.invoke(
168: null, seamInstanceArgs);
169:
170: if (seamAppendConversationMethodInstance != null) {
171: seamEncodeMethodArgs[0] = cleanedUrl;
172: if (seamEncodeMethodArgs.length == 2) {
173: seamEncodeMethodArgs[1] = viewId;
174: }
175:
176: // seamBeforeRedirectMethodInstance.invoke(
177: // seamManagerInstance, seamMethodNoArgs);
178:
179: // This has to do what the Manager.redirect method does.
180: cleanedUrl = (String) seamAppendConversationMethodInstance
181: .invoke(seamManagerInstance,
182: seamEncodeMethodArgs);
183:
184: if (log.isDebugEnabled()) {
185: log.debug("Enabled redirect from: " + uri
186: + ", to: " + cleanedUrl);
187: }
188: }
189: } catch (Exception e) {
190: seamInstanceMethod = null;
191: seamManagerClass = null;
192: log.error("Exception encoding seam conversationId: ", e);
193:
194: }
195: return cleanedUrl;
196: }
197:
198: /**
199: * Retrieve the current Seam conversationId (if any) by introspection from
200: * the SeamManager. The seam Conversation must be a long running
201: * conversation, otherwise it isn't useful to encode it in the form. Long
202: * running conversations are started by Seam components at various parts of
203: * the application lifecycle, and their Id is necessary during a partial
204: * submit to continue the thread of the conversation.
205: *
206: * @return The current conversation id, or null if not a seam environment,
207: * or there is no current long running conversation.
208: */
209: public static String getSeamConversationId() {
210:
211: if (!isSeamEnvironment()) {
212: return null;
213: }
214:
215: String returnVal = null;
216:
217: try {
218: // The manager instance is a singleton, but it's continuously
219: // destroyed after each request and thus must be re-obtained.
220: Object seamManagerInstance = seamInstanceMethod.invoke(
221: null, seamMethodNoArgs);
222:
223: if (seamConversationIdMethodInstance != null) {
224:
225: String conversationId = (String) seamConversationIdMethodInstance
226: .invoke(seamManagerInstance, seamMethodNoArgs);
227:
228: Boolean is = (Boolean) seamLongRunningMethodInstance
229: .invoke(seamManagerInstance, seamMethodNoArgs);
230:
231: if (is.booleanValue()) {
232: returnVal = conversationId;
233: }
234: }
235:
236: } catch (Exception e) {
237: seamInstanceMethod = null;
238: seamManagerClass = null;
239: log.error("Exception determining Seam ConversationId: ", e);
240:
241: }
242: return returnVal;
243: }
244:
245: /**
246: * Retrieve the PageContext key. Equivalent to
247: * <code>ScopeType.PAGE.getPrefix()</code>. Can be used to
248: * manipulate the PageContext, without loading the class.
249: *
250: * This String is used as the key to store the PageContext in the
251: * ViewRoot attribute map, and does not equal the string
252: * "org.jboss.seam.PAGE"
253: *
254: * @return The String Key that can be used to find the Seam PageContext
255: */
256: public static String getPageContextKey() {
257:
258: String returnVal = "";
259: if (!isSeamEnvironment()) {
260: return returnVal;
261: }
262:
263: try {
264:
265: if (seamConversationIdMethodInstance != null) {
266: returnVal = (String) seamPageContextGetPrefixInstance
267: .invoke(pageContextInstance, seamMethodNoArgs);
268: }
269:
270: } catch (Exception e) {
271: log.error("Exception fetching Page from ScopeType: ", e);
272:
273: }
274: return returnVal;
275: }
276:
277: /**
278: * Attempt to load the classes from the Seam jars. The methods I
279: * can locate and load here, but the values (for example, the
280: * conversationIdParameter name which is not mutable) have to be
281: * retrieved from a Manager instance when the Manager object is
282: * available, and that is only during a valid EventContext.
283: */
284: private static void loadSeamEnvironment() {
285: try {
286:
287: // load classes
288: seamManagerClass = Class
289: .forName("org.jboss.seam.core.Manager");
290: Class seamScopeTypeClass = Class
291: .forName("org.jboss.seam.ScopeType");
292:
293: // load method instances
294: seamInstanceMethod = seamManagerClass.getMethod("instance",
295: seamClassArgs);
296:
297: Field fieldInstance = seamScopeTypeClass.getField("PAGE");
298:
299: pageContextInstance = fieldInstance.get(seamScopeTypeClass);
300:
301: seamPageContextGetPrefixInstance = seamScopeTypeClass
302: .getMethod("getPrefix", seamClassArgs);
303:
304: // for D2DSeamFaceletViewHandler need to know version
305: try {
306: Class seamClass = Class.forName("org.jboss.seam.Seam");
307: seamVersionMethod = seamClass.getMethod("getVersion",
308: null);
309: if (seamVersionMethod != null) {
310: seamVersion = (String) seamVersionMethod.invoke(
311: null, seamMethodNoArgs);
312: log.info("SeamUtilities: loadSeam.. seamVersion="
313: + seamVersion);
314: }
315: } catch (NoSuchMethodException e) {
316: /* no getVersion method exists for Seam1.2.1 or earlier */
317: seamVersion = "1.2.1.GA";
318: log.info("\t -->>>> seamVersion is null");
319: }
320: log.info("\t ->>> seamVersion=" + seamVersion);
321:
322: try {
323: seamAppendConversationMethodInstance = seamManagerClass
324: .getMethod("encodeConversationId",
325: seamGetEncodeMethodArgs);
326: } catch (NoSuchMethodException e) {
327: /* revert our reflectively discovered Seam method
328: to the Seam 1.2.0 API
329: */
330: seamGetEncodeMethodArgs = new Class[] { String.class };
331: seamEncodeMethodArgs = new Object[1];
332: seamAppendConversationMethodInstance = seamManagerClass
333: .getMethod("encodeConversationId",
334: seamGetEncodeMethodArgs);
335: }
336:
337: seamConversationIdMethodInstance = seamManagerClass
338: .getMethod("getCurrentConversationId",
339: seamClassArgs);
340: seamLongRunningMethodInstance = seamManagerClass.getMethod(
341: "isLongRunningConversation", seamClassArgs);
342:
343: seamConversationIdParameterMethod = seamManagerClass
344: .getMethod("getConversationIdParameter",
345: seamClassArgs);
346:
347: // This method is protected
348: // seamParentConversationIdParameterMethod =
349: // seamManagerClass.getMethod("getParentConversationIdParameter",
350: // seamClassArgs);
351:
352: // seamLongRunningConversationParameterMethod =
353: // seamManagerClass.getMethod("getConversationIsLongRunningParameter",
354: // seamClassArgs);
355:
356: // seamBeforeRedirectMethodInstance =
357: // seamManagerClass.getMethod("beforeRedirect",
358: // seamClassArgs);
359:
360: Class.forName("org.jboss.seam.util.Parameters");
361:
362: } catch (ClassNotFoundException cnf) {
363: // log.info ("Seam environment not detected ");
364: } catch (Exception e) {
365: seamInstanceMethod = null;
366: seamManagerClass = null;
367: log.info("Exception loading seam environment: ", e);
368: }
369:
370: if (seamManagerClass != null) {
371: log.info("Seam environment detected ");
372: }
373: }
374:
375: /**
376: * Seam 1.0.1 uses an element <code>'conversationId'</code> as the
377: * parameter name, whereas Seam 1.1 has it as a configurable item. This method
378: * will call the Manager instance to retrieve the current parameter name
379: * defining containing the conversation ID. This method must only be called
380: * when the EventContext is valid (and thus the Manager
381: * instance is retrievable). The parameter is configurable on a
382: * per application basis, so it wont change at runtime.
383: *
384: * <p>
385: * Calling this method also fills in the conversationIdParameter,
386: * the conversationIsLongRunningParameter, and the conversationParentIdParameter
387: * fields, as they are all configurable, and used in the encoding conversation
388: * id method
389: *
390: * @return the appropriate parameter name for the application
391: */
392: public static String getConversationIdParameterName() {
393: if (!isSeamEnvironment()) {
394: return null;
395: }
396: if (conversationIdParameter != null) {
397: return conversationIdParameter;
398: }
399:
400: String returnVal = null;
401: try {
402:
403: Object seamManagerInstance = seamInstanceMethod.invoke(
404: null, seamMethodNoArgs);
405:
406: // The method may not be available on all versions of Manager
407: if (seamConversationIdParameterMethod != null) {
408:
409: returnVal = (String) seamConversationIdParameterMethod
410: .invoke(seamManagerInstance, seamMethodNoArgs);
411: conversationIdParameter = returnVal;
412: }
413:
414: // if (seamParentConversationIdParameterMethod != null) {
415: // conversationParentParameter = (String)
416: // seamParentConversationIdParameterMethod.
417: // invoke(seamManagerInstance, seamMethodNoArgs);
418: // }
419:
420: // if (seamLongRunningConversationParameterMethod != null) {
421: // conversationLongRunningParameter = (String)
422: // seamLongRunningConversationParameterMethod.invoke(
423: // seamManagerInstance, seamMethodNoArgs);
424: // }
425:
426: } catch (Exception e) {
427: log
428: .error(
429: "Exception fetching conversationId Parameter name: ",
430: e);
431:
432: }
433: return returnVal;
434: }
435:
436: /**
437: * ICE-1084 : We have to turn off Seam's PhaseListener that makes
438: * it's debug page appear, so that our SeamDebugResourceResolver
439: * can do its work.
440: *
441: * @param lifecycle The Lifecycle maintains the list of PhaseListeners
442: */
443: public static void removeSeamDebugPhaseListener(Lifecycle lifecycle) {
444: PhaseListener[] phaseListeners = lifecycle.getPhaseListeners();
445: // System.out.println("*** SeamUtilities.removeSeamDebugPhaseListener()");
446: // System.out.println("*** phaseListeners: " + phaseListeners.length);
447: for (int i = 0; i < phaseListeners.length; i++) {
448: // System.out.println("*** phaseListeners["+i+"]: " + phaseListeners[i]);
449: if (phaseListeners[i].getClass().getName().equals(
450: "org.jboss.seam.debug.jsf.SeamDebugPhaseListener")) {
451: lifecycle.removePhaseListener(phaseListeners[i]);
452: //System.out.println("*** REMOVED: " + phaseListeners[i]);
453: seamDebugPhaseListenerClassLoader = phaseListeners[i]
454: .getClass().getClassLoader();
455: //System.out.println("****** SeamDebugPhaseListener.class.getClassLoader(): " + phaseListeners[i].getClass().getClassLoader());
456: }
457: }
458: }
459:
460: public static ClassLoader getSeamDebugPhaseListenerClassLoader() {
461: return seamDebugPhaseListenerClassLoader;
462: }
463:
464: private static ClassLoader seamDebugPhaseListenerClassLoader;
465:
466: /**
467: * Perform any needed Spring initialization.
468: */
469: private static void loadSpringEnvironment() {
470: Class flowVariableResolver = null;
471: try {
472: flowVariableResolver = Class.forName(SPRING_CLASS_NAME);
473: } catch (Throwable t) {
474: if (log.isDebugEnabled()) {
475: log.debug("Spring webflow not detected: " + t);
476: }
477: }
478: if (null != flowVariableResolver) {
479: isSpringLoaded = true;
480: if (log.isDebugEnabled()) {
481: log.debug("Spring webflow detected: "
482: + flowVariableResolver);
483: }
484: }
485:
486: }
487:
488: /**
489: * Utility method to determine if Spring WebFlow is active.
490: *
491: * @return true if Spring WebFlow is enabled
492: */
493: public static boolean isSpringEnvironment() {
494: return isSpringLoaded;
495: }
496:
497: /**
498: * Retrieve the current Spring flowId (if any).
499: *
500: * @return The current Spring flowId.
501: */
502: public static String getSpringFlowId() {
503: if (!isSpringEnvironment()) {
504: return null;
505: }
506: FacesContext facesContext = FacesContext.getCurrentInstance();
507: //obtain key by evaluating expression with Spring VariableResolver
508: Object value = facesContext.getApplication()
509: .createValueBinding("#{flowExecutionKey}").getValue(
510: facesContext);
511: if (null == value) {
512: return null;
513: }
514:
515: return value.toString();
516: }
517:
518: /**
519: * Return the parameter name for the Spring Flow Id
520: *
521: * @return the appropriate parameter name for the application
522: */
523: public static String getFlowIdParameterName() {
524: return "_flowExecutionKey";
525: }
526:
527: }
|