/** * 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.io.UnsupportedEncodingException; import java.util.ArrayDeque; import java.util.Deque; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.hdiv.context.RequestContextHolder; import org.hdiv.exception.HDIVException; import org.hdiv.state.IPage; import org.hdiv.state.IState; import org.hdiv.state.State; import org.hdiv.state.scope.StateScope; import org.hdiv.state.scope.StateScopeManager; import org.hdiv.state.scope.StateScopeType; import org.hdiv.util.Constants; import org.hdiv.util.HDIVStateUtils; import org.hdiv.util.HDIVUtil; import org.hdiv.util.Method; /** * <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.dataComposer.IDataComposer * @author Roberto Velasco */ public class DataComposerMemory extends AbstractDataComposer { /** * Commons Logging instance. */ private static final Log log = LogFactory.getLog(DataComposerMemory.class); /** * State scope manager. */ protected StateScopeManager stateScopeManager; /** * Stack to store existing scopes, active and inactive */ protected Deque<StateScopeType> scopeStack; protected StateScope stateScope; public DataComposerMemory(final RequestContextHolder requestContext) { super(requestContext); } /** * DataComposer initialization with new stack to store all states of the page <code>page</code>. */ @Override public void init() { super.init(); scopeStack = new ArrayDeque<StateScopeType>(); // Add default scope startScope(StateScopeType.PAGE); } /* * (non-Javadoc) * * @see org.hdiv.dataComposer.IDataComposer#startScope(java.lang.String) */ public void startScope(final StateScopeType scope) { scopeStack.push(scope); stateScope = getStateScope(); } /* * (non-Javadoc) * * @see org.hdiv.dataComposer.IDataComposer#endScope() */ public void endScope() { scopeStack.pop(); stateScope = getStateScope(); } /** * 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. * * @return state id for this request */ public final String beginRequest() { return beginRequest(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 method HTTP method of the request. * @param action action name * @return state id for this request * * @see org.hdiv.dataComposer.DataComposerMemory#beginRequest() */ public String beginRequest(final Method method, String action) { try { action = HDIVUtil.decodeValue(sb, action, Constants.ENCODING_UTF_8); } catch (UnsupportedEncodingException e) { throw new HDIVException(Constants.ENCODING_UTF_8 + " enconding not supported.", e); } catch (IllegalArgumentException e) { // Some decoding errors throw IllegalArgumentException } return beginRequest(createNewState(page.getNextStateId(), method, action)); } /** * Create new {@link IState} instance. * * @param stateId Identifier for the new {@link IState} * @param method HTTP method of the request. * @param action action name * @return new {@link IState} instance. */ protected IState createNewState(final int stateId, final Method method, final String action) { IState state = new State(stateId); state.setAction(action); state.setMethod(method); return state; } private final StateScope getStateScope() { StateScopeType type = scopeStack.peek(); if (type != StateScopeType.PAGE) { return stateScopeManager.getStateScope(type); } return null; } public String beginRequest(final IState state) { states.push(state); // Add to scope if (stateScope != null) { // Its custom scope Scope // TODO can't return the state id // We can't know the id before compose all parameters return null; } return toId(state); } protected String toId(final IState state) { return HDIVStateUtils.encode(page.getId(), state.getId(), getStateSuffix(state.getTokenType())); } /** * 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 composed by the page identifier and the state * identifier. * * @return Identifier composed by the page identifier and the state identifier. */ public String endRequest() { IState state = states.pop(); // Add to scope if (stateScope != null) { // Its custom Scope return stateScope.addState(context, state, getStateSuffix(state.getTokenType())); } // Add to page scope page.addState(state); // Save Page in session if this is the first state to add if (page.getStatesCount() == 1) { session.addPartialPage(context, page); } return toId(state); } private void logCompact(final HDIVException e) { if (hdivConfig.isDebugMode()) { StringBuilder sb = new StringBuilder(); sb.append("Error on page").append(e.getMessage()); if (e.getStackTrace().length > 0) { sb.append(" Cl:").append(e.getStackTrace()[0].getClassName()).append(":").append(e.getStackTrace()[0].getLineNumber()); } log.info(sb.toString()); } else { throw e; } } /** * It is called in the pre-processing stage of each user request assigning a new page identifier to the page. */ public final void startPage() { try { initPage(); } catch (HDIVException e) { logCompact(e); } } /** * It is called in the pre-processing stage of each user request assigning a new page identifier to the page with its parent state id. */ public void startPage(final String parentStateId) { initPage(parentStateId); } /** * It is called in the pre-processing stage of each user request. Create a new {@link IPage} based on an existing page. * * @param existingPage other IPage */ public void startPage(final IPage existingPage) { existingPage.markAsReused(); setPage(existingPage); } /** * 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 final void endPage() { try { if (isRequestStarted()) { // A request is started but not ended endRequest(); } if (page.getStatesCount() > 0) { // The page has states, update them in session session.addPage(context, page); } else { if (log.isDebugEnabled()) { log.debug("The page [" + page.getId() + "] has no states, is not stored in session"); } } } catch (HDIVException e) { logCompact(e); } } /** * @param stateScopeManager the stateScopeManager to set */ public void setStateScopeManager(final StateScopeManager stateScopeManager) { this.stateScopeManager = stateScopeManager; } }