View Javadoc

1   package org.apache.velocity.tools.view;
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.util.Iterator;
23  import java.util.Map;
24  import java.util.Set;
25  import javax.servlet.http.HttpServletRequest;
26  import javax.servlet.http.HttpServletResponse;
27  import org.apache.velocity.tools.generic.ValueParser;
28  import org.apache.velocity.tools.view.ServletUtils;
29  
30  /**
31   * <p>The LinkTool provides many methods to work with URIs and can help you:
32   * <ul>
33   *     <li>construct full URIs (absolute or relative)</li>
34   *     <li>encode session ID into a URI</li>
35   *     <li>retrieve server, port and path info for the current request</li>
36   *     <li>reconstruct or alter the current request URI</li>
37   *     <li>and more..</li>
38   * </ul></p>
39   * 
40   * <p>The LinkTool is somewhat special in that nearly all public methods return
41   * a new instance of LinkTool. This facilitates greatly the repeated use
42   * of the LinkTool in Velocity and leads to an elegant syntax.</p>
43   * 
44   * <p><pre>
45   * Template example(s):
46   *   #set( $base = $link.path('MyPage.vm').anchor('view') )
47   *   &lt;a href="$base.param('select','this')"&gt;this&lt;/a&gt;
48   *   &lt;a href="$base.param('select','that')"&gt;that&lt;/a&gt;
49   *
50   * Toolbox configuration:
51   * &lt;tools&gt;
52   *   &lt;toolbox scope="request"&gt;
53   *     &lt;tool class="org.apache.velocity.tools.view.LinkTool"
54   *              forceRelative="true" includeRequestParams="true"/&gt;
55   *   &lt;/toolbox&gt;
56   * &lt;/tools&gt;
57   * </pre></p>
58   *
59   * <p>This tool may only be used in the request scope.</p>
60   *
61   * @author <a href="mailto:sidler@teamup.com">Gabe Sidler</a>
62   * @author Nathan Bubna
63   * @author Chris Schultz
64   * @since VelocityTools 2.0
65   * @version $Id: LinkTool.java 749726 2009-03-03 20:19:15Z nbubna $
66   */
67  public class LinkTool extends org.apache.velocity.tools.generic.LinkTool
68  {
69      public static final String INCLUDE_REQUEST_PARAMS_KEY = "includeRequestParams";
70  
71      protected HttpServletRequest request;
72      protected HttpServletResponse response;
73      protected boolean includeRequestParams;
74  
75      public LinkTool()
76      {
77          super();
78          includeRequestParams = false;
79      }
80  
81      // --------------------------------------- Setup Methods -------------
82  
83      @Override
84      protected void configure(ValueParser props)
85      {
86          // request values override configured defaults 
87          //NOTE: not sure this is the most intuitive way in all cases;
88          // it might make sense to provide the option of whether req/res
89          // values override configured ones or vice versa.
90          super.configure(props);
91  
92          this.request = (HttpServletRequest)props.getValue(ViewContext.REQUEST);
93          Boolean incParams = props.getBoolean(INCLUDE_REQUEST_PARAMS_KEY);
94          if (incParams != null)
95          {
96              setIncludeRequestParams(incParams);
97          }
98          
99          // set default/start values from request & response
100         this.response =
101             (HttpServletResponse)props.getValue(ViewContext.RESPONSE);
102         setCharacterEncoding(response.getCharacterEncoding());
103         setFromRequest(this.request);
104     }
105 
106     protected void setFromRequest(HttpServletRequest request)
107     {
108         setScheme(request.getScheme());
109         setPort(request.getServerPort());
110         setHost(request.getServerName());
111         // the path we get from ViewToolContext lacks the context path
112         String ctx = request.getContextPath();
113         String pth = ServletUtils.getPath(request);
114         setPath(combinePath(ctx, pth));
115         if (this.includeRequestParams)
116         {
117             setQuery(request.getParameterMap());
118         }
119     }
120 
121     /**
122      * <p>Controls whether or not this tool starts off with all parameters
123      * from the last request automatically.  Default is false.</p>
124      */
125     public void setIncludeRequestParams(boolean includeRequestParams)
126     {
127         this.includeRequestParams = includeRequestParams;
128     }
129 
130     /**
131      * Adds the specified parameters (if they exist) from the current
132      * request to the query data of a copy of this instance.
133      * If no parameters are specified,
134      * then all of the current request's parameters will be added.
135      *
136      * @return A LinkTool object with the specified parameters from
137      *         the current request added to it or all the params if none specified.
138      */
139     public LinkTool addRequestParams(String... butOnlyThese)
140     {
141         return addRequestParams(false, butOnlyThese);
142     }
143 
144     /**
145      * Adds all of the current request's parameters to this link's
146      * "query data" except for those whose keys match any of the specified strings.
147      *
148      * @return A LinkTool object with all of the current request's parameters
149      *         added to it, except those specified.
150      */
151     public LinkTool addRequestParamsExcept(String... ignoreThese)
152     {
153         return addRequestParams(true, ignoreThese);
154     }
155 
156     /**
157      * Adds all of the current request's parameters to this link's
158      * "query data" except for those whose keys match any of the specified strings
159      * or already have a value set for them in the current instance.
160      *
161      * @return A LinkTool object with all of the current request's parameters
162      *         added to it, except those specified or those that already have
163      *         values.
164      */
165     public LinkTool addMissingRequestParams(String... ignoreThese)
166     {
167         String[] these;
168         if (query != null && !query.isEmpty())
169         {
170             Set keys = query.keySet();
171             these = new String[keys.size() + ignoreThese.length];
172             int i = 0;
173             for (; i < ignoreThese.length; i++)
174             {
175                 these[i] = ignoreThese[i];
176             }
177             for (Iterator iter = keys.iterator(); iter.hasNext(); i++)
178             {
179                 these[i] = String.valueOf(iter.next());
180             }
181         }
182         else
183         {
184             these = ignoreThese;
185         }
186         return addRequestParams(true, these);
187     }
188 
189     private LinkTool addRequestParams(boolean ignore, String... special)
190     {
191         LinkTool copy = (LinkTool)duplicate(true);
192         Map reqParams = request.getParameterMap();
193         boolean noSpecial = (special == null || special.length == 0);
194         for (Object e : reqParams.entrySet())
195         {
196             Map.Entry entry = (Map.Entry)e;
197             String key = String.valueOf(entry.getKey());
198             boolean isSpecial = (!noSpecial && contains(special, key));
199             // we actually add the parameter only under three cases:
200             //  take all     not one being ignored     one of the privileged few
201             if (noSpecial || (ignore && !isSpecial) || (!ignore && isSpecial))
202             {
203                 // this is not one being ignored
204                 copy.setParam(key, entry.getValue(), this.appendParams);
205             }
206         }
207         return copy;
208     }
209 
210     private boolean contains(String[] set, String name)
211     {
212         for (String i : set)
213         {
214             if (name.equals(i))
215             {
216                 return true;
217             }
218         }
219         return false;
220     }
221 
222     protected boolean isPathChanged()
223     {
224         if (this.path == self.getPath())
225         {
226             return false;
227         }
228         if (this.path == null)
229         {
230             return true;
231         }
232         return (!this.path.equals(self.getPath()));
233     }
234 
235     /**
236      * <p>Initially, this returns the context path that addresses this web
237      * application, e.g. <code>/myapp</code>. This string starts
238      * with a "/" but does not end with a "/".  If the path has been
239      * changed (e.g. via a call to {@link #path(Object)}), then this will
240      * simply be the first "directory" in the path (i.e. everything from
241      * the start up to the second backslash).
242      * @see #relative(Object)
243      */
244     @Override
245     public String getContextPath()
246     {
247         if (!isPathChanged())
248         {
249             return request.getContextPath();
250         }
251         if (this.path == null || this.opaque)
252         {
253             return null;
254         }
255         int firstInternalSlash = this.path.indexOf('/', 1);
256         if (firstInternalSlash <= 0)
257         {
258             return this.path;
259         }
260         return this.path.substring(0, firstInternalSlash);
261     }
262 
263     /**
264      * <p>Initially, this retrieves the path for the current
265      * request regardless of
266      * whether this is a direct request or an include by the
267      * RequestDispatcher. This string should always start with
268      * a "/".  If the path has been changed (e.g. via a call to
269      * {@link #path(Object)}), then this will
270      * simply be everything in the path after the {@link #getContextPath()}
271      * (i.e. the second "/" in the path and everything after).
272      */
273     public String getRequestPath()
274     {
275         if (this.path == null || this.opaque)
276         {
277             return null;
278         }
279         if (!isPathChanged())
280         {
281             return ServletUtils.getPath(request);
282         }
283         int firstInternalSlash = this.path.indexOf('/', 1);
284         if (firstInternalSlash <= 0)
285         {
286             return this.path;
287         }
288         return this.path.substring(firstInternalSlash, this.path.length());
289     }
290 
291     /**
292      * <p>Returns a URL that addresses the web application. (e.g.
293      * <code>http://myserver.net/myapp/</code>. 
294      * This essentially just replaces the full path with
295      * the {@link #getContextPath()} and removes the anchor and query
296      * data.
297      */
298     public String getContextURL()
299     {
300         LinkTool copy = (LinkTool)duplicate();
301         copy.setQuery(null);
302         copy.setFragment(null);
303         copy.setPath(getContextPath());
304         return copy.toString();
305     }
306 
307     /**
308      * Overrides to use response.encodeURL to get session id into URL
309      * if sessions are used but cookies are not supported.
310      */
311     @Override
312     public String toString()
313     {
314         String str = super.toString();
315         if (str.length() == 0)
316         {
317             // avoid a potential NPE from Tomcat's response.encodeURL impl
318             return str;
319         }
320         else
321         {
322             // encode session ID into URL if sessions are used but cookies are
323             // not supported
324             return response.encodeURL(str);
325         }
326     }
327 
328 }