/**
* Copyright 2005-2010 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.net.URLDecoder;
import java.util.Stack;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hdiv.config.HDIVConfig;
import org.hdiv.idGenerator.UidGenerator;
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.State;
/**
* <p>
* It generates the states of each page by storing them in the user session. To be
* able to associate the request state with the state stored in session, an extra
* parameter is added to each request, containing the state identifier which makes
* possible to get the state of the user session.
* </p>
* <p>
* Non editable values are hidden to the client, guaranteeing <b>confidentiality</b>
* </p>
*
* @see org.hdiv.dataComposer.AbstractDataComposer
* @see org.hdiv.composer.IDataComposer
* @author Roberto Velasco
*/
public class DataComposerMemory extends AbstractDataComposer implements IDataComposer {
/**
* Commons Logging instance.
*/
private static Log log = LogFactory.getLog(DataComposerMemory.class);
/**
* Dash character
*/
protected static final String DASH = "-";
/**
* Page with the possible requests or states
*/
protected IPage page;
/**
* State that represents all the data of a request or a form existing in a page
* <code>page</code>
*/
protected IState state;
/**
* Last parameter treated by the compose method
*/
protected IParameter lastParameter;
/**
* Represents the identifier of each possible state stored in the page
* <code>page</code>.
*/
protected int requestCounter = 0;
/**
* States stack to store all states of the page <code>page</code>
*/
protected Stack statesStack;
/**
* HDIV configuration object.
*/
protected HDIVConfig hdivConfig;
/**
* Unique id generator
*/
protected UidGenerator uidGenerator;
/**
* DataComposerMemory initialization with HTTP session wrapper and new stack to
* store all states of the page <code>page</code>.
*/
public void init() {
this.page = new Page();
this.statesStack = new Stack();
}
/**
* 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 encoded and memory strategies 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(String parameter, String value, boolean editable) {
return this.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 encoded and memory strategies 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(String action, String parameter, String value, boolean editable) {
return this.compose(action, parameter, value, editable, false, "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 encoded and memory strategies.
*
* @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(String parameter, String value, boolean editable, boolean isActionParam) {
return this.compose(parameter, value, editable, isActionParam, "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 encoded and memory strategies 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(String parameter, String value, boolean editable, String editableName) {
return this.compose(parameter, value, editable, editableName, false, "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 encoded and memory strategies 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(String action, String parameter, String value, boolean editable,
boolean isActionParam, String charEncoding) {
this.setAction(action);
return this.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 encoded and memory strategies.
*
* @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(String parameter, String value, boolean editable, boolean isActionParam,
String charEncoding) {
return this.compose(parameter, value, editable, null, 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 encoded and memory strategies.
*
* @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 charEncoding character encoding
* @return Codified value to send to the client
* @since HDIV 1.1
*/
public String compose(String parameter, String value, boolean editable, String editableName,
boolean isActionParam, String charEncoding) {
this.composeParameter(parameter, value, editable, editableName, isActionParam, charEncoding);
if (this.hdivConfig.isStartParameter(parameter)) {
return value;
}
if (this.isUserDefinedNonValidationParameter(parameter)) {
return value;
}
if (Boolean.FALSE.equals(this.hdivConfig.getConfidentiality())) {
return value;
}
if (this.getAction() != null) {
if (this.hdivConfig.isStartPage(this.getAction())) {
return value;
}
}
return (this.lastParameter.getCount() - 1) + "";
}
/**
* 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
*/
private boolean isUserDefinedNonValidationParameter(String parameter) {
String actionWithoutContextPath = this.getAction();
if (actionWithoutContextPath.startsWith("/")) {
int secondSlash = actionWithoutContextPath.indexOf("/", 1);
if (secondSlash > 0) {
actionWithoutContextPath = actionWithoutContextPath.substring(secondSlash);
}
}
if (this.hdivConfig.isParameterWithoutValidation(actionWithoutContextPath, 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 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 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
*/
private void composeParameter(String parameter, String value, boolean editable,
String editableName, boolean isActionParam, String charEncoding) {
// we decoded value before store it in state.
String decodedValue = this.getDecodedValue(value, charEncoding);
if (this.state.existParameter(parameter)) {
if ((this.lastParameter == null) || (!this.lastParameter.getName().equals(parameter))) {
this.lastParameter = this.state.getParameter(parameter);
}
// add a new value to the parameter
this.lastParameter.addValue(decodedValue);
} else {
// create a new parameter and add to the request
this.lastParameter = new Parameter();
this.lastParameter.addValue(decodedValue);
this.lastParameter.setName(parameter);
this.lastParameter.setEditable(editable);
this.lastParameter.setEditableDataType(editableName);
this.lastParameter.setActionParam(isActionParam);
this.state.addParameter(parameter, lastParameter);
}
}
/**
* 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(String oldParameter, String newParameter) {
IParameter storedParameter = this.state.getParameter(oldParameter);
if (storedParameter.getValues().size() > 0) {
this.composeParameter(newParameter, storedParameter.getValuePosition(0), false, "", false, "UTF-8");
String currentValue = null;
// 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++) {
currentValue = storedParameter.getValuePosition(i);
this.lastParameter.addValue(currentValue);
}
}
}
/**
* Decoded <code>value</code> using input <code>charEncoding</code>.
*
* @param value value to decode
* @param charEncoding character encoding
* @return value decoded
*/
private String getDecodedValue(String value, String charEncoding) {
String decodedValue = null;
try {
decodedValue = URLDecoder.decode(value, charEncoding);
} catch (Exception e) {
decodedValue = value;
}
return (decodedValue == null) ? "" : decodedValue;
}
/**
* It is called by each request or form existing in the page returned by the
* server. It creates a new state to store all the parameters and values of the
* request or form.
*/
public void beginRequest() {
this.state = new State();
this.state.setAction(this.getAction());
String currentRequestCounter = String.valueOf(requestCounter);
this.state.setId(currentRequestCounter);
this.statesStack.push(this.state);
requestCounter++;
this.lastParameter = null;
}
/**
* It is called in the pre-processing stage of each request or form existing in
* the page returned by the server, as long as the destiny of the request is an
* action. It creates a new state to store all the parameters and values of the
* request or form.
*
* @param action action name
* @see org.hdiv.dataComposer.DataComposerMemory#beginRequest()
*/
public void beginRequest(String action) {
this.setAction(action);
this.beginRequest();
}
/**
* It is called in the pre-processing stage of each request or form existing in
* the page returned by the server. It adds the state of the treated request or
* form to the page <code>page</code> and returns and identifier compoded by
* the page identifier and the state identifier.
*
* @return Identifier composed by the page identifier and the state identifier.
*/
public String endRequest() {
this.state = (IState) this.statesStack.pop();
this.state.setPageId(this.getPage().getName());
this.page.addState(this.state);
String id = this.getPage().getName() + DASH + this.state.getId() + DASH
+ this.getHdivStateSuffix();
this.updateComposerState();
return id;
}
/**
* Obtains the suffix to add to the _HDIV_STATE_ parameter in the memory version.
*
* @return Returns suffix added to the _HDIV_STATE_ parameter in the memory
* version.
* @since HDIV 1.1
*/
public String getHdivStateSuffix() {
return this.page.getRandomToken();
}
/**
* Updates the state <code>state</code> of <code>this</code> with the state
* in the first position of the state stack. If the state stack is empty it does
* nothing.
*/
public void updateComposerState() {
if (this.statesStack.size() > 0) {
this.state = (IState) this.statesStack.lastElement();
}
}
/**
* It is called in the pre-processing stage of each user request assigning a new
* page identifier to the page.
*/
public void startPage() {
if (this.page.getName() == null) {
this.initPageId();
this.page.setName(this.getPageId());
this.page.setRandomToken(this.uidGenerator.generateUid().toString());
}
}
/**
* This method is called in the pre-processing stage of each user request to add
* an IPage object, which represents the page to show by the server, with all its
* states to the user session.
*/
public void endPage() {
if(this.page.getStates().size() > 0){
this.getSession().addPage(this.getPageId(), this.page);
}else{
log.debug("The page ["+this.page.getName()+"] has no states, is not stored in session");
}
}
/**
* @return IPage which represents the page in memory.
*/
public IPage getPage() {
return this.page;
}
/**
* @param page The page to set.
*/
public void setPage(IPage page) {
this.page = page;
}
/**
* @param hdivConfig The HDIV configuration object to set.
*/
public void setHdivConfig(HDIVConfig hdivConfig) {
this.hdivConfig = hdivConfig;
}
/**
* Adds the flow identifier to the page of type <code>IPage</code>.
* @since HDIV 2.0.3
*/
public void addFlowId(String id) {
this.page.setFlowId(id);
}
public void setUidGenerator(UidGenerator uidGenerator) {
this.uidGenerator = uidGenerator;
}
}