db-derby-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From abr...@apache.org
Subject svn commit: r476365 - in /db/derby/code/trunk/java/testing/org/apache/derbyTesting: functionTests/tests/lang/XMLBindingTest.java functionTests/tests/lang/XMLTypeAndOpsTest.java junit/TestConfiguration.java junit/XML.java
Date Fri, 17 Nov 2006 23:30:53 GMT
Author: abrown
Date: Fri Nov 17 15:30:51 2006
New Revision: 476365

URL: http://svn.apache.org/viewvc?view=rev&rev=476365
Log:
Adds a new JUnit test to replace the old lang/xmlBinding.java test. The patch does the following:

  - Adds XML file insertion utility methods to junit/XML.java
  - Creates a new JUnit test called lang/XMLBindingTest.java that
    uses the new utility methods to test various binding scenarios
    with Derby's SQL/XML operators.
  - Overloads the TestConfiguration.defaultSuite() method with a boolean
    signature to allow optional addition of CleanDatabaseSetup.
  - Updates lang/XMLTypeAndOpsTest to use the new overloaded defaultSuite()
    method. 

Added:
    db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/XMLBindingTest.java
  (with props)
Modified:
    db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/XMLTypeAndOpsTest.java
    db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/TestConfiguration.java
    db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/XML.java

