0001: /*
0002: * Licensed to the Apache Software Foundation (ASF) under one or more
0003: * contributor license agreements. See the NOTICE file distributed with
0004: * this work for additional information regarding copyright ownership.
0005: * The ASF licenses this file to You under the Apache License, Version 2.0
0006: * (the "License"); you may not use this file except in compliance with
0007: * the License. You may obtain a copy of the License at
0008: *
0009: * http://www.apache.org/licenses/LICENSE-2.0
0010: *
0011: * Unless required by applicable law or agreed to in writing, software
0012: * distributed under the License is distributed on an "AS IS" BASIS,
0013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014: * See the License for the specific language governing permissions and
0015: * limitations under the License.
0016: */
0017: package org.apache.cocoon.transformation;
0018:
0019: import org.apache.avalon.framework.configuration.Configuration;
0020: import org.apache.avalon.framework.configuration.ConfigurationException;
0021: import org.apache.avalon.framework.parameters.Parameters;
0022: import org.apache.avalon.framework.service.ServiceSelector;
0023: import org.apache.avalon.framework.thread.ThreadSafe;
0024:
0025: import org.apache.cocoon.ProcessingException;
0026: import org.apache.cocoon.acting.ValidatorActionResult;
0027: import org.apache.cocoon.transformation.helpers.FormValidatorHelper;
0028: import org.apache.cocoon.components.modules.input.InputModule;
0029: import org.apache.cocoon.environment.SourceResolver;
0030: import org.apache.cocoon.util.HashMap;
0031: import org.apache.cocoon.xml.dom.DOMStreamer;
0032: import org.apache.commons.lang.BooleanUtils;
0033:
0034: import org.w3c.dom.DocumentFragment;
0035: import org.xml.sax.Attributes;
0036: import org.xml.sax.SAXException;
0037: import org.xml.sax.helpers.AttributesImpl;
0038:
0039: import java.io.IOException;
0040: import java.util.Iterator;
0041: import java.util.LinkedList;
0042: import java.util.List;
0043: import java.util.Map;
0044:
0045: /**
0046: * @cocoon.sitemap.component.documentation
0047: * Eliminates the need for XSP to use FormValidatorAction or HTML forms.
0048: * Caveat: Select options need a value attribute to work correctly.
0049: *
0050: * @cocoon.sitemap.component.name simple-form
0051: * @cocoon.sitemap.component.logger sitemap.transformer.simple-form
0052: *
0053: *
0054: * <p>This transformer fills all HTML 4 form elements with values from
0055: * an InputModule, e.g. request, with the same name. It handles select
0056: * boxes, textareas, checkboxes, radio buttons, password and text
0057: * fields, and buttons. Form elements and complete forms can be protected
0058: * from substitution by adding an attribute fixed="true" to them.</p>
0059: *
0060: * <p>In addition, it handles FormValidatorAction results by
0061: * selectively omitting <error/> elements. These elements need a
0062: * "name" attribute corresponding to the name of the form element, and
0063: * either a "when" or a "when-ge" attribute.</p>
0064: *
0065: * <p>An error element is send down the pipeline if validation results
0066: * are available and either the results equals the "when" attribute or
0067: * the result is greater or equal to the "when-ge" attribute.</p>
0068: *
0069: * <p>Names for validation results are "ok", "not-present", "error",
0070: * "is-null", "too-small", "too-large", and "no-match" for the similar
0071: * named values from ValidatorActionResult.</p>
0072: *
0073: * <p>There need not to be an "error" element for every form element,
0074: * multiple error elements for the same form element may be
0075: * present.</p>
0076: *
0077: * <p><em>Names of error elements are never augmented by prefix, suffix or
0078: * form name.</em></p>
0079: *
0080: * <p>Page parts with multiple occurrences depending on the number of
0081: * actual parameters can be enclosed in <repeat on="expr" using="var"/>
0082: * elements. <em>expr</em> is used to determine the number of occurrences
0083: * and <em>var</em> will be expanded with the ordinary number. Repeat elements
0084: * can be nested.</p>
0085: *
0086: * <p>Example:</p>
0087: * <pre>
0088: * <repeat on="mult" using="i"><input type="text" name="mult[${i}]"/></repeat>
0089: * </pre>
0090: * <p>Will include as many input elements as mult parameters are present. Adding
0091: * the repeater variable to the elements name is necessary only with structured
0092: * parameters or when they should be numbered. See also the <em>strip-number</em>
0093: * configuration parameter.</p>
0094: *
0095: * <p>To use this transformer, add the following to your
0096: * transformation pipeline: <pre>
0097: * <map:transform type="simple-form"/>
0098: * </pre></p>
0099: *
0100: * <p>Configuration elements:
0101: * <table>
0102: * <tr><td>input-module</td><td>(String) InputModule configuration,
0103: * defaults to an empty configuration and the "request-param" module</td></tr>
0104: * <tr><td>fixed-attribute</td><td>(String) Name of the attribute used to
0105: * indicate that this element should not be changed. ("fixed")</td></tr>
0106: * <tr><td>use-form-name</td><td>(boolean) Add the name of the form to the
0107: * name of form elements. Uses default Separator , if default separator is null
0108: * or empty, separator is set to "/". ("false")</td></tr>
0109: * <tr><td>use-form-name-twice</td><td>(boolean) Add the name of the form twice to the
0110: * name of form elements. This is useful when the form instance has no
0111: * all enclosing root tag and the form name is used instead <em>and</em> the
0112: * form name needs to be used to find the form data. Uses default Separator ,
0113: * if default separator is null or empty, separator is set to "/".("false")</td></tr>
0114: * <tr><td>separator</td><td>(String) Separator between form name and element name ("/")
0115: * </td></tr>
0116: * <tr><td>prefix</td><td>(String) Prefix to add to element name for value lookup. No
0117: * separator will be added between prefix and rest of the name. Default
0118: * is "", when use-form-name is set, defaults to separator.</td></tr>
0119: * <tr><td>suffix</td><td>(String) Added to the input element's name. No
0120: * separator will be added between rest of the name and suffix. ("")</td></tr>
0121: * <tr><td>ignore-validation</td><td>(boolean) If set to true, all error
0122: * tags are copied as is regardless of the validation results.("false")</td></tr>
0123: * <tr><td>decoration</td><td>(int) Length of decorations around repeat variable. Example:
0124: * when using JXPath based module, decoration would be "[" and "]", hence 1. (1)</td></tr>
0125: * <tr><td>strip-number</td><td>(boolean) If set to false, element names of repeated
0126: * elements will contain the expanded repeater variable. ("true")</td></tr>
0127: * </table>
0128: * </p>
0129: *
0130: * <p>Sitemap parameters:
0131: * <table>
0132: * <tr><td>fixed</td><td>(boolean) Do not change values</td></tr>
0133: * <tr><td>prefix</td><td>(String) Added to the input element's name</td></tr>
0134: * <tr><td>suffix</td><td>(String) Added to the input element's name</td></tr>
0135: * <tr><td>input</td><td>(String) InputModule name</td></tr>
0136: * <tr><td>decoration</td><td>(int) Length of decorations around repeat variable.</td></tr>
0137: * <tr><td>strip-number</td><td>(boolean) Expanded repeater variable.</td></tr>
0138: * </table>
0139: * </p>
0140: *
0141: * <p>Example:<pre>
0142: * <input name="user.name" size="50" maxlength="60"/>
0143: * <error name="user.name" when-ge="error">required</error>
0144: * </pre></p>
0145: *
0146: * @author <a href="mailto:haul@apache.org">Christian Haul</a>
0147: * @version $Id: SimpleFormTransformer.java 433543 2006-08-22 06:22:54Z crossley $
0148: */
0149: public class SimpleFormTransformer extends AbstractSAXTransformer {
0150:
0151: /** strip numbers from repeated element name attributes */
0152: private boolean stripNumber = true;
0153:
0154: /** Symbolic names for elements */
0155: /** unknown element */
0156: private static final int ELEMENT_DEFAULT = 0;
0157: /** input element */
0158: private static final int ELEMENT_INPUT = 1;
0159: /** select element */
0160: private static final int ELEMENT_SELECT = 2;
0161: /** option element */
0162: private static final int ELEMENT_OPTION = 3;
0163: /** textarea element */
0164: private static final int ELEMENT_TXTAREA = 4;
0165: /** error element */
0166: private static final int ELEMENT_ERROR = 5;
0167: /** form element */
0168: private static final int ELEMENT_FORM = 6;
0169: /** repeat element */
0170: private static final int ELEMENT_REPEAT = 7;
0171: /** default element as Integer (needed as default in org.apache.cocoon.util.HashMap.get()) */
0172: private static final Integer defaultElement = new Integer(
0173: ELEMENT_DEFAULT);
0174:
0175: /** input type unknown */
0176: private static final int TYPE_DEFAULT = 0;
0177: /** input type checkbox */
0178: private static final int TYPE_CHECKBOX = 1;
0179: /** input type radio */
0180: private static final int TYPE_RADIO = 2;
0181: /** default input type as Integer (needed as default in org.apache.cocoon.util.HashMap.get()) */
0182: private static final Integer defaultType = new Integer(TYPE_DEFAULT);
0183:
0184: protected static final String INPUT_MODULE_ROLE = InputModule.ROLE;
0185: protected static final String INPUT_MODULE_SELECTOR = INPUT_MODULE_ROLE
0186: + "Selector";
0187:
0188: /** map element name string to symbolic name */
0189: private static final HashMap elementNames;
0190: /** map input type string to symbolic name */
0191: private static final HashMap inputTypes;
0192: /** map ValidatorActionResult to name string */
0193: private static final HashMap validatorResults;
0194: /** map name string to ValidatorActionResult */
0195: private static final HashMap validatorResultLabel;
0196:
0197: /** setup mapping tables */
0198: static {
0199: HashMap names = new HashMap();
0200: names.put("input", new Integer(ELEMENT_INPUT));
0201: names.put("select", new Integer(ELEMENT_SELECT));
0202: names.put("option", new Integer(ELEMENT_OPTION));
0203: names.put("textarea", new Integer(ELEMENT_TXTAREA));
0204: names.put("error", new Integer(ELEMENT_ERROR));
0205: names.put("form", new Integer(ELEMENT_FORM));
0206: names.put("repeat", new Integer(ELEMENT_REPEAT));
0207: elementNames = names;
0208: names = null;
0209:
0210: names = new HashMap();
0211: names.put("checkbox", new Integer(TYPE_CHECKBOX));
0212: names.put("radio", new Integer(TYPE_RADIO));
0213: inputTypes = names;
0214: names = null;
0215:
0216: names = new HashMap();
0217: names.put("ok", ValidatorActionResult.OK);
0218: names.put("not-present", ValidatorActionResult.NOTPRESENT);
0219: names.put("error", ValidatorActionResult.ERROR);
0220: names.put("is-null", ValidatorActionResult.ISNULL);
0221: names.put("too-small", ValidatorActionResult.TOOSMALL);
0222: names.put("too-large", ValidatorActionResult.TOOLARGE);
0223: names.put("no-match", ValidatorActionResult.NOMATCH);
0224: validatorResultLabel = names;
0225:
0226: names = new HashMap();
0227: names.put(ValidatorActionResult.OK, "ok");
0228: names.put(ValidatorActionResult.NOTPRESENT, "not-present");
0229: names.put(ValidatorActionResult.ERROR, "error");
0230: names.put(ValidatorActionResult.ISNULL, "is-null");
0231: names.put(ValidatorActionResult.TOOSMALL, "too-small");
0232: names.put(ValidatorActionResult.TOOLARGE, "too-large");
0233: names.put(ValidatorActionResult.NOMATCH, "no-match");
0234: validatorResults = names;
0235: names = null;
0236: }
0237:
0238: /** current element's request parameter values */
0239: protected Object[] values;
0240:
0241: /** current request's validation results (all validated elements) */
0242: protected Map validationResults;
0243:
0244: /** Should we skip inserting values? */
0245: private boolean fixed;
0246: /** Is the complete document protected? */
0247: private boolean documentFixed;
0248:
0249: private String fixedName = "fixed";
0250: private String prefix;
0251: private String suffix;
0252: private String defaultPrefix;
0253: private String defaultSuffix;
0254: private String separator;
0255: private String formName;
0256: private boolean useFormName;
0257: private boolean useFormNameTwice;
0258: private boolean ignoreValidation;
0259: private int decorationSize = 1;
0260:
0261: private String defaultInput = "request-param";
0262: private Configuration defaultInputConf;
0263: private Configuration inputConf;
0264: private InputModule input;
0265: private ServiceSelector inputSelector;
0266: private String inputName;
0267:
0268: /** Skip element's content only. Otherwise skip also surrounding element. */
0269: protected boolean skipChildrenOnly;
0270:
0271: /** Count nested repeat elements. */
0272: protected int recordingCount;
0273:
0274: /** List of {@link RepeaterStatus} elements keeping track of nested repeat blocks. */
0275: protected List repeater;
0276:
0277: /** Map of {@link ValueList} to track multiple parameters. */
0278: protected Map formValues;
0279:
0280: /**
0281: * Keep track of repeater status.
0282: */
0283: protected static class RepeaterStatus {
0284: public String var = null;
0285: public String expr = null;
0286: public int count = 0;
0287:
0288: public RepeaterStatus(String var, int count, String expr) {
0289: this .var = var;
0290: this .count = count;
0291: this .expr = expr;
0292: }
0293:
0294: public String toString() {
0295: return "[" + this .var + "," + this .expr + "," + this .count
0296: + "]";
0297: }
0298: }
0299:
0300: /**
0301: * Keep track of multiple values.
0302: */
0303: protected static class ValueList {
0304: private int current = -1;
0305: private Object[] values = null;
0306:
0307: public ValueList(Object[] values) {
0308: this .values = values;
0309: this .current = (values != null && values.length > 0 ? 0
0310: : -1);
0311: }
0312:
0313: public Object getNext() {
0314: Object result = null;
0315: if (this .values != null) {
0316: if (this .current < this .values.length) {
0317: result = this .values[this .current++];
0318: }
0319: }
0320: return result;
0321: }
0322: }
0323:
0324: public SimpleFormTransformer() {
0325: this .defaultNamespaceURI = "";
0326: }
0327:
0328: /** set per instance variables to defaults */
0329: private void reset() {
0330: this .skipChildrenOnly = false;
0331: this .values = null;
0332: this .validationResults = null;
0333: this .documentFixed = false;
0334: this .fixed = false;
0335: this .formName = null;
0336: this .recordingCount = 0;
0337: this .repeater = new LinkedList();
0338: this .formValues = new HashMap();
0339:
0340: if (this .inputSelector != null) {
0341: if (this .input != null)
0342: this .inputSelector.release(this .input);
0343: this .manager.release(this .inputSelector);
0344: }
0345: }
0346:
0347: /**
0348: * Avalon Configurable Interface
0349: */
0350: public void configure(Configuration config)
0351: throws ConfigurationException {
0352: super .configure(config);
0353:
0354: this .defaultInputConf = config.getChild("input-module");
0355: this .defaultInput = this .defaultInputConf.getAttribute("name",
0356: this .defaultInput);
0357: this .separator = config.getChild("separator").getValue(
0358: this .separator);
0359: this .defaultPrefix = config.getChild("prefix").getValue(
0360: this .defaultPrefix);
0361: this .defaultSuffix = config.getChild("suffix").getValue(
0362: this .defaultSuffix);
0363: this .fixedName = config.getChild("fixed-attribute").getValue(
0364: this .fixedName);
0365: this .useFormName = config.getChild("use-form-name")
0366: .getValueAsBoolean(this .useFormName);
0367: this .useFormNameTwice = config.getChild("use-form-name-twice")
0368: .getValueAsBoolean(this .useFormNameTwice);
0369: this .useFormName = this .useFormName || this .useFormNameTwice;
0370: if (this .useFormName) {
0371: this .separator = (this .separator == null
0372: || this .separator.length() == 0 ? "/"
0373: : this .separator);
0374: this .defaultPrefix = this .separator;
0375: }
0376: this .ignoreValidation = config.getChild("ignore-validation")
0377: .getValueAsBoolean(this .ignoreValidation);
0378: this .decorationSize = config.getChild("decoration")
0379: .getValueAsInteger(this .decorationSize);
0380: this .stripNumber = config.getChild("strip-number")
0381: .getValueAsBoolean(this .stripNumber);
0382: }
0383:
0384: /**
0385: * Read sitemap parameters and set properties accordingly.
0386: */
0387: private void evaluateParameters() {
0388: this .documentFixed = this .parameters.getParameterAsBoolean(
0389: "fixed", false);
0390: this .fixed = this .documentFixed;
0391: this .prefix = this .parameters.getParameter("prefix",
0392: this .defaultPrefix);
0393: this .suffix = this .parameters.getParameter("suffix",
0394: this .defaultSuffix);
0395: this .inputName = this .parameters.getParameter("input", null);
0396: this .decorationSize = this .parameters.getParameterAsInteger(
0397: "decoration", this .decorationSize);
0398: this .stripNumber = this .parameters.getParameterAsBoolean(
0399: "strip-number", this .stripNumber);
0400: }
0401:
0402: /**
0403: * Setup the next round.
0404: * The instance variables are initialised.
0405: * @param resolver The current SourceResolver
0406: * @param objectModel The objectModel of the environment.
0407: * @param src The value of the src attribute in the sitemap.
0408: * @param par The parameters from the sitemap.
0409: */
0410: public void setup(SourceResolver resolver, Map objectModel,
0411: String src, Parameters par) throws ProcessingException,
0412: SAXException, IOException {
0413:
0414: this .reset();
0415:
0416: super .setup(resolver, objectModel, src, par);
0417:
0418: if (request == null) {
0419: getLogger().debug("no request object");
0420: throw new ProcessingException("no request object");
0421: }
0422: this .evaluateParameters();
0423: this .setupInputModule();
0424:
0425: }
0426:
0427: /**
0428: * Setup and obtain reference to the input module.
0429: */
0430: private void setupInputModule() {
0431: this .inputConf = null;
0432: if (this .ignoreValidation) {
0433: this .validationResults = null;
0434: } else {
0435: this .validationResults = FormValidatorHelper
0436: .getResults(this .objectModel);
0437: }
0438:
0439: if (this .inputName == null) {
0440: this .inputName = this .defaultInput;
0441: this .inputConf = this .defaultInputConf;
0442: }
0443:
0444: try {
0445: // obtain input module
0446: this .inputSelector = (ServiceSelector) this .manager
0447: .lookup(INPUT_MODULE_SELECTOR);
0448: if (this .inputName != null && this .inputSelector != null
0449: && this .inputSelector.isSelectable(this .inputName)) {
0450: this .input = (InputModule) this .inputSelector
0451: .select(this .inputName);
0452: if (!(this .input instanceof ThreadSafe && this .inputSelector instanceof ThreadSafe)) {
0453: this .inputSelector.release(this .input);
0454: this .manager.release(this .inputSelector);
0455: this .input = null;
0456: this .inputSelector = null;
0457: }
0458: } else {
0459: if (this .inputName != null)
0460: if (getLogger().isErrorEnabled())
0461: getLogger()
0462: .error(
0463: "A problem occurred setting up '"
0464: + this .inputName
0465: + "': Selector is "
0466: + (this .inputSelector != null ? "not "
0467: : "")
0468: + "null, Component is "
0469: + (this .inputSelector != null
0470: && this .inputSelector
0471: .isSelectable(this .inputName) ? "known"
0472: : "unknown"));
0473: }
0474: } catch (Exception e) {
0475: if (getLogger().isWarnEnabled())
0476: getLogger().warn(
0477: "A problem occurred setting up '"
0478: + this .inputName + "': "
0479: + e.getMessage());
0480: }
0481: }
0482:
0483: /**
0484: * Recycle this component.
0485: */
0486: public void recycle() {
0487: reset();
0488: super .recycle();
0489: }
0490:
0491: /**
0492: * Generate string representation of attributes. For debug only.
0493: */
0494: protected String printAttributes(Attributes attr) {
0495: StringBuffer sb = new StringBuffer();
0496: sb.append('[');
0497: for (int i = 0; i < attr.getLength(); i++) {
0498: sb.append('@').append(attr.getLocalName(i)).append("='")
0499: .append(attr.getValue(i)).append("' ");
0500: }
0501: sb.append(']');
0502: return sb.toString();
0503: }
0504:
0505: /**
0506: * Handle input elements that may have a "checked" attributes,
0507: * i.e. checkbox and radio.
0508: */
0509: protected void startCheckableElement(String aName, String uri,
0510: String name, String raw, AttributesImpl attributes)
0511: throws SAXException {
0512:
0513: // @fixed and this.fixed already considered in startInputElement
0514: this .values = this .getValues(aName);
0515: String checked = attributes.getValue("checked");
0516: String value = attributes.getValue("value");
0517: boolean found = false;
0518:
0519: if (getLogger().isDebugEnabled())
0520: getLogger().debug(
0521: "startCheckableElement " + name + " attributes "
0522: + this .printAttributes(attributes));
0523: if (this .values != null) {
0524: if (getLogger().isDebugEnabled())
0525: getLogger().debug("replacing");
0526: for (int i = 0; i < this .values.length; i++) {
0527: if (this .values[i].equals(value)) {
0528: found = true;
0529: if (checked == null) {
0530: attributes.addAttribute("", "checked",
0531: "checked", "CDATA", "");
0532: }
0533: break;
0534: }
0535: }
0536: if (!found && checked != null) {
0537: attributes.removeAttribute(attributes
0538: .getIndex("checked"));
0539: }
0540: }
0541: this .relayStartElement(uri, name, raw, attributes);
0542: }
0543:
0544: /**
0545: * Handle input elements that may don't have a "checked"
0546: * attributes, e.g. text, password, button.
0547: */
0548: protected void startNonCheckableElement(String aName, String uri,
0549: String name, String raw, AttributesImpl attributes)
0550: throws SAXException {
0551:
0552: // @fixed and this.fixed already considered in startInputElement
0553: Object fValue = this .getNextValue(aName);
0554: String value = attributes.getValue("value");
0555: if (getLogger().isDebugEnabled())
0556: getLogger().debug(
0557: "startNonCheckableElement " + name + " attributes "
0558: + this .printAttributes(attributes));
0559: if (fValue != null) {
0560: if (getLogger().isDebugEnabled())
0561: getLogger().debug("replacing");
0562: if (value != null) {
0563: attributes.setValue(attributes.getIndex("value"),
0564: String.valueOf(fValue));
0565: } else {
0566: attributes.addAttribute("", "value", "value", "CDATA",
0567: String.valueOf(fValue));
0568: }
0569: }
0570: this .relayStartElement(uri, name, raw, attributes);
0571: }
0572:
0573: /**
0574: * Handle input elements. Calls startCheckableElement or
0575: * startNonCheckableElement.
0576: */
0577: protected void startInputElement(String uri, String name,
0578: String raw, Attributes attr) throws SAXException {
0579:
0580: // @value = request.getParameterValues(@name)
0581: String aName = getName(attr.getValue("name"));
0582: String fixed = attr.getValue(this .fixedName);
0583:
0584: if (getLogger().isDebugEnabled())
0585: getLogger().debug(
0586: "startInputElement " + name + " attributes "
0587: + this .printAttributes(attr));
0588: if (aName == null || this .fixed
0589: || (fixed != null && BooleanUtils.toBoolean(fixed))) {
0590: this .relayStartElement(uri, name, raw, attr);
0591:
0592: } else {
0593: if (getLogger().isDebugEnabled())
0594: getLogger().debug("replacing");
0595:
0596: attr = this .normalizeAttributes(attr);
0597:
0598: AttributesImpl attributes = null;
0599: if (attr instanceof AttributesImpl) {
0600: attributes = (AttributesImpl) attr;
0601: } else {
0602: attributes = new AttributesImpl(attr);
0603: }
0604: String type = attributes.getValue("type");
0605: switch (((Integer) inputTypes.get(type, defaultType))
0606: .intValue()) {
0607: case TYPE_CHECKBOX:
0608: case TYPE_RADIO:
0609: this .startCheckableElement(aName, uri, name, raw,
0610: attributes);
0611: break;
0612:
0613: case TYPE_DEFAULT:
0614: this .startNonCheckableElement(aName, uri, name, raw,
0615: attributes);
0616: break;
0617: }
0618: this .values = null;
0619: }
0620: }
0621:
0622: /**
0623: * Handle select elements. Sets up some instance variables for
0624: * following option elements.
0625: */
0626: protected void startSelectElement(String uri, String name,
0627: String raw, Attributes attr) throws SAXException {
0628:
0629: // this.values = request.getParameterValues(@name)
0630: String aName = getName(attr.getValue("name"));
0631: String fixed = attr.getValue(this .fixedName);
0632: this .values = null;
0633: if (getLogger().isDebugEnabled())
0634: getLogger().debug(
0635: "startSelectElement " + name + " attributes "
0636: + this .printAttributes(attr));
0637: if (aName != null
0638: && !(this .fixed || (fixed != null && BooleanUtils
0639: .toBoolean(fixed)))) {
0640: if (attr.getIndex("multiple") > -1) {
0641: this .values = this .getValues(aName);
0642: } else {
0643: Object val = this .getNextValue(aName);
0644: if (val != null) {
0645: this .values = new Object[1];
0646: this .values[0] = val;
0647: } else {
0648: this .values = null;
0649: }
0650: }
0651: attr = this .normalizeAttributes(attr);
0652: }
0653: this .relayStartElement(uri, name, raw, attr);
0654: }
0655:
0656: /**
0657: * Handle option elements. Uses instance variables set up by
0658: * startSelectElement. Relies on option having a "value"
0659: * attribute, i.e. does not check following characters if "value"
0660: * is not present.
0661: */
0662: protected void startOptionElement(String uri, String name,
0663: String raw, Attributes attr) throws SAXException {
0664:
0665: // add @selected if @value in request.getParameterValues(@name)
0666: if (getLogger().isDebugEnabled())
0667: getLogger().debug(
0668: "startOptionElement " + name + " attributes "
0669: + this .printAttributes(attr));
0670: if (this .values == null || this .fixed) {
0671: this .relayStartElement(uri, name, raw, attr);
0672: } else {
0673: if (getLogger().isDebugEnabled())
0674: getLogger().debug("replacing");
0675: AttributesImpl attributes = null;
0676: if (attr instanceof AttributesImpl) {
0677: attributes = (AttributesImpl) attr;
0678: } else {
0679: attributes = new AttributesImpl(attr);
0680: }
0681: String selected = attributes.getValue("selected");
0682: String value = attributes.getValue("value");
0683: boolean found = false;
0684:
0685: for (int i = 0; i < this .values.length; i++) {
0686: if (this .values[i].equals(value)) {
0687: found = true;
0688: if (selected == null) {
0689: attributes.addAttribute("", "selected",
0690: "selected", "CDATA", "");
0691: }
0692: break;
0693: }
0694: }
0695: if (!found && selected != null) {
0696: attributes.removeAttribute(attributes
0697: .getIndex("selected"));
0698: }
0699:
0700: this .relayStartElement(uri, name, raw, attributes);
0701: }
0702: }
0703:
0704: /**
0705: * Handles textarea elements. Skips nested events if request
0706: * parameter with same name exists.
0707: */
0708: protected void startTextareaElement(String uri, String name,
0709: String raw, Attributes attributes) throws SAXException {
0710:
0711: String aName = getName(attributes.getValue("name"));
0712: String fixed = attributes.getValue(this .fixedName);
0713: Object value = null;
0714: if (getLogger().isDebugEnabled())
0715: getLogger().debug(
0716: "startTextareaElement " + name + " attributes "
0717: + this .printAttributes(attributes));
0718: if (aName != null) {
0719: value = this .getNextValue(aName);
0720: }
0721: if (value == null || this .fixed
0722: || (fixed != null && BooleanUtils.toBoolean(fixed))) {
0723: this .relayStartElement(uri, name, raw, attributes);
0724: } else {
0725: if (getLogger().isDebugEnabled())
0726: getLogger().debug("replacing");
0727: this .relayStartElement(uri, name, raw, this
0728: .normalizeAttributes(attributes));
0729: String valString = String.valueOf(value);
0730: this .characters(valString.toCharArray(), 0, valString
0731: .length());
0732: // well, this doesn't really work out nicely. do it the hard way.
0733: if (this .ignoreEventsCount == 0)
0734: this .skipChildrenOnly = true;
0735: this .ignoreEventsCount++;
0736: }
0737: }
0738:
0739: /**
0740: * Handle error elements. If validation results are available,
0741: * compares validation result for parameter with the same name as
0742: * the "name" attribute with the result names is "when" and
0743: * "when-ge". Drops element and all nested events when error
0744: * condition is not met.
0745: */
0746: protected void startErrorElement(String uri, String name,
0747: String raw, Attributes attr) throws SAXException {
0748:
0749: if (getLogger().isDebugEnabled())
0750: getLogger().debug(
0751: "startErrorElement " + name + " attributes "
0752: + this .printAttributes(attr));
0753: if (this .ignoreValidation) {
0754: this .relayStartElement(uri, name, raw, attr);
0755: } else if (this .validationResults == null || this .fixed) {
0756: this .relayStartElement(true, false, uri, name, raw, attr);
0757: } else {
0758: String aName = attr.getValue("name");
0759: if (aName == null) {
0760: this .relayStartElement(uri, name, raw, attr);
0761: } else {
0762: ValidatorActionResult validation = FormValidatorHelper
0763: .getParamResult(this .objectModel, aName);
0764: String when = attr.getValue("when");
0765: String when_ge = attr.getValue("when-ge");
0766:
0767: if ((when != null && when.equals(validatorResults
0768: .get(validation)))
0769: || (when_ge != null && validation
0770: .ge((ValidatorActionResult) validatorResultLabel
0771: .get(
0772: when_ge,
0773: ValidatorActionResult.MAXERROR)))) {
0774: AttributesImpl attributes = null;
0775: if (attr instanceof AttributesImpl) {
0776: attributes = (AttributesImpl) attr;
0777: } else {
0778: attributes = new AttributesImpl(attr);
0779: }
0780: // remove attributes not meant for client
0781: attributes.removeAttribute(attributes
0782: .getIndex("name"));
0783: if (when != null)
0784: attributes.removeAttribute(attributes
0785: .getIndex("when"));
0786: if (when_ge != null)
0787: attributes.removeAttribute(attributes
0788: .getIndex("when-ge"));
0789: this .relayStartElement(uri, name, raw, this
0790: .normalizeAttributes(attributes));
0791: } else {
0792: this .relayStartElement(true, true, uri, name, raw,
0793: attr);
0794: }
0795: }
0796: }
0797: }
0798:
0799: /**
0800: * Start processing a form element. Sets protection indicator if attribute
0801: * "fixed" is present and either "true" or "yes". Removes attribute "fixed"
0802: * if present.
0803: * @param uri The namespace of the element.
0804: * @param name The local name of the element.
0805: * @param raw The qualified name of the element.
0806: * @param attr The attributes of the element.
0807: */
0808: protected void startFormElement(String uri, String name,
0809: String raw, Attributes attr) throws SAXException {
0810:
0811: String fixed = attr.getValue(this .fixedName);
0812: if (this .useFormName) {
0813: this .formName = attr.getValue("name");
0814: }
0815: if (fixed == null) {
0816: this .relayStartElement(uri, name, raw, attr);
0817: } else {
0818: if (!this .fixed && BooleanUtils.toBoolean(fixed)) {
0819: this .fixed = true;
0820: }
0821: // remove attributes not meant for client
0822: AttributesImpl attributes = null;
0823: if (attr instanceof AttributesImpl) {
0824: attributes = (AttributesImpl) attr;
0825: } else {
0826: attributes = new AttributesImpl(attr);
0827: }
0828: attributes.removeAttribute(attributes
0829: .getIndex(this .fixedName));
0830: this .relayStartElement(uri, name, raw, this
0831: .normalizeAttributes(attributes));
0832: }
0833: }
0834:
0835: /**
0836: * Start recording repeat element contents and push repeat expression and
0837: * variable to repeater stack. Only start recording, if no other recorder is
0838: * currently running.
0839: *
0840: * @param uri
0841: * @param name
0842: * @param raw
0843: * @param attr
0844: * @throws SAXException
0845: */
0846: protected void startRepeatElement(String uri, String name,
0847: String raw, Attributes attr) throws SAXException {
0848:
0849: if (this .recordingCount == 0) {
0850: if (!(this .fixed || BooleanUtils.toBoolean(attr
0851: .getValue(this .fixedName)))) {
0852: RepeaterStatus status = new RepeaterStatus("${"
0853: + attr.getValue("using") + "}", 0, attr
0854: .getValue("on"));
0855: this .repeater.add(status);
0856: this .startRecording();
0857: this .recordingCount++;
0858: } else {
0859: this .relayStartElement(uri, name, raw, attr);
0860: }
0861: } else {
0862: this .relayStartElement(uri, name, raw, attr);
0863: this .recordingCount++;
0864: }
0865: }
0866:
0867: /**
0868: * Stop recording repeat contents and replay required number of times.
0869: * Stop only if outmost repeat element is ending.
0870: *
0871: * @param uri
0872: * @param name
0873: * @param raw
0874: * @throws SAXException
0875: */
0876: protected void endRepeatElement(String uri, String name, String raw)
0877: throws SAXException {
0878: this .recordingCount--;
0879: if (this .recordingCount == 0) {
0880: DocumentFragment fragment = this .endRecording();
0881: RepeaterStatus status = (RepeaterStatus) this .repeater
0882: .get(this .repeater.size() - 1);
0883: Object[] vals = this .getValues(this .getName(status.expr));
0884: int count = (vals != null ? vals.length : 0);
0885: for (status.count = 1; status.count <= count; status.count++) {
0886: DOMStreamer streamer = new DOMStreamer(this , this );
0887: streamer.stream(fragment);
0888: }
0889: this .repeater.remove(this .repeater.size() - 1);
0890: } else {
0891: this .relayEndElement(uri, name, raw);
0892: if (this .recordingCount < 0) {
0893: this .recordingCount = 0;
0894: }
0895: }
0896: }
0897:
0898: /**
0899: * Start processing elements of our namespace.
0900: * This hook is invoked for each sax event with our namespace.
0901: * @param uri The namespace of the element.
0902: * @param name The local name of the element.
0903: * @param raw The qualified name of the element.
0904: * @param attr The attributes of the element.
0905: */
0906: public void startTransformingElement(String uri, String name,
0907: String raw, Attributes attr) throws SAXException {
0908:
0909: if (this .ignoreEventsCount == 0 && this .recordingCount == 0) {
0910: switch (((Integer) elementNames.get(name, defaultElement))
0911: .intValue()) {
0912: case ELEMENT_INPUT:
0913: this .startInputElement(uri, name, raw, attr);
0914: break;
0915:
0916: case ELEMENT_SELECT:
0917: this .startSelectElement(uri, name, raw, attr);
0918: break;
0919:
0920: case ELEMENT_OPTION:
0921: this .startOptionElement(uri, name, raw, attr);
0922: break;
0923:
0924: case ELEMENT_TXTAREA:
0925: this .startTextareaElement(uri, name, raw, attr);
0926: break;
0927:
0928: case ELEMENT_ERROR:
0929: this .startErrorElement(uri, name, raw, attr);
0930: break;
0931:
0932: case ELEMENT_FORM:
0933: this .startFormElement(uri, name, raw, attr);
0934: break;
0935:
0936: case ELEMENT_REPEAT:
0937: this .startRepeatElement(uri, name, raw, attr);
0938: break;
0939:
0940: default:
0941: this .relayStartElement(uri, name, raw, attr);
0942: }
0943:
0944: } else if (this .recordingCount > 0) {
0945: switch (((Integer) elementNames.get(name, defaultElement))
0946: .intValue()) {
0947: case ELEMENT_REPEAT:
0948: this .startRepeatElement(uri, name, raw, attr);
0949: break;
0950:
0951: default:
0952: this .relayStartElement(uri, name, raw, attr);
0953: }
0954: } else {
0955: this .relayStartElement(uri, name, raw, attr);
0956: }
0957: }
0958:
0959: /**
0960: * Start processing elements of our namespace.
0961: * This hook is invoked for each sax event with our namespace.
0962: * @param uri The namespace of the element.
0963: * @param name The local name of the element.
0964: * @param raw The qualified name of the element.
0965: */
0966: public void endTransformingElement(String uri, String name,
0967: String raw) throws SAXException {
0968:
0969: if (this .ignoreEventsCount > 0) {
0970: this .relayEndElement(uri, name, raw);
0971: } else if (this .recordingCount > 0) {
0972: switch (((Integer) elementNames.get(name, defaultElement))
0973: .intValue()) {
0974: case ELEMENT_REPEAT:
0975: this .endRepeatElement(uri, name, raw);
0976: break;
0977:
0978: default:
0979: this .relayEndElement(uri, name, raw);
0980: }
0981: } else {
0982: switch (((Integer) elementNames.get(name, defaultElement))
0983: .intValue()) {
0984: case ELEMENT_SELECT:
0985: this .values = null;
0986: this .relayEndElement(uri, name, raw);
0987: break;
0988: case ELEMENT_INPUT:
0989: case ELEMENT_OPTION:
0990: case ELEMENT_TXTAREA:
0991: case ELEMENT_ERROR:
0992: this .relayEndElement(uri, name, raw);
0993: break;
0994: case ELEMENT_FORM:
0995: this .fixed = this .documentFixed;
0996: this .formName = null;
0997: this .relayEndElement(uri, name, raw);
0998: break;
0999:
1000: case ELEMENT_REPEAT:
1001: this .endRepeatElement(uri, name, raw);
1002: break;
1003:
1004: default:
1005: this .relayEndElement(uri, name, raw);
1006: }
1007: }
1008: }
1009:
1010: /**
1011: * Remove extra information from element's attributes. Currently only removes
1012: * the repeater variable from the element's name attribute if present.
1013: *
1014: * @param attr
1015: * @return modified attributes
1016: */
1017: private Attributes normalizeAttributes(Attributes attr) {
1018: Attributes result = attr;
1019: if (this .stripNumber && this .repeater.size() > 0) {
1020: String name = attr.getValue("name");
1021: if (name != null) {
1022: for (Iterator i = this .repeater.iterator(); i.hasNext();) {
1023: RepeaterStatus status = (RepeaterStatus) i.next();
1024: int pos = name.indexOf(status.var);
1025: if (pos >= 0) {
1026: AttributesImpl attributes;
1027: if (result instanceof AttributesImpl) {
1028: attributes = (AttributesImpl) result;
1029: } else {
1030: attributes = new AttributesImpl(result);
1031: }
1032: name = name.substring(0, pos
1033: - this .decorationSize)
1034: + name.substring(pos
1035: + status.var.length()
1036: + this .decorationSize);
1037: attributes.setValue(
1038: attributes.getIndex("name"), name);
1039: result = attributes;
1040: }
1041: }
1042: }
1043: }
1044: return result;
1045: }
1046:
1047: /**
1048: * Generate the "real" name of an element for value lookup.
1049: * @param name
1050: * @return "real" name.
1051: */
1052: private String getName(String name) {
1053: String result = name;
1054: if (this .useFormName && this .formName != null) {
1055: if (this .separator != null) {
1056: if (this .useFormNameTwice) {
1057: result = this .formName + this .separator
1058: + this .formName + this .separator + result;
1059: } else {
1060: result = this .formName + this .separator + result;
1061: }
1062: } else {
1063: if (this .useFormNameTwice) {
1064: result = this .formName + result;
1065: } else {
1066: // does this make sense ?
1067: result = this .formName + this .formName + result;
1068: }
1069: }
1070: }
1071: if (this .prefix != null) {
1072: result = this .prefix + result;
1073: }
1074: if (this .suffix != null) {
1075: result = result + this .prefix;
1076: }
1077: if (this .repeater.size() > 0) {
1078: for (Iterator i = this .repeater.iterator(); i.hasNext();) {
1079: RepeaterStatus status = (RepeaterStatus) i.next();
1080: int pos = result.indexOf(status.var);
1081: if (pos != -1) {
1082: result = result.substring(0, pos)
1083: + status.count
1084: + result.substring(pos
1085: + status.var.length());
1086: }
1087: }
1088: }
1089: return result;
1090: }
1091:
1092: /**
1093: * Obtain values from used InputModule if not done already and return the
1094: * next value. If no more values exist, returns null.
1095: *
1096: * @param name
1097: */
1098: private Object getNextValue(String name) {
1099: Object result = null;
1100: if (this .formValues.containsKey(name)) {
1101: ValueList vList = (ValueList) this .formValues.get(name);
1102: result = vList.getNext();
1103: } else {
1104: ValueList vList = new ValueList(this .getValues(name));
1105: result = vList.getNext();
1106: this .formValues.put(name, vList);
1107: }
1108: return result;
1109: }
1110:
1111: /**
1112: * Obtain values from the used InputModule.
1113: */
1114: private Object[] getValues(String name) {
1115: Object[] values = null;
1116: ServiceSelector iputSelector = null;
1117: InputModule iput = null;
1118: try {
1119: if (this .input != null) {
1120: // input module is thread safe
1121: // thus we still have a reference to it
1122: values = input.getAttributeValues(name, this .inputConf,
1123: objectModel);
1124: if (getLogger().isDebugEnabled())
1125: getLogger().debug(
1126: "cached module " + this .input
1127: + " attribute " + name
1128: + " returns " + values);
1129: } else {
1130: // input was not thread safe
1131: // so acquire it again
1132: iputSelector = (ServiceSelector) this .manager
1133: .lookup(INPUT_MODULE_SELECTOR);
1134: if (this .inputName != null && iputSelector != null
1135: && iputSelector.isSelectable(this .inputName)) {
1136:
1137: iput = (InputModule) iputSelector
1138: .select(this .inputName);
1139: }
1140: if (iput != null) {
1141: values = iput.getAttributeValues(name,
1142: this .inputConf, objectModel);
1143: }
1144: if (getLogger().isDebugEnabled())
1145: getLogger().debug(
1146: "fresh module " + iput + " attribute "
1147: + name + " returns " + values);
1148: }
1149: } catch (Exception e) {
1150: if (getLogger().isWarnEnabled())
1151: getLogger().warn(
1152: "A problem occurred acquiring a value from '"
1153: + this .inputName + "' for '" + name
1154: + "': " + e.getMessage());
1155: } finally {
1156: // release components if necessary
1157: if (iputSelector != null) {
1158: if (iput != null)
1159: iputSelector.release(iput);
1160: this .manager.release(iputSelector);
1161: }
1162: }
1163:
1164: return values;
1165: }
1166:
1167: /**
1168: * Calls the super's method startTransformingElement.
1169: *
1170: * @param uri
1171: * @param name
1172: * @param raw
1173: * @param attr
1174: * @throws SAXException
1175: */
1176: protected void relayStartElement(String uri, String name,
1177: String raw, Attributes attr) throws SAXException {
1178: this .relayStartElement(false, false, uri, name, raw, attr);
1179: }
1180:
1181: /**
1182: * Calls the super's method startTransformingElement and increments the
1183: * ignoreEventsCount if skip is true. Increment can be done either before
1184: * invoking super's method, so that the element itself is skipped, or afterwards,
1185: * so that only the children are skipped.
1186: *
1187: * @param skip
1188: * @param skipChildrenOnly
1189: * @param uri
1190: * @param name
1191: * @param raw
1192: * @param attr
1193: * @throws SAXException
1194: */
1195: protected void relayStartElement(boolean skip,
1196: boolean skipChildrenOnly, String uri, String name,
1197: String raw, Attributes attr) throws SAXException {
1198:
1199: try {
1200: if (this .ignoreEventsCount > 0) {
1201: this .ignoreEventsCount++;
1202: super .startTransformingElement(uri, name, raw, attr);
1203: } else {
1204: if (skip)
1205: this .skipChildrenOnly = skipChildrenOnly;
1206: if (skip && !skipChildrenOnly)
1207: this .ignoreEventsCount++;
1208: super .startTransformingElement(uri, name, raw, attr);
1209: if (skip && skipChildrenOnly)
1210: this .ignoreEventsCount++;
1211: }
1212: } catch (ProcessingException e) {
1213: throw new SAXException(e);
1214: } catch (IOException e) {
1215: throw new SAXException(e);
1216: }
1217: }
1218:
1219: /**
1220: * Calls the super's method endTransformingElement and decrements the
1221: * ignoreEventsCount if larger than zero.
1222: *
1223: * @param uri
1224: * @param name
1225: * @param raw
1226: * @throws SAXException
1227: */
1228: protected void relayEndElement(String uri, String name, String raw)
1229: throws SAXException {
1230:
1231: if (this .ignoreEventsCount == 1 && this .skipChildrenOnly)
1232: this .ignoreEventsCount--;
1233: try {
1234: super .endTransformingElement(uri, name, raw);
1235: } catch (ProcessingException e) {
1236: throw new SAXException(e);
1237: } catch (IOException e) {
1238: throw new SAXException(e);
1239: } catch (Exception e) {
1240: getLogger().error("exception", e);
1241: }
1242:
1243: if (this .ignoreEventsCount > 0)
1244: this.ignoreEventsCount--;
1245: }
1246:
1247: }
|