KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > velocity > tools > view > tools > LinkTool


1 /*
2  * Copyright 2003 The Apache Software Foundation.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16
17 package org.apache.velocity.tools.view.tools;
18
19 import java.lang.reflect.InvocationTargetException JavaDoc;
20 import java.lang.reflect.Method JavaDoc;
21 import java.net.URLEncoder JavaDoc;
22 import java.util.ArrayList JavaDoc;
23 import java.util.List JavaDoc;
24
25 import javax.servlet.http.HttpServletRequest JavaDoc;
26 import javax.servlet.http.HttpServletResponse JavaDoc;
27 import javax.servlet.http.HttpSession JavaDoc;
28 import javax.servlet.ServletContext JavaDoc;
29
30 import org.apache.velocity.app.Velocity;
31 import org.apache.velocity.tools.view.context.ViewContext;
32 import org.apache.velocity.tools.view.tools.ViewTool;
33
34 /**
35  * View tool to make building URIs pleasant and fun!
36  * <p><pre>
37  * Template example(s):
38  * #set( $base = $link.setRelative('MyPage.vm').setAnchor('view') )
39  * &lt;a HREF="$base.addQueryData('select','this')"&gt;this&lt;/a&gt;
40  * &lt;a HREF="$base.addQueryData('select','that')"&gt;that&lt;/a&gt;
41  *
42  * Toolbox configuration:
43  * &lt;tool&gt;
44  * &lt;key&gt;link&lt;/key&gt;
45  * &lt;scope&gt;request&lt;/scope&gt;
46  * &lt;class&gt;org.apache.velocity.tools.view.tools.LinkTool&lt;/class&gt;
47  * &lt;/tool&gt;
48  * </pre></p>
49  *
50  * <p>This tool should only be used in the request scope.</p>
51  *
52  * @author <a HREF="mailto:sidler@teamup.com">Gabe Sidler</a>
53  * @author <a HREF="mailto:nathan@esha.com">Nathan Bubna</a>
54  * @since VelocityTools 1.0
55  * @version $Id: LinkTool.java,v 1.14.2.1 2004/03/12 20:16:28 nbubna Exp $
56  */