Added: db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/XMLBindingTest.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/XMLBindingTest.java?view=auto&rev=476365
==============================================================================
--- db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/XMLBindingTest.java
(added)
+++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/XMLBindingTest.java
Fri Nov 17 15:30:51 2006
@@ -0,0 +1,339 @@
+/*
+
+   Derby - Class org.apache.derbyTesting.functionTests.tests.lang.XMLBindingTest
+
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+ */
+
+package org.apache.derbyTesting.functionTests.tests.lang;
+
+import java.io.InputStreamReader;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.Statement;
+import java.sql.Types;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+import org.apache.derbyTesting.junit.JDBC;
+import org.apache.derbyTesting.junit.XML;
+import org.apache.derbyTesting.junit.BaseJDBCTestCase;
+import org.apache.derbyTesting.junit.BaseJDBCTestSetup;
+import org.apache.derbyTesting.junit.BaseTestCase;
+import org.apache.derbyTesting.junit.SupportFilesSetup;
+import org.apache.derbyTesting.junit.TestConfiguration;
+
+/**
+ * This test checks to make sure that the XML data type and
+ * the corresponding XML operations all work as expected
+ * from the JDBC side of things.  In particular, this test
+ * verifies that 1) it is NOT possible to bind to/from an XML
+ * datatype (because such an operation requires JDBC 4.0 and
+ * is not yet supported by Derby), and 2) the correct behavior
+ * occurs when null values (both Java and SQL) are bound
+ * into the bindable parameters for the XML operators.
+ *
+ * This test also checks that insertion from XML files
+ * via a character stream works, which is important since
+ * XML files can be arbitrarily long and thus stream-based
+ * processing is a must.
+ */
+public class XMLBindingTest extends BaseJDBCTestCase {
+
+    /**
+     * Public constructor required for running test as standalone JUnit.
+     */
+    public XMLBindingTest(String name)
+    {
+        super(name);
+    }
+
+    /**
+     * Return a suite that runs a set of XML binding tests.  Only return
+     * such a suite IF the testing classpath has the required XML classes.
+     * Otherwise just return an empty suite.
+     */
+    public static Test suite()
+    {
+        TestSuite suite = new TestSuite("XML Binding Suite");
+        if (XML.classpathMeetsXMLReqs())
+        {
+            /* "false" in the next line means that we will *not* clean the
+             * database before the embedded and client suites.  This ensures
+             * that we do not remove the objects created by XBindTestSetup.
+             */
+            suite.addTest(
+                TestConfiguration.defaultSuite(XMLBindingTest.class, false));
+
+            XBindTestSetup wrapper = new XBindTestSetup(suite);
+
+            /* XML parser needs to read "personal.dtd" for schema-based
+             * insertion, so copy it to user directory.
+             */
+            return new SupportFilesSetup(wrapper,
+                new String [] {
+                    "functionTests/tests/lang/xmlTestFiles/personal.dtd"
+                });
+        }
+
+        return suite;
+    }
+
+    /**
+     * Performs a series of binding checks to make sure binding
+     * to or from an XML value never works.
+     */
+    public void testInvalidXMLBindings() throws Exception
+    {
+        // Binding to an XML column.
+        assertCompileError("42Z70", "insert into xTable.t1(x) values (?)");
+
+        // Binding to an XML value in the XMLSERIALIZE operator.
+        assertCompileError("42Z70", 
+                "select XMLSERIALIZE(? AS CLOB) FROM XTABLE.T1");
+
+        // Binding to an XML value in the XMLEXISTS operator.
+        assertCompileError("42Z70", "select i from xTable.t1 where " +
+                "XMLEXISTS('//*' PASSING BY REF ?)");
+
+        // Binding to an XML value in the XMLQUERY operator.
+        assertCompileError("42Z70", "select i from xTable.t1 where " +
+                "XMLQUERY('//*' PASSING BY REF ? EMPTY ON EMPTY) is " +
+                "not null");
+
+        /* Make sure that attempts to bind _from_ XML will fail.
+         * We should fail at compile time with an error saying
+         * that XML values are not allowed in top-level result
+         * sets (and thus cannot be bound).
+         */
+        assertCompileError("42Z71", "select x from xTable.t1");
+    }
+
+    /**
+     * Test serialization of the XML values inserted as part
+     * XBindTestSetup processing.  For the documents that are
+     * are larger than 32K, this tests that they can be correctly
+     * read from disk as a stream (instead of just as as string).
+     */
+    public void testXMLSerializeBinding() throws Exception
+    {
+        // Array of expected character counts for every row inserted
+        // into xTable.t1 as part of XBindTestSetup setup.  A "0"
+        // means empty string; a "-1" means we inserted a null.
+        int [] expectedCharCounts =
+            new int [] { 40869, 40375, 1199, 1187, 1218, 954, 22, -1, -1 };
+
+        int rowCount = 0;
+        ResultSet rs = createStatement().executeQuery(
+                "select i, XMLSERIALIZE(X AS CLOB) FROM xTable.t1");
+
+        while (rs.next())
+        {
+            int charCount;
+            java.io.Reader xResult = rs.getCharacterStream(2);
+
+            // Count the number of characters we read back.
+            if (!rs.wasNull())
+            {
+                for (charCount = 0; xResult.read() != -1; charCount++);
+                xResult.close();
+            }
+            else
+                charCount = -1;
+
+            assertEquals("Unexpected serialized character count:",
+                expectedCharCounts[rowCount], charCount);
+
+            rowCount++;
+        }
+
+        assertEquals("Unexpected row count when serializing:",
+            expectedCharCounts.length, rowCount);
+
+        /* Test binding to the XMLSERIALIZE operand.  Since
+         * the operand is an XML value, and since we don't
+         * allow binding to an XML value (which is tested in
+         * testInvalidXMLBindings()), there's nothing more to
+         * to do here.
+         */
+    }
+
+    /**
+     * Run some simple XPath queries against the documents
+     * inserted as part of XBindTestSetup to verify correct
+     * functionality in insertion and XMLEXISTS.  Also test
+     * binding of values into the first XMLEXISTS operator
+     * (should fail).
+     */
+    public void testXMLExistsBinding() throws Exception
+    {
+        /* Test binding to the XMLEXISTS operands.  Binding
+         * of the second (XML) operand is not allowed and was
+         * checked in "testInvalidXMLBindings()" above.  Here we
+         * check binding of the first operand, which should fail
+         * because SQL/XML spec says the first operand must
+         * be a string literal.
+         */
+        assertCompileError("42Z75", 
+                "select i from xTable.t1 where " +
+                "XMLEXISTS (? PASSING BY REF x)");
+
+        // Run some sample queries.
+        existsQuery("//abb", 1);
+        existsQuery("//d50", 1);
+        existsQuery("//person/email", 4);
+        existsQuery("/personnel", 5);
+        existsQuery("//person/@id", 4);
+
+        /* This next one is important because it verifies
+         * that implicit/default values which are defined
+         * in a DTD _are_ actually processed, even though
+         * we don't perform validation.  Thus this next
+         * query _should_ return a match.
+         */
+        existsQuery("//person/@noteTwo", 1);
+    }
+
+    /**
+     * Test binding of values into the first XMLQUERY operand
+     * (should fail).
+     */
+    public void testXMLQueryBinding() throws Exception
+    {
+        /* Binding of the second (XML) operand is not allowed
+         * and is checked as part of "testInvalidXMLBindings()".
+         * Here we check binding of the first operand, which
+         * should fail because SQL/XML spec says the first
+         * operand must be a string literal.
+         */
+        assertCompileError("42Z75", 
+                "select i from xTable.t1 where " +
+                "XMLQUERY (? PASSING BY REF x EMPTY ON EMPTY) " +
+                "is not null");
+    }
+
+    /**
+     * Helper method.  Selects all rows (from xTable.t1) against which
+     * evaluation of the received XPath expression returns a non-empty
+     * sequence.  Evaluates the query using the XMLEXISTS operator and
+     * then verifies that the number of rows matches the expected row
+     * row count.
+     *
+     * @param xPath The XPath expression to evaluate.
+     * @param expectedRows Number of rows for which we expect XMLEXISTS
+     *  to return "true".
+     */
+    private void existsQuery(String xPath, int expectedRows)
+        throws Exception
+    {
+        ResultSet rs = createStatement().executeQuery(
+            "select i from xTable.t1 where " +
+            "xmlexists('" + xPath + "' passing by ref x)");
+
+        JDBC.assertDrainResults(rs, expectedRows);
+    }
+
+    /**
+     * Helper class.  Creates a test table and populates it with data.
+     * That data is then used throughout the various test methods that
+     * are run in XMLBindingTest.
+     */
+    private static class XBindTestSetup extends BaseJDBCTestSetup
+    {
+        public XBindTestSetup(TestSuite tSuite) {
+            super(tSuite);
+        }
+
+        /**
+         * Create the XML table and insert all of the test documents.
+         * Some of the documents are small, others are larger than
+         * 32K (which will test stream processing of XML data).  This
+         * method is called as part of the XBindTestSetup because the
+         * data is used throughout the test methods in XMLBindingTest.
+         * That said, though, this method is itself a test, as well--
+         * namley, it tests that XMLPARSE binding succeeds in all
+         * of the cases where it is expected to succeed.
+         */
+        public void setUp() throws Exception
+        {
+            String tName = "xTable.t1";
+            Connection c = getConnection();
+            c.createStatement().execute("create table " + tName +
+                 "(i int generated always as identity, x xml)");
+
+            // Test parsing of > 32K XML documents.
+            XML.insertFile(c, tName, "x", "wide40k.xml", 1);
+            XML.insertFile(c, tName, "x", "deep40k.xml", 1);
+
+            /* Test parsing of docs that use schemas.  Since DTDs
+             * are stored in "{user.dir}/extin" we have to modify
+             * the XML documents that use DTDs so that they can find
+             * the DTD files.
+             */
+
+            XML.insertFile(c, tName, "x", "xsdDoc.xml", 1);
+            XML.insertDocWithDTD(c, tName, "x",
+                "dtdDoc.xml", "personal.dtd", 1);
+
+            // XMLPARSE is not supposed to validate, so the following
+            // inserts should SUCCEED, even though the documents
+            // don't adhere to their schemas.
+
+            XML.insertFile(c, tName, "x", "xsdDoc_invalid.xml", 1);
+            XML.insertDocWithDTD(c, tName, "x",
+                "dtdDoc_invalid.xml", "personal.dtd", 1);
+
+            // Test simple binding to the XMLPARSE operand.
+
+            PreparedStatement pSt = getConnection().prepareStatement(
+                "insert into xTable.t1(x) values " +
+                "(XMLPARSE (DOCUMENT CAST (? as CLOB) PRESERVE WHITESPACE))");
+
+            // This should work.  Note we check binding via
+            // a character stream method in XML.insertFile().
+
+            pSt.setString(1, "<simple> doc </simple>");
+            pSt.execute();
+
+            // Null should work, too.  Make sure the inserts execute without
+            // error here.  We'll verify the results as part of the testing
+            // for XMLSERIALIZE.
+
+            // Java null.
+            pSt.setString(1, null);
+            pSt.execute();
+
+            // SQL null.
+            pSt.setNull(1, Types.CLOB);
+            pSt.execute();
+            pSt.close();
+            c = null;
+        }
+
+        /**
+         * Just have to drop the table we created in setUp().
+         */
+        public void tearDown() throws Exception
+        {
+            getConnection().createStatement().execute("drop table xTable.t1");
+            super.tearDown();
+        }
+    }
+}

