/**
* 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.dataComposer;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hdiv.config.HDIVConfig;
import org.hdiv.context.RequestContextHolder;
import org.hdiv.idGenerator.UidGenerator;
import org.hdiv.session.ISession;
import org.hdiv.state.IPage;
import org.hdiv.state.IParameter;
import org.hdiv.state.IState;
import org.hdiv.state.Page;
import org.hdiv.state.Parameter;
import org.hdiv.state.RandomTokenType;
import org.hdiv.util.Constants;
import org.hdiv.util.HDIVUtil;
import org.hdiv.util.Method;
/**
* <p>
* It processes the data contributed by the HDIV custom tags. The aim of this class is to create an object of type IState for each possible
* request (form or link) in every page processed by the HDIV custom tags. The IState object is used to validate client's later requests.
* </p>
* <p>
* The process of creating an IState object is as follows: Each time a link or a form processing begins, HDIV custom tags set the request
* beginning by calling beginRequest method. Once the beginning is set, an IState object is created and it is fill in with all the data of
* the request(parameter values, non editable values, parameter types) using the compose method. After processing all the request data of
* the link or form, custom tags set the end of the processing by calling endRequest method.
* </p>
*
* @author Roberto Velasco
*/
public abstract class AbstractDataComposer implements IDataComposer {
/**
* Commons Logging instance
*/
private static final Log log = LogFactory.getLog(AbstractDataComposer.class);
/**
* Http session wrapper
*/
protected ISession session;
/**
* Unique id generator
*/
protected UidGenerator uidGenerator;
/**
* Page with the possible requests or states
*/
protected IPage page;
/**
* States stack to store all states of the page <code>page</code>
*/
Deque<IState> states;
/**
* HDIV configuration object.
*/
protected HDIVConfig hdivConfig;
/**
* Context holder for request-specific state.
*/
protected RequestContextHolder context;
protected final StringBuilder sb = new StringBuilder(128);
private final String hdivParameterName;
public AbstractDataComposer(final RequestContextHolder context) {
this.context = context;
hdivParameterName = context.getHdivParameterName();
}
public String getHdivParameterName() {
return hdivParameterName;
}
/**
* DataComposer initialization with new stack to store all states of the page <code>page</code>.
*/
public void init() {
setPage(new Page(0));
states = new ArrayDeque<IState>();
}
protected Deque<IState> getStates() {
return states;
}
/**
* Obtains a new unique identifier for the page.
*
* @param parentStateId Parent state id
*/
public void initPage(final String parentStateId) {
setPage(new Page(session.getPageId(context)));
page.setParentStateId(parentStateId);
}
/**
* Obtains a new unique identifier for the page.
*/
public void initPage() {
initPage(null);
}
/**
* It generates a new encoded value for the parameter <code>parameter</code> and the value <code>value</code> passed as parameters. The
* returned value guarantees the confidentiality in the memory strategy if confidentiality indicator <code>confidentiality</code> is
* true.
*
* @param parameter HTTP parameter name
* @param value value generated by server
* @param editable parameter type: editable(textbox, password,etc.) or non editable (hidden, select, radio, ...)
* @return Codified value to send to the client
*/
public String compose(final String parameter, final String value, final boolean editable) {
return compose(parameter, value, editable, false);
}
/**
* It generates a new encoded value for the parameter <code>parameter</code> and the value <code>value</code> passed as parameters. The
* returned value guarantees the confidentiality in the memory strategy if confidentiality indicator <code>confidentiality</code> is
* true.
*
* @param action target action
* @param parameter HTTP parameter name
* @param value value generated by server
* @param editable parameter type: editable(textbox, password,etc.) or non editable (hidden, select, radio, ...)
* @return Codified value to send to the client
*/
public String compose(final String action, final String parameter, final String value, final boolean editable) {
return compose(action, parameter, value, editable, false, Constants.ENCODING_UTF_8);
}
/**
* Adds a new IParameter object, generated from the values passed as parameters, to the current state <code>state</code>. If
* confidentiality is activated it generates a new encoded value that will be returned by the server for the parameter
* <code>parameter</code> in the memory strategy.
*
* @param parameter HTTP parameter
* @param value value generated by server
* @param editable Parameter type: editable(textbox, password,etc.) or non editable (hidden, select, radio, ...)
* @param isActionParam parameter added in action attribute
* @return Codified value to send to the client
*/
public String compose(final String parameter, final String value, final boolean editable, final boolean isActionParam) {
return compose(parameter, value, editable, isActionParam, Constants.ENCODING_UTF_8);
}
/**
* It generates a new encoded value for the parameter <code>parameter</code> and the value <code>value</code> passed as parameters. The
* returned value guarantees the confidentiality in the memory strategy if confidentiality indicator <code>confidentiality</code> is
* true.
*
* @param parameter HTTP parameter name
* @param value value generated by server
* @param editable parameter type: editable(textbox, password,etc.) or non editable (hidden, select, radio, ...)
* @param editableName editable name (text or textarea)
* @return Codified value to send to the client
* @since HDIV 1.1
*/
public String compose(final String parameter, final String value, final boolean editable, final String editableName) {
return compose(parameter, value, editable, editableName, false, null, Constants.ENCODING_UTF_8);
}
/**
* It generates a new encoded value for the parameter <code>parameter</code> and the value <code>value</code> passed as parameters. The
* returned value guarantees the confidentiality in the memory strategy if confidentiality indicator <code>confidentiality</code> is
* true.
*
* @param action target action
* @param parameter parameter name
* @param value value generated by server
* @param editable parameter type: editable(textbox, password,etc.) or non editable (hidden, select,...)
* @param isActionParam parameter added in action attribute
* @param charEncoding character encoding
* @return Codified value to send to the client
*/
public String compose(final String action, final String parameter, final String value, final boolean editable,
final boolean isActionParam, final String charEncoding) {
// Get current IState
IState state = states.peek();
if (state.getAction() != null && state.getAction().trim().length() == 0) {
state.setAction(action);
}
return compose(parameter, value, editable, isActionParam, charEncoding);
}
/**
* Adds a new IParameter object, generated from the values passed as parameters, to the current state <code>state</code>. If
* confidentiality is activated it generates a new encoded value that will be returned by the server for the parameter
* <code>parameter</code> in the memory strategy.
*
* @param parameter HTTP parameter
* @param value value generated by server
* @param editable Parameter type: editable(textbox, password,etc.) or non editable (hidden, select, radio, ...)
* @param isActionParam parameter added in action attribute
* @param charEncoding character encoding
* @return Codified value to send to the client
*/
public String compose(final String parameter, final String value, final boolean editable, final boolean isActionParam,
final String charEncoding) {
return compose(parameter, value, editable, null, isActionParam, null, charEncoding);
}
/**
* Adds a new IParameter object, generated from the values passed as parameters, to the current state <code>state</code>. If
* confidentiality is activated it generates a new encoded value that will be returned by the server for the parameter
* <code>parameter</code> in the memory strategy.
*
* @param parameter HTTP parameter
* @param value value generated by server
* @param editable Parameter type: editable(textbox, password,etc.) or non editable (hidden, select, radio, ...)
* @param editableName editable name (text or textarea)
* @param isActionParam parameter added in action attribute
* @param method http method, GET or POST
* @return Codified value to send to the client
* @since HDIV 2.1.5
*/
public String compose(final String parameter, final String value, final boolean editable, final String editableName,
final boolean isActionParam, final Method method) {
return compose(parameter, value, editable, editableName, isActionParam, method, Constants.ENCODING_UTF_8);
}
/*
* (non-Javadoc)
*
* @see org.hdiv.dataComposer.IDataComposer#composeParams(java.lang.String, java.lang.String, java.lang.String)
*/
public final String composeParams(String parameters, final Method method, final String charEncoding) {
if (parameters == null || parameters.length() == 0) {
return null;
}
// Get current IState
IState state = states.peek();
state.setParams(parameters);
if (hdivConfig.getConfidentiality()) {
// replace real values with confidential ones
parameters = applyConfidentialityToParams(parameters);
}
return parameters;
}
/**
* Apply confidentiality to parameters String. Replaces real values with confidential ones.
*
* @param parameters parameters in query format
* @return parameters in query format with confidential values
*/
protected String applyConfidentialityToParams(String parameters) {
Map<String, Integer> pCount = new HashMap<String, Integer>();
parameters = parameters.replaceAll("&", "&");
String newParameters = parameters;
// Init indexes
int beginIndex = 0;
int ampIndex = parameters.indexOf('&');
int endIndex = ampIndex >= 0 ? ampIndex : parameters.length();
do {
String param = parameters.substring(beginIndex, endIndex);
int index = param.indexOf('=');
index = index < 0 ? param.length() : index;
String name = param.substring(0, index);
if (isConfidentialParam(name)) {
// Parameter is not a start parameter
Integer count = pCount.get(name);
int num = count == null ? 0 : count + 1;
pCount.put(name, num);
// Replace parameter with confidential values
newParameters = HDIVUtil.replaceOnce(newParameters, param, name + "=" + num);
}
// Update indexes
beginIndex = endIndex + 1;
endIndex = parameters.indexOf('&', endIndex + 1);
if (endIndex < 0) {
endIndex = parameters.length();
}
} while (endIndex > beginIndex);
return newParameters;
}
/**
* Adds a new IParameter object, generated from the values passed as parameters, to the current state <code>state</code>. If
* confidentiality is activated it generates a new encoded value that will be returned by the server for the parameter
* <code>parameter</code> in the memory strategy.
* <p>
* Custom method for form field.
*
* @param parameter HTTP parameter
* @param value value generated by server
* @param editable Parameter type: editable(textbox, password,etc.) or non editable (hidden, select, radio, ...)
* @param editableName editable name (text or textarea)
* @return Codified value to send to the client
* @since HDIV 2.1.5
*/
public String composeFormField(final String parameter, final String value, final boolean editable, final String editableName) {
return compose(parameter, value, editable, editableName, false, Method.POST, Constants.ENCODING_UTF_8);
}
/**
* Adds a new IParameter object, generated from the values passed as parameters, to the current state <code>state</code>. If
* confidentiality is activated it generates a new encoded value that will be returned by the server for the parameter
* <code>parameter</code> in the memory strategy.
*
* @param parameterName HTTP parameter
* @param value value generated by server
* @param editable Parameter type: editable(textbox, password,etc.) or non editable (hidden, select, radio, ...)
* @param editableName editable name (text or textarea)
* @param isActionParam parameter added in action attribute
* @param method http method, GET or POST
* @param charEncoding character encoding
* @return Codified value to send to the client
* @since HDIV 2.1.5
*/
public String compose(final String parameterName, final String value, final boolean editable, final String editableName,
final boolean isActionParam, final Method method, final String charEncoding) {
if (!isRequestStarted()) {
// If request not started, do nothing
return value;
}
IParameter parameter = composeParameter(parameterName, value, editable, editableName, isActionParam, charEncoding);
if (isConfidentialParam(parameterName)) {
return parameter.getConfidentialValue();
}
else {
return value;
}
}
/**
* Returns true if the parameter requires confidentiality. False otherwise.
*
* @param parameterName the name of the parameter
* @return boolean result
* @since HDIV 2.1.6
*/
protected boolean isConfidentialParam(final String parameterName) {
if (!hdivConfig.getConfidentiality()) {
return false;
}
if (hdivConfig.isStartParameter(parameterName)) {
return false;
}
if (isUserDefinedNonValidationParameter(parameterName)) {
return false;
}
if (hdivConfig.isParameterWithoutConfidentiality(context, parameterName)) {
return false;
}
return true;
}
/**
* Checks if the parameter <code>parameter</code> is defined by the user as a no required validation parameter for the action
* <code>this.target</code>.
*
* @param parameter parameter name
* @return True If it is parameter that needs no validation. False otherwise.
* @since HDIV 2.0.6
*/
protected boolean isUserDefinedNonValidationParameter(final String parameter) {
String action = states.peek().getAction();
if (hdivConfig.isParameterWithoutValidation(action, parameter)) {
if (log.isDebugEnabled()) {
log.debug("parameter " + parameter + " doesn't need validation. It is user defined parameter.");
}
return true;
}
return false;
}
/**
* Adds a new IParameter object, generated from the values passed as parameters, to the current state <code>state</code>.
*
* @param parameterName HTTP parameter
* @param value value generated by server
* @param editable Parameter type: editable(textbox, password,etc.) or non editable (hidden, select, radio, ...)
* @param editableDataType editable parameter name (text or textarea)
* @param isActionParam parameter added in action attribute
* @param charEncoding character encoding
* @return Codified value to send to the client
* @since HDIV 1.1
*/
protected IParameter composeParameter(final String parameterName, final String value, final boolean editable,
final String editableDataType, final boolean isActionParam, final String charEncoding) {
// we decoded value before store it in state.
String decodedValue = null;
if (!editable) {
decodedValue = HDIVUtil.getDecodedValue(sb, value, charEncoding);
}
// Get current IState
IState state = states.peek();
IParameter parameter = state.getParameter(parameterName);
if (parameter != null) {
if (parameter.isEditable() != editable) {
// A parameter can be created as editable but if a new non editable value is added, the parameter is
// changed to non editable. This is required in some frameworks like Struts 2.
parameter.setEditable(editable);
}
parameter.addValue(decodedValue);
}
else {
// create a new parameter and add to the request
parameter = createParameter(parameterName, decodedValue, editable, editableDataType, isActionParam, charEncoding);
state.addParameter(parameter);
}
return parameter;
}
/**
* Instantiates the parameter
*
* @param parameterName name of the parameter
* @param decodedValue the decoded value of the parameter
* @param editable Parameter type: editable(textbox, password,etc.) or non editable (hidden, select, radio, ...)
* @param editableDataType editable parameter name (text or textarea)
* @param isActionParam parameter added in action attribute
* @param charEncoding character encoding
* @return New IParameter object
*/
protected IParameter createParameter(final String parameterName, final String decodedValue, final boolean editable,
final String editableDataType, final boolean isActionParam, final String charEncoding) {
return new Parameter(parameterName, decodedValue, editable, editableDataType, isActionParam);
}
/**
* Creates a new parameter called <code>newParameter</code> and adds all the values of <code>oldParameter</code> stored in the state to
* it.
*
* @param oldParameter name of the parameter stored in the state
* @param newParameter name of the new parameter
*/
public void mergeParameters(final String oldParameter, final String newParameter) {
// Get current IState
IState state = states.peek();
IParameter storedParameter = state.getParameter(oldParameter);
if (!storedParameter.getValues().isEmpty()) {
IParameter parameter = composeParameter(newParameter, storedParameter.getValuePosition(0), false, "", false,
Constants.ENCODING_UTF_8);
// We check the parameters since the second position because the first
// value has been used to create the parameter
for (int i = 1; i < storedParameter.getValues().size(); i++) {
parameter.addValue(storedParameter.getValuePosition(i));
}
}
}
/**
* True if beginRequest has been executed and endRequest not.
*
* @return boolean
*/
public boolean isRequestStarted() {
return !states.isEmpty();
}
/**
* Adds the flow identifier to the page of type <code>IPage</code>.
*
* @since HDIV 2.0.3
*/
public void addFlowId(final String id) {
page.setFlowId(id);
}
/**
* Obtains the suffix to add to the _HDIV_STATE_ parameter in the memory strategy.
*
* @param type Random token type
*
* @return Returns suffix added to the _HDIV_STATE_ parameter.
* @since 2.1.7
*/
protected final String getStateSuffix(final RandomTokenType type) {
String randomToken = page.getRandomToken(type);
if (randomToken == null) {
randomToken = uidGenerator.generateUid().toString();
page.setRandomToken(randomToken, type);
}
return randomToken;
}
/**
* @param session the session to setg
*/
public void setSession(final ISession session) {
this.session = session;
}
/**
* @return the page
*/
public final IPage getPage() {
return page;
}
/**
* @param page the page to set
*/
public void setPage(final IPage page) {
this.page = page;
}
/**
* @param uidGenerator the uidGenerator to set
*/
public void setUidGenerator(final UidGenerator uidGenerator) {
this.uidGenerator = uidGenerator;
}
/**
* @param hdivConfig The HDIV configuration object to set.
*/
public void setHdivConfig(final HDIVConfig hdivConfig) {
this.hdivConfig = hdivConfig;
}
public StringBuilder getBuilder() {
return sb;
}
}