cocoon-cvs mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From tcu...@apache.org
Subject svn commit: r109661 - /cocoon/branches/BRANCH_2_1_X/src/blocks/paranoid/java/org/apache/cocoon/servlet/ParanoidClassLoader.java /cocoon/branches/BRANCH_2_1_X/src/blocks/paranoid/java/org/apache/cocoon/servlet/ParanoidCocoonServlet.java /cocoon/branches/BRANCH_2_1_X/status.xml
Date Fri, 03 Dec 2004 10:09:31 GMT
Author: tcurdt
Date: Fri Dec  3 02:09:30 2004
New Revision: 109661

URL: http://svn.apache.org/viewcvs?view=rev&rev=109661
Log:
make the classloader implementation configurable inside the web.xml


Modified:
   cocoon/branches/BRANCH_2_1_X/src/blocks/paranoid/java/org/apache/cocoon/servlet/ParanoidClassLoader.java
   cocoon/branches/BRANCH_2_1_X/src/blocks/paranoid/java/org/apache/cocoon/servlet/ParanoidCocoonServlet.java
   cocoon/branches/BRANCH_2_1_X/status.xml

Modified: cocoon/branches/BRANCH_2_1_X/src/blocks/paranoid/java/org/apache/cocoon/servlet/ParanoidClassLoader.java
Url: http://svn.apache.org/viewcvs/cocoon/branches/BRANCH_2_1_X/src/blocks/paranoid/java/org/apache/cocoon/servlet/ParanoidClassLoader.java?view=diff&rev=109661&p1=cocoon/branches/BRANCH_2_1_X/src/blocks/paranoid/java/org/apache/cocoon/servlet/ParanoidClassLoader.java&r1=109660&p2=cocoon/branches/BRANCH_2_1_X/src/blocks/paranoid/java/org/apache/cocoon/servlet/ParanoidClassLoader.java&r2=109661
==============================================================================
--- cocoon/branches/BRANCH_2_1_X/src/blocks/paranoid/java/org/apache/cocoon/servlet/ParanoidClassLoader.java
(original)
+++ cocoon/branches/BRANCH_2_1_X/src/blocks/paranoid/java/org/apache/cocoon/servlet/ParanoidClassLoader.java
Fri Dec  3 02:09:30 2004
@@ -22,12 +22,13 @@
 import java.net.URLStreamHandlerFactory;
 
 /**
- * The <code>ParanoidClassLoader</code> reverses the search order for
- * classes.  It checks this classloader before it checks its parent.
- *
- * @author <a href="mailto:bloritsch@apache.org">Berin Loritsch</a>
- * @author <a href="http://www.apache.org/~sylvain/">Sylvain Wallez</a>
- * @version CVS $Id: ParanoidClassLoader.java,v 1.2 2004/03/05 13:02:02 bdelacretaz Exp $
+ * The <code>ParanoidClassLoader</code> reverses the search order for classes.
+ * It checks this classloader before it checks its parent.
+ * 
+ * @author <a href="mailto:bloritsch@apache.org">Berin Loritsch </a>
+ * @author <a href="http://www.apache.org/~sylvain/">Sylvain Wallez </a>
+ * @version CVS $Id: ParanoidClassLoader.java 30932 2004-07-29 17:35:38Z
+ *          vgritsenko $
  */
 
 public class ParanoidClassLoader extends URLClassLoader {
@@ -54,33 +55,32 @@
     }
 
     /**
-     * Alternate constructor to define a parent and initial
-     * <code>URL</code>s.
+     * Alternate constructor to define a parent and initial <code>URL</code>
+     * s.
      */
     public ParanoidClassLoader(final URL[] urls, final ClassLoader parent) {
         this(urls, parent, null);
     }
 
     /**
-     * Alternate constructor to define a parent, initial
-     * <code>URL</code>s, and a default
-     * <code>URLStreamHandlerFactory</code>.
+     * Alternate constructor to define a parent, initial <code>URL</code>s,
+     * and a default <code>URLStreamHandlerFactory</code>.
      */
     public ParanoidClassLoader(final URL[] urls, final ClassLoader parent, final URLStreamHandlerFactory
factory) {
         super(urls, parent, factory);
     }
 
     /**
-     * Extends <code>URLClassLoader</code>'s initialization methods so
-     * we return a <code>ParanoidClassLoad</code> instead.
+     * Extends <code>URLClassLoader</code>'s initialization methods so we
+     * return a <code>ParanoidClassLoad</code> instead.
      */
     public static final URLClassLoader newInstance(final URL[] urls) {
         return new ParanoidClassLoader(urls);
     }
 
     /**
-     * Extends <code>URLClassLoader</code>'s initialization methods so
-     * we return a <code>ParanoidClassLoad</code> instead.
+     * Extends <code>URLClassLoader</code>'s initialization methods so we
+     * return a <code>ParanoidClassLoad</code> instead.
      */
     public static final URLClassLoader newInstance(final URL[] urls, final ClassLoader parent)
{
         return new ParanoidClassLoader(urls, parent);
@@ -98,21 +98,19 @@
      * @return    the resulting <code>Class</code> object
      * @exception ClassNotFoundException if the class could not be found
      */
-    public final Class loadClass(String name, boolean resolve)
-      throws ClassNotFoundException
-    {
+    public final Class loadClass(String name, boolean resolve) throws ClassNotFoundException
{
         // First check if it's already loaded
         Class clazz = findLoadedClass(name);
-        
+
         if (clazz == null) {
-            
+
             try {
                 clazz = findClass(name);
                 //System.err.println("Paranoid load : " + name);
             } catch (ClassNotFoundException cnfe) {
                 ClassLoader parent = getParent();
                 if (parent != null) {
-                     // Ask to parent ClassLoader (can also throw a CNFE).
+                    // Ask to parent ClassLoader (can also throw a CNFE).
                     clazz = parent.loadClass(name);
                 } else {
                     // Propagate exception
@@ -120,14 +118,14 @@
                 }
             }
         }
-        
+
         if (resolve) {
             resolveClass(clazz);
         }
-        
+
         return clazz;
     }
-    
+
     /**
      * Gets a resource from this <code>ClassLoader</class>.  If the
      * resource does not exist in this one, we check the parent.
@@ -150,19 +148,20 @@
 
     /**
      * Adds a new directory of class files.
-     *
-     * @param file for jar or directory
+     * 
+     * @param file
+     *            for jar or directory
      * @throws IOException
      */
     public final void addDirectory(File file) throws IOException {
         this.addURL(file.getCanonicalFile().toURL());
     }
-    
+
     /**
      * Adds a new URL
      */
-    
+
     public void addURL(URL url) {
-    	super.addURL(url);
+        super.addURL(url);
     }
-}
+}
\ No newline at end of file

