[dna-commits] DNA SVN: r764 - in trunk: dna-common/src/main/java/org/jboss/dna/common/util and 5 other directories.

dna-commits at lists.jboss.org dna-commits at lists.jboss.org
Tue Mar 10 11:17:23 EDT 2009


Author: rhauch
Date: 2009-03-10 11:17:23 -0400 (Tue, 10 Mar 2009)
New Revision: 764

Added:
   trunk/dna-common/src/main/java/org/jboss/dna/common/text/XmlValueEncoder.java
   trunk/dna-common/src/test/java/org/jboss/dna/common/text/XmlValueEncoderTest.java
   trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/AbstractJcrExporter.java
   trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrDocumentViewExporter.java
   trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrSystemViewExporter.java
Modified:
   trunk/dna-common/src/main/java/org/jboss/dna/common/util/Base64.java
   trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/DnaBuiltinNodeTypeSource.java
   trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/DnaLexicon.java
   trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrI18n.java
   trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNamespaceRegistry.java
   trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNodeType.java
   trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrSession.java
   trunk/dna-jcr/src/main/resources/org/jboss/dna/jcr/JcrI18n.properties
   trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrTckTest.java
   trunk/dna-jcr/src/test/resources/repositoryJackrabbitTck.xml
Log:
DNA-295 TCK Tests for Session.export* Methods Fail

Applied the "DNA-295_final.patch" and "DNA-295_namespace2.patch" files, and cleaned up a few very minor things (like changed the repositoryJackrabbitTck.xml file to be more DRY by removing the "jcr:primaryTime" attributes since the importer looks at the element name for the primary type).

