cocoon-cvs mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From cziege...@apache.org
Subject cvs commit: xml-cocoon2/src/blocks/session-fw/java/org/apache/cocoon/webapps/session/acting SessionFormAction.java
Date Tue, 03 Dec 2002 12:31:36 GMT
cziegeler    2002/12/03 04:31:35

  Modified:    src/blocks/session-fw/conf session-act.xmap
               src/blocks/session-fw/java/org/apache/cocoon/webapps/session/transformation
                        SessionPostTransformer.java
  Added:       src/blocks/session-fw/java/org/apache/cocoon/webapps/session/acting
                        SessionFormAction.java
  Log:
  Adding simple validation. Submitted by Guido Casper (gcasper@s-und-n.de)
  
  Revision  Changes    Path
  1.2       +2 -0      xml-cocoon2/src/blocks/session-fw/conf/session-act.xmap
  
  Index: session-act.xmap
  ===================================================================
  RCS file: /home/cvs/xml-cocoon2/src/blocks/session-fw/conf/session-act.xmap,v
  retrieving revision 1.1
  retrieving revision 1.2
  diff -u -r1.1 -r1.2
  --- session-act.xmap	27 Sep 2002 09:05:11 -0000	1.1
  +++ session-act.xmap	3 Dec 2002 12:31:35 -0000	1.2
  @@ -6,4 +6,6 @@
       <map:action name="session"
                   src="org.apache.cocoon.webapps.session.acting.SessionAction"/>
   
  +    <map:action name="session-form"
  +                src="org.apache.cocoon.webapps.session.acting.SessionFormAction"/>
   </xmap>
  
  
  
  1.3       +119 -10   xml-cocoon2/src/blocks/session-fw/java/org/apache/cocoon/webapps/session/transformation/SessionPostTransformer.java
  
  Index: SessionPostTransformer.java
  ===================================================================
  RCS file: /home/cvs/xml-cocoon2/src/blocks/session-fw/java/org/apache/cocoon/webapps/session/transformation/SessionPostTransformer.java,v
  retrieving revision 1.2
  retrieving revision 1.3
  diff -u -r1.2 -r1.3
  --- SessionPostTransformer.java	8 Oct 2002 14:43:19 -0000	1.2
  +++ SessionPostTransformer.java	3 Dec 2002 12:31:35 -0000	1.3
  @@ -51,17 +51,28 @@
   package org.apache.cocoon.webapps.session.transformation;
   
   import java.io.IOException;
  +import java.io.StringBufferInputStream;
   import java.util.Map;
   
  +import org.apache.excalibur.source.Source;
   import org.apache.excalibur.source.SourceParameters;
  +import org.apache.excalibur.source.SourceException;
   import org.apache.avalon.framework.parameters.Parameters;
  +import org.apache.avalon.framework.configuration.Configuration;
  +import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder;
  +import org.apache.avalon.framework.configuration.SAXConfigurationHandler;
  +import org.apache.avalon.framework.configuration.ConfigurationException;
   import org.apache.cocoon.ProcessingException;
  +import org.apache.cocoon.xml.XMLUtils;
  +import org.apache.cocoon.environment.Session;
   import org.apache.cocoon.webapps.session.SessionConstants;
  +import org.apache.cocoon.acting.ValidatorActionResult;
   
   import org.xml.sax.Attributes;
   import org.xml.sax.SAXException;
   import org.xml.sax.helpers.AttributesImpl;
   import org.w3c.dom.DocumentFragment;
  +import org.w3c.dom.Node;
   
   /**
    * This is the session post transformer. It does all the setting and
  @@ -105,6 +116,7 @@
       public static final String INPUTXML_PATH_ATTRIBUTE = "path";
       public static final String INPUTXML_NAME_ATTRIBUTE = "name";
       public static final String INPUTXML_TYPE_ATTRIBUTE = "type"; // optional
  +    public static final String INPUTXML_VALIDATIONRESULT_ATTRIBUTE = "valresult";
   
       /** The form element */
       public static final String FORM_ELEMENT = "form";
  @@ -115,6 +127,11 @@
       /** The form content element */
       public static final String FORM_CONTENT_ELEMENT = "content";
   
  +    /** The form validation rules */
  +    public static final String FORM_VALIDATION_ELEMENT = "validate";
  +    public static final String FORM_VALIDATION_SOURCE_ATTRIBUTE = "src";
  +    public static final String FORM_VALIDATESET_ELEMENT = "validate-set";
  +
       /** State: no element parsed */
       private static final int STATE_OUTSIDE   = 0;
       /** State: form element */
  @@ -126,8 +143,8 @@
       /** The current form name */
       private String formName;
   
  -    /** The unique form number */
  -    private static int currentFormNumber = 0;
  +    /** The validation results */
  +    private Map validationResultMap;
   
       public void setupTransforming()
       throws ProcessingException, SAXException, IOException {
  @@ -202,6 +219,15 @@
                                        "CDATA", attr.getValue(INPUTXML_TYPE_ATTRIBUTE));
               }
   
  +            ValidatorActionResult validationResult = null;
  +            if (validationResultMap != null && validationResultMap.get(fieldname)
!= null) {
  +                validationResult = (ValidatorActionResult)validationResultMap.get(fieldname);
  +                newattr.addAttribute("",
  +                                     INPUTXML_VALIDATIONRESULT_ATTRIBUTE,
  +                                     INPUTXML_VALIDATIONRESULT_ATTRIBUTE,
  +                                     "CDATA", validationResult.toString());
  +            }
  +
               super.startTransformingElement("", name, name, newattr); // remove namespace
               this.startRecording();
   
  @@ -224,7 +250,19 @@
           // Element form content
           } else if (name.equals(FORM_CONTENT_ELEMENT) == true
                      && this.state == STATE_FORM) {
  -            // ignore this
  +            // get validation results to be used for inputxml elements
  +            validationResultMap = (Map)this.getSessionManager().getSession(true).
  +                            getAttribute(this.formName + "validation-result");
  +
  +        // Element form validation rules
  +        } else if (name.equals(FORM_VALIDATION_ELEMENT) == true
  +                   && this.state == STATE_FORM) {
  +            this.startRecording();
  +            if (attr.getValue(FORM_VALIDATION_SOURCE_ATTRIBUTE) != null) {
  +                stack.push(attr.getValue(FORM_VALIDATION_SOURCE_ATTRIBUTE));
  +            } else {
  +                stack.push("EMPTY");
  +            }
   
           } else {
               super.startTransformingElement(uri, name, raw, attr);
  @@ -310,12 +348,7 @@
                      && this.state == STATE_FORM) {
               String action = this.endTextRecording();
               AttributesImpl a = (AttributesImpl)this.stack.pop();
  -            // append a unique number to the form
  -            synchronized (this.getClass()) {
  -                this.formName = a.getValue("name") + '_' + currentFormNumber;
  -                currentFormNumber++;
  -                if (currentFormNumber > 99999) currentFormNumber = 0;
  -            }
  +            this.formName = a.getValue("name");
               boolean hasPars = (action.indexOf("?") != -1);
               action = this.response.encodeURL(action + (hasPars ? '&' : '?') + SessionConstants.SESSION_FORM_PARAMETER+'='+this.formName);
               a.addAttribute("", "action", "action", "CDATA", action);
  @@ -328,6 +361,82 @@
           } else if (name.equals(FORM_CONTENT_ELEMENT) == true
                      && this.state == STATE_FORM) {
               // ignore this
  +
  +        // Element form validation rules
  +        } else if (name.equals(FORM_VALIDATION_ELEMENT) == true
  +                   && this.state == STATE_FORM) {
  +            if (this.formName == null) {
  +                throw new ProcessingException("The validate element must be contained inside
a form.");
  +            }
  +            DocumentFragment validationDoc = this.endRecording();
  +            String source = (String)stack.pop();
  +            if (!source.equals("EMPTY")) {
  +                // get configuration from external file
  +                // referenced by "src" attribute of "validate" element
  +
  +                Configuration conf = null;
  +                Session session = null;
  +                try {
  +                    Source resource = this.resolver.resolveURI(source);
  +                    SAXConfigurationHandler saxBuilder = new SAXConfigurationHandler();
  +                    resolver.toSAX(resource, saxBuilder);
  +
  +                    conf = saxBuilder.getConfiguration();
  +                    session = this.getSessionManager().getSession(true);
  +                    session.setAttribute(this.formName, conf);
  +                    
  +                    if (validationDoc != null) {
  +                        //validationDoc contains "validate-set" element
  +                        validationDoc.normalize();
  +                        Node validationNode = validationDoc.getFirstChild();
  +                        while (validationNode.getNodeType() != Node.ELEMENT_NODE) {
  +                            validationNode = validationNode.getNextSibling();
  +                            if (validationNode == null) break;
  +                        }
  +                        if (validationNode != null &&
  +                            validationNode.getNodeType() == Node.ELEMENT_NODE &&
  +                            validationNode.getNodeName().equals(FORM_VALIDATESET_ELEMENT))
{
  +                            String validationXML = XMLUtils.serializeNodeToXML(validationNode);
  +                            DefaultConfigurationBuilder builder = new DefaultConfigurationBuilder();
  +                            conf = builder.build(new StringBufferInputStream(validationXML));
  +                            session.setAttribute(this.formName+"validate-set", conf);
  +                        }
  +                    }
  +                    
  +                } catch (SourceException se) {
  +                    throw new ProcessingException("Cannot resolve"+source, se);
  +                } catch (ConfigurationException ce) {
  +                    throw new ProcessingException("Error building Configuration out of
validate-set element", ce);
  +                }
  +
  +            } else if (validationDoc != null) {
  +                //validationDoc contains the validation rules inline
  +                try {
  +                    validationDoc.normalize();
  +                    Node validationNode = validationDoc.getFirstChild();
  +                    while (validationNode.getNodeType() != Node.ELEMENT_NODE) {
  +                        validationNode = validationNode.getNextSibling();
  +                        if (validationNode == null) break;
  +                    }
  +                    if (validationNode != null &&
  +                        validationNode.getNodeType() == Node.ELEMENT_NODE &&
  +                        validationNode.getNodeName().equals("root")) {
  +
  +                        String validationXML = XMLUtils.serializeNodeToXML(validationNode);
  +                        DefaultConfigurationBuilder builder = new DefaultConfigurationBuilder();
  +                        Configuration conf = null;
  +                        Session session = null;
  +                        conf = builder.build(new StringBufferInputStream(validationXML));
  +                        session = this.getSessionManager().getSession(true);
  +                        session.setAttribute(this.formName, conf);
  +                        //the constraint-set to validate is the first and single one
  +                        session.setAttribute(this.formName+"validate-set", conf.getChildren
("constraint-set")[0]);
  +
  +                    }
  +                } catch (ConfigurationException ce) {
  +                    throw new ProcessingException("Error building Configuration out of
validation XML", ce);
  +                }
  +            }
   
           } else {
               super.endTransformingElement(uri, name, raw);
  
  
  
  1.1                  xml-cocoon2/src/blocks/session-fw/java/org/apache/cocoon/webapps/session/acting/SessionFormAction.java
  
  Index: SessionFormAction.java
  ===================================================================
  /*
  
   ============================================================================
                     The Apache Software License, Version 1.1
   ============================================================================
  
   Copyright (C) 1999-2002 The Apache Software Foundation. All rights reserved.
  
   Redistribution and use in source and binary forms, with or without modifica-
   tion, are permitted provided that the following conditions are met:
  
   1. Redistributions of  source code must  retain the above copyright  notice,
      this list of conditions and the following disclaimer.
  
   2. Redistributions in binary form must reproduce the above copyright notice,
      this list of conditions and the following disclaimer in the documentation
      and/or other materials provided with the distribution.
  
   3. The end-user documentation included with the redistribution, if any, must
      include  the following  acknowledgment:  "This product includes  software
      developed  by the  Apache Software Foundation  (http://www.apache.org/)."
      Alternately, this  acknowledgment may  appear in the software itself,  if
      and wherever such third-party acknowledgments normally appear.
  
   4. The names "Apache Cocoon" and  "Apache Software Foundation" must  not  be
      used to  endorse or promote  products derived from  this software without
      prior written permission. For written permission, please contact
      apache@apache.org.
  
   5. Products  derived from this software may not  be called "Apache", nor may
      "Apache" appear  in their name,  without prior written permission  of the
      Apache Software Foundation.
  
   THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
   INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
   FITNESS  FOR A PARTICULAR  PURPOSE ARE  DISCLAIMED.  IN NO  EVENT SHALL  THE
   APACHE SOFTWARE  FOUNDATION  OR ITS CONTRIBUTORS  BE LIABLE FOR  ANY DIRECT,
   INDIRECT, INCIDENTAL, SPECIAL,  EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLU-
   DING, BUT NOT LIMITED TO, PROCUREMENT  OF SUBSTITUTE GOODS OR SERVICES; LOSS
   OF USE, DATA, OR  PROFITS; OR BUSINESS  INTERRUPTION)  HOWEVER CAUSED AND ON
   ANY  THEORY OF LIABILITY,  WHETHER  IN CONTRACT,  STRICT LIABILITY,  OR TORT
   (INCLUDING  NEGLIGENCE OR  OTHERWISE) ARISING IN  ANY WAY OUT OF THE  USE OF
   THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  
   This software  consists of voluntary contributions made  by many individuals
   on  behalf of the Apache Software  Foundation and was  originally created by
   Stefano Mazzocchi  <stefano@apache.org>. For more  information on the Apache
   Software Foundation, please see <http://www.apache.org/>.
  
  */
  package org.apache.cocoon.webapps.session.acting;
  
  import org.apache.cocoon.acting.AbstractValidatorAction;
  import org.apache.cocoon.acting.ValidatorActionHelper;
  import org.apache.cocoon.acting.ValidatorActionResult;
  import org.apache.avalon.framework.configuration.Configuration;
  import org.apache.avalon.framework.parameters.Parameters;
  import org.apache.avalon.framework.thread.ThreadSafe;
  import org.apache.cocoon.Constants;
  import org.apache.cocoon.environment.ObjectModelHelper;
  import org.apache.cocoon.environment.Redirector;
  import org.apache.cocoon.environment.Request;
  import org.apache.cocoon.environment.SourceResolver;
  import org.apache.cocoon.environment.Session;
  import org.apache.cocoon.webapps.session.SessionConstants;
  import org.apache.cocoon.webapps.session.components.SessionManager;
  import org.apache.cocoon.util.Tokenizer;
  
  import java.util.HashMap;
  import java.util.Map;
  
  /**
   * This is the action used to validate Request parameters.
   * The validation rules are either embedded within the form
   * 
   * <pre>
   *    &lt;session:form name="info_form"&gt;
   *      &lt;session:action&gt;next_page&lt;/session:action&gt;
   *      &lt;session:content&gt;
   *        &lt;session:inputxml name="name" type="text" context="trackdemo" path="/user/name"/&gt;
   *      &lt;/session:content&gt;
   *      &lt;session:validate&gt;
   *        &lt;root&gt;
   *          &lt;parameter name="name" type="string" nullable="no"/&gt;
   *          &lt;constraint-set name="form_a_set"&gt;
   *            &lt;validate name="name"/&gt;
   *          &lt;/constraint-set&gt;
   *        &lt;/root&gt;
   *      &lt;/session:validate&gt;
   *    &lt;/session:form&gt;
   * </pre>
   * 
   * or described via the external xml file referenced
   * through the "src" attribute
   * (the format is defined in AbstractValidatorAction).
   * Then the constraint-set to be used has to be identified
   * through the "validate-set" element
   *
   * <pre>
   *    &lt;session:form name="info_form&gt;
   *      &lt;session:action&gt;next_page&lt;/session:action&gt;
   *      &lt;session:content&gt;
   *        &lt;session:inputxml name="name" type="text" context="trackdemo" path="/user/name"/&gt;
   *      &lt;/session:content&gt;
   *      &lt;session:validate src="descriptor.xml"&gt;
   *        &lt;validate-set name="form_a_set"/&gt;
   *      &lt;/session:validate&gt;
   *    &lt;/session:form&gt;
   * </pre>
   *
   * Since the validation rules are contained within the form document
   * they are read and supplied by the SessionTransformer.
   * So this action has to be used in conjunction with the SessionTransformer.
   * The "next_page" pipeline might look like this:
   * 
   * <pre>
   *     &lt;map:match pattern="next_page"&gt;
   *       &lt;map:act type="session-form"&gt;
   *           &lt;map:generate src="next_page.xml"/&gt;
   *           &lt;map:transform type="session"/&gt;
   *           &lt;map:transform src="simple2html.xsl"/&gt;
   *           &lt;map:serialize/&gt;
   *       &lt;/map:act&gt;
   *       &lt;map:generate src="first_page.xml"/&gt;
   *       &lt;map:transform type="session"/&gt;
   *       &lt;map:transform src="simple2html.xsl"/&gt;
   *       &lt;map:serialize/&gt;
   *     &lt;/map:match&gt;
   * </pre>
   *
   * where "session-form" is configured as SessionFormAction
   * 
   * @author <a href="mailto:gcasper@s-und-n.de">Guido Casper</a>
   * @version CVS $Id: SessionFormAction.java,v 1.1 2002/10/09 11:47:33 gcasper Exp $
  */
  public class SessionFormAction extends AbstractValidatorAction implements ThreadSafe
  {
      /**
       * Main invocation routine.
       */
      public Map act (Redirector redirector, SourceResolver resolver, Map objectModel, String
src,
              Parameters parameters) throws Exception {
          Request req = ObjectModelHelper.getRequest(objectModel);
  
          /* check request validity */
          if (req == null) {
              if (getLogger().isDebugEnabled())
                  getLogger().debug ("No request object");
              return null;
          }
  
          // read global parameter settings
          boolean reloadable = Constants.DESCRIPTOR_RELOADABLE_DEFAULT;
          if (this.settings.containsKey("reloadable")) {
              reloadable = Boolean.valueOf((String) this.settings.get("reloadable")).booleanValue();
          }
  
          // read local settings
          try {
  
              SessionManager sessionManager = (SessionManager)this.manager.lookup(SessionManager.ROLE);
              Session session = sessionManager.getSession(true);
  
              Configuration conf = (Configuration)session.getAttribute(
                                    req.getParameter(SessionConstants.SESSION_FORM_PARAMETER));
  
              String valstr = parameters.getParameter ("validate", (String) settings.get("validate",""));
              String valsetstr = parameters.getParameter ("validate-set", (String) settings.get("validate-set",""));
  
              Configuration valConf = (Configuration)session.getAttribute(
                                       req.getParameter(SessionConstants.SESSION_FORM_PARAMETER)+"validate-set");
              if (valConf != null) {
                  valsetstr = valConf.getAttribute("name","");
              }
  
              Configuration[] desc = conf.getChildren ("parameter");
              Configuration[] csets = conf.getChildren ("constraint-set");
  
              HashMap actionMap = new HashMap ();
              HashMap resultMap = new HashMap ();
              boolean allOK = true;
  
              /*
               * old obsoleted method
               */
              if (!"".equals (valstr.trim ())) {
                  if (getLogger().isDebugEnabled())
                      getLogger().debug ("Validating parameters "
                                         + "as specified via 'validate' parameter");
                  /* get list of params to be validated */
                  String[] rparams = null;
                  if (!"*".equals(valstr.trim())) {
                      rparams = Tokenizer.tokenize (valstr, ",", false);
                  } else {
                      // validate _all_ parameters
                      rparams = new String[desc.length];
                      for (int i=0; i<desc.length; i++) {
                          rparams[i] = desc[i].getAttribute("name","");
                          if ("".equals(rparams[i])) {
                              if (getLogger().isDebugEnabled())
                                  getLogger().debug ("Wrong syntax of the 'validate' parameter");
                              return null;
                          }
                      }
                  }
                  /* perform actual validation */
                  ValidatorActionHelper result = null;
                  String name = null;
                  HashMap params = new HashMap (rparams.length);
                  /* put required params into hash */
                  for (int i = 0; i < rparams.length; i ++) {
                      name = rparams[i];
                      if (name == null || "".equals (name.trim ())) {
                          if (getLogger().isDebugEnabled())
                              getLogger().debug ("Wrong syntax of the 'validate' parameter");
                          return null;
                      }
                      name = name.trim ();
                      params.put (name, req.getParameter (name));
                  }
                  for (int i = 0; i < rparams.length; i ++) {
                      name = rparams[i].trim ();
                      result = validateParameter (name, null, desc,
                                                  params, true);
                      if (!result.isOK()) {
                          if (getLogger().isDebugEnabled())
                              getLogger().debug ("Validation failed for parameter " + name);
                          allOK = false;
                      }
                      actionMap.put (name, result.getObject());
                      resultMap.put (name, result.getResult());
                  }
              }
              /*
               * new set-based method
               */
              if (!"".equals (valsetstr.trim ())) {
                  if (getLogger().isDebugEnabled())
                      getLogger().debug ("Validating parameters "
                                         + "from given constraint-set " + valsetstr);
                  // go over all constraint sets
                  // untill the requested set is found
                  // set number will be in j
                  Configuration cset = null;
                  String setname = null;
                  int j = 0;
                  boolean found = false;
                  for (j = 0; j < csets.length; j ++) {
                      setname = csets[j].getAttribute ("name", "");
                      if (valsetstr.trim().equals (setname.trim ())) {
                          found = true;
                          break;
                      }
                  }
                  if (!found) {
                      if (getLogger().isDebugEnabled())
                          getLogger().debug ("Given set "
                                             + valsetstr
                                             + " does not exist in a description file");
                      return null;
                  }
                  cset = csets[j];
                  /* get the list of params to be validated */
                  Configuration[] set = cset.getChildren ("validate");
  
                  /* perform actuall validation */
                  ValidatorActionHelper result = null;
                  String name = null;
                  HashMap params = new HashMap (set.length);
                  if (getLogger().isDebugEnabled())
                      getLogger().debug ("Given set "
                                         + valsetstr
                                         + " contains " + set.length + " rules");
                  /* put required params into hash */
                  for (int i = 0; i < set.length; i ++) {
                      name = set[i].getAttribute ("name", "").trim();
                      if ("".equals(name)) {
                          if (getLogger().isDebugEnabled())
                              getLogger().debug ("Wrong syntax "
                                                 + " of 'validate' children nr. " + i);
                          return null;
                      }
                      Object[] values = req.getParameterValues(name);
                      if (values != null) {
                          switch (values.length) {
                          case 0: params.put(name,null); break;
                          case 1: params.put(name,values[0]); break;
                          default: params.put(name,values);
                          }
                      } else {
                          params.put(name,values);
                      }
                  }
                  String rule = null;
                  for (int i = 0; i < set.length; i ++) {
                      name = set[i].getAttribute ("name", null);
                      rule = set[i].getAttribute("rule",name);
                      result = validateParameter (name, rule, set[i],
                              desc, params, true);
                      if (!result.isOK()) {
                          if (getLogger().isDebugEnabled())
                              getLogger().debug ("Validation failed for parameter " + name);
                          allOK = false;
                      }
                      actionMap.put (name, result.getObject());
                      resultMap.put (name, result.getResult());
                  }
              }
              if (!allOK) {
                  // if any validation failed return an empty map
                  actionMap = null;
                  resultMap.put("*", ValidatorActionResult.ERROR);
                  if (getLogger().isDebugEnabled())
                      getLogger().debug ("All form params validated. An error occurred.");
              } else {
                  resultMap.put("*", ValidatorActionResult.OK);
                  if (getLogger().isDebugEnabled())
                      getLogger().debug ("All form params successfully validated");
              }
              // store validation results in request attribute
              req.setAttribute(Constants.XSP_FORMVALIDATOR_PATH, resultMap);
              // store validation results in session attribute
              // to be used by SessionTransformer
              session.setAttribute(req.getParameter(SessionConstants.SESSION_FORM_PARAMETER)+
                                   "validation-result", resultMap);
              //return Collections.unmodifiableMap (actionMap);
              return actionMap;
          } catch (Exception e) {
              if (getLogger().isDebugEnabled())
                  getLogger().debug ("exception: ", e);
          }
          return null;
      }
  }
  
  
  

----------------------------------------------------------------------
In case of troubles, e-mail:     webmaster@xml.apache.org
To unsubscribe, e-mail:          cocoon-cvs-unsubscribe@xml.apache.org
For additional commands, e-mail: cocoon-cvs-help@xml.apache.org


Mime
View raw message