Propchange: db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/XMLBindingTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/XMLTypeAndOpsTest.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/XMLTypeAndOpsTest.java?view=diff&rev=476365&r1=476364&r2=476365
==============================================================================
--- db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/XMLTypeAndOpsTest.java
(original)
+++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/XMLTypeAndOpsTest.java
Fri Nov 17 15:30:51 2006
@@ -82,22 +82,14 @@
         if (!XML.classpathMeetsXMLReqs())
             return suite;
 
-        // First wrap the tests in a TestSetup that sets up / tears down
-        // the "fixture", and run those wrapped tests in embedded mode.
+        /* "false" in the next line means that we will *not* clean the
+         * database before the embedded and client suites.  This ensures
+         * that we do not remove the objects created by XMLTestSetup.
+         */
+        suite.addTest(
+            TestConfiguration.defaultSuite(XMLTypeAndOpsTest.class, false));
 
-        TestSuite helperSuite = new TestSuite("XML Type And Ops -- Embedded\n");
-        helperSuite.addTestSuite(XMLTypeAndOpsTest.class);
-        suite.addTest(new XMLTestSetup(helperSuite));
-
-        // Then wrap the tests in another TestSetup that does the same
-        // thing, but this time run the wrapped tests in client mode.
-
-        helperSuite = new TestSuite("XML Type And Ops -- Derby Client\n");
-        helperSuite.addTest(TestConfiguration.clientServerSuite(
-            XMLTypeAndOpsTest.class));
-        suite.addTest(new XMLTestSetup(helperSuite));
-
-        return suite;
+        return (new XMLTestSetup(suite));
     }
 
     /**

Modified: db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/TestConfiguration.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/TestConfiguration.java?view=diff&rev=476365&r1=476364&r2=476365
==============================================================================
--- db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/TestConfiguration.java
(original)
+++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/TestConfiguration.java
Fri Nov 17 15:30:51 2006
@@ -166,11 +166,34 @@
      */
     public static Test defaultSuite(Class testClass)
     {
-        final TestSuite suite = new TestSuite(suiteName(testClass));
-        
-        suite.addTest(new CleanDatabaseTestSetup(embeddedSuite(testClass)));            
-        suite.addTest(new CleanDatabaseTestSetup(clientServerSuite(testClass)));
- 
+        return defaultSuite(testClass, true);
+    }
+
+    /**
+     * Does the work of "defaultSuite" as defined above.  Takes
+     * a boolean argument to determine whether or not to "clean"
+     * the test database before each suite.  If the resultant
+     * suite is going to be wrapped inside a TestSetup that creates
+     * database objects to be used throughout the tests, then the
+     * cleanDB parameter should be "false" to prevent cleanup of the
+     * database objects that TestSetup created.  For example, see
+     * XMLBindingTest.suite().
+     */
+    public static Test defaultSuite(Class testClass, boolean cleanDB)
+    {
+         final TestSuite suite = new TestSuite(suiteName(testClass));
+         
+        if (cleanDB)
+        {
+            suite.addTest(new CleanDatabaseTestSetup(embeddedSuite(testClass)));
+            suite.addTest(new CleanDatabaseTestSetup(clientServerSuite(testClass)));
+        }
+        else
+        {
+            suite.addTest(embeddedSuite(testClass));
+            suite.addTest(clientServerSuite(testClass));
+        }
+
         return (suite);
     }
     