Added: trunk/dna-common/src/main/java/org/jboss/dna/common/text/XmlValueEncoder.java
===================================================================
--- trunk/dna-common/src/main/java/org/jboss/dna/common/text/XmlValueEncoder.java	                        (rev 0)
+++ trunk/dna-common/src/main/java/org/jboss/dna/common/text/XmlValueEncoder.java	2009-03-10 15:17:23 UTC (rev 764)
@@ -0,0 +1,162 @@
+/*
+ * JBoss DNA (http://www.jboss.org/dna)
+ * See the COPYRIGHT.txt file distributed with this work for information
+ * regarding copyright ownership.  Some portions may be licensed
+ * to Red Hat, Inc. under one or more contributor license agreements.
+ * See the AUTHORS.txt file in the distribution for a full listing of 
+ * individual contributors.
+ *
+ * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
+ * is licensed to you under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ * 
+ * JBoss DNA is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.common.text;
+
+import java.text.CharacterIterator;
+import java.text.StringCharacterIterator;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * An encoder useful for converting text to be used within XML attribute values.
+ * The following translations will be performed:
+ * <table cellspacing="0" cellpadding="1" border="1">
+ * <tr>
+ * <th>Raw (Unencoded)<br/>Character</th>
+ * <th>Translated (Encoded)<br/>Entity</th>
+ * </tr>
+ * <tr>
+ * <td> &amp; </td>
+ * <td> &amp;amp; </td>
+ * </tr>
+ * <tr>
+ * <td> &lt; </td>
+ * <td> &amp;lt; </td>
+ * </tr>
+ * <tr>
+ * <td> &gt; </td>
+ * <td> &amp;gt; </td>
+ * </tr>
+ * <tr>
+ * <td> &quot; </td>
+ * <td> &amp;quot; </td>
+ * </tr>
+ * <tr>
+ * <td> &#039; </td>
+ * <td> &amp;#039; </td>
+ * </tr>
+ * <tr>
+ * <td>All Others</td>
+ * <td>No Translation</td>
+ * </tr>
+ * </table>
+ * </p>
+ */
+public class XmlValueEncoder implements TextEncoder, TextDecoder {
+    
+    private static final Map<String, Character> SPECIAL_ENTITIES;
+    
+    static {
+        SPECIAL_ENTITIES = new HashMap<String, Character>();
+        
+        SPECIAL_ENTITIES.put("quot", '"');
+        SPECIAL_ENTITIES.put("gt", '>');
+        SPECIAL_ENTITIES.put("lt", '<');
+        SPECIAL_ENTITIES.put("amp", '&');
+        
+    }
+    
+    /**
+     * {@inheritDoc}
+     *
+     * @see org.jboss.dna.common.text.TextEncoder#encode(java.lang.String)
+     */
+    public String encode( String text ) {
+        if (text == null) return null;
+        StringBuilder sb = new StringBuilder();
+        CharacterIterator iter = new StringCharacterIterator(text);
+        for (char c = iter.first(); c != CharacterIterator.DONE; c = iter.next()) {
+            switch (c) {
+                case '&':
+                    sb.append("&amp;");
+                    break;
+                case '"':
+                    sb.append("&quot;");
+                    break;
+                case '<':
+                    sb.append("&lt;");
+                    break;
+                case '>':
+                    sb.append("&gt;");
+                    break;
+                case '\'':
+                    sb.append("&#039;");
+                    break;
+                default:
+                    sb.append(c);
+            }
+        }
+        return sb.toString();
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @see org.jboss.dna.common.text.TextDecoder#decode(java.lang.String)
+     */
+    public String decode( String encodedText ) {
+        if (encodedText == null) return null;
+        StringBuilder sb = new StringBuilder();
+        CharacterIterator iter = new StringCharacterIterator(encodedText);
+        for (char c = iter.first(); c != CharacterIterator.DONE; c = iter.next()) {
+            if (c == '&') {
+                int index = iter.getIndex();
+                
+                do {
+                    c = iter.next();
+                }
+                while (c != CharacterIterator.DONE && c != ';');
+
+                // We found a closing semicolon
+                if (c == ';') {
+                    String s = encodedText.substring(index + 1, iter.getIndex());
+                    
+                    if (SPECIAL_ENTITIES.containsKey(s)) {
+                        sb.append(SPECIAL_ENTITIES.get(s));
+                        continue;
+                        
+                    }
+                    
+                    if (s.length() > 0 && s.charAt(0) == '#') {
+                        try {
+                            sb.append((char) Short.parseShort(s.substring(1, s.length())));
+                            continue;
+                        }
+                        catch (NumberFormatException nfe) {
+                            // This is possible in malformed encodings, but let it fall through
+                        }
+                    }
+                }
+                
+                // Malformed encoding, restore state and pass poorly encoded data back
+                c = '&';
+                iter.setIndex(index);                            
+            }
+
+            sb.append(c);
+
+        }
+        return sb.toString();
+    }
+}


Property changes on: trunk/dna-common/src/main/java/org/jboss/dna/common/text/XmlValueEncoder.java
___________________________________________________________________
Name: svn:mime-type
   + text/plain

Modified: trunk/dna-common/src/main/java/org/jboss/dna/common/util/Base64.java
===================================================================
--- trunk/dna-common/src/main/java/org/jboss/dna/common/util/Base64.java	2009-03-09 17:57:18 UTC (rev 763)
+++ trunk/dna-common/src/main/java/org/jboss/dna/common/util/Base64.java	2009-03-10 15:17:23 UTC (rev 764)
@@ -23,6 +23,8 @@
  */
 package org.jboss.dna.common.util;
 
+import java.io.IOException;
+
 /**
  * <p>
  * Encodes and decodes to and from Base64 notation.
@@ -93,6 +95,37 @@
  */
 public class Base64 {
 
+    /* ********  P U B L I C   F I E L D S  ******** */
+
+    /** No options specified. Value is zero. */
+    public final static int NO_OPTIONS = 0;
+
+    /** Specify encoding. */
+    public final static int ENCODE = 1;
+
+    /** Specify decoding. */
+    public final static int DECODE = 0;
+
+    /** Specify that data should be gzip-compressed. */
+    public final static int GZIP = 2;
+
+    /** Don't break lines when encoding (violates strict Base64 specification) */
+    public final static int DONT_BREAK_LINES = 8;
+
+    /**
+     * Encode using Base64-like encoding that is URL- and Filename-safe as described in Section 4 of RFC3548: <a
+     * href="http://www.faqs.org/rfcs/rfc3548.html">http://www.faqs.org/rfcs/rfc3548.html</a>. It is important to note that data
+     * encoded this way is <em>not</em> officially valid Base64, or at the very least should not be called Base64 without also
+     * specifying that is was encoded using the URL- and Filename-safe dialect.
+     */
+    public final static int URL_SAFE = 16;
+
+    /**
+     * Encode using the special "ordered" dialect of Base64 described here: <a
+     * href="http://www.faqs.org/qa/rfcc-1940.html">http://www.faqs.org/qa/rfcc-1940.html</a>.
+     */
+    public final static int ORDERED = 32;
+
     /* ********  P R I V A T E   F I E L D S  ******** */
 
     /** Maximum line length (76) of Base64 output. */
@@ -107,12 +140,15 @@
     /** Preferred encoding. */
     private final static String PREFERRED_ENCODING = "UTF-8";
 
+    // I think I end up not using the BAD_ENCODING indicator.
+    // private final static byte BAD_ENCODING = -9; // Indicates error in encoding
     private final static byte WHITE_SPACE_ENC = -5; // Indicates white space in encoding
     private final static byte EQUALS_SIGN_ENC = -1; // Indicates equals sign in encoding
 
     /* ********  S T A N D A R D   B A S E 6 4   A L P H A B E T  ******** */
 
     /** The 64 valid Base64 values. */
+    // private final static byte[] ALPHABET;
     /* Host platform me be something funny like EBCDIC, so we hardcode these values. */
     private final static byte[] _STANDARD_ALPHABET = {(byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F',
         (byte)'G', (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', (byte)'O', (byte)'P', (byte)'Q',
@@ -158,6 +194,147 @@
     -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9         // Decimal 244 - 255 */
     };
 
+    /* ********  U R L   S A F E   B A S E 6 4   A L P H A B E T  ******** */
+
+    /**
+     * Used in the URL- and Filename-safe dialect described in Section 4 of RFC3548: <a
+     * href="http://www.faqs.org/rfcs/rfc3548.html">http://www.faqs.org/rfcs/rfc3548.html</a>. Notice that the last two bytes
+     * become "hyphen" and "underscore" instead of "plus" and "slash."
+     */
+    private final static byte[] _URL_SAFE_ALPHABET = {(byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F',
+        (byte)'G', (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', (byte)'O', (byte)'P', (byte)'Q',
+        (byte)'R', (byte)'S', (byte)'T', (byte)'U', (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', (byte)'a', (byte)'b',
+        (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m',
+        (byte)'n', (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', (byte)'v', (byte)'w', (byte)'x',
+        (byte)'y', (byte)'z', (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', (byte)'6', (byte)'7', (byte)'8',
+        (byte)'9', (byte)'-', (byte)'_'};
+
+    /**
+     * Used in decoding URL- and Filename-safe dialects of Base64.
+     */
+    private final static byte[] _URL_SAFE_DECODABET = {-9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8
+        -5, -5, // Whitespace: Tab and Linefeed
+        -9, -9, // Decimal 11 - 12
+        -5, // Whitespace: Carriage Return
+        -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26
+        -9, -9, -9, -9, -9, // Decimal 27 - 31
+        -5, // Whitespace: Space
+        -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42
+        -9, // Plus sign at decimal 43
+        -9, // Decimal 44
+        62, // Minus sign at decimal 45
+        -9, // Decimal 46
+        -9, // Slash at decimal 47
+        52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine
+        -9, -9, -9, // Decimal 58 - 60
+        -1, // Equals sign at decimal 61
+        -9, -9, -9, // Decimal 62 - 64
+        0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N'
+        14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z'
+        -9, -9, -9, -9, // Decimal 91 - 94
+        63, // Underscore at decimal 95
+        -9, // Decimal 96
+        26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm'
+        39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z'
+        -9, -9, -9, -9 // Decimal 123 - 126
+    /*,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 127 - 139
+    -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 140 - 152
+    -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 153 - 165
+    -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 166 - 178
+    -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 179 - 191
+    -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 192 - 204
+    -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 205 - 217
+    -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 218 - 230
+    -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 231 - 243
+    -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9         // Decimal 244 - 255 */
+    };
+
+    /* ********  O R D E R E D   B A S E 6 4   A L P H A B E T  ******** */
+
+    /**
+     * I don't get the point of this technique, but it is described here: <a
+     * href="http://www.faqs.org/qa/rfcc-1940.html">http://www.faqs.org/qa/rfcc-1940.html</a>.
+     */
+    private final static byte[] _ORDERED_ALPHABET = {(byte)'-', (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5',
+        (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G',
+        (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', (byte)'O', (byte)'P', (byte)'Q', (byte)'R',
+        (byte)'S', (byte)'T', (byte)'U', (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', (byte)'_', (byte)'a', (byte)'b',
+        (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m',
+        (byte)'n', (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', (byte)'v', (byte)'w', (byte)'x',
+        (byte)'y', (byte)'z'};
+
+    /**
+     * Used in decoding the "ordered" dialect of Base64.
+     */
+    private final static byte[] _ORDERED_DECODABET = {-9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8
+        -5, -5, // Whitespace: Tab and Linefeed
+        -9, -9, // Decimal 11 - 12
+        -5, // Whitespace: Carriage Return
+        -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26
+        -9, -9, -9, -9, -9, // Decimal 27 - 31
+        -5, // Whitespace: Space
+        -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42
+        -9, // Plus sign at decimal 43
+        -9, // Decimal 44
+        0, // Minus sign at decimal 45
+        -9, // Decimal 46
+        -9, // Slash at decimal 47
+        1, 2, 3, 4, 5, 6, 7, 8, 9, 10, // Numbers zero through nine
+        -9, -9, -9, // Decimal 58 - 60
+        -1, // Equals sign at decimal 61
+        -9, -9, -9, // Decimal 62 - 64
+        11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, // Letters 'A' through 'M'
+        24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, // Letters 'N' through 'Z'
+        -9, -9, -9, -9, // Decimal 91 - 94
+        37, // Underscore at decimal 95
+        -9, // Decimal 96
+        38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, // Letters 'a' through 'm'
+        51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, // Letters 'n' through 'z'
+        -9, -9, -9, -9 // Decimal 123 - 126
+    /*,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 127 - 139
+      -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 140 - 152
+      -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 153 - 165
+      -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 166 - 178
+      -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 179 - 191
+      -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 192 - 204
+      -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 205 - 217
+      -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 218 - 230
+      -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 231 - 243
+      -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9         // Decimal 244 - 255 */
+    };
+
+    /* ********  D E T E R M I N E   W H I C H   A L H A B E T  ******** */
+
+    /**
+     * Returns one of the _SOMETHING_ALPHABET byte arrays depending on the options specified. It's possible, though silly, to
+     * specify ORDERED and URLSAFE in which case one of them will be picked, though there is no guarantee as to which one will be
+     * picked.
+     * 
+     * @param options The options to use in this operation
+     * @return the appropriate alphabet
+     */
+    private final static byte[] getAlphabet( int options ) {
+        if ((options & URL_SAFE) == URL_SAFE) return _URL_SAFE_ALPHABET;
+        else if ((options & ORDERED) == ORDERED) return _ORDERED_ALPHABET;
+        else return _STANDARD_ALPHABET;
+
+    } // end getAlphabet
+
+    /**
+     * Returns one of the _SOMETHING_DECODABET byte arrays depending on the options specified. It's possible, though silly, to
+     * specify ORDERED and URL_SAFE in which case one of them will be picked, though there is no guarantee as to which one will be
+     * picked.
+     * 
+     * @param options The options to use in this operation
+     * @return the appropriate decodabets
+     */
+    private final static byte[] getDecodabet( int options ) {
+        if ((options & URL_SAFE) == URL_SAFE) return _URL_SAFE_DECODABET;
+        else if ((options & ORDERED) == ORDERED) return _ORDERED_DECODABET;
+        else return _STANDARD_DECODABET;
+
+    } // end getAlphabet
+
     /** Defeats instantiation. */
     private Base64() {
     }
@@ -165,6 +342,26 @@
     /* ********  E N C O D I N G   M E T H O D S  ******** */
 
     /**
+     * Encodes up to the first three bytes of array <var>threeBytes</var> and returns a four-byte array in Base64 notation. The
+     * actual number of significant bytes in your array is given by <var>numSigBytes</var>. The array <var>threeBytes</var> needs
+     * only be as big as <var>numSigBytes</var>. Code can reuse a byte array by passing a four-byte array as <var>b4</var>.
+     * 
+     * @param b4 A reusable byte array to reduce array instantiation
+     * @param threeBytes the array to convert
+     * @param numSigBytes the number of significant bytes in your array
+     * @param options The options to use in this operation
+     * @return four byte array in Base64 notation.
+     * @since 1.5.1
+     */
+    private static byte[] encode3to4( byte[] b4,
+                                      byte[] threeBytes,
+                                      int numSigBytes,
+                                      int options ) {
+        encode3to4(threeBytes, 0, numSigBytes, b4, 0, options);
+        return b4;
+    } // end encode3to4
+
+    /**
      * <p>
      * Encodes up to three bytes of the array <var>source</var> and writes the resulting four Base64 bytes to
      * <var>destination</var>. The source and destination arrays can be manipulated anywhere along their length by specifying
@@ -181,6 +378,7 @@
      * @param numSigBytes the number of significant bytes in your array
      * @param destination the array to hold the conversion
      * @param destOffset the index where output will be put
+     * @param options The options to use in this operation
      * @return the <var>destination</var> array
      * @since 1.3
      */
@@ -188,8 +386,9 @@
                                       int srcOffset,
                                       int numSigBytes,
                                       byte[] destination,
-                                      int destOffset ) {
-        byte[] ALPHABET = _STANDARD_ALPHABET;
+                                      int destOffset,
+                                      int options ) {
+        byte[] ALPHABET = getAlphabet(options);
 
         // 1 2 3
         // 01234567890123456789012345678901 Bit position
@@ -234,50 +433,268 @@
     } // end encode3to4
 
     /**
-     * Encodes a byte array into Base64 notation. Does not GZip-compress data.
+     * Serializes an object and returns the Base64-encoded version of that serialized object. If the object cannot be serialized
+     * or there is another error, the method will return <tt>null</tt>. The object is not GZip-compressed before being encoded.
      * 
-     * @param source The data to convert
-     * @return the encoded data
+     * @param serializableObject The object to encode
+     * @return The Base64-encoded object
      * @since 1.4
      */
-    public static String encodeBytes( byte[] source ) {
-        // Convert option to boolean in way that code likes it.
-        boolean breakLines = false;
-        int len = source.length;
-        int len43 = len * 4 / 3;
-        byte[] outBuff = new byte[(len43) // Main 4:3
-                                  + ((len % 3) > 0 ? 4 : 0) // Account for padding
-                                  + (breakLines ? (len43 / MAX_LINE_LENGTH) : 0)]; // New lines
-        int d = 0;
-        int e = 0;
-        int len2 = len - 2;
-        int lineLength = 0;
-        for (; d < len2; d += 3, e += 4) {
-            encode3to4(source, d, 3, outBuff, e);
+    public static String encodeObject( java.io.Serializable serializableObject ) {
+        return encodeObject(serializableObject, NO_OPTIONS);
+    } // end encodeObject
 
-            lineLength += 4;
-            if (breakLines && lineLength == MAX_LINE_LENGTH) {
-                outBuff[e + 4] = NEW_LINE;
-                e++;
-                lineLength = 0;
-            } // end if: end of line
-        } // en dfor: each piece of array
+    /**
+     * Serializes an object and returns the Base64-encoded version of that serialized object. If the object cannot be serialized
+     * or there is another error, the method will return <tt>null</tt>.
+     * <p>
+     * Valid options:
+     * 
+     * <pre>
+     *   GZIP: gzip-compresses object before encoding it.
+     *   DONT_BREAK_LINES: don't break lines at 76 characters
+     *     &lt;i&gt;Note: Technically, this makes your encoding non-compliant.&lt;/i&gt;
+     * </pre>
+     * <p>
+     * Example: <code>encodeObject( myObj, Base64.GZIP )</code> or
+     * <p>
+     * Example: <code>encodeObject( myObj, Base64.GZIP | Base64.DONT_BREAK_LINES )</code>
+     * 
+     * @param serializableObject The object to encode
+     * @param options Specified options
+     * @return The Base64-encoded object
+     * @see Base64#GZIP
+     * @see Base64#DONT_BREAK_LINES
+     * @since 2.0
+     */
+    public static String encodeObject( java.io.Serializable serializableObject,
+                                       int options ) {
+        // Streams
+        java.io.ByteArrayOutputStream baos = null;
+        java.io.OutputStream b64os = null;
+        java.io.ObjectOutputStream oos = null;
+        java.util.zip.GZIPOutputStream gzos = null;
 
-        if (d < len) {
-            encode3to4(source, d, len - d, outBuff, e);
-            e += 4;
-        } // end if: some padding needed
+        // Isolate options
+        int gzip = (options & GZIP);
+        int dontBreakLines = (options & DONT_BREAK_LINES);
 
+        try {
+            // ObjectOutputStream -> (GZIP) -> Base64 -> ByteArrayOutputStream
+            baos = new java.io.ByteArrayOutputStream();
+            b64os = new Base64.OutputStream(baos, ENCODE | options);
+
+            // GZip?
+            if (gzip == GZIP) {
+                gzos = new java.util.zip.GZIPOutputStream(b64os);
+                oos = new java.io.ObjectOutputStream(gzos);
+            } // end if: gzip
+            else oos = new java.io.ObjectOutputStream(b64os);
+
+            oos.writeObject(serializableObject);
+        } // end try
+        catch (IOException e) {
+            e.printStackTrace();
+            return null;
+        } // end catch
+        finally {
+            try {
+                oos.close();
+            } catch (Exception e) {
+            }
+            try {
+                gzos.close();
+            } catch (Exception e) {
+            }
+            try {
+                b64os.close();
+            } catch (Exception e) {
+            }
+            try {
+                baos.close();
+            } catch (Exception e) {
+            }
+        } // end finally
+
         // Return value according to relevant encoding.
         try {
-            return new String(outBuff, 0, e, PREFERRED_ENCODING);
+            return new String(baos.toByteArray(), PREFERRED_ENCODING);
         } // end try
         catch (java.io.UnsupportedEncodingException uue) {
-            return new String(outBuff, 0, e);
+            return new String(baos.toByteArray());
         } // end catch
 
-    } // end else: don't compress
+    } // end encode
 
+    /**
+     * Encodes a byte array into Base64 notation. Does not GZip-compress data.
+     * 
+     * @param source The data to convert
+     * @return the encoded bytes
+     * @since 1.4
+     */
+    public static String encodeBytes( byte[] source ) {
+        return encodeBytes(source, 0, source.length, NO_OPTIONS);
+    } // end encodeBytes
+
+    /**
+     * Encodes a byte array into Base64 notation.
+     * <p>
+     * Valid options:
+     * 
+     * <pre>
+     *   GZIP: gzip-compresses object before encoding it.
+     *   DONT_BREAK_LINES: don't break lines at 76 characters
+     *     &lt;i&gt;Note: Technically, this makes your encoding non-compliant.&lt;/i&gt;
+     * </pre>
+     * <p>
+     * Example: <code>encodeBytes( myData, Base64.GZIP )</code> or
+     * <p>
+     * Example: <code>encodeBytes( myData, Base64.GZIP | Base64.DONT_BREAK_LINES )</code>
+     * 
+     * @param source The data to convert
+     * @param options Specified options
+     * @return the encoded bytes
+     * @see Base64#GZIP
+     * @see Base64#DONT_BREAK_LINES
+     * @since 2.0
+     */
+    public static String encodeBytes( byte[] source,
+                                      int options ) {
+        return encodeBytes(source, 0, source.length, options);
+    } // end encodeBytes
+
+    /**
+     * Encodes a byte array into Base64 notation. Does not GZip-compress data.
+     * 
+     * @param source The data to convert
+     * @param off Offset in array where conversion should begin
+     * @param len Length of data
+     * @return the encoded bytes
+     * @since 1.4
+     */
+    public static String encodeBytes( byte[] source,
+                                      int off,
+                                      int len ) {
+        return encodeBytes(source, off, len, NO_OPTIONS);
+    } // end encodeBytes
+
+    /**
+     * Encodes a byte array into Base64 notation.
+     * <p>
+     * Valid options:
+     * 
+     * <pre>
+     *   GZIP: gzip-compresses object before encoding it.
+     *   DONT_BREAK_LINES: don't break lines at 76 characters
+     *     &lt;i&gt;Note: Technically, this makes your encoding non-compliant.&lt;/i&gt;
+     * </pre>
+     * <p>
+     * Example: <code>encodeBytes( myData, Base64.GZIP )</code> or
+     * <p>
+     * Example: <code>encodeBytes( myData, Base64.GZIP | Base64.DONT_BREAK_LINES )</code>
+     * 
+     * @param source The data to convert
+     * @param off Offset in array where conversion should begin
+     * @param len Length of data to convert
+     * @param options Specified options- the alphabet type is pulled from this (standard, url-safe, ordered)
+     * @return the encoded bytes
+     * @see Base64#GZIP
+     * @see Base64#DONT_BREAK_LINES
+     * @since 2.0
+     */
+    public static String encodeBytes( byte[] source,
+                                      int off,
+                                      int len,
+                                      int options ) {
+        // Isolate options
+        int dontBreakLines = (options & DONT_BREAK_LINES);
+        int gzip = (options & GZIP);
+
+        // Compress?
+        if (gzip == GZIP) {
+            java.io.ByteArrayOutputStream baos = null;
+            java.util.zip.GZIPOutputStream gzos = null;
+            Base64.OutputStream b64os = null;
+
+            try {
+                // GZip -> Base64 -> ByteArray
+                baos = new java.io.ByteArrayOutputStream();
+                b64os = new Base64.OutputStream(baos, ENCODE | options);
+                gzos = new java.util.zip.GZIPOutputStream(b64os);
+
+                gzos.write(source, off, len);
+                gzos.close();
+            } // end try
+            catch (IOException e) {
+                e.printStackTrace();
+                return null;
+            } // end catch
+            finally {
+                try {
+                    gzos.close();
+                } catch (Exception e) {
+                }
+                try {
+                    b64os.close();
+                } catch (Exception e) {
+                }
+                try {
+                    baos.close();
+                } catch (Exception e) {
+                }
+            } // end finally
+
+            // Return value according to relevant encoding.
+            try {
+                return new String(baos.toByteArray(), PREFERRED_ENCODING);
+            } // end try
+            catch (java.io.UnsupportedEncodingException uue) {
+                return new String(baos.toByteArray());
+            } // end catch
+        } // end if: compress
+
+        // Else, don't compress. Better not to use streams at all then.
+        else {
+            // Convert option to boolean in way that code likes it.
+            boolean breakLines = dontBreakLines == 0;
+
+            int len43 = len * 4 / 3;
+            byte[] outBuff = new byte[(len43) // Main 4:3
+                                      + ((len % 3) > 0 ? 4 : 0) // Account for padding
+                                      + (breakLines ? (len43 / MAX_LINE_LENGTH) : 0)]; // New lines
+            int d = 0;
+            int e = 0;
+            int len2 = len - 2;
+            int lineLength = 0;
+            for (; d < len2; d += 3, e += 4) {
+                encode3to4(source, d + off, 3, outBuff, e, options);
+
+                lineLength += 4;
+                if (breakLines && lineLength == MAX_LINE_LENGTH) {
+                    outBuff[e + 4] = NEW_LINE;
+                    e++;
+                    lineLength = 0;
+                } // end if: end of line
+            } // en dfor: each piece of array
+
+            if (d < len) {
+                encode3to4(source, d + off, len - d, outBuff, e, options);
+                e += 4;
+            } // end if: some padding needed
+
+            // Return value according to relevant encoding.
+            try {
+                return new String(outBuff, 0, e, PREFERRED_ENCODING);
+            } // end try
+            catch (java.io.UnsupportedEncodingException uue) {
+                return new String(outBuff, 0, e);
+            } // end catch
+
+        } // end else: don't compress
+
+    } // end encodeBytes
+
     /* ********  D E C O D I N G   M E T H O D S  ******** */
 
     /**
@@ -293,15 +710,17 @@
      * @param source the array to convert
      * @param srcOffset the index where conversion begins
      * @param destination the array to hold the conversion
-     * @param destOffset destination offset
+     * @param destOffset the index where output will be put
+     * @param options alphabet type is pulled from this (standard, url-safe, ordered)
      * @return the number of decoded bytes converted
      * @since 1.3
      */
     private static int decode4to3( byte[] source,
                                    int srcOffset,
                                    byte[] destination,
-                                   int destOffset ) {
-        byte[] DECODABET = _STANDARD_DECODABET;
+                                   int destOffset,
+                                   int options ) {
+        byte[] DECODABET = getDecodabet(options);
 
         // Example: Dk==
         if (source[srcOffset + 2] == EQUALS_SIGN) {
@@ -330,44 +749,49 @@
 
         // Example: DkLE
         else {
-            // Two ways to do the same thing. Don't know which way I like best.
-            // int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 )
-            // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 )
-            // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 )
-            // | ( ( DECODABET[ source[ srcOffset + 3 ] ] << 24 ) >>> 24 );
-            int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18) | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12)
-                          | ((DECODABET[source[srcOffset + 2]] & 0xFF) << 6) | ((DECODABET[source[srcOffset + 3]] & 0xFF));
+            try {
+                // Two ways to do the same thing. Don't know which way I like best.
+                // int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 )
+                // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 )
+                // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 )
+                // | ( ( DECODABET[ source[ srcOffset + 3 ] ] << 24 ) >>> 24 );
+                int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18) | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12)
+                              | ((DECODABET[source[srcOffset + 2]] & 0xFF) << 6) | ((DECODABET[source[srcOffset + 3]] & 0xFF));
 
-            destination[destOffset] = (byte)(outBuff >> 16);
-            destination[destOffset + 1] = (byte)(outBuff >> 8);
-            destination[destOffset + 2] = (byte)(outBuff);
+                destination[destOffset] = (byte)(outBuff >> 16);
+                destination[destOffset + 1] = (byte)(outBuff >> 8);
+                destination[destOffset + 2] = (byte)(outBuff);
 
-            return 3;
+                return 3;
+            } catch (Exception e) {
+                System.out.println("" + source[srcOffset] + ": " + (DECODABET[source[srcOffset]]));
+                System.out.println("" + source[srcOffset + 1] + ": " + (DECODABET[source[srcOffset + 1]]));
+                System.out.println("" + source[srcOffset + 2] + ": " + (DECODABET[source[srcOffset + 2]]));
+                System.out.println("" + source[srcOffset + 3] + ": " + (DECODABET[source[srcOffset + 3]]));
+                return -1;
+            } // end catch
         }
     } // end decodeToBytes
 
     /**
-     * Decodes data from Base64 notation.
+     * Very low-level access to decoding ASCII characters in the form of a byte array. Does not support automatically gunzipping
+     * or any other "fancy" features.
      * 
-     * @param s the string to decode
-     * @return the decoded data
-     * @since 1.4
+     * @param source The Base64 encoded data
+     * @param off The offset of where to begin decoding
+     * @param len The length of characters to decode
+     * @param options The options to use in this operation
+     * @return decoded data
+     * @since 1.3
      */
-    public static byte[] decode( String s ) {
-        byte[] source;
-        try {
-            source = s.getBytes(PREFERRED_ENCODING);
-        } // end try
-        catch (java.io.UnsupportedEncodingException uee) {
-            source = s.getBytes();
-        } // end catch
-        // </change>
-        if (source.length % 4 != 0) {
-            throw new IllegalArgumentException("Source bytes are not valid"); //$NON-NLS-1$
-        }
-        byte[] DECODABET = _STANDARD_DECODABET;
-        int len = source.length;
-        byte[] outBuff = new byte[len * 3 / 4]; // Upper limit on size of output
+    public static byte[] decode( byte[] source,
+                                 int off,
+                                 int len,
+                                 int options ) {
+        byte[] DECODABET = getDecodabet(options);
+
+        int len34 = len * 3 / 4;
+        byte[] outBuff = new byte[len34]; // Upper limit on size of output
         int outBuffPosn = 0;
 
         byte[] b4 = new byte[4];
@@ -375,7 +799,7 @@
         int i = 0;
         byte sbiCrop = 0;
         byte sbiDecode = 0;
-        for (i = 0; i < len; i++) {
+        for (i = off; i < off + len; i++) {
             sbiCrop = (byte)(source[i] & 0x7f); // Only the low seven bits
             sbiDecode = DECODABET[sbiCrop];
 
@@ -384,7 +808,7 @@
                 if (sbiDecode >= EQUALS_SIGN_ENC) {
                     b4[b4Posn++] = sbiCrop;
                     if (b4Posn > 3) {
-                        outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn);
+                        outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn, options);
                         b4Posn = 0;
 
                         // If that was the equals sign, break out of 'for' loop
@@ -395,7 +819,8 @@
 
             } // end if: white space, equals sign or better
             else {
-                throw new IllegalArgumentException("Source bytes are not valid"); //$NON-NLS-1$
+                System.err.println("Bad Base64 input character at " + i + ": " + source[i] + "(decimal)");
+                return null;
             } // end else:
         } // each input character
 
@@ -403,4 +828,763 @@
         System.arraycopy(outBuff, 0, out, 0, outBuffPosn);
         return out;
     } // end decode
-}
+
+    /**
+     * Decodes data from Base64 notation, automatically detecting gzip-compressed data and decompressing it.
+     * 
+     * @param s the string to decode
+     * @return the decoded data
+     * @since 1.4
+     */
+    public static byte[] decode( String s ) {
+        return decode(s, NO_OPTIONS);
+    }
+
+    /**
+     * Decodes data from Base64 notation, automatically detecting gzip-compressed data and decompressing it.
+     * 
+     * @param s the string to decode
+     * @param options encode options such as URL_SAFE
+     * @return the decoded data
+     * @since 1.4
+     */
+    public static byte[] decode( String s,
+                                 int options ) {
+        byte[] bytes;
+        try {
+            bytes = s.getBytes(PREFERRED_ENCODING);
+        } // end try
+        catch (java.io.UnsupportedEncodingException uee) {
+            bytes = s.getBytes();
+        } // end catch
+        // </change>
+
+        // Decode
+        bytes = decode(bytes, 0, bytes.length, options);
+
+        // Check to see if it's gzip-compressed
+        // GZIP Magic Two-Byte Number: 0x8b1f (35615)
+        if (bytes != null && bytes.length >= 4) {
+
+            int head = ((int)bytes[0] & 0xff) | ((bytes[1] << 8) & 0xff00);
+            if (java.util.zip.GZIPInputStream.GZIP_MAGIC == head) {
+                java.io.ByteArrayInputStream bais = null;
+                java.util.zip.GZIPInputStream gzis = null;
+                java.io.ByteArrayOutputStream baos = null;
+                byte[] buffer = new byte[2048];
+                int length = 0;
+
+                try {
+                    baos = new java.io.ByteArrayOutputStream();
+                    bais = new java.io.ByteArrayInputStream(bytes);
+                    gzis = new java.util.zip.GZIPInputStream(bais);
+
+                    while ((length = gzis.read(buffer)) >= 0) {
+                        baos.write(buffer, 0, length);
+                    } // end while: reading input
+
+                    // No error? Get new bytes.
+                    bytes = baos.toByteArray();
+
+                } // end try
+                catch (IOException e) {
+                    // Just return originally-decoded bytes
+                } // end catch
+                finally {
+                    try {
+                        baos.close();
+                    } catch (Exception e) {
+                    }
+                    try {
+                        gzis.close();
+                    } catch (Exception e) {
+                    }
+                    try {
+                        bais.close();
+                    } catch (Exception e) {
+                    }
+                } // end finally
+
+            } // end if: gzipped
+        } // end if: bytes.length >= 2
+
+        return bytes;
+    } // end decode
+
+    /**
+     * Attempts to decode Base64 data and deserialize a Java Object within. Returns <tt>null</tt> if there was an error.
+     * 
+     * @param encodedObject The Base64 data to decode
+     * @return The decoded and deserialized object
+     * @since 1.5
+     */
+    public static Object decodeToObject( String encodedObject ) {
+        // Decode and gunzip if necessary
+        byte[] objBytes = decode(encodedObject);
+
+        java.io.ByteArrayInputStream bais = null;
+        java.io.ObjectInputStream ois = null;
+        Object obj = null;
+
+        try {
+            bais = new java.io.ByteArrayInputStream(objBytes);
+            ois = new java.io.ObjectInputStream(bais);
+
+            obj = ois.readObject();
+        } // end try
+        catch (IOException e) {
+            e.printStackTrace();
+            obj = null;
+        } // end catch
+        catch (java.lang.ClassNotFoundException e) {
+            e.printStackTrace();
+            obj = null;
+        } // end catch
+        finally {
+            try {
+                bais.close();
+            } catch (Exception e) {
+            }
+            try {
+                ois.close();
+            } catch (Exception e) {
+            }
+        } // end finally
+
+        return obj;
+    } // end decodeObject
+
+    /**
+     * Convenience method for encoding data to a file.
+     * 
+     * @param dataToEncode byte array of data to encode in base64 form
+     * @param filename Filename for saving encoded data
+     * @return <tt>true</tt> if successful, <tt>false</tt> otherwise
+     * @since 2.1
+     */
+    public static boolean encodeToFile( byte[] dataToEncode,
+                                        String filename ) {
+        boolean success = false;
+        Base64.OutputStream bos = null;
+        try {
+            bos = new Base64.OutputStream(new java.io.FileOutputStream(filename), Base64.ENCODE);
+            bos.write(dataToEncode);
+            success = true;
+        } // end try
+        catch (IOException e) {
+
+            success = false;
+        } // end catch: IOException
+        finally {
+            try {
+                bos.close();
+            } catch (Exception e) {
+            }
+        } // end finally
+
+        return success;
+    } // end encodeToFile
+
+    /**
+     * Convenience method for decoding data to a file.
+     * 
+     * @param dataToDecode Base64-encoded data as a string
+     * @param filename Filename for saving decoded data
+     * @return <tt>true</tt> if successful, <tt>false</tt> otherwise
+     * @since 2.1
+     */
+    public static boolean decodeToFile( String dataToDecode,
+                                        String filename ) {
+        boolean success = false;
+        Base64.OutputStream bos = null;
+        try {
+            bos = new Base64.OutputStream(new java.io.FileOutputStream(filename), Base64.DECODE);
+            bos.write(dataToDecode.getBytes(PREFERRED_ENCODING));
+            success = true;
+        } // end try
+        catch (IOException e) {
+            success = false;
+        } // end catch: IOException
+        finally {
+            try {
+                bos.close();
+            } catch (Exception e) {
+            }
+        } // end finally
+
+        return success;
+    } // end decodeToFile
+
+    /**
+     * Convenience method for reading a base64-encoded file and decoding it.
+     * 
+     * @param filename Filename for reading encoded data
+     * @return decoded byte array or null if unsuccessful
+     * @since 2.1
+     */
+    public static byte[] decodeFromFile( String filename ) {
+        byte[] decodedData = null;
+        Base64.InputStream bis = null;
+        try {
+            // Set up some useful variables
+            java.io.File file = new java.io.File(filename);
+            byte[] buffer = null;
+            int length = 0;
+            int numBytes = 0;
+
+            // Check for size of file
+            if (file.length() > Integer.MAX_VALUE) {
+                System.err.println("File is too big for this convenience method (" + file.length() + " bytes).");
+                return null;
+            } // end if: file too big for int index
+            buffer = new byte[(int)file.length()];
+
+            // Open a stream
+            bis = new Base64.InputStream(new java.io.BufferedInputStream(new java.io.FileInputStream(file)), Base64.DECODE);
+
+            // Read until done
+            while ((numBytes = bis.read(buffer, length, 4096)) >= 0)
+                length += numBytes;
+
+            // Save in a variable to return
+            decodedData = new byte[length];
+            System.arraycopy(buffer, 0, decodedData, 0, length);
+
+        } // end try
+        catch (IOException e) {
+            System.err.println("Error decoding from file " + filename);
+        } // end catch: IOException
+        finally {
+            try {
+                bis.close();
+            } catch (Exception e) {
+            }
+        } // end finally
+
+        return decodedData;
+    } // end decodeFromFile
+
+    /**
+     * Convenience method for reading a binary file and base64-encoding it.
+     * 
+     * @param filename Filename for reading binary data
+     * @return base64-encoded string or null if unsuccessful
+     * @since 2.1
+     */
+    public static String encodeFromFile( String filename ) {
+        String encodedData = null;
+        Base64.InputStream bis = null;
+        try {
+            // Set up some useful variables
+            java.io.File file = new java.io.File(filename);
+            byte[] buffer = new byte[Math.max((int)(file.length() * 1.4), 40)]; // Need max() for math on small files (v2.2.1)
+            int length = 0;
+            int numBytes = 0;
+
+            // Open a stream
+            bis = new Base64.InputStream(new java.io.BufferedInputStream(new java.io.FileInputStream(file)), Base64.ENCODE);
+
+            // Read until done
+            while ((numBytes = bis.read(buffer, length, 4096)) >= 0)
+                length += numBytes;
+
+            // Save in a variable to return
+            encodedData = new String(buffer, 0, length, Base64.PREFERRED_ENCODING);
+
+        } // end try
+        catch (IOException e) {
+            System.err.println("Error encoding from file " + filename);
+        } // end catch: IOException
+        finally {
+            try {
+                bis.close();
+            } catch (Exception e) {
+            }
+        } // end finally
+
+        return encodedData;
+    } // end encodeFromFile
+
+    /**
+     * Reads <tt>infile</tt> and encodes it to <tt>outfile</tt>.
+     * 
+     * @param infile Input file
+     * @param outfile Output file
+     * @return true if the operation is successful
+     * @since 2.2
+     */
+    public static boolean encodeFileToFile( String infile,
+                                            String outfile ) {
+        boolean success = false;
+        java.io.InputStream in = null;
+        java.io.OutputStream out = null;
+        try {
+            in = new Base64.InputStream(new java.io.BufferedInputStream(new java.io.FileInputStream(infile)), Base64.ENCODE);
+            out = new java.io.BufferedOutputStream(new java.io.FileOutputStream(outfile));
+            byte[] buffer = new byte[65536]; // 64K
+            int read = -1;
+            while ((read = in.read(buffer)) >= 0) {
+                out.write(buffer, 0, read);
+            } // end while: through file
+            success = true;
+        } catch (IOException exc) {
+            exc.printStackTrace();
+        } finally {
+            try {
+                in.close();
+            } catch (Exception exc) {
+            }
+            try {
+                out.close();
+            } catch (Exception exc) {
+            }
+        } // end finally
+
+        return success;
+    } // end encodeFileToFile
+
+    /**
+     * Reads <tt>infile</tt> and decodes it to <tt>outfile</tt>.
+     * 
+     * @param infile Input file
+     * @param outfile Output file
+     * @return true if the operation is successful
+     * @since 2.2
+     */
+    public static boolean decodeFileToFile( String infile,
+                                            String outfile ) {
+        boolean success = false;
+        java.io.InputStream in = null;
+        java.io.OutputStream out = null;
+        try {
+            in = new Base64.InputStream(new java.io.BufferedInputStream(new java.io.FileInputStream(infile)), Base64.DECODE);
+            out = new java.io.BufferedOutputStream(new java.io.FileOutputStream(outfile));
+            byte[] buffer = new byte[65536]; // 64K
+            int read = -1;
+            while ((read = in.read(buffer)) >= 0) {
+                out.write(buffer, 0, read);
+            } // end while: through file
+            success = true;
+        } catch (IOException exc) {
+            exc.printStackTrace();
+        } finally {
+            try {
+                in.close();
+            } catch (Exception exc) {
+            }
+            try {
+                out.close();
+            } catch (Exception exc) {
+            }
+        } // end finally
+
+        return success;
+    } // end decodeFileToFile
+
+    /* ********  I N N E R   C L A S S   I N P U T S T R E A M  ******** */
+
+    /**
+     * A {@link Base64.InputStream} will read data from another <tt>java.io.InputStream</tt>, given in the constructor, and
+     * encode/decode to/from Base64 notation on the fly.
+     * 
+     * @see Base64
+     * @since 1.3
+     */
+    public static class InputStream extends java.io.FilterInputStream {
+        private boolean encode; // Encoding or decoding
+        private int position; // Current position in the buffer
+        private byte[] buffer; // Small buffer holding converted data
+        private int bufferLength; // Length of buffer (3 or 4)
+        private int numSigBytes; // Number of meaningful bytes in the buffer
+        private int lineLength;
+        private boolean breakLines; // Break lines at less than 80 characters
+        private int options; // Record options used to create the stream.
+        private byte[] alphabet; // Local copies to avoid extra method calls
+        private byte[] decodabet; // Local copies to avoid extra method calls
+
+        /**
+         * Constructs a {@link Base64.InputStream} in DECODE mode.
+         * 
+         * @param in the <tt>java.io.InputStream</tt> from which to read data.
+         * @since 1.3
+         */
+        public InputStream( java.io.InputStream in ) {
+            this(in, DECODE);
+        } // end constructor
+
+        /**
+         * Constructs a {@link Base64.InputStream} in either ENCODE or DECODE mode.
+         * <p>
+         * Valid options:
+         * 
+         * <pre>
+         *   ENCODE or DECODE: Encode or Decode as data is read.
+         *   DONT_BREAK_LINES: don't break lines at 76 characters
+         *     (only meaningful when encoding)
+         *     &lt;i&gt;Note: Technically, this makes your encoding non-compliant.&lt;/i&gt;
+         * </pre>
+         * <p>
+         * Example: <code>new Base64.InputStream( in, Base64.DECODE )</code>
+         * 
+         * @param in the <tt>java.io.InputStream</tt> from which to read data.
+         * @param options Specified options
+         * @see Base64#ENCODE
+         * @see Base64#DECODE
+         * @see Base64#DONT_BREAK_LINES
+         * @since 2.0
+         */
+        public InputStream( java.io.InputStream in,
+                            int options ) {
+            super(in);
+            this.breakLines = (options & DONT_BREAK_LINES) != DONT_BREAK_LINES;
+            this.encode = (options & ENCODE) == ENCODE;
+            this.bufferLength = encode ? 4 : 3;
+            this.buffer = new byte[bufferLength];
+            this.position = -1;
+            this.lineLength = 0;
+            this.options = options; // Record for later, mostly to determine which alphabet to use
+            this.alphabet = getAlphabet(options);
+            this.decodabet = getDecodabet(options);
+        } // end constructor
+
+        /**
+         * Reads enough of the input stream to convert to/from Base64 and returns the next byte.
+         * 
+         * @return next byte
+         * @throws IOException
+         * @since 1.3
+         */
+        @Override
+        public int read() throws IOException {
+            // Do we need to get data?
+            if (position < 0) {
+                if (encode) {
+                    byte[] b3 = new byte[3];
+                    int numBinaryBytes = 0;
+                    for (int i = 0; i < 3; i++) {
+                        try {
+                            int b = in.read();
+
+                            // If end of stream, b is -1.
+                            if (b >= 0) {
+                                b3[i] = (byte)b;
+                                numBinaryBytes++;
+                            } // end if: not end of stream
+
+                        } // end try: read
+                        catch (IOException e) {
+                            // Only a problem if we got no data at all.
+                            if (i == 0) throw e;
+
+                        } // end catch
+                    } // end for: each needed input byte
+
+                    if (numBinaryBytes > 0) {
+                        encode3to4(b3, 0, numBinaryBytes, buffer, 0, options);
+                        position = 0;
+                        numSigBytes = 4;
+                    } // end if: got data
+                    else {
+                        return -1;
+                    } // end else
+                } // end if: encoding
+
+                // Else decoding
+                else {
+                    byte[] b4 = new byte[4];
+                    int i = 0;
+                    for (i = 0; i < 4; i++) {
+                        // Read four "meaningful" bytes:
+                        int b = 0;
+                        do {
+                            b = in.read();
+                        } while (b >= 0 && decodabet[b & 0x7f] <= WHITE_SPACE_ENC);
+
+                        if (b < 0) break; // Reads a -1 if end of stream
+
+                        b4[i] = (byte)b;
+                    } // end for: each needed input byte
+
+                    if (i == 4) {
+                        numSigBytes = decode4to3(b4, 0, buffer, 0, options);
+                        position = 0;
+                    } // end if: got four characters
+                    else if (i == 0) {
+                        return -1;
+                    } // end else if: also padded correctly
+                    else {
+                        // Must have broken out from above.
+                        throw new IOException("Improperly padded Base64 input.");
+                    } // end
+
+                } // end else: decode
+            } // end else: get data
+
+            // Got data?
+            if (position >= 0) {
+                // End of relevant data?
+                if ( /*!encode &&*/position >= numSigBytes) return -1;
+
+                if (encode && breakLines && lineLength >= MAX_LINE_LENGTH) {
+                    lineLength = 0;
+                    return '\n';
+                } // end if
+                else {
+                    lineLength++; // This isn't important when decoding
+                    // but throwing an extra "if" seems
+                    // just as wasteful.
+
+                    int b = buffer[position++];
+
+                    if (position >= bufferLength) position = -1;
+
+                    return b & 0xFF; // This is how you "cast" a byte that's
+                    // intended to be unsigned.
+                } // end else
+            } // end if: position >= 0
+
+            // Else error
+            else {
+                // When JDK1.4 is more accepted, use an assertion here.
+                throw new IOException("Error in Base64 code reading stream.");
+            } // end else
+        } // end read
+
+        /**
+         * Calls {@link #read()} repeatedly until the end of stream is reached or <var>len</var> bytes are read. Returns number of
+         * bytes read into array or -1 if end of stream is encountered.
+         * 
+         * @param dest array to hold values
+         * @param off offset for array
+         * @param len max number of bytes to read into array
+         * @return bytes read into array or -1 if end of stream is encountered.
+         * @throws IOException
+         * @since 1.3
+         */
+        @Override
+        public int read( byte[] dest,
+                         int off,
+                         int len ) throws IOException {
+            int i;
+            int b;
+            for (i = 0; i < len; i++) {
+                b = read();
+
+                // if( b < 0 && i == 0 )
+                // return -1;
+
+                if (b >= 0) dest[off + i] = (byte)b;
+                else if (i == 0) return -1;
+                else break; // Out of 'for' loop
+            } // end for: each byte read
+            return i;
+        } // end read
+
+    } // end inner class InputStream
+
+    /* ********  I N N E R   C L A S S   O U T P U T S T R E A M  ******** */
+
+    /**
+     * A {@link Base64.OutputStream} will write data to another <tt>java.io.OutputStream</tt>, given in the constructor, and
+     * encode/decode to/from Base64 notation on the fly.
+     * 
+     * @see Base64
+     * @since 1.3
+     */
+    public static class OutputStream extends java.io.FilterOutputStream {
+        private boolean encode;
+        private int position;
+        private byte[] buffer;
+        private int bufferLength;
+        private int lineLength;
+        private boolean breakLines;
+        private byte[] b4; // Scratch used in a few places
+        private boolean suspendEncoding;
+        private int options; // Record for later
+        private byte[] alphabet; // Local copies to avoid extra method calls
+        private byte[] decodabet; // Local copies to avoid extra method calls
+
+        /**
+         * Constructs a {@link Base64.OutputStream} in ENCODE mode.
+         * 
+         * @param out the <tt>java.io.OutputStream</tt> to which data will be written.
+         * @since 1.3
+         */
+        public OutputStream( java.io.OutputStream out ) {
+            this(out, ENCODE);
+        } // end constructor
+
+        /**
+         * Constructs a {@link Base64.OutputStream} in either ENCODE or DECODE mode.
+         * <p>
+         * Valid options:
+         * 
+         * <pre>
+         *   ENCODE or DECODE: Encode or Decode as data is read.
+         *   DONT_BREAK_LINES: don't break lines at 76 characters
+         *     (only meaningful when encoding)
+         *     &lt;i&gt;Note: Technically, this makes your encoding non-compliant.&lt;/i&gt;
+         * </pre>
+         * <p>
+         * Example: <code>new Base64.OutputStream( out, Base64.ENCODE )</code>
+         * 
+         * @param out the <tt>java.io.OutputStream</tt> to which data will be written.
+         * @param options Specified options.
+         * @see Base64#ENCODE
+         * @see Base64#DECODE
+         * @see Base64#DONT_BREAK_LINES
+         * @since 1.3
+         */
+        public OutputStream( java.io.OutputStream out,
+                             int options ) {
+            super(out);
+            this.breakLines = (options & DONT_BREAK_LINES) != DONT_BREAK_LINES;
+            this.encode = (options & ENCODE) == ENCODE;
+            this.bufferLength = encode ? 3 : 4;
+            this.buffer = new byte[bufferLength];
+            this.position = 0;
+            this.lineLength = 0;
+            this.suspendEncoding = false;
+            this.b4 = new byte[4];
+            this.options = options;
+            this.alphabet = getAlphabet(options);
+            this.decodabet = getDecodabet(options);
+        } // end constructor
+
+        /**
+         * Writes the byte to the output stream after converting to/from Base64 notation. When encoding, bytes are buffered three
+         * at a time before the output stream actually gets a write() call. When decoding, bytes are buffered four at a time.
+         * 
+         * @param theByte the byte to write
+         * @throws IOException
+         * @since 1.3
+         */
+        @Override
+        public void write( int theByte ) throws IOException {
+            // Encoding suspended?
+            if (suspendEncoding) {
+                super.out.write(theByte);
+                return;
+            } // end if: supsended
+
+            // Encode?
+            if (encode) {
+                buffer[position++] = (byte)theByte;
+                if (position >= bufferLength) // Enough to encode.
+                {
+                    out.write(encode3to4(b4, buffer, bufferLength, options));
+
+                    lineLength += 4;
+                    if (breakLines && lineLength >= MAX_LINE_LENGTH) {
+                        out.write(NEW_LINE);
+                        lineLength = 0;
+                    } // end if: end of line
+
+                    position = 0;
+                } // end if: enough to output
+            } // end if: encoding
+
+            // Else, Decoding
+            else {
+                // Meaningful Base64 character?
+                if (decodabet[theByte & 0x7f] > WHITE_SPACE_ENC) {
+                    buffer[position++] = (byte)theByte;
+                    if (position >= bufferLength) // Enough to output.
+                    {
+                        int len = Base64.decode4to3(buffer, 0, b4, 0, options);
+                        out.write(b4, 0, len);
+                        // out.write( Base64.decode4to3( buffer ) );
+                        position = 0;
+                    } // end if: enough to output
+                } // end if: meaningful base64 character
+                else if (decodabet[theByte & 0x7f] != WHITE_SPACE_ENC) {
+                    throw new IOException("Invalid character in Base64 data.");
+                } // end else: not white space either
+            } // end else: decoding
+        } // end write
+
+        /**
+         * Calls {@link #write(int)} repeatedly until <var>len</var> bytes are written.
+         * 
+         * @param theBytes array from which to read bytes
+         * @param off offset for array
+         * @param len max number of bytes to read into array
+         * @throws IOException
+         * @since 1.3
+         */
+        @Override
+        public void write( byte[] theBytes,
+                           int off,
+                           int len ) throws IOException {
+            // Encoding suspended?
+            if (suspendEncoding) {
+                super.out.write(theBytes, off, len);
+                return;
+            } // end if: supsended
+
+            for (int i = 0; i < len; i++) {
+                write(theBytes[off + i]);
+            } // end for: each byte written
+
+        } // end write
+
+        /**
+         * Method added by PHIL. [Thanks, PHIL. -Rob] This pads the buffer without closing the stream.
+         * 
+         * @throws IOException
+         */
+        public void flushBase64() throws IOException {
+            if (position > 0) {
+                if (encode) {
+                    out.write(encode3to4(b4, buffer, position, options));
+                    position = 0;
+                } // end if: encoding
+                else {
+                    throw new IOException("Base64 input not properly padded.");
+                } // end else: decoding
+            } // end if: buffer partially full
+
+        } // end flush
+
+        /**
+         * Flushes and closes (I think, in the superclass) the stream.
+         * 
+         * @throws IOException
+         * @since 1.3
+         */
+        @Override
+        public void close() throws IOException {
+            // 1. Ensure that pending characters are written
+            flushBase64();
+
+            // 2. Actually close the stream
+            // Base class both flushes and closes.
+            super.close();
+
+            buffer = null;
+            out = null;
+        } // end close
+
+        /**
+         * Suspends encoding of the stream. May be helpful if you need to embed a piece of base640-encoded data in a stream.
+         * 
+         * @throws IOException
+         * @since 1.5.1
+         */
+        public void suspendEncoding() throws IOException {
+            flushBase64();
+            this.suspendEncoding = true;
+        } // end suspendEncoding
+
+        /**
+         * Resumes encoding of the stream. May be helpful if you need to embed a piece of base640-encoded data in a stream.
+         * 
+         * @since 1.5.1
+         */
+        public void resumeEncoding() {
+            this.suspendEncoding = false;
+        } // end resumeEncoding
+
+    } // end inner class OutputStream
+
+} // end class Base64

Added: trunk/dna-common/src/test/java/org/jboss/dna/common/text/XmlValueEncoderTest.java
===================================================================
--- trunk/dna-common/src/test/java/org/jboss/dna/common/text/XmlValueEncoderTest.java	                        (rev 0)
+++ trunk/dna-common/src/test/java/org/jboss/dna/common/text/XmlValueEncoderTest.java	2009-03-10 15:17:23 UTC (rev 764)
@@ -0,0 +1,110 @@
+/*
+ * JBoss DNA (http://www.jboss.org/dna)
+ * See the COPYRIGHT.txt file distributed with this work for information
+ * regarding copyright ownership.  Some portions may be licensed
+ * to Red Hat, Inc. under one or more contributor license agreements.
+ * See the AUTHORS.txt file in the distribution for a full listing of 
+ * individual contributors. 
+ *
+ * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
+ * is licensed to you under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * JBoss DNA is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.common.text;
+
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsNull.notNullValue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+import org.junit.Before;
+import org.junit.Test;
+
+public class XmlValueEncoderTest {
+
+    private XmlValueEncoder encoder = new XmlValueEncoder();
+
+    @Before
+    public void beforeEach() {
+    }
+
+    protected void checkEncoding( String input,
+                                  String expected ) {
+        String output = this.encoder.encode(input);
+        assertThat(output, is(notNullValue()));
+        assertEquals(expected, output);
+        assertThat(output.length(), is(expected.length()));
+        assertThat(output, is(expected));
+
+        checkDecoding(output, input);
+    }
+
+    protected void checkForNoEncoding( String input ) {
+        String output = this.encoder.encode(input);
+        assertThat(output, is(notNullValue()));
+        assertEquals(input, output);
+        assertThat(output.length(), is(input.length()));
+        assertThat(output, is(input));
+
+        checkForNoDecoding(input);
+    }
+
+    protected void checkForNoDecoding( String input ) {
+        String output = this.encoder.decode(input);
+        assertThat(output, is(notNullValue()));
+        assertEquals(input, output);
+        assertThat(output.length(), is(input.length()));
+        assertThat(output, is(input));
+    }
+
+    protected void checkDecoding( String input,
+                                  String output ) {
+        String decoded = this.encoder.decode(input);
+        assertEquals(output, decoded);
+        assertThat(decoded.length(), is(output.length()));
+        assertThat(decoded, is(output));
+    }
+
+    @Test 
+    public void shouldEncodeStringWithNoSpecialChars() {
+        checkForNoEncoding("The quick brown fox jumped over the lazy dog.?+=!@#$%^*()_+-[]{}|\\");
+    }
+
+    @Test
+    public void shouldEncodeStringWithSpecialChars() {
+        checkEncoding("<>&'\"", "&lt;&gt;&amp;&#039;&quot;");
+    }
+
+    @Test
+    public void shouldHandleTrivialCase() {
+        assertNull(encoder.encode(null));
+        assertNull(encoder.decode(null));
+        checkEncoding("", "");
+
+    }
+
+    @Test
+    public void shouldDecodeStringWithInvalidMappings() {
+        checkDecoding("&amp", "&amp");
+        checkDecoding("&quot", "&quot");
+        checkDecoding("&gt", "&gt");
+        checkDecoding("&lt", "&lt");
+        checkDecoding("amp;", "amp;");
+        checkDecoding("quot;", "quot;");
+        checkDecoding("gt;", "gt;");
+        checkDecoding("lt;", "lt;");
+        checkDecoding("&;", "&;");
+        checkDecoding("&amp;&", "&&");
+    }
+}


Property changes on: trunk/dna-common/src/test/java/org/jboss/dna/common/text/XmlValueEncoderTest.java
___________________________________________________________________
Name: svn:mime-type
   + text/plain

Added: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/AbstractJcrExporter.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/AbstractJcrExporter.java	                        (rev 0)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/AbstractJcrExporter.java	2009-03-10 15:17:23 UTC (rev 764)
@@ -0,0 +1,359 @@
+/*
+ * JBoss DNA (http://www.jboss.org/dna)
+ * See the COPYRIGHT.txt file distributed with this work for information
+ * regarding copyright ownership.  Some portions may be licensed
+ * to Red Hat, Inc. under one or more contributor license agreements.
+ * See the AUTHORS.txt file in the distribution for a full listing of 
+ * individual contributors.
+ *
+ * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
+ * is licensed to you under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ * 
+ * JBoss DNA is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.jcr;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.jcr.ItemVisitor;
+import javax.jcr.NamespaceRegistry;
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import net.jcip.annotations.NotThreadSafe;
+import org.jboss.dna.common.text.TextEncoder;
+import org.jboss.dna.common.text.XmlNameEncoder;
+import org.jboss.dna.common.text.XmlValueEncoder;
+import org.jboss.dna.graph.property.Name;
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * Superclass of DNA JCR exporters, provides basic support for traversing through the nodes recursively (if needed), exception
+ * wrapping (since {@link ItemVisitor} does not allow checked exceptions to be thrown from its visit* methods, and the ability to
+ * wrap an {@link OutputStream} with a {@link ContentHandler}.
+ * <p />
+ * Each exporter is only intended to be used once (by calling <code>exportView</code>) and discarded. This class is <b>NOT</b>
+ * thread-safe.
+ * 
+ * @see JcrSystemViewExporter
+ * @see JcrDocumentViewExporter
+ */
+ at NotThreadSafe
+abstract class AbstractJcrExporter {
+
+    /**
+     * Encoder to properly escape XML names.
+     * 
+     * @see XmlNameEncoder
+     */
+    private static final TextEncoder NAME_ENCODER = new XmlNameEncoder();
+
+    /**
+     * The session in which this exporter was created.
+     */
+    protected final JcrSession session;
+
+    /**
+     * The list of XML namespace prefixes that should never be exported.
+     */
+    private final Collection<String> restrictedPrefixes;
+
+    /**
+     * Creates the exporter
+     * 
+     * @param session the session in which the exporter is created
+     * @param restrictedPrefixes the list of XML namespace prefixes that should not be exported
+     */
+    AbstractJcrExporter( JcrSession session,
+                         Collection<String> restrictedPrefixes ) {
+        this.session = session;
+        this.restrictedPrefixes = restrictedPrefixes;
+    }
+
+    /**
+     * Exports <code>node</code> (or the subtree rooted at <code>node</code>) into an XML document by invoking SAX events on
+     * <code>contentHandler</code>.
+     * 
+     * @param exportRootNode the node which should be exported. If <code>noRecursion</code> was set to <code>false</code> in the
+     *        constructor, the entire subtree rooted at <code>node</code> will be exported.
+     * @param contentHandler the SAX content handler for which SAX events will be invoked as the XML document is created.
+     * @param skipBinary if <code>true</code>, indicates that binary properties should not be exported
+     * @param noRecurse if<code>true</code>, indicates that only the given node should be exported, otherwise a recursice export
+     *        and not any of its child nodes.
+     * @throws SAXException if an exception occurs during generation of the XML document
+     * @throws RepositoryException if an exception occurs accessing the content repository
+     */
+    public void exportView( Node exportRootNode,
+                            ContentHandler contentHandler,
+                            boolean skipBinary,
+                            boolean noRecurse ) throws RepositoryException, SAXException {
+        assert exportRootNode != null;
+        assert contentHandler != null;
+
+        // Export the namespace mappings used in this session
+        NamespaceRegistry registry = session.getWorkspace().getNamespaceRegistry();
+
+        contentHandler.startDocument();
+        String[] namespacePrefixes = registry.getPrefixes();
+        for (int i = 0; i < namespacePrefixes.length; i++) {
+            String prefix = namespacePrefixes[i];
+
+            if (!restrictedPrefixes.contains(prefix)) {
+                contentHandler.startPrefixMapping(prefix, registry.getURI(prefix));
+            }
+        }
+
+        exportNode(exportRootNode, contentHandler, skipBinary, noRecurse);
+
+        for (int i = 0; i < namespacePrefixes.length; i++) {
+            if (!restrictedPrefixes.contains(namespacePrefixes[i])) {
+                contentHandler.endPrefixMapping(namespacePrefixes[i]);
+            }
+        }
+
+        contentHandler.endDocument();
+    }
+
+    /**
+     * Exports <code>node</code> (or the subtree rooted at <code>node</code>) into an XML document that is written to
+     * <code>os</code>.
+     * 
+     * @param node the node which should be exported. If <code>noRecursion</code> was set to <code>false</code> in the
+     *        constructor, the entire subtree rooted at <code>node</code> will be exported.
+     * @param os the {@link OutputStream} to which the XML document will be written
+     * @param skipBinary if <code>true</code>, indicates that binary properties should not be exported
+     * @param noRecurse if<code>true</code>, indicates that only the given node should be exported, otherwise a recursive export
+     *        and not any of its child nodes.
+     * @throws RepositoryException if an exception occurs accessing the content repository, generating the XML document, or
+     *         writing it to the output stream <code>os</code>.
+     */
+    public void exportView( Node node,
+                            OutputStream os,
+                            boolean skipBinary,
+                            boolean noRecurse ) throws RepositoryException {
+        try {
+            exportView(node, new StreamingContentHandler(os), skipBinary, noRecurse);
+            os.flush();
+        } catch (IOException ioe) {
+            throw new RepositoryException(ioe);
+        } catch (SAXException se) {
+            throw new RepositoryException(se);
+        }
+    }
+
+    /**
+     * Exports <code>node</code> (or the subtree rooted at <code>node</code>) into an XML document by invoking SAX events on
+     * <code>contentHandler</code>.
+     * 
+     * @param node the node which should be exported. If <code>noRecursion</code> was set to <code>false</code> in the
+     *        constructor, the entire subtree rooted at <code>node</code> will be exported.
+     * @param contentHandler the SAX content handler for which SAX events will be invoked as the XML document is created.
+     * @param skipBinary if <code>true</code>, indicates that binary properties should not be exported
+     * @param noRecurse if<code>true</code>, indicates that only the given node should be exported, otherwise a recursive export
+     *        and not any of its child nodes.
+     * @throws SAXException if an exception occurs during generation of the XML document
+     * @throws RepositoryException if an exception occurs accessing the content repository
+     */
+    public abstract void exportNode( Node node,
+                                     ContentHandler contentHandler,
+                                     boolean skipBinary,
+                                     boolean noRecurse ) throws RepositoryException, SAXException;
+
+    /**
+     * Convenience method to invoke the {@link ContentHandler#startElement(String, String, String, Attributes)} method on the
+     * given content handler. The name will be encoded to properly escape invalid XML characters.
+     * 
+     * @param contentHandler the content handler on which the <code>startElement</code> method should be invoked.
+     * @param name the un-encoded, un-prefixed name of the element to start
+     * @param atts the attributes that should be created for the given element
+     * @throws SAXException if there is an error starting the element
+     */
+    protected void startElement( ContentHandler contentHandler,
+                                 Name name,
+                                 Attributes atts ) throws SAXException {
+        contentHandler.startElement(name.getNamespaceUri(),
+                                    NAME_ENCODER.encode(name.getLocalName()),
+                                    NAME_ENCODER.encode(name.getString(session.getExecutionContext().getNamespaceRegistry())),
+                                    atts);
+    }
+
+    /**
+     * Convenience method to invoke the {@link ContentHandler#endElement(String, String, String)} method on the given content
+     * handler. The name will be encoded to properly escape invalid XML characters.
+     * 
+     * @param contentHandler the content handler on which the <code>endElement</code> method should be invoked.
+     * @param name the un-encoded, un-prefixed name of the element to end
+     * @throws SAXException if there is an error ending the element
+     */
+    protected void endElement( ContentHandler contentHandler,
+                               Name name ) throws SAXException {
+        contentHandler.endElement(name.getNamespaceUri(),
+                                  NAME_ENCODER.encode(name.getLocalName()),
+                                  NAME_ENCODER.encode(name.getString(session.getExecutionContext().getNamespaceRegistry())));
+    }
+
+    /**
+     * Helper class that adapts an arbitrary, open {@link OutputStream} to the {@link ContentHandler} interface. SAX events
+     * invoked on this object will be translated into their corresponding XML text and written to the output stream.
+     * 
+     * @see AbstractJcrExporter#exportView(Node, OutputStream, boolean, boolean)
+     */
+    private class StreamingContentHandler extends DefaultHandler {
+
+        /** Debug setting that allows all output to be written to {@link System#out}. */
+        private static final boolean LOG_TO_CONSOLE = false;
+
+        /**
+         * Encoder to properly escape XML attribute values
+         * 
+         * @see XmlValueEncoder
+         */
+        private final TextEncoder VALUE_ENCODER = new XmlValueEncoder();
+
+        /**
+         * The list of XML namespaces that are predefined and should not be exported by the content handler.
+         */
+        private final List<String> UNEXPORTABLE_NAMESPACES = Arrays.asList(new String[] {"", "xml", "xmlns"});
+
+        /**
+         * The output stream to which the XML will be written
+         */
+        private final OutputStream os;
+
+        /**
+         * The XML namespace prefixes that are currently mapped
+         */
+        private final Map<String, String> mappedPrefixes;
+
+        public StreamingContentHandler( OutputStream os ) {
+            this.os = os;
+            mappedPrefixes = new HashMap<String, String>();
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see org.xml.sax.helpers.DefaultHandler#characters(char[], int, int)
+         */
+        @Override
+        public void characters( char[] ch,
+                                int start,
+                                int length ) throws SAXException {
+            emit(VALUE_ENCODER.encode(new String(ch, start, length)));
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see org.xml.sax.helpers.DefaultHandler#startDocument()
+         */
+        @Override
+        public void startDocument() throws SAXException {
+            emit("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see org.xml.sax.helpers.DefaultHandler#startElement(java.lang.String, java.lang.String, java.lang.String,
+         *      org.xml.sax.Attributes)
+         */
+        @Override
+        public void startElement( String uri,
+                                  String localName,
+                                  String name,
+                                  Attributes attributes ) throws SAXException {
+            emit("<");
+            emit(name);
+
+            for (Map.Entry<String, String> mapping : mappedPrefixes.entrySet()) {
+                emit(" xmlns:");
+                emit(mapping.getKey());
+                emit("=\"");
+                emit(mapping.getValue());
+                emit("\"");
+            }
+
+            mappedPrefixes.clear();
+
+            if (attributes != null) {
+                for (int i = 0; i < attributes.getLength(); i++) {
+                    emit(" ");
+                    emit(attributes.getQName(i));
+                    emit("=\"");
+                    emit(VALUE_ENCODER.encode(attributes.getValue(i)));
+                    emit("\"");
+                }
+            }
+
+            emit(">");
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see org.xml.sax.helpers.DefaultHandler#endElement(java.lang.String, java.lang.String, java.lang.String)
+         */
+        @Override
+        public void endElement( String uri,
+                                String localName,
+                                String name ) throws SAXException {
+            emit("</");
+            emit(name);
+            emit(">");
+            System.out.println();
+
+        }
+
+        /**
+         * {@inheritDoc}
+         * 
+         * @see org.xml.sax.helpers.DefaultHandler#startPrefixMapping(java.lang.String, java.lang.String)
+         */
+        @Override
+        public void startPrefixMapping( String prefix,
+                                        String uri ) {
+            if (!UNEXPORTABLE_NAMESPACES.contains(prefix)) {
+                mappedPrefixes.put(prefix, uri);
+            }
+        }
+
+        /**
+         * Writes the given text to the output stream for this {@link StreamingContentHandler}.
+         * 
+         * @param text the text to output
+         * @throws SAXException if there is an error writing to the stream
+         * @see StreamingContentHandler#os
+         */
+        private void emit( String text ) throws SAXException {
+
+            try {
+                if (LOG_TO_CONSOLE) {
+                    System.out.print(text);
+                }
+
+                os.write(text.getBytes());
+            } catch (IOException ioe) {
+                throw new SAXException(ioe);
+            }
+        }
+    }
+
+}


Property changes on: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/AbstractJcrExporter.java
___________________________________________________________________
Name: svn:mime-type
   + text/plain

Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/DnaBuiltinNodeTypeSource.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/DnaBuiltinNodeTypeSource.java	2009-03-09 17:57:18 UTC (rev 763)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/DnaBuiltinNodeTypeSource.java	2009-03-10 15:17:23 UTC (rev 764)
@@ -29,8 +29,8 @@
 import java.util.List;
 import javax.jcr.PropertyType;
 import javax.jcr.nodetype.NodeType;
+import net.jcip.annotations.Immutable;
 import org.jboss.dna.graph.JcrMixLexicon;
-import net.jcip.annotations.Immutable;
 
 /**
  * {@link JcrNodeTypeSource} that provides built-in node types provided by DNA.
@@ -136,8 +136,49 @@
 
                                            }), NO_PROPERTIES, NOT_MIXIN, UNORDERABLE_CHILD_NODES);
 
-        primaryNodeTypes.addAll(Arrays.asList(new JcrNodeType[] {root, system, namespaces, namespace}));
+        /* Name of node type that holds xmltext from document view import (see JCR 1.0 spec section 7.3.2) */
+        JcrNodeType xmlText = new JcrNodeType(
+                                              session,
+                                              DnaLexicon.XML_TEXT_TYPE,
+                                              Arrays.asList(new NodeType[] {base}),
+                                              NO_PRIMARY_ITEM_NAME,
+                                              NO_CHILD_NODES,
+                                              Arrays.asList(new JcrPropertyDefinition[] {new JcrPropertyDefinition(
+                                                                                                                   session,
+                                                                                                                   null,
+                                                                                                                   JcrLexicon.XMLCHARACTERS,
+                                                                                                                   OnParentVersionBehavior.VERSION.getJcrValue(),
+                                                                                                                   true,
+                                                                                                                   true,
+                                                                                                                   true,
+                                                                                                                   NO_DEFAULT_VALUES,
+                                                                                                                   PropertyType.STRING,
+                                                                                                                   NO_CONSTRAINTS,
+                                                                                                                   false)}),
+                                              NOT_MIXIN, UNORDERABLE_CHILD_NODES);
 
+        /* Mixin type that indicates the node contains an xmltext node which holds xmltext from document view import (see JCR 1.0 spec section 7.3.2) */
+        JcrNodeType xmlContent = new JcrNodeType(
+                                                 session,
+                                                 DnaLexicon.XML_CONTENT,
+                                                 Arrays.asList(new NodeType[] {base}),
+                                                 NO_PRIMARY_ITEM_NAME,
+                                                 Arrays.asList(new JcrNodeDefinition[] {new JcrNodeDefinition(
+                                                                                                              session,
+                                                                                                              null,
+                                                                                                              DnaLexicon.XML_TEXT,
+                                                                                                              OnParentVersionBehavior.VERSION.getJcrValue(),
+                                                                                                              false,
+                                                                                                              true,
+                                                                                                              false,
+                                                                                                              false,
+                                                                                                              DnaLexicon.XML_TEXT_TYPE,
+                                                                                                              new NodeType[] {xmlText})}),
+                                                 NO_PROPERTIES, IS_A_MIXIN, UNORDERABLE_CHILD_NODES);
+
+        primaryNodeTypes.addAll(Arrays.asList(new JcrNodeType[] {root, system, namespaces, namespace, xmlText,}));
+        mixinNodeTypes.addAll(Arrays.asList(new JcrNodeType[] {xmlContent}));
+
     }
 
     /**

Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/DnaLexicon.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/DnaLexicon.java	2009-03-09 17:57:18 UTC (rev 763)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/DnaLexicon.java	2009-03-10 15:17:23 UTC (rev 764)
@@ -33,9 +33,28 @@
 
     public static final Name NAMESPACES = new BasicName(Namespace.URI, "namespaces");
     public static final Name NAMESPACE = new BasicName(Namespace.URI, "namespace");
+    public static final Name NODE_DEFINITON = new BasicName(Namespace.URI, "nodeDefinition");
     public static final Name ROOT = new BasicName(Namespace.URI, "root");
     public static final Name SYSTEM = new BasicName(Namespace.URI, "system");
     public static final Name URI = new BasicName(Namespace.URI, "uri");
-    public static final Name NODE_DEFINITON = new BasicName(Namespace.URI, "nodeDefinition");
 
+    /**
+     * Mixin type that indicates the node contains an xmltext node which holds xmltext from document view import (see JCR 1.0
+     * specification section 7.3.2). This node has a child node named {@link DnaLexicon#XML_TEXT} of type
+     * {@link DnaLexicon#XML_TEXT_TYPE}.
+     */
+    public static final Name XML_CONTENT = new BasicName(Namespace.URI, "xmlContent");
+
+    /**
+     * Name of node type that holds xmltext from document view import (see JCR 1.0 specification section 7.3.2). It is defined in
+     * the node type named {@link DnaLexicon#XML_CONTENT}.
+     */
+    public static final Name XML_TEXT_TYPE = new BasicName(Namespace.URI, "xmlText");
+
+    /**
+     * Name of the child node that holds xmltext from document view import (see JCR 1.0 specification section 7.3.2). This is the
+     * name of the child node of the {@link DnaLexicon#XML_CONTENT} mixin type. By definition, this node has a required primary
+     * type of {@link DnaLexicon#XML_TEXT_TYPE}.
+     */
+    public static final Name XML_TEXT = new BasicName(Namespace.URI, "xmltext");
 }

Added: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrDocumentViewExporter.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrDocumentViewExporter.java	                        (rev 0)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrDocumentViewExporter.java	2009-03-10 15:17:23 UTC (rev 764)
@@ -0,0 +1,216 @@
+/*
+ * JBoss DNA (http://www.jboss.org/dna)
+ * See the COPYRIGHT.txt file distributed with this work for information
+ * regarding copyright ownership.  Some portions may be licensed
+ * to Red Hat, Inc. under one or more contributor license agreements.
+ * See the AUTHORS.txt file in the distribution for a full listing of 
+ * individual contributors.
+ *
+ * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
+ * is licensed to you under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ * 
+ * JBoss DNA is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.dna.jcr;
+
+import java.io.OutputStream;
+import java.util.Collections;
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.Property;
+import javax.jcr.PropertyIterator;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+import net.jcip.annotations.NotThreadSafe;
+import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.property.Name;
+import org.jboss.dna.graph.property.ValueFactories;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+/**
+ * Implementation of {@link AbstractJcrExporter} that implements the document view mapping described in section 6.4.2 of the JCR
+ * 1.0 specification.
+ * 
+ * @see JcrSession#exportDocumentView(String, ContentHandler, boolean, boolean)
+ * @see JcrSession#exportDocumentView(String, OutputStream, boolean, boolean)
+ */
+ at NotThreadSafe
+class JcrDocumentViewExporter extends AbstractJcrExporter {
+
+    JcrDocumentViewExporter( JcrSession session ) {
+        super(session, Collections.<String>emptyList());
+    }
+
+    /**
+     * Exports <code>node</code> (or the subtree rooted at <code>node</code>) into an XML document by invoking SAX events on
+     * <code>contentHandler</code>.
+     * 
+     * @param node the node which should be exported. If <code>noRecursion</code> was set to <code>false</code> in the
+     *        constructor, the entire subtree rooted at <code>node</code> will be exported.
+     * @param contentHandler the SAX content handler for which SAX events will be invoked as the XML document is created.
+     * @param skipBinary if <code>true</code>, indicates that binary properties should not be exported
+     * @param noRecurse if<code>true</code>, indicates that only the given node should be exported, otherwise a recursive export
+     *        and not any of its child nodes.
+     * @throws SAXException if an exception occurs during generation of the XML document
+     * @throws RepositoryException if an exception occurs accessing the content repository
+     */
+    @Override
+    public void exportNode( Node node,
+                            ContentHandler contentHandler,
+                            boolean skipBinary,
+                            boolean noRecurse ) throws RepositoryException, SAXException {
+        ExecutionContext executionContext = session.getExecutionContext();
+
+        // If this node is a special xmltext node, output it as raw content (see JCR 1.0 spec - section 6.4.2.3
+        if (node.getParent() != null && isXmlTextNode(node)) {
+
+            String xmlCharacters = getXmlCharacters(node);
+            contentHandler.characters(xmlCharacters.toCharArray(), 0, xmlCharacters.length());
+
+            return;
+        }
+
+        ValueFactories valueFactories = executionContext.getValueFactories();
+        AttributesImpl atts = new AttributesImpl();
+
+        // Build the attributes for this node's element
+        PropertyIterator properties = node.getProperties();
+        while (properties.hasNext()) {
+            Property prop = properties.nextProperty();
+
+            if (skipBinary && PropertyType.BINARY == prop.getType()) {
+                continue;
+            }
+
+            Name propName = ((AbstractJcrProperty)prop).getDnaProperty().getName();
+
+            String localPropName = propName.getString(executionContext.getNamespaceRegistry());
+
+            Value value;
+            if (prop instanceof JcrSingleValueProperty) {
+                value = prop.getValue();
+            } else {
+                // Only output the first value of the multi-valued property. This is acceptable as per JCR 1.0 Spec - section
+                // 6.4.2.5
+                value = prop.getValues()[0];
+            }
+            atts.addAttribute(propName.getNamespaceUri(),
+                              propName.getLocalName(),
+                              localPropName,
+                              PropertyType.nameFromValue(prop.getType()),
+                              value.getString());
+        }
+
+        Name name;
+
+        // Special case to stub in name for root node as per JCR 1.0 Spec - 6.4.2.2
+        if ("/".equals(node.getPath())) {
+            name = JcrLexicon.ROOT;
+        } else {
+            name = valueFactories.getNameFactory().create(node.getName());
+        }
+
+        startElement(contentHandler, name, atts);
+
+        if (!noRecurse) {
+            NodeIterator nodes = node.getNodes();
+            while (nodes.hasNext()) {
+                exportNode(nodes.nextNode(), contentHandler, skipBinary, noRecurse);
+            }
+        }
+
+        endElement(contentHandler, name);
+    
+    }
+
+    /**
+     * Indicates whether the current node is an XML text node as per section 6.4.2.3 of the JCR 1.0 specification.
+     * XML text nodes are nodes that have the name &quot;jcr:xmltext&quot; and only one property (besides the mandatory
+     * &quot;jcr:primaryType&quot;).  The property must have a property name of &quot;jcr:xmlcharacters&quot;, a type of <code>String</code>,
+     * and does not have multiple values.<p/>
+     * In practice, this is handled in DNA by making XML text nodes have a type of &quot;dna:xmltext&quot;, which
+     * enforces these property characteristics.
+     * 
+     * @param node the node to test
+     * @return whether this node is a special xml text node
+     * @throws RepositoryException if there is an error accessing the repository
+     */
+    private boolean isXmlTextNode( Node node ) throws RepositoryException {
+        // ./xmltext/xmlcharacters exception (see JSR-170 Spec 6.4.2.3)
+
+        ExecutionContext executionContext = session.getExecutionContext();
+        if (JcrLexicon.XMLTEXT.getString(executionContext.getNamespaceRegistry()).equals(node.getName())) {
+            if (node.getNodes().getSize() == 0) {
+
+                PropertyIterator properties = node.getProperties();
+                boolean xmlCharactersFound = false;
+
+                while (properties.hasNext()) {
+                    Property property = properties.nextProperty();
+
+                    if (JcrLexicon.PRIMARY_TYPE.getString(executionContext.getNamespaceRegistry()).equals(property.getName())) {
+                        continue;
+                    }
+
+                    if (JcrLexicon.XMLCHARACTERS.getString(executionContext.getNamespaceRegistry()).equals(property.getName())) {
+                        xmlCharactersFound = true;
+                        continue;
+                    }
+
+                    // If the xmltext node has any properties other than primaryType or xmlcharacters, return false;
+                    return false;
+                }
+
+                return xmlCharactersFound;
+            }
+        }
+
+        return false;
+
+    }
+
+    /**
+     * Returns the XML characters for the given node. 
+     * The node must be an XML text node, as defined in {@link #isXmlTextNode(Node)}.
+     * 
+     * @param node the node for which XML characters will be retrieved.
+     * @return the xml characters for this node
+     * @throws RepositoryException if there is an error accessing this node
+     */
+    private String getXmlCharacters( Node node ) throws RepositoryException {
+        // ./xmltext/xmlcharacters exception (see JSR-170 Spec 6.4.2.3)
+
+        assert isXmlTextNode(node);
+        
+        ExecutionContext executionContext = session.getExecutionContext();
+        Property xmlCharacters = node.getProperty(JcrLexicon.XMLCHARACTERS.getString(executionContext.getNamespaceRegistry()));
+
+        assert xmlCharacters != null;
+
+        if (xmlCharacters.getDefinition().isMultiple()) {
+            StringBuffer buff = new StringBuffer();
+
+            for (Value value : xmlCharacters.getValues()) {
+                buff.append(value.getString());
+            }
+
+            return buff.toString();
+        }
+
+        return xmlCharacters.getValue().getString();
+    }
+
+}


Property changes on: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrDocumentViewExporter.java
___________________________________________________________________
Name: svn:mime-type
   + text/plain

Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrI18n.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrI18n.java	2009-03-09 17:57:18 UTC (rev 763)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrI18n.java	2009-03-10 15:17:23 UTC (rev 764)
@@ -63,7 +63,7 @@
     public static I18n invalidNamePattern;
     public static I18n itemNotFoundWithUuid;
     public static I18n errorWhileFindingNodeWithUuid;
-    public static I18n nodeDefinitionCouldBeDeterminedForNode;
+    public static I18n nodeDefinitionCouldNotBeDeterminedForNode;
 
     public static I18n typeNotFound;
     public static I18n supertypeNotFound;

Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNamespaceRegistry.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNamespaceRegistry.java	2009-03-09 17:57:18 UTC (rev 763)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNamespaceRegistry.java	2009-03-10 15:17:23 UTC (rev 764)
@@ -67,6 +67,11 @@
     static final String XMLNS_NAMESPACE_PREFIX = XMLConstants.XMLNS_ATTRIBUTE;
     static final String XMLNS_NAMESPACE_URI = XMLConstants.XMLNS_ATTRIBUTE_NS_URI;
 
+    static final String XML_SCHEMA_NAMESPACE_PREFIX = "xsd";
+    static final String XML_SCHEMA_NAMESPACE_URI = "http://www.w3.org/2001/XMLSchema";
+    static final String XML_SCHEMA_INSTANCE_NAMESPACE_PREFIX = "xsi";
+    static final String XML_SCHEMA_INSTANCE_NAMESPACE_URI = "http://www.w3.org/2001/XMLSchema-instance";
+    
     static final Set<String> STANDARD_BUILT_IN_PREFIXES;
     static final Set<String> STANDARD_BUILT_IN_URIS;
     static final Map<String, String> STANDARD_BUILT_IN_NAMESPACES_BY_PREFIX;
@@ -82,6 +87,8 @@
         namespaces.put(JcrSvLexicon.Namespace.PREFIX, JcrSvLexicon.Namespace.URI);
         namespaces.put(XML_NAMESPACE_PREFIX, XML_NAMESPACE_URI);
         namespaces.put(XMLNS_NAMESPACE_PREFIX, XMLNS_NAMESPACE_URI);
+        namespaces.put(XML_SCHEMA_NAMESPACE_PREFIX, XML_SCHEMA_NAMESPACE_URI);
+        namespaces.put(XML_SCHEMA_INSTANCE_NAMESPACE_PREFIX, XML_SCHEMA_INSTANCE_NAMESPACE_URI);
         namespaces.put(DnaLexicon.Namespace.PREFIX, DnaLexicon.Namespace.URI);
         // Set up the reverse map for the standard namespaces ...
         Map<String, String> prefixes = new HashMap<String, String>();

Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNodeType.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNodeType.java	2009-03-09 17:57:18 UTC (rev 763)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNodeType.java	2009-03-10 15:17:23 UTC (rev 764)
@@ -171,6 +171,7 @@
         // Check if the node can be added with the named child node definition
         if (childNode != null && primaryNodeTypeName != null) {
             NodeType primaryNodeType = getPrimaryNodeType(primaryNodeTypeName);
+            if (primaryNodeType == null) return null;
             if (!checkTypeAgainstDefinition(primaryNodeType, childNode)) return null;
         }
         return childNode;

Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrSession.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrSession.java	2009-03-09 17:57:18 UTC (rev 763)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrSession.java	2009-03-10 15:17:23 UTC (rev 764)
@@ -73,6 +73,7 @@
 import org.jboss.dna.graph.property.basic.LocalNamespaceRegistry;
 import org.jboss.dna.jcr.JcrNamespaceRegistry.Behavior;
 import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
 import com.google.common.base.ReferenceType;
 import com.google.common.collect.ReferenceMap;
 
@@ -290,53 +291,81 @@
     /**
      * {@inheritDoc}
      * 
-     * @throws UnsupportedOperationException always
      * @see javax.jcr.Session#exportDocumentView(java.lang.String, org.xml.sax.ContentHandler, boolean, boolean)
      */
     public void exportDocumentView( String absPath,
                                     ContentHandler contentHandler,
                                     boolean skipBinary,
-                                    boolean noRecurse ) {
-        throw new UnsupportedOperationException();
+                                    boolean noRecurse ) throws RepositoryException, SAXException {
+        CheckArg.isNotNull(absPath, "absPath");
+        CheckArg.isNotNull(contentHandler, "contentHandler");
+
+        Path exportRootPath = executionContext.getValueFactories().getPathFactory().create(absPath);
+        Node exportRootNode = getNode(exportRootPath);
+
+        AbstractJcrExporter exporter = new JcrDocumentViewExporter(this);
+
+        exporter.exportView(exportRootNode, contentHandler, skipBinary, noRecurse);
     }
 
     /**
      * {@inheritDoc}
      * 
-     * @throws UnsupportedOperationException always
      * @see javax.jcr.Session#exportDocumentView(java.lang.String, java.io.OutputStream, boolean, boolean)
      */
     public void exportDocumentView( String absPath,
                                     OutputStream out,
                                     boolean skipBinary,
-                                    boolean noRecurse ) {
-        throw new UnsupportedOperationException();
+                                    boolean noRecurse ) throws RepositoryException {
+        CheckArg.isNotNull(absPath, "absPath");
+        CheckArg.isNotNull(out, "out");
+
+        Path exportRootPath = executionContext.getValueFactories().getPathFactory().create(absPath);
+        Node exportRootNode = getNode(exportRootPath);
+
+        AbstractJcrExporter exporter = new JcrDocumentViewExporter(this);
+
+        exporter.exportView(exportRootNode, out, skipBinary, noRecurse);
     }
 
     /**
      * {@inheritDoc}
      * 
-     * @throws UnsupportedOperationException always
      * @see javax.jcr.Session#exportSystemView(java.lang.String, org.xml.sax.ContentHandler, boolean, boolean)
      */
     public void exportSystemView( String absPath,
                                   ContentHandler contentHandler,
                                   boolean skipBinary,
-                                  boolean noRecurse ) {
-        throw new UnsupportedOperationException();
+                                  boolean noRecurse ) throws RepositoryException, SAXException {
+        CheckArg.isNotNull(absPath, "absPath");
+        CheckArg.isNotNull(contentHandler, "contentHandler");
+
+        Path exportRootPath = executionContext.getValueFactories().getPathFactory().create(absPath);
+        Node exportRootNode = getNode(exportRootPath);
+
+        AbstractJcrExporter exporter = new JcrSystemViewExporter(this);
+
+        exporter.exportView(exportRootNode, contentHandler, skipBinary, noRecurse);
     }
 
     /**
      * {@inheritDoc}
      * 
-     * @throws UnsupportedOperationException always
      * @see javax.jcr.Session#exportSystemView(java.lang.String, java.io.OutputStream, boolean, boolean)
      */
     public void exportSystemView( String absPath,
                                   OutputStream out,
                                   boolean skipBinary,
-                                  boolean noRecurse ) {
-        throw new UnsupportedOperationException();
+                                  boolean noRecurse ) throws RepositoryException {
+        CheckArg.isNotNull(absPath, "absPath");
+        CheckArg.isNotNull(out, "out");
+
+        Path exportRootPath = executionContext.getValueFactories().getPathFactory().create(absPath);
+        Node exportRootNode = getNode(exportRootPath);
+
+        AbstractJcrExporter exporter = new JcrSystemViewExporter(this);
+
+        exporter.exportView(exportRootNode, out, skipBinary, noRecurse);
     }
 
     /**
@@ -518,7 +547,7 @@
                 String childName = path.getLastSegment().getName().getString(namespaces);
                 definition = nodeType.findBestNodeDefinitionForChild(childName, primaryTypeNameString);
                 if (definition == null) {
-                    String msg = JcrI18n.nodeDefinitionCouldBeDeterminedForNode.text(path, workspace.getName());
+                    String msg = JcrI18n.nodeDefinitionCouldNotBeDeterminedForNode.text(path, workspace.getName());
                     throw new RepositorySourceException(msg);
                 }
             }

Added: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrSystemViewExporter.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrSystemViewExporter.java	                        (rev 0)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrSystemViewExporter.java	2009-03-10 15:17:23 UTC (rev 764)
@@ -0,0 +1,244 @@
+package org.jboss.dna.jcr;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Arrays;
+import java.util.List;
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.Property;
+import javax.jcr.PropertyIterator;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+import net.jcip.annotations.NotThreadSafe;
+import org.jboss.dna.common.util.Base64;
+import org.jboss.dna.common.xml.XmlCharacters;
+import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.property.Name;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+/**
+ * Implementation of {@link AbstractJcrExporter} that implements the system view mapping described in section 6.4.1 of the JCR 1.0
+ * specification.
+ * 
+ * @see JcrSession#exportSystemView(String, ContentHandler, boolean, boolean)
+ * @see JcrSession#exportSystemView(String, OutputStream, boolean, boolean)
+ */
+ at NotThreadSafe
+class JcrSystemViewExporter extends AbstractJcrExporter {
+
+    /**
+     * Buffer size for reading Base64-encoded binary streams for export.
+     */
+    private static final int BASE_64_BUFFER_SIZE = 1024;
+
+    /**
+     * The list of the special JCR properties that must be exported first for each node. These properties must be exported in list
+     * order if they are present on the node as per section 6.4.1 rule 11.
+     */
+    private static final List<Name> SPECIAL_PROPERTY_NAMES = Arrays.asList(new Name[] {JcrLexicon.PRIMARY_TYPE,
+        JcrLexicon.MIXIN_TYPES, JcrLexicon.UUID});
+
+    JcrSystemViewExporter( JcrSession session ) {
+        super(session, Arrays.asList(new String[] {"xml"}));
+    }
+
+    /**
+     * Exports <code>node</code> (or the subtree rooted at <code>node</code>) into an XML document by invoking SAX events on
+     * <code>contentHandler</code>.
+     * 
+     * @param node the node which should be exported. If <code>noRecursion</code> was set to <code>false</code> in the
+     *        constructor, the entire subtree rooted at <code>node</code> will be exported.
+     * @param contentHandler the SAX content handler for which SAX events will be invoked as the XML document is created.
+     * @param skipBinary if <code>true</code>, indicates that binary properties should not be exported
+     * @param noRecurse if<code>true</code>, indicates that only the given node should be exported, otherwise a recursive export
+     *        and not any of its child nodes.
+     * @throws SAXException if an exception occurs during generation of the XML document
+     * @throws RepositoryException if an exception occurs accessing the content repository
+     */
+    @Override
+    public void exportNode( Node node,
+                            ContentHandler contentHandler,
+                            boolean skipBinary,
+                            boolean noRecurse ) throws RepositoryException, SAXException {
+        ExecutionContext executionContext = session.getExecutionContext();
+
+        // start the sv:node element for this JCR node
+        AttributesImpl atts = new AttributesImpl();
+        atts.addAttribute(JcrSvLexicon.NAME.getNamespaceUri(),
+                          JcrSvLexicon.NAME.getLocalName(),
+                          JcrSvLexicon.NAME.getString(executionContext.getNamespaceRegistry()),
+                          PropertyType.nameFromValue(PropertyType.STRING),
+                          node.getName());
+
+        startElement(contentHandler, JcrSvLexicon.NODE, atts);
+
+        // Output any special properties first (see Javadoc for SPECIAL_PROPERTY_NAMES for more context)
+        for (Name specialPropertyName : SPECIAL_PROPERTY_NAMES) {
+            Property specialProperty = ((AbstractJcrNode)node).getProperty(specialPropertyName);
+
+            if (specialProperty != null) {
+                emitProperty(specialProperty, contentHandler);
+            }
+        }
+
+        PropertyIterator properties = node.getProperties();
+        while (properties.hasNext()) {
+            exportProperty(properties.nextProperty(), contentHandler, skipBinary);
+        }
+
+        if (!noRecurse) {
+            NodeIterator nodes = node.getNodes();
+            while (nodes.hasNext()) {
+                exportNode(nodes.nextNode(), contentHandler, skipBinary, noRecurse);
+            }
+        }
+
+        endElement(contentHandler, JcrSvLexicon.NODE);
+    }
+
+    /**
+     * @param property
+     * @param contentHandler the SAX content handler for which SAX events will be invoked as the XML document is created.
+     * @param skipBinary if <code>true</code>, indicates that binary properties should not be exported
+     * @throws SAXException if an exception occurs during generation of the XML document
+     * @throws RepositoryException if an exception occurs accessing the content repository
+     */
+    private void exportProperty( Property property,
+                                 ContentHandler contentHandler,
+                                 boolean skipBinary ) throws RepositoryException, SAXException {
+        assert property instanceof AbstractJcrProperty : "Illegal attempt to use " + getClass().getName()
+                                                         + " on non-DNA property";
+
+        AbstractJcrProperty prop = (AbstractJcrProperty)property;
+
+        Name propertyName = prop.getDnaProperty().getName();
+        if (SPECIAL_PROPERTY_NAMES.contains(propertyName)) {
+            return;
+        }
+
+        if (skipBinary && PropertyType.BINARY == prop.getType()) {
+            return;
+        }
+
+        emitProperty(property, contentHandler);
+    }
+
+    /**
+     * Fires the appropriate SAX events on the content handler to build the XML elements for the property.
+     * 
+     * @param property the property to be exported
+     * @param contentHandler the SAX content handler for which SAX events will be invoked as the XML document is created.
+     * @throws SAXException if an exception occurs during generation of the XML document
+     * @throws RepositoryException if an exception occurs accessing the content repository
+     */
+    private void emitProperty( Property property,
+                               ContentHandler contentHandler ) throws RepositoryException, SAXException {
+        assert property instanceof AbstractJcrProperty : "Illegal attempt to use " + getClass().getName()
+                                                         + " on non-DNA property";
+
+        ExecutionContext executionContext = session.getExecutionContext();
+        AbstractJcrProperty prop = (AbstractJcrProperty)property;
+
+        // first set the property sv:name attribute
+        AttributesImpl propAtts = new AttributesImpl();
+        propAtts.addAttribute(JcrSvLexicon.NAME.getNamespaceUri(),
+                              JcrSvLexicon.NAME.getLocalName(),
+                              JcrSvLexicon.NAME.getString(executionContext.getNamespaceRegistry()),
+                              PropertyType.nameFromValue(PropertyType.STRING),
+                              prop.getName());
+
+        // and it's sv:type attribute
+        propAtts.addAttribute(JcrSvLexicon.TYPE.getNamespaceUri(),
+                              JcrSvLexicon.TYPE.getLocalName(),
+                              JcrSvLexicon.TYPE.getString(executionContext.getNamespaceRegistry()),
+                              PropertyType.nameFromValue(PropertyType.STRING),
+                              PropertyType.nameFromValue(prop.getType()));
+
+        // output the sv:property element
+        startElement(contentHandler, JcrSvLexicon.PROPERTY, propAtts);
+
+        // then output a sv:value element for each of its values
+        if (prop instanceof JcrMultiValueProperty) {
+            Value[] values = prop.getValues();
+            for (int i = 0; i < values.length; i++) {
+
+                emitValue(values[i], contentHandler, property.getType());
+            }
+        } else {
+            emitValue(property.getValue(), contentHandler, property.getType());
+        }
+
+        // end the sv:property element
+        endElement(contentHandler, JcrSvLexicon.PROPERTY);
+    }
+
+    /**
+     * Fires the appropriate SAX events on the content handler to build the XML elements for the value.
+     * 
+     * @param value the value to be exported
+     * @param contentHandler the SAX content handler for which SAX events will be invoked as the XML document is created.
+     * @param propertyType the {@link PropertyType} for the given value
+     * @throws SAXException if an exception occurs during generation of the XML document
+     * @throws RepositoryException if an exception occurs accessing the content repository
+     */
+    private void emitValue( Value value,
+                            ContentHandler contentHandler,
+                            int propertyType ) throws RepositoryException, SAXException {
+
+        if (PropertyType.BINARY == propertyType) {
+            startElement(contentHandler, JcrSvLexicon.VALUE, null);
+
+            byte[] bytes = new byte[BASE_64_BUFFER_SIZE];
+            int len;
+
+            try {
+                InputStream stream = new Base64.InputStream(value.getStream(), Base64.ENCODE | Base64.URL_SAFE
+                                                                               | Base64.DONT_BREAK_LINES);
+
+                while (-1 != (len = stream.read(bytes))) {
+                    contentHandler.characters(new String(bytes, 0, len).toCharArray(), 0, len);
+                }
+            } catch (IOException ioe) {
+                throw new RepositoryException(ioe);
+            }
+
+            endElement(contentHandler, JcrSvLexicon.VALUE);
+        } else {
+            String s = value.getString();
+
+            // Per 6.4.1.2 Rule #7 of the JCR 1.0 spec, need to check invalid XML characters
+
+            char[] chars = s.toCharArray();
+
+            boolean allCharsAreValidXml = true;
+            for (int i = 0; i < chars.length; i++) {
+                if (!XmlCharacters.isValid(chars[i])) {
+                    allCharsAreValidXml = false;
+                    break;
+                }
+            }
+
+            if (allCharsAreValidXml) {
+                
+                startElement(contentHandler, JcrSvLexicon.VALUE, null);
+                contentHandler.characters(chars, 0, chars.length);
+                endElement(contentHandler, JcrSvLexicon.VALUE);
+            } else {
+                AttributesImpl valueAtts = new AttributesImpl();
+                valueAtts.addAttribute("xsi", "type", "xsi:type", "STRING", "xsd:base64Binary");
+
+                startElement(contentHandler, JcrSvLexicon.VALUE, valueAtts);
+                chars = Base64.encodeBytes(s.getBytes(), Base64.URL_SAFE).toCharArray();
+                contentHandler.characters(chars, 0, chars.length);
+                endElement(contentHandler, JcrSvLexicon.VALUE);
+            }
+        }
+
+    }
+
+}


Property changes on: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrSystemViewExporter.java
___________________________________________________________________
Name: svn:mime-type
   + text/plain

Modified: trunk/dna-jcr/src/main/resources/org/jboss/dna/jcr/JcrI18n.properties
===================================================================
--- trunk/dna-jcr/src/main/resources/org/jboss/dna/jcr/JcrI18n.properties	2009-03-09 17:57:18 UTC (rev 763)
+++ trunk/dna-jcr/src/main/resources/org/jboss/dna/jcr/JcrI18n.properties	2009-03-10 15:17:23 UTC (rev 764)
@@ -53,7 +53,7 @@
 invalidNamePattern = The "{1}" name pattern contained the '{0}' character, which is not allowed in a name pattern
 itemNotFoundWithUuid = An item with UUID "{0}" could not be found in workspace "{1}"
 errorWhileFindingNodeWithUuid = Error while finding the item with UUID "{0}" in workspace "{1}"
-nodeDefinitionCouldBeDeterminedForNode = Unable to determine a valid node definition for the node "{0}" in workspace "{1}"
+nodeDefinitionCouldNotBeDeterminedForNode = Unable to determine a valid node definition for the node "{0}" in workspace "{1}"
 
 REP_NAME_DESC = DNA Repository
 REP_VENDOR_DESC = JBoss - A division of Red Hat Middleware LLC

Modified: trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrTckTest.java
===================================================================
--- trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrTckTest.java	2009-03-09 17:57:18 UTC (rev 763)
+++ trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrTckTest.java	2009-03-10 15:17:23 UTC (rev 764)
@@ -108,8 +108,8 @@
             addTestSuite(org.apache.jackrabbit.test.api.SessionReadMethodsTest.class);
             // addTestSuite(org.apache.jackrabbit.test.api.WorkspaceReadMethodsTest.class);
             addTestSuite(org.apache.jackrabbit.test.api.ReferenceableRootNodesTest.class);
-            // addTestSuite(org.apache.jackrabbit.test.api.ExportSysViewTest.class);
-            // addTestSuite(org.apache.jackrabbit.test.api.ExportDocViewTest.class);
+            addTestSuite(org.apache.jackrabbit.test.api.ExportSysViewTest.class);
+            addTestSuite(org.apache.jackrabbit.test.api.ExportDocViewTest.class);
             addTestSuite(org.apache.jackrabbit.test.api.RepositoryLoginTest.class);
 
             // These might not all be level one tests

Modified: trunk/dna-jcr/src/test/resources/repositoryJackrabbitTck.xml
===================================================================
--- trunk/dna-jcr/src/test/resources/repositoryJackrabbitTck.xml	2009-03-09 17:57:18 UTC (rev 763)
+++ trunk/dna-jcr/src/test/resources/repositoryJackrabbitTck.xml	2009-03-10 15:17:23 UTC (rev 764)
@@ -26,9 +26,13 @@
   -->
 <testroot xmlns:jcr="http://www.jcp.org/jcr/1.0" 
             xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
+            xmlns:dna="http://www.jboss.org/dna/1.0"
     jcr:primaryType="nt:unstructured">
-	<nt:unstructured jcr:name="node1" jcr:primaryType="nt:unstructured" prop1="foo" />
-	<nt:unstructured jcr:name="node2" jcr:primaryType="nt:unstructured" prop2="bar" />
-	<nt:unstructured jcr:name="node3" jcr:primaryType="nt:unstructured" />
-	<nt:unstructured jcr:name="node4" jcr:primaryType="nt:unstructured" />
+	<nt:unstructured jcr:name="node1" prop1="&lt;foo&amp;foo&gt;" >
+	<!--  This stanza checks for the jcr:xmltext special case for document export.  DO NOT change this element. -->
+	   <dna:xmlText jcr:name="jcr:xmltext" jcr:xmlcharacters="This is my &quot;XML&quot; text!" />
+	</nt:unstructured>
+	<nt:unstructured jcr:name="node2 has a multi-word name" prop2="bar" />
+	<nt:unstructured jcr:name="node3" />
+	<nt:unstructured jcr:name="node4" />
 </testroot>
\ No newline at end of file




More information about the dna-commits mailing list