blob: db7ca58f75b2dd4926412d335ca78581c834420f [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.struts2.components;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.config.entities.ActionConfig;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.util.ValueStack;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.struts2.StrutsException;
import org.apache.struts2.dispatcher.mapper.ActionMapper;
import org.apache.struts2.dispatcher.mapper.ActionMapping;
import org.apache.struts2.views.TagAttribute;
import org.apache.struts2.views.util.UrlHelper;
import java.io.IOException;
import java.io.Writer;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Implementation of the {@link UrlRenderer} interface that creates URLs suitable in a servlet environment.
*/
public class ServletUrlRenderer implements UrlRenderer {
/**
* Provide a logging instance.
*/
private static final Logger LOG = LogManager.getLogger(ServletUrlRenderer.class);
private ActionMapper actionMapper;
private UrlHelper urlHelper;
@Inject
public void setActionMapper(ActionMapper mapper) {
this.actionMapper = mapper;
}
@Inject
public void setUrlHelper(UrlHelper urlHelper) {
this.urlHelper = urlHelper;
}
/**
* {@inheritDoc}
*/
public void renderUrl(Writer writer, UrlProvider urlComponent) {
String scheme = urlComponent.getHttpServletRequest().getScheme();
if (urlComponent.getScheme() != null) {
ValueStack vs = ActionContext.getContext().getValueStack();
scheme = vs.findString(urlComponent.getScheme());
if (scheme == null) {
scheme = urlComponent.getScheme();
}
}
String result;
ActionInvocation ai = ActionContext.getContext().getActionInvocation();
if (urlComponent.getValue() == null && urlComponent.getAction() != null) {
result = urlComponent.determineActionURL(urlComponent.getAction(), urlComponent.getNamespace(), urlComponent.getMethod(), urlComponent.getHttpServletRequest(), urlComponent.getHttpServletResponse(), urlComponent.getParameters(), scheme, urlComponent.isIncludeContext(), urlComponent.isEncode(), urlComponent.isForceAddSchemeHostAndPort(), urlComponent.isEscapeAmp());
} else if (urlComponent.getValue() == null && urlComponent.getAction() == null && ai != null) {
// both are null, we will default to the current action
final String action = ai.getProxy().getActionName();
final String namespace = ai.getProxy().getNamespace();
final String method = urlComponent.getMethod() != null || !ai.getProxy().isMethodSpecified() ? urlComponent.getMethod() : ai.getProxy().getMethod();
result = urlComponent.determineActionURL(action, namespace, method, urlComponent.getHttpServletRequest(), urlComponent.getHttpServletResponse(), urlComponent.getParameters(), scheme, urlComponent.isIncludeContext(), urlComponent.isEncode(), urlComponent.isForceAddSchemeHostAndPort(), urlComponent.isEscapeAmp());
} else {
String _value = urlComponent.getValue();
// We don't include the request parameters cause they would have been
// prioritised before this [in start(Writer) method]
if (_value != null && _value.indexOf('?') > 0) {
_value = _value.substring(0, _value.indexOf('?'));
}
result = urlHelper.buildUrl(_value, urlComponent.getHttpServletRequest(), urlComponent.getHttpServletResponse(), urlComponent.getParameters(), scheme, urlComponent.isIncludeContext(), urlComponent.isEncode(), urlComponent.isForceAddSchemeHostAndPort(), urlComponent.isEscapeAmp());
}
String anchor = urlComponent.getAnchor();
if (StringUtils.isNotEmpty(anchor)) {
result += '#' + urlComponent.findString(anchor);
}
if (urlComponent.isPutInContext()) {
String var = urlComponent.getVar();
if (StringUtils.isNotEmpty(var)) {
urlComponent.putInContext(result);
// add to the request and page scopes as well
urlComponent.getHttpServletRequest().setAttribute(var, result);
} else {
try {
writer.write(result);
} catch (IOException e) {
throw new StrutsException("IOError: " + e.getMessage(), e);
}
}
} else {
try {
writer.write(result);
} catch (IOException e) {
throw new StrutsException("IOError: " + e.getMessage(), e);
}
}
}
/**
* {@inheritDoc}
*/
public void renderFormUrl(Form formComponent) {
String namespace = formComponent.determineNamespace(formComponent.namespace, formComponent.getStack(), formComponent.request);
String action;
ValueStack vs = ActionContext.getContext().getValueStack();
String scheme = vs.findString("scheme");
if (formComponent.action != null) {
action = formComponent.findString(formComponent.action);
} else {
// no action supplied? ok, then default to the current request
// (action or general URL)
ActionInvocation ai = formComponent.getStack().getActionContext().getActionInvocation();
if (ai != null) {
action = ai.getProxy().getActionName();
namespace = ai.getProxy().getNamespace();
} else {
// hmm, ok, we need to just assume the current URL cut down
String uri = formComponent.request.getRequestURI();
action = uri.substring(uri.lastIndexOf('/'));
}
}
Map actionParams = null;
if (action != null && action.indexOf('?') > 0) {
String queryString = action.substring(action.indexOf('?') + 1);
actionParams = urlHelper.parseQueryString(queryString, false);
action = action.substring(0, action.indexOf('?'));
}
ActionMapping nameMapping = actionMapper.getMappingFromActionName(action);
String actionName = nameMapping.getName();
String actionMethod = nameMapping.getMethod();
final ActionConfig actionConfig = formComponent.configuration.getRuntimeConfiguration().getActionConfig(
namespace, actionName);
if (actionConfig != null) {
ActionMapping mapping = new ActionMapping(actionName, namespace, actionMethod, formComponent.parameters);
String result = urlHelper.buildUrl(formComponent.actionMapper.getUriFromActionMapping(mapping),
formComponent.request, formComponent.response, actionParams, scheme, formComponent.includeContext, true, false, false);
formComponent.addParameter("action", result);
// let's try to get the actual action class and name
// this can be used for getting the list of validators
formComponent.addParameter("actionName", actionName);
try {
Class clazz = formComponent.objectFactory.getClassInstance(actionConfig.getClassName());
formComponent.addParameter("actionClass", clazz);
} catch (ClassNotFoundException e) {
// this is OK, we'll just move on
}
formComponent.addParameter("namespace", namespace);
// if the name isn't specified, use the action name
if (formComponent.name == null) {
formComponent.addParameter("name", actionName);
}
// if the id isn't specified, use the action name
TagAttribute id = formComponent.getId();
if (id.isNull() && actionName != null) {
formComponent.addParameter("id", TagAttribute.evaluated(actionName).escaped());
}
} else if (action != null) {
// Since we can't find an action alias in the configuration, we just
// assume the action attribute supplied is the path to be used as
// the URI this form is submitting to.
// Warn user that the specified namespace/action combo
// was not found in the configuration.
if (namespace != null && LOG.isWarnEnabled()) {
LOG.warn("No configuration found for the specified action: '{}' in namespace: '{}'. Form action defaulting to 'action' attribute's literal value.", actionName, namespace);
}
String result = urlHelper.buildUrl(action, formComponent.request, formComponent.response, actionParams, scheme, formComponent.includeContext, true);
formComponent.addParameter("action", result);
// namespace: cut out anything between the start and the last /
int slash = result.lastIndexOf('/');
if (slash != -1) {
formComponent.addParameter("namespace", result.substring(0, slash));
} else {
formComponent.addParameter("namespace", "");
}
// name/id: cut out anything between / and . should be the id and
// name
TagAttribute id = formComponent.getId();
if (id == null || id.isNull()) {
slash = result.lastIndexOf('/');
int dot = result.indexOf('.', slash);
if (dot != -1) {
id = TagAttribute.evaluated(result.substring(slash + 1, dot));
} else {
id = TagAttribute.evaluated(result.substring(slash + 1));
}
formComponent.addParameter("id", id.escaped());
}
}
// WW-1284
// evaluate if client-side js is to be enabled. (if validation
// interceptor does allow validation eg. method is not filtered out)
formComponent.evaluateClientSideJsEnablement(actionName, namespace, actionMethod);
}
public void beforeRenderUrl(UrlProvider urlComponent) {
if (urlComponent.getValue() != null) {
urlComponent.setValue(urlComponent.findString(urlComponent.getValue()));
}
// no explicit url set so attach params from current url, do
// this at start so body params can override any of these they wish.
try {
// ww-1266
String includeParams = (urlComponent.getUrlIncludeParams() != null ? urlComponent.getUrlIncludeParams().toLowerCase() : UrlProvider.GET);
if (urlComponent.getIncludeParams() != null) {
includeParams = urlComponent.findString(urlComponent.getIncludeParams());
}
if (UrlProvider.NONE.equalsIgnoreCase(includeParams)) {
mergeRequestParameters(urlComponent.getValue(), urlComponent.getParameters(), Collections.<String, Object>emptyMap());
} else if (UrlProvider.ALL.equalsIgnoreCase(includeParams)) {
mergeRequestParameters(urlComponent.getValue(), urlComponent.getParameters(), urlComponent.getHttpServletRequest().getParameterMap());
// for ALL also include GET parameters
includeGetParameters(urlComponent);
includeExtraParameters(urlComponent);
} else if (UrlProvider.GET.equalsIgnoreCase(includeParams) || (includeParams == null && urlComponent.getValue() == null && urlComponent.getAction() == null)) {
includeGetParameters(urlComponent);
includeExtraParameters(urlComponent);
} else if (includeParams != null) {
LOG.warn("Unknown value for includeParams parameter to URL tag: {}", includeParams);
}
} catch (Exception e) {
LOG.warn("Unable to put request parameters ({}) into parameter map.", urlComponent.getHttpServletRequest().getQueryString(), e);
}
}
private void includeExtraParameters(UrlProvider urlComponent) {
if (urlComponent.getExtraParameterProvider() != null) {
mergeRequestParameters(urlComponent.getValue(), urlComponent.getParameters(), urlComponent.getExtraParameterProvider().getExtraParameters());
}
}
private void includeGetParameters(UrlProvider urlComponent) {
String query = extractQueryString(urlComponent);
mergeRequestParameters(urlComponent.getValue(), urlComponent.getParameters(), urlHelper.parseQueryString(query, false));
}
private String extractQueryString(UrlProvider urlComponent) {
// Parse the query string to make sure that the parameters come from the query, and not some posted data
String query = urlComponent.getHttpServletRequest().getQueryString();
if (query == null) {
query = (String) urlComponent.getHttpServletRequest().getAttribute("javax.servlet.forward.query_string");
}
if (query != null) {
// Remove possible #foobar suffix
int idx = query.lastIndexOf('#');
if (idx != -1) {
query = query.substring(0, idx);
}
}
return query;
}
/**
* Merge request parameters into current parameters. If a parameter is
* already present, than the request parameter in the current request and value atrribute
* will not override its value.
*
* The priority is as follows:-
* <ul>
* <li>parameter from the current request (least priority)</li>
* <li>parameter form the value attribute (more priority)</li>
* <li>parameter from the param tag (most priority)</li>
* </ul>
*
* @param value the value attribute (url to be generated by this component)
* @param parameters component parameters
* @param contextParameters request parameters
*/
protected void mergeRequestParameters(String value, Map<String, Object> parameters, Map<String, Object> contextParameters) {
Map<String, Object> mergedParams = new LinkedHashMap<>(contextParameters);
// Merge contextParameters (from current request) with parameters specified in value attribute
// eg. value="someAction.action?id=someId&venue=someVenue"
// where the parameters specified in value attribute takes priority.
if (StringUtils.contains(value, "?")) {
String queryString = value.substring(value.indexOf('?') + 1);
mergedParams = urlHelper.parseQueryString(queryString, false);
for (Map.Entry<String, Object> entry : contextParameters.entrySet()) {
if (!mergedParams.containsKey(entry.getKey())) {
mergedParams.put(entry.getKey(), entry.getValue());
}
}
}
// Merge parameters specified in value attribute
// eg. value="someAction.action?id=someId&venue=someVenue"
// with parameters specified though param tag
// eg. <param name="id" value="%{'someId'}" />
// where parameters specified through param tag takes priority.
for (Map.Entry<String, Object> entry : mergedParams.entrySet()) {
if (!parameters.containsKey(entry.getKey())) {
parameters.put(entry.getKey(), entry.getValue());
}
}
}
}