57 public class LinkTool implements ViewTool, Cloneable JavaDoc
58 {
59
60
61     /** Standard HTML delimiter for query data ('&') */
62     public static final String JavaDoc HTML_QUERY_DELIMITER = "&";
63
64     /** XHTML delimiter for query data ('&amp;amp;') */
65     public static final String JavaDoc XHTML_QUERY_DELIMITER = "&amp;";
66
67
68     /** A reference to the ServletContext */
69     protected ServletContext JavaDoc application;
70
71     /** A reference to the HttpServletRequest. */
72     protected HttpServletRequest JavaDoc request;
73
74     /** A reference to the HttpServletResponse. */
75     protected HttpServletResponse JavaDoc response;
76
77
78     /** The URI reference set for this link. */
79     private String JavaDoc uri;
80
81     /** The anchor set for this link. */
82     private String JavaDoc anchor;
83
84     /** A list of query string parameters. */
85     private ArrayList JavaDoc queryData;
86
87     /** The current delimiter for query data */
88     private String JavaDoc queryDataDelim;
89
90     
91     /** Java 1.4 encode method to use instead of deprecated 1.3 version. */
92     private static Method JavaDoc encode = null;
93     
94     /* Initialize the encode variable with the 1.4 method if available.
95      * this code was adapted from org.apache.struts.utils.RequestUtils */

96     static
97     {
98         try
99         {
100             /* get version of encode method with two String args */
101             Class JavaDoc[] args = new Class JavaDoc[] { String JavaDoc.class, String JavaDoc.class };
102             encode = URLEncoder JavaDoc.class.getMethod("encode", args);
103         }
104         catch (NoSuchMethodException JavaDoc e)
105         {
106             Velocity.debug("LinkTool: Can't find JDK 1.4 encode method. Using JDK 1.3 version.");
107         }
108     }
109
110
111     /**
112      * Default constructor. Tool must be initialized before use.
113      */

114     public LinkTool()
115     {
116         uri = null;
117         anchor = null;
118         queryData = null;
119         queryDataDelim = HTML_QUERY_DELIMITER;
120     }
121
122
123     // --------------------------------------- Protected Methods -------------
124

125     /**
126      * <p>Controls the delimiter used for separating query data pairs.
127      * By default, the standard '&' character is used.</p>
128      * <p>This is not exposed to templates as this decision is best not
129      * made at that level.</p>
130      * <p>Subclasses may easily override the init() method to set this
131      * appropriately and then call super.init()</p>
132      *
133      * @param useXhtml if true, the XHTML query data delimiter ('&amp;amp;')
134      * will be used. if false, then '&' will be used.
135      * @see <a HREF="http://www.w3.org/TR/xhtml1/#C_12">Using Ampersands in Attribute Values (and Elsewhere)</a>
136      */

137     protected void setXhtml(boolean useXhtml)
138     {
139         queryDataDelim =
140             (useXhtml) ? XHTML_QUERY_DELIMITER : HTML_QUERY_DELIMITER;
141     }
142
143
144     /**
145      * For internal use.
146      *
147      * Copies 'that' LinkTool into this one and adds the new query data.
148      *
149      * @param pair the query parameter to add
150      */

151     protected LinkTool copyWith(QueryPair pair)
152     {
153         LinkTool copy = duplicate();
154         if (copy.queryData != null)
155         {
156             // set the copy's query data to a shallow clone of
157
// the current query data array
158
copy.queryData = (ArrayList JavaDoc)this.queryData.clone();
159         }
160         else
161         {
162             copy.queryData = new ArrayList JavaDoc();
163         }
164         //add new pair to this LinkTool's query data
165
copy.queryData.add(pair);
166         return copy;
167     }
168
169
170     /**
171      * For internal use.
172      *
173      * Copies 'that' LinkTool into this one and sets the new URI.
174      *
175      * @param uri uri string
176      */

177     protected LinkTool copyWith(String JavaDoc uri)
178     {
179         LinkTool copy = duplicate();
180         copy.uri = uri;
181         return copy;
182     }
183
184
185     /**
186      * For internal use.
187      *
188      * Copies 'that' LinkTool into this one and sets the new
189      * anchor for the link.
190      *
191      * @param anchor URI string
192      */

193     protected LinkTool copyWithAnchor(String JavaDoc anchor)
194     {
195         LinkTool copy = duplicate();
196         copy.anchor = anchor;
197         return copy;
198     }
199
200
201     /**
202      * This is just to avoid duplicating this code for both copyWith() methods
203      */

204     private LinkTool duplicate()
205     {
206         try
207         {
208             return (LinkTool)this.clone();
209         }
210         catch (CloneNotSupportedException JavaDoc e)
211         {
212             Velocity.warn("LinkTool: could not properly clone " + getClass() +
213                           " - " + e);
214
215             // "clone" manually
216
LinkTool copy;
217             try
218             {
219                 // one last try for a subclass instance...
220
copy = (LinkTool)getClass().newInstance();
221             }
222             catch (Exception JavaDoc ee)
223             {
224                 // fine, we'll use the base class
225
copy = new LinkTool();
226             }
227             copy.application = this.application;
228             copy.request = this.request;
229             copy.response = this.response;
230             copy.uri = this.uri;
231             copy.anchor = this.anchor;
232             copy.queryData = this.queryData;
233             copy.queryDataDelim = this.queryDataDelim;
234             return copy;
235         }
236     }
237
238
239     // --------------------------------------------- ViewTool Interface -------
240

241     /**
242      * Initializes this tool.
243      *
244      * @param obj the current ViewContext
245      * @throws IllegalArgumentException if the param is not a ViewContext
246      */

247     public void init(Object JavaDoc obj)
248     {
249         if (!(obj instanceof ViewContext))
250         {
251             throw new IllegalArgumentException JavaDoc("Tool can only be initialized with a ViewContext");
252         }
253  
254         ViewContext context = (ViewContext)obj;
255         this.request = context.getRequest();
256         this.response = context.getResponse();
257         this.application = context.getServletContext();
258         Boolean JavaDoc b = (Boolean JavaDoc)context.getAttribute(ViewContext.XHTML);
259         if (b != null)
260         {
261             setXhtml(b.booleanValue());
262         }
263     }
264
265
266     // --------------------------------------------- Template Methods -----------
267

268     /**
269      * <p>Returns a copy of the link with the specified anchor to be
270      * added to the end of the generated hyperlink.</p>
271      *
272      * Example:<br>
273      * <code>&lt;a HREF='$link.setAnchor("foo")'&gt;Foo&lt;/a&gt;</code><br>
274      * produces something like</br>
275      * <code>&lt;a HREF="#foo"&gt;Foo&lt;/a&gt;</code><br>
276      *
277      * @param anchor an internal document reference
278      *
279      * @return a new instance of LinkTool with the set anchor
280      */

281     public LinkTool setAnchor(String JavaDoc anchor)
282     {
283         return copyWithAnchor(anchor);
284     }
285
286
287     /**
288      * Returns the anchor (internal document reference) set for this link.
289      */

290     public String JavaDoc getAnchor()
291     {
292         return anchor;
293     }
294
295
296     /**
297      * <p>Returns a copy of the link with the specified context-relative
298      * URI reference converted to a server-relative URI reference. This
299      * method will overwrite any previous URI reference settings but will
300      * copy the query string.</p>
301      *
302      * Example:<br>
303      * <code>&lt;a HREF='$link.setRelative("/templates/login/index.vm")'&gt;Login Page&lt;/a&gt;</code><br>
304      * produces something like</br>
305      * <code>&lt;a HREF="../../../../../../../myapp/templates/login/index.vm"&gt;Login Page&lt;/a&gt;</code><br>
306      *
307      * @param uri A context-relative URI reference. A context-relative URI
308      * is a URI that is relative to the root of this web application.
309      *
310      * @return a new instance of LinkTool with the specified URI
311      */

312     public LinkTool setRelative(String JavaDoc uri)
313     {
314         if (uri.startsWith("/"))
315         {
316             return copyWith(request.getContextPath() + uri);
317         }
318         else
319         {
320             return copyWith(request.getContextPath() + '/' + uri);
321         }
322     }
323
324
325     /**
326      * <p>Returns a copy of the link with the given URI reference set.
327      * No conversions are applied to the given URI reference. The URI
328      * reference can be absolute, server-relative, relative and may
329      * contain query parameters. This method will overwrite any
330      * previous URI reference settings but will copy the query
331      * string.</p>
332      *
333      * @param uri URI reference to set
334      *
335      * @return a new instance of LinkTool
336      */

337     public LinkTool setURI(String JavaDoc uri)
338     {
339         return copyWith(uri);
340     }
341
342
343     /**
344      * <p>Returns the current URI of this link as set by the setURI(String)
345      * or setRelative(String) methods. Any conversions
346      * have been applied. The returned URI reference does not include query
347      * data that was added with method addQueryData().</p>
348      */

349     public String JavaDoc getURI()
350     {
351         return uri;
352     }
353
354
355     /**
356      * <p>Adds a key=value pair to the query data. This returns a new LinkTool
357      * containing both a copy of this LinkTool's query data and the new data.
358      * Query data is URL encoded before it is appended.</p>
359      *
360      * @param key key of new query parameter
361      * @param value value of new query parameter
362      *
363      * @return a new instance of LinkTool
364      */

365     public LinkTool addQueryData(String JavaDoc key, Object JavaDoc value)
366     {
367         return copyWith(new QueryPair(key, value));
368     }
369
370     
371     /**
372      * <p>Returns this link's query data as a url-encoded string e.g.
373      * <code>key=value&foo=this+is+encoded</code>.</p>
374      */

375     public String JavaDoc getQueryData()
376     {
377         if (queryData != null && !queryData.isEmpty())
378         {
379
380             StringBuffer JavaDoc out = new StringBuffer JavaDoc();
381             for(int i=0; i < queryData.size(); i++)
382             {
383                 out.append(queryData.get(i));
384                 if (i+1 < queryData.size())
385                 {
386                     out.append(queryDataDelim);
387                 }
388             }
389             return out.toString();
390         }
391         return null;
392     }
393
394
395     /**
396      * <p>Returns the URI that addresses this web application. E.g.
397      * <code>http://myserver.net/myapp</code>. This string does not end
398      * with a "/". Note! This will not represent any URI reference or
399      * query data set for this LinkTool.</p>
400      */

401     public String JavaDoc getContextURL()
402     {
403         String JavaDoc scheme = request.getScheme();
404         int port = request.getServerPort();
405
406         StringBuffer JavaDoc out = new StringBuffer JavaDoc();
407         out.append(request.getScheme());
408         out.append("://");
409         out.append(request.getServerName());
410         if ((scheme.equals("http") && port != 80) ||
411             (scheme.equals("https") && port != 443))
412         {
413             out.append(':');
414             out.append(port);
415         }
416         out.append(request.getContextPath());
417         return out.toString();
418     }
419
420
421     /**
422      * <p>Returns the context path that addresses this web
423      * application, e.g. <code>/myapp</code>. This string starts
424      * with a "/" but does not end with a "/" Note! This will not
425      * represent any URI reference or query data set for this
426      * LinkTool.</p>
427      */

428     public String JavaDoc getContextPath()
429     {
430         return request.getContextPath();
431     }
432
433
434     /**
435      * Returns the full URI of this template without any query data.
436      * e.g. <code>http://myserver.net/myapp/stuff/View.vm</code>
437      * Note! The returned String will not represent any URI reference
438      * or query data set for this LinkTool. A typical application of
439      * this method is with the HTML base tag. For example:
440      * <code>&lt;base HREF="$link.baseRef"&gt;</code>
441      */

442     public String JavaDoc getBaseRef()
443     {
444         StringBuffer JavaDoc out = new StringBuffer JavaDoc();
445         out.append(getContextURL());
446         out.append(request.getServletPath());
447         return out.toString();
448     }
449
450
451     /**
452      * Returns the full URI reference that's been built with this tool,
453      * including the query string and anchor, e.g.
454      * <code>http://myserver.net/myapp/stuff/View.vm?id=42&type=blue#foo</code>.
455      * Typically, it is not necessary to call this method explicitely.
456      * Velocity will call the toString() method automatically to obtain
457      * a representable version of an object.
458      */

459     public String JavaDoc toString()
460     {
461         StringBuffer JavaDoc out = new StringBuffer JavaDoc();
462
463         if (uri != null)
464         {
465             out.append(uri);
466         }
467
468         String JavaDoc query = getQueryData();
469         if (query != null)
470         {
471             // Check if URI already contains query data
472
if ( uri == null || uri.indexOf('?') < 0)
473             {
474                 // no query data yet, start query data with '?'
475
out.append('?');
476             }
477             else
478             {
479                 // there is already query data, use data delimiter
480
out.append(queryDataDelim);
481             }
482             out.append(query);
483         }
484
485         if (anchor != null)
486         {
487             out.append('#');
488             out.append(encodeURL(anchor));
489         }
490
491         // encode session ID into URL if sessions are used but cookies are
492
// not supported
493
return response.encodeURL(out.toString());
494     }
495
496
497     /**
498      * Use the new URLEncoder.encode() method from java 1.4 if available, else
499      * use the old deprecated version. This method uses reflection to find the appropriate
500      * method; if the reflection operations throw exceptions, this will return the url
501      * encoded with the old URLEncoder.encode() method.
502      *
503      * @return String - the encoded url.
504      */

505     public String JavaDoc encodeURL(String JavaDoc url)
506     {
507         /* first try encoding with new 1.4 method */
508         if (encode != null)
509         {
510             try
511             {
512                 Object JavaDoc[] args =
513                     new Object JavaDoc[] { url, this.response.getCharacterEncoding() };
514                 return (String JavaDoc)encode.invoke(null, args);
515             }
516             catch (IllegalAccessException JavaDoc e)
517             {
518                 // don't keep trying if we get one of these
519
encode = null;
520
521                 Velocity.debug("LinkTool: Can't access JDK 1.4 encode method ("
522                                + e + "). Using deprecated version from now on.");
523             }
524             catch (InvocationTargetException JavaDoc e)
525             {
526                 Velocity.debug("LinkTool: Error using JDK 1.4 encode method ("
527                                + e + "). Using deprecated version.");
528             }
529         }
530         return URLEncoder.encode(url);
531     }
532
533
534   
535     // --------------------------------------------- Internal Class -----------
536

537     /**
538      * Internal util class to handle representation and
539      * encoding of key/value pairs in the query string
540      */

541     protected final class QueryPair
542     {
543
544         private final String JavaDoc key;
545         private final Object JavaDoc value;
546
547
548         /**
549          * Construct a new query pair.
550          *
551          * @param key query pair
552          * @param value query value
553          */

554         public QueryPair(String JavaDoc key, Object JavaDoc value)
555         {
556             this.key = key;
557             this.value = value;
558         }
559
560         /**
561          * Return the URL-encoded query string.
562          */

563         public String JavaDoc toString()
564         {
565             StringBuffer JavaDoc out = new StringBuffer JavaDoc();
566             if (value == null)
567             {
568                 out.append(encodeURL(key));
569                 out.append('=');
570                 /* Interpret null as "no value" */
571             }
572             else if (value instanceof List JavaDoc)
573             {
574                 appendAsArray(out, key, ((List JavaDoc)value).toArray());
575             }
576             else if (value instanceof Object JavaDoc[])
577             {
578                 appendAsArray(out, key, (Object JavaDoc[])value);
579             }
580             else
581             {
582                 out.append(encodeURL(key));
583                 out.append('=');
584                 out.append(encodeURL(String.valueOf(value)));
585             }
586             return out.toString();
587         }
588
589         /* Utility method to avoid logic duplication in toString() */
590         private void appendAsArray(StringBuffer JavaDoc out, String JavaDoc key, Object JavaDoc[] arr)
591         {
592             String JavaDoc encKey = encodeURL(key);
593             for (int i=0; i < arr.length; i++)
594             {
595                 out.append(encKey);
596                 out.append('=');
597                 if (arr[i] != null)
598                 {
599                     out.append(encodeURL(String.valueOf(arr[i])));
600                 }
601                 if (i+1 < arr.length)
602                 {
603                     out.append(queryDataDelim);
604                 }
605             }
606         }
607
608     }
609
610
611 }
612
Popular Tags