Modified: cocoon/branches/BRANCH_2_1_X/src/blocks/paranoid/java/org/apache/cocoon/servlet/ParanoidCocoonServlet.java
Url: http://svn.apache.org/viewcvs/cocoon/branches/BRANCH_2_1_X/src/blocks/paranoid/java/org/apache/cocoon/servlet/ParanoidCocoonServlet.java?view=diff&rev=109661&p1=cocoon/branches/BRANCH_2_1_X/src/blocks/paranoid/java/org/apache/cocoon/servlet/ParanoidCocoonServlet.java&r1=109660&p2=cocoon/branches/BRANCH_2_1_X/src/blocks/paranoid/java/org/apache/cocoon/servlet/ParanoidCocoonServlet.java&r2=109661
==============================================================================
--- cocoon/branches/BRANCH_2_1_X/src/blocks/paranoid/java/org/apache/cocoon/servlet/ParanoidCocoonServlet.java
(original)
+++ cocoon/branches/BRANCH_2_1_X/src/blocks/paranoid/java/org/apache/cocoon/servlet/ParanoidCocoonServlet.java
Fri Dec  3 02:09:30 2004
@@ -20,6 +20,8 @@
 import java.io.FilenameFilter;
 import java.io.IOException;
 import java.io.LineNumberReader;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.ArrayList;
