ivelin 2002/10/27 00:23:18 Modified: src/java/org/apache/cocoon/components/xmlform Form.java Log: Implement automated population with default values of unchecked check-boxes Revision Changes Path 1.12 +235 -48 xml-cocoon2/src/java/org/apache/cocoon/components/xmlform/Form.java Index: Form.java =================================================================== RCS file: /home/cvs/xml-cocoon2/src/java/org/apache/cocoon/components/xmlform/Form.java,v retrieving revision 1.11 retrieving revision 1.12 diff -u -r1.11 -r1.12 --- Form.java 5 Oct 2002 16:32:00 -0000 1.11 +++ Form.java 27 Oct 2002 07:23:18 -0000 1.12 @@ -62,35 +62,32 @@ package org.apache.cocoon.components.xmlform; +import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.Enumeration; -import java.util.SortedSet; -import java.util.Set; +import java.util.HashMap; import java.util.HashSet; -import java.util.TreeSet; -import java.util.List; -import java.util.LinkedList; -import java.util.ArrayList; import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; import java.util.Map; -import java.util.Collections; - -import org.apache.commons.jxpath.JXPathContext; -import org.apache.commons.jxpath.Pointer; -import org.apache.commons.jxpath.JXPathException; -//import org.apache.commons.jxpath.ri.model.dom.DOMAttributePointer; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; import org.apache.cocoon.Constants; +import org.apache.cocoon.components.validation.Validator; +import org.apache.cocoon.components.validation.Violation; import org.apache.cocoon.environment.ObjectModelHelper; import org.apache.cocoon.environment.Request; import org.apache.cocoon.environment.Session; -import org.apache.cocoon.environment.Session; -import org.apache.cocoon.components.validation.Validator; -import org.apache.cocoon.components.validation.Violation; - -import org.w3c.dom.*; -import javax.xml.parsers.*; -import javax.xml.transform.*; +import org.apache.cocoon.transformation.XMLFormTransformer; +import org.apache.commons.jxpath.JXPathContext; +import org.apache.commons.jxpath.JXPathException; +import org.apache.commons.jxpath.Pointer; +import org.w3c.dom.Element; +import org.w3c.dom.Text; /** * <p> @@ -401,7 +398,7 @@ * radio button, text area, file upload, etc. * </p> */ - public void populate( Map objectModel ) + public void populate( Map sitemapObjectModel ) { // clean violations_ set clearViolations(); @@ -418,18 +415,19 @@ // while the request parameter name points to an int attribute List pviolations = new ArrayList(); - Request request = getRequest( objectModel ); - - Enumeration enum = request.getParameterNames (); - while (enum.hasMoreElements ()) - { - String path = (String) enum.nextElement (); + Map filteredParameters = getFilteredRequestParameters( sitemapObjectModel ); + Iterator iter = filteredParameters.entrySet().iterator(); + while (iter.hasNext()) + { + Map.Entry entry = (Map.Entry) iter.next(); + + String path = (String) entry.getKey(); // filter custom request parameter // not refering to the model if ( filterRequestParameter( path ) ) continue; - Object[] values = request.getParameterValues ( path ); + Object[] values = (Object[]) entry.getValue(); try { @@ -439,13 +437,14 @@ { Violation v = new Violation(); v.setPath( path ); - v.setMessage( VIOLATION_MESSAGE_DATA_FORMAT_ERROR ); + String message = VIOLATION_MESSAGE_DATA_FORMAT_ERROR; + v.setMessage( message ); pviolations.add( v ); } } // while // validate form model - autoValidate( objectModel ); + autoValidate( sitemapObjectModel ); // merge violation sets if ( violations_ != null) @@ -464,6 +463,81 @@ } + /** + * + * Filters request parameters which are not references to model properties. + * Sets default values for parameters which were expected in the request, + * but did not arrive (e.g. check boxes). + * + * @return filtered request parameters + * + */ + protected Map getFilteredRequestParameters( Map sitemapObjectModel ) + { + + Request request = getRequest( sitemapObjectModel ); + + Map filteredParameters = new HashMap(); + + // first filter out request parameters which do not refer to model properties + Enumeration enum = request.getParameterNames (); + while (enum.hasMoreElements ()) + { + String path = (String) enum.nextElement (); + + // filter custom request parameter + // not refering to the model + if ( filterRequestParameter( path ) ) continue; + + Object[] values = request.getParameterValues ( path ); + + filteredParameters.put( path, values ); + } + + // now, find expected parameters which did not arrive + // and set default values for them + String viewName = getFormView( sitemapObjectModel ); + Map expectedReferences = getFormViewState( viewName ).getModelReferenceMap(); + + Iterator iter = expectedReferences.entrySet().iterator(); + while ( iter.hasNext() ) + { + Map.Entry entry = (Map.Entry)iter.next(); + String propertyReference = ( String )entry.getKey(); + + // check if the expected parameter actually arrived in the request + if ( filteredParameters.get( propertyReference ) == null ) + { + // Since it is not there, try to provide a default value + String inputType = ( String )entry.getValue(); + + Object defaultValue = null; + if ( inputType.equals( XMLFormTransformer.TAG_SELECTBOOLEAN ) ) + { + // false for boolean type (usually, single check-box) + defaultValue = new Object[] { Boolean.FALSE }; + } + else if ( inputType.equals( XMLFormTransformer.TAG_SELECTMANY )) + { + // empty array for select many (usually, multi check-box) + defaultValue = new Object[0]; + } + else + { + // for all the rest, use a blank value and hope for the best + defaultValue = new Object[] {""}; + } + + filteredParameters.put( propertyReference, defaultValue ); + + } + + } // iterate over expectedReferences.entrySet() + + return filteredParameters; + + } // getFilteredRequestParameters + /** create a SortedSet view of the violations collection * for convenience of processors down the pipeline * protected void updateViolationsAsSortedSet() @@ -488,13 +562,13 @@ * this method to change the behaviour. * */ - protected void autoValidate( Map objectModel ) + protected void autoValidate( Map sitemapObjectModel ) { if (!autoValidateEnabled_) return; // perform validation for the phase // which matches the name of the current form view // if one is available - String formView = getFormView( objectModel ); + String formView = getFormView( sitemapObjectModel ); if ( formView != null) { validate( formView ); @@ -555,10 +629,10 @@ * Try to extract from the request * and return the current form view */ - public String getFormView( Map objectModel ) + public String getFormView( Map sitemapObjectModel ) { - Request request =getRequest( objectModel ); - return (String) getRequest( objectModel ).getParameter ( Form.FORM_VIEW_PARAM ); + Request request = getRequest( sitemapObjectModel ); + return (String) getRequest( sitemapObjectModel ).getParameter ( Form.FORM_VIEW_PARAM ); } @@ -598,9 +672,9 @@ * @param objectMap * @param id the form id */ - public static Form lookup ( Map objectModel, String id ) + public static Form lookup ( Map sitemapObjectModel, String id ) { - Request request =getRequest( objectModel ); + Request request = getRequest( sitemapObjectModel ); Form form = (Form) request.getAttribute( id ); if (form != null) return form; else @@ -622,9 +696,9 @@ * @param objectMap * @param id the form id */ - public static void remove ( Map objectModel, String id ) + public static void remove ( Map sitemapObjectModel, String id ) { - Request request =getRequest( objectModel ); + Request request =getRequest( sitemapObjectModel ); request.removeAttribute( id ); Session session = request.getSession( false ); @@ -638,11 +712,11 @@ * @param objectMap * @param isSessionScope if true the form will be bound in the session, otherwise request */ - public void save ( Map objectModel, String scope ) + public void save ( Map sitemapObjectModel, String scope ) { - Request request =getRequest( objectModel ); + Request request =getRequest( sitemapObjectModel ); - if ( lookup( objectModel, id_ ) != null ) + if ( lookup( sitemapObjectModel, id_ ) != null ) throw new java.lang.IllegalStateException( "Form [id=" + id_ + "] already bound in request or session " ); if (SCOPE_REQUEST.equals ( scope ) ) @@ -676,9 +750,9 @@ formListeners_.remove( formListener ); } - protected final static Request getRequest( Map objectModel ) + protected final static Request getRequest( Map sitemapObjectModel ) { - return (Request)objectModel.get(ObjectModelHelper.REQUEST_OBJECT); + return (Request)sitemapObjectModel.get(ObjectModelHelper.REQUEST_OBJECT); } @@ -688,6 +762,113 @@ } + /** + * <pre> + * When the transformer renders a form view, + * it lets the form wrapper know about each referenced model property. + * This allows a precise tracking and can be used for multiple reasons: + * 1) Verify that the client does not temper with the input fields as specified by the + * form view author + * 2) Allow default values to be used for properties which were expected to be send by the client, + * but for some reason were not. A typical example is a check box. When unchecked, the browser + * does not send any request parameter, leaving it to the server to handle the situation. + * This proves to be a very error prone problem when solved on a case by case basis. + * By having a list of expected property references, the model populator can detect + * a checkbox which was not send and set the property value to false. + * + * NOTE: This added functionality is ONLY useful for SESSION scope forms. + * Request scope forms are constructed anew for every request and therefore + * cannot benefit from this extra feature. + * With the high performance CPUs and cheap memory used in today's servers, + * session scope forms are a safe choice. + * + * </pre> + * + */ + public void saveExpectedModelReferenceForView( String currentFormView, String ref, String inputType ) + { + // if the form view is null, we are not interested in saving any references + if (currentFormView == null) return; + + FormViewState formViewState = getFormViewState( currentFormView ); + formViewState.addModelReferenceAndInputType( ref, inputType ); + } + + /** + * When the transformer starts rendering a new form element + * It needs to reset previously saved references for another + * transformation of the same view. + */ + public void clearSavedModelReferences( String currentFormView ) + { + FormViewState formViewState = getFormViewState( currentFormView ); + formViewState.clear(); + } + + + /** + * We keep a map of ViewState objects which store + * all references to model properties in a particular form view + * which were rendered by the + * XMLFormTansformer in the most recent transformation. + */ + protected FormViewState getFormViewState( String viewName ) + { + FormViewState formViewState = ( FormViewState ) viewStateMap_.get( viewName ); + if (formViewState == null) + { + formViewState = new FormViewState( viewName ); + viewStateMap_.put( viewName, formViewState ); + } + return formViewState; + } + + + + + /** + * Internal class used for keeping state information + * during the life cycle of a form. + * + * Used only for session scoped forms + * + */ + class FormViewState + { + + + FormViewState( String viewName ) + { + viewName_ = viewName; + } + + /** + * + * @return Map of (String modelPropertyReference, String inputType) pairs + * + */ + Map getModelReferenceMap() + { + return modelReferences_; + } + + void addModelReferenceAndInputType( String modelPropertyReference, String inputType) + { + modelReferences_.put( modelPropertyReference, inputType ); + } + + void clear() + { + modelReferences_.clear(); + } + + private Map modelReferences_ = new HashMap(); + + private String viewName_; + + } + + /** the set of violations the model commited during validation */ private List violations_ = null; @@ -724,7 +905,13 @@ * */ private Validator validator_ = null; - + + /** + * Keeps a state information for + * each form view that has been processed + * + */ + private Map viewStateMap_ = new HashMap(); }
---------------------------------------------------------------------- In case of troubles, e-mail: [EMAIL PROTECTED] To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]