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 * <a href="$base.param('select','this')">this</a>
48 * <a href="$base.param('select','that')">that</a>
49 *
50 * Toolbox configuration:
51 * <tools>
52 * <toolbox scope="request">
53 * <tool class="org.apache.velocity.tools.view.LinkTool"
54 * forceRelative="true" includeRequestParams="true"/>
55 * </toolbox>
56 * </tools>
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 }