/**
* Copyright 2005-2016 hdiv.org
*
* Licensed 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.hdiv.tiles;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts.Globals;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.ActionMessage;
import org.apache.struts.action.ActionMessages;
import org.apache.struts.action.InvalidCancelException;
import org.apache.struts.config.ForwardConfig;
import org.apache.struts.taglib.TagUtils;
import org.apache.struts.tiles.TilesRequestProcessor;
import org.apache.struts.util.RequestUtils;
import org.hdiv.filter.RequestWrapper;
import org.hdiv.filter.ValidatorError;
import org.hdiv.urlProcessor.LinkUrlProcessor;
import org.hdiv.urlProcessor.UrlData;
import org.hdiv.util.Constants;
import org.hdiv.util.HDIVUtil;
import org.hdiv.util.Method;
/**
* <p>
* <strong>RequestProcessor</strong> contains the processing logic that the Struts controller servlet performs as it receives each servlet
* request from the container.
* </p>
* <p>
* This processor subclasses the Struts RequestProcessor in order to intercept calls to forward or include. When such calls are done, the
* Tiles processor checks if the specified URI is a definition name. If true, the definition is retrieved and included. If false, the
* original URI is included or a forward is performed.
* <p>
* Actually, catching is done by overloading the following methods:
* <ul>
* <li>{@link #processForwardConfig(HttpServletRequest,HttpServletResponse,ForwardConfig)}</li>
* <li>{@link #internalModuleRelativeForward(String, HttpServletRequest , HttpServletResponse)}</li>
* <li>{@link #internalModuleRelativeInclude(String, HttpServletRequest , HttpServletResponse)}</li>
* </ul>
* </p>
*
* @author Gorka Vicente
* @see org.apache.struts.tiles.TilesRequestProcessor
* @since HDIV 1.1.2
*/
public class HDIVTilesRequestProcessor extends TilesRequestProcessor {
/**
* The request attributes key under HDIV should store errors produced in the editable fields.
*/
private static final String EDITABLE_PARAMETER_ERROR = Constants.EDITABLE_PARAMETER_ERROR;
/**
* Property that contains the error message to be shown by Struts when the value of the editable parameter is not valid.
*/
private static final String HDIV_EDITABLE_ERROR = Constants.HDIV_EDITABLE_ERROR_KEY;
/**
* Property that contains the error message to be shown by Struts when the value of the editable password parameter is not valid.
*/
private static final String HDIV_EDITABLE_PASSWORD_ERROR = Constants.HDIV_EDITABLE_PASSWORD_ERROR_KEY;
/**
* <p>
* If this request was not cancelled, and the request's {@link ActionMapping} has not disabled validation, call the
* <code>validate</code> method of the specified {@link ActionForm}, and forward to the input path if there were any errors. Return
* <code>true</code> if we should continue processing, or <code>false</code> if we have already forwarded control back to the input
* form.
* </p>
*
* @param request The servlet request we are processing
* @param response The servlet response we are creating
* @param form The ActionForm instance we are populating
* @param mapping The ActionMapping we are using
* @exception IOException if an input/output error occurs
* @exception ServletException if a servlet exception occurs
* @exception InvalidCancelException if a cancellation is attempted without the proper action configuration
*/
@Override
protected boolean processValidate(final HttpServletRequest request, final HttpServletResponse response, final ActionForm form,
final ActionMapping mapping) throws IOException, ServletException, InvalidCancelException {
if (form == null) {
return true;
}
// Has validation been turned off for this mapping?
if (!mapping.getValidate()) {
return true;
}
// Was this request cancelled? If it has been, the mapping also
// needs to state whether the cancellation is permissable; otherwise
// the cancellation is considered to be a symptom of a programmer
// error or a spoof.
if (request.getAttribute(Globals.CANCEL_KEY) != null) {
if (mapping.getCancellable()) {
if (log.isDebugEnabled()) {
log.debug(" Cancelled transaction, skipping validation");
}
return true;
}
else {
request.removeAttribute(Globals.CANCEL_KEY);
throw new InvalidCancelException();
}
}
// Call the form bean's validation method
if (log.isDebugEnabled()) {
log.debug(" Validating input form properties");
}
ActionMessages errors = form.validate(mapping, request);
if (errors == null || errors.isEmpty()) {
errors = getEditableParametersErrors(request);
if (errors == null || errors.isEmpty()) {
if (log.isTraceEnabled()) {
log.trace(" No errors detected, accepting input");
}
return true;
}
}
// Special handling for multipart request
if (form.getMultipartRequestHandler() != null) {
if (log.isTraceEnabled()) {
log.trace(" Rolling back multipart request");
}
form.getMultipartRequestHandler().rollback();
}
// Was an input path (or forward) specified for this mapping?
String input = mapping.getInput();
if (input == null) {
if (log.isTraceEnabled()) {
log.trace(" Validation failed but no input form available");
}
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, getInternal().getMessage("noInput", mapping.getPath()));
return false;
}
// Save our error messages and return to the input form if possible
if (log.isDebugEnabled()) {
log.debug(" Validation failed, returning to '" + input + "'");
}
request.setAttribute(Globals.ERROR_KEY, errors);
if (moduleConfig.getControllerConfig().getInputForward()) {
ForwardConfig forward = mapping.findForward(input);
processForwardConfig(request, response, forward);
}
else {
internalModuleRelativeForward(input, request, response);
}
return false;
}
/**
* Obtains the errors detected by HDIV during the validation process of the editable parameters.
*
* @param request The servlet request we are processing
* @return errors detected by HDIV during the validation process of the editable parameters.
*/
public ActionMessages getEditableParametersErrors(final HttpServletRequest request) {
@SuppressWarnings("unchecked")
List<ValidatorError> validationErrors = (List<ValidatorError>) request.getAttribute(EDITABLE_PARAMETER_ERROR);
ActionMessages errors = null;
if (validationErrors != null && validationErrors.size() > 0) {
errors = new ActionMessages();
for (ValidatorError validationError : validationErrors) {
String errorValues = validationError.getParameterValue();
ActionMessage error = null;
if (errorValues.contains(HDIV_EDITABLE_PASSWORD_ERROR)) {
error = new ActionMessage(HDIV_EDITABLE_PASSWORD_ERROR);
}
else {
String printedValue = createMessageError(errorValues);
error = new ActionMessage(HDIV_EDITABLE_ERROR, printedValue);
}
errors.add("hdiv.editable." + validationError.getParameterName(), error);
}
}
return errors;
}
/**
* It creates the message error from the values <code>values</code>.
*
* @param paramValues values with not allowed characters
* @return message error to show
*/
public String createMessageError(final String paramValues) {
String[] values = paramValues.split(",");
StringBuilder printedValue = new StringBuilder();
for (int i = 0; i < values.length; i++) {
if (i > 0) {
printedValue.append(", ");
}
if (values[i].length() > 20) {
printedValue.append(TagUtils.getInstance().filter(values[i]).substring(0, 20) + "...");
}
else {
printedValue.append(TagUtils.getInstance().filter(values[i]));
}
if (printedValue.length() > 20) {
break;
}
}
return printedValue.toString();
}
/**
* Overloaded method from Struts' RequestProcessor. Forward or redirect to the specified destination by the specified mechanism. This
* method catches the Struts' actionForward call. It checks if the actionForward is done on a Tiles definition name. If true, process
* the definition and insert it. If false, call the original parent's method.
*
* @param request The servlet request we are processing.
* @param response The servlet response we are creating.
* @param forward The ActionForward controlling where we go next.
*
* @throws IOException if an input/output error occurs.
* @throws ServletException if a servlet exception occurs.
* @since HDIV 2.1.12
*/
@Override
protected void processForwardConfig(final HttpServletRequest request, final HttpServletResponse response, ForwardConfig forward)
throws IOException, ServletException {
// Required by struts contract
if (forward == null) {
return;
}
String forwardPath = forward.getPath();
if (log.isDebugEnabled()) {
log.debug("processForwardConfig(" + forwardPath + ")");
}
// Try to process the definition.
if (processTilesDefinition(forwardPath, request, response)) {
if (log.isDebugEnabled()) {
log.debug(" '" + forwardPath + "' - processed as definition");
}
return;
}
if (log.isDebugEnabled()) {
log.debug(" '" + forwardPath + "' - processed as uri");
}
// forward doesn't contain a definition, let parent do processing
// If the forward can be unaliased into an action, then use the path of
// the action
String actionIdPath = RequestUtils.actionIdURL(forward, request, servlet);
if (actionIdPath != null) {
forwardPath = actionIdPath;
ForwardConfig actionIdForward = new ForwardConfig(forward);
actionIdForward.setPath(actionIdPath);
forward = actionIdForward;
}
// paths not starting with / should be passed through without any
// processing (i.e. they're absolute)
String uri = forwardPath.startsWith("/") ? RequestUtils.forwardURL(request, forward, null) : forwardPath;
if (forward.getRedirect()) {
// only prepend context path for relative uri
if (uri.startsWith("/")) {
uri = request.getContextPath() + uri;
}
// Call to HDIV LinkUrlProcessor
LinkUrlProcessor linkUrlProcessor = HDIVUtil.getLinkUrlProcessor(request.getSession().getServletContext());
uri = linkUrlProcessor.processUrl(request, uri);
if (log.isDebugEnabled()) {
log.debug("redirecting to " + uri);
}
response.sendRedirect(response.encodeRedirectURL(uri));
}
else {
doForward(uri, request, response);
}
}
/**
* Do a forward using request dispatcher. Uri is a valid uri. If response has already been commited, do an include instead.
*
* @param uri Uri or Definition name to forward.
* @param request Current page request.
* @param response Current page response.
*/
@Override
protected void doForward(final String uri, final HttpServletRequest request, final HttpServletResponse response)
throws IOException, ServletException {
RequestWrapper requestWrapper = HDIVUtil.getNativeRequest(request, RequestWrapper.class);
if (requestWrapper != null) {
LinkUrlProcessor linkUrlProcessorForForward = HDIVUtil.getLinkUrlProcessor(request.getSession().getServletContext());
UrlData urlData = linkUrlProcessorForForward.createUrlData(uri, Method.GET, HDIVUtil.getHdivStateParameterName(request),
request);
Map<String, String[]> urlParamsAsMap = linkUrlProcessorForForward.getUrlParamsAsMap(new StringBuilder(128), request,
urlData.getUrlParams());
for (Map.Entry<String, String[]> entry : urlParamsAsMap.entrySet()) {
requestWrapper.addParameter(entry.getKey(), entry.getValue());
}
}
if (response.isCommitted()) {
doInclude(uri, request, response);
}
else {
RequestDispatcher rd = getServletContext().getRequestDispatcher(uri);
if (rd == null) {
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, getInternal().getMessage("requestDispatcher", uri));
return;
}
rd.forward(request, response);
}
}
}