Modified: db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/XML.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/XML.java?view=diff&rev=476365&r1=476364&r2=476365
==============================================================================
--- db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/XML.java (original)
+++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/junit/XML.java Fri Nov 17 15:30:51
2006
@@ -19,11 +19,18 @@
  */
 package org.apache.derbyTesting.junit;
 
+import java.io.IOException;
 import java.io.PrintWriter;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
+import java.io.InputStreamReader;
 
 import java.lang.reflect.Method;
+import java.security.PrivilegedActionException;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
 
 import java.util.StringTokenizer;
 import java.util.Properties;
@@ -32,7 +39,6 @@
 
 /**
  * XML utility methods for the JUnit tests.
- *
  */
 public class XML {
     
@@ -77,6 +83,13 @@
             = HAVE_XALAN && checkXalanVersion();
 
     /**
+     * The filepath for the directory that holds the XML "helper" files
+     * (i.e. the files to insert and their schema documents).
+     */
+    private static final String HELPER_FILE_LOCATION =
+        "org/apache/derbyTesting/functionTests/tests/lang/xmlTestFiles/";
+
+    /**
      * Return true if the classpath contains JAXP and
      * Xalan classes (this method doesn't care about
      * the particular version of Xalan).
@@ -95,6 +108,129 @@
     public static boolean classpathMeetsXMLReqs()
     {
         return HAVE_JAXP && HAVE_MIN_XALAN;
+    }
+
+    /**
+     * Insert the contents of a file into the received column of
+     * the received table using "setCharacterStream".  Expectation
+     * is that the file is in the directory indicated by 
+     * HELPER_FILE_LOCATION.
+     *
+     * @param conn Connection on which to perform the insert.
+     * @param tableName Table into which we want to insert.
+     * @param colName Column in tableName into which we want to insert.
+     * @param fName Name of the file whose content we want to insert.
+     * @param numRows Number of times we should insert the received
+     *  file's content.
+     */
+    public static void insertFile(Connection conn, String tableName,
+        String colName, String fName, int numRows)
+        throws IOException, SQLException, PrivilegedActionException
+    {
+        // First we have to figure out many chars long the file is.
+
+        fName = HELPER_FILE_LOCATION + fName;
+        java.net.URL xFile = BaseTestCase.getTestResource(fName);
+        Assert.assertNotNull("XML input file missing: " + fName, xFile);
+        
+        int charCount = 0;
+        char [] cA = new char[1024];
+        InputStreamReader reader =
+            new InputStreamReader(BaseTestCase.openTestResource(xFile));
+
+        for (int len = reader.read(cA, 0, cA.length); len != -1;
+            charCount += len, len = reader.read(cA, 0, cA.length));
+
+        reader.close();
+
+        // Now that we know the number of characters, we can insert
+        // using a stream.
+
+        PreparedStatement pSt = conn.prepareStatement(
+            "insert into " + tableName + "(" + colName + ") values " +
+            "(xmlparse(document cast (? as clob) preserve whitespace))");
+
+        for (int i = 0; i < numRows; i++)
+        {
+            reader = new InputStreamReader(
+                BaseTestCase.openTestResource(xFile));
+
+            pSt.setCharacterStream(1, reader, charCount);
+            pSt.execute();
+            reader.close();
+        }
+
+        pSt.close();
+    }
+
+    /**
+     * Insert an XML document into the received column of the received
+     * test table using setString.  This method parallels "insertFiles"
+     * above, except that it should be used for documents that require
+     * a Document Type Definition (DTD).  In that case the location of
+     * the DTD has to be modified _within_ the document so that it can
+     * be found in the running user directory.
+     *
+     * Expectation is that the file to be inserted is in the directory
+     * indicated by HELPER_FILE_LOCATION and that the DTD file has been
+     * copied to the user's running directory (via use of the util
+     * methods in SupportFilesSetup).
+     *
+     * @param conn Connection on which to perform the insert.
+     * @param tableName Table into which we want to insert.
+     * @param colName Column in tableName into which we want to insert.
+     * @param fName Name of the file whose content we want to insert.
+     * @param dtdName Name of the DTD file that the received file uses.
+     * @param numRows Number of times we should insert the received
+     *  file's content.
+     */
+    public static void insertDocWithDTD(Connection conn, String tableName,
+        String colName, String fName, String dtdName, int numRows)
+        throws IOException, SQLException, PrivilegedActionException
+    {
+        // Read the file into memory so we can update it.
+        fName = HELPER_FILE_LOCATION + fName;
+        java.net.URL xFile = BaseTestCase.getTestResource(fName);
+        Assert.assertNotNull("XML input file missing: " + fName, xFile);
+
+        int charCount = 0;
+        char [] cA = new char[1024];
+        StringBuffer sBuf = new StringBuffer();
+        InputStreamReader reader =
+            new InputStreamReader(BaseTestCase.openTestResource(xFile));
+
+        for (int len = reader.read(cA, 0, cA.length); len != -1;
+            charCount += len, len = reader.read(cA, 0, cA.length))
+        {
+            sBuf.append(cA, 0, len);
+        }
+
+        reader.close();
+
+        // Now replace the DTD location.
+
+        java.net.URL dtdURL = SupportFilesSetup.getReadOnlyURL(dtdName);
+        Assert.assertNotNull("DTD file missing: " + dtdName, dtdURL);
+
+        String docAsString = sBuf.toString();
+        int pos = docAsString.indexOf(dtdName);
+        if (pos != -1)
+            sBuf.replace(pos, pos+dtdName.length(), dtdURL.toExternalForm());
+
+        // Now (finally) do the insert using the in-memory document with
+        // the correct DTD location.
+        docAsString = sBuf.toString();
+        PreparedStatement pSt = conn.prepareStatement(
+            "insert into " + tableName + "(" + colName + ") values " +
+            "(xmlparse(document cast (? as clob) preserve whitespace))");
+
+        for (int i = 0; i < numRows; i++)
+        {
+            pSt.setString(1, docAsString);
+            pSt.execute();
+        }
+
+        pSt.close();
     }
 
     /**



Mime
View raw message