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