Save This Page
Home » cocoon-2.1.11-src » org.apache » cocoon » transformation » [javadoc | source]
    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one or more
    3    * contributor license agreements.  See the NOTICE file distributed with
    4    * this work for additional information regarding copyright ownership.
    5    * The ASF licenses this file to You under the Apache License, Version 2.0
    6    * (the "License"); you may not use this file except in compliance with
    7    * the License.  You may obtain a copy of the License at
    8    *
    9    *      http://www.apache.org/licenses/LICENSE-2.0
   10    *
   11    * Unless required by applicable law or agreed to in writing, software
   12    * distributed under the License is distributed on an "AS IS" BASIS,
   13    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   14    * See the License for the specific language governing permissions and
   15    * limitations under the License.
   16    */
   17   package org.apache.cocoon.transformation;
   18   
   19   import org.apache.avalon.framework.configuration.Configuration;
   20   import org.apache.avalon.framework.configuration.ConfigurationException;
   21   import org.apache.avalon.framework.parameters.Parameters;
   22   import org.apache.avalon.framework.service.ServiceSelector;
   23   import org.apache.avalon.framework.thread.ThreadSafe;
   24   
   25   import org.apache.cocoon.ProcessingException;
   26   import org.apache.cocoon.acting.ValidatorActionResult;
   27   import org.apache.cocoon.transformation.helpers.FormValidatorHelper;
   28   import org.apache.cocoon.components.modules.input.InputModule;
   29   import org.apache.cocoon.environment.SourceResolver;
   30   import org.apache.cocoon.util.HashMap;
   31   import org.apache.cocoon.xml.dom.DOMStreamer;
   32   import org.apache.commons.lang.BooleanUtils;
   33   
   34   import org.w3c.dom.DocumentFragment;
   35   import org.xml.sax.Attributes;
   36   import org.xml.sax.SAXException;
   37   import org.xml.sax.helpers.AttributesImpl;
   38   
   39   import java.io.IOException;
   40   import java.util.Iterator;
   41   import java.util.LinkedList;
   42   import java.util.List;
   43   import java.util.Map;
   44   
   45   /**
   46    * @cocoon.sitemap.component.documentation
   47    * Eliminates the need for XSP to use FormValidatorAction or HTML forms.
   48    * Caveat: Select options need a value attribute to work correctly.
   49    *
   50    * @cocoon.sitemap.component.name   simple-form
   51    * @cocoon.sitemap.component.logger sitemap.transformer.simple-form
   52    *
   53    *
   54    * <p>This transformer fills all HTML 4 form elements with values from
   55    * an InputModule, e.g. request, with the same name. It handles select
   56    * boxes, textareas, checkboxes, radio buttons, password and text
   57    * fields, and buttons. Form elements and complete forms can be protected
   58    * from substitution by adding an attribute fixed="true" to them.</p>
   59    *
   60    * <p>In addition, it handles FormValidatorAction results by
   61    * selectively omitting &lt;error/&gt; elements. These elements need a
   62    * "name" attribute corresponding to the name of the form element, and
   63    * either a "when" or a "when-ge" attribute.</p>
   64    *
   65    * <p>An error element is send down the pipeline if validation results
   66    * are available and either the results equals the "when" attribute or
   67    * the result is greater or equal to the "when-ge" attribute.</p>
   68    *
   69    * <p>Names for validation results are "ok", "not-present", "error",
   70    * "is-null", "too-small", "too-large", and "no-match" for the similar
   71    * named values from ValidatorActionResult.</p>
   72    *
   73    * <p>There need not to be an "error" element for every form element,
   74    * multiple error elements for the same form element may be
   75    * present.</p>
   76    *
   77    * <p><em>Names of error elements are never augmented by prefix, suffix or
   78    * form name.</em></p>
   79    *
   80    * <p>Page parts with multiple occurrences depending on the number of
   81    * actual parameters can be enclosed in &lt;repeat on="expr" using="var"/&gt;
   82    * elements. <em>expr</em> is used to determine the number of occurrences
   83    * and <em>var</em> will be expanded with the ordinary number. Repeat elements
   84    * can be nested.</p>
   85    *
   86    * <p>Example:</p>
   87    * <pre>
   88    *  <repeat on="mult" using="i"><input type="text" name="mult[${i}]"/></repeat>
   89    * </pre>
   90    * <p>Will include as many input elements as mult parameters are present. Adding
   91    * the repeater variable to the elements name is necessary only with structured
   92    * parameters or when they should be numbered. See also the <em>strip-number</em>
   93    * configuration parameter.</p>
   94    *
   95    * <p>To use this transformer, add the following to your
   96    * transformation pipeline: <pre>
   97    *   &lt;map:transform type="simple-form"/&gt;
   98    * </pre></p>
   99    *
  100    * <p>Configuration elements:
  101    * <table>
  102    *   <tr><td>input-module</td><td>(String) InputModule configuration,
  103    *           defaults to an empty configuration and the "request-param" module</td></tr>
  104    *   <tr><td>fixed-attribute</td><td>(String) Name of the attribute used to
  105    *           indicate that this element should not be changed. ("fixed")</td></tr>
  106    *   <tr><td>use-form-name</td><td>(boolean) Add the name of the form to the
  107    *           name of form elements. Uses default Separator , if default separator is null
  108    *           or empty, separator is set to "/". ("false")</td></tr>
  109    *   <tr><td>use-form-name-twice</td><td>(boolean) Add the name of the form twice to the
  110    *           name of form elements. This is useful when the form instance has no
  111    *           all enclosing root tag and the form name is used instead <em>and</em> the
  112    *           form name needs to be used to find the form data. Uses default Separator ,
  113    *           if default separator is null or empty, separator is set to "/".("false")</td></tr>
  114    *   <tr><td>separator</td><td>(String) Separator between form name and element name ("/")
  115    *           </td></tr>
  116    *   <tr><td>prefix</td><td>(String) Prefix to add to element name for value lookup. No
  117    *           separator will be added between prefix and rest of the name. Default
  118    *           is "", when use-form-name is set, defaults to separator.</td></tr>
  119    *   <tr><td>suffix</td><td>(String) Added to the input element's name. No
  120    *           separator will be added between rest of the name and suffix. ("")</td></tr>
  121    *   <tr><td>ignore-validation</td><td>(boolean) If set to true, all error
  122    *           tags are copied as is regardless of the validation results.("false")</td></tr>
  123    *   <tr><td>decoration</td><td>(int) Length of decorations around repeat variable. Example:
  124    *           when using JXPath based module, decoration would be "[" and "]", hence 1. (1)</td></tr>
  125    *   <tr><td>strip-number</td><td>(boolean) If set to false, element names of repeated
  126    *           elements will contain the expanded repeater variable. ("true")</td></tr>
  127    * </table>
  128    * </p>
  129    *
  130    * <p>Sitemap parameters:
  131    * <table>
  132    *   <tr><td>fixed</td><td>(boolean) Do not change values</td></tr>
  133    *   <tr><td>prefix</td><td>(String) Added to the input element's name</td></tr>
  134    *   <tr><td>suffix</td><td>(String) Added to the input element's name</td></tr>
  135    *   <tr><td>input</td><td>(String) InputModule name</td></tr>
  136    *   <tr><td>decoration</td><td>(int) Length of decorations around repeat variable.</td></tr>
  137    *   <tr><td>strip-number</td><td>(boolean) Expanded repeater variable.</td></tr>
  138    * </table>
  139    * </p>
  140    *
  141    * <p>Example:<pre>
  142    *     &lt;input name="user.name" size="50" maxlength="60"/&gt;
  143    *     &lt;error name="user.name" when-ge="error"&gt;required&lt;/error&gt;
  144    * </pre></p>
  145    *
  146    * @author <a href="mailto:haul@apache.org">Christian Haul</a>
  147    * @version $Id: SimpleFormTransformer.java 433543 2006-08-22 06:22:54Z crossley $
  148    */
  149   public class SimpleFormTransformer extends AbstractSAXTransformer {
  150   
  151       /** strip numbers from repeated element name attributes */
  152       private boolean stripNumber = true;
  153   
  154       /** Symbolic names for elements */
  155       /** unknown element */
  156       private static final int ELEMENT_DEFAULT = 0;
  157       /** input element */
  158       private static final int ELEMENT_INPUT = 1;
  159       /** select element */
  160       private static final int ELEMENT_SELECT = 2;
  161       /** option element */
  162       private static final int ELEMENT_OPTION = 3;
  163       /** textarea element */
  164       private static final int ELEMENT_TXTAREA = 4;
  165       /** error element */
  166       private static final int ELEMENT_ERROR = 5;
  167       /** form element */
  168       private static final int ELEMENT_FORM = 6;
  169       /** repeat element */
  170       private static final int ELEMENT_REPEAT = 7;
  171       /** default element as Integer (needed as default in org.apache.cocoon.util.HashMap.get()) */
  172       private static final Integer defaultElement = new Integer(ELEMENT_DEFAULT);
  173   
  174       /** input type unknown */
  175       private static final int TYPE_DEFAULT = 0;
  176       /** input type checkbox */
  177       private static final int TYPE_CHECKBOX = 1;
  178       /** input type radio */
  179       private static final int TYPE_RADIO = 2;
  180       /** default input type as Integer (needed as default in org.apache.cocoon.util.HashMap.get()) */
  181       private static final Integer defaultType = new Integer(TYPE_DEFAULT);
  182   
  183       protected static final String INPUT_MODULE_ROLE = InputModule.ROLE;
  184       protected static final String INPUT_MODULE_SELECTOR = INPUT_MODULE_ROLE + "Selector";
  185   
  186       /** map element name string to symbolic name */
  187       private static final HashMap elementNames;
  188       /** map input type string to symbolic name */
  189       private static final HashMap inputTypes;
  190       /** map ValidatorActionResult to name string */
  191       private static final HashMap validatorResults;
  192       /** map name string to ValidatorActionResult */
  193       private static final HashMap validatorResultLabel;
  194   
  195       /** setup mapping tables */
  196       static {
  197           HashMap names = new HashMap();
  198           names.put("input", new Integer(ELEMENT_INPUT));
  199           names.put("select", new Integer(ELEMENT_SELECT));
  200           names.put("option", new Integer(ELEMENT_OPTION));
  201           names.put("textarea", new Integer(ELEMENT_TXTAREA));
  202           names.put("error", new Integer(ELEMENT_ERROR));
  203           names.put("form", new Integer(ELEMENT_FORM));
  204           names.put("repeat", new Integer(ELEMENT_REPEAT));
  205           elementNames = names;
  206           names = null;
  207   
  208           names = new HashMap();
  209           names.put("checkbox", new Integer(TYPE_CHECKBOX));
  210           names.put("radio", new Integer(TYPE_RADIO));
  211           inputTypes = names;
  212           names = null;
  213   
  214           names = new HashMap();
  215           names.put("ok", ValidatorActionResult.OK);
  216           names.put("not-present", ValidatorActionResult.NOTPRESENT);
  217           names.put("error", ValidatorActionResult.ERROR);
  218           names.put("is-null", ValidatorActionResult.ISNULL);
  219           names.put("too-small", ValidatorActionResult.TOOSMALL);
  220           names.put("too-large", ValidatorActionResult.TOOLARGE);
  221           names.put("no-match", ValidatorActionResult.NOMATCH);
  222           validatorResultLabel = names;
  223   
  224           names = new HashMap();
  225           names.put(ValidatorActionResult.OK, "ok");
  226           names.put(ValidatorActionResult.NOTPRESENT, "not-present");
  227           names.put(ValidatorActionResult.ERROR, "error");
  228           names.put(ValidatorActionResult.ISNULL, "is-null");
  229           names.put(ValidatorActionResult.TOOSMALL, "too-small");
  230           names.put(ValidatorActionResult.TOOLARGE, "too-large");
  231           names.put(ValidatorActionResult.NOMATCH, "no-match");
  232           validatorResults = names;
  233           names = null;
  234       }
  235   
  236       /** current element's request parameter values */
  237       protected Object[] values;
  238   
  239       /** current request's validation results (all validated elements) */
  240       protected Map validationResults;
  241   
  242       /** Should we skip inserting values? */
  243       private boolean fixed;
  244       /** Is the complete document protected? */
  245       private boolean documentFixed;
  246   
  247       private String fixedName = "fixed";
  248       private String prefix;
  249       private String suffix;
  250       private String defaultPrefix;
  251       private String defaultSuffix;
  252       private String separator;
  253       private String formName;
  254       private boolean useFormName;
  255       private boolean useFormNameTwice;
  256       private boolean ignoreValidation;
  257       private int decorationSize = 1;
  258   
  259       private String defaultInput = "request-param";
  260       private Configuration defaultInputConf;
  261       private Configuration inputConf;
  262       private InputModule input;
  263       private ServiceSelector inputSelector;
  264       private String inputName;
  265   
  266       /** Skip element's content only. Otherwise skip also surrounding element. */
  267       protected boolean skipChildrenOnly;
  268   
  269       /** Count nested repeat elements. */
  270       protected int recordingCount;
  271   
  272       /** List of {@link RepeaterStatus} elements keeping track of nested repeat blocks. */
  273       protected List repeater;
  274   
  275       /** Map of {@link ValueList} to track multiple parameters. */
  276       protected Map formValues;
  277   
  278       /**
  279        * Keep track of repeater status.
  280        */
  281       protected static class RepeaterStatus {
  282           public String var = null;
  283           public String expr = null;
  284           public int count = 0;
  285   
  286           public RepeaterStatus(String var, int count, String expr) {
  287               this.var = var;
  288               this.count = count;
  289               this.expr = expr;
  290           }
  291   
  292           public String toString() {
  293               return "[" + this.var + "," + this.expr + "," + this.count + "]";
  294           }
  295       }
  296   
  297       /**
  298        * Keep track of multiple values.
  299        */
  300       protected static class ValueList {
  301           private int current = -1;
  302           private Object[] values = null;
  303   
  304           public ValueList(Object[] values) {
  305               this.values = values;
  306               this.current = (values != null && values.length > 0 ? 0 : -1);
  307           }
  308   
  309           public Object getNext() {
  310               Object result = null;
  311               if (this.values != null) {
  312                   if (this.current < this.values.length) {
  313                       result = this.values[this.current++];
  314                   }
  315               }
  316               return result;
  317           }
  318       }
  319   
  320       public SimpleFormTransformer() {
  321           this.defaultNamespaceURI = "";
  322       }
  323   
  324       /** set per instance variables to defaults */
  325       private void reset() {
  326           this.skipChildrenOnly = false;
  327           this.values = null;
  328           this.validationResults = null;
  329           this.documentFixed = false;
  330           this.fixed = false;
  331           this.formName = null;
  332           this.recordingCount = 0;
  333           this.repeater = new LinkedList();
  334           this.formValues = new HashMap();
  335   
  336           if (this.inputSelector != null) {
  337               if (this.input != null)
  338                   this.inputSelector.release(this.input);
  339               this.manager.release(this.inputSelector);
  340           }
  341       }
  342   
  343       /**
  344        * Avalon Configurable Interface
  345        */
  346       public void configure(Configuration config) throws ConfigurationException {
  347           super.configure(config);
  348   
  349           this.defaultInputConf = config.getChild("input-module");
  350           this.defaultInput = this.defaultInputConf.getAttribute("name", this.defaultInput);
  351           this.separator = config.getChild("separator").getValue(this.separator);
  352           this.defaultPrefix = config.getChild("prefix").getValue(this.defaultPrefix);
  353           this.defaultSuffix = config.getChild("suffix").getValue(this.defaultSuffix);
  354           this.fixedName = config.getChild("fixed-attribute").getValue(this.fixedName);
  355           this.useFormName = config.getChild("use-form-name").getValueAsBoolean(this.useFormName);
  356           this.useFormNameTwice =
  357               config.getChild("use-form-name-twice").getValueAsBoolean(this.useFormNameTwice);
  358           this.useFormName = this.useFormName || this.useFormNameTwice;
  359           if (this.useFormName) {
  360               this.separator =
  361                   (this.separator == null || this.separator.length() == 0 ? "/" : this.separator);
  362               this.defaultPrefix = this.separator;
  363           }
  364           this.ignoreValidation =
  365               config.getChild("ignore-validation").getValueAsBoolean(this.ignoreValidation);
  366           this.decorationSize = config.getChild("decoration").getValueAsInteger(this.decorationSize);
  367           this.stripNumber = config.getChild("strip-number").getValueAsBoolean(this.stripNumber);
  368       }
  369   
  370       /**
  371        * Read sitemap parameters and set properties accordingly.
  372        */
  373       private void evaluateParameters() {
  374           this.documentFixed = this.parameters.getParameterAsBoolean("fixed", false);
  375           this.fixed = this.documentFixed;
  376           this.prefix = this.parameters.getParameter("prefix", this.defaultPrefix);
  377           this.suffix = this.parameters.getParameter("suffix", this.defaultSuffix);
  378           this.inputName = this.parameters.getParameter("input", null);
  379           this.decorationSize =
  380               this.parameters.getParameterAsInteger("decoration", this.decorationSize);
  381           this.stripNumber = this.parameters.getParameterAsBoolean("strip-number", this.stripNumber);
  382       }
  383   
  384       /**
  385        * Setup the next round.
  386        * The instance variables are initialised.
  387        * @param resolver The current SourceResolver
  388        * @param objectModel The objectModel of the environment.
  389        * @param src The value of the src attribute in the sitemap.
  390        * @param par The parameters from the sitemap.
  391        */
  392       public void setup(SourceResolver resolver, Map objectModel, String src, Parameters par)
  393           throws ProcessingException, SAXException, IOException {
  394   
  395           this.reset();
  396   
  397           super.setup(resolver, objectModel, src, par);
  398   
  399           if (request == null) {
  400               getLogger().debug("no request object");
  401               throw new ProcessingException("no request object");
  402           }
  403           this.evaluateParameters();
  404           this.setupInputModule();
  405   
  406       }
  407   
  408       /**
  409        * Setup and obtain reference to the input module.
  410        */
  411       private void setupInputModule() {
  412           this.inputConf = null;
  413           if (this.ignoreValidation) {
  414               this.validationResults = null;
  415           } else {
  416               this.validationResults = FormValidatorHelper.getResults(this.objectModel);
  417           }
  418   
  419           if (this.inputName == null) {
  420               this.inputName = this.defaultInput;
  421               this.inputConf = this.defaultInputConf;
  422           }
  423   
  424           try {
  425               // obtain input module
  426               this.inputSelector = (ServiceSelector) this.manager.lookup(INPUT_MODULE_SELECTOR);
  427               if (this.inputName != null
  428                   && this.inputSelector != null
  429                   && this.inputSelector.isSelectable(this.inputName)) {
  430                   this.input = (InputModule) this.inputSelector.select(this.inputName);
  431                   if (!(this.input instanceof ThreadSafe
  432                       && this.inputSelector instanceof ThreadSafe)) {
  433                       this.inputSelector.release(this.input);
  434                       this.manager.release(this.inputSelector);
  435                       this.input = null;
  436                       this.inputSelector = null;
  437                   }
  438               } else {
  439                   if (this.inputName != null)
  440                       if (getLogger().isErrorEnabled())
  441                           getLogger().error(
  442                               "A problem occurred setting up '"
  443                                   + this.inputName
  444                                   + "': Selector is "
  445                                   + (this.inputSelector != null ? "not " : "")
  446                                   + "null, Component is "
  447                                   + (this.inputSelector != null
  448                                       && this.inputSelector.isSelectable(this.inputName)
  449                                           ? "known"
  450                                           : "unknown"));
  451               }
  452           } catch (Exception e) {
  453               if (getLogger().isWarnEnabled())
  454                   getLogger().warn(
  455                       "A problem occurred setting up '" + this.inputName + "': " + e.getMessage());
  456           }
  457       }
  458   
  459       /**
  460        *  Recycle this component.
  461        */
  462       public void recycle() {
  463           reset();
  464           super.recycle();
  465       }
  466   
  467       /**
  468        * Generate string representation of attributes. For debug only.
  469        */
  470       protected String printAttributes(Attributes attr) {
  471           StringBuffer sb = new StringBuffer();
  472           sb.append('[');
  473           for (int i = 0; i < attr.getLength(); i++) {
  474               sb.append('@').append(attr.getLocalName(i)).append("='").append(
  475                   attr.getValue(i)).append(
  476                   "' ");
  477           }
  478           sb.append(']');
  479           return sb.toString();
  480       }
  481   
  482       /**
  483        * Handle input elements that may have a "checked" attributes,
  484        * i.e. checkbox and radio.
  485        */
  486       protected void startCheckableElement(
  487           String aName,
  488           String uri,
  489           String name,
  490           String raw,
  491           AttributesImpl attributes)
  492           throws SAXException {
  493   
  494           // @fixed and this.fixed already considered in startInputElement
  495           this.values = this.getValues(aName);
  496           String checked = attributes.getValue("checked");
  497           String value = attributes.getValue("value");
  498           boolean found = false;
  499   
  500           if (getLogger().isDebugEnabled())
  501               getLogger().debug(
  502                   "startCheckableElement "
  503                       + name
  504                       + " attributes "
  505                       + this.printAttributes(attributes));
  506           if (this.values != null) {
  507               if (getLogger().isDebugEnabled())
  508                   getLogger().debug("replacing");
  509               for (int i = 0; i < this.values.length; i++) {
  510                   if (this.values[i].equals(value)) {
  511                       found = true;
  512                       if (checked == null) {
  513                           attributes.addAttribute("", "checked", "checked", "CDATA", "");
  514                       }
  515                       break;
  516                   }
  517               }
  518               if (!found && checked != null) {
  519                   attributes.removeAttribute(attributes.getIndex("checked"));
  520               }
  521           }
  522           this.relayStartElement(uri, name, raw, attributes);
  523       }
  524   
  525       /**
  526        * Handle input elements that may don't have a "checked"
  527        * attributes, e.g. text, password, button.
  528        */
  529       protected void startNonCheckableElement(
  530           String aName,
  531           String uri,
  532           String name,
  533           String raw,
  534           AttributesImpl attributes)
  535           throws SAXException {
  536   
  537           // @fixed and this.fixed already considered in startInputElement
  538           Object fValue = this.getNextValue(aName);
  539           String value = attributes.getValue("value");
  540           if (getLogger().isDebugEnabled())
  541               getLogger().debug(
  542                   "startNonCheckableElement "
  543                       + name
  544                       + " attributes "
  545                       + this.printAttributes(attributes));
  546           if (fValue != null) {
  547               if (getLogger().isDebugEnabled())
  548                   getLogger().debug("replacing");
  549               if (value != null) {
  550                   attributes.setValue(attributes.getIndex("value"), String.valueOf(fValue));
  551               } else {
  552                   attributes.addAttribute("", "value", "value", "CDATA", String.valueOf(fValue));
  553               }
  554           }
  555           this.relayStartElement(uri, name, raw, attributes);
  556       }
  557   
  558       /**
  559        * Handle input elements. Calls startCheckableElement or
  560        * startNonCheckableElement.
  561        */
  562       protected void startInputElement(String uri, String name, String raw, Attributes attr)
  563           throws SAXException {
  564   
  565           // @value = request.getParameterValues(@name)
  566           String aName = getName(attr.getValue("name"));
  567           String fixed = attr.getValue(this.fixedName);
  568   
  569           if (getLogger().isDebugEnabled())
  570               getLogger().debug(
  571                   "startInputElement " + name + " attributes " + this.printAttributes(attr));
  572           if (aName == null || this.fixed || (fixed != null && BooleanUtils.toBoolean(fixed))) {
  573               this.relayStartElement(uri, name, raw, attr);
  574   
  575           } else {
  576               if (getLogger().isDebugEnabled())
  577                   getLogger().debug("replacing");
  578   
  579               attr = this.normalizeAttributes(attr);
  580   
  581               AttributesImpl attributes = null;
  582               if (attr instanceof AttributesImpl) {
  583                   attributes = (AttributesImpl) attr;
  584               } else {
  585                   attributes = new AttributesImpl(attr);
  586               }
  587               String type = attributes.getValue("type");
  588               switch (((Integer) inputTypes.get(type, defaultType)).intValue()) {
  589                   case TYPE_CHECKBOX :
  590                   case TYPE_RADIO :
  591                       this.startCheckableElement(aName, uri, name, raw, attributes);
  592                       break;
  593   
  594                   case TYPE_DEFAULT :
  595                       this.startNonCheckableElement(aName, uri, name, raw, attributes);
  596                       break;
  597               }
  598               this.values = null;
  599           }
  600       }
  601   
  602       /**
  603        * Handle select elements. Sets up some instance variables for
  604        * following option elements.
  605        */
  606       protected void startSelectElement(String uri, String name, String raw, Attributes attr)
  607           throws SAXException {
  608   
  609           // this.values = request.getParameterValues(@name)
  610           String aName = getName(attr.getValue("name"));
  611           String fixed = attr.getValue(this.fixedName);
  612           this.values = null;
  613           if (getLogger().isDebugEnabled())
  614               getLogger().debug(
  615                   "startSelectElement " + name + " attributes " + this.printAttributes(attr));
  616           if (aName != null && !(this.fixed || (fixed != null && BooleanUtils.toBoolean(fixed)))) {
  617               if (attr.getIndex("multiple") > -1) {
  618                   this.values = this.getValues(aName);
  619               } else {
  620                   Object val = this.getNextValue(aName);
  621                   if (val != null) {
  622                       this.values = new Object[1];
  623                       this.values[0] = val;
  624                   } else {
  625                       this.values = null;
  626                   }
  627               }
  628               attr = this.normalizeAttributes(attr);
  629           }
  630           this.relayStartElement(uri, name, raw, attr);
  631       }
  632   
  633       /**
  634        * Handle option elements. Uses instance variables set up by
  635        * startSelectElement. Relies on option having a "value"
  636        * attribute, i.e. does not check following characters if "value"
  637        * is not present.
  638        */
  639       protected void startOptionElement(String uri, String name, String raw, Attributes attr)
  640           throws SAXException {
  641   
  642           // add @selected if @value in request.getParameterValues(@name)
  643           if (getLogger().isDebugEnabled())
  644               getLogger().debug(
  645                   "startOptionElement " + name + " attributes " + this.printAttributes(attr));
  646           if (this.values == null || this.fixed) {
  647               this.relayStartElement(uri, name, raw, attr);
  648           } else {
  649               if (getLogger().isDebugEnabled())
  650                   getLogger().debug("replacing");
  651               AttributesImpl attributes = null;
  652               if (attr instanceof AttributesImpl) {
  653                   attributes = (AttributesImpl) attr;
  654               } else {
  655                   attributes = new AttributesImpl(attr);
  656               }
  657               String selected = attributes.getValue("selected");
  658               String value = attributes.getValue("value");
  659               boolean found = false;
  660   
  661               for (int i = 0; i < this.values.length; i++) {
  662                   if (this.values[i].equals(value)) {
  663                       found = true;
  664                       if (selected == null) {
  665                           attributes.addAttribute("", "selected", "selected", "CDATA", "");
  666                       }
  667                       break;
  668                   }
  669               }
  670               if (!found && selected != null) {
  671                   attributes.removeAttribute(attributes.getIndex("selected"));
  672               }
  673   
  674               this.relayStartElement(uri, name, raw, attributes);
  675           }
  676       }
  677   
  678       /**
  679        * Handles textarea elements. Skips nested events if request
  680        * parameter with same name exists.
  681        */
  682       protected void startTextareaElement(String uri, String name, String raw, Attributes attributes)
  683           throws SAXException {
  684   
  685           String aName = getName(attributes.getValue("name"));
  686           String fixed = attributes.getValue(this.fixedName);
  687           Object value = null;
  688           if (getLogger().isDebugEnabled())
  689               getLogger().debug(
  690                   "startTextareaElement " + name + " attributes " + this.printAttributes(attributes));
  691           if (aName != null) {
  692               value = this.getNextValue(aName);
  693           }
  694           if (value == null || this.fixed || (fixed != null && BooleanUtils.toBoolean(fixed))) {
  695               this.relayStartElement(uri, name, raw, attributes);
  696           } else {
  697               if (getLogger().isDebugEnabled())
  698                   getLogger().debug("replacing");
  699               this.relayStartElement(uri, name, raw, this.normalizeAttributes(attributes));
  700               String valString = String.valueOf(value);
  701               this.characters(valString.toCharArray(), 0, valString.length());
  702               // well, this doesn't really work out nicely. do it the hard way.
  703               if (this.ignoreEventsCount == 0)
  704                   this.skipChildrenOnly = true;
  705               this.ignoreEventsCount++;
  706           }
  707       }
  708   
  709       /**
  710        * Handle error elements. If validation results are available,
  711        * compares validation result for parameter with the same name as
  712        * the "name" attribute with the result names is "when" and
  713        * "when-ge". Drops element and all nested events when error
  714        * condition is not met.
  715        */
  716       protected void startErrorElement(String uri, String name, String raw, Attributes attr)
  717           throws SAXException {
  718   
  719           if (getLogger().isDebugEnabled())
  720               getLogger().debug(
  721                   "startErrorElement " + name + " attributes " + this.printAttributes(attr));
  722           if (this.ignoreValidation) {
  723               this.relayStartElement(uri, name, raw, attr);
  724           } else if (this.validationResults == null || this.fixed) {
  725               this.relayStartElement(true, false, uri, name, raw, attr);
  726           } else {
  727               String aName = attr.getValue("name");
  728               if (aName == null) {
  729                   this.relayStartElement(uri, name, raw, attr);
  730               } else {
  731                   ValidatorActionResult validation =
  732                       FormValidatorHelper.getParamResult(this.objectModel, aName);
  733                   String when = attr.getValue("when");
  734                   String when_ge = attr.getValue("when-ge");
  735   
  736                   if ((when != null && when.equals(validatorResults.get(validation)))
  737                       || (when_ge != null
  738                           && validation.ge(
  739                               (ValidatorActionResult) validatorResultLabel.get(
  740                                   when_ge,
  741                                   ValidatorActionResult.MAXERROR)))) {
  742                       AttributesImpl attributes = null;
  743                       if (attr instanceof AttributesImpl) {
  744                           attributes = (AttributesImpl) attr;
  745                       } else {
  746                           attributes = new AttributesImpl(attr);
  747                       }
  748                       // remove attributes not meant for client
  749                       attributes.removeAttribute(attributes.getIndex("name"));
  750                       if (when != null)
  751                           attributes.removeAttribute(attributes.getIndex("when"));
  752                       if (when_ge != null)
  753                           attributes.removeAttribute(attributes.getIndex("when-ge"));
  754                       this.relayStartElement(uri, name, raw, this.normalizeAttributes(attributes));
  755                   } else {
  756                       this.relayStartElement(true, true, uri, name, raw, attr);
  757                   }
  758               }
  759           }
  760       }
  761   
  762       /**
  763        * Start processing a form element. Sets protection indicator if attribute
  764        * "fixed" is present and either "true" or "yes". Removes attribute "fixed"
  765        * if present.
  766        * @param uri The namespace of the element.
  767        * @param name The local name of the element.
  768        * @param raw The qualified name of the element.
  769        * @param attr The attributes of the element.
  770        */
  771       protected void startFormElement(String uri, String name, String raw, Attributes attr)
  772           throws SAXException {
  773   
  774           String fixed = attr.getValue(this.fixedName);
  775           if (this.useFormName) {
  776               this.formName = attr.getValue("name");
  777           }
  778           if (fixed == null) {
  779               this.relayStartElement(uri, name, raw, attr);
  780           } else {
  781               if (!this.fixed && BooleanUtils.toBoolean(fixed)) {
  782                   this.fixed = true;
  783               }
  784               // remove attributes not meant for client
  785               AttributesImpl attributes = null;
  786               if (attr instanceof AttributesImpl) {
  787                   attributes = (AttributesImpl) attr;
  788               } else {
  789                   attributes = new AttributesImpl(attr);
  790               }
  791               attributes.removeAttribute(attributes.getIndex(this.fixedName));
  792               this.relayStartElement(uri, name, raw, this.normalizeAttributes(attributes));
  793           }
  794       }
  795   
  796       /**
  797        * Start recording repeat element contents and push repeat expression and
  798        * variable to repeater stack. Only start recording, if no other recorder is
  799        * currently running.
  800        *
  801        * @param uri
  802        * @param name
  803        * @param raw
  804        * @param attr
  805        * @throws SAXException
  806        */
  807       protected void startRepeatElement(String uri, String name, String raw, Attributes attr)
  808           throws SAXException {
  809   
  810           if (this.recordingCount == 0) {
  811               if (!(this.fixed || BooleanUtils.toBoolean(attr.getValue(this.fixedName)))) {
  812                   RepeaterStatus status =
  813                       new RepeaterStatus("${" + attr.getValue("using") + "}", 0, attr.getValue("on"));
  814                   this.repeater.add(status);
  815                   this.startRecording();
  816                   this.recordingCount++;
  817               } else {
  818                   this.relayStartElement(uri, name, raw, attr);
  819               }
  820           } else {
  821               this.relayStartElement(uri, name, raw, attr);
  822               this.recordingCount++;
  823           }
  824       }
  825   
  826       /**
  827        * Stop recording repeat contents and replay required number of times.
  828        * Stop only if outmost repeat element is ending.
  829        *
  830        * @param uri
  831        * @param name
  832        * @param raw
  833        * @throws SAXException
  834        */
  835       protected void endRepeatElement(String uri, String name, String raw) throws SAXException {
  836           this.recordingCount--;
  837           if (this.recordingCount == 0) {
  838               DocumentFragment fragment = this.endRecording();
  839               RepeaterStatus status = (RepeaterStatus) this.repeater.get(this.repeater.size() - 1);
  840               Object[] vals = this.getValues(this.getName(status.expr));
  841               int count = (vals != null ? vals.length : 0);
  842               for (status.count = 1; status.count <= count; status.count++) {
  843                   DOMStreamer streamer = new DOMStreamer(this, this);
  844                   streamer.stream(fragment);
  845               }
  846               this.repeater.remove(this.repeater.size() - 1);
  847           } else {
  848               this.relayEndElement(uri, name, raw);
  849               if (this.recordingCount < 0) {
  850                   this.recordingCount = 0;
  851               }
  852           }
  853       }
  854   
  855       /**
  856        * Start processing elements of our namespace.
  857        * This hook is invoked for each sax event with our namespace.
  858        * @param uri The namespace of the element.
  859        * @param name The local name of the element.
  860        * @param raw The qualified name of the element.
  861        * @param attr The attributes of the element.
  862        */
  863       public void startTransformingElement(String uri, String name, String raw, Attributes attr)
  864           throws SAXException {
  865   
  866           if (this.ignoreEventsCount == 0 && this.recordingCount == 0) {
  867               switch (((Integer) elementNames.get(name, defaultElement)).intValue()) {
  868                   case ELEMENT_INPUT :
  869                       this.startInputElement(uri, name, raw, attr);
  870                       break;
  871   
  872                   case ELEMENT_SELECT :
  873                       this.startSelectElement(uri, name, raw, attr);
  874                       break;
  875   
  876                   case ELEMENT_OPTION :
  877                       this.startOptionElement(uri, name, raw, attr);
  878                       break;
  879   
  880                   case ELEMENT_TXTAREA :
  881                       this.startTextareaElement(uri, name, raw, attr);
  882                       break;
  883   
  884                   case ELEMENT_ERROR :
  885                       this.startErrorElement(uri, name, raw, attr);
  886                       break;
  887   
  888                   case ELEMENT_FORM :
  889                       this.startFormElement(uri, name, raw, attr);
  890                       break;
  891   
  892                   case ELEMENT_REPEAT :
  893                       this.startRepeatElement(uri, name, raw, attr);
  894                       break;
  895   
  896                   default :
  897                       this.relayStartElement(uri, name, raw, attr);
  898               }
  899   
  900           } else if (this.recordingCount > 0) {
  901               switch (((Integer) elementNames.get(name, defaultElement)).intValue()) {
  902                   case ELEMENT_REPEAT :
  903                       this.startRepeatElement(uri, name, raw, attr);
  904                       break;
  905   
  906                   default :
  907                       this.relayStartElement(uri, name, raw, attr);
  908               }
  909           } else {
  910               this.relayStartElement(uri, name, raw, attr);
  911           }
  912       }
  913   
  914       /**
  915        * Start processing elements of our namespace.
  916        * This hook is invoked for each sax event with our namespace.
  917        * @param uri The namespace of the element.
  918        * @param name The local name of the element.
  919        * @param raw The qualified name of the element.
  920        */
  921       public void endTransformingElement(String uri, String name, String raw) throws SAXException {
  922   
  923           if (this.ignoreEventsCount > 0) {
  924               this.relayEndElement(uri, name, raw);
  925           } else if (this.recordingCount > 0) {
  926               switch (((Integer) elementNames.get(name, defaultElement)).intValue()) {
  927                   case ELEMENT_REPEAT :
  928                       this.endRepeatElement(uri, name, raw);
  929                       break;
  930   
  931                   default :
  932                       this.relayEndElement(uri, name, raw);
  933               }
  934           } else {
  935               switch (((Integer) elementNames.get(name, defaultElement)).intValue()) {
  936                   case ELEMENT_SELECT :
  937                       this.values = null;
  938                       this.relayEndElement(uri, name, raw);
  939                       break;
  940                   case ELEMENT_INPUT :
  941                   case ELEMENT_OPTION :
  942                   case ELEMENT_TXTAREA :
  943                   case ELEMENT_ERROR :
  944                       this.relayEndElement(uri, name, raw);
  945                       break;
  946                   case ELEMENT_FORM :
  947                       this.fixed = this.documentFixed;
  948                       this.formName = null;
  949                       this.relayEndElement(uri, name, raw);
  950                       break;
  951   
  952                   case ELEMENT_REPEAT :
  953                       this.endRepeatElement(uri, name, raw);
  954                       break;
  955   
  956                   default :
  957                       this.relayEndElement(uri, name, raw);
  958               }
  959           }
  960       }
  961   
  962       /**
  963        * Remove extra information from element's attributes. Currently only removes
  964        * the repeater variable from the element's name attribute if present.
  965        *
  966        * @param attr
  967        * @return modified attributes
  968        */
  969       private Attributes normalizeAttributes(Attributes attr) {
  970           Attributes result = attr;
  971           if (this.stripNumber && this.repeater.size() > 0) {
  972               String name = attr.getValue("name");
  973               if (name != null) {
  974                   for (Iterator i = this.repeater.iterator(); i.hasNext();) {
  975                       RepeaterStatus status = (RepeaterStatus) i.next();
  976                       int pos = name.indexOf(status.var);
  977                       if (pos >= 0) {
  978                           AttributesImpl attributes;
  979                           if (result instanceof AttributesImpl) {
  980                               attributes = (AttributesImpl) result;
  981                           } else {
  982                               attributes = new AttributesImpl(result);
  983                           }
  984                           name =
  985                               name.substring(0, pos - this.decorationSize)
  986                                   + name.substring(pos + status.var.length() + this.decorationSize);
  987                           attributes.setValue(attributes.getIndex("name"), name);
  988                           result = attributes;
  989                       }
  990                   }
  991               }
  992           }
  993           return result;
  994       }
  995   
  996       /**
  997        * Generate the "real" name of an element for value lookup.
  998        * @param name
  999        * @return "real" name.
 1000        */
 1001       private String getName(String name) {
 1002           String result = name;
 1003           if (this.useFormName && this.formName != null) {
 1004               if (this.separator != null) {
 1005                   if (this.useFormNameTwice) {
 1006                       result =
 1007                           this.formName + this.separator + this.formName + this.separator + result;
 1008                   } else {
 1009                       result = this.formName + this.separator + result;
 1010                   }
 1011               } else {
 1012                   if (this.useFormNameTwice) {
 1013                       result = this.formName + result;
 1014                   } else {
 1015                       // does this make sense ?
 1016                       result = this.formName + this.formName + result;
 1017                   }
 1018               }
 1019           }
 1020           if (this.prefix != null) {
 1021               result = this.prefix + result;
 1022           }
 1023           if (this.suffix != null) {
 1024               result = result + this.prefix;
 1025           }
 1026           if (this.repeater.size() > 0) {
 1027               for (Iterator i = this.repeater.iterator(); i.hasNext();) {
 1028                   RepeaterStatus status = (RepeaterStatus) i.next();
 1029                   int pos = result.indexOf(status.var);
 1030                   if (pos != -1) {
 1031                       result =
 1032                           result.substring(0, pos)
 1033                               + status.count
 1034                               + result.substring(pos + status.var.length());
 1035                   }
 1036               }
 1037           }
 1038           return result;
 1039       }
 1040   
 1041       /**
 1042        * Obtain values from used InputModule if not done already and return the
 1043        * next value. If no more values exist, returns null.
 1044        *
 1045        * @param name
 1046        */
 1047       private Object getNextValue(String name) {
 1048           Object result = null;
 1049           if (this.formValues.containsKey(name)) {
 1050               ValueList vList = (ValueList) this.formValues.get(name);
 1051               result = vList.getNext();
 1052           } else {
 1053               ValueList vList = new ValueList(this.getValues(name));
 1054               result = vList.getNext();
 1055               this.formValues.put(name, vList);
 1056           }
 1057           return result;
 1058       }
 1059   
 1060       /**
 1061        * Obtain values from the used InputModule.
 1062        */
 1063       private Object[] getValues(String name) {
 1064           Object[] values = null;
 1065           ServiceSelector iputSelector = null;
 1066           InputModule iput = null;
 1067           try {
 1068               if (this.input != null) {
 1069                   // input module is thread safe
 1070                   // thus we still have a reference to it
 1071                   values = input.getAttributeValues(name, this.inputConf, objectModel);
 1072                   if (getLogger().isDebugEnabled())
 1073                       getLogger().debug("cached module " + this.input
 1074                                         + " attribute " + name
 1075                                         + " returns " + values);
 1076               } else {
 1077                   // input was not thread safe
 1078                   // so acquire it again
 1079                   iputSelector = (ServiceSelector)this.manager.lookup(INPUT_MODULE_SELECTOR);
 1080                   if (this.inputName != null
 1081                       && iputSelector != null
 1082                       && iputSelector.isSelectable(this.inputName)) {
 1083   
 1084                       iput = (InputModule) iputSelector.select(this.inputName);
 1085                   }
 1086                   if (iput != null) {
 1087                       values = iput.getAttributeValues(name, this.inputConf, objectModel);
 1088                   }
 1089                   if (getLogger().isDebugEnabled())
 1090                       getLogger().debug(
 1091                           "fresh module " + iput + " attribute " + name + " returns " + values);
 1092               }
 1093           } catch (Exception e) {
 1094               if (getLogger().isWarnEnabled())
 1095                   getLogger().warn(
 1096                       "A problem occurred acquiring a value from '"
 1097                           + this.inputName
 1098                           + "' for '"
 1099                           + name
 1100                           + "': "
 1101                           + e.getMessage());
 1102           } finally {
 1103               // release components if necessary
 1104               if (iputSelector != null) {
 1105                   if (iput != null)
 1106                       iputSelector.release(iput);
 1107                   this.manager.release(iputSelector);
 1108               }
 1109           }
 1110   
 1111           return values;
 1112       }
 1113   
 1114       /**
 1115        * Calls the super's method startTransformingElement.
 1116        *
 1117        * @param uri
 1118        * @param name
 1119        * @param raw
 1120        * @param attr
 1121        * @throws SAXException
 1122        */
 1123       protected void relayStartElement(String uri, String name, String raw, Attributes attr)
 1124           throws SAXException {
 1125           this.relayStartElement(false, false, uri, name, raw, attr);
 1126       }
 1127   
 1128       /**
 1129        * Calls the super's method startTransformingElement and increments the
 1130        * ignoreEventsCount if skip is true. Increment can be done either before
 1131        * invoking super's method, so that the element itself is skipped, or afterwards,
 1132        * so that only the children are skipped.
 1133        *
 1134        * @param skip
 1135        * @param skipChildrenOnly
 1136        * @param uri
 1137        * @param name
 1138        * @param raw
 1139        * @param attr
 1140        * @throws SAXException
 1141        */
 1142       protected void relayStartElement(
 1143           boolean skip,
 1144           boolean skipChildrenOnly,
 1145           String uri,
 1146           String name,
 1147           String raw,
 1148           Attributes attr)
 1149           throws SAXException {
 1150   
 1151           try {
 1152               if (this.ignoreEventsCount > 0) {
 1153                   this.ignoreEventsCount++;
 1154                   super.startTransformingElement(uri, name, raw, attr);
 1155               } else {
 1156                   if (skip)
 1157                       this.skipChildrenOnly = skipChildrenOnly;
 1158                   if (skip && !skipChildrenOnly)
 1159                       this.ignoreEventsCount++;
 1160                   super.startTransformingElement(uri, name, raw, attr);
 1161                   if (skip && skipChildrenOnly)
 1162                       this.ignoreEventsCount++;
 1163               }
 1164           } catch (ProcessingException e) {
 1165               throw new SAXException(e);
 1166           } catch (IOException e) {
 1167               throw new SAXException(e);
 1168           }
 1169       }
 1170   
 1171       /**
 1172        * Calls the super's method endTransformingElement and decrements the
 1173        * ignoreEventsCount if larger than zero.
 1174        *
 1175        * @param uri
 1176        * @param name
 1177        * @param raw
 1178        * @throws SAXException
 1179        */
 1180       protected void relayEndElement(String uri, String name, String raw) throws SAXException {
 1181   
 1182           if (this.ignoreEventsCount == 1 && this.skipChildrenOnly)
 1183               this.ignoreEventsCount--;
 1184           try {
 1185               super.endTransformingElement(uri, name, raw);
 1186           } catch (ProcessingException e) {
 1187               throw new SAXException(e);
 1188           } catch (IOException e) {
 1189               throw new SAXException(e);
 1190           } catch (Exception e) {
 1191               getLogger().error("exception", e);
 1192           }
 1193   
 1194           if (this.ignoreEventsCount > 0)
 1195               this.ignoreEventsCount--;
 1196       }
 1197   
 1198   }

Save This Page
Home » cocoon-2.1.11-src » org.apache » cocoon » transformation » [javadoc | source]