001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.jetspeed.container.state.impl;
018:
019: import java.io.UnsupportedEncodingException;
020: import java.util.ArrayList;
021: import java.util.Iterator;
022: import java.util.Map;
023:
024: import javax.portlet.PortletMode;
025: import javax.portlet.WindowState;
026:
027: import org.apache.commons.codec.binary.Base64;
028: import org.apache.commons.logging.Log;
029: import org.apache.commons.logging.LogFactory;
030: import org.apache.jetspeed.JetspeedActions;
031: import org.apache.jetspeed.PortalContext;
032: import org.apache.jetspeed.PortalReservedParameters;
033: import org.apache.jetspeed.container.window.PortletWindowAccessor;
034: import org.apache.pluto.om.window.PortletWindow;
035:
036: /**
037: * JetspeedNavigationalStateCodec
038: *
039: * @author <a href="mailto:ate@apache.org">Ate Douma</a>
040: * @version $Id: JetspeedNavigationalStateCodec.java 554926 2007-07-10 13:12:26Z ate $
041: */
042: public class JetspeedNavigationalStateCodec implements
043: NavigationalStateCodec {
044: /** Commons logging */
045: protected final static Log log = LogFactory
046: .getLog(JetspeedNavigationalStateCodec.class);
047:
048: protected static final char PARAMETER_SEPARATOR = '|';
049: protected static final char PARAMETER_ELEMENT_SEPARATOR = '=';
050: protected static final char RENDER_WINDOW_ID_KEY = 'a';
051: protected static final char ACTION_WINDOW_ID_KEY = 'b';
052: protected static final char MODE_KEY = 'c';
053: protected static final char STATE_KEY = 'd';
054: protected static final char PARAM_KEY = 'e';
055: protected static final char CLEAR_PARAMS_KEY = 'f';
056: protected static final char RESOURCE_WINDOW_ID_KEY = 'g';
057:
058: protected static final String keytable = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
059: protected final PortletMode[] portletModes;
060: protected final WindowState[] windowStates;
061: private final PortletWindowAccessor windowAccessor;
062:
063: public JetspeedNavigationalStateCodec(PortalContext portalContext,
064: PortletWindowAccessor windowAccessor) {
065: ArrayList list = new ArrayList();
066: this .windowAccessor = windowAccessor;
067:
068: // ensure standard modes will be first in the portletModeNames array
069: // this ensures those modes are never lost from a bookmarked url when new modes are added somewhere in the
070: // middle
071: list.addAll(JetspeedActions.getStandardPortletModes());
072: list.addAll(JetspeedActions.getExtendedPortletModes());
073:
074: portletModes = (PortletMode[]) list
075: .toArray(new PortletMode[list.size()]);
076: if (portletModes.length > keytable.length()) {
077: throw new UnsupportedOperationException(
078: "Too many supported PortletModes found. Can only handle max: "
079: + keytable.length());
080: }
081:
082: list.clear();
083:
084: // ensure standard states will be first in the windowStateNames array
085: // this ensures those states are never lost from a bookmarked url when new states are added somewhere in the
086: // middle
087: list.addAll(JetspeedActions.getStandardWindowStates());
088: list.addAll(JetspeedActions.getExtendedWindowStates());
089:
090: windowStates = (WindowState[]) list
091: .toArray(new WindowState[list.size()]);
092: if (windowStates.length > keytable.length()) {
093: throw new UnsupportedOperationException(
094: "Too many supported WindowModes found. Can only handle max: "
095: + keytable.length());
096: }
097: }
098:
099: public PortletWindowRequestNavigationalStates decode(
100: String parameters, String characterEncoding)
101: throws UnsupportedEncodingException {
102: PortletWindowRequestNavigationalStates states = new PortletWindowRequestNavigationalStates(
103: characterEncoding);
104: if (parameters != null && parameters.length() > 0) {
105: String decodedParameters = decodeParameters(parameters,
106: characterEncoding);
107:
108: int position = 0;
109: StringBuffer buffer = new StringBuffer();
110:
111: PortletWindowRequestNavigationalState currentState = null;
112: String parameter;
113: while ((position = decodeArgument(position,
114: decodedParameters, buffer, PARAMETER_SEPARATOR)) != -1) {
115: parameter = buffer.toString();
116: currentState = decodeParameter(windowAccessor, states,
117: currentState, parameter);
118: }
119:
120: if (log.isDebugEnabled()) {
121: logDecode(states, buffer);
122: if (buffer.length() > 0) {
123: buffer.append("]");
124: log.debug("navstate decoded=" + buffer.toString());
125: }
126: }
127: }
128: return states;
129: }
130:
131: private void logDecode(
132: PortletWindowRequestNavigationalStates states,
133: StringBuffer buffer) {
134: PortletWindowRequestNavigationalState currentState;
135: buffer.setLength(0);
136: String actionWindowId = states.getActionWindow() != null ? states
137: .getActionWindow().getId().toString()
138: : "";
139: Iterator iter = states.getWindowIdIterator();
140: while (iter.hasNext()) {
141: if (buffer.length() == 0) {
142: buffer.append("[[");
143: } else {
144: buffer.append(",[");
145: }
146: currentState = states
147: .getPortletWindowNavigationalState((String) iter
148: .next());
149: buffer.append("window:" + currentState.getWindowId());
150:
151: if (currentState.getWindowId().equals(actionWindowId)) {
152: buffer.append(",action:true");
153: }
154: if (currentState.getPortletMode() != null) {
155: buffer.append(",mode:" + currentState.getPortletMode());
156: }
157: if (currentState.getWindowState() != null) {
158: buffer
159: .append(",state:"
160: + currentState.getWindowState());
161: }
162: if (!currentState.isClearParameters()) {
163: if (currentState.getParametersMap() != null) {
164: buffer.append(",parameters:[");
165: boolean first = true;
166: Iterator parIter = currentState.getParametersMap()
167: .keySet().iterator();
168: while (parIter.hasNext()) {
169: if (first) {
170: first = false;
171: } else {
172: buffer.append(",");
173: }
174: String name = (String) parIter.next();
175: buffer.append(name + ":[");
176: String[] values = (String[]) currentState
177: .getParametersMap().get(name);
178: for (int i = 0; i < values.length; i++) {
179: if (i > 0) {
180: buffer.append(",");
181: }
182: buffer.append(values[i]);
183: }
184: buffer.append("]");
185: }
186: }
187: }
188: buffer.append("]");
189: }
190: }
191:
192: public String encode(PortletWindowRequestNavigationalStates states,
193: PortletWindow window, PortletMode portletMode,
194: WindowState windowState, boolean navParamsStateFull,
195: boolean renderParamsStateFull)
196: throws UnsupportedEncodingException {
197: String windowId = window.getId().toString();
198: PortletWindowRequestNavigationalState currentState = states
199: .getPortletWindowNavigationalState(windowId);
200: PortletWindowRequestNavigationalState targetState = new PortletWindowRequestNavigationalState(
201: windowId);
202: targetState.setPortletMode(portletMode != null ? portletMode
203: : currentState != null ? currentState.getPortletMode()
204: : null);
205: targetState.setWindowState(windowState != null ? windowState
206: : currentState != null ? currentState.getWindowState()
207: : null);
208:
209: // never retain actionRequest parameters nor session stored renderParameters
210: if (currentState != null && !renderParamsStateFull) {
211: // retain current request parameters if any
212: if (currentState.getParametersMap() != null) {
213: Iterator parametersIter = currentState
214: .getParametersMap().entrySet().iterator();
215: Map.Entry entry;
216: while (parametersIter.hasNext()) {
217: entry = (Map.Entry) parametersIter.next();
218: targetState.setParameters((String) entry.getKey(),
219: (String[]) entry.getValue());
220: }
221: }
222: }
223: // encode as requestURL parameters
224: return encode(states, windowId, targetState, false, false,
225: navParamsStateFull, renderParamsStateFull);
226: }
227:
228: public String encode(PortletWindowRequestNavigationalStates states,
229: PortletWindow window, Map parameters,
230: PortletMode portletMode, WindowState windowState,
231: boolean action, boolean navParamsStateFull,
232: boolean renderParamsStateFull)
233: throws UnsupportedEncodingException {
234: String windowId = window.getId().toString();
235: PortletWindowRequestNavigationalState currentState = states
236: .getPortletWindowNavigationalState(windowId);
237: PortletWindowRequestNavigationalState targetState = new PortletWindowRequestNavigationalState(
238: windowId);
239: targetState.setPortletMode(portletMode != null ? portletMode
240: : currentState != null ? currentState.getPortletMode()
241: : null);
242: targetState.setWindowState(windowState != null ? windowState
243: : currentState != null ? currentState.getWindowState()
244: : null);
245:
246: Iterator parametersIter = parameters.entrySet().iterator();
247:
248: boolean resource = false;
249:
250: Map.Entry entry;
251: String parameter;
252: // fill in the new parameters
253: while (parametersIter.hasNext()) {
254: entry = (Map.Entry) parametersIter.next();
255: parameter = (String) entry.getKey();
256: if (!resource
257: && !action
258: && PortalReservedParameters.PORTLET_RESOURCE_URL_REQUEST_PARAMETER
259: .equals(parameter)) {
260: resource = true;
261: navParamsStateFull = true;
262: renderParamsStateFull = true;
263: } else {
264: targetState.setParameters(parameter, (String[]) entry
265: .getValue());
266: }
267: }
268: if (renderParamsStateFull
269: && targetState.getParametersMap() == null) {
270: // Indicate that the saved (in the session) render parameters for this PortletWindow must be cleared
271: // and not copied when synchronizing the state (encoded as CLEAR_PARAMS_KEY)
272: targetState.setClearParameters(true);
273: }
274: return encode(states, windowId, targetState, action, resource,
275: navParamsStateFull, renderParamsStateFull);
276: }
277:
278: public String encode(PortletWindowRequestNavigationalStates states,
279: boolean navParamsStateFull, boolean renderParamsStateFull)
280: throws UnsupportedEncodingException {
281: return encode(states, null, null, false, false,
282: navParamsStateFull, renderParamsStateFull);
283: }
284:
285: protected String encode(
286: PortletWindowRequestNavigationalStates states,
287: String targetWindowId,
288: PortletWindowRequestNavigationalState targetState,
289: boolean action, boolean resource,
290: boolean navParamsStateFull, boolean renderParamsStateFull)
291: throws UnsupportedEncodingException {
292: StringBuffer buffer = new StringBuffer();
293: String encodedState;
294: boolean haveState = false;
295:
296: // skip other states if all non-targeted PortletWindow states are kept in the session
297: if (!navParamsStateFull || !renderParamsStateFull) {
298: PortletWindowRequestNavigationalState pwfns;
299: String windowId;
300: Iterator iter = states.getWindowIdIterator();
301: while (iter.hasNext()) {
302: windowId = (String) iter.next();
303: pwfns = states
304: .getPortletWindowNavigationalState(windowId);
305: if (targetWindowId != null
306: && windowId.equals(targetWindowId)) {
307: // skip it for now, it will be encoded as the last one below
308: } else {
309: encodedState = encodePortletWindowNavigationalState(
310: windowId, pwfns, false, false,
311: navParamsStateFull, renderParamsStateFull);
312: if (encodedState.length() > 0) {
313: if (!haveState) {
314: haveState = true;
315: } else {
316: buffer.append(PARAMETER_SEPARATOR);
317: }
318: buffer.append(encodedState);
319: }
320: }
321: }
322: }
323: if (targetWindowId != null) {
324: encodedState = encodePortletWindowNavigationalState(
325: targetWindowId, targetState, action, resource,
326: false, false);
327: if (encodedState.length() > 0) {
328: if (!haveState) {
329: haveState = true;
330: } else {
331: buffer.append(PARAMETER_SEPARATOR);
332: }
333: buffer.append(encodedState);
334: }
335: }
336: String encodedNavState = null;
337: if (haveState) {
338: encodedNavState = encodeParameters(buffer.toString(),
339: states.getCharacterEncoding());
340: }
341: return encodedNavState;
342: }
343:
344: protected String encodePortletWindowNavigationalState(
345: String windowId,
346: PortletWindowRequestNavigationalState state,
347: boolean action, boolean resource,
348: boolean navParamsStateFull, boolean renderParamsStateFull) {
349: StringBuffer buffer = new StringBuffer();
350: buffer.append(action ? ACTION_WINDOW_ID_KEY
351: : resource ? RESOURCE_WINDOW_ID_KEY
352: : RENDER_WINDOW_ID_KEY);
353: buffer.append(windowId);
354: boolean encoded = action || resource;
355:
356: if (action || !navParamsStateFull) {
357: if (state.getPortletMode() != null) {
358: buffer.append(PARAMETER_SEPARATOR);
359: buffer.append(MODE_KEY);
360: buffer
361: .append(encodePortletMode(state
362: .getPortletMode()));
363: encoded = true;
364: }
365:
366: if (state.getWindowState() != null) {
367: buffer.append(PARAMETER_SEPARATOR);
368: buffer.append(STATE_KEY);
369: buffer
370: .append(encodeWindowState(state
371: .getWindowState()));
372: encoded = true;
373: }
374: }
375:
376: if (state.getParametersMap() != null
377: && (action || !renderParamsStateFull)) {
378: Map.Entry entry;
379: String parameterName;
380: String[] parameterValues;
381:
382: StringBuffer paramBuffer = new StringBuffer();
383: Iterator iter = state.getParametersMap().entrySet()
384: .iterator();
385: while (iter.hasNext()) {
386: encoded = true;
387: entry = (Map.Entry) iter.next();
388: parameterName = (String) entry.getKey();
389: parameterValues = (String[]) entry.getValue();
390:
391: buffer.append(PARAMETER_SEPARATOR);
392: buffer.append(PARAM_KEY);
393:
394: paramBuffer.setLength(0);
395: paramBuffer.append(encodeArgument(parameterName,
396: PARAMETER_ELEMENT_SEPARATOR));
397: paramBuffer.append(PARAMETER_ELEMENT_SEPARATOR);
398: paramBuffer.append(Integer
399: .toHexString(parameterValues.length));
400: for (int i = 0; i < parameterValues.length; i++) {
401: paramBuffer.append(PARAMETER_ELEMENT_SEPARATOR);
402: paramBuffer.append(encodeArgument(
403: parameterValues[i],
404: PARAMETER_ELEMENT_SEPARATOR));
405: }
406:
407: buffer.append(encodeArgument(paramBuffer.toString(),
408: PARAMETER_SEPARATOR));
409: }
410: } else if (state.isClearParameters()) {
411: // Special case: for a targeted PortletWindow for which no parameters are specified
412: // indicate its saved (in the session) request parameters must be cleared instead of copying them when
413: // synchronizing the state.
414: // During decoding this CLEAR_PARAMS_KEY will set the clearParameters flag of the PortletWindowRequestNavigationalState.
415: buffer.append(PARAMETER_SEPARATOR);
416: buffer.append(CLEAR_PARAMS_KEY);
417: encoded = true;
418: }
419: return encoded ? buffer.toString() : "";
420: }
421:
422: protected PortletWindowRequestNavigationalState decodeParameter(
423: PortletWindowAccessor accessor,
424: PortletWindowRequestNavigationalStates states,
425: PortletWindowRequestNavigationalState currentState,
426: String parameter) {
427: char parameterType = parameter.charAt(0);
428: if (parameterType == RENDER_WINDOW_ID_KEY
429: || parameterType == ACTION_WINDOW_ID_KEY
430: || parameterType == RESOURCE_WINDOW_ID_KEY) {
431: String windowId = parameter.substring(1);
432: currentState = states
433: .getPortletWindowNavigationalState(windowId);
434: if (currentState == null) {
435: PortletWindow window = accessor
436: .getPortletWindow(windowId);
437: if (window == null) {
438: window = accessor.createPortletWindow(windowId);
439: }
440: currentState = new PortletWindowRequestNavigationalState(
441: windowId);
442: states.addPortletWindowNavigationalState(windowId,
443: currentState);
444: if (parameterType == ACTION_WINDOW_ID_KEY) {
445: states.setActionWindow(window);
446: } else if (parameterType == RESOURCE_WINDOW_ID_KEY) {
447: states.setResourceWindow(window);
448: }
449: }
450: } else if (currentState != null) {
451: switch (parameterType) {
452: case MODE_KEY: {
453: PortletMode portletMode = decodePortletMode(parameter
454: .charAt(1));
455: if (portletMode != null) {
456: currentState.setPortletMode(portletMode);
457: }
458: break;
459: }
460: case STATE_KEY: {
461: WindowState windowState = decodeWindowState(parameter
462: .charAt(1));
463: if (windowState != null) {
464: currentState.setWindowState(windowState);
465: if (windowState.equals(WindowState.MAXIMIZED)
466: || windowState
467: .equals(JetspeedActions.SOLO_STATE)) {
468: PortletWindow window = accessor
469: .getPortletWindow(currentState
470: .getWindowId());
471: if (window == null) {
472: window = accessor
473: .createPortletWindow(currentState
474: .getWindowId());
475: }
476: states.setMaximizedWindow(window);
477: }
478: }
479: break;
480: }
481: case PARAM_KEY: {
482: int position = 1;
483: StringBuffer buffer = new StringBuffer();
484: String parameterName = null;
485: int parameterValueCount = -1;
486: String parameterValues[] = null;
487: int parameterValueIndex = -1;
488: while ((position = decodeArgument(position, parameter,
489: buffer, PARAMETER_ELEMENT_SEPARATOR)) != -1) {
490: if (parameterName == null) {
491: parameterName = buffer.toString();
492: parameterValueCount = -1;
493: } else if (parameterValueCount == -1) {
494: parameterValueCount = Integer.parseInt(buffer
495: .toString(), 16);
496: parameterValues = new String[parameterValueCount];
497: parameterValueIndex = 0;
498: } else {
499: parameterValues[parameterValueIndex++] = buffer
500: .toString();
501: parameterValueCount--;
502: if (parameterValueCount == 0) {
503: currentState.setParameters(parameterName,
504: parameterValues);
505: break;
506: }
507: }
508: }
509: break;
510: }
511: case CLEAR_PARAMS_KEY: {
512: currentState.setClearParameters(true);
513: }
514: }
515: }
516: return currentState;
517:
518: }
519:
520: protected PortletMode decodePortletMode(char mode) {
521: PortletMode portletMode = null;
522: int index = keytable.indexOf(mode);
523: if (index > -1 && index < portletModes.length) {
524: portletMode = portletModes[index];
525: }
526: return portletMode;
527: }
528:
529: protected char encodePortletMode(PortletMode portletMode) {
530: for (int i = 0; i < portletModes.length; i++) {
531: if (portletModes[i].equals(portletMode))
532: return keytable.charAt(i);
533: }
534: throw new IllegalArgumentException("Unsupported PortletMode: "
535: + portletMode);
536: }
537:
538: protected WindowState decodeWindowState(char state) {
539: WindowState windowState = null;
540: int index = keytable.indexOf(state);
541: if (index > -1 && index < windowStates.length) {
542: windowState = windowStates[index];
543: }
544: return windowState;
545: }
546:
547: protected char encodeWindowState(WindowState windowState) {
548: for (int i = 0; i < windowStates.length; i++) {
549: if (windowStates[i].equals(windowState))
550: return keytable.charAt(i);
551: }
552: throw new IllegalArgumentException("Unsupported WindowState: "
553: + windowState);
554: }
555:
556: /**
557: * Decodes a Base64 encoded string.
558: *
559: * Because the encoded string is used in an URL
560: * the two '/' and '=' which has some significance in an URL
561: * are encoded on top of the Base64 encoding and are first translated back before decoding.
562: *
563: * @param value
564: * @param characterEncoding String containing the name of the chararacter encoding
565: * @return decoded string
566: */
567: protected String decodeParameters(String value,
568: String characterEncoding)
569: throws UnsupportedEncodingException {
570: value = value.replace('-', '/').replace('_', '=');
571: if (characterEncoding != null) {
572: return new String(Base64.decodeBase64(value
573: .getBytes(characterEncoding)), characterEncoding);
574: } else {
575: return new String(Base64.decodeBase64(value.getBytes()));
576: }
577: }
578:
579: /**
580: * Encodes a string with Base64.
581: *
582: * Because the encoded string is used in an URL
583: * the two '/' and '=' which has some significance in an URL
584: * are encoded on top of/after the Base64 encoding
585: *
586: * @param value
587: * @return encoded string
588: */
589: protected String encodeParameters(String value,
590: String characterEncoding)
591: throws UnsupportedEncodingException {
592: if (characterEncoding != null) {
593: value = new String(Base64.encodeBase64(value
594: .getBytes(characterEncoding)));
595: } else {
596: value = new String(Base64.encodeBase64(value.getBytes()));
597: }
598: return value.replace('/', '-').replace('=', '_');
599: }
600:
601: protected String encodeArgument(String argument, char escape) {
602: int length = argument.length();
603: StringBuffer buffer = new StringBuffer(length);
604: buffer.setLength(0);
605: char c;
606: for (int i = 0; i < length; i++) {
607: c = argument.charAt(i);
608: buffer.append(c);
609: if (c == escape) {
610: buffer.append(c);
611: }
612: }
613: return buffer.toString();
614: }
615:
616: protected int decodeArgument(int position, String arguments,
617: StringBuffer buffer, char escape) {
618: int maxLength = arguments.length();
619: buffer.setLength(0);
620: char c;
621: for (; position < maxLength; position++) {
622: c = arguments.charAt(position);
623: if (c != escape) {
624: buffer.append(c);
625: } else {
626: if (c == escape && position < maxLength - 1
627: && arguments.charAt(position + 1) == escape) {
628: buffer.append(c);
629: position++;
630: } else {
631: position++;
632: break;
633: }
634: }
635: }
636: return buffer.length() > 0 ? position : -1;
637: }
638: }
|