View Javadoc

1   // 
2   //Copyright (c) 2003, Caltha - Gajda, Krzewski, Mach, Potempski Sp.J. 
3   //All rights reserved. 
4   //   
5   //Redistribution and use in source and binary forms, with or without modification,  
6   //are permitted provided that the following conditions are met: 
7   //   
8   //* Redistributions of source code must retain the above copyright notice,  
9   //this list of conditions and the following disclaimer. 
10  //* Redistributions in binary form must reproduce the above copyright notice,  
11  //this list of conditions and the following disclaimer in the documentation  
12  //and/or other materials provided with the distribution. 
13  //* Neither the name of the Caltha - Gajda, Krzewski, Mach, Potempski Sp.J.  
14  //nor the names of its contributors may be used to endorse or promote products  
15  //derived from this software without specific prior written permission. 
16  // 
17  //THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"  
18  //AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED  
19  //WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
20  //IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,  
21  //INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,  
22  //BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 
23  //OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,  
24  //WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)  
25  //ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE  
26  //POSSIBILITY OF SUCH DAMAGE. 
27  //
28  
29  package org.objectledge.web.mvc.tools;
30  
31  import java.io.UnsupportedEncodingException;
32  import java.net.URLDecoder;
33  import java.net.URLEncoder;
34  import java.util.Enumeration;
35  import java.util.HashMap;
36  import java.util.HashSet;
37  import java.util.Iterator;
38  import java.util.List;
39  import java.util.Map;
40  import java.util.Set;
41  import java.util.StringTokenizer;
42  
43  import org.jcontainer.dna.Configurable;
44  import org.jcontainer.dna.ConfigurationException;
45  import org.objectledge.ComponentInitializationError;
46  import org.objectledge.parameters.Parameters;
47  import org.objectledge.parameters.RequestParameters;
48  import org.objectledge.parameters.SortedParameters;
49  import org.objectledge.utils.StringUtils;
50  import org.objectledge.web.HttpContext;
51  import org.objectledge.web.WebConfigurator;
52  import org.objectledge.web.mvc.MVCContext;
53  
54  /**
55   * Context tool used to build web application links. It works in a pull manner. The template
56   * designer provides instances of <code>LinkTool</code> with all the necessary parameters,
57   * the <code>LinkTool</code> itself is responsible for generation of a proper URL string based on 
58   * this parameters. An example of <code>LinkTool</code> usage in Velocity template:
59   * 
60   * <h4>template</h4>
61   * <pre>
62   * $link.view('somepackage.SomeView').action('somepackage.SomeAction').set('paramName','paramValue').fragment('this_line')
63   * </pre>
64   *
65   * <h4>output</h4>
66   * <pre>
67   * /context/servlet/view/somepackage.SomeView?action=somepackage.SomeAction&amp;paramName=paramValue#this_line
68   * </pre>
69   * 
70   * <p>The links are encoded using UTF-8 encoding no matter what encoding is used in the generated
71   * HTML page. This is in line with JavaScript URI encoding and decoding functions.</p>
72   * 
73   * @author <a href="mailto:pablo@caltha.pl">Pawel Potempski</a>
74   * @author <a href="mailto:dgajda@caltha.pl">Damian Gajda</a>
75   * @version $Id: LinkTool.java,v 1.33 2006/04/24 13:35:50 zwierzem Exp $
76   */
77  public class LinkTool
78  {
79  	/*** utf encoding. */
80  	public static final String PARAMETER_ENCODING = "UTF-8";
81  	
82      /*** query string parameter values and content paths encoder. */
83      private static final org.objectledge.encodings.URLEncoder URL_ENCODER =
84          new org.objectledge.encodings.URLEncoder();
85      
86      /*** configuration. */
87      protected LinkTool.Configuration config;
88      
89  	/*** the http context. */
90      protected HttpContext httpContext;
91  
92      /*** the mvc context. */
93      protected MVCContext mvcContext;
94  
95      /*** the request parameters. */
96      protected RequestParameters requestParameters;
97  	
98  	/*** view, null for current request parameters view value, empty string for unset value */
99  	private String view;
100 	
101 	/*** the action, empty string for unset value */
102 	private String action;
103 	
104 	/*** is content link switch */
105 	private boolean contentLink;
106 	
107 	/*** include session information */
108 	private boolean includeSession;
109 	
110 	/*** show the protocol in link */
111 	private boolean showProtocolName;
112 	
113 	/*** protocol name */
114 	private String protocolName;
115 	
116     /*** host name */
117     private String host;
118     
119 	/*** the port */
120 	private int port;
121 	
122 	/*** the content path */
123 	private String path;
124 	
125     /*** the special path info suffix */
126     private String pathInfoSuffix;
127 
128     /*** the fragment part of the url */
129 	private String fragment;
130 	
131 	/*** the parameters */
132 	private Parameters parameters;
133     
134 	/*** the string buffer */
135 	private StringBuilder sb;
136 	
137 	/*** 
138 	 * Tool constructor.
139 	 * 
140 	 * @param httpContext the http context.
141      * @param mvcContext the mvc context.
142      * @param requestParameters the request parameters.
143      * @param config the link tool configuraiton.
144 	 */
145 	public LinkTool(HttpContext httpContext, MVCContext mvcContext,
146         RequestParameters requestParameters, LinkTool.Configuration config)
147 	{
148         this.config = config;
149 		this.httpContext = httpContext;
150         this.mvcContext = mvcContext;
151 		this.requestParameters = requestParameters;
152 		contentLink = false;
153 		includeSession = true;
154 		showProtocolName = false;
155 		protocolName = ""; 
156         port = 0;
157         host = null;
158         // this means no override for view
159         view = null;
160 		action = "";
161         pathInfoSuffix = null;
162 		fragment = null;
163 		if(config.stickyParameterNames.size() > 0)
164 		{
165 			parameters = new SortedParameters(requestParameters);
166 		    parameters.removeExcept(config.stickyParameterNames);
167 		}
168         else
169         {
170             parameters = new SortedParameters();
171         }
172 	}
173 	
174 	/***
175      * Set the protocol to http with default port on 80.
176      * 
177      * @return the link tool.
178      */
179 	public LinkTool http()
180 	{
181 		return http(80);
182 	}
183 
184 	/***
185 	 * Set the protocol to http with specified port.
186 	 * 
187 	 * @param port the port.
188 	 * @return the link tool.
189 	 */
190     public LinkTool http(int port)
191 	{
192 	    LinkTool target = getLinkTool(this);
193 	    target.showProtocolName = true;
194 		target.protocolName = "http";
195 		target.port = port;
196 		return target;
197 	}
198 
199 	/***
200 	 * Set the protocol to https with default port on 443.
201 	 * 
202 	 * @return the link tool.
203 	 */
204 	public LinkTool https()
205 	{
206 		return https(443);
207 	}
208 
209 	/***
210 	 * Set the protocol to https with specified port.
211 	 * 
212 	 * @param port the port.
213 	 * @return the link tool.
214 	 */
215 	public LinkTool https(int port)
216 	{
217 		LinkTool target = getLinkTool(this);
218 		target.showProtocolName = true;
219 		target.protocolName = "https";
220 		target.port = port;
221 		return target;
222 	}
223 
224     /***
225      * Generate an absolute link including protocol name (schema), server name and port number.
226      * 
227      * @return the link tool.
228      */
229 	public LinkTool absolute()
230 	{
231 	    LinkTool target = getLinkTool(this);
232 		target.showProtocolName = true;
233         if(target.port == 0)
234         {
235             target.port = httpContext.getRequest().getServerPort();    
236         }
237         if(target.protocolName == null || target.protocolName.length()==0)
238         {
239  		    if(httpContext.getRequest().isSecure())
240 		    {
241 			    target.protocolName = "https";
242 		    }
243 		    else
244 		    {
245 			    target.protocolName = "http";
246 		    }
247         }
248 		return target;
249     }
250 
251     /***
252      * Geneate an absolute link to another host.
253      * 
254      * @param host the host domain name.
255      * @return the link tool.
256      */
257     public LinkTool host(String host)
258     {
259         LinkTool target = absolute();
260         target.host = host;
261         return target;
262     }
263     
264     /***
265      * Avoid session information in link - useful for content served using external HTTP server. 
266      *
267      * @return the link tool. 
268      */
269     public LinkTool sessionless()
270     {
271         LinkTool target = getLinkTool(this);
272         target.includeSession = false;
273         return target;
274     }
275 
276     /***
277      * Set link to point to the content stored in <code>/</code> directory of servlet context.
278      * May be used to generate relative content paths (not recommended).
279      *
280      * @param path the path to content.
281      * @return the link tool.
282      */
283     public LinkTool rootContent(String path)
284     {
285         LinkTool target = getLinkTool(this);
286         target.contentLink = true;
287         target.includeSession = this.includeSession && !config.externalContent;
288         target.path = path;
289         return target;
290     }
291 
292     /***
293      * Set link to point to the content stored in configured content (<code>/content</code>)
294      * directory of servlet context. 
295      *
296      * @param path the relative path to content.
297      * @return the link tool.
298      */
299     public LinkTool content(String path)
300     {
301         if (path.length() == 0)
302         {
303             path = config.baseContentPath;
304         }
305         else if (path.charAt(0) != '/')
306         {
307             path = config.baseContentPath + '/' + path;
308         }
309         else
310         {
311             path = config.baseContentPath + path;
312         }
313         return rootContent(path);
314     }
315 
316     // parameter set methods ---------------------------------------------------------------------- 
317     
318     /***
319      * Sets a request parameter, replacing previously set value.
320      * Unless configured differently it will be rendered in the link as query string parameter.
321      *
322      * @param name the name of the parameter.
323      * @param value the value of the parameter.
324      * @return the link tool.
325      */
326     public LinkTool set(String name, String value)
327     {
328         LinkTool target = getSetTargetLinkTool(name);
329 		target.parameters.set(name, value);
330         return target;
331     }
332 
333     /***
334      * Sets a request parameter, replacing previously set value.
335      * Unless configured differently it will be rendered in the link as query string parameter.
336      *
337      * @param name the name of the parameter.
338      * @param value the value of the parameter.
339      * @return the link tool. 
340      */
341     public LinkTool set(String name, int value)
342     {
343         LinkTool target = getSetTargetLinkTool(name);
344 		target.parameters.set(name, value);
345 		return target;
346     }
347 
348     /***
349      * Sets a request parameter, replacing previously set value.
350      * Unless configured differently it will be rendered in the link as query string parameter.
351      *
352      * @param name the name of the parameter.
353      * @param value the value of the parameter.
354      * @return the link tool.
355      */
356     public LinkTool set(String name, long value)
357     {
358         LinkTool target = getSetTargetLinkTool(name);
359         target.parameters.set(name, value);
360         return target;
361     }
362 
363     /***
364      * Sets a request parameter, replacing previously set value.
365      * Unless configured differently it will be rendered in the link as query string parameter.
366      *
367      * @param name the name of the parameter.
368      * @param value the value of the parameter.
369      * @return the link tool.
370      */
371     public LinkTool set(String name, float value)
372     {
373         LinkTool target = getSetTargetLinkTool(name);
374         target.parameters.set(name, value);
375         return target;
376     }
377 
378     /***
379      * Sets a request parameter, replacing previously set value.
380      * Unless configured differently it will be rendered in the link as query string parameter.
381      *
382      * @param name the name of the parameter.
383      * @param value the value of the parameter.
384      * @return the link tool.
385      */
386     public LinkTool set(String name, boolean value)
387     {
388         LinkTool target = getSetTargetLinkTool(name);
389         target.parameters.set(name, value);
390         return target;
391     }
392 
393     /***
394      * Sets multiple values of a parameter using a {@link java.util.List}.
395      *
396      * @param name the name of the parameter.
397      * @param list a list of parameter values.
398      * @return the link tool.
399      */
400     public LinkTool set(String name, List<String> list)
401     {
402         LinkTool target = getSetTargetLinkTool(name);
403         target.parameters.remove(name);
404         for (String value : list) 
405         {
406             target.parameters.add(name, value);
407         }
408         return target;
409     }
410 
411     /***
412      * Adds a set of parameters defined as a {@link java.util.Map} to the request.
413      * Removes old values of the parameters.
414      *
415      * @param map a set of parametres as a Map.
416      * @return the link tool.
417      */
418     public LinkTool set(Map<String, String> map)
419     {
420         if (map.containsKey(config.viewToken))
421         {
422             checkSetParamName(config.viewToken);
423         }
424         else if (map.containsKey(config.actionToken))
425         {
426             checkSetParamName(config.actionToken);
427         }
428         LinkTool target = getLinkTool(this);
429         for (Iterator<Map.Entry<String, String>> iter = map.entrySet().iterator(); iter.hasNext();)
430         {
431             Map.Entry<String, String> e = iter.next();
432             target.parameters.remove(e.getKey());
433             target.parameters.add(e.getKey(), e.getValue());
434         }
435         return target;
436     }
437 
438     
439     
440     /***
441      * Sets the request parameters to be equal to contents of the specified
442      * parameter container.
443      *
444      * @param parameters a set of paramters
445      * @return the link tool.
446      */
447     public LinkTool set(Parameters parameters)
448     {
449 		if (parameters.isDefined(config.viewToken))
450 		{
451 			checkSetParamName(config.viewToken);
452 		}
453 		else if (parameters.isDefined(config.actionToken))
454 		{
455 			checkSetParamName(config.actionToken);
456 		}
457         LinkTool target = getLinkTool(this);
458         target.parameters = new SortedParameters(parameters);
459         return target;
460     }
461 
462     /***
463      * Checks the name of the set parameter and returns target link tool.
464      * Method created for code reuse.
465      *  
466      * @param paramName checked name of the set parameter.
467      * @return target link tool
468      */
469     private LinkTool getSetTargetLinkTool(String paramName)
470     {
471         checkSetParamName(paramName);
472         return getLinkTool(this);
473     }
474     
475     // --------------------------------------------------------------------------------------------
476     
477     /***
478      * Sets the path info suffix for this link.
479      * May be used for file download links.
480      * 
481      * @param pathInfoSuffix the path info suffix.
482      * @return the link tool.
483      */
484     public LinkTool pathInfoSuffix(String pathInfoSuffix)
485     {
486         LinkTool target = getLinkTool(this);
487         target.pathInfoSuffix = pathInfoSuffix;
488         return target;
489     }
490 
491     /***
492      * Returns the path info suffix for this link.
493      * 
494      * @return the path info suffx in the current link
495      */
496     public String pathInfoSuffix()
497     {
498         return pathInfoSuffix;
499     }
500 
501     /***
502      * Sets the fragment for this link.
503      * Fragment is appended as <code>#fragment-value</code> to the rendered link.
504      * 
505      * @param fragment the fragment.
506      * @return the link tool.
507      */
508     public LinkTool fragment(String fragment)
509     {
510         LinkTool target = getLinkTool(this);
511         target.fragment = fragment;
512         return target;
513     }
514 
515     /***
516      * Returns the fragment for this link.
517      * 
518      * @return the fragment in the current link
519      */
520     public String fragment()
521     {
522         return fragment;
523     }
524 
525     /***
526      * Sets the parameters to be equal to the paremeters of the
527      * current request.
528      * 
529      * <p>WARN: This method creates links different from the request URI if some of the parameters
530      * were passed as path info parameters and not configured as such.</p>  
531      * 
532      * @return the link tool.
533      */
534     public LinkTool self()
535     {
536         LinkTool target = getLinkTool(this);
537         target.parameters.remove(config.stickyParameterNames);
538         target.parameters.add(requestParameters, true);
539         target.parameters.remove(config.viewToken);
540         target.parameters.remove(config.actionToken);
541         String url = httpContext.getRequest().getRequestURI();
542         if (url.indexOf('#') > 0)
543         {
544             target.fragment = url.substring(url.lastIndexOf('#') + 1);
545         }
546         return target;
547     }
548 
549     /***
550      * Prepare link tool pointed to referer page
551      * @return the link tool
552      */
553     public LinkTool getReferer()
554     {
555         Enumeration enumeration = httpContext.getRequest().getHeaders("referer");
556         if( enumeration != null && enumeration.hasMoreElements())
557         {
558             return parseURL(enumeration.nextElement().toString());
559         }
560         else
561         {
562             return null;
563         }
564     }
565     
566     /***
567      * Parse given string to obtain link tool object
568      * @param url - string of the toString() shape
569      * @return the link tool
570      * @see #toString()
571      */
572     private LinkTool parseURL(String url)
573     {   
574         LinkTool target = getLinkTool(this);
575         int index = url.indexOf(config.viewToken+"/");
576         if( StringUtils.isEmpty(url) || index < 0)
577         {
578             return target;
579         }
580         Map<String,String> paramsMap = parseURLParams(url.substring(index), "&=?/");
581         
582         for( String name: paramsMap.keySet())
583         {
584             if(config.viewToken.equals(name))
585             {
586                 target.view = paramsMap.get(name);
587                 continue;
588             }
589             if( config.actionToken.equals(name))
590             {
591                 target.action = paramsMap.get(name);
592                 continue;
593             }
594             target.parameters.add(name, paramsMap.get(name));
595         }
596         return target;
597     }
598     
599     private static final int START = 0;
600     private static final int NAME = 1;
601     private static final int SEPARATOR_AFTER_NAME = 2;
602     private static final int VALUE = 3;
603     private static final int SEPARATOR_AFTER_VALUE = 4;
604     
605     private Map<String,String> parseURLParams(String urlPart, String separator)
606     {       
607         Map<String, String> paramsMap = new HashMap<String, String>();
608         if (urlPart == null)
609         {
610             return paramsMap;
611         }
612         
613         try
614         {
615             StringTokenizer st = new StringTokenizer(urlPart, separator, true);
616             int state = START;
617             String name = null;
618             while (st.hasMoreTokens())
619             {
620                 String token = st.nextToken();
621                 // separators
622                 if(token.length() == 1 && separator.indexOf(token.charAt(0)) > -1 )
623                 {
624                     switch(state)
625                     {
626                     case START:
627                         state = NAME;
628                         break;
629                     case SEPARATOR_AFTER_NAME:
630                         state = VALUE;
631                         break;
632                     case SEPARATOR_AFTER_VALUE:
633                         state = NAME;
634                         break;
635                     case VALUE:
636                         add(name, "");
637                         name = null;
638                         state = NAME;
639                         break;
640                     case NAME:
641                         throw new IllegalStateException("empty parameter name");
642                     default:
643                         throw new IllegalStateException(
644                         "illegal state while parsing params");
645                     }
646                 }
647                 // names and values
648                 else
649                 {
650                     switch(state)
651                     {
652                     case START:
653                     case NAME:
654                         name = URLDecoder.decode(token, LinkTool.PARAMETER_ENCODING);
655                         paramsMap.put(name,"");
656                         state = SEPARATOR_AFTER_NAME;
657                         break;
658                     case VALUE:
659                         paramsMap.put(name, URLDecoder.decode(token, LinkTool.PARAMETER_ENCODING));
660                         name = null;
661                         state = SEPARATOR_AFTER_VALUE;
662                         break;
663                     default:
664                         break;  
665                     }
666                 }
667             }
668             return paramsMap;
669         }
670         ///CLOVER:OFF
671         catch (UnsupportedEncodingException e)
672         {
673             throw new IllegalArgumentException("Unsupported encoding exception " + e.getMessage());
674         }
675         ///CLOVER:ON
676     }
677 
678     // parameter add methods ---------------------------------------------------------------------- 
679     
680     /***
681      * Adds a request parameter, extending it's values set.
682      *
683      * @param name the name of the parameter.
684      * @param value the value of the parameter.
685      * @return the link tool.
686      */
687     public LinkTool add(String name, String value)
688     {
689         LinkTool target = getAddTargetLinkTool(name);
690         target.parameters.add(name, value);
691         return target;
692     }
693 
694     /***
695      * Adds a request parameter, extending it's values set.
696      *
697      * @param name the name of the parameter.
698      * @param value the value of the parameter.
699      * @return the link tool.
700      */
701     public LinkTool add(String name, int value)
702     {
703         LinkTool target = getAddTargetLinkTool(name);
704         target.parameters.add(name, value);
705         return target;
706     }
707 
708     /***
709      * Adds a request parameter, extending it's values set.
710      *
711      * @param name the name of the parameter.
712      * @param value the value of the parameter.
713      * @return the link tool.
714      */
715     public LinkTool add(String name, long value)
716     {
717         LinkTool target = getAddTargetLinkTool(name);
718         target.parameters.add(name, value);
719         return target;
720     }
721 
722     /***
723      * Adds a request parameter, extending it's values set.
724      *
725      * @param name the name of the parameter.
726      * @param value the value of the parameter.
727      * @return the link tool.
728      */
729     public LinkTool add(String name, float value)
730     {
731         LinkTool target = getAddTargetLinkTool(name);
732         target.parameters.add(name, value);
733         return target;
734     }
735 
736     /***
737      * Adds a request parameter, extending it's values set.
738      *
739      * @param name the name of the parameter.
740      * @param value the value of the parameter.
741      * @return the link tool.
742      */
743     public LinkTool add(String name, boolean value)
744     {
745         LinkTool target = getAddTargetLinkTool(name);
746         target.parameters.add(name, value);
747         return target;
748     }
749 
750     /***
751      * Adds multiple parameter values using a {@link java.util.List}.
752      *
753      * @param name the name of the parameter.
754      * @param list a set of parameter values.
755      * @return the link tool.
756      */
757     public LinkTool add(String name, List<String> list)
758     {
759         LinkTool target = getAddTargetLinkTool(name);
760         for (String value : list) 
761         {
762             target.parameters.add(name, value);
763         }
764         return target;
765     }
766 
767     /***
768      * Adds a set of parameters defined as a {@link java.util.Map} to the request.
769      *
770      * @param map a set of parametres as a Map.
771      * @return the link tool.
772      */
773     public LinkTool add(Map<String, String> map)
774     {
775         if (map.containsKey(config.viewToken))
776         {
777             checkAddParamName(config.viewToken);
778         }
779         else if (map.containsKey(config.actionToken))
780         {
781             checkAddParamName(config.actionToken);
782         }
783         LinkTool target = getLinkTool(this);
784         for (Iterator<Map.Entry<String, String>> iter = map.entrySet().iterator(); iter.hasNext();)
785         {
786             Map.Entry<String, String> e = iter.next();
787             target.parameters.add(e.getKey(), e.getValue());
788         }
789         return target;
790     }
791 
792     /***
793      * Adds a set of parameters to the request.
794      *
795      * @param parameters a set of paremteres.
796      * @return the link tool.
797      */
798     public LinkTool add(Parameters parameters)
799     {
800 		if (parameters.isDefined(config.viewToken))
801 		{
802 			checkAddParamName(config.viewToken);
803 		}
804 		else if (parameters.isDefined(config.actionToken))
805 		{
806 			checkAddParamName(config.actionToken);
807 		}
808         LinkTool target = getLinkTool(this);
809         target.parameters.add(parameters, false);
810         return target;
811     }
812 
813     /***
814      * Checks the name of the set parameter and returns target link tool.
815      * Method created for code reuse.
816      *  
817      * @param paramName checked name of the set parameter.
818      * @return target link tool
819      */
820     private LinkTool getAddTargetLinkTool(String paramName)
821     {
822         checkAddParamName(paramName);
823         return getLinkTool(this);
824     }
825 
826     // ---------------------------------------------------------------------------------------------
827     
828     /***
829      * Removes all values of a request parameter.
830      *
831      * @param name the name of the parameter.
832      * @return the link tool.
833      */
834     public LinkTool unset(String name)
835     {
836         LinkTool target = getLinkTool(this);
837         if (name.equals(config.viewToken))
838         {
839 			throw new IllegalArgumentException("to unset the value of the view parameter, " +
840 				"call the unsetView() method");
841         }
842         else if (name.equals(config.actionToken))
843         {
844 			throw new IllegalArgumentException("to unset the value of the action parameter, " +
845 				"call the unsetAction() method");
846         }
847         else
848         {
849             target.parameters.remove(name);
850         }
851         return target;
852     }
853 
854     /***
855      * Sets the view parameter in the link.
856      *
857      * @param view the view.
858      * @return the link tool.
859      */
860     public LinkTool view(String view)
861     {
862         LinkTool target = getLinkTool(this);
863         target.view = view;
864         return target;
865     }
866 
867     /***
868      * Sets the action parameter in the link.
869      *
870      * @param action the action.
871      * @return the link tool.
872      */
873     public LinkTool action(String action)
874     {
875         LinkTool target = getLinkTool(this);
876         target.action = action;
877         return target;
878     }
879 
880     /***
881      * @return the name of the action parameter.
882      */
883     public String actionParam()
884     {
885         return config.actionToken;
886     }
887     
888     /***
889      * @return the name of the view parameter.
890      */
891     public String viewParam()
892     {
893         return config.viewToken;
894     }
895 
896     /***
897 	 * Removes the view parameter.
898 	 *
899 	 * @return the link tool.
900 	 */
901 	public LinkTool unsetView()
902 	{
903 		LinkTool target = getLinkTool(this);
904 		target.view = "";
905 		return target;
906 	}
907 
908 	/***
909 	 * Removes the action parameter.
910 	 *
911 	 * @return the link tool.
912 	 */
913 	public LinkTool unsetAction()
914 	{
915 		LinkTool target = getLinkTool(this);
916 		target.action = "";
917 		return target;
918 	}
919 
920     // start toString() - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
921     
922     /***
923      * Produces a {@link java.lang.String} representation of this link.
924      * 
925      * @return the link.
926      */
927     public String toString()
928     {
929         if(sb != null)
930         {
931             sb.setLength(0);
932         }
933         else
934         {
935             sb = new StringBuilder();
936         }
937 
938         try
939         {
940             // prepare server part if needed
941             appendServerPart(sb);
942 
943             // prepare address part
944             sb.append(getContextPath());
945 
946             if (contentLink)
947             {
948                 appendContentLink(sb);
949             }
950             else
951             {
952                 sb.append(getServletPath());
953 
954                 Parameters parametersTmp = getParameters(); 
955                 String[] keys = parametersTmp.getParameterNames();
956                 appendPathInfo(sb, keys, parametersTmp);
957                 appendPathInfoSuffix(sb);
958                 appendQueryString(sb, keys, parametersTmp);
959             }
960             
961             if (fragment != null)
962             {
963                 sb.append('#').append(fragment);
964             }
965             
966             // return link
967             String link = sb.toString(); 
968             if (includeSession)
969             {
970                 link = httpContext.getResponse().encodeURL(link);
971             }
972             return link;
973         }
974         ///CLOVER:OFF
975         catch (UnsupportedEncodingException e)
976         {
977             throw new RuntimeException("Exception occurred", e);
978         }
979         ///CLOVER:ON
980     }
981 
982     /***
983      * Allows other Tools to check link properties and 
984      * creaton of subclasses which can override action name 
985      * rendered in {@link #toString()} method.
986      * 
987      * <p>WARN: This implementation only returns a reference to this <code>LinkTool</code>'s
988      * parameters field. If the subclass needs to change the rendered parameters, it should copy
989      * the parameters object.</p> 
990      * 
991      * @return the overriden parameters container
992      */
993     public Parameters getParameters()
994     {
995         return parameters;
996     }
997     
998     private void appendServerPart(StringBuilder sb)
999     {
1000         if (showProtocolName)
1001         {
1002             final String protocolNameTmp = getProtocolName();
1003             sb.append(protocolNameTmp);
1004             sb.append("://");
1005             sb.append(getServerName());
1006             final int portTmp = getPort();
1007             if (mustAppendPort(protocolNameTmp, portTmp))
1008             {
1009                 sb.append(':').append(portTmp);
1010             }
1011         }
1012     }
1013 
1014     /***
1015      * Allows subclasses to override protocol name rendered in {@link #toString()} method.
1016      * 
1017      * @return the overriden protocol name
1018      */
1019     protected String getProtocolName()
1020     {
1021         return protocolName;
1022     }
1023 
1024     /***
1025      * Allows subclasses to override server name rendered in {@link #toString()} method.
1026      * 
1027      * @return the overriden server name
1028      */
1029     protected String getServerName()
1030     {
1031         if(host == null)
1032         {
1033             return httpContext.getRequest().getServerName();
1034         }
1035         else
1036         {
1037             return host;
1038         }
1039     }
1040 
1041     /***
1042      * Allows subclasses to override cotext path name rendered in {@link #toString()} method.
1043      * 
1044      * @return the overriden context path
1045      */
1046     protected String getContextPath()
1047     {
1048         if(config.rewrite)
1049         {
1050             // rely on mod rewrite to put context path into URL
1051             return "";
1052         }
1053         else
1054         {
1055             return httpContext.getRequest().getContextPath();
1056         }
1057     }
1058 
1059     /***
1060      * Allows subclasses to override servlet path name rendered in {@link #toString()} method.
1061      * 
1062      * @return the overriden serlvet path
1063      */
1064     protected String getServletPath()
1065     {
1066         if(config.rewrite)
1067         {
1068             // rely on mod rewrite to put servlet path into URL
1069             return "";
1070         }
1071         else
1072         {
1073             return httpContext.getRequest().getServletPath();
1074         }
1075     }
1076 
1077     /***
1078      * Allows subclasses to override check for port number inclusion requirement in
1079      * {@link #toString()} method.
1080      * This implementation returns <code>true</code> if port number is different from default
1081      * protocol port (default port number for http is 80, for https 443). 
1082      * 
1083      * @param protocolNameTmp appended protocol name
1084      * @param portTmp appended (or not) port number
1085      * @return <code>true</code> if port number must be appended.
1086      */
1087     protected boolean mustAppendPort(String protocolNameTmp, int portTmp)
1088     {
1089         return ((protocolNameTmp.length() == 0 || protocolNameTmp.equals("http")) && portTmp != 80)
1090                || (protocolNameTmp.equals("https") && portTmp != 443)
1091                || (protocolNameTmp.length() > 0
1092                    && !protocolNameTmp.equals("https") && !protocolNameTmp.equals("http"));
1093     }
1094 
1095     /***
1096      * Allows subclasses to override port number rendered in {@link #toString()} method.
1097      * 
1098      * @return the overriden port number
1099      */
1100     protected int getPort()
1101     {
1102         return port;
1103     }
1104     
1105     
1106     private void appendContentLink(StringBuilder sb)
1107         throws UnsupportedEncodingException
1108     {
1109         final String pathTmp = getPath();
1110         if (pathTmp.length() > 0)
1111         {
1112             if (pathTmp.charAt(0) != '/')
1113             {
1114                 sb.append('/');
1115             }
1116             sb.append(URL_ENCODER.encodeContentPath(pathTmp, PARAMETER_ENCODING));
1117         }
1118     }
1119     
1120     /***
1121      * Allows subclasses to override static content path rendered in {@link #toString()} method.
1122      * 
1123      * @return the overriden content path
1124      */
1125     protected String getPath()
1126     {
1127         return path;
1128     }
1129 
1130     private void appendPathInfo(StringBuilder sb, String[] keys, Parameters parametersTmp)
1131         throws UnsupportedEncodingException
1132     {
1133         String outView = getView(); 
1134         if (outView == null) // override with current view
1135         {
1136             outView = mvcContext.getView();
1137         }
1138         if (outView != null && outView.length() > 0)
1139         {
1140             sb.append('/').append(config.viewToken).append('/').append(outView);
1141         }
1142 
1143         for (int i = 0; i < keys.length; i++)
1144         {
1145             String key = keys[i];
1146             if (config.pathinfoParameterNames.contains(key))
1147             {
1148                 String[] values = parametersTmp.getStrings(key);
1149                 for (int j = 0; j < values.length; j++)
1150                 {
1151                     sb.append('/').append(URLEncoder.encode(key, PARAMETER_ENCODING));
1152                     sb.append('/').append(URLEncoder.encode(values[j], PARAMETER_ENCODING));
1153                 }
1154             }
1155         }
1156     }
1157     
1158     /***
1159      * Allows other Tools to check link properties and 
1160      * creaton of subclasses which can override action name 
1161      * rendered in {@link #toString()} method.
1162      * 
1163      * @return the overriden view name or <code>null</code> if it should be replaced with view
1164      *  from current request.
1165      */
1166     public String getView()
1167     {
1168         return view;
1169     }
1170 
1171     private void appendPathInfoSuffix(StringBuilder sb)
1172         throws UnsupportedEncodingException
1173     {
1174         String pathInfoSuffixTmp = getPathInfoSuffix();
1175         if(pathInfoSuffixTmp != null && pathInfoSuffixTmp.length() > 0)
1176         {
1177             if(pathInfoSuffixTmp.charAt(0) != '/')
1178             {
1179                 sb.append('/');    
1180             }
1181             pathInfoSuffixTmp = URLEncoder.encode(pathInfoSuffixTmp, PARAMETER_ENCODING);
1182             // if someone puts slashes into the suffix, assume they know what they are doing.
1183             pathInfoSuffixTmp = pathInfoSuffixTmp.replace("%2F","/");
1184             sb.append(pathInfoSuffixTmp);
1185         }
1186     }
1187     
1188     /***
1189      * Allows subclasses to override pathinfo suffix rendered in {@link #toString()} method.
1190      * 
1191      * @return the overriden pathinfo suffix
1192      */
1193     protected String getPathInfoSuffix()
1194     {
1195         return pathInfoSuffix;
1196     }
1197     
1198     private void appendQueryString(StringBuilder sb, String[] keys, Parameters parametersTmp)
1199         throws UnsupportedEncodingException
1200     {
1201         String querySeparator = "?";
1202         final String querySeparator2 = config.queryStringSeparator;
1203 
1204         String actionTmp = getAction();
1205         if (actionTmp != null && actionTmp.length() > 0)
1206         {
1207             sb.append(querySeparator);
1208             sb.append(config.actionToken).append('=').append(actionTmp);
1209             querySeparator = querySeparator2;
1210         }
1211         
1212         for (int i = 0; i < keys.length; i++)
1213         {
1214             String key = keys[i];
1215             if (!config.pathinfoParameterNames.contains(key))
1216             {
1217                 String[] values = parametersTmp.getStrings(key);
1218                 for (int j = 0; j < values.length; j++)
1219                 {
1220                     sb.append(querySeparator);
1221                     
1222                     sb.append(URLEncoder.encode(key, PARAMETER_ENCODING));
1223                     sb.append('=');
1224                     sb.append(URL_ENCODER
1225                         .encodeQueryStringValue(values[j], PARAMETER_ENCODING));
1226 
1227                     querySeparator = querySeparator2;
1228                 }
1229             }
1230         }
1231     }
1232     
1233     /***
1234      * Allows other Tools to check link properties and 
1235      * creaton of subclasses which can override action name 
1236      * rendered in {@link #toString()} method.
1237      * 
1238      * @return the overriden action name
1239      */
1240     public String getAction()
1241     {
1242         return action;
1243     }
1244 
1245     // end toString() - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
1246     
1247 	/***
1248 	 * Clone the given LinkTool.
1249 	 * 
1250      * @param source to LinkTool to clone
1251 	 * @return the clone.
1252 	 */
1253 	private LinkTool getLinkTool(LinkTool source)
1254 	{
1255 		LinkTool target = createInstance(source);
1256 		target.view = source.view;
1257 		target.action = source.action;
1258 		target.contentLink = source.contentLink;
1259 		target.includeSession = source.includeSession;
1260 		target.showProtocolName = source.showProtocolName;
1261 		target.protocolName = source.protocolName;
1262 		target.port = source.port;
1263         target.host = source.host;
1264 		target.path = source.path;
1265         target.pathInfoSuffix = source.pathInfoSuffix;
1266 		target.fragment = source.fragment;
1267 	    target.parameters = new SortedParameters(source.parameters);
1268 		return target;		
1269 	}
1270     
1271     /***
1272      * Creates the LinkTool instance for copying. This method is intended to be overriden by
1273      * extending classes in order to provide LinkTool instances of proper class.
1274      * 
1275      * @param source copied object
1276      * @return created instance of the linktool.
1277      */
1278     protected LinkTool createInstance(LinkTool source)
1279     {
1280         return new LinkTool(source.httpContext, source.mvcContext, source.requestParameters,
1281             source.config);
1282     }
1283     
1284     private void checkSetParamName(String name)
1285     {
1286 		if (name.equals(config.viewToken))
1287 		{
1288 			throw new IllegalArgumentException("to set the value of the view parameter, " +
1289 				"call the view(String) method");
1290 		}
1291 		else if (name.equals(config.actionToken))
1292 		{
1293 			throw new IllegalArgumentException("to set the value of the action parameter, " +
1294 				"call the action(String) method");
1295 		}
1296     }
1297 
1298 	private void checkAddParamName(String name)
1299 	{
1300         if (name.equals(config.viewToken))
1301         {
1302             throw new IllegalArgumentException("to set the value of the view parameter, " +
1303                 "call the view(String) method");
1304         }
1305         else if (name.equals(config.actionToken))
1306         {
1307             throw new IllegalArgumentException("to set the value of the action parameter, " +
1308                 "call the action(String) method");
1309         }
1310 	}
1311     
1312     /***
1313      * Represents the shared configuration of the LinkTools.
1314      *
1315      * <p>Created on Jan 14, 2004</p>
1316      * @author <a href="Rafal.Krzewski">rafal@caltha.pl</a>
1317      */
1318     public static class Configuration
1319         implements Configurable
1320     {        
1321         /*** the default query separator. */
1322         public static final String DEFAULT_QUERY_SEPARATOR = "&";
1323 
1324         /*** the default base content path. */
1325         public static final String DEFAULT_BASE_CONTENT_PATH = "/content";
1326 
1327         /*** the sticky parameters keys */
1328         private Set stickyParameterNames = new HashSet();
1329     
1330         /*** the pathinfo parameters keys */
1331         private Set pathinfoParameterNames = new HashSet();
1332     
1333         /*** the query separator */
1334         private String queryStringSeparator;
1335     
1336         /*** external content switch */
1337         private boolean externalContent;
1338         
1339         /*** rewrite (drop context and servlet path from URLs) switch. */
1340         private boolean rewrite;
1341 
1342         /*** the web configurator. */
1343         private WebConfigurator webConfigurator;
1344         
1345         /*** base content path */
1346         private String baseContentPath;
1347         
1348         /*** currently used view parameter name */
1349         private String viewToken;
1350 
1351         /*** currently used action parameter name */
1352         private String actionToken;
1353 
1354         /***
1355          * Initializes the configuraiton object.
1356          * 
1357          * @param config DNA configuration
1358          * @param webConfigurator the configuration of the web subsystem.
1359          * @throws ConfigurationException if the configuration is invalid.
1360          */
1361         public Configuration(org.jcontainer.dna.Configuration config, 
1362             WebConfigurator webConfigurator)
1363             throws ConfigurationException
1364         {
1365             this.webConfigurator = webConfigurator;
1366             configure(config);
1367         }
1368         
1369         /***
1370          * Initializes the internal state from DNA configuration object.
1371          * 
1372          * <p>This method may be used to reconfigure link tool at runtime.</p>
1373          * 
1374          * @param config DNA configuration
1375          * @throws ConfigurationException if the configuration is invalid.
1376          */
1377         public void configure(org.jcontainer.dna.Configuration config)
1378             throws ConfigurationException
1379         {
1380             try
1381             {
1382                 org.jcontainer.dna.Configuration[] keys = 
1383                     config.getChild("sticky").getChildren("key");
1384                 for (int i = 0; i < keys.length; i++)
1385                 {
1386                     stickyParameterNames.add(keys[i].getValue());
1387                 }
1388                 keys = config.getChild("pathinfo").getChildren("key");
1389                 for (int i = 0; i < keys.length; i++)
1390                 {
1391                     pathinfoParameterNames.add(keys[i].getValue());
1392                 }
1393                 baseContentPath =
1394                     config.getChild("base_content_path").getValue(DEFAULT_BASE_CONTENT_PATH);
1395             }
1396             ///CLOVER:OFF
1397             catch (ConfigurationException e)
1398             {
1399                 throw new ComponentInitializationError("failed to configure the component", e);
1400             }
1401             ///CLOVER:ON
1402             queryStringSeparator = config.getChild("query_separator").
1403                 getValue(DEFAULT_QUERY_SEPARATOR);
1404             externalContent = config.getChild("external_content").getValueAsBoolean(false);
1405             rewrite = config.getChild("rewrite").getValueAsBoolean(false);
1406             // TODO: remove WebConfigurator
1407             viewToken = webConfigurator.getViewToken();
1408             actionToken = webConfigurator.getActionToken();
1409         }
1410     }
1411 }