001: /* Copyright 2004 The JA-SIG Collaborative. All rights reserved.
002: * See license distributed with this file and
003: * available online at http://www.uportal.org/license.html
004: */
005:
006: package org.jasig.portal.container.services.information;
007:
008: import java.io.UnsupportedEncodingException;
009: import java.net.URLDecoder;
010: import java.net.URLEncoder;
011: import java.util.HashMap;
012: import java.util.Iterator;
013: import java.util.Map;
014:
015: import javax.portlet.PortletMode;
016: import javax.portlet.WindowState;
017: import javax.servlet.ServletRequest;
018: import javax.servlet.http.HttpServletRequest;
019: import javax.servlet.http.HttpServletRequestWrapper;
020: import javax.servlet.http.HttpSession;
021:
022: import org.apache.commons.logging.Log;
023: import org.apache.commons.logging.LogFactory;
024: import org.apache.pluto.om.window.PortletWindow;
025: import org.jasig.portal.ChannelManager;
026: import org.jasig.portal.ChannelRuntimeData;
027: import org.jasig.portal.PortalException;
028: import org.jasig.portal.UPFileSpec;
029: import org.jasig.portal.UserInstance;
030: import org.jasig.portal.container.om.window.PortletWindowImpl;
031: import org.jasig.portal.layout.IUserLayout;
032:
033: /**
034: * The PortletStateManager implementation.
035: * Analyzes the incoming request parameters for the given PortletWindow,
036: * changes the window states/portlet modes, stores them in the static hash maps,
037: * builds a portlet URL based on the changed modes/states and
038: * portlet render parameters for the current PortletWindow.
039: *
040: * @author Michael Ivanov, mvi@immagic.com
041: * @version $Revision: 42278 $
042: */
043: public class PortletStateManager {
044: private static final Log LOG = LogFactory
045: .getLog(PortletStateManager.class);
046:
047: public static final String UP_PARAM_PREFIX = "uP_";
048:
049: // The portlet control parameter names
050: public static final String ACTION = UP_PARAM_PREFIX
051: + "portlet_action";
052: public static final String UP_ROOT = UP_PARAM_PREFIX + "root";
053: public static final String UP_TCATTR = UP_PARAM_PREFIX + "tcattr";
054: public static final String UP_HELP_TARGET = UP_PARAM_PREFIX
055: + "help_target";
056: public static final String UP_EDIT_TARGET = UP_PARAM_PREFIX
057: + "edit_target";
058: public static final String UP_VIEW_TARGET = UP_PARAM_PREFIX
059: + "view_target";
060: public static final String UP_WINDOW_STATE = UP_PARAM_PREFIX
061: + "window_state";
062: public static final String MIN_CHAN_ID = "minimized_channelId";
063:
064: private static final String MINIMIZED = "minimized";
065: private static final String ROOT = IUserLayout.ROOT_NODE_NAME;
066:
067: private final PortletWindowImpl windowOfAction;
068:
069: //** Fields for URL generation
070: // Indicates the current action
071: private boolean isAction;
072: // Indicates the action of the next request
073: private boolean nextAction;
074: private PortletMode nextMode;
075: private WindowState nextState;
076:
077: private final Map params = new HashMap();
078:
079: /**
080: * Creates a new PortletStateManager instance which can be used for
081: * generating a URL for the specified PortletWindow.
082: *
083: * @param window The PortletWindow to generate a URL for.
084: */
085: public PortletStateManager(PortletWindow window) {
086: if (window == null)
087: throw new IllegalArgumentException("window cannot be null");
088:
089: this .windowOfAction = (PortletWindowImpl) window;
090: this .nextMode = null;
091: this .nextState = null;
092: this .isAction = false;
093: this .nextAction = false;
094:
095: if (windowOfAction.getChannelRuntimeData() != null
096: && windowOfAction.getHttpServletRequest() != null)
097: analyzeRequestInformation();
098: }
099:
100: /**
101: * Sets the next portlet mode for the current PortletWindow
102: * @param mode a portlet mode
103: */
104: public void setNextMode(PortletMode mode) {
105: nextMode = mode;
106: }
107:
108: /**
109: * Sets the next window state for the current PortletWindow
110: * @param state a window state
111: */
112: public void setNextState(WindowState state) {
113: nextState = state;
114: }
115:
116: /**
117: * Setting the portlet action parameter for the next request
118: */
119: public void setAction() {
120: nextAction = true;
121: }
122:
123: /**
124: * Adds the render parameters to the portlet URL
125: * @param parameters a <code>Map</code> containing the render parameters
126: */
127: public void setParameters(Map parameters) {
128: if (parameters != null && !parameters.isEmpty())
129: params.putAll(parameters);
130: }
131:
132: /**
133: * Returns true if the current PortletRequest is ActionRequest,
134: * false - otherwise
135: */
136: public boolean isAction() {
137: return isAction;
138: }
139:
140: /**
141: * Clears the render parameters for the current PortletWindow
142: */
143: public void clearParameters() {
144: params.clear();
145: }
146:
147: /**
148: * @see java.lang.Object#toString()
149: */
150: public String toString() {
151: return this .getEncodedParameterString();
152: }
153:
154: /**
155: * Returns a full portlet URL including uP file
156: */
157: public String getActionURL() {
158: final StringBuffer actionUrl = new StringBuffer();
159: final WindowState curState = getState(this .windowOfAction);
160: final HttpServletRequest request = this .windowOfAction
161: .getHttpServletRequest();
162: final ChannelRuntimeData runtimeData = this .windowOfAction
163: .getChannelRuntimeData();
164:
165: //URLs are always absolute
166: actionUrl.append(request.getContextPath());
167: actionUrl.append("/");
168:
169: //Get the appropriate URL base
170: if (PortalContextProviderImpl.EXCLUSIVE.equals(this .nextState)
171: || (this .nextState == null && PortalContextProviderImpl.EXCLUSIVE
172: .equals(curState))) {
173: final String urlBase = runtimeData
174: .getBaseWorkerURL(UPFileSpec.FILE_DOWNLOAD_WORKER);
175: actionUrl.append(urlBase);
176: } else {
177: final String urlBase = runtimeData.getBaseActionURL();
178: actionUrl.append(urlBase);
179: }
180:
181: actionUrl.append("?");
182: actionUrl.append(this .getEncodedParameterString());
183:
184: //Add the anchor to the URL
185: if (ChannelManager.isUseAnchors()) {
186: actionUrl.append("#");
187: actionUrl.append(windowOfAction.getId());
188: }
189:
190: return actionUrl.toString();
191: }
192:
193: /**
194: * Generates the string representation of the request parameters for
195: * the portlet based on it's current state.
196: */
197: private String getEncodedParameterString() {
198: StringBuffer url = new StringBuffer();
199: String windowId = windowOfAction.getId().toString();
200:
201: if (nextAction)
202: url.append(ACTION + "=true&");
203: else
204: url.append(ACTION + "=false&");
205:
206: // Window state
207: if (nextState != null) {
208: if (nextAction) {
209: url.append(UP_WINDOW_STATE + "=" + nextState + "&");
210: } else {
211: final WindowState curState;
212: if (isAction())
213: //Generating a URL during an action means the action is
214: //complete and this is the re-direct. We are actually interested
215: //in the previous WindowState which is the state the last
216: //render call took place in.
217: curState = getPrevState(windowOfAction);
218: else
219: curState = getState(windowOfAction);
220:
221: if (!nextState.equals(curState)) {
222:
223: //Switching to MINIMIZED, Goal is not focused and minimized
224: if (WindowState.MINIMIZED.equals(nextState)) {
225: url.append(UP_TCATTR + "=" + MINIMIZED + "&");
226: url.append(MIN_CHAN_ID + "=" + windowId + "&");
227: url.append(MINIMIZED + "_" + windowId
228: + "_value=true&");
229: }
230: //Switching to NORMAL, Goal is not focused and not minimized
231: else if (WindowState.NORMAL.equals(nextState)) {
232: url.append(UP_ROOT + "=" + ROOT + "&");
233: }
234: //Switching to MAXIMIZED, Goal is focused and not minimized
235: else if (WindowState.MAXIMIZED.equals(nextState)) {
236: url.append(UP_ROOT + "=" + windowId + "&");
237: }
238:
239: //If our last state was minimized un-minimize it
240: if (WindowState.MINIMIZED.equals(curState)
241: && !PortalContextProviderImpl.EXCLUSIVE
242: .equals(nextState)) {
243: url.append(UP_TCATTR + "=" + MINIMIZED + "&");
244: url.append(MIN_CHAN_ID + "=" + windowId + "&");
245: url.append(MINIMIZED + "_" + windowId
246: + "_value=false&");
247: }
248: }
249: }
250: }
251:
252: // Portlet mode
253: if (nextMode != null) {
254:
255: if (nextMode.equals(PortletMode.EDIT))
256: url.append(UP_EDIT_TARGET + "=" + windowId);
257: else if (nextMode.equals(PortletMode.HELP))
258: url.append(UP_HELP_TARGET + "=" + windowId);
259: else if (nextMode.equals(PortletMode.VIEW))
260: url.append(UP_VIEW_TARGET + "=" + windowId);
261:
262: url.append("&");
263: }
264:
265: // Other parameters
266: for (final Iterator entryItr = params.entrySet().iterator(); entryItr
267: .hasNext();) {
268: final Map.Entry entry = (Map.Entry) entryItr.next();
269: String name = (String) entry.getKey();
270:
271: //Deals with parameters that start with the prefix string
272: if (name.startsWith(UP_PARAM_PREFIX)) {
273: name = encodeString(UP_PARAM_PREFIX + name);
274: } else {
275: name = encodeString(name);
276: }
277:
278: final Object value = entry.getValue();
279: final String[] values;
280: if (value instanceof String[])
281: values = (String[]) value;
282: else
283: values = new String[] { value.toString() };
284:
285: for (int i = 0; i < values.length; i++) {
286: url.append(name);
287: url.append("=");
288:
289: if (values[i] != null)
290: url.append(encodeString(values[i]));
291:
292: url.append("&");
293: }
294: }
295:
296: while (url.charAt(url.length() - 1) == '&') {
297: url.deleteCharAt(url.length() - 1);
298: }
299:
300: for (int index = url.indexOf("&&"); index >= 0; index = url
301: .indexOf("&&")) {
302: url.deleteCharAt(index);
303: }
304:
305: return url.toString();
306: }
307:
308: /**
309: * Analyzes the request parameters and sets portlet modes/window states for the current PortletWindow
310: */
311: private void analyzeRequestInformation() {
312: this .params.clear();
313: final String windowId = this .windowOfAction.getId().toString();
314:
315: final ChannelRuntimeData runtimeData = this .windowOfAction
316: .getChannelRuntimeData();
317: for (Iterator i = runtimeData.getParameters().keySet()
318: .iterator(); i.hasNext();) {
319: String paramName = (String) i.next();
320: String[] values = runtimeData.getParameterValues(paramName);
321:
322: if (ACTION.equals(paramName)) {
323: isAction = new Boolean(values[0]).booleanValue();
324: } else if (UP_HELP_TARGET.equals(paramName)
325: && windowId.equals(values[0])) {
326: setMode(windowOfAction, PortletMode.HELP);
327: } else if (UP_EDIT_TARGET.equals(paramName)
328: && windowId.equals(values[0])) {
329: setMode(windowOfAction, PortletMode.EDIT);
330: } else if (UP_VIEW_TARGET.equals(paramName)
331: && windowId.equals(values[0])) {
332: setMode(windowOfAction, PortletMode.VIEW);
333: } else if (UP_ROOT.equals(paramName)) {
334: if (!ROOT.equals(values[0]))
335: setState(windowOfAction, WindowState.MAXIMIZED);
336: else if (getPrevState(windowOfAction).equals(
337: WindowState.MAXIMIZED))
338: setState(windowOfAction, WindowState.NORMAL);
339: } else if (UP_TCATTR.equals(paramName)) {
340: if (MINIMIZED.equals(values[0])) {
341: String state = runtimeData.getParameter(MINIMIZED
342: + "_" + windowId + "_value");
343: if (new Boolean(state).booleanValue())
344: setState(windowOfAction, WindowState.MINIMIZED);
345: else
346: setState(windowOfAction, WindowState.NORMAL);
347: }
348: }
349: }
350: }
351:
352: /**
353: * Generates the UTF-8 URL encoded version of the string, wrapping the
354: * possible UnsupportedEncodingException in a RuntimeException.
355: *
356: * @param text The string to encode.
357: * @return The encoded version of the string.
358: */
359: private String encodeString(String text) {
360: try {
361: return URLEncoder.encode(text, "UTF-8");
362: } catch (UnsupportedEncodingException e) {
363: LOG.error("Error URL encoding string to 'UTF-8'", e);
364: throw new RuntimeException(e);
365: }
366: }
367:
368: /**
369: * Generates the UTF-8 URL decoded version of the string, wrapping the
370: * possible UnsupportedEncodingException in a RuntimeException.
371: *
372: * @param text The string to decode.
373: * @return The decoded version of the string.
374: */
375: private String decodeString(String text) {
376: try {
377: return URLDecoder.decode(text, "UTF-8");
378: } catch (UnsupportedEncodingException e) {
379: LOG.error("Error URL decoding string to 'UTF-8'", e);
380: throw new RuntimeException(e);
381: }
382: }
383:
384: /**
385: * Clears the PorletMode and WindowState information for
386: * the specifid PortletWindow
387: *
388: * @param window The PortletWindow to clear the state information for
389: */
390: public static void clearState(PortletWindow window) {
391: final HttpSession session = getSession(window);
392:
393: if (session != null)
394: session.removeAttribute(getKey(window));
395: }
396:
397: /**
398: * Returns the current portlet mode for the given PortletWindow
399: *
400: * @param window a portlet window
401: * @return a PortletMode instance
402: */
403: public static PortletMode getMode(PortletWindow window) {
404: final PortletWindowStateInfo stateInfo = getStateInfo(window);
405: return stateInfo.getCurrentMode();
406: }
407:
408: /**
409: * Returns the previous portlet mode for the given PortletWindow
410: *
411: * @param window a portlet window
412: * @return a PortletMode instance
413: */
414: public static PortletMode getPrevMode(PortletWindow window) {
415: final PortletWindowStateInfo stateInfo = getStateInfo(window);
416: return stateInfo.getPreviousMode();
417: }
418:
419: /**
420: * Returns the current portlet state for the given PortletWindow
421: *
422: * @param window a portlet window
423: * @return a WindowState instance
424: */
425: public static WindowState getState(PortletWindow window) {
426: final PortletWindowStateInfo stateInfo = getStateInfo(window);
427: return stateInfo.getCurrentState();
428: }
429:
430: /**
431: * Returns the previous portlet state for the given PortletWindow
432: *
433: * @param window a portlet window
434: * @return a WindowState instance
435: */
436: public static WindowState getPrevState(PortletWindow window) {
437: final PortletWindowStateInfo stateInfo = getStateInfo(window);
438: return stateInfo.getPreviousState();
439: }
440:
441: /**
442: * Sets the portlet mode for the given PortletWindow
443: *
444: * @param window a portlet window
445: * @param mode a portlet mode
446: */
447: public static void setMode(PortletWindow window, PortletMode mode) {
448: final PortletWindowStateInfo stateInfo = getStateInfo(window);
449: stateInfo.setCurrentMode(mode);
450: }
451:
452: /**
453: * Sets the window state for the given PortletWindow
454: *
455: * @param window a portlet window
456: * @param state a window state
457: */
458: public static void setState(PortletWindow window, WindowState state) {
459: final PortletWindowStateInfo stateInfo = getStateInfo(window);
460: stateInfo.setCurrentState(state);
461:
462: //Ensure the uPFile for the window is setup appropriately for the window state
463: try {
464: final PortletWindowImpl windowImpl = (PortletWindowImpl) window;
465: final ChannelRuntimeData runtimeData = windowImpl
466: .getChannelRuntimeData();
467:
468: if (!PortalContextProviderImpl.EXCLUSIVE.equals(stateInfo
469: .getCurrentState())) {
470: runtimeData.getUPFile().setMethod(
471: UPFileSpec.RENDER_METHOD);
472: runtimeData.getUPFile().setMethodNodeId(
473: UserInstance.USER_LAYOUT_ROOT_NODE);
474: }
475: } catch (PortalException pe) {
476: LOG
477: .error(
478: "Error setting the uPFileSpec for a non-EXCLUSIVE URL",
479: pe);
480: }
481: }
482:
483: /**
484: * Gets a HttpSession for the specified PortletWindow. Will create one
485: * if needed.
486: *
487: * @param window The PortletWindow to get the HttpSession for.
488: * @return The HttpSession for the PortletWindow.
489: * @throws IllegalStateException If the PortletWindow doesn't have an associated HttpServletRequest object.
490: */
491: private static HttpSession getSession(PortletWindow window) {
492: final PortletWindowImpl windowImpl = (PortletWindowImpl) window;
493: final HttpServletRequest request = windowImpl
494: .getHttpServletRequest();
495:
496: /*
497: * Unwrap the HttpServletRequest!
498: * When this is getting called via the dispatched portlet it gets
499: * a HttpServletRequestWrapper generated by the cross context
500: * dispatch. If you dig deep enough in the HttpServletRequestWrapper
501: * layers you can find the original request which is needed here to
502: * ensure the correct session is returned.
503: */
504: final HttpServletRequest realRequest = getUnwrapedRequest(request);
505:
506: if (realRequest != null) {
507: return realRequest.getSession(true);
508: } else {
509: throw new IllegalStateException(
510: "No HttpServletRequest could be found for PortletWindow.id='"
511: + window.getId() + "'");
512: }
513: }
514:
515: /**
516: * Recursivly unwrapps a HttpServletRequest. Determines if the current object
517: * implements HttpServletRequestWrapper, gets the wrapped request and tries to
518: * un wrap it until a base HttpServletRequest is found.
519: *
520: * @param request The request to unwrap.
521: * @return The unwrapped request.
522: */
523: private static HttpServletRequest getUnwrapedRequest(
524: HttpServletRequest request) {
525: if (request instanceof HttpServletRequestWrapper) {
526: final HttpServletRequestWrapper wrapper = (HttpServletRequestWrapper) request;
527: final ServletRequest wrappedRequest = wrapper.getRequest();
528:
529: if (request != wrappedRequest
530: && wrappedRequest instanceof HttpServletRequest)
531: return getUnwrapedRequest((HttpServletRequest) wrappedRequest);
532: else
533: return wrapper;
534: } else {
535: return request;
536: }
537: }
538:
539: /**
540: * Gets PortletWindowStateInfo for the PortletWindow, creates one if
541: * nessesary.
542: *
543: * @param window The PortletWindow to get the PortletWindowStateInfo for.
544: * @return The PortletWindowStateInfo for the PortletWindow.
545: */
546: private static PortletWindowStateInfo getStateInfo(
547: PortletWindow window) {
548: final HttpSession session = getSession(window);
549: final String stateKey = getKey(window);
550:
551: synchronized (session) {
552: PortletWindowStateInfo stateInfo = (PortletWindowStateInfo) session
553: .getAttribute(stateKey);
554:
555: if (stateInfo == null) {
556: stateInfo = new PortletWindowStateInfo();
557: session.setAttribute(stateKey, stateInfo);
558: }
559:
560: return stateInfo;
561: }
562: }
563:
564: /**
565: * Generates a key from the PortletWindow id.
566: *
567: * @param window The PortletWindow to generate the key from.
568: * @return The key for the PortletWindow.
569: */
570: private static String getKey(PortletWindow window) {
571: final StringBuffer keyBuf = new StringBuffer();
572:
573: keyBuf.append(PortletWindowStateInfo.class.getName());
574: keyBuf.append("_");
575: keyBuf.append(window.getId());
576:
577: return keyBuf.toString();
578: }
579: }
|