jackrabbit-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From thom...@apache.org
Subject svn commit: r752458 - in /jackrabbit/trunk/jackrabbit-core/src: main/java/org/apache/jackrabbit/core/xml/ test/java/org/apache/jackrabbit/core/data/
Date Wed, 11 Mar 2009 14:40:39 GMT
Author: thomasm
Date: Wed Mar 11 14:40:39 2009
New Revision: 752458

URL: http://svn.apache.org/viewvc?rev=752458&view=rev
Log:
JCR-2007 Importing strings with special characters fails

Modified:
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/BufferedStringValue.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/SysViewImportHandler.java
    jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/data/ExportImportTest.java

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/BufferedStringValue.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/BufferedStringValue.java?rev=752458&r1=752457&r2=752458&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/BufferedStringValue.java
(original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/BufferedStringValue.java
Wed Mar 11 14:40:39 2009
@@ -29,15 +29,20 @@
 import javax.jcr.RepositoryException;
 import javax.jcr.Value;
 import javax.jcr.ValueFormatException;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
+import java.io.FileInputStream;
 import java.io.FileOutputStream;
-import java.io.FileReader;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.InputStreamReader;
 import java.io.OutputStreamWriter;
 import java.io.Reader;
 import java.io.StringReader;
+import java.io.StringWriter;
 import java.io.Writer;
 
 /**
@@ -54,42 +59,46 @@
     private static Logger log = LoggerFactory.getLogger(BufferedStringValue.class);
 
     /**
-     * max size for buffering data in memory
+     * The maximum size for buffering data in memory.
      */
     private static final int MAX_BUFFER_SIZE = 0x10000;
+    
     /**
-     * size of increment if capacity buffer needs to be enlarged
-     */
-    private static final int BUFFER_INCREMENT = 0x2000;
-    /**
-     * in-memory buffer
+     * The in-memory buffer.
      */
-    private char[] buffer;
+    private StringWriter buffer;
+    
     /**
-     * current position within buffer (size of actual data in buffer)
+     * The number of characters written so far.
+     * If the in-memory buffer is used, this is position within buffer (size of actual data
in buffer)
      */
-    private int bufferPos;
+    private long length;
 
     /**
-     * backing temporary file created when size of data exceeds
-     * MAX_BUFFER_SIZE
+     * Backing temporary file created when size of data exceeds
+     * MAX_BUFFER_SIZE.
      */
     private File tmpFile;
+    
     /**
-     * writer used to write to tmpFile; writer & tmpFile are always
-     * instantiated together, i.e. they are either both null or both not null.
+     * Writer used to write to tmpFile.
      */
     private Writer writer;
 
     private final NamePathResolver nsContext;
+    
+    /**
+     * Whether the value is base64 encoded.
+     */
+    private boolean base64;
 
     /**
      * Constructs a new empty <code>BufferedStringValue</code>.
      * @param nsContext
      */
     protected BufferedStringValue(NamePathResolver nsContext) {
-        buffer = new char[0x2000];
-        bufferPos = 0;
+        buffer = new StringWriter();
+        length = 0;
         tmpFile = null;
         writer = null;
         this.nsContext = nsContext;
@@ -102,15 +111,17 @@
      * @throws IOException if an I/O error occurs
      */
     public long length() throws IOException {
-        if (buffer != null) {
-            return bufferPos;
-        } else if (tmpFile != null) {
-            // flush writer first
-            writer.flush();
-            return tmpFile.length();
-        } else {
-            throw new IOException("this instance has already been disposed");
+        return length;
+    }
+    
+    private String retrieveString() throws IOException {
+        String value = retrieve();
+        if (base64) {
+            ByteArrayOutputStream out = new ByteArrayOutputStream();
+            Base64.decode(value, out);
+            value = new String(out.toByteArray(), "UTF-8");
         }
+        return value;
     }
 
     /**
@@ -121,18 +132,18 @@
      */
     public String retrieve() throws IOException {
         if (buffer != null) {
-            return new String(buffer, 0, bufferPos);
+            return buffer.toString();
         } else if (tmpFile != null) {
-            // flush writer first
-            writer.flush();
+            // close writer first
+            writer.close();
             if (tmpFile.length() > Integer.MAX_VALUE) {
                 throw new IOException("size of value is too big, use reader()");
             }
-            StringBuffer sb = new StringBuffer((int) tmpFile.length());
+            StringBuffer sb = new StringBuffer((int) length);
             char[] chunk = new char[0x2000];
-            int read;
-            Reader reader = new FileReader(tmpFile);
+            Reader reader = openReader();
             try {
+                int read;
                 while ((read = reader.read(chunk)) > -1) {
                     sb.append(chunk, 0, read);
                 }
@@ -144,6 +155,11 @@
             throw new IOException("this instance has already been disposed");
         }
     }
+    
+    private Reader openReader() throws IOException {
+        return new InputStreamReader(
+                new BufferedInputStream(new FileInputStream(tmpFile)), "UTF-8");
+    }
 
     /**
      * Returns a <code>Reader</code> for reading the serialized value.
@@ -153,11 +169,11 @@
      */
     public Reader reader() throws IOException {
         if (buffer != null) {
-            return new StringReader(new String(buffer, 0, bufferPos));
+            return new StringReader(retrieve());
         } else if (tmpFile != null) {
-            // flush writer first
-            writer.flush();
-            return new FileReader(tmpFile);
+            // close writer first
+            writer.close();
+            return openReader();
         } else {
             throw new IOException("this instance has already been disposed");
         }
@@ -168,48 +184,32 @@
      *
      * @param chars  the characters to be appended
      * @param start  the index of the first character to append
-     * @param length the number of characters to append
+     * @param len the number of characters to append
      * @throws IOException if an I/O error occurs
      */
-    public void append(char[] chars, int start, int length)
+    public void append(char[] chars, int start, int len)
             throws IOException {
         if (buffer != null) {
-            if (bufferPos + length > MAX_BUFFER_SIZE) {
+            if (this.length + len > MAX_BUFFER_SIZE) {
                 // threshold for keeping data in memory exceeded;
                 // create temp file and spool buffer contents
                 TransientFileFactory fileFactory = TransientFileFactory.getInstance();
                 tmpFile = fileFactory.createTransientFile("txt", null, null);
-                final FileOutputStream fout = new FileOutputStream(tmpFile);
-                writer = new OutputStreamWriter(fout) {
-                    public void flush() throws IOException {
-                        // flush this writer
-                        super.flush();
-                        // force synchronization with underlying file
-                        fout.getFD().sync();
-                    }
-                };
-                writer.write(buffer, 0, bufferPos);
-                writer.write(chars, start, length);
-                // reset fields
+                BufferedOutputStream fout = new BufferedOutputStream(new FileOutputStream(tmpFile));
+                writer = new OutputStreamWriter(fout, "UTF-8");
+                writer.write(buffer.toString());
+                writer.write(chars, start, len);
+                // reset the in-memory buffer
                 buffer = null;
-                bufferPos = 0;
             } else {
-                if (bufferPos + length > buffer.length) {
-                    // reallocate new buffer and spool old buffer contents
-                    int bufferSize =
-                            BUFFER_INCREMENT * (((bufferPos + length) / BUFFER_INCREMENT)
+ 1);
-                    char[] newBuffer = new char[bufferSize];
-                    System.arraycopy(buffer, 0, newBuffer, 0, bufferPos);
-                    buffer = newBuffer;
-                }
-                System.arraycopy(chars, start, buffer, bufferPos, length);
-                bufferPos += length;
+                buffer.write(chars, start, len);
             }
         } else if (tmpFile != null) {
-            writer.write(chars, start, length);
+            writer.write(chars, start, len);
         } else {
             throw new IOException("this instance has already been disposed");
         }
+        length += len;
     }
 
     /**
@@ -262,7 +262,7 @@
                 }
             } else {
                 // all other types
-                return ValueHelper.deserialize(retrieve(), targetType, false, ValueFactoryImpl.getInstance());
+                return ValueHelper.deserialize(retrieveString(), targetType, false, ValueFactoryImpl.getInstance());
             }
         } catch (IOException e) {
             String msg = "failed to retrieve serialized value";
@@ -286,7 +286,7 @@
                     return InternalValue.create(baos.toByteArray());
                 } else {
                     // >= 65kb: deserialize BINARY type
-                    // using Reader and temporay file
+                    // using Reader and temporary file
                     if (InternalValue.USE_DATA_STORE) {
                         Base64ReaderInputStream in = new Base64ReaderInputStream(reader());
                         return InternalValue.createTemporary(in);
@@ -307,13 +307,16 @@
                 // convert serialized value to InternalValue using
                 // current namespace context of xml document
                 return InternalValue.create(ValueHelper.convert(
-                        retrieve(), type, ValueFactoryImpl.getInstance()), nsContext);
+                        retrieveString(), type, ValueFactoryImpl.getInstance()), nsContext);
             }
         } catch (IOException e) {
             throw new RepositoryException("Error accessing property value", e);
         }
     }
 
+    /**
+     * This class converts the text read Converts a base64 reader to an input stream.
+     */
     private static class Base64ReaderInputStream extends InputStream {
 
         private static final int BUFFER_SIZE = 1024;
@@ -361,7 +364,6 @@
     public void dispose() {
         if (buffer != null) {
             buffer = null;
-            bufferPos = 0;
         } else if (tmpFile != null) {
             try {
                 writer.close();
@@ -375,4 +377,14 @@
             log.warn("this instance has already been disposed");
         }
     }
+
+    /**
+     * Whether this value is base64 encoded
+     * 
+     * @param base64 the flag
+     */
+    public void setBase64(boolean base64) {
+        this.base64 = base64;
+    }
+
 }

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/SysViewImportHandler.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/SysViewImportHandler.java?rev=752458&r1=752457&r2=752458&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/SysViewImportHandler.java
(original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/xml/SysViewImportHandler.java
Wed Mar 11 14:40:39 2009
@@ -172,9 +172,9 @@
             }
         } else if (name.equals(NameConstants.SV_VALUE)) {
             // sv:value element
-
-            // reset temp fields
             currentPropValue = new BufferedStringValue(resolver);
+            String xsiType = atts.getValue("xsi:type");
+            currentPropValue.setBase64("xs:base64Binary".equals(xsiType));
         } else {
             throw new SAXException(new InvalidSerializedDataException(
                     "Unexpected element in system view xml document: " + name));
@@ -301,6 +301,9 @@
     }
 
     //--------------------------------------------------------< inner classes >
+    /**
+     * The state of parsing the XML stream.
+     */
     class ImportState {
         /**
          * name of current node
@@ -327,7 +330,7 @@
         /**
          * flag indicating whether startNode() has been called for current node
          */
-        boolean started = false;
+        boolean started;
     }
 
     //-------------------------------------------------------------< private >

Modified: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/data/ExportImportTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/data/ExportImportTest.java?rev=752458&r1=752457&r2=752458&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/data/ExportImportTest.java
(original)
+++ jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/data/ExportImportTest.java
Wed Mar 11 14:40:39 2009
@@ -29,9 +29,75 @@
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
 
+/**
+ * Test importing and exporting large binary and text objects.
+ */
 public class ExportImportTest extends AbstractJCRTest {
 
     /**
+     * Test importing a large text property with Unicode characters larger than 255.
+     */
+    public void testExportImportText() throws RepositoryException {
+        doTestExportImportLargeText("Hello \t\r\n!".toCharArray());
+        doTestExportImportLargeText("World\f\f\f.".toCharArray());
+        doTestExportImportLargeText("Hello\t\n\n.\n".toCharArray());
+        doTestExportImportLargeRandomText(100);
+        doTestExportImportLargeRandomText(10000);
+        doTestExportImportLargeRandomText(100000);
+    }
+    
+    private void doTestExportImportLargeRandomText(int len) throws RepositoryException {
+        char[] chars = new char[len];
+        Random random = new Random(1);
+        // The UCS code values 0xd800-0xdfff (UTF-16 surrogates)
+        // as well as 0xfffe and 0xffff (UCS non-characters)
+        // should not appear in conforming UTF-8 streams.
+        // (String.getBytes("UTF-8") only returns 1 byte for 0xd800-0xdfff)            
+        for (int i = 0; i < chars.length; i++) {
+            chars[i] = (char) random.nextInt(0xd000);
+        }
+        doTestExportImportLargeText(chars);
+    }
+    
+    private void doTestExportImportLargeText(char[] chars) throws RepositoryException {
+        try {
+            Session session = helper.getReadWriteSession();
+            Node root = session.getRootNode();
+            clean(root);
+            Node test = root.addNode("testText");
+            session.save();
+            String s = new String(chars);
+            test.setProperty("text", s);
+            test.save();
+            session.save();
+            ByteArrayOutputStream out = new ByteArrayOutputStream();
+            session.exportSystemView("/testText", out, false, false);
+            byte[] output = out.toByteArray();
+            Node test2 = root.addNode("testText2");
+            Node test3 = root.addNode("testText3");
+            session.save();
+            session.importXML("/testText2", new ByteArrayInputStream(output), ImportUUIDBehavior.IMPORT_UUID_CREATE_NEW);
+            session.save();
+            session.getWorkspace().importXML("/testText3", new ByteArrayInputStream(output),
ImportUUIDBehavior.IMPORT_UUID_CREATE_NEW);
+            test2 = root.getNode("testText2");
+            test2 = test2.getNode("testText");
+            test3 = root.getNode("testText3");
+            test3 = test3.getNode("testText");
+            String s2 = test2.getProperty("text").getString();
+            String s3 = test3.getProperty("text").getString();
+            assertEquals(s.length(), s2.length());
+            assertEquals(s.length(), s3.length());
+            assertEquals(s, s2);
+            assertEquals(s, s3);
+            clean(root);
+        } catch (Exception e) {
+            e.printStackTrace();
+            assertFalse(e.getMessage(), true);
+        }
+    }
+
+    
+    /**
      * Test a node type with a binary default value
      * @throws RepositoryException
      */
@@ -105,6 +171,15 @@
         while (root.hasNode("testBinary3")) {
             root.getNode("testBinary3").remove();
         }
+        while (root.hasNode("testText")) {
+            root.getNode("testText").remove();
+        }
+        while (root.hasNode("testText2")) {
+            root.getNode("testText2").remove();
+        }
+        while (root.hasNode("testText3")) {
+            root.getNode("testText3").remove();
+        }
         root.getSession().save();
     }
 }



Mime
View raw message