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.lang.StringUtils;
30 import org.apache.commons.logging.Log;
31 import org.apache.commons.logging.LogFactory;
32 import org.apache.fulcrum.parser.ParameterParser;
33 import org.apache.fulcrum.parser.ParserService;
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 1078552 2011-03-06 19:58:46Z tv $
49 */
50
51 public class TurbineURI
52 extends BaseURI
53 {
54 /** Logging */
55 private static Log log = LogFactory.getLog(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 * <p>
591 * <code><pre>
592 * TurbineURI tui = new TurbineURI (data, "UserScreen");
593 * tui.addPathInfo("user","jon");
594 * tui.getAbsoluteLink();
595 * </pre></code>
596 *
597 * The above call to absoluteLink() would return the String:
598 *
599 * <p>
600 * http://www.server.com/servlets/Turbine/screen/UserScreen/user/jon
601 *
602 * @return A String with the built URL.
603 */
604 public String getAbsoluteLink()
605 {
606 StringBuffer output = new StringBuffer();
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 * <p>
624 * <code><pre>
625 * TurbineURI tui = new TurbineURI (data, "UserScreen");
626 * tui.addPathInfo("user","jon");
627 * tui.getRelativeLink();
628 * </pre></code>
629 *
630 * The above call to relativeLink() would return the String:
631 *
632 * <p>
633 * /servlets/Turbine/screen/UserScreen/user/jon
634 *
635 * @return A String with the built URL.
636 */
637 public String getRelativeLink()
638 {
639 StringBuffer output = new StringBuffer();
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 StringBuffer.
651 *
652 * @param output A Stringbuffer
653 */
654 private void buildRelativeLink(StringBuffer 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 StringBuffer that should hold the path info.
751 */
752 private void getPathInfoAsString(StringBuffer output)
753 {
754 doEncode(output, dataVectors[PATH_INFO], '/', '/');
755 }
756
757 /**
758 * Returns the Query data as a String.
759 *
760 * @param output The StringBuffer that should hold the query data.
761 */
762 private void getQueryDataAsString(StringBuffer 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(StringBuffer 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 if (log.isWarnEnabled())
798 {
799 log.warn("Found a null value for " + key);
800 }
801 // For backwards compatibility:
802 val = "null";
803 }
804 output.append(val);
805 }
806 else
807 {
808 output.append(URLEncoder.encode(val, encoding));
809 }
810
811 if (it.hasNext())
812 {
813 output.append(fieldDelim);
814 }
815 }
816 catch (UnsupportedEncodingException e)
817 {
818 log.warn("Unsupported encoding " + encoding);
819 }
820 }
821 }
822 }
823
824 /**
825 * If the type is PATH_INFO, then add name/value to the pathInfo
826 * hashtable.
827 * <p>
828 * If the type is QUERY_DATA, then add name/value to the queryData
829 * hashtable.
830 *
831 * @param type Type (PATH_INFO or QUERY_DATA) of insertion.
832 * @param name A String with the name to add.
833 * @param value A String with the value to add.
834 */
835 protected void add(int type,
836 String name,
837 String value)
838 {
839 URIParam uriParam = new URIParam(parserService.convertAndTrim(name), value);
840
841 dataVectors[type].add(uriParam); // Code so clean you can eat from...
842 }
843
844 /**
845 * Method for a quick way to add all the parameters in a
846 * ParameterParser.
847 *
848 * <p>If the type is P (0), then add name/value to the pathInfo
849 * hashtable.
850 *
851 * <p>If the type is Q (1), then add name/value to the queryData
852 * hashtable.
853 *
854 * @param type Type of insertion (@see #add(char type, String name, String value))
855 * @param pp A ParameterParser.
856 */
857 protected void add(int type,
858 ParameterParser pp)
859 {
860 for(Iterator<?> it = pp.keySet().iterator(); it.hasNext();)
861 {
862 String key = (String) it.next();
863
864 if (!key.equalsIgnoreCase(CGI_ACTION_PARAM) &&
865 !key.equalsIgnoreCase(CGI_SCREEN_PARAM))
866 {
867 String[] values = pp.getStrings(key);
868 if(values != null)
869 {
870 for (int i = 0; i < values.length; i++)
871 {
872 add(type, key, values[i]);
873 }
874 }
875 else
876 {
877 add(type, key, "");
878 }
879 }
880 }
881 }
882
883 /**
884 * Method for a quick way to add all the parameters in a
885 * List with URIParam objects.
886 *
887 * <p>If the type is P (0), then add name/value to the pathInfo
888 * hashtable.
889 *
890 * <p>If the type is Q (1), then add name/value to the queryData
891 * hashtable.
892 *
893 * @param type Type of insertion (@see #add(char type, String name, String value))
894 * @param list A List of URIParam objects
895 */
896 protected void add(int type, List<URIParam> list)
897 {
898 for (URIParam uriParam : list)
899 {
900 dataVectors[type].add(uriParam);
901 }
902 }
903
904 /**
905 * If the type is P (0), then remove name/value from the
906 * pathInfo hashtable.
907 *
908 * <p>If the type is Q (1), then remove name/value from the
909 * queryData hashtable.
910 *
911 * @param type Type (P or Q) of removal.
912 * @param name A String with the name to be removed.
913 */
914 protected void remove (int type, String name)
915 {
916 Collection<URIParam> c = dataVectors[type];
917 String key = parserService.convertAndTrim(name);
918
919 for (Iterator<URIParam> it = c.iterator(); it.hasNext();)
920 {
921 URIParam uriParam = it.next();
922
923 if (key.equals(uriParam.getKey()))
924 {
925 it.remove();
926 }
927 }
928 }
929 }