View Javadoc

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