View Javadoc

1   /*
2    * $Id: ServletUrlRenderer.java 768855 2009-04-27 02:09:35Z wesw $
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  package org.apache.struts2.components;
23  
24  import com.opensymphony.xwork2.ActionContext;
25  import com.opensymphony.xwork2.ActionInvocation;
26  import com.opensymphony.xwork2.config.entities.ActionConfig;
27  import com.opensymphony.xwork2.inject.Inject;
28  import com.opensymphony.xwork2.util.logging.Logger;
29  import com.opensymphony.xwork2.util.logging.LoggerFactory;
30  import org.apache.struts2.StrutsException;
31  import org.apache.struts2.dispatcher.mapper.ActionMapper;
32  import org.apache.struts2.dispatcher.mapper.ActionMapping;
33  import org.apache.struts2.views.util.UrlHelper;
34  import org.apache.commons.lang.xwork.StringUtils;
35  
36  import java.io.IOException;
37  import java.io.Writer;
38  import java.util.Collections;
39  import java.util.Iterator;
40  import java.util.LinkedHashMap;
41  import java.util.Map;
42  
43  /***
44   * Implementation of the {@link UrlRenderer} interface that creates URLs suitable in a servlet environment.
45   * 
46   */
47  public class ServletUrlRenderer implements UrlRenderer {
48      /***
49       * Provide a logging instance.
50       */
51      private static final Logger LOG = LoggerFactory.getLogger(ServletUrlRenderer.class);
52  
53      private ActionMapper actionMapper;
54  
55      @Inject
56      public void setActionMapper(ActionMapper mapper) {
57          this.actionMapper = mapper;
58      }
59  
60  
61      /***
62  	 * {@inheritDoc}
63  	 */
64  	public void renderUrl(Writer writer, UrlProvider urlComponent) {
65  		String scheme = urlComponent.getHttpServletRequest().getScheme();
66  
67  		if (urlComponent.getScheme() != null) {
68  			scheme = urlComponent.getScheme();
69  		}
70  
71  	       String result;
72  	       ActionInvocation ai = (ActionInvocation) ActionContext.getContext().get(ActionContext.ACTION_INVOCATION);
73  	        if (urlComponent.getValue() == null && urlComponent.getAction() != null) {
74  	                result = urlComponent.determineActionURL(urlComponent.getAction(), urlComponent.getNamespace(), urlComponent.getMethod(), urlComponent.getHttpServletRequest(), urlComponent.getHttpServletResponse(), urlComponent.getParameters(), scheme, urlComponent.isIncludeContext(), urlComponent.isEncode(), urlComponent.isForceAddSchemeHostAndPort(), urlComponent.isEscapeAmp());
75  	        } else if (urlComponent.getValue() == null && urlComponent.getAction() == null && ai != null) {
76  	                // both are null, we will default to the current action
77  
78  	                final String action = ai.getProxy().getActionName();
79  	                final String namespace = ai.getProxy().getNamespace();
80  	                result = urlComponent.determineActionURL(action, namespace, urlComponent.getMethod(),urlComponent.getHttpServletRequest(), urlComponent.getHttpServletResponse(), urlComponent.getParameters(), scheme, urlComponent.isIncludeContext(), urlComponent.isEncode(), urlComponent.isForceAddSchemeHostAndPort(), urlComponent.isEscapeAmp());
81  	        } else {
82  	                String _value = urlComponent.getValue();
83  
84  	                // We don't include the request parameters cause they would have been
85  	                // prioritised before this [in start(Writer) method]
86  	                if (_value != null && _value.indexOf("?") > 0) {
87  	                    _value = _value.substring(0, _value.indexOf("?"));
88  	                }
89  	                result = UrlHelper.buildUrl(_value, urlComponent.getHttpServletRequest(), urlComponent.getHttpServletResponse(), urlComponent.getParameters(), scheme, urlComponent.isIncludeContext(), urlComponent.isEncode(), urlComponent.isForceAddSchemeHostAndPort(), urlComponent.isEscapeAmp());
90  	        }
91              String anchor = urlComponent.getAnchor();
92  	        if (StringUtils.isNotEmpty(anchor)) {
93  	        	result += '#' + urlComponent.findString(anchor);
94  	        }
95  
96          if (urlComponent.isPutInContext()) {
97              String var = urlComponent.getVar();
98              if (StringUtils.isNotEmpty(var)) {
99                  urlComponent.putInContext(result);
100 
101                 // add to the request and page scopes as well
102                 urlComponent.getHttpServletRequest().setAttribute(var, result);
103             } else {
104                 try {
105                     writer.write(result);
106                 } catch (IOException e) {
107                     throw new StrutsException("IOError: " + e.getMessage(), e);
108                 }
109             }
110         } else {
111             try {
112                 writer.write(result);
113             } catch (IOException e) {
114                 throw new StrutsException("IOError: " + e.getMessage(), e);
115             }
116         }
117 	}
118 
119 	/***
120 	 * {@inheritDoc}
121 	 */
122 	public void renderFormUrl(Form formComponent) {
123 		String namespace = formComponent.determineNamespace(formComponent.namespace, formComponent.getStack(),
124 				formComponent.request);
125 		String action;
126 
127 		if(formComponent.action != null) {
128 			action = formComponent.findString(formComponent.action);
129 		} else {
130 			// no action supplied? ok, then default to the current request
131 			// (action or general URL)
132 			ActionInvocation ai = (ActionInvocation) formComponent.getStack().getContext().get(
133 					ActionContext.ACTION_INVOCATION);
134 			if (ai != null) {
135 				action = ai.getProxy().getActionName();
136 				namespace = ai.getProxy().getNamespace();
137 			} else {
138 				// hmm, ok, we need to just assume the current URL cut down
139 				String uri = formComponent.request.getRequestURI();
140 				action = uri.substring(uri.lastIndexOf('/'));
141 			}
142 		}
143 
144         ActionMapping nameMapping = actionMapper.getMappingFromActionName(action);
145         String actionName = nameMapping.getName();
146         String actionMethod = nameMapping.getMethod();
147 
148 		final ActionConfig actionConfig = formComponent.configuration.getRuntimeConfiguration().getActionConfig(
149 				namespace, actionName);
150 		if (actionConfig != null) {
151 
152 			ActionMapping mapping = new ActionMapping(actionName, namespace, actionMethod, formComponent.parameters);
153 			String result = UrlHelper.buildUrl(formComponent.actionMapper.getUriFromActionMapping(mapping),
154 					formComponent.request, formComponent.response, null);
155 			formComponent.addParameter("action", result);
156 
157 			// let's try to get the actual action class and name
158 			// this can be used for getting the list of validators
159 			formComponent.addParameter("actionName", actionName);
160 			try {
161 				Class clazz = formComponent.objectFactory.getClassInstance(actionConfig.getClassName());
162 				formComponent.addParameter("actionClass", clazz);
163 			} catch (ClassNotFoundException e) {
164 				// this is OK, we'll just move on
165 			}
166 
167 			formComponent.addParameter("namespace", namespace);
168 
169 			// if the name isn't specified, use the action name
170 			if (formComponent.name == null) {
171 				formComponent.addParameter("name", actionName);
172 			}
173 
174 			// if the id isn't specified, use the action name
175 			if (formComponent.getId() == null  && actionName!=null ) {
176 				formComponent.addParameter("id", formComponent.escape(actionName));
177 			}
178 		} else if (action != null) {
179 			// Since we can't find an action alias in the configuration, we just
180 			// assume the action attribute supplied is the path to be used as
181 			// the URI this form is submitting to.
182 
183             // Warn user that the specified namespace/action combo
184             // was not found in the configuration.
185             if (namespace != null) {
186               LOG.warn("No configuration found for the specified action: '" + actionName + "' in namespace: '" + namespace + "'. Form action defaulting to 'action' attribute's literal value.");
187             }
188 
189 			String result = UrlHelper.buildUrl(action, formComponent.request, formComponent.response, null);
190 			formComponent.addParameter("action", result);
191 
192 			// namespace: cut out anything between the start and the last /
193 			int slash = result.lastIndexOf('/');
194 			if (slash != -1) {
195 				formComponent.addParameter("namespace", result.substring(0, slash));
196 			} else {
197 				formComponent.addParameter("namespace", "");
198 			}
199 
200 			// name/id: cut out anything between / and . should be the id and
201 			// name
202 			String id = formComponent.getId();
203 			if (id == null) {
204 				slash = result.lastIndexOf('/');
205 				int dot = result.indexOf('.', slash);
206 				if (dot != -1) {
207 					id = result.substring(slash + 1, dot);
208 				} else {
209 					id = result.substring(slash + 1);
210 				}
211 				formComponent.addParameter("id", formComponent.escape(id));
212 			}
213 		}
214 
215 		// WW-1284
216 		// evaluate if client-side js is to be enabled. (if validation
217 		// interceptor does allow validation eg. method is not filtered out)
218 		formComponent.evaluateClientSideJsEnablement(actionName, namespace, actionMethod);
219 	}
220 
221 
222 	public void beforeRenderUrl(UrlProvider urlComponent) {
223 		if (urlComponent.getValue() != null) {
224             urlComponent.setValue(urlComponent.findString(urlComponent.getValue()));
225         }
226 
227         // no explicit url set so attach params from current url, do
228         // this at start so body params can override any of these they wish.
229         try {
230             // ww-1266
231             String includeParams = (urlComponent.getUrlIncludeParams() != null ? urlComponent.getUrlIncludeParams().toLowerCase() : UrlProvider.GET);
232 
233             if (urlComponent.getIncludeParams() != null) {
234                 includeParams = urlComponent.findString(urlComponent.getIncludeParams());
235             }
236 
237             if (UrlProvider.NONE.equalsIgnoreCase(includeParams)) {
238                 mergeRequestParameters(urlComponent.getValue(), urlComponent.getParameters(), Collections.EMPTY_MAP);
239             } else if (UrlProvider.ALL.equalsIgnoreCase(includeParams)) {
240                 mergeRequestParameters(urlComponent.getValue(), urlComponent.getParameters(), urlComponent.getHttpServletRequest().getParameterMap());
241 
242                 // for ALL also include GET parameters
243                 includeGetParameters(urlComponent);
244                 includeExtraParameters(urlComponent);
245             } else if (UrlProvider.GET.equalsIgnoreCase(includeParams) || (includeParams == null && urlComponent.getValue() == null && urlComponent.getAction() == null)) {
246                 includeGetParameters(urlComponent);
247                 includeExtraParameters(urlComponent);
248             } else if (includeParams != null) {
249                 LOG.warn("Unknown value for includeParams parameter to URL tag: " + includeParams);
250             }
251         } catch (Exception e) {
252             LOG.warn("Unable to put request parameters (" + urlComponent.getHttpServletRequest().getQueryString() + ") into parameter map.", e);
253         }
254 
255 		
256 	}
257 	
258     private void includeExtraParameters(UrlProvider urlComponent) {
259         if (urlComponent.getExtraParameterProvider() != null) {
260             mergeRequestParameters(urlComponent.getValue(), urlComponent.getParameters(), urlComponent.getExtraParameterProvider().getExtraParameters());
261         }
262     }
263     private void includeGetParameters(UrlProvider urlComponent) {
264     	String query = extractQueryString(urlComponent);
265     	mergeRequestParameters(urlComponent.getValue(), urlComponent.getParameters(), UrlHelper.parseQueryString(query));
266     }
267 
268     private String extractQueryString(UrlProvider urlComponent) {
269         // Parse the query string to make sure that the parameters come from the query, and not some posted data
270         String query = urlComponent.getHttpServletRequest().getQueryString();
271         if (query == null) {
272             query = (String) urlComponent.getHttpServletRequest().getAttribute("javax.servlet.forward.query_string");
273         }
274 
275         if (query != null) {
276             // Remove possible #foobar suffix
277             int idx = query.lastIndexOf('#');
278 
279             if (idx != -1) {
280                 query = query.substring(0, idx);
281             }
282         }
283         return query;
284     }
285     
286     /***
287      * Merge request parameters into current parameters. If a parameter is
288      * already present, than the request parameter in the current request and value atrribute
289      * will not override its value.
290      *
291      * The priority is as follows:-
292      * <ul>
293      *  <li>parameter from the current request (least priority)</li>
294      *  <li>parameter form the value attribute (more priority)</li>
295      *  <li>parameter from the param tag (most priority)</li>
296      * </ul>
297      *
298      * @param value the value attribute (url to be generated by this component)
299      * @param parameters component parameters
300      * @param contextParameters request parameters
301      */
302     protected void mergeRequestParameters(String value, Map parameters, Map contextParameters){
303 
304         Map mergedParams = new LinkedHashMap(contextParameters);
305 
306         // Merge contextParameters (from current request) with parameters specified in value attribute
307         // eg. value="someAction.action?id=someId&venue=someVenue"
308         // where the parameters specified in value attribute takes priority.
309 
310         if (value != null && value.trim().length() > 0 && value.indexOf("?") > 0) {
311             String queryString = value.substring(value.indexOf("?")+1);
312 
313             mergedParams = UrlHelper.parseQueryString(queryString);
314             for (Iterator iterator = contextParameters.entrySet().iterator(); iterator.hasNext();) {
315                 Map.Entry entry = (Map.Entry) iterator.next();
316                 Object key = entry.getKey();
317 
318                 if (!mergedParams.containsKey(key)) {
319                     mergedParams.put(key, entry.getValue());
320                 }
321             }
322         }
323 
324 
325         // Merge parameters specified in value attribute
326         // eg. value="someAction.action?id=someId&venue=someVenue"
327         // with parameters specified though param tag
328         // eg. <param name="id" value="%{'someId'}" />
329         // where parameters specified through param tag takes priority.
330 
331         for (Iterator iterator = mergedParams.entrySet().iterator(); iterator.hasNext();) {
332             Map.Entry entry = (Map.Entry) iterator.next();
333             Object key = entry.getKey();
334 
335             if (!parameters.containsKey(key)) {
336                 parameters.put(key, entry.getValue());
337             }
338         }
339     }
340 }