View Javadoc
1   package org.apache.turbine.util.uri;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.UnsupportedEncodingException;
23  import java.net.URLEncoder;
24  import java.util.ArrayList;
25  import java.util.Collection;
26  import java.util.Iterator;
27  import java.util.List;
28  
29  import org.apache.commons.lang3.StringUtils;
30  import org.apache.fulcrum.parser.ParameterParser;
31  import org.apache.fulcrum.parser.ParserService;
32  import org.apache.logging.log4j.LogManager;
33  import org.apache.logging.log4j.Logger;
34  import org.apache.turbine.services.TurbineServices;
35  import org.apache.turbine.util.RunData;
36  import org.apache.turbine.util.ServerData;
37  
38  /**
39   * This class allows you to keep all the information needed for a single
40   * link at one place. It keeps your query data, path info, the server
41   * scheme, name, port and the script path.
42   *
43   * If you must generate a Turbine Link, use this class.
44   *
45   * @author <a href="mailto:jon@clearink.com">Jon S. Stevens</a>
46   * @author <a href="mailto:jvanzyl@periapt.com">Jason van Zyl</a>
47   * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
48   * @version $Id: TurbineURI.java 1854688 2019-03-03 10:36:42Z tv $
49   */
50  
51  public class TurbineURI
52          extends BaseURI
53  {
54      /** Logging */
55      private static final Logger log = LogManager.getLogger(TurbineURI.class);
56  
57      /** Contains the PathInfo and QueryData vectors */
58      private List<URIParam> [] dataVectors = null;
59  
60      /** Local reference to the parser service for URI parameter folding */
61      private ParserService parserService;
62  
63      /*
64       * ========================================================================
65       *
66       * Constructors
67       *
68       * ========================================================================
69       *
70       */
71  
72      /**
73       * Empty C'tor. Uses Turbine.getDefaultServerData().
74       */
75      public TurbineURI()
76      {
77          super();
78          init();
79      }
80  
81      /**
82       * Constructor with a RunData object.
83       *
84       * @param runData A RunData object
85       */
86      public TurbineURI(RunData runData)
87      {
88          super(runData);
89          init();
90      }
91  
92      /**
93       * Constructor, set explicit redirection.
94       *
95       * @param runData A RunData object
96       * @param redirect True if redirection allowed.
97       */
98      public TurbineURI(RunData runData, boolean redirect)
99      {
100         super(runData, redirect);
101         init();
102     }
103 
104     /**
105      * Constructor, set Screen.
106      *
107      * @param runData A RunData object
108      * @param screen A Screen Name
109      */
110     public TurbineURI(RunData runData, String screen)
111     {
112         this(runData);
113         setScreen(screen);
114     }
115 
116     /**
117      * Constructor, set Screen, set explicit redirection.
118      *
119      * @param runData A RunData object
120      * @param screen A Screen Name
121      * @param redirect True if redirection allowed.
122      */
123     public TurbineURI(RunData runData, String screen, boolean redirect)
124     {
125         this(runData, redirect);
126         setScreen(screen);
127     }
128 
129     /**
130      * Constructor, set Screen and Action.
131      *
132      * @param runData A RunData object
133      * @param screen A Screen Name
134      * @param action An Action Name
135      */
136     public TurbineURI(RunData runData, String screen, String action)
137     {
138         this(runData, screen);
139         setAction(action);
140     }
141 
142     /**
143      * Constructor, set Screen and Action, set explicit redirection.
144      *
145      * @param runData A RunData object
146      * @param screen A Screen Name
147      * @param action An Action Name
148      * @param redirect True if redirection allowed.
149      */
150     public TurbineURI(RunData runData, String screen, String action, boolean redirect)
151     {
152         this(runData, screen, redirect);
153         setAction(action);
154     }
155 
156     /**
157      * Constructor with a ServerData object.
158      *
159      * @param serverData A ServerData object
160      */
161     public TurbineURI(ServerData serverData)
162     {
163         super(serverData);
164         init();
165     }
166 
167     /**
168      * Constructor, set explicit redirection.
169      *
170      * @param serverData A ServerData object
171      * @param redirect True if redirection allowed.
172      */
173     public TurbineURI(ServerData serverData, boolean redirect)
174     {
175         super(serverData, redirect);
176         init();
177     }
178 
179     /**
180      * Constructor, set Screen.
181      *
182      * @param serverData A ServerData object
183      * @param screen A Screen Name
184      */
185     public TurbineURI(ServerData serverData, String screen)
186     {
187         this(serverData);
188         setScreen(screen);
189     }
190 
191     /**
192      * Constructor, set Screen, set explicit redirection.
193      *
194      * @param serverData A ServerData object
195      * @param screen A Screen Name
196      * @param redirect True if redirection allowed.
197      */
198     public TurbineURI(ServerData serverData, String screen, boolean redirect)
199     {
200         this(serverData, redirect);
201         setScreen(screen);
202     }
203 
204     /**
205      * Constructor, set Screen and Action.
206      *
207      * @param serverData A ServerData object
208      * @param screen A Screen Name
209      * @param action An Action Name
210      */
211     public TurbineURI(ServerData serverData, String screen, String action)
212     {
213         this(serverData, screen);
214         setAction(action);
215     }
216 
217     /**
218      * Constructor, set Screen and Action, set explicit redirection.
219      *
220      * @param serverData A ServerData object
221      * @param screen A Screen Name
222      * @param action An Action Name
223      * @param redirect True if redirection allowed.
224      */
225     public TurbineURI(ServerData serverData, String screen, String action,
226                       boolean redirect)
227     {
228         this(serverData, screen, redirect);
229         setAction(action);
230     }
231 
232     /**
233      * Constructor, user Turbine.getDefaultServerData(), set Screen and Action.
234      *
235      * @param screen A Screen Name
236      * @param action An Action Name
237      */
238     public TurbineURI(String screen, String action)
239     {
240         this();
241         setScreen(screen);
242         setAction(action);
243     }
244 
245     /*
246      * ========================================================================
247      *
248      * Init
249      *
250      * ========================================================================
251      *
252      */
253 
254     /**
255      * Init the TurbineURI.
256      */
257     @SuppressWarnings("unchecked")
258     private void init()
259     {
260         dataVectors = new List[2];
261         dataVectors[PATH_INFO]  = new ArrayList<URIParam>();
262         dataVectors[QUERY_DATA] = new ArrayList<URIParam>();
263         parserService = (ParserService)TurbineServices.getInstance().getService(ParserService.ROLE);
264     }
265 
266     /**
267      * Sets the action= value for this URL.
268      *
269      * By default it adds the information to the path_info instead
270      * of the query data. An empty value (null or "") cleans out
271      * an existing value.
272      *
273      * @param action A String with the action value.
274      */
275     public void setAction(String action)
276     {
277         if(StringUtils.isNotEmpty(action))
278         {
279             add(PATH_INFO, CGI_ACTION_PARAM, action);
280         }
281         else
282         {
283             clearAction();
284         }
285     }
286 
287     /**
288      * Sets the fired eventSubmit= value for this URL.
289      *
290      * @param event The event to fire.
291      *
292      */
293     public void setEvent(String event)
294     {
295         add(PATH_INFO, EVENT_PREFIX + event, event);
296     }
297 
298     /**
299      * Sets the action= and eventSubmit= values for this URL.
300      *
301      * By default it adds the information to the path_info instead
302      * of the query data. An empty value (null or "") for the action cleans out
303      * an existing value.  An empty value (null or "") for the event has no
304      * effect.
305      *
306      * @param action A String with the action value.
307      * @param event A string with the event name.
308      */
309     public void setActionEvent(String action, String event)
310     {
311         setAction(action);
312         if(StringUtils.isNotEmpty(event))
313         {
314             setEvent(event);
315         }
316     }
317 
318     /**
319      * Clears the action= value for this URL.
320      */
321     public void clearAction()
322     {
323         removePathInfo(CGI_ACTION_PARAM);
324     }
325 
326     /**
327      * Sets the screen= value for this URL.
328      *
329      * By default it adds the information to the path_info instead
330      * of the query data. An empty value (null or "") cleans out
331      * an existing value.
332      *
333      * @param screen A String with the screen value.
334      */
335     public void setScreen(String screen)
336     {
337         if(StringUtils.isNotEmpty(screen))
338         {
339             add(PATH_INFO, CGI_SCREEN_PARAM, screen);
340         }
341         else
342         {
343             clearScreen();
344         }
345     }
346 
347     /**
348      * Clears the screen= value for this URL.
349      */
350     public void clearScreen()
351     {
352         removePathInfo(CGI_SCREEN_PARAM);
353     }
354 
355     /*
356      * ========================================================================
357      *
358      * Adding and removing Data from the Path Info and Query Data
359      *
360      * ========================================================================
361      */
362 
363 
364     /**
365      * Adds a name=value pair for every entry in a ParameterParser
366      * object to the path_info string.
367      *
368      * @param pp A ParameterParser.
369      */
370     public void addPathInfo(ParameterParser pp)
371     {
372         add(PATH_INFO, pp);
373     }
374 
375     /**
376      * Adds an existing List of URIParam objects to
377      * the path_info string.
378      *
379      * @param list A list with URIParam objects.
380      */
381     public void addPathInfo(List<URIParam> list)
382     {
383         add(PATH_INFO, list);
384     }
385 
386     /**
387      * Adds a name=value pair to the path_info string.
388      *
389      * @param name A String with the name to add.
390      * @param value An Object with the value to add.
391      */
392     public void addPathInfo(String name, Object value)
393     {
394         add(PATH_INFO, name, null == value ? null : value.toString());
395     }
396 
397     /**
398      * Adds a name=value pair to the path_info string.
399      *
400      * @param name A String with the name to add.
401      * @param value A String with the value to add.
402      */
403     public void addPathInfo(String name, String value)
404     {
405         add(PATH_INFO, name, value);
406     }
407 
408     /**
409      * Adds a name=value pair to the path_info string.
410      *
411      * @param name A String with the name to add.
412      * @param value A double with the value to add.
413      */
414     public void addPathInfo(String name, double value)
415     {
416         add(PATH_INFO, name, Double.toString(value));
417     }
418 
419     /**
420      * Adds a name=value pair to the path_info string.
421      *
422      * @param name A String with the name to add.
423      * @param value An int with the value to add.
424      */
425     public void addPathInfo(String name, int value)
426     {
427         add(PATH_INFO, name, Integer.toString(value));
428     }
429 
430     /**
431      * Adds a name=value pair to the path_info string.
432      *
433      * @param name A String with the name to add.
434      * @param value A long with the value to add.
435      */
436     public void addPathInfo(String name, long value)
437     {
438         add(PATH_INFO, name, Long.toString(value));
439     }
440 
441     /**
442      * Adds a name=value pair to the query string.
443      *
444      * @param name A String with the name to add.
445      * @param value An Object with the value to add.
446      */
447     public void addQueryData(String name, Object value)
448     {
449         add(QUERY_DATA, name, null == value ? null : value.toString());
450     }
451 
452     /**
453      * Adds a name=value pair to the query string.
454      *
455      * @param name A String with the name to add.
456      * @param value A String with the value to add.
457      */
458     public void addQueryData(String name, String value)
459     {
460         add(QUERY_DATA, name, value);
461     }
462 
463     /**
464      * Adds a name=value pair to the query string.
465      *
466      * @param name A String with the name to add.
467      * @param value A double with the value to add.
468      */
469     public void addQueryData(String name, double value)
470     {
471         add(QUERY_DATA, name, Double.toString(value));
472     }
473 
474     /**
475      * Adds a name=value pair to the query string.
476      *
477      * @param name A String with the name to add.
478      * @param value An int with the value to add.
479      */
480     public void addQueryData(String name, int value)
481     {
482         add(QUERY_DATA, name, Integer.toString(value));
483     }
484 
485     /**
486      * Adds a name=value pair to the query string.
487      *
488      * @param name A String with the name to add.
489      * @param value A long with the value to add.
490      */
491     public void addQueryData(String name, long value)
492     {
493         add(QUERY_DATA, name, Long.toString(value));
494     }
495 
496     /**
497      * Adds a name=value pair for every entry in a ParameterParser
498      * object to the query string.
499      *
500      * @param pp A ParameterParser.
501      */
502     public void addQueryData(ParameterParser pp)
503     {
504         add(QUERY_DATA, pp);
505     }
506 
507     /**
508      * Adds an existing List of URIParam objects to the query data.
509      *
510      * @param list A list with URIParam objects.
511      */
512     public void addQueryData(List<URIParam> list)
513     {
514         add(QUERY_DATA, list);
515     }
516 
517     /**
518      * Is Path Info data set in this URI?
519      *
520      * @return true if Path Info has values
521      */
522     public boolean hasPathInfo()
523     {
524         return !dataVectors[PATH_INFO].isEmpty();
525     }
526 
527     /**
528      * Removes all the path info elements.
529      */
530     public void removePathInfo()
531     {
532         dataVectors[PATH_INFO].clear();
533     }
534 
535     /**
536      * Removes a name=value pair from the path info.
537      *
538      * @param name A String with the name to be removed.
539      */
540     public void removePathInfo(String name)
541     {
542         remove(PATH_INFO, name);
543     }
544 
545     /**
546      * Is Query data set in this URI?
547      *
548      * @return true if Query data has values
549      */
550     public boolean hasQueryData()
551     {
552         return !dataVectors[QUERY_DATA].isEmpty();
553     }
554 
555     /**
556      * Removes all the query string elements.
557      */
558     public void removeQueryData()
559     {
560         dataVectors[QUERY_DATA].clear();
561     }
562 
563     /**
564      * Removes a name=value pair from the query string.
565      *
566      * @param name A String with the name to be removed.
567      */
568     public void removeQueryData(String name)
569     {
570         remove (QUERY_DATA, name);
571     }
572 
573     /**
574      * Template Link and friends want to be able to turn the encoding
575      * of the servlet container off. After calling this method,
576      * the no encoding will happen any longer. If you think, that you
577      * need this outside a template context, think again.
578      */
579     public void clearResponse()
580     {
581         setResponse(null);
582     }
583 
584 
585     /**
586      * Builds the URL with all of the data URL-encoded as well as
587      * encoded using HttpServletResponse.encodeUrl(). The resulting
588      * URL is absolute; it starts with http/https...
589      *
590      * <pre>
591      * TurbineURI tui = new TurbineURI (data, "UserScreen");
592      * tui.addPathInfo("user","jon");
593      * tui.getAbsoluteLink();
594      * </pre>
595      *
596      *  The above call to absoluteLink() would return the String:
597      *
598      * <p>
599      * http://www.server.com/servlets/Turbine/screen/UserScreen/user/jon
600      * </p>
601      *
602      * @return A String with the built URL.
603      */
604     public String getAbsoluteLink()
605     {
606         StringBuilder output = new StringBuilder();
607 
608         getSchemeAndPort(output);
609 
610         buildRelativeLink(output);
611 
612         //
613         // Encode Response does all the fixup for the Servlet Container
614         //
615         return encodeResponse(output.toString());
616     }
617 
618     /**
619      * Builds the URL with all of the data URL-encoded as well as
620      * encoded using HttpServletResponse.encodeUrl(). The resulting
621      * URL is relative to the webserver root.
622      *
623      * <pre>
624      * TurbineURI tui = new TurbineURI (data, "UserScreen");
625      * tui.addPathInfo("user","jon");
626      * tui.getRelativeLink();
627      * </pre>
628      *
629      *  The above call to relativeLink() would return the String:
630      *
631      * <p>
632      * /servlets/Turbine/screen/UserScreen/user/jon
633      * </p>
634      *
635      * @return A String with the built URL.
636      */
637     public String getRelativeLink()
638     {
639         StringBuilder output = new StringBuilder();
640 
641         buildRelativeLink(output);
642 
643         //
644         // Encode Response does all the fixup for the Servlet Container
645         //
646         return encodeResponse(output.toString());
647     }
648 
649     /**
650      * Add everything needed for a relative link to the passed StringBuilder.
651      *
652      * @param output A Stringbuffer
653      */
654     private void buildRelativeLink(StringBuilder output)
655     {
656         getContextAndScript(output);
657 
658         if (hasPathInfo())
659         {
660             output.append('/');
661             getPathInfoAsString(output);
662         }
663 
664         if (hasReference())
665         {
666             output.append('#');
667             output.append(getReference());
668         }
669 
670         if (hasQueryData())
671         {
672             output.append('?');
673             getQueryDataAsString(output);
674         }
675     }
676 
677     /**
678      * Gets the current Query Data List.
679      *
680      * @return A List which contains all query data keys. The keys
681      * are URIParam objects.
682      */
683     public List<URIParam> getPathInfo()
684     {
685         return dataVectors[PATH_INFO];
686     }
687 
688     /**
689      * Sets the Query Data List. Replaces the current query data list
690      * with the one supplied. The list must contain only URIParam
691      * objects!
692      *
693      * @param pathInfo A List with new param objects.
694      */
695 
696     public void setPathInfo(List<URIParam> pathInfo)
697     {
698         dataVectors[PATH_INFO] = pathInfo;
699     }
700 
701     /**
702      * Gets the current Query Data List.
703      *
704      * @return A List which contains all query data keys. The keys
705      * are URIParam objects.
706      */
707     public List<URIParam> getQueryData()
708     {
709         return dataVectors[QUERY_DATA];
710     }
711 
712     /**
713      * Sets the Query Data List. Replaces the current query data list
714      * with the one supplied. The list must contain only URIParam
715      * objects!
716      *
717      * @param queryData A List with new param objects.
718      */
719 
720     public void setQueryData(List<URIParam> queryData)
721     {
722         dataVectors[QUERY_DATA] = queryData;
723     }
724 
725     /**
726      * Simply calls getAbsoluteLink(). You should not use this in your
727      * code unless you have to. Use getAbsoluteLink.
728      *
729      * @return This URI as a String
730      *
731      */
732     @Override
733     public String toString()
734     {
735         return getAbsoluteLink();
736     }
737 
738     /*
739      * ========================================================================
740      *
741      * Protected / Private Methods
742      *
743      * ========================================================================
744      *
745      */
746 
747     /**
748      * Returns the Path Info data as a String.
749      *
750      * @param output The StringBuilder that should hold the path info.
751      */
752     private void getPathInfoAsString(StringBuilder output)
753     {
754         doEncode(output, dataVectors[PATH_INFO], '/', '/');
755     }
756 
757     /**
758      * Returns the Query data as a String.
759      *
760      * @param output The StringBuilder that should hold the query data.
761      */
762     private void getQueryDataAsString(StringBuilder output)
763     {
764         doEncode(output, dataVectors[QUERY_DATA], '&', '=');
765     }
766 
767     /**
768      * Does the actual encoding for pathInfoAsString and queryDataAsString.
769      *
770      * @param output The Stringbuffer that should contain the information.
771      * @param list A Collection
772      * @param fieldDelim A char which is used to separate key/value pairs
773      * @param valueDelim A char which is used to separate key and value
774      */
775     private void doEncode(StringBuilder output, Collection<URIParam> list, char fieldDelim, char valueDelim)
776     {
777         if(!list.isEmpty())
778         {
779         	String encoding = parserService.getParameterEncoding();
780 
781             for(Iterator<URIParam> it = list.iterator(); it.hasNext();)
782             {
783             	try
784             	{
785 					URIParam uriParam = it.next();
786 					String key = URLEncoder.encode(uriParam.getKey(), encoding);
787 					String val = null == uriParam.getValue()
788 					        ? null : String.valueOf(uriParam.getValue());
789 
790 					output.append(key);
791 					output.append(valueDelim);
792 
793 					if(StringUtils.isEmpty(val))
794 					{
795 					    if (val == null)
796 					    {
797 				            log.warn("Found a null value for {}", key);
798 					        // For backwards compatibility:
799 					        val = "null";
800 					    }
801 					    output.append(val);
802 					}
803 					else
804 					{
805 					    output.append(URLEncoder.encode(val, encoding));
806 					}
807 
808 					if (it.hasNext())
809 					{
810 					    output.append(fieldDelim);
811 					}
812 				}
813             	catch (UnsupportedEncodingException e)
814             	{
815             		log.warn("Unsupported encoding {}", encoding);
816 				}
817             }
818         }
819     }
820 
821     /**
822      * If the type is PATH_INFO, then add name/value to the pathInfo
823      * hashtable.
824      * <p>
825      * If the type is QUERY_DATA, then add name/value to the queryData
826      * hashtable.
827      *
828      * @param type Type (PATH_INFO or QUERY_DATA) of insertion.
829      * @param name A String with the name to add.
830      * @param value A String with the value to add.
831      */
832     protected void add(int type,
833             String name,
834             String value)
835     {
836         URIParamRIParam.html#URIParam">URIParam uriParam = new URIParam(parserService.convertAndTrim(name), value);
837 
838         dataVectors[type].add(uriParam); // Code so clean you can eat from...
839     }
840 
841     /**
842      * Method for a quick way to add all the parameters in a
843      * ParameterParser.
844      *
845      * <p>If the type is P (0), then add name/value to the pathInfo
846      * hashtable.
847      *
848      * <p>If the type is Q (1), then add name/value to the queryData
849      * hashtable.
850      *
851      * @param type Type of insertion (@see #add(char type, String name, String value))
852      * @param pp A ParameterParser.
853      */
854     protected void add(int type,
855             ParameterParser pp)
856     {
857         for(Iterator<?> it = pp.keySet().iterator(); it.hasNext();)
858         {
859             String key = (String) it.next();
860 
861             if (!key.equalsIgnoreCase(CGI_ACTION_PARAM) &&
862                     !key.equalsIgnoreCase(CGI_SCREEN_PARAM))
863             {
864                 String[] values = pp.getStrings(key);
865                 if(values != null)
866                 {
867                     for (int i = 0; i < values.length; i++)
868                     {
869                         add(type, key, values[i]);
870                     }
871                 }
872                 else
873                 {
874                     add(type, key, "");
875                 }
876             }
877         }
878     }
879 
880     /**
881      * Method for a quick way to add all the parameters in a
882      * List with URIParam objects.
883      *
884      * <p>If the type is P (0), then add name/value to the pathInfo
885      * hashtable.
886      *
887      * <p>If the type is Q (1), then add name/value to the queryData
888      * hashtable.
889      *
890      * @param type Type of insertion (@see #add(char type, String name, String value))
891      * @param list A List of URIParam objects
892      */
893     protected void add(int type, List<URIParam> list)
894     {
895         for (URIParam uriParam : list)
896         {
897             dataVectors[type].add(uriParam);
898         }
899     }
900 
901     /**
902      * If the type is P (0), then remove name/value from the
903      * pathInfo hashtable.
904      *
905      * <p>If the type is Q (1), then remove name/value from the
906      * queryData hashtable.
907      *
908      * @param type Type (P or Q) of removal.
909      * @param name A String with the name to be removed.
910      */
911     protected void remove (int type, String name)
912     {
913         Collection<URIParam> c = dataVectors[type];
914         String key = parserService.convertAndTrim(name);
915 
916         for (Iterator<URIParam> it = c.iterator(); it.hasNext();)
917         {
918             URIParam uriParam = it.next();
919 
920             if (key.equals(uriParam.getKey()))
921             {
922                 it.remove();
923             }
924         }
925     }
926 }