001    package org.apache.turbine.util.uri;
002    
003    /*
004     * Licensed to the Apache Software Foundation (ASF) under one
005     * or more contributor license agreements.  See the NOTICE file
006     * distributed with this work for additional information
007     * regarding copyright ownership.  The ASF licenses this file
008     * to you under the Apache License, Version 2.0 (the
009     * "License"); you may not use this file except in compliance
010     * with the License.  You may obtain a copy of the License at
011     *
012     *   http://www.apache.org/licenses/LICENSE-2.0
013     *
014     * Unless required by applicable law or agreed to in writing,
015     * software distributed under the License is distributed on an
016     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017     * KIND, either express or implied.  See the License for the
018     * specific language governing permissions and limitations
019     * under the License.
020     */
021    
022    import java.io.UnsupportedEncodingException;
023    import java.net.URLEncoder;
024    import java.util.ArrayList;
025    import java.util.Collection;
026    import java.util.Iterator;
027    import java.util.List;
028    
029    import org.apache.commons.lang.StringUtils;
030    import org.apache.commons.logging.Log;
031    import org.apache.commons.logging.LogFactory;
032    import org.apache.fulcrum.parser.ParameterParser;
033    import org.apache.fulcrum.parser.ParserService;
034    import org.apache.turbine.services.TurbineServices;
035    import org.apache.turbine.util.RunData;
036    import org.apache.turbine.util.ServerData;
037    
038    /**
039     * This class allows you to keep all the information needed for a single
040     * link at one place. It keeps your query data, path info, the server
041     * scheme, name, port and the script path.
042     *
043     * If you must generate a Turbine Link, use this class.
044     *
045     * @author <a href="mailto:jon@clearink.com">Jon S. Stevens</a>
046     * @author <a href="mailto:jvanzyl@periapt.com">Jason van Zyl</a>
047     * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
048     * @version $Id: TurbineURI.java 1078552 2011-03-06 19:58:46Z tv $
049     */
050    
051    public class TurbineURI
052            extends BaseURI
053    {
054        /** Logging */
055        private static Log log = LogFactory.getLog(TurbineURI.class);
056    
057        /** Contains the PathInfo and QueryData vectors */
058        private List<URIParam> [] dataVectors = null;
059    
060        /** Local reference to the parser service for URI parameter folding */
061        private ParserService parserService;
062    
063        /*
064         * ========================================================================
065         *
066         * Constructors
067         *
068         * ========================================================================
069         *
070         */
071    
072        /**
073         * Empty C'tor. Uses Turbine.getDefaultServerData().
074         */
075        public TurbineURI()
076        {
077            super();
078            init();
079        }
080    
081        /**
082         * Constructor with a RunData object.
083         *
084         * @param runData A RunData object
085         */
086        public TurbineURI(RunData runData)
087        {
088            super(runData);
089            init();
090        }
091    
092        /**
093         * Constructor, set explicit redirection.
094         *
095         * @param runData A RunData object
096         * @param redirect True if redirection allowed.
097         */
098        public TurbineURI(RunData runData, boolean redirect)
099        {
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    }