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.pluto.driver.url.impl;
18  
19  import java.io.UnsupportedEncodingException;
20  import java.net.URLDecoder;
21  import java.net.URLEncoder;
22  import java.util.Enumeration;
23  import java.util.Iterator;
24  import java.util.Map;
25  import java.util.StringTokenizer;
26  
27  import javax.portlet.PortletMode;
28  import javax.portlet.WindowState;
29  import javax.servlet.http.HttpServletRequest;
30  
31  import org.slf4j.Logger;
32  import org.slf4j.LoggerFactory;
33  import org.apache.pluto.driver.url.PortalURL;
34  import org.apache.pluto.driver.url.PortalURLParameter;
35  import org.apache.pluto.driver.url.PortalURLParser;
36  
37  /**
38   * @version 1.0
39   * @since Sep 30, 2004
40   */
41  public class PortalURLParserImpl implements PortalURLParser {
42  
43  	/** Logger. */
44      private static final Logger LOG = LoggerFactory.getLogger(PortalURLParserImpl.class);
45  
46      /** The singleton parser instance. */
47      private static final PortalURLParser PARSER = new PortalURLParserImpl();
48  
49  
50      // Constants used for Encoding/Decoding ------------------------------------
51  
52      private static final String PREFIX = "__";
53      private static final String DELIM = "_";
54      private static final String PORTLET_ID = "pd";
55      private static final String ACTION = "ac";
56      private static final String RESOURCE = "rs";
57      private static final String RESOURCE_ID = "ri";
58      private static final String CACHE_LEVEL = "cl";
59      private static final String RENDER_PARAM = "rp";
60      private static final String PRIVATE_RENDER_PARAM = "pr";
61      private static final String PUBLIC_RENDER_PARAM = "sp";
62      private static final String WINDOW_STATE = "ws";
63      private static final String PORTLET_MODE = "pm";
64      private static final String VALUE_DELIM = "0x0";
65  
66      //This is a list of characters that need to be encoded  to be protected
67      //The ? is necessary to protect URI's with a query portion that is being passed as a parameter
68      private static final String[][] ENCODINGS = new String[][] {
69      		new String[] { "_",  "_" },
70              new String[] { ".",  "." },
71              new String[] { "/",  "0x3" },
72              new String[] { "\r", "0x4" },
73              new String[] { "\n", "0x5" },
74              new String[] { "<",  "0x6" },
75              new String[] { ">",  "0x7" },
76              new String[] { " ",  "0x8" },
77              new String[] { "#",  "0x9" },
78              new String[] { "?",  "0xa" },
79              new String[] { "\\", "0xb" },
80              new String[] { "%",  "0xc" },
81      };
82  
83      // Constructor -------------------------------------------------------------
84  
85      /**
86       * Private constructor that prevents external instantiation.
87       */
88      private PortalURLParserImpl() {
89      	// Do nothing.
90      }
91  
92      /**
93       * Returns the singleton parser instance.
94       * @return the singleton parser instance.
95       */
96      public static PortalURLParser getParser() {
97      	return PARSER;
98      }
99  
100 
101     // Public Methods ----------------------------------------------------------
102 
103     /**
104      * Parse a servlet request to a portal URL.
105      * @param request  the servlet request to parse.
106      * @return the portal URL.
107      */
108     public PortalURL parse(HttpServletRequest request) {
109 
110     	if (LOG.isDebugEnabled()) {
111             LOG.debug("Parsing URL: " + request.getRequestURI());
112         }
113 
114         String contextPath = request.getContextPath();
115         String servletName = request.getServletPath();
116 
117         String urlBase = request.getScheme()+"://" + request.getServerName() + ":" + request.getServerPort();
118         // Construct portal URL using info retrieved from servlet request.
119         PortalURL portalURL =  new RelativePortalURLImpl(urlBase, contextPath, servletName, this);
120 
121         // Support added for filter.  Should we seperate into a different impl?
122         String pathInfo = request.getPathInfo();
123        /* if (pathInfo == null) {
124             if(servletName.contains(".jsp") && !servletName.endsWith(".jsp")) {
125                 int idx = servletName.indexOf(".jsp")+".jsp".length();
126                 pathInfo = servletName.substring(idx);
127                 servletName = servletName.substring(0, idx);
128                 portalURL = new RelativePortalURLImpl(urlBase, contextPath, servletName, this);
129             } else {
130                 return portalURL;
131             }
132         }*/
133 
134         if (LOG.isDebugEnabled()) {
135             LOG.debug("Parsing request pathInfo: " + pathInfo);
136         }
137 
138         StringBuffer renderPath = new StringBuffer();
139         /*StringTokenizer st = new StringTokenizer(pathInfo, "/", false);
140         while (st.hasMoreTokens()) {
141 
142         	String token = st.nextToken();
143 
144         	// Part of the render path: append to renderPath.
145         	if (!token.startsWith(PREFIX)) {
146 //        		renderPath.append(token);
147         		//Fix for PLUTO-243
148         		renderPath.append('/').append(token);
149         	}
150 //        	 Resource window definition: portalURL.setResourceWindow().
151            else if (token.startsWith(PREFIX + RESOURCE)) {
152                portalURL.setResourceWindow(decodeControlParameter(token)[0]);
153            }
154         	// Action window definition: portalURL.setActionWindow().
155         	else if (token.startsWith(PREFIX + ACTION)) {
156         		portalURL.setActionWindow(decodeControlParameter(token)[0]);
157         	}
158             // Cacheability definition: portalURL.setCacheability().
159             else if (token.startsWith(PREFIX + CACHE_LEVEL)) {
160                 portalURL.setCacheability(decodeControlParameter(token)[0]);
161             }
162             // ResourceID definition: portalURL.setResourceID().
163             else if (token.startsWith(PREFIX + RESOURCE_ID)) {
164                 portalURL.setResourceID(decodeControlParameter(token)[0]);
165             }
166         	// Window state definition: portalURL.setWindowState().
167         	else if (token.startsWith(PREFIX + WINDOW_STATE)) {
168         		String[] decoded = decodeControlParameter(token);
169         		portalURL.setWindowState(decoded[0], new WindowState(decoded[1]));
170         	}
171         	// Portlet mode definition: portalURL.setPortletMode().
172         	else if (token.startsWith(PREFIX + PORTLET_MODE)) {
173         		String[] decoded = decodeControlParameter(token);
174         		portalURL.setPortletMode(decoded[0], new PortletMode(decoded[1]));
175         	}
176         	// Portal URL parameter: portalURL.addParameter().
177         	else if(token.startsWith(PREFIX + RENDER_PARAM)) {
178         		String value = null;
179         		if (st.hasMoreTokens()) {
180         			value = st.nextToken();
181         		}
182         		//set the
183         		PortalURLParameter param = decodeParameter(token, value);
184         		portalURL.addParameter(param);
185 
186 
187         	}
188             else if (token.startsWith(PREFIX + PRIVATE_RENDER_PARAM)){
189                 String value = null;
190                 if (st.hasMoreTokens()) {
191                     value = st.nextToken();
192                 }
193                 PortalURLParameter param = decodePublicParameter(token, value);
194                 if( param != null )
195                 {
196                     //set private (Resource) parameter in portalURL
197                     portalURL.getPrivateRenderParameters().put(param.getName(), param.getValues());
198                 }
199             }
200         	else if (token.startsWith(PREFIX + PUBLIC_RENDER_PARAM)){
201         		String value = null;
202         		if (st.hasMoreTokens()) {
203         			value = st.nextToken();
204         		}
205         		PortalURLParameter param = decodePublicParameter(token, value);
206         		if( param != null )
207         		{
208         			//set public parameter in portalURL
209     	    		portalURL.addPublicParameterCurrent(param.getName(), param.getValues());
210         		}
211         	}
212         }*/       
213         Enumeration<String> paramNames = request.getParameterNames();
214         while(paramNames.hasMoreElements())
215         {        	  
216         	String paramName = paramNames.nextElement();
217         	if(paramName.startsWith("page_id")) {
218         		
219         		//PortalURLParameter param = new PortalURLParameter(window, paramName,request.getParameterValues(paramName));
220         		//portalURL.addParameter(param);
221         		portalURL.getPrivateRenderParameters().put(paramName, request.getParameterValues(paramName));	       
222         	}
223         	else
224     		{
225     			//portalURL.getPrivateRenderParameters().put(paramName, request.getParameterValues(paramName));	        	
226     		}
227         }
228         
229        
230         if (renderPath.length() > 0) {
231             portalURL.setRenderPath(renderPath.toString());
232         }
233 
234         // Return the portal URL.
235         return portalURL;
236     }
237 
238 
239     /**
240      * Converts a portal URL to a URL string.
241      * @param portalURL  the portal URL to convert.
242      * @return a URL string representing the portal URL.
243      */
244     public String toString(PortalURL portalURL) {
245 
246     	StringBuffer buffer = null;
247     	
248         // Start the pathInfo with the path to the render URL (page).
249         if (portalURL.getRenderPath() != null) {
250         	if(buffer == null)
251         	{
252         		buffer = new StringBuffer();
253         		 // Append the server URI and the servlet path.
254             	buffer.append(portalURL.getServletPath().startsWith("/")?"":"/")
255                     .append(portalURL.getServletPath());
256         	}
257         	buffer.append(portalURL.getRenderPath());
258         }
259         //Append the resource window definition, if it exists.
260        /* if (portalURL.getResourceWindow() != null){
261         	if(buffer == null)
262         	{
263         		buffer = new StringBuffer();
264         		 // Append the server URI and the servlet path.
265             	buffer.append(portalURL.getServletPath().startsWith("/")?"":"/")
266                     .append(portalURL.getServletPath());
267         	}
268                buffer.append("/");
269                buffer.append(PREFIX).append(RESOURCE)
270                                .append(encodeCharacters(portalURL.getResourceWindow()));
271         }*/
272         // Append the action window definition, if it exists.
273         boolean firstParam = true;
274         if (portalURL.getActionWindow() != null) {
275         	buffer = new StringBuffer();
276         	String strContextPath = portalURL.getServletPath();        
277         	strContextPath = strContextPath.substring(0, strContextPath.lastIndexOf("/"));
278         	strContextPath = strContextPath.substring(0, strContextPath.lastIndexOf("/"));
279         	buffer.append(strContextPath);
280         	buffer.append("/");
281         	buffer.append("DoAction.jsp?");
282         	buffer.append(ACTION).append("=")
283         			.append("true");
284         	firstParam = false;
285         }
286         
287         if (portalURL.getResourceWindow() != null)
288         {
289         	if(buffer == null)
290         	{
291         		buffer = new StringBuffer();
292             	String strContextPath = portalURL.getServletPath();        
293             	strContextPath = strContextPath.substring(0, strContextPath.lastIndexOf("/"));
294             	strContextPath = strContextPath.substring(0, strContextPath.lastIndexOf("/"));
295             	buffer.append(strContextPath);
296             	buffer.append("/");
297             	buffer.append("DoServeResource.jsp?");
298         	}            
299         }
300        
301 
302         if(buffer == null)
303     	{    		
304     		buffer = new StringBuffer();
305         	String strContextPath = portalURL.getServletPath(); 
306         	if(strContextPath.substring(strContextPath.lastIndexOf("/")).equals("/DoAction.jsp"))
307         	{
308         		strContextPath = strContextPath.substring(0, strContextPath.lastIndexOf("/"));         	
309             	buffer.append(strContextPath);
310             	buffer.append("/site/");
311             	buffer.append("Portal.jsp?");
312         	}
313         	else
314         	{
315         		strContextPath = strContextPath.substring(0, strContextPath.lastIndexOf("/")); 
316         		strContextPath = strContextPath.substring(0, strContextPath.lastIndexOf("/")); 
317         		buffer.append(strContextPath);
318         		buffer.append("/");
319             	buffer.append("DoAction.jsp?");
320         	}  
321     	}
322         // Append portlet mode definitions.
323         for (Iterator it = portalURL.getPortletModes().entrySet().iterator();
324         		it.hasNext(); ) {
325         	/*if(buffer == null)
326          	{    		
327          		buffer = new StringBuffer();
328              	String strContextPath = portalURL.getServletPath();        
329              	strContextPath = strContextPath.substring(0, strContextPath.lastIndexOf("/"));     
330              	strContextPath = strContextPath.substring(0, strContextPath.lastIndexOf("/")); 
331              	buffer.append(strContextPath);             	
332              	buffer.append("/");
333              	buffer.append("DoAction.jsp?");
334          	}*/
335             Map.Entry entry = (Map.Entry) it.next();
336             if ( firstParam ) {
337                 firstParam = false;
338             } else {
339             	buffer.append("&");
340             }
341             buffer.append(
342             		encodeControlParameter(PORTLET_MODE, entry.getKey().toString(),
343                        entry.getValue().toString()));
344         }
345 
346         // Append window state definitions.
347         for (Iterator it = portalURL.getWindowStates().entrySet().iterator();
348         		it.hasNext(); ) {
349         	
350             Map.Entry entry = (Map.Entry) it.next();
351             if ( firstParam ) {
352                 firstParam = false;
353             } else {
354             	buffer.append("&");
355             }
356             buffer.append(
357             		encodeControlParameter(WINDOW_STATE, entry.getKey().toString(),
358                        entry.getValue().toString()));
359         }
360         
361        
362 
363         // Append action and render parameters.
364         StringBuffer query = new StringBuffer();
365         
366         //add the lutece portlet id 
367         String strId = ((RelativePortalURLImpl)portalURL).getLutecePortletId();
368         if(strId != null)
369         {     
370         	if ( firstParam ) {
371 	            firstParam = false;
372 	        } else {
373 	        	buffer.append("&");
374 	        }
375         	buffer.append("rp_pid").append("=").append(strId);
376         }
377         
378         for (Iterator it = portalURL.getParameters().iterator();
379         		it.hasNext(); ) {
380 
381         	PortalURLParameter param = (PortalURLParameter) it.next();
382 
383             // Encode action params in the query appended at the end of the URL.
384             if (portalURL.getActionWindow() != null
385             		&& portalURL.getActionWindow().equals(param.getWindowId())
386             		|| (portalURL.getResourceWindow() != null
387             				&& portalURL.getResourceWindow().equals(param.getWindowId()))) {
388             	for (int i = 0; i < param.getValues().length; i++) {
389                     // FIX for PLUTO-247
390                     if ( firstParam ) {
391                         firstParam = false;
392                     } else {
393                        query.append("&");
394                     }
395                     query.append(encodeQueryParam(param.getName())).append("=")
396                     		.append(encodeQueryParam(param.getValues()[i]));
397                 }
398             }
399             
400            
401 
402             // Encode render params as a part of the URL.
403             else if (param.getValues() != null
404             		&& param.getValues().length > 0) {
405                 String valueString = encodeMultiValues(param.getValues());
406                 if (valueString.length() > 0) {
407                 	/*buffer.append("/").append(
408                 			encodeControlParameter(RENDER_PARAM, param.getWindowId(),
409                                param.getName()));*/
410                 	 if ( firstParam ) {
411                          firstParam = false;
412                      } else {
413                     	 buffer.append("&");
414                      }
415                 	buffer.append(
416                 			encodeControlParameter( param.getName(),
417                 					valueString));                	
418                 }
419             }
420         }
421         
422         
423         encode(buffer);
424 
425         if (portalURL.getResourceWindow() != null)
426         {
427             Map<String, String[]> privateParamList = portalURL.getPrivateRenderParameters();
428             if (privateParamList!=null){
429                 for (Iterator iter = privateParamList.keySet().iterator();iter.hasNext();){
430                     String paramname = (String)iter.next();
431                     String[] tmp = privateParamList.get(paramname);
432                     //String valueString = encodeMultiValues(tmp);
433                     for(int i = 0; i < tmp.length; i++)
434     	        	{
435     	        		if (tmp[i].length()>0){
436     		        		if ( firstParam ) {
437     	                        firstParam = false;
438     	                    } else {
439     	                    	buffer.append("&");
440     	                    }
441     		        		buffer.append( paramname ).append("=");
442     		        		buffer.append(tmp[i]);
443     		        	}
444     	        	}    	        	
445     	        
446                 }
447             }
448         }
449         
450         Map<String, String[]> publicParamList = portalURL.getPublicParameters();
451         if (publicParamList!=null){
452 	        for (Iterator iter = publicParamList.keySet().iterator();iter.hasNext();){
453 	        	String paramname = (String)iter.next();
454 	        	String[] tmp = publicParamList.get(paramname);
455 	        	//String valueString = encodeMultiValues(tmp);
456 	        	for(int i = 0; i < tmp.length; i++)
457 	        	{
458 	        		if (tmp[i].length()>0){
459 		        		if ( firstParam ) {
460 	                        firstParam = false;
461 	                    } else {
462 	                    	buffer.append("&");
463 	                    }
464 		        		buffer.append( paramname ).append("=");
465 		        		buffer.append(tmp[i]);
466 		        	}
467 	        	}
468 	        	
469 	        }
470         }
471         
472         Map<String, String[]> privateParamList = portalURL.getPrivateRenderParameters();
473         if (privateParamList!=null){
474 	        for (Iterator iter = privateParamList.keySet().iterator();iter.hasNext();){
475 	        	String paramname = (String)iter.next();
476 	        	String[] tmp = privateParamList.get(paramname);
477 	        	//String valueString = encodeMultiValues(tmp);
478 	        	for(int i = 0; i < tmp.length; i++)
479 	        	{
480 	        		if (tmp[i].length()>0){
481 		        		if ( firstParam ) {
482 	                        firstParam = false;
483 	                    } else {
484 	                    	buffer.append("&");
485 	                    }
486 		        		buffer.append( paramname ).append("=");
487 		        		buffer.append(tmp[i]);
488 		        	}
489 	        	}
490 	        }
491         }
492 
493         // Construct the string representing the portal URL.
494         // Fix for PLUTO-247 - check if query string contains parameters
495         if ( query.length() > 1 ) {
496             return buffer.append(query).toString();
497         }
498 
499         // Construct the string representing the portal URL.
500         return buffer.append(query).toString();
501     }
502 
503     public static void encode(StringBuffer url){
504         replaceChar(url,"|","%7C");
505         replaceChar(url,"\"","%22");
506     }
507     private static void replaceChar(StringBuffer url, String character, String change){
508         boolean contains = url.toString().contains(character);
509         while (contains){
510             int index = url.indexOf(character);
511             url.deleteCharAt(index);
512             url.insert(index, change, 0, 3);
513             contains = url.toString().contains(character);
514         }
515     }
516 
517     private String encodeQueryParam(String param) {
518         try {
519             return URLEncoder.encode(param, "UTF-8");
520         }
521         catch (UnsupportedEncodingException e) {
522             // If this happens, we've got bigger problems.
523             throw new RuntimeException(e);
524         }
525     }
526 
527     // Private Encoding/Decoding Methods ---------------------------------------
528 
529     
530     
531     /**
532      * Encode a control parameter.
533      * @param type  the type of the control parameter, which may be:
534      *              portlet mode, window state, or render parameter.
535      * @param windowId  the portlet window ID.
536      * @param name  the name to encode.
537      */
538     private String encodeControlParameter( String name,
539                                           String value) {
540     	StringBuffer buffer = new StringBuffer();
541     	buffer.append(name).append("=")
542     			.append(encodeCharacters(value));    			
543     	return buffer.toString();
544     }
545     
546     /**
547      * Encode a control parameter.
548      * @param type  the type of the control parameter, which may be:
549      *              portlet mode, window state, or render parameter.
550      * @param windowId  the portlet window ID.
551      * @param name  the name to encode.
552      */
553     private String encodeControlParameter(String type,
554                                           String windowId,
555                                           String name) {
556     	StringBuffer buffer = new StringBuffer();
557     	buffer.append(type)
558     			.append("=").append(name);
559     	return buffer.toString();
560     }
561 
562     private String encodePublicParamname( String name ){
563     	StringBuffer buffer = new StringBuffer();
564     	buffer.append(name).append("=");
565     	return buffer.toString();
566     }
567     
568     private String encodePublicParamname(String type, String name){
569     	StringBuffer buffer = new StringBuffer();
570     	buffer.append(PREFIX).append(type)
571     	.append(DELIM).append(name);
572     	return buffer.toString();
573     }
574 
575     /**
576      * Encode a string array containing multiple values into a single string.
577      * This method is used to encode multiple render parameter values.
578      * @param values  the string array to encode.
579      * @return a single string containing all the values.
580      */
581     private String encodeMultiValues(String[] values) {
582     	StringBuffer buffer = new StringBuffer();
583         for (int i = 0; i < values.length; i++) {
584         	// Do not operate on the array reference
585         	String currentValue = values[i];
586         	try {
587         		if (currentValue != null)
588         			currentValue = URLEncoder.encode(values[i], "UTF-8");
589 			} catch (UnsupportedEncodingException e) {
590 				LOG.warn(e.getMessage(),e);
591 			}
592         	buffer.append(currentValue != null ? currentValue : "");
593             if (i + 1 < values.length) {
594             	buffer.append(VALUE_DELIM);
595             }
596         }
597         return encodeCharacters(buffer.toString());
598     }
599 
600     /**
601      * Encode special characters contained in the string value.
602      * @param string  the string value to encode.
603      * @return the encoded string.
604      */
605     private String encodeCharacters(String string) {
606         for (int i = 0; i < ENCODINGS.length; i++) {
607             string = string.replace(ENCODINGS[i][0], ENCODINGS[i][1]);
608         }
609         return string;
610     }
611 
612 
613     /**
614      * Decode a control parameter.
615      * @param control  the control parameter to decode.
616      * @return values  a pair of decoded values.
617      */
618     private String[] decodeControlParameter(String control) {
619         String[] valuePair = new String[2];
620         control = control.substring((PREFIX + PORTLET_ID).length());
621         int index = control.indexOf(DELIM);
622         if (index >= 0) {
623         	valuePair[0] = control.substring(0, index);
624         	valuePair[0] = decodeCharacters(valuePair[0]);
625         	if (index + 1 <= control.length()) {
626         		valuePair[1] = control.substring(index + 1);
627         		valuePair[1] = decodeCharacters(valuePair[1]);
628         	} else {
629         		valuePair[1] = "";
630         	}
631         } else {
632         	valuePair[0] = decodeCharacters(control);
633         }
634         return valuePair;
635     }
636 
637     /**
638      * Decode a name-value pair into a portal URL parameter.
639      * @param name  the parameter name.
640      * @param value  the parameter value.
641      * @return the decoded portal URL parameter.
642      */
643     private PortalURLParameter decodeParameter(String name, String value) {
644 
645         if (LOG.isDebugEnabled()) {
646             LOG.debug("Decoding parameter: name=" + name
647             		+ ", value=" + value);
648         }
649 
650     	// Decode the name into window ID and parameter name.
651         String noPrefix = name.substring((PREFIX + PORTLET_ID).length());
652         String windowId = noPrefix.substring(0, noPrefix.indexOf(DELIM));
653         String paramName = noPrefix.substring(noPrefix.indexOf(DELIM) + 1);
654 
655         // Decode special characters in window ID and parameter value.
656         windowId = decodeCharacters(windowId);
657         if (value != null) {
658         	value = decodeCharacters(value);
659         }
660 
661         // Split multiple values into a value array.
662         String[] paramValues = value.split(VALUE_DELIM);
663         for (int i = 0; i < paramValues.length;i++){
664         	try {
665         		paramValues[i] = URLDecoder.decode(paramValues[i], "UTF-8");
666 			} catch (UnsupportedEncodingException e) {
667 				LOG.warn(e.getMessage(),e);
668 			}
669         }
670         // Construct portal URL parameter and return.
671         return new PortalURLParameter(windowId, paramName, paramValues);
672     }
673 
674     private PortalURLParameter decodePublicParameter(String name, String value) {
675 
676         if (LOG.isDebugEnabled()) {
677             LOG.debug("Decoding parameter: name=" + name
678             		+ ", value=" + value);
679         }
680 
681 //    	// Decode the name into window ID and parameter name.
682         String noPrefix = name.substring((PREFIX + PORTLET_ID).length());
683         String paramName = noPrefix.substring(noPrefix.indexOf(DELIM) + 1);
684 
685         // Decode special characters in parameter value.
686 
687         if (value != null) {
688         	value = decodeCharacters(value);
689         }
690 
691         // Split multiple values into a value array.
692         String[] paramValues = value.split(VALUE_DELIM);
693 
694         // Construct portal URL parameter and return.
695         return new PortalURLParameter(null, paramName, paramValues);
696     }
697 
698     /**
699      * Decode special characters contained in the string value.
700      * @param string  the string value to decode.
701      * @return the decoded string.
702      */
703     private String decodeCharacters(String string) {
704         for (int i = 0; i < ENCODINGS.length; i++) {
705         	string = string.replace(ENCODINGS[i][1], ENCODINGS[i][0]);
706         }
707 		return string;
708     }
709 
710 }
711