@@ -33,18 +35,20 @@
 import javax.servlet.http.HttpServlet;
 
 /**
- * This servlet builds a classloading sandbox and runs another servlet inside that
- * sandbox. The purpose is to shield the libraries and classes shipped with the web
- * application from any other classes with the same name that may exist in the system,
- * such as Xerces and Xalan versions included in JDK 1.4.
+ * This servlet builds a classloading sandbox and runs another servlet inside
+ * that sandbox. The purpose is to shield the libraries and classes shipped with
+ * the web application from any other classes with the same name that may exist
+ * in the system, such as Xerces and Xalan versions included in JDK 1.4.
  * <p>
- * This servlet propagates all initialisation parameters to the sandboxed servlet, and
- * accepts the parameters <code>servlet-class</code> and <code>paranoid-classpath</code>.
+ * This servlet propagates all initialisation parameters to the sandboxed
+ * servlet, and accepts the parameters <code>servlet-class</code> and
+ * <code>paranoid-classpath</code>.
  * <ul>
- *  <li><code>servlet-class</code> defines the sandboxed servlet class,
the default is 
- *      {@link CocoonServlet}
- *  <li><code>paranoid-classpath</code> expects the name of a text file
that can contain 
- *      lines begining with <code>class-dir:<code> (directory containing classes),
+ * <li><code>servlet-class</code> defines the sandboxed servlet class,
the
+ * default is {@link CocoonServlet}
+ * <li><code>paranoid-classpath</code> expects the name of a text file
that
+ * can contain lines begining with
+ * <code>class-dir:<code> (directory containing classes),
  *      <code>lib-dir:<code> (directory containing JAR or ZIP libraries) and
<code>#</code>
  *      (for comments). <br/>
  *      All other lines are considered as URLs.
@@ -55,166 +59,167 @@
  *
  * @author <a href="mailto:bloritsch@apache.org">Berin Loritsch</a>
  * @author <a href="http://www.apache.org/~sylvain/">Sylvain Wallez</a>
+ * @author <a href="mailto:tcurdt@apache.org">Torsten Curdt</a>
  * @version CVS $Id$
  */
 
 public class ParanoidCocoonServlet extends HttpServlet {
 
-	/**
-	 * The name of the actual servlet class.
-	 */
-	public static final String DEFAULT_SERVLET_CLASS = "org.apache.cocoon.servlet.CocoonServlet";
-    
+    /**
+     * The name of the actual servlet class.
+     */
+    public static final String DEFAULT_SERVLET_CLASS = "org.apache.cocoon.servlet.CocoonServlet";
+
     protected static final String CONTEXT_PREFIX = "context:";
-    
+
     protected static final String FILE_PREFIX = "file:";
-    
-	protected Servlet servlet;
-    
+
+    protected Servlet servlet;
+
     protected ClassLoader classloader;
-    
-	public void init(ServletConfig config) throws ServletException {
-		
-		super.init(config);
 
-		// Create the classloader in which we will load the servlet
+    public void init(ServletConfig config) throws ServletException {
+
+        super.init(config);
+
+        // Create the classloader in which we will load the servlet
         // this can either be specified by an external file configured
-        // as a parameter in web.xml or (the default) all jars and 
+        // as a parameter in web.xml or (the default) all jars and
         // classes from WEB-INF/lib and WEB-INF/classes are used.
         final String externalClasspath = config.getInitParameter("paranoid-classpath");
-        if ( externalClasspath == null ) {
-            this.classloader = this.getClassLoader(this.getContextDir());
-        } else {
-            this.classloader = this.getClassLoader(externalClasspath, this.getContextDir());
+        final URL[] classPath = (externalClasspath == null)
+            ? getClassPath(getContextDir())
+            : getClassPath(externalClasspath, getContextDir());
+
+            
+        final String classLoaderName = config.getInitParameter("classloader-class");
+        if (classLoaderName != null) {
+            log("Using classloader " + classLoaderName);
         }
+        this.classloader = createClassLoader(classLoaderName, classPath);
+
         
         String servletName = config.getInitParameter("servlet-class");
         if (servletName == null) {
             servletName = DEFAULT_SERVLET_CLASS;
         }
+        log("Loading servlet class " + servletName);
+
         
         // Create the servlet
-		try {
-			Class servletClass = this.classloader.loadClass(servletName);
-            
-			this.servlet = (Servlet)servletClass.newInstance();
+        try {
 
-		} catch(Exception e) {
-			throw new ServletException("Cannot load servlet " + servletName, e);
-		}
-        
-		// Always set the context classloader. JAXP uses it to find a ParserFactory,
-		// and thus fails if it's not set to the webapp classloader.
+            Class servletClass = this.classloader.loadClass(servletName);
+            this.servlet = (Servlet) servletClass.newInstance();
+
+        } catch (Exception e) {
+            throw new ServletException("Cannot load servlet " + servletName, e);
+        }
+
+        // Always set the context classloader. JAXP uses it to find a
+        // ParserFactory,
+        // and thus fails if it's not set to the webapp classloader.
         final ClassLoader old = Thread.currentThread().getContextClassLoader();
         try {
             Thread.currentThread().setContextClassLoader(this.classloader);
-        
+
             // Inlitialize the actual servlet
             this.initServlet();
         } finally {
             Thread.currentThread().setContextClassLoader(old);
         }
-        
-	}
-	
-	/**
-	 * Initialize the wrapped servlet. Subclasses (see {@link BootstrapServlet} change the
-	 * <code>ServletConfig</code> given to the servlet.
-	 * 
-	 * @throws ServletException
-	 */
-	protected void initServlet() throws ServletException {
-		this.servlet.init(this.getServletConfig());
-	}
-	
-	/**
-	 * Get the web application context directory.
-	 * 
-	 * @return the context dir
-	 * @throws ServletException
-	 */
-	protected File getContextDir() throws ServletException {
-		String result = getServletContext().getRealPath("/");
-		if (result == null) {
-			throw new ServletException(this.getClass().getName() + " cannot run in an undeployed WAR
file");
-		}
-		return new File(result);
-	}
-    
-	/**
-	 * Get the classloader that will be used to create the actual servlet. Its classpath is
defined
-	 * by the WEB-INF/classes and WEB-INF/lib directories in the context dir.
-	 */
-	protected ClassLoader getClassLoader(File contextDir) throws ServletException {
-		List urlList = new ArrayList();
-        
-		try {
-			File classDir = new File(contextDir + "/WEB-INF/classes");
-			if (classDir.exists()) {
-				if (!classDir.isDirectory()) {
-					throw new ServletException(classDir + " exists but is not a directory");
-				}
-            
-				URL classURL = classDir.toURL();
-				log("Adding class directory " + classURL);
-				urlList.add(classURL);
-                
-			}
-            
-            // List all .jar and .zip
-			File libDir = new File(contextDir + "/WEB-INF/lib");
-			File[] libraries = libDir.listFiles(new JarFileFilter());
 
-			for (int i = 0; i < libraries.length; i++) {
-				URL lib = libraries[i].toURL();
-				log("Adding class library " + lib);
-				urlList.add(lib);
-			}
-		} catch (MalformedURLException mue) {
-			throw new ServletException(mue);
-		}
-        
-		URL[] urls = (URL[])urlList.toArray(new URL[urlList.size()]);
-        
-		return ParanoidClassLoader.newInstance(urls, this.getClass().getClassLoader());
-	}
-    
+    }
+
     /**
-     * Get the classloader that will be used to create the actual servlet. Its classpath
is defined
-     * by an external file.
+     * Initialize the wrapped servlet. Subclasses (see {@link BootstrapServlet}
+     * change the <code>ServletConfig</code> given to the servlet.
+     * 
+     * @throws ServletException
      */
-    protected ClassLoader getClassLoader(String externalClasspath, File contextDir) 
-    throws ServletException {
+    protected void initServlet() throws ServletException {
+        this.servlet.init(this.getServletConfig());
+    }
+
+    /**
+     * Get the web application context directory.
+     * 
+     * @return the context dir
+     * @throws ServletException
+     */
+    protected File getContextDir() throws ServletException {
+        String result = getServletContext().getRealPath("/");
+        if (result == null) {
+            throw new ServletException(this.getClass().getName() + " cannot run in an undeployed
WAR file");
+        }
+        return new File(result);
+    }
+
+    protected URL[] getClassPath(final File contextDir) throws ServletException {
+        List urlList = new ArrayList();
+
+        try {
+            File classDir = new File(contextDir + "/WEB-INF/classes");
+            if (classDir.exists()) {
+                if (!classDir.isDirectory()) {
+                    throw new ServletException(classDir + " exists but is not a directory");
+                }
+
+                URL classURL = classDir.toURL();
+                log("Adding class directory " + classURL);
+                urlList.add(classURL);
+
+            }
+
+            // List all .jar and .zip
+            File libDir = new File(contextDir + "/WEB-INF/lib");
+            File[] libraries = libDir.listFiles(new JarFileFilter());
+
+            for (int i = 0; i < libraries.length; i++) {
+                URL lib = libraries[i].toURL();
+                log("Adding class library " + lib);
+                urlList.add(lib);
+            }
+        } catch (MalformedURLException mue) {
+            throw new ServletException(mue);
+        }
+
+        URL[] urls = (URL[]) urlList.toArray(new URL[urlList.size()]);
+
+        return urls;
+    }
+    
+    protected URL[] getClassPath(final String externalClasspath, final File contextDir) throws
ServletException {
         final List urlList = new ArrayList();
 
         File file = new File(externalClasspath);
         if (!file.isAbsolute()) {
-            file = new File(contextDir,externalClasspath);
+            file = new File(contextDir, externalClasspath);
         }
 
         log("Adding classpath from " + file);
         try {
             FileReader fileReader = new FileReader(file);
             LineNumberReader lineReader = new LineNumberReader(fileReader);
-        
+
             String line;
             do {
-                line = lineReader.readLine();                
-                if ( line != null ) {
+                line = lineReader.readLine();
+                if (line != null) {
                     if (line.startsWith("class-dir:")) {
                         line = line.substring("class-dir:".length()).trim();
-                        if( line.startsWith(CONTEXT_PREFIX)) {
+                        if (line.startsWith(CONTEXT_PREFIX)) {
                             line = contextDir + line.substring(CONTEXT_PREFIX.length());
                         }
                         URL url = new File(line).toURL();
                         log("Adding class directory " + url);
                         urlList.add(url);
-                        
+
                     } else if (line.startsWith("lib-dir:")) {
                         line = line.substring("lib-dir:".length()).trim();
-                        if( line.startsWith(CONTEXT_PREFIX)) {
-                            line = contextDir + line.substring(CONTEXT_PREFIX.length());
                           
-                        }                        
+                        if (line.startsWith(CONTEXT_PREFIX)) {
+                            line = contextDir + line.substring(CONTEXT_PREFIX.length());
+                        }
                         File dir = new File(line);
                         File[] libraries = dir.listFiles(new JarFileFilter());
                         log("Adding " + libraries.length + " libraries from " + dir.toURL());
@@ -222,74 +227,121 @@
                             URL url = libraries[i].toURL();
                             urlList.add(url);
                         }
-                    } else if(line.startsWith("#")) {
+                    } else if (line.startsWith("#")) {
                         // skip it (consider it as comment)
                     } else {
                         // Consider it as a URL
                         final URL lib;
-                        if( line.startsWith(CONTEXT_PREFIX)) {
-                            line = FILE_PREFIX + "/" + contextDir + 
-                                line.substring(CONTEXT_PREFIX.length()).trim();
-                        }                        
-                        if ( line.indexOf(':') == -1) {
-                            File entry = new File(line);        
-                            lib = entry.toURL();                          
+                        if (line.startsWith(CONTEXT_PREFIX)) {
+                            line = FILE_PREFIX + "/" + contextDir + line.substring(CONTEXT_PREFIX.length()).trim();
+                        }
+                        if (line.indexOf(':') == -1) {
+                            File entry = new File(line);
+                            lib = entry.toURL();
                         } else {
                             lib = new URL(line);
-                        }                        
+                        }
                         log("Adding class URL " + lib);
                         urlList.add(lib);
                     }
                 }
-            } while ( line != null );
+            } while (line != null);
             lineReader.close();
         } catch (IOException io) {
             throw new ServletException(io);
         }
-              
-        URL[] urls = (URL[])urlList.toArray(new URL[urlList.size()]);
-        
-        return ParanoidClassLoader.newInstance(urls, this.getClass().getClassLoader());
+
+        URL[] urls = (URL[]) urlList.toArray(new URL[urlList.size()]);
+
+        return urls;
+    }
+
+    protected ClassLoader createClassLoader(final String className, final URL[] classPath)
throws ServletException {
+        if (className != null) {
+            try {
+                final Class classLoaderClass = Class.forName(className);
+                final Class[] parameterClasses = new Class[] { ClassLoader.class };
+                final Constructor constructor = classLoaderClass.getConstructor(parameterClasses);
+                final Object[] parameters = new Object[] { this.getClass().getClassLoader()
};
+                final ClassLoader classloader = (ClassLoader) constructor.newInstance(parameters);
+                return classloader;
+            } catch (InstantiationException e) {
+                throw new ServletException("", e);
+            } catch (IllegalAccessException e) {
+                throw new ServletException("", e);
+            } catch (ClassNotFoundException e) {
+                throw new ServletException("", e);
+            } catch (SecurityException e) {
+                throw new ServletException("", e);
+            } catch (NoSuchMethodException e) {
+                throw new ServletException("", e);
+            } catch (IllegalArgumentException e) {
+                throw new ServletException("", e);
+            } catch (InvocationTargetException e) {
+                throw new ServletException("", e);
+            }
+        } else {
+            return ParanoidClassLoader.newInstance(classPath, this.getClass().getClassLoader());
+        }
+    }
+
+    
+    /**
+     * Get the classloader that will be used to create the actual servlet. Its
+     * classpath is defined by the WEB-INF/classes and WEB-INF/lib directories
+     * in the context dir.
+     * @deprecated
+     */
+    protected ClassLoader getClassLoader(File contextDir) throws ServletException {
+        return createClassLoader(null, getClassPath(contextDir));
+    }
+
+    /**
+     * Get the classloader that will be used to create the actual servlet. Its
+     * classpath is defined by an external file.
+     * @deprecated
+     */
+    protected ClassLoader getClassLoader(final String externalClasspath, final File contextDir)
throws ServletException {
+        return createClassLoader(null, getClassPath(externalClasspath, contextDir));
     }
 
-	/**
-	 * Service the request by delegating the call to the real servlet
-	 */
-	public void service(ServletRequest request, ServletResponse response)
-    throws ServletException, IOException {
+    /**
+     * Service the request by delegating the call to the real servlet
+     */
+    public void service(ServletRequest request, ServletResponse response) throws ServletException,
IOException {
 
         final ClassLoader old = Thread.currentThread().getContextClassLoader();
         try {
-		    Thread.currentThread().setContextClassLoader(this.classloader);
-		    this.servlet.service(request, response);
+            Thread.currentThread().setContextClassLoader(this.classloader);
+            this.servlet.service(request, response);
         } finally {
-            Thread.currentThread().setContextClassLoader(old);    
+            Thread.currentThread().setContextClassLoader(old);
         }
-	}
-    
-	/**
-	 * Destroy the actual servlet
-	 */
-	public void destroy() {
+    }
+
+    /**
+     * Destroy the actual servlet
+     */
+    public void destroy() {
 
-        if ( this.servlet != null ) {
+        if (this.servlet != null) {
             final ClassLoader old = Thread.currentThread().getContextClassLoader();
             try {
                 Thread.currentThread().setContextClassLoader(this.classloader);
                 this.servlet.destroy();
             } finally {
-                Thread.currentThread().setContextClassLoader(old);    
+                Thread.currentThread().setContextClassLoader(old);
             }
         }
 
-		super.destroy();
-	}
-    
+        super.destroy();
+    }
+
     private class JarFileFilter implements FilenameFilter {
         public boolean accept(File dir, String name) {
             return name.endsWith(".zip") || name.endsWith(".jar");
         }
     }
-   
+
 }
 

Modified: cocoon/branches/BRANCH_2_1_X/status.xml
Url: http://svn.apache.org/viewcvs/cocoon/branches/BRANCH_2_1_X/status.xml?view=diff&rev=109661&p1=cocoon/branches/BRANCH_2_1_X/status.xml&r1=109660&p2=cocoon/branches/BRANCH_2_1_X/status.xml&r2=109661
==============================================================================
--- cocoon/branches/BRANCH_2_1_X/status.xml	(original)
+++ cocoon/branches/BRANCH_2_1_X/status.xml	Fri Dec  3 02:09:30 2004
@@ -202,6 +202,9 @@
 
   <changes>
  <release version="@version@" date="@date@">
+   <action dev="TC" type="add">
+    paranoid: make the classloader implementation configurable
+   </action>
    <action dev="AG" type="fix" fixes-bug="32408" due-to="Juan Jose Pablos" due-to-email="cheche@che-che.com">
      typo on the date-selector doc.
    </action>

Mime
View raw message