/* * Copyright (C) 2001-2004 Red Hat Inc. All Rights Reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ package com.arsdigita.bebop; import com.arsdigita.bebop.parameters.BitSetParameter; import com.arsdigita.bebop.parameters.ParameterData; import com.arsdigita.bebop.parameters.ParameterModel; import com.arsdigita.bebop.util.Traversal; import com.arsdigita.dispatcher.DispatcherHelper; import com.arsdigita.developersupport.DeveloperSupport; import com.arsdigita.util.Assert; import com.arsdigita.util.UncheckedWrapperException; import com.arsdigita.web.ParameterMap; import com.arsdigita.web.RedirectSignal; import com.arsdigita.web.URL; import com.arsdigita.web.Web; import com.arsdigita.xml.Element; import java.io.IOException; import java.util.ArrayList; import java.util.BitSet; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.apache.log4j.Logger; /** *

The request-specific data (state) for a {@link Page}. All * methods that need access to the state of the associated page during * the processing of an HTTP request are passed a * PageState object. Since PageState * contains request-specific data, it should only be live during the * servicing of one HTTP request. This class has several related * responsibilites:

* *

State Management

* *

PageState objects store the values for global and * component state parameters and are responsible for retrieving these * values from the HTTP request. Components can access the values of * their state parameters through the {@link #getValue getValue} and * {@link #setValue setValue} methods.

* *

This class is also responsible for serializing the current state of * the page in a variety of ways for inclusion in the output: {@link * #generateXML generateXML} adds the state to an XML element and {@link * #stateAsURL stateAsURL} encodes the page's URL.

* *

The serialized form of the current page state can be quite large * so the page state can also be preserved in the HttpSession, on the * server. The Page object specifies this by calling {@link * Page#setUsingHttpSession setUsingHttpSession(true)}. When this * flag is set, then the page state will be preserved in the * HttpSession, and the {@link #stateAsURL stateAsURL} method will * only serialize the current URL and the control event. It will also * include a URL variable that the subsequent request uses to retrieve * the correct page state from the HttpSession. If the page state for * a particular request cannot be found, the constructor will throw a * {@link SessionExpiredException SessionExpiredException}.

* *

Up to {@link #getMaxStatesInSession getMaxStatesInSession()} * independent copies of the page state may be stored in the * HttpSession, to preserve the behavior of the browser's "back" * button.

* *

Note: As a convention, only the component to which a * state parameter belongs should modify it by calling * getValue and setValue. All other objects * should manipulate the state of a component only through * well-defined methods on the component.

* *

Control Events

* *

The control event consists of a pair of strings, the name * of the event and its associated value. Components use the * control event to send themselves a "delayed signal", i.e. a signal * that is triggered when the same page is requested again through a * link that has been generated with the result of {@link #stateAsURL} * as the target. The component can then access the name and value of * the event with calls to {@link #getControlEventName} and {@link * #getControlEventValue}.

* *

Typically, a component will contain code corresponding to the following * in its generateXML method: *

 *  public void generateXML(PageState state, Element parent) {
 *  if ( isVisible(state) ) {
 *      MyComponent target = firePrintEvent(state);
 *      Element link = new Element ("bebop:link", BEBOP_XML_NS);
 *      parent.addContent(link);
 *      target.generateURL(state, link);
 *      target.exportAttributes(link);
 *      target.generateExtraXMLAttributes(state, link);
 *      target.getChild().generateXML(state, link);
 *  }
 *  }
 * 
* (In reality, the component would not write a bebop:link element * directly, but use a {@link ControlLink} to do that automatically.) *

When the user clicks on the link that the above generateXML * method produces, the component's respond method is called * and can access the name and value of the event with code similar to the * following: *

 *   public void respond(PageState state) {
 *     String name = state.getControlEventName();
 *     String value = state.getControlEventValue();
 *     if ( "name".equals(name) ) {
 *       doSomeStateChange(value);
 *     } else {
 *       throw new IllegalArgumentException("Can't understand event " + name);
 *     }
 *   }
 * 
* * *

Temporary Storage

* *

Request local variables make it possible to store arbitrary objects * in the page state and allow components (and other objects) to cache * results of previous computations. For example, the {@link Form} * component uses a request local variable to store the {@link FormData} it * generates from the request and make it acessible to the widgets it * contains, and to other components through calling {@link * Form#getFormData Form.getFormData(state)}. See the documentation for * {@link RequestLocal} on how to use your own request local variables. * *

Convenience Access to Related Objects

* *

PageState objects store references to the HTTP request * and response that is currently being served. Components are free to * manipulate these objects as they see fit. *

* * @author David Lutterkort * @author Uday Mathur * @version $Id: PageState.java 975 2005-11-07 09:27:08Z clasohm $ */ public class PageState { /** Class specific logger instance. */ private static final Logger s_log = Logger.getLogger(PageState.class); /** The underlying Page object. */ private Page m_page; /** * The request to which this object corresponds */ private HttpServletRequest m_request; /** * The response to which results will be sent. */ private HttpServletResponse m_response; /** * The values of global and component specific state parameters extracted * from the request. */ private FormData m_pageState; /** * Temporary storage of arbitrary objects. */ private Map m_attributes; /** * The component that currently holds exclusive access to the control * event. Usually null, unlesss a component calls {@link * #grabControlEvent}. */ private Component m_grabbingComponent; /** * The visibility state of components. For a component with n = * Page.stateIndex, the n-th bit is set if the component is not * visible. * *

Initially, this variable refers to Page.m_invisible. Only when a * call to {@link #setVisible} is made, is that value * copied. (Copy-on-write) */ private BitSet m_invisible; private boolean m_visibilityDirty = true; private int m_nextSession; private final static String SESSION_ATTRIBUTE = "com.arsdigita.bebop.FormData"; private final static String SESSION_COUNTER_ATTRIBUTE = "com.arsdigita.bebop.FormData.counter"; private final static String CURRENT_SESSION_PARAMETER = "bbp.session"; private final static String PAGE_STATE_ATTRIBUTE = "com.arsdigita.bebop.PageState"; private static int s_maxSessions = 10; private Traversal m_visibilityTraversal = new VisibilityTraversal(); private List m_visibleComponents; /** * Returns the maximum number of independent page states that may be stored * in the HttpSession, for preserving the behavior of the "back" button. * @return the maximum number of independent page states that may * be stored in the HttpSession. */ public static int getMaxStatesInSession() { return s_maxSessions; } /** * Sets the maximum number of independent page states that may be stored in * the HttpSession, for preserving the behavior of the "back" button. * @param x the maximum number of independent page states to store * in the HttpSession. */ public static void setMaxStatesInSession(int x) { s_maxSessions = x; } /** * Returns the page state object for the given request, or null if none * exists yet. * * @param request The servlet request. * * @return The page state object for the given request, or null if none * exists yet. **/ public static PageState getPageState(HttpServletRequest request) { return (PageState) request.getAttribute(PAGE_STATE_ATTRIBUTE); } /** * Returns the page state object for the current request, or null if none * exists yet. * * @return The page state object for the current request, or null if none * exists yet. **/ public static PageState getPageState() { HttpServletRequest request = DispatcherHelper.getRequest(); if (request == null) { return null; } else { return getPageState(request); } } /** * Construct the PageState for an HTTP request. * * Calls {@link FormModel#process process} on the form model underlying * the page model and calls {@link Component#respond respond} on the * component from which the request originated. * * @param page The model of the page * @param request The request being served * @param response Where the response should be sent * * @pre request != null && request.get(page.PAGE_SELECTED) != null * @pre response != null && ! response.isCommitted() * @pre page != null * */ public PageState(Page page, HttpServletRequest request, HttpServletResponse response) throws ServletException { m_page = page; if ( m_page == null ) { m_page = new Page(); m_page.lock(); } m_request = request; m_response = response; FormData pageStateFromSession = null; if (m_page.isUsingHttpSession()) { pageStateFromSession = getStateFromSession(); } // always treat the request as a submission m_pageState = new FormData(m_page.getStateModel(), request, true, pageStateFromSession); m_page.getStateModel().process(this, m_pageState); m_invisible = decodeVisibility(); // Add the PageState to the request m_request.setAttribute(PAGE_STATE_ATTRIBUTE, this); } protected BitSet decodeVisibility() { BitSet difference = (BitSet)m_pageState.get(Page.INVISIBLE); BitSet current = (BitSet)m_page.m_invisible.clone(); if (difference != null) { current.xor(difference); } return current; } protected BitSet encodeVisibility(BitSet current) { BitSet difference = (BitSet)m_page.m_invisible.clone(); difference.xor(current); return difference; } /** * Helper function that returns the PageState object from the * HttpSession. */ private FormData getStateFromSession() throws SessionExpiredException { HttpSession session = m_request.getSession(true); FormData state = null; // have to bootstrap this manually from the request String key = m_request.getParameter(CURRENT_SESSION_PARAMETER); if (key != null) { state = (FormData)session.getAttribute(SESSION_ATTRIBUTE + key); // session is expired if we're looking for a particular // page state and couldn't find it. if (state == null) { throw new SessionExpiredException(); } } return state; } /** * Process the PageState and fire necessary events. Call respond on the * selected bebop component. */ public void respond() throws ServletException { String compKey = (String) m_pageState.get(Page.SELECTED); if ( compKey != null ) { Component c = m_page.getComponent(compKey); if ( c == null ) { throw new ServletException("Selected component not on page."); } try { DeveloperSupport.startStage("Bebop Respond " + c.getClass().getName()); c.respond(this); } finally { DeveloperSupport.endStage("Bebop Respond " + c.getClass().getName()); } } } /** * Return the page for which this object holds the state. * * @return the page for which this object holds state. * @post return != null */ public final Page getPage() { return m_page; } /** * Return the request object for the HTTP request. * * @return The HTTP request being currently served. */ public final HttpServletRequest getRequest() { return m_request; } /** * Return the response object for the HTTP response. * * @return The response for the HTTP request being served */ public final HttpServletResponse getResponse() { return m_response; } /** * The index of a component in the page model * * @pre c != null */ private int indexOf(Component c) { return m_page.stateIndex(c); } /** * Return true is the comonent c is currently * visible. This method should only be used by components * internally. All other objects should call {@link Component#isVisible * Component.isVisible} on the component. * * @param c the components whose visibility should be returned * @return true if the component is visible */ public boolean isVisible(Component c) { if ( ! getPage().stateContains(c)) { return true; } if ( m_invisible == null ) { return m_page.isVisibleDefault(c); } else { return ! m_invisible.get(indexOf(c)); } } /** * Return true is the component c is currently * visible on the page displayed to the end user. This is true * if the component's visibility flag is set, and it is in a * container visible to the end user. * *

This method is different than isVisible which * only returns the visibility flag for the individual component. * * @param c the components whose visibility should be returned * @return true if the component is visible */ public boolean isVisibleOnPage(Component c) { if (m_visibleComponents == null) { m_visibleComponents = new ArrayList(); m_visibilityTraversal.preorder(getPage().getPanel()); } return m_visibleComponents.contains(c); } private class VisibilityTraversal extends Traversal { protected void act(Component c) { m_visibleComponents.add(c); } protected int test(Component c) { if (isVisible(c)) { return PERFORM_ACTION; } else { // not visible, so neither are children return SKIP_SUBTREE; } } } /** * Set the visibility of a component. Calls to this method change the * default visibility set with {@link Page#setVisibleDefault * Page.setVisibleDefault}. This method should only be used by * components internally. All other objects should call {@link * Component#setVisible Component.setVisible} on the component. * *

Without explicit changes, a component is visible. * * @param c the component whose visibility is to be changed * @param v true if the component should be visible */ public void setVisible(final Component c, final boolean v) { if (Assert.isEnabled()) { Assert.isTrue(getPage().stateContains(c), "Component" + c + " is not registered on Page " + getPage()); } if (m_invisible == null || m_invisible == getPage().m_invisible) { // copy on write m_invisible = (BitSet) getPage().m_invisible.clone(); } int i = indexOf(c); if (v) { if (!m_invisible.get(i)) return; m_invisible.clear(i); } else { if (m_invisible.get(i)) return; m_invisible.set(i); } if (s_log.isInfoEnabled()) { s_log.info("Marking visibility parameter as dirty " + m_request + " because of component " + c); } // Do this only in toURL since the RLE is expensive //m_pageState.put(Page.INVISIBLE, encodeVisibility(m_invisible)); m_visibilityDirty = true; m_visibleComponents = null; } /** * Resets the given component and its children to their default * visibility. Also resets the state parameters of the given * component and its children to null. This is not a speedy * method. Do not call gratuitously. * * @param c the parent component whose state parameters and * visibility you wish to reset. * */ public void reset(Component c) { getPage().reset(this, c); } // /** // * Store an attribute keyed on the object key. The // * PageState puts no restrictions on what can be stored as // * an attribute or how they are managed. // * // * To remove an attribute, call setAttribute(key, null). // * // * The attributes are only accessible as long as the // * PageState is alive, typically only for the duration of // * the request. // * // * @deprecated Use either setAttribute on {@link // * HttpServletRequest the HTTP request object}, or, preferrably, use a // * {@link RequestLocal request local} variable. Will be removed on // * 2001-06-13. // * // */ // public void setAttribute(Object key, Object value) { // if ( m_attributes == null ) { // m_attributes = new HashMap(); // } // m_attributes.put(key, value); // } // /** // * Get the value of an attribute stored with the same key with {@link // * #setAttribute setAttribute}. // * // * @deprecated Use either getAttribute on {@link // * HttpServletRequest the HTTP request object}, or, preferrably, use a // * {@link RequestLocal request local} variable. Will be removed on // * 2001-06-13. // * // */ // public Object getAttribute(Object key) { // if ( m_attributes == null ) { // return null; // } // return m_attributes.get(key); // } /** * Set the value of the state parameter p. The concrete * type of value must be compatible with the type of the * state parameter. * *

The parameter must have been previously added with a call to * {@link Page#addComponentStateParam * Page.getComponentStateParam}. This method should only be called by * the component that added the state parameter to the page. Users of * the component should manipulate the parameter through methods the * component provides. * * @param p a state parameter * @param value the new value for this state parameter. The concrete * type depends on the type of the parameter being used. */ public void setValue(ParameterModel p, Object value) { m_pageState.put(p.getName(), value); } /** * Get the value of state parameter p. The concrete type of * the return value depends on the type of the parameter being * used. * *

The parameter must have been previously added with a call to * {@link Page#addComponentStateParam * Page.addComponentStateParam}. This method should only be called by * the component that added the state parameter to the page. Users of * the component should manipulate the parameter through methods the * component provides. * * @param p a state parameter * @return the current value for this state parameter. The concrete * type depends on the type of the parameter being used. */ public Object getValue(ParameterModel p) { return m_pageState.get(p.getName()); } /** * Get the value of a global state parameter. * @deprecated Use {@link #getValue(ParameterModel m)} instead. If you * don't have a reference to the parameter model, you should not be * calling this method. Instead, the component that registered the * parameter should provide methods to manipulate it. Will be removed * 2001-06-20. */ public Object getGlobalValue(String name) { // WRS 9/7/01 // work-around: m_pageState will throw an exception when we get // a named parameter that's not in the form model. But for // API stability, we need to trap this and return null; since // there's no way to tell what set of globalValues are legal // in any particular page--that's what ParameterModels are for. try { return m_pageState.get(m_page.parameterName(name)); } catch (IllegalArgumentException iae) { return null; } } // /** // * Change the value of a global parameter // * // * @deprecated Use {@link #setValue(ParameterModel m, Object o)} // * instead. If you don't have a reference to the parameter model, you // * should not be calling this method. Instead, the component that // * registered the parameter should provide methods to manipulate // * it. Will be removed 2001-06-20. // */ // public void setGlobalValue(String name, Object value) { // m_pageState.put(m_page.parameterName(name), value); // } // Handling the control event /** * Grab the control event. Until {@link #releaseControlEvent * releaseControlEvent(c)} is called, only the component c * can be used in calls to {@link #setControlEvent setControlEvent}. * @pre c != null */ public void grabControlEvent(Component c) { if ( m_grabbingComponent != null && m_grabbingComponent != c ) { throw new IllegalStateException ("Component " + m_grabbingComponent.toString() + " already holds the control event"); } m_grabbingComponent = c; } /** * Set the control event. The control event is a delayed event * that only gets acted on when another request to this Page * is made. It is used to set which component should receive the * submission and lets the component set one component-specific name-value * pair to be used in the submission. *

* After calling this method links and hidden form controls generated * with {@link #stateAsURL} have been amended so that if the user clicks such a * link or submits a form containing those hidden controls, the exact * same values can be retrieved with {@link #getControlEventName} and * {@link #getControlEventValue}. *

* Stateful components can use the control event to change their * state. For example, a tabbed pane t might call * setControlEvent(t, "select", "2") just prior to * generating the link for its second tab. *

* The values of name and value have no * meaning to the page state, they are simply passed through without * modifications. It is up to specific components what values of * name and value are meaningful for it. * * @param c * @param name The component specific name of the event, may be * null * @param value The component specific value of the event, may be * null * @pre c == null || getPage().stateContains(c) */ public void setControlEvent(Component c, String name, String value) { Assert.isTrue(c == null || getPage().stateContains(c), "c == null || getPage().stateContains(c)"); if ( m_grabbingComponent != null && m_grabbingComponent != c ) { throw new IllegalStateException ("Component " + m_grabbingComponent.toString() + " holds the control event"); } // FIXME: This needs to take named components into account String key = null; if (c != null) { key = c.getKey(); if (key == null) { key = Integer.toString(m_page.stateIndex(c)); } } m_pageState.put(Page.SELECTED, (c == null) ? null : key); m_pageState.put(Page.CONTROL_EVENT, name); m_pageState.put(Page.CONTROL_VALUE, value); } /** * Set the control event. Both the event name and its value will be * null. */ public void setControlEvent(Component c) { setControlEvent(c, null, null); } /** * Clear the control event. Links and hidden form variables generated * after this call will not cause any component's respond method to be * called. * * @throws IllegalStateException if any component has grabbed the * control event but not released it yet. */ public void clearControlEvent() { setControlEvent(null); } /** * Get the name of the control event. */ public String getControlEventName() { return (String) m_pageState.get(Page.CONTROL_EVENT); } /** * Get the value associated with the control event. */ public String getControlEventValue() { return (String) m_pageState.get(Page.CONTROL_VALUE); } /** * Release the control event. * @param c The component that was passed to the last call to * grabControlEvent * @pre getPage().stateContains(c) */ public void releaseControlEvent(Component c) { if ( m_grabbingComponent == null ) { throw new IllegalStateException ("No component holds the control event, but " + c.toString() + " tries to release it."); } if ( c != m_grabbingComponent ) { throw new IllegalStateException ("Component " + m_grabbingComponent.toString() + " holds the control event, but " + c.toString() + " tries to release it."); } m_grabbingComponent = null; } /** * Add elements to parent that represent the current page * state. For each component or global state parameter on the page, a * <bebop:pageState> element is added to * parent. The name and value attributes * of the element contain the name and value of the state parameters as * they should appear in an HTTP request made back to this page. * *

Generates DOM fragment: *

     * <bebop:pageState name=... value=.../>
     * 
* * @param form This is the form in which the hidden variables will exist * * @see #setControlEvent setControlEvent */ public void generateXML(Element parent) { synchronizeVisibility(); for ( Iterator i = m_pageState.getParameters().iterator(); i.hasNext(); ) { ParameterData p = (ParameterData) i.next(); String key = (String) p.getName(); String value = p.marshal(); if ( value != null ) { Element hidden = parent.newChildElement("bebop:pageState", Component.BEBOP_XML_NS); hidden.addAttribute("name", key); hidden.addAttribute("value", value); } } } public void generateXML(Element parent, Iterator models) { synchronizeVisibility(); List excludeParams = new ArrayList(); if (models != null) { while (models.hasNext()) { excludeParams.add(((ParameterModel)models.next()).getName()); } } for ( Iterator i = m_pageState.getParameters().iterator(); i.hasNext(); ) { ParameterData p = (ParameterData) i.next(); String key = (String) p.getName(); String value = p.marshal(); if (value == null || excludeParams.contains(key)) { continue; } Element hidden = parent.newChildElement("bebop:pageState", Component.BEBOP_XML_NS); hidden.addAttribute("name", key); hidden.addAttribute("value", value); } } /** * Export the current page state into the HttpSession by putting the entire * m_pageState (type FormData) object into the HttpSession. * *

* Package visibility is intentional. * * @see #setControlEvent setControlEvent */ void stateAsHttpSession() { // create session if we need to HttpSession session = m_request.getSession(true); // get + increment counter counter Integer counterObj = (Integer)session.getAttribute(SESSION_COUNTER_ATTRIBUTE); if (counterObj == null) { m_nextSession = 0; } else { m_nextSession = counterObj.intValue() + 1; } session.setAttribute(SESSION_ATTRIBUTE + m_nextSession, m_pageState); session.setAttribute(SESSION_COUNTER_ATTRIBUTE, new Integer(m_nextSession)); // remove an old session int toRemove = m_nextSession - s_maxSessions; if (toRemove >= 0) { session.removeAttribute(SESSION_ATTRIBUTE + toRemove); } } /** *

Write the current state of the page as a URL.

* *

The URL representing the state points to the same URL that * the current request was made from and contains a query string * that represents the page state.

* *

If the current page has the useHttpSession flag set, then * the URL query string that we generate will only contain the * current value of the control event, and the rest of the page * state is preserved via the HttpSession. Otherwise, the query * string contains the entire page state.

* * @return a string containing the current state of a page. * @see #setControlEvent setControlEvent * @see Page#isUsingHttpSession Page.isUsingHttpSession * @see Page#setUsingHttpSession Page.setUsingHttpSession */ public String stateAsURL() throws IOException { return m_response.encodeURL(toURL().toString()); } public final URL toURL() { synchronizeVisibility(); final ParameterMap params = new ParameterMap(); if (s_log.isDebugEnabled()) { dumpVisibility(); } final Iterator iter = m_pageState.getParameters().iterator(); while (iter.hasNext()) { final ParameterData data = (ParameterData) iter.next(); final String key = (String) data.getName(); if (!m_page.isUsingHttpSession() || Page.CONTROL_EVENT_KEYS.contains(key)) { final String value = data.marshal(); if (value != null) { params.setParameter(key, value); } } } if (m_page.isUsingHttpSession()) { params.setParameter(CURRENT_SESSION_PARAMETER, new Integer(m_nextSession)); } return URL.request(m_request, params); } private void synchronizeVisibility() { if (m_visibilityDirty) { if (s_log.isInfoEnabled()) { s_log.info("Encoding visibility parameter " + m_request); } m_pageState.put(Page.INVISIBLE, encodeVisibility(m_invisible)); m_visibilityDirty = false; } } private void dumpVisibility() { BitSetParameter raw = new BitSetParameter("raw", BitSetParameter.ENCODE_RAW); BitSetParameter dgap = new BitSetParameter("dgap", BitSetParameter.ENCODE_DGAP); BitSet current = (BitSet)m_invisible; BitSet base = (BitSet)m_page.m_invisible; BitSet difference = (BitSet)current.clone(); difference.xor(base); s_log.debug("Current: " + current.toString()); s_log.debug("Default: " + base.toString()); s_log.debug("Difference: " + difference.toString()); s_log.debug("Current RAW: " + raw.marshal(current)); s_log.debug("Default RAW: " + raw.marshal(base)); s_log.debug("Difference RAW: " + raw.marshal(difference)); s_log.debug("Current DGAP: " + dgap.marshal(current)); s_log.debug("Default DGAP: " + dgap.marshal(base)); s_log.debug("Difference DGAP: " + dgap.marshal(difference)); s_log.debug("Current Result: " + dgap.unmarshal(dgap.marshal(current))); s_log.debug("Default Result: " + dgap.unmarshal(dgap.marshal(base))); s_log.debug("Difference Result: " + dgap.unmarshal(dgap.marshal(difference))); if (!current.equals(dgap.unmarshal(dgap.marshal(current)))) { s_log.debug("Broken marshal/unmarshal for current"); } if (!base.equals(dgap.unmarshal(dgap.marshal(base)))) { s_log.debug("Broken marshal/unmarshal for default"); } if (!difference.equals(dgap.unmarshal(dgap.marshal(difference)))) { s_log.debug("Broken marshal/unmarshal for difference"); } } /** * Get the URI to which the current request was made. Copes with the * black magic that is needed to get the URI if the request was handled * through a dispatcher. If no dispatcher was involved in the request, * returns the request URI from the HTTP request. * * @post return != null * * @return the URI to which the current request was made */ public String getRequestURI() { final URL url = Web.getContext().getRequestURL(); if (url == null) { return m_request.getRequestURI(); } else { return url.getRequestURI(); } } /** * Return true if all the global and component state parameters * extracted from the HTTP request were successfully validated against * their parameter models in the {@link Page}. * * @return true if the values of all global and component state * parameters are valid with respect to their parameter models. */ public boolean isValid() { return m_pageState.isValid(); } /** * Return an iterator over the errors that occurred in trying to * validate the state parameters against their parameter models in * {@link Page}. * * @return an iterator over validation errors * @see FormData#getErrors */ public Iterator getErrors() { return m_pageState.getAllErrors(); } /** * Return a string with all the errors that occurred in trying to * validate the state parameters against their parameter models in * {@link Page}. The string consists simply of the concatenation of all * error messages that the result of {@link #getErrors} iterates over. * * @return all validation errors concatenated into one string */ public String getErrorsString() { StringBuffer s = new StringBuffer(); for (Iterator i = m_pageState.getAllErrors(); i.hasNext(); ) { s.append(i.next().toString()); s.append(System.getProperty("line.separator")); } return s.toString(); } /** * Force the validation of all global and component state parameters * against their parameter models. This method only needs to be called if * the values of the parameters have been changed with {@link #setValue * setValue} or {@link #setGlobalValue setGlobalValue} and may now * contain invalid values. */ public void forceValidate() { m_pageState.forceValidate(this); } /** Convert to a String. * @return a human-readable representation of this. */ public String toString() { String newLine = System.getProperty("line.separator"); String result = super.toString() + " = {" + newLine + "m_page = " + m_page + "," + newLine + "m_request = " + m_request + "," + newLine + "m_response = " + m_response + "," + newLine // FormData + "m_pageState = " + m_pageState.asString() + "," + newLine + "m_attributes = " + m_attributes + "," + newLine // Map to FormData + "," + newLine + "m_grabbingComponent = " + m_grabbingComponent + "," + newLine + "m_invisible = " + m_invisible + newLine + "}"; return result; } /** * Clear the control event then redirect to the new page state. * * @param isCommitRequested indicates if a commit required before the redirect * * @throws RedirectSignal to the new page state * * @see RedirectSignal#RedirectSignal(String, boolean) */ public void redirectWithoutControlEvent(boolean isCommitRequested) { clearControlEvent(); try { throw new RedirectSignal(stateAsURL(), true); } catch (IOException ioe) { throw new UncheckedWrapperException(ioe); } } }