/* * Copyright 2001-2008 Geert Bevin (gbevin[remove] at uwyn dot com) * Licensed under the Apache License, Version 2.0 (the "License") * $Id: RequestState.java 3928 2008-04-22 16:25:18Z gbevin $ */ package com.uwyn.rife.engine; import com.uwyn.rife.config.RifeConfig; import com.uwyn.rife.continuations.CallState; import com.uwyn.rife.continuations.ContinuationContext; import com.uwyn.rife.continuations.exceptions.AnswerException; import com.uwyn.rife.engine.exceptions.CancelEmbeddingTriggeredException; import com.uwyn.rife.engine.exceptions.EmbedPropertiesErrorException; import com.uwyn.rife.engine.exceptions.EngineException; import com.uwyn.rife.tools.exceptions.LightweightError; import javax.servlet.http.Cookie; import java.io.IOException; import java.util.*; class RequestState { private final static ThreadLocal<RequestState> ACTIVE_REQUEST_STATES = new ThreadLocal<RequestState>(); private InitConfig mInitConfig = null; private Site mSite = null; private Request mRequest = null; private Response mResponse = null; private String mGateUrl = null; private ElementInfo mTarget = null; private ElementInfo mSnapback = null; private String mActiveElementId = null; private EmbeddingContext mEmbeddingContext = null; private ElementInfo mPrecedenceTarget = null; private Map<String, Cookie> mStateCookies = null; private Map<String, String[]> mStateGlobalVars = null; private ResultStates mResultStatesRestored = null; private ResultStates mResultStatesObtained = null; private ElementSupport mErrorElement = null; private Throwable mErrorException = null; private ElementExecutionState mElementExecutionState = null; private String mContinuationId = null; private ContinuationContext<ElementSupport> mContinuationContext = null; static RequestState getActiveRequestState() { return ACTIVE_REQUEST_STATES.get(); } static RequestState getInstance(InitConfig initConfig, Site site, Request request, Response response, String gateUrl, ResultStates resultStates, String pathInfo, ElementInfo target) { RequestState state = new RequestState(initConfig, site, request, response, gateUrl, resultStates, new ResultStates(resultStates), pathInfo, null); state.setTarget(target); state.setSnapback(target); state.setupContinuations(); return state; } static RequestState getEmbeddedInstance(Response response, EmbeddingContext embeddingContext, Map<String, String[]> parameters, ElementInfo embeddedElement) { RequestState embedding_state = embeddingContext.getElementContext().getRequestState(); RequestState embedded_state = new RequestState(embedding_state.getInitConfig(), embedding_state.getSite(), embedding_state.getRequest(), response, embedding_state.getGateUrl(), embedding_state.getElementResultStatesRestored(), embedding_state.getElementResultStatesObtained(), "", embeddingContext); embedded_state.setErrorElement(embedding_state.getErrorElement()); embedded_state.setErrorException(embedding_state.getErrorException()); embedded_state.setTarget(embeddedElement); embedded_state.setSnapback(embeddedElement); embedded_state.setupContinuations(); // preserve the request parameters, trigger inputs and request method, this // makes the embedded element behave correctly after exits and such embedded_state.getElementState().setMethod(embedding_state.getElementState().getMethod()); embedded_state.getElementState().setRequestParameters(parameters); embedded_state.getElementState().setTriggerInputs(embedding_state.getElementState().getTriggerInputs()); embedded_state.setStateCookies(embedding_state.getStateCookies()); return embedded_state; } private RequestState(InitConfig initConfig, Site site, Request request, Response response, String gateUrl, ResultStates resultStatesRestored, ResultStates resultStatesObtained, String pathInfo, EmbeddingContext embeddingContext) throws EngineException { assert initConfig != null; assert request != null; assert gateUrl != null; mInitConfig = initConfig; mGateUrl = gateUrl; mSite = site; mEmbeddingContext = embeddingContext; mResultStatesRestored = resultStatesRestored; mResultStatesObtained = resultStatesObtained; mRequest = request; mResponse = response; mElementExecutionState = new ElementExecutionState(this); mElementExecutionState.setPathInfo(pathInfo); } void setupContinuations() { mContinuationId = null; mContinuationContext = null; // try to obtain the continuation ID from the result states if (mResultStatesObtained != null) { String context_id = buildContextId(); ElementResultState result_state = mResultStatesObtained.get(context_id); if (result_state != null) { mContinuationId = result_state.getContinuationId(); } } // get the explicit continuation ID if it exists if (null == mContinuationId) { String[] continuation_id_values = mElementExecutionState.getRequestParameters().get(ReservedParameters.CONTID); if (continuation_id_values != null && continuation_id_values.length > 0) { mContinuationId = continuation_id_values[0]; } } } String getContinuationId() { return mContinuationId; } void setContinuationId(String id) { mContinuationId = id; mContinuationContext = null; } ContinuationContext getContinuationContext(ElementInfo elementInfo) { if (mContinuationId != null && null == mContinuationContext) { try { mContinuationContext = mSite.getContinuationManager().resumeContext(mContinuationId); } catch (CloneNotSupportedException e) { throw new EngineException(e); } } if (mContinuationContext != null) { ElementSupport continuable_element = mContinuationContext.getContinuable(); ElementInfo continuable_element_info = continuable_element._getElementInfo(); // the element info can be null if this continuation context was obtained // through Terracotta from another node, in that case the comparison below // can't be done if (null == continuable_element_info) { return mContinuationContext; } String continuable_absolute_element_id = Site.getAbsoluteId(continuable_element_info.getId(), continuable_element_info); if (null == elementInfo || continuable_absolute_element_id.equals(elementInfo.getId())) { return mContinuationContext; } } return null; } ResultStates getElementResultStatesRestored() { return mResultStatesRestored; } ResultStates getElementResultStatesObtained() { return mResultStatesObtained; } String buildContextId() { return buildContextId(new StringBuilder()).toString(); } private StringBuilder buildContextId(StringBuilder buffer) { boolean is_embedded_submission = isEmbedded() && null == getTarget().getUrl(); if (is_embedded_submission) { buffer = getEmbeddingContext().getElementContext().getRequestState().buildContextId(buffer); buffer.append("::"); buffer.append(getEmbeddingContext().getTemplate().getName()); buffer.append(":"); } // during precedence, target the submission context id to the actual target element if (isPreceeding()) { buffer.append(getPrecedenceTarget().getId()); } // during inheritance, target the submission context id to the actual target element else if (mElementExecutionState.inInheritanceStructure()) { buffer.append(getTarget().getId()); } // use the ID of the active element if it's available else if (mActiveElementId != null) { buffer.append(mActiveElementId); } // use the id of the target element else { buffer.append(getTarget().getId()); } if (is_embedded_submission) { String differentiator = getEmbeddingContext().getDifferentiator(); if (differentiator != null) { buffer.append(":"); buffer.append(differentiator); } } return buffer; } ElementContext getElementContext(ElementInfo elementInfo, Response response) throws EngineException { ElementSupport element = null; ContinuationContext context = getContinuationContext(elementInfo); if (context != null) { element = (ElementSupport)mContinuationContext.getContinuable(); synchronized (element) { // ensure that the element's element info is set to the element, mainly // for use with Terracotta element.setElementInfo(elementInfo); } } else { element = elementInfo.getElement(); } if (null == element) { throw new EngineException("Error while instantiating the element '"+elementInfo.getDeclarationName()+"' at url."); } ElementContext element_context = new ElementContext(element, this, response); if (null == element_context) { throw new EngineException("Error while constructing the context for the element '"+elementInfo.getDeclarationName()+"'."); } return element_context; } void handlePrecedence(Response response, ElementInfo precedenceTarget) { // obtain the precedence stack and if it exists the top parent Stack<ElementInfo> precedence_stack = precedenceTarget.getPrecedenceStack(); if (precedence_stack != null && precedence_stack.size() > 0) { // preserve the original state variables ElementInfo original_target = getTarget(); Stack<String> original_inheritance_stack = mElementExecutionState.getInheritanceStack(); RequestMethod original_method = mElementExecutionState.getMethod(); // retain initiating precedence target element info to be able to // check for a precedence structure later if (null == getPrecedenceTarget()) { setPrecedenceTarget(precedenceTarget); mElementExecutionState.setMethod(RequestMethod.PRECEDENCE); } // process all precedence elements ElementInfo active_element = null; for (int i = precedence_stack.size()-1; i >= 0; i--) { active_element = precedence_stack.get(i); if (active_element == precedenceTarget) { break; } // process the precedence element setTarget(active_element); service(); } // clear the preserved precedence target element info only if this // was the element info that initiated it if (precedenceTarget == getPrecedenceTarget()) { setPrecedenceTarget(null); } // restore original state variables setTarget(original_target); mElementExecutionState.setInheritanceStack(original_inheritance_stack); mElementExecutionState.setMethod(original_method); } } void service() throws EngineException { // Get the currently active request state to be able to restore it // as the active one after this new one finished executing. This is // needed for embedded requests. RequestState previous = ACTIVE_REQUEST_STATES.get(); // Set the actively running request state ACTIVE_REQUEST_STATES.set(this); try { // obtain the inheritance stack and if it exists the top parent ElementInfo element_info = null; if (mElementExecutionState.inInheritanceStructure()) { String element_id = mElementExecutionState.getInheritanceStack().pop(); element_info = mSite.resolveId(element_id); } if (null == element_info) { element_info = mTarget; } // preserve the ongoing continuation context ContinuationContext previous_context = ContinuationContext.getActiveContext(); try { Object call_answer = null; ElementContext element_context = getElementContext(element_info, mResponse); while(true) { try { try { // successively process each element context until none is available anymore ContinuationContext.setActiveContext(null); ElementContext next_element_context = element_context.processContext(); while (next_element_context != null) { element_context = next_element_context; ContinuationContext.setActiveContext(null); synchronized (this) { mActiveElementId = element_context.getElementInfo().getId(); try { next_element_context = element_context.processContext(); } finally { mActiveElementId = null; } } } call_answer = null; break; } catch (AnswerException e) { synchronized (this) { // obtain the context and the answer of the answering element ContinuationContext context = e.getContext(); call_answer = e.getAnswer(); // handle the call state of the last processed element context if (context != null && context.getActiveCallState() != null) { CallState call_state = context.getActiveCallState(); mContinuationContext = null; mContinuationId = call_state.getContinuationId(); mElementExecutionState = ((ElementExecutionState)call_state.getState()).clone(); mElementExecutionState.setRequestState(this); // try to obtain the continuation context ContinuationContext continuation_context = getContinuationContext(null); if (null == continuation_context) { break; } element_info = ((ElementSupport)continuation_context.getContinuable()).getElementInfo(); // set the call answer continuation_context.setCallAnswer(call_answer); // set the request target to the actual element, cancelling // the previous exit target that was set during the call // continuation mTarget = element_info; // create the new element context element_context = getElementContext(element_info, mResponse); // propagate the outputs of the answer element to the call element ElementSupport answer_element = (ElementSupport)e.getContext().getContinuable(); ElementContext element_context_answer = (ElementContext)answer_element._getElementContext(); OutputValues outputs = element_context.getOutputs(); for (Map.Entry<String, String[]> output_entry : element_context_answer.getOutputs().aggregateValues().entrySet()) { outputs.put(output_entry.getKey(), output_entry.getValue()); } } } } catch (CancelEmbeddingTriggeredException e) { if (isEmbedded()) { throw e; } mResponse.print(e.getEmbeddingContent()); mResponse.flush(); break; } } catch (LightweightError e) { throw e; } catch (Throwable e) { ErrorHandler matching_handler = null; if (element_info != null) { if (element_info.hasErrorHandlers()) { for (ErrorHandler handler : element_info.getErrorHandlers()) { if (handler.appliesToException(e)) { matching_handler = handler; break; } } } } if (matching_handler != null) { mResponse.clearBuffer(); mErrorElement = element_context.getElementSupport(); mErrorException = e; element_context = getElementContext(matching_handler.getTarget(), mResponse); } else { if (e instanceof RuntimeException) { throw (RuntimeException)e; } else { throw new EngineException(e); } } } } } finally { // restore ongoing continuation context ContinuationContext.setActiveContext(previous_context); } } finally { // restory the previously running request state ACTIVE_REQUEST_STATES.set(previous); } } void setTarget(ElementInfo targetElement) { assert targetElement != null; synchronized (mElementExecutionState) { mTarget = targetElement; mElementExecutionState.clearVirtualInputs(); // create the initial inheritance stack Stack<ElementInfo> target_inheritancestack = mTarget.getInheritanceStack(); mElementExecutionState.setInheritanceStack(null); if (target_inheritancestack != null) { Stack<String> inheritance_stack = new Stack<String>(); for (ElementInfo element_info : target_inheritancestack) { inheritance_stack.add(element_info.getId()); } mElementExecutionState.setInheritanceStack(inheritance_stack); } // process the possible trigger list if (mElementExecutionState.inInheritanceStructure()) { if (!mElementExecutionState.hasTriggerList()) { if (isEmbedded()) // don't take the trigger list that the embedder created as the trigger list for the embedded element { mElementExecutionState.setTriggerList(new ArrayList<TriggerContext>()); } else { // if no trigger list is present, decode it from the request mElementExecutionState.setTriggerList(TriggerListEncoder.decode(mElementExecutionState.getRequestParameterValues(ReservedParameters.TRIGGERLIST))); } } } } } void setSnapback(ElementInfo snapbackElement) { assert snapbackElement != null; mSnapback = snapbackElement; } InitConfig getInitConfig() { return mInitConfig; } Site getSite() { return mSite; } void clearRequest() { mRequest = null; } Request getRequest() { return mRequest; } Response getResponse() { return mResponse; } ElementInfo getTarget() { return mTarget; } ElementInfo getSnapback() { return mSnapback; } EmbeddingContext getEmbeddingContext() { return mEmbeddingContext; } boolean isEmbedded() { return mEmbeddingContext != null; } String getEmbedDifferentiator() { if (null == mEmbeddingContext) { return null; } return mEmbeddingContext.getDifferentiator(); } String getEmbedValue() { if (null == mEmbeddingContext) { return null; } return mEmbeddingContext.getValue(); } Object getEmbedData() { if (null == mEmbeddingContext) { return null; } return mEmbeddingContext.getData(); } Properties getEmbedProperties() throws EmbedPropertiesErrorException { if (null == mEmbeddingContext) { return null; } try { return mEmbeddingContext.getEmbedProperties(); } catch (IOException e) { throw new EmbedPropertiesErrorException(mTarget.getDeclarationName(), mEmbeddingContext.getValue(), e); } } void setPrecedenceTarget(ElementInfo elementInfo) { mPrecedenceTarget = elementInfo; } ElementInfo getPrecedenceTarget() { return mPrecedenceTarget; } boolean isPreceeding() { return mPrecedenceTarget != null; } String getGateUrl() { return mGateUrl; } ElementExecutionState getElementState() { return mElementExecutionState; } String getServerRootUrl(int port) { return mRequest.getServerRootUrl(port); } String getWebappRootUrl(int port) { if (RifeConfig.Engine.getProxyRootUrl() != null) { return RifeConfig.Engine.getProxyRootUrl(); } StringBuilder webapp_root = new StringBuilder(); webapp_root.append(getServerRootUrl(port)); String gate_url = getGateUrl(); if (!gate_url.startsWith("/")) { webapp_root.append("/"); } webapp_root.append(gate_url); if (gate_url.length() > 0 && !gate_url.endsWith("/")) { webapp_root.append("/"); } return webapp_root.toString(); } void setErrorElement(ElementSupport errorElement) { mErrorElement = errorElement; } void setErrorException(Throwable errorException) { mErrorException = errorException; } ElementSupport getErrorElement() { return mErrorElement; } Throwable getErrorException() { return mErrorException; } // wrapped methods Object getRequestAttribute(String name) { return mRequest.getAttribute(name); } boolean hasRequestAttribute(String name) { return mRequest.hasAttribute(name); } Enumeration getRequestAttributeNames() { return mRequest.getAttributeNames(); } String getCharacterEncoding() { return mRequest.getCharacterEncoding(); } String getContentType() { return mRequest.getContentType(); } long getDateHeader(String name) { return mRequest.getDateHeader(name); } String getHeader(String name) { return mRequest.getHeader(name); } Enumeration getHeaderNames() { return mRequest.getHeaderNames(); } Enumeration getHeaders(String name) { return mRequest.getHeaders(name); } int getIntHeader(String name) { return mRequest.getIntHeader(name); } Locale getLocale() { return mRequest.getLocale(); } Enumeration getLocales() { return mRequest.getLocales(); } String getProtocol() { return mRequest.getProtocol(); } String getRemoteAddr() { return mRequest.getRemoteAddr(); } String getRemoteUser() { return mRequest.getRemoteUser(); } String getRemoteHost() { return mRequest.getRemoteHost(); } int getServerPort() { return mRequest.getServerPort(); } String getScheme() { return mRequest.getScheme(); } String getServerName() { return mRequest.getServerName(); } boolean isSecure() { return mRequest.isSecure(); } void removeRequestAttribute(String name) { mRequest.removeAttribute(name); } void setRequestAttribute(String name, Object object) { mRequest.setAttribute(name, object); } // shielded methods boolean hasUploadedFile(String name) { return mRequest.hasFile(name); } UploadedFile getUploadedFile(String name) { return mRequest.getFile(name); } UploadedFile[] getUploadedFiles(String name) { return mRequest.getFiles(name); } Collection<String> getUploadedFileNames() { return mRequest.getFiles().keySet(); } void setStateCookies(Map<String, Cookie> stateCookies) { mStateCookies = stateCookies; } Map<String, Cookie> getStateCookies() { return mStateCookies; } void setStateCookie(Cookie cookie) { assert cookie != null; assert cookie.getName() != null; if (null == mStateCookies) { mStateCookies = new HashMap<String, Cookie>(); } mStateCookies.put(cookie.getName(), cookie); } boolean hasCookie(String name) { assert name != null; if (mStateCookies != null && mStateCookies.containsKey(name)) { return true; } return mRequest.hasCookie(name); } Cookie getCookie(String name) { assert name != null; if (mStateCookies != null && mStateCookies.containsKey(name)) { return mStateCookies.get(name); } return mRequest.getCookie(name); } HashMap<String, Cookie> getCookies() { HashMap<String, Cookie> cookies = new HashMap<String, Cookie>(); Cookie[] request_cookies = mRequest.getCookies(); if (request_cookies != null) { for (Cookie cookie : request_cookies) { cookies.put(cookie.getName(), cookie); } } if (mStateCookies != null) { cookies.putAll(mStateCookies); } return cookies; } Map<String, String[]> getStateGlobalVars() { return mStateGlobalVars; } void setStateGlobalVar(String name, String[] values) { assert name != null; if (null == mStateGlobalVars) { mStateGlobalVars = new HashMap<String, String[]>(); } mStateGlobalVars.put(name, values); } void clearStateGlobalVar(String name) { assert name != null; if (null == mStateGlobalVars) { return; } mStateGlobalVars.remove(name); } EmbeddingListener getEmbeddingListener() { return new EmbeddingListener(this); } PrecedenceListener getPrecedenceListener() { return new PrecedenceListener(this); } static class EmbeddingListener implements OutputListener, OutcookieListener { private RequestState mState = null; private EmbeddingListener(RequestState state) { assert state != null; mState = state; } public void outputValueSet(String name, String[] values) { if (!mState.isEmbedded()) { return; } if (!mState.getEmbeddingContext().getElementContext().getElementInfo().containsGlobalVar(name)) { return; } mState.getEmbeddingContext().getElementContext().setOutputValues(name, values); mState.getEmbeddingContext().getElementContext().getRequestState().setStateGlobalVar(name, values); } public void outputValueCleared(String name) { if (!mState.isEmbedded()) { return; } if (!mState.getEmbeddingContext().getElementContext().getElementInfo().containsGlobalVar(name)) { return; } mState.getEmbeddingContext().getElementContext().clearOutputValue(name); mState.getEmbeddingContext().getElementContext().getRequestState().clearStateGlobalVar(name); } public void automatedOutputValueSet(String name, String[] values) { if (!mState.isEmbedded()) { return; } if (!mState.getEmbeddingContext().getElementContext().getElementInfo().containsGlobalVar(name)) { return; } mState.getEmbeddingContext().getElementContext().setAutomatedOutputValues(name, values); mState.getEmbeddingContext().getElementContext().getRequestState().setStateGlobalVar(name, values); } public void automatedOutputValueCleared(String name) { if (!mState.isEmbedded()) { return; } if (!mState.getEmbeddingContext().getElementContext().getElementInfo().containsGlobalVar(name)) { return; } mState.getEmbeddingContext().getElementContext().clearAutomatedOutputValue(name); mState.getEmbeddingContext().getElementContext().getRequestState().clearStateGlobalVar(name); } public void outcookieSet(Cookie cookie) { if (!mState.isEmbedded()) { return; } if (!mState.getEmbeddingContext().getElementContext().getElementInfo().containsIncookie(cookie.getName()) && !mState.getEmbeddingContext().getElementContext().getElementInfo().containsGlobalCookie(cookie.getName())) { return; } mState.getEmbeddingContext().getElementContext().setCookie(cookie); mState.getEmbeddingContext().getElementContext().getRequestState().setStateCookie(cookie); } } static class PrecedenceListener implements OutputListener, OutcookieListener { private RequestState mState = null; private PrecedenceListener(RequestState state) { assert state != null; mState = state; } public void outputValueSet(String name, String[] values) { if (!mState.isPreceeding()) { return; } if (!mState.getPrecedenceTarget().containsGlobalVar(name)) { return; } mState.getElementState().getRequestParameters().put(name, values); } public void outputValueCleared(String name) { if (!mState.isPreceeding()) { return; } if (!mState.getPrecedenceTarget().containsGlobalVar(name)) { return; } mState.getElementState().getRequestParameters().remove(name); } public void automatedOutputValueSet(String name, String[] values) { outputValueSet(name, values); } public void automatedOutputValueCleared(String name) { outputValueCleared(name); } public void outcookieSet(Cookie cookie) { if (!mState.isPreceeding()) { return; } if (!mState.getPrecedenceTarget().containsIncookie(cookie.getName()) && !mState.getPrecedenceTarget().containsGlobalCookie(cookie.getName())) { return; } mState.setStateCookie(cookie); } } }