001: /*
002: * Copyright (c) 2002-2006 by OpenSymphony
003: * All rights reserved.
004: */
005: package com.opensymphony.webwork.portlet.dispatcher;
006:
007: import java.io.IOException;
008: import java.util.HashMap;
009: import java.util.Locale;
010: import java.util.Map;
011:
012: import javax.portlet.ActionRequest;
013: import javax.portlet.ActionResponse;
014: import javax.portlet.GenericPortlet;
015: import javax.portlet.PortletConfig;
016: import javax.portlet.PortletException;
017: import javax.portlet.PortletMode;
018: import javax.portlet.PortletRequest;
019: import javax.portlet.PortletResponse;
020: import javax.portlet.RenderRequest;
021: import javax.portlet.RenderResponse;
022:
023: import org.apache.commons.lang.StringUtils;
024: import org.apache.commons.logging.Log;
025: import org.apache.commons.logging.LogFactory;
026:
027: import com.opensymphony.util.ClassLoaderUtil;
028: import com.opensymphony.util.FileManager;
029: import com.opensymphony.webwork.WebWorkStatics;
030: import com.opensymphony.webwork.WebWorkConstants;
031: import com.opensymphony.webwork.config.Configuration;
032: import com.opensymphony.webwork.dispatcher.ApplicationMap;
033: import com.opensymphony.webwork.dispatcher.DispatcherUtils;
034: import com.opensymphony.webwork.dispatcher.RequestMap;
035: import com.opensymphony.webwork.dispatcher.SessionMap;
036: import com.opensymphony.webwork.dispatcher.mapper.ActionMapping;
037: import com.opensymphony.webwork.portlet.PortletActionConstants;
038: import com.opensymphony.webwork.portlet.PortletApplicationMap;
039: import com.opensymphony.webwork.portlet.PortletRequestMap;
040: import com.opensymphony.webwork.portlet.PortletSessionMap;
041: import com.opensymphony.webwork.portlet.context.PortletActionContext;
042: import com.opensymphony.webwork.portlet.context.ServletContextHolderListener;
043: import com.opensymphony.webwork.util.AttributeMap;
044: import com.opensymphony.webwork.util.ObjectFactoryInitializable;
045: import com.opensymphony.xwork.*;
046: import com.opensymphony.xwork.config.ConfigurationException;
047: import com.opensymphony.xwork.util.LocalizedTextUtil;
048: import com.opensymphony.xwork.util.OgnlValueStack;
049:
050: /**
051: * <!-- START SNIPPET: javadoc -->
052: * <p>
053: * WebWork2 JSR-168 portlet dispatcher. Similar to the WW2 Servlet dispatcher,
054: * but adjusted to a portal environment. The portlet is configured through the <tt>portlet.xml</tt>
055: * descriptor. Examples and descriptions follow below:
056: * </p>
057: * <!-- END SNIPPET: javadoc -->
058: *
059: * @author <a href="nils-helge.garli@bekk.no">Nils-Helge Garli </a>
060: * @author Rainer Hermanns
061: *
062: * <p><b>Init parameters</b></p>
063: * <!-- START SNIPPET: params -->
064: * <table class="confluenceTable">
065: * <tr>
066: * <th class="confluenceTh">Name</th>
067: * <th class="confluenceTh">Description</th>
068: * <th class="confluenceTh">Default value</th>
069: * </tr>
070: * <tr>
071: * <td class="confluenceTd">portletNamespace</td><td class="confluenceTd">The namespace for the portlet in the xwork configuration. This
072: * namespace is prepended to all action lookups, and makes it possible to host multiple
073: * portlets in the same portlet application. If this parameter is set, the complete namespace
074: * will be <tt>/portletNamespace/modeNamespace/actionName</tt></td><td class="confluenceTd">The default namespace</td>
075: * </tr>
076: * <tr>
077: * <td class="confluenceTd">viewNamespace</td><td class="confluenceTd">Base namespace in the xwork configuration for the <tt>view</tt> portlet
078: * mode</td><td class="confluenceTd">The default namespace</td>
079: * </tr>
080: * <tr>
081: * <td class="confluenceTd">editNamespace</td><td class="confluenceTd">Base namespace in the xwork configuration for the <tt>edit</tt> portlet
082: * mode</td><td class="confluenceTd">The default namespace</td>
083: * </tr>
084: * <tr>
085: * <td class="confluenceTd">helpNamespace</td><td class="confluenceTd">Base namespace in the xwork configuration for the <tt>help</tt> portlet
086: * mode</td><td class="confluenceTd">The default namespace</td>
087: * </tr>
088: * <tr>
089: * <td class="confluenceTd">defaultViewAction</td><td class="confluenceTd">Default action to invoke in the <tt>view</tt> portlet mode if no action is
090: * specified</td><td class="confluenceTd"><tt>default</tt></td>
091: * </tr>
092: * <tr>
093: * <td class="confluenceTd">defaultEditAction</td><td class="confluenceTd">Default action to invoke in the <tt>edit</tt> portlet mode if no action is
094: * specified</td><td class="confluenceTd"><tt>default</tt></td>
095: * </tr>
096: * <tr>
097: * <td class="confluenceTd">defaultHelpAction</td><td class="confluenceTd">Default action to invoke in the <tt>help</tt> portlet mode if no action is
098: * specified</td><td class="confluenceTd"><tt>default</tt></td>
099: * </tr>
100: * </table>
101: * <!-- END SNIPPET: params -->
102: * <p><b>Example:</b></p>
103: * <pre>
104: * <!-- START SNIPPET: example -->
105: *
106: * <init-param>
107: * <!-- The view mode namespace. Maps to a namespace in the xwork config file -->
108: * <name>viewNamespace</name>
109: * <value>/view</value>
110: * </init-param>
111: * <init-param>
112: * <!-- The default action to invoke in view mode -->
113: * <name>defaultViewAction</name>
114: * <value>index</value>
115: * </init-param>
116: * <init-param>
117: * <!-- The view mode namespace. Maps to a namespace in the xwork config file -->
118: * <name>editNamespace</name>
119: * <value>/edit</value>
120: * </init-param>
121: * <init-param>
122: * <!-- The default action to invoke in view mode -->
123: * <name>defaultEditAction</name>
124: * <value>index</value>
125: * </init-param>
126: * <init-param>
127: * <!-- The view mode namespace. Maps to a namespace in the xwork config file -->
128: * <name>helpNamespace</name>
129: * <value>/help</value>
130: * </init-param>
131: * <init-param>
132: * <!-- The default action to invoke in view mode -->
133: * <name>defaultHelpAction</name>
134: * <value>index</value>
135: * </init-param>
136: *
137: * <!-- END SNIPPET: example -->
138: * </pre>
139: */
140: public class Jsr168Dispatcher extends GenericPortlet implements
141: WebWorkStatics, PortletActionConstants {
142:
143: private static final Log LOG = LogFactory
144: .getLog(Jsr168Dispatcher.class);
145:
146: private ActionProxyFactory factory = null;
147:
148: private Map modeMap = new HashMap(3);
149:
150: private Map actionMap = new HashMap(3);
151:
152: private String portletNamespace = null;
153:
154: private boolean devMode = false;
155:
156: /**
157: * Initialize the portlet with the init parameters from <tt>portlet.xml</tt>
158: */
159: public void init(PortletConfig cfg) throws PortletException {
160: super .init(cfg);
161: LOG.debug("Initializing portlet " + getPortletName());
162: // For testability
163: if (factory == null) {
164: factory = ActionProxyFactory.getFactory();
165: }
166: portletNamespace = cfg.getInitParameter("portletNamespace");
167: LOG.debug("PortletNamespace: " + portletNamespace);
168: parseModeConfig(cfg, PortletMode.VIEW, "viewNamespace",
169: "defaultViewAction");
170: parseModeConfig(cfg, PortletMode.EDIT, "editNamespace",
171: "defaultEditAction");
172: parseModeConfig(cfg, PortletMode.HELP, "helpNamespace",
173: "defaultHelpAction");
174: parseModeConfig(cfg, new PortletMode("config"),
175: "configNamespace", "defaultConfigAction");
176: parseModeConfig(cfg, new PortletMode("about"),
177: "aboutNamespace", "defaultAboutAction");
178: parseModeConfig(cfg, new PortletMode("print"),
179: "printNamespace", "defaultPrintAction");
180: parseModeConfig(cfg, new PortletMode("preview"),
181: "previewNamespace", "defaultPreviewAction");
182: parseModeConfig(cfg, new PortletMode("edit_defaults"),
183: "editDefaultsNamespace", "defaultEditDefaultsAction");
184: if (StringUtils.isEmpty(portletNamespace)) {
185: portletNamespace = "";
186: }
187: LocalizedTextUtil
188: .addDefaultResourceBundle("com/opensymphony/webwork/webwork-messages");
189:
190: //check for configuration reloading
191: if ("true"
192: .equalsIgnoreCase(Configuration
193: .getString(WebWorkConstants.WEBWORK_CONFIGURATION_XML_RELOAD))) {
194: FileManager.setReloadingConfigs(true);
195: }
196:
197: if ("true".equals(Configuration
198: .get(WebWorkConstants.WEBWORK_DEVMODE))) {
199: devMode = true;
200: Configuration.set(WebWorkConstants.WEBWORK_I18N_RELOAD,
201: "true");
202: Configuration.set(
203: WebWorkConstants.WEBWORK_CONFIGURATION_XML_RELOAD,
204: "true");
205: }
206:
207: if (Configuration.isSet(WebWorkConstants.WEBWORK_OBJECTFACTORY)) {
208: String className = (String) Configuration
209: .get(WebWorkConstants.WEBWORK_OBJECTFACTORY);
210: if (className.equals("spring")) {
211: // note: this class name needs to be in string form so we don't put hard
212: // dependencies on spring, since it isn't technically required.
213: className = "com.opensymphony.webwork.spring.WebWorkSpringObjectFactory";
214: } else if (className.equals("plexus")) {
215: // note: this class name needs to be in string form so we don't put hard
216: // dependencies on spring, since it isn't technically required.
217: className = "com.opensymphony.webwork.plexus.PlexusObjectFactory";
218: }
219:
220: try {
221: Class clazz = ClassLoaderUtil.loadClass(className,
222: Jsr168Dispatcher.class);
223: ObjectFactory objectFactory = (ObjectFactory) clazz
224: .newInstance();
225: if (objectFactory instanceof ObjectFactoryInitializable) {
226: ((ObjectFactoryInitializable) objectFactory)
227: .init(ServletContextHolderListener
228: .getServletContext());
229: }
230: ObjectFactory.setObjectFactory(objectFactory);
231: } catch (Exception e) {
232: LOG.error("Could not load ObjectFactory named "
233: + className + ". Using default ObjectFactory.",
234: e);
235: }
236: }
237: DispatcherUtils.setPortletSupportActive(true);
238: }
239:
240: /**
241: * @see javax.portlet.GenericPortlet#destroy()
242: */
243: public void destroy() { // WW-1395
244: super .destroy();
245: DispatcherUtils dispatcherUtils = DispatcherUtils.getInstance();
246: if (dispatcherUtils == null) {
247: LOG
248: .warn("something is seriously wrong, DispatcherUtil is not initialized (null) ");
249: } else {
250: dispatcherUtils.cleanup();
251: }
252: }
253:
254: /**
255: * Parse the mode to namespace mappings configured in portlet.xml
256: * @param portletConfig The PortletConfig
257: * @param portletMode The PortletMode
258: * @param nameSpaceParam Name of the init parameter where the namespace for the mode
259: * is configured.
260: * @param defaultActionParam Name of the init parameter where the default action to
261: * execute for the mode is configured.
262: */
263: private void parseModeConfig(PortletConfig portletConfig,
264: PortletMode portletMode, String nameSpaceParam,
265: String defaultActionParam) {
266: String namespace = portletConfig
267: .getInitParameter(nameSpaceParam);
268: if (StringUtils.isEmpty(namespace)) {
269: namespace = "";
270: }
271: modeMap.put(portletMode, namespace);
272: String defaultAction = portletConfig
273: .getInitParameter(defaultActionParam);
274: if (StringUtils.isEmpty(defaultAction)) {
275: defaultAction = DEFAULT_ACTION_NAME;
276: }
277: StringBuffer fullPath = new StringBuffer();
278: if (StringUtils.isNotEmpty(portletNamespace)) {
279: fullPath.append(portletNamespace + "/");
280: }
281: if (StringUtils.isNotEmpty(namespace)) {
282: fullPath.append(namespace + "/");
283: }
284: fullPath.append(defaultAction);
285: ActionMapping mapping = new ActionMapping();
286: mapping.setName(getActionName(fullPath.toString()));
287: mapping.setNamespace(getNamespace(fullPath.toString()));
288: actionMap.put(portletMode, mapping);
289: }
290:
291: /**
292: * Service an action from the <tt>event</tt> phase.
293: *
294: * @see javax.portlet.Portlet#processAction(javax.portlet.ActionRequest,
295: * javax.portlet.ActionResponse)
296: */
297: public void processAction(ActionRequest request,
298: ActionResponse response) throws PortletException,
299: IOException {
300: LOG.debug("Entering processAction");
301: resetActionContext();
302: try {
303: serviceAction(request, response, getActionMapping(request),
304: getRequestMap(request), getParameterMap(request),
305: getSessionMap(request), getApplicationMap(),
306: portletNamespace, EVENT_PHASE);
307: LOG.debug("Leaving processAction");
308: } finally {
309: ActionContext.setContext(null);
310: }
311: }
312:
313: /**
314: * Service an action from the <tt>render</tt> phase.
315: *
316: * @see javax.portlet.Portlet#render(javax.portlet.RenderRequest,
317: * javax.portlet.RenderResponse)
318: */
319: public void render(RenderRequest request, RenderResponse response)
320: throws PortletException, IOException {
321:
322: LOG.debug("Entering render");
323: resetActionContext();
324: response.setTitle(getTitle(request));
325: try {
326: // Check to see if an event set the render to be included directly
327: serviceAction(request, response, getActionMapping(request),
328: getRequestMap(request), getParameterMap(request),
329: getSessionMap(request), getApplicationMap(),
330: portletNamespace, RENDER_PHASE);
331: LOG.debug("Leaving render");
332: } finally {
333: resetActionContext();
334: }
335: }
336:
337: /**
338: * Reset the action context.
339: */
340: private void resetActionContext() {
341: ActionContext.setContext(null);
342: }
343:
344: /**
345: * Merges all application and portlet attributes into a single
346: * <tt>HashMap</tt> to represent the entire <tt>Action</tt> context.
347: *
348: * @param requestMap a Map of all request attributes.
349: * @param parameterMap a Map of all request parameters.
350: * @param sessionMap a Map of all session attributes.
351: * @param applicationMap a Map of all servlet context attributes.
352: * @param request the PortletRequest object.
353: * @param response the PortletResponse object.
354: * @param portletConfig the PortletConfig object.
355: * @param phase The portlet phase (render or action, see
356: * {@link PortletActionConstants})
357: * @return a HashMap representing the <tt>Action</tt> context.
358: */
359: public HashMap createContextMap(Map requestMap, Map parameterMap,
360: Map sessionMap, Map applicationMap, PortletRequest request,
361: PortletResponse response, PortletConfig portletConfig,
362: Integer phase) {
363:
364: // TODO Must put http request/response objects into map for use with
365: // ServletActionContext
366: HashMap extraContext = new HashMap();
367: extraContext.put(ActionContext.PARAMETERS, parameterMap);
368: extraContext.put(ActionContext.SESSION, sessionMap);
369: extraContext.put(ActionContext.APPLICATION, applicationMap);
370:
371: Locale locale = null;
372: if (Configuration.isSet(WebWorkConstants.WEBWORK_LOCALE)) {
373: locale = LocalizedTextUtil.localeFromString(Configuration
374: .getString(WebWorkConstants.WEBWORK_LOCALE),
375: request.getLocale());
376: } else {
377: locale = request.getLocale();
378: }
379: extraContext.put(ActionContext.LOCALE, locale);
380:
381: extraContext.put(ActionContext.DEV_MODE, Boolean
382: .valueOf(devMode));
383: extraContext.put(WebWorkConstants.WEBWORK_PORTLET_CONTEXT,
384: getPortletContext());
385: extraContext.put(REQUEST, request);
386: extraContext.put(RESPONSE, response);
387: extraContext.put(PORTLET_CONFIG, portletConfig);
388: extraContext.put(PORTLET_NAMESPACE, portletNamespace);
389: extraContext.put(DEFAULT_ACTION_FOR_MODE, actionMap.get(request
390: .getPortletMode()));
391: // helpers to get access to request/session/application scope
392: extraContext.put("request", requestMap);
393: extraContext.put("session", sessionMap);
394: extraContext.put("application", applicationMap);
395: extraContext.put("parameters", parameterMap);
396: extraContext.put(MODE_NAMESPACE_MAP, modeMap);
397:
398: extraContext.put(PHASE, phase);
399:
400: AttributeMap attrMap = new AttributeMap(extraContext);
401: extraContext.put("attr", attrMap);
402:
403: return extraContext;
404: }
405:
406: /**
407: * Loads the action and executes it. This method first creates the action
408: * context from the given parameters then loads an <tt>ActionProxy</tt>
409: * from the given action name and namespace. After that, the action is
410: * executed and output channels throught the response object.
411: *
412: * @param request the HttpServletRequest object.
413: * @param response the HttpServletResponse object.
414: * @param mapping the action mapping.
415: * @param requestMap a Map of request attributes.
416: * @param parameterMap a Map of request parameters.
417: * @param sessionMap a Map of all session attributes.
418: * @param applicationMap a Map of all application attributes.
419: * @param portletNamespace the namespace or context of the action.
420: * @param phase The portlet phase (render or action, see
421: * {@link PortletActionConstants})
422: */
423: public void serviceAction(PortletRequest request,
424: PortletResponse response, ActionMapping mapping,
425: Map requestMap, Map parameterMap, Map sessionMap,
426: Map applicationMap, String portletNamespace, Integer phase)
427: throws PortletException {
428: LOG.debug("serviceAction");
429: HashMap extraContext = createContextMap(requestMap,
430: parameterMap, sessionMap, applicationMap, request,
431: response, getPortletConfig(), phase);
432: PortletMode mode = request.getPortletMode();
433: String actionName = mapping.getName();
434: String namespace = mapping.getNamespace();
435: try {
436: LOG.debug("Creating action proxy for name = " + actionName
437: + ", namespace = " + namespace);
438: ActionProxy proxy = factory.createActionProxy(namespace,
439: actionName, extraContext);
440: request.setAttribute("webwork.valueStack", proxy
441: .getInvocation().getStack());
442: if (PortletActionConstants.RENDER_PHASE.equals(phase)
443: && StringUtils.isNotEmpty(request
444: .getParameter(EVENT_ACTION))) {
445:
446: ActionProxy action = (ActionProxy) request
447: .getPortletSession().getAttribute(EVENT_ACTION);
448: if (action != null) {
449: OgnlValueStack stack = proxy.getInvocation()
450: .getStack();
451: Object top = stack.pop();
452: stack.push(action.getInvocation().getAction());
453: stack.push(top);
454: }
455: }
456: proxy.execute();
457: if (PortletActionConstants.EVENT_PHASE.equals(phase)) {
458: // Store the executed action in the session for retrieval in the
459: // render phase.
460: ActionResponse actionResp = (ActionResponse) response;
461: request.getPortletSession().setAttribute(EVENT_ACTION,
462: proxy);
463: actionResp.setRenderParameter(EVENT_ACTION, "true");
464: }
465: } catch (ConfigurationException e) {
466: LOG.error("Could not find action", e);
467: throw new PortletException("Could not find action "
468: + actionName, e);
469: } catch (Exception e) {
470: LOG.error("Could not execute action", e);
471: throw new PortletException("Error executing action "
472: + actionName, e);
473: }
474: }
475:
476: /**
477: * Returns a Map of all application attributes. Copies all attributes from
478: * the {@link PortletActionContext}into an {@link ApplicationMap}.
479: *
480: * @return a Map of all application attributes.
481: */
482: protected Map getApplicationMap() {
483: return new PortletApplicationMap(getPortletContext());
484: }
485:
486: /**
487: * Gets the namespace of the action from the request. The namespace is the
488: * same as the portlet mode. E.g, view mode is mapped to namespace
489: * <code>view</code>, and edit mode is mapped to the namespace
490: * <code>edit</code>
491: *
492: * @param request the PortletRequest object.
493: * @return the namespace of the action.
494: */
495: protected ActionMapping getActionMapping(PortletRequest request) {
496: ActionMapping mapping = new ActionMapping();
497: if (resetAction(request)) {
498: mapping = (ActionMapping) actionMap.get(request
499: .getPortletMode());
500: } else {
501: String actionPath = request.getParameter(ACTION_PARAM);
502: if (StringUtils.isEmpty(actionPath)) {
503: mapping = (ActionMapping) actionMap.get(request
504: .getPortletMode());
505: } else {
506: String namespace = "";
507: String action = actionPath;
508: int idx = actionPath.lastIndexOf('/');
509: if (idx >= 0) {
510: namespace = actionPath.substring(0, idx);
511: action = actionPath.substring(idx + 1);
512: }
513: mapping.setName(action);
514: mapping.setNamespace(namespace);
515: }
516: }
517: return mapping;
518: }
519:
520: /**
521: * Get the namespace part of the action path.
522: * @param actionPath Full path to action
523: * @return The namespace part.
524: */
525: String getNamespace(String actionPath) {
526: int idx = actionPath.lastIndexOf('/');
527: String namespace = "";
528: if (idx >= 0) {
529: namespace = actionPath.substring(0, idx);
530: }
531: return namespace;
532: }
533:
534: /**
535: * Get the action name part of the action path.
536: * @param actionPath Full path to action
537: * @return The action name.
538: */
539: String getActionName(String actionPath) {
540: int idx = actionPath.lastIndexOf('/');
541: String action = actionPath;
542: if (idx >= 0) {
543: action = actionPath.substring(idx + 1);
544: }
545: return action;
546: }
547:
548: /**
549: * Returns a Map of all request parameters. This implementation just calls
550: * {@link PortletRequest#getParameterMap()}.
551: *
552: * @param request the PortletRequest object.
553: * @return a Map of all request parameters.
554: * @throws IOException if an exception occurs while retrieving the parameter
555: * map.
556: */
557: protected Map getParameterMap(PortletRequest request)
558: throws IOException {
559: return new HashMap(request.getParameterMap());
560: }
561:
562: /**
563: * Returns a Map of all request attributes. The default implementation is to
564: * wrap the request in a {@link RequestMap}. Override this method to
565: * customize how request attributes are mapped.
566: *
567: * @param request the PortletRequest object.
568: * @return a Map of all request attributes.
569: */
570: protected Map getRequestMap(PortletRequest request) {
571: return new PortletRequestMap(request);
572: }
573:
574: /**
575: * Returns a Map of all session attributes. The default implementation is to
576: * wrap the reqeust in a {@link SessionMap}. Override this method to
577: * customize how session attributes are mapped.
578: *
579: * @param request the PortletRequest object.
580: * @return a Map of all session attributes.
581: */
582: protected Map getSessionMap(PortletRequest request) {
583: return new PortletSessionMap(request);
584: }
585:
586: /**
587: * Convenience method to ease testing.
588: * @param factory
589: */
590: protected void setActionProxyFactory(ActionProxyFactory factory) {
591: this .factory = factory;
592: }
593:
594: /**
595: * Check to see if the action parameter is valid for the current portlet mode. If the portlet
596: * mode has been changed with the portal widgets, the action name is invalid, since the
597: * action name belongs to the previous executing portlet mode. If this method evaluates to
598: * <code>true</code> the <code>default<Mode>Action</code> is used instead.
599: * @param request The portlet request.
600: * @return <code>true</code> if the action should be reset.
601: */
602: private boolean resetAction(PortletRequest request) {
603: boolean reset = false;
604: Map paramMap = request.getParameterMap();
605: String[] modeParam = (String[]) paramMap.get(MODE_PARAM);
606: if (modeParam != null && modeParam.length == 1) {
607: String originatingMode = modeParam[0];
608: String currentMode = request.getPortletMode().toString();
609: if (!currentMode.equals(originatingMode)) {
610: reset = true;
611: }
612: }
613: return reset;
614: }
615: }
|