You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by ba...@apache.org on 2013/03/07 19:18:19 UTC

svn commit: r1453985 [1/2] - in /jackrabbit/oak/trunk/oak-jcr: ./ src/main/java/org/apache/jackrabbit/oak/jcr/ src/main/java/org/apache/jackrabbit/oak/jcr/xml/

Author: baedke
Date: Thu Mar  7 18:18:19 2013
New Revision: 1453985

URL: http://svn.apache.org/r1453985
Log:
OAK-127: initial version of session import

Added:
    jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/xml/BufferedStringValue.java
    jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/xml/DocViewImportHandler.java
    jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/xml/ImportHandler.java
    jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/xml/Importer.java
    jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/xml/SessionImporter.java
    jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/xml/StringValue.java
    jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/xml/SysViewImportHandler.java
    jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/xml/TargetImportHandler.java
Removed:
    jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/xml/DocumentViewHandler.java
    jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/xml/XmlImportHandler.java
Modified:
    jackrabbit/oak/trunk/oak-jcr/pom.xml
    jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/SessionImpl.java

Modified: jackrabbit/oak/trunk/oak-jcr/pom.xml
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/pom.xml?rev=1453985&r1=1453984&r2=1453985&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/pom.xml (original)
+++ jackrabbit/oak/trunk/oak-jcr/pom.xml Thu Mar  7 18:18:19 2013
@@ -67,8 +67,39 @@
       org.apache.jackrabbit.test.api.WorkspaceMoveTest#testMoveNodesLocked                             <!-- OAK-118 -->
       org.apache.jackrabbit.test.api.WorkspaceMoveTest#testMoveNodesAccessDenied                       <!-- OAK-118 -->
       org.apache.jackrabbit.test.api.CheckPermissionTest
-      org.apache.jackrabbit.test.api.DocumentViewImportTest                                            <!-- OAK-127 -->
-      org.apache.jackrabbit.test.api.SerializationTest                                                 <!-- OAK-127 -->
+      org.apache.jackrabbit.test.api.DocumentViewImportTest#testSameUUIDAtAncestorWorkspaceHandler              <!-- OAK-127: no workspace import yet -->
+      org.apache.jackrabbit.test.api.DocumentViewImportTest#testSameUUIDAtAncestorWorkspace                     <!-- OAK-127: no workspace import yet -->
+      org.apache.jackrabbit.test.api.DocumentViewImportTest#testSessionGetImportContentHandler                  <!-- OAK-127: no workspace import yet, test method is misnamed -->
+      org.apache.jackrabbit.test.api.DocumentViewImportTest#testWorkspaceImportXml                              <!-- OAK-127: no workspace import yet -->
+      org.apache.jackrabbit.test.api.SerializationTest#testVersioningExceptionFileParentWorkspaceContentHandler <!-- OAK-127: no workspace import yet, NodeImpl.checkin() not implemented -->
+      org.apache.jackrabbit.test.api.SerializationTest#testVersioningExceptionFileParentSessionContentHandler   <!-- OAK-127: NodeImpl.checkin() not implemented -->
+      org.apache.jackrabbit.test.api.SerializationTest#testVersioningExceptionFileParentWorkspace               <!-- OAK-127: no workspace import yet, NodeImpl.checkin() not implemented -->
+      org.apache.jackrabbit.test.api.SerializationTest#testVersioningExceptionFileParentSession                 <!-- OAK-127: NodeImpl.checkin() not implemented -->
+      org.apache.jackrabbit.test.api.SerializationTest#testVersioningExceptionFileChildWorkspaceContentHandler  <!-- OAK-127: no workspace import yet, NodeImpl.checkin() not implemented -->
+      org.apache.jackrabbit.test.api.SerializationTest#testVersioningExceptionFileChildSessionContentHandler    <!-- OAK-127: NodeImpl.checkin() not implemented -->
+      org.apache.jackrabbit.test.api.SerializationTest#testVersioningExceptionFileChildWorkspace                <!-- OAK-127: no workspace import yet, NodeImpl.checkin() not implemented -->
+      org.apache.jackrabbit.test.api.SerializationTest#testVersioningExceptionFileChildSession                  <!-- OAK-127: NodeImpl.checkin() not implemented -->
+      org.apache.jackrabbit.test.api.SerializationTest#testLockExceptionWorkspaceWithHandler                    <!-- OAK-127: no workspace import yet, no session scoped locking -->
+      org.apache.jackrabbit.test.api.SerializationTest#testLockExceptionSessionWithHandler                      <!-- OAK-127: no session scoped locking -->
+      org.apache.jackrabbit.test.api.SerializationTest#testLockExceptionWorkspace                               <!-- OAK-127: no workspace import yet, no session scoped locking -->
+      org.apache.jackrabbit.test.api.SerializationTest#testLockExceptionSession                                 <!-- OAK-127: no session scoped locking -->
+      org.apache.jackrabbit.test.api.SerializationTest#testWorkspaceGetImportContentHandlerExceptions           <!-- OAK-127: no workspace import yet -->
+      org.apache.jackrabbit.test.api.SerializationTest#testWorkspaceImportXmlExceptions                         <!-- OAK-127: no workspace import yet -->
+      org.apache.jackrabbit.test.api.SerializationTest#testOverwriteExceptionWorkspaceWithHandler               <!-- OAK-127: no workspace import yet -->
+      org.apache.jackrabbit.test.api.SerializationTest#testOverwriteExceptionWorkspace                          <!-- OAK-127: no workspace import yet -->
+      org.apache.jackrabbit.test.api.SerializationTest#testNodeTypeConstraintViolationWorkspaceWithHandler      <!-- OAK-127: no workspace import yet -->
+      org.apache.jackrabbit.test.api.SerializationTest#testNodeTypeConstraintViolationWorkspace                 <!-- OAK-127: no workspace import yet -->
+      org.apache.jackrabbit.test.api.SerializationTest#testStreamHandling                                       <!-- OAK-127: no workspace import yet -->
+      org.apache.jackrabbit.test.api.SerializationTest#testInvalidXmlThrowsSaxException                         <!-- OAK-127: no workspace import yet -->
+      org.apache.jackrabbit.test.api.SerializationTest#testInvalidXmlThrowsInvalidSerializedDataException       <!-- OAK-127: no workspace import yet -->
+      org.apache.jackrabbit.test.api.SerializationTest#testExportSysView_stream_workspace_skipBinary_noRecurse  <!-- OAK-127: no workspace import yet -->
+      org.apache.jackrabbit.test.api.SerializationTest#testExportSysView_stream_workspace_skipBinary_recurse    <!-- OAK-127: no workspace import yet -->
+      org.apache.jackrabbit.test.api.SerializationTest#testExportSysView_stream_workspace_saveBinary_noRecurse  <!-- OAK-127: no workspace import yet -->
+      org.apache.jackrabbit.test.api.SerializationTest#testExportSysView_stream_workspace_saveBinary_recurse    <!-- OAK-127: no workspace import yet -->
+      org.apache.jackrabbit.test.api.SerializationTest#testExportSysView_handler_workspace_skipBinary_noRecurse <!-- OAK-127: no workspace import yet -->
+      org.apache.jackrabbit.test.api.SerializationTest#testExportSysView_handler_workspace_skipBinary_recurse   <!-- OAK-127: no workspace import yet -->
+      org.apache.jackrabbit.test.api.SerializationTest#testExportSysView_handler_workspace_saveBinary_noRecurse <!-- OAK-127: no workspace import yet -->
+      org.apache.jackrabbit.test.api.SerializationTest#testExportSysView_handler_workspace_saveBinary_recurse   <!-- OAK-127: no workspace import yet -->
       org.apache.jackrabbit.test.api.HasPermissionTest
       org.apache.jackrabbit.test.api.lock.LockManagerTest#testAddInvalidLockToken                      <!-- OAK-150 ... -->
       org.apache.jackrabbit.test.api.lock.LockManagerTest#testLockNonLockable

Modified: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/SessionImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/SessionImpl.java?rev=1453985&r1=1453984&r2=1453985&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/SessionImpl.java (original)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/SessionImpl.java Thu Mar  7 18:18:19 2013
@@ -46,7 +46,7 @@ import org.apache.jackrabbit.api.securit
 import org.apache.jackrabbit.commons.AbstractSession;
 import org.apache.jackrabbit.oak.api.TreeLocation;
 import org.apache.jackrabbit.oak.commons.PathUtils;
-import org.apache.jackrabbit.oak.jcr.xml.XmlImportHandler;
+import org.apache.jackrabbit.oak.jcr.xml.ImportHandler;
 import org.apache.jackrabbit.oak.spi.security.authentication.ImpersonationCredentials;
 import org.apache.jackrabbit.oak.util.TODO;
 import org.apache.jackrabbit.util.Text;
@@ -307,8 +307,7 @@ public class SessionImpl extends Abstrac
     @Nonnull
     public ContentHandler getImportContentHandler(
             String parentAbsPath, int uuidBehavior) throws RepositoryException {
-        final Node parent = getNode(parentAbsPath);
-        return new XmlImportHandler(parent, uuidBehavior);
+        return new ImportHandler(getNode(parentAbsPath), dlg.getRoot(), this, uuidBehavior);
     }
 
     /**

Added: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/xml/BufferedStringValue.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/xml/BufferedStringValue.java?rev=1453985&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/xml/BufferedStringValue.java (added)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/xml/BufferedStringValue.java Thu Mar  7 18:18:19 2013
@@ -0,0 +1,347 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.jcr.xml;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.io.Writer;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+import javax.jcr.ValueFactory;
+
+import org.apache.jackrabbit.oak.namepath.NamePathMapper;
+import org.apache.jackrabbit.oak.spi.xml.TextValue;
+import org.apache.jackrabbit.util.Base64;
+import org.apache.jackrabbit.util.TransientFileFactory;
+import org.apache.jackrabbit.value.ValueHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * <code>BufferedStringValue</code> represents an appendable
+ * serialized value that is either buffered in-memory or backed
+ * by a temporary file if its size exceeds a certain limit.
+ * <p/>
+ * <b>Important:</b> Note that in order to free resources
+ * <code>{@link #dispose()}</code> should be called as soon as
+ * <code>BufferedStringValue</code> instance is not used anymore.
+ */
+class BufferedStringValue implements TextValue {
+
+    private static Logger log = LoggerFactory.getLogger(BufferedStringValue.class);
+
+    /**
+     * The maximum size for buffering data in memory.
+     */
+    private static final int MAX_BUFFER_SIZE = 0x10000;
+
+    /**
+     * The in-memory buffer.
+     */
+    private StringWriter buffer;
+
+    /**
+     * The number of characters written so far.
+     * If the in-memory buffer is used, this is position within buffer (size of actual data in buffer)
+     */
+    private long length;
+
+    /**
+     * Backing temporary file created when size of data exceeds
+     * MAX_BUFFER_SIZE.
+     */
+    private File tmpFile;
+
+    /**
+     * Writer used to write to tmpFile.
+     */
+    private Writer writer;
+
+    private final NamePathMapper namePathMapper;
+    private final ValueFactory valueFactory;
+
+    /**
+     * Whether the value is base64 encoded.
+     */
+    private boolean base64;
+
+    /**
+     * Constructs a new empty <code>BufferedStringValue</code>.
+     */
+    protected BufferedStringValue(ValueFactory valueFactory, NamePathMapper namePathMapper) {
+        buffer = new StringWriter();
+        length = 0;
+        tmpFile = null;
+        writer = null;
+        this.namePathMapper = namePathMapper;
+        this.valueFactory = valueFactory;
+    }
+
+    /**
+     * Returns the length of the serialized value.
+     *
+     * @return the length of the serialized value
+     * @throws IOException if an I/O error occurs
+     */
+    public long length() throws IOException {
+        return length;
+    }
+
+    @Override
+    public String getString() {
+        try {
+            return retrieveString();
+        } catch (IOException e) {
+            log.error("could not retrieve string value", e);
+            return "";
+        }
+    }
+
+    private String retrieveString() throws IOException {
+        String value = retrieve();
+        if (base64) {
+            ByteArrayOutputStream out = new ByteArrayOutputStream();
+            Base64.decode(value, out);
+            value = new String(out.toByteArray(), "UTF-8");
+        }
+        return value;
+    }
+
+    /**
+     * Retrieves the serialized value.
+     *
+     * @return the serialized value
+     * @throws IOException if an I/O error occurs
+     */
+    public String retrieve() throws IOException {
+        if (buffer != null) {
+            return buffer.toString();
+        } else if (tmpFile != null) {
+            // close writer first
+            writer.close();
+            if (tmpFile.length() > Integer.MAX_VALUE) {
+                throw new IOException("size of value is too big, use reader()");
+            }
+            StringBuilder sb = new StringBuilder((int) length);
+            char[] chunk = new char[0x2000];
+            Reader reader = openReader();
+            try {
+                int read;
+                while ((read = reader.read(chunk)) > -1) {
+                    sb.append(chunk, 0, read);
+                }
+            } finally {
+                reader.close();
+            }
+            return sb.toString();
+        } else {
+            throw new IOException("this instance has already been disposed");
+        }
+    }
+
+    private Reader openReader() throws IOException {
+        return new InputStreamReader(
+                new BufferedInputStream(new FileInputStream(tmpFile)), "UTF-8");
+    }
+
+    /**
+     * Returns a <code>Reader</code> for reading the serialized value.
+     *
+     * @return a <code>Reader</code> for reading the serialized value.
+     * @throws IOException if an I/O error occurs
+     */
+    public Reader reader() throws IOException {
+        if (buffer != null) {
+            return new StringReader(retrieve());
+        } else if (tmpFile != null) {
+            // close writer first
+            writer.close();
+            return openReader();
+        } else {
+            throw new IOException("this instance has already been disposed");
+        }
+    }
+
+    /**
+     * Append a portion of an array of characters.
+     *
+     * @param chars the characters to be appended
+     * @param start the index of the first character to append
+     * @param len   the number of characters to append
+     * @throws IOException if an I/O error occurs
+     */
+    public void append(char[] chars, int start, int len)
+            throws IOException {
+        if (buffer != null) {
+            if (this.length + len > MAX_BUFFER_SIZE) {
+                // threshold for keeping data in memory exceeded;
+                // create temp file and spool buffer contents
+                TransientFileFactory fileFactory = TransientFileFactory.getInstance();
+                tmpFile = fileFactory.createTransientFile("txt", null, null);
+                BufferedOutputStream fout = new BufferedOutputStream(new FileOutputStream(tmpFile));
+                writer = new OutputStreamWriter(fout, "UTF-8");
+                writer.write(buffer.toString());
+                writer.write(chars, start, len);
+                // reset the in-memory buffer
+                buffer = null;
+            } else {
+                buffer.write(chars, start, len);
+            }
+        } else if (tmpFile != null) {
+            writer.write(chars, start, len);
+        } else {
+            throw new IOException("this instance has already been disposed");
+        }
+        length += len;
+    }
+
+    /**
+     * Close this value. Once a value has been closed,
+     * further append() invocations will cause an IOException to be thrown.
+     *
+     * @throws IOException if an I/O error occurs
+     */
+    public void close() throws IOException {
+        if (buffer != null) {
+            // nop
+        } else if (tmpFile != null) {
+            writer.close();
+        } else {
+            throw new IOException("this instance has already been disposed");
+        }
+    }
+
+    //--------------------------------------------------------< TextValue >
+
+    public Value getValue(int targetType) throws RepositoryException {
+        try {
+            if (targetType == PropertyType.NAME) {
+                return ValueHelper.deserialize(
+                        namePathMapper.getOakName(retrieveString()), targetType, false, valueFactory);
+            } else if (targetType == PropertyType.PATH) {
+                return ValueHelper.deserialize(
+                        namePathMapper.getOakPath(retrieveString()), targetType, false, valueFactory);
+            } else if (targetType == PropertyType.BINARY) {
+                if (length() < 0x10000) {
+                    // < 65kb: deserialize BINARY type using String
+                    return ValueHelper.deserialize(retrieve(), targetType, false, valueFactory);
+                } else {
+                    // >= 65kb: deserialize BINARY type using Reader
+                    Reader reader = reader();
+                    try {
+                        return ValueHelper.deserialize(reader, targetType, false, valueFactory);
+                    } finally {
+                        reader.close();
+                    }
+                }
+            } else {
+                // all other types
+                return ValueHelper.deserialize(retrieveString(), targetType, false, valueFactory);
+            }
+        } catch (IOException e) {
+            String msg = "failed to retrieve serialized value";
+            log.debug(msg, e);
+            throw new RepositoryException(msg, e);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void dispose() {
+        if (buffer != null) {
+            buffer = null;
+        } else if (tmpFile != null) {
+            try {
+                writer.close();
+                tmpFile.delete();
+                tmpFile = null;
+                writer = null;
+            } catch (IOException e) {
+                log.warn("Problem disposing property value", e);
+            }
+        } else {
+            log.warn("this instance has already been disposed");
+        }
+    }
+
+    /**
+     * This class converts the text read Converts a base64 reader to an input stream.
+     */
+    private static class Base64ReaderInputStream extends InputStream {
+
+        private static final int BUFFER_SIZE = 1024;
+        private final char[] chars;
+        private final ByteArrayOutputStream out;
+        private final Reader reader;
+        private int pos;
+        private int remaining;
+        private byte[] buffer;
+
+        public Base64ReaderInputStream(Reader reader) {
+            chars = new char[BUFFER_SIZE];
+            this.reader = reader;
+            out = new ByteArrayOutputStream(BUFFER_SIZE);
+        }
+
+        private void fillBuffer() throws IOException {
+            int len = reader.read(chars, 0, BUFFER_SIZE);
+            if (len < 0) {
+                remaining = -1;
+                return;
+            }
+            Base64.decode(chars, 0, len, out);
+            buffer = out.toByteArray();
+            pos = 0;
+            remaining = buffer.length;
+            out.reset();
+        }
+
+        public int read() throws IOException {
+            if (remaining == 0) {
+                fillBuffer();
+            }
+            if (remaining < 0) {
+                return -1;
+            }
+            remaining--;
+            return buffer[pos++] & 0xff;
+        }
+    }
+
+    /**
+     * Whether this value is base64 encoded
+     *
+     * @param base64 the flag
+     */
+    public void setBase64(boolean base64) {
+        this.base64 = base64;
+    }
+
+}

Added: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/xml/DocViewImportHandler.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/xml/DocViewImportHandler.java?rev=1453985&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/xml/DocViewImportHandler.java (added)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/xml/DocViewImportHandler.java Thu Mar  7 18:18:19 2013
@@ -0,0 +1,330 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.jcr.xml;
+
+
+import java.io.IOException;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Stack;
+import javax.jcr.NamespaceRegistry;
+import javax.jcr.Node;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.ValueFactory;
+import javax.jcr.nodetype.ConstraintViolationException;
+
+import org.apache.jackrabbit.commons.NamespaceHelper;
+import org.apache.jackrabbit.oak.namepath.JcrNameParser;
+import org.apache.jackrabbit.oak.plugins.name.NamespaceConstants;
+import org.apache.jackrabbit.oak.spi.xml.NodeInfo;
+import org.apache.jackrabbit.oak.spi.xml.PropInfo;
+import org.apache.jackrabbit.oak.spi.xml.TextValue;
+import org.apache.jackrabbit.util.ISO9075;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+
+/**
+ * <code>DocViewImportHandler</code> processes Document View XML SAX events
+ * and 'translates' them into <code>{@link Importer}</code> method calls.
+ */
+class DocViewImportHandler extends TargetImportHandler {
+
+    private static Logger log = LoggerFactory.getLogger(DocViewImportHandler.class);
+
+    /**
+     * stack of NodeInfo instances; an instance is pushed onto the stack
+     * in the startElement method and is popped from the stack in the
+     * endElement method.
+     */
+    private final Stack<NodeInfo> stack = new Stack<NodeInfo>();
+    // buffer used to merge adjacent character data
+    private BufferedStringValue textHandler = null;
+
+    /**
+     * Constructs a new <code>DocViewImportHandler</code>.
+     *
+     * @param importer     the importer
+     * @param valueFactory a value factory
+     */
+    DocViewImportHandler(Importer importer, ValueFactory valueFactory, NamespaceHelper helper) {
+        super(importer, valueFactory, helper);
+    }
+
+    /**
+     * Parses the given string as a list of JCR names. Any whitespace sequence
+     * is supported as a names separator instead of just a single space to
+     * be more liberal in what we accept. The current namespace context is
+     * used to convert the prefixed name strings to QNames.
+     *
+     * @param value string value
+     * @return the parsed names
+     * @throws SAXException if an invalid name was encountered
+     */
+    private String[] parseNames(String value) throws SAXException {
+        String[] names = value.split("\\p{Space}+");
+        String[] qnames = new String[names.length];
+        for (int i = 0; i < names.length; i++) {
+            try {
+                qnames[i] = new NameInfo(names[i]).getRepoQualifiedName();
+            } catch (RepositoryException e) {
+                throw new SAXException("Invalid name: " + names[i], e);
+            }
+        }
+        return qnames;
+    }
+
+    /**
+     * Appends the given character data to the internal buffer.
+     *
+     * @param ch     the characters to be appended
+     * @param start  the index of the first character to append
+     * @param length the number of characters to append
+     * @throws SAXException if an error occurs
+     * @see #characters(char[], int, int)
+     * @see #ignorableWhitespace(char[], int, int)
+     * @see #processCharacters()
+     */
+    private void appendCharacters(char[] ch, int start, int length)
+            throws SAXException {
+        if (textHandler == null) {
+            textHandler = new BufferedStringValue(valueFactory, currentNamePathMapper());
+        }
+        try {
+            textHandler.append(ch, start, length);
+        } catch (IOException ioe) {
+            String msg = "internal error while processing internal buffer data";
+            log.error(msg, ioe);
+            throw new SAXException(msg, ioe);
+        }
+    }
+
+    /**
+     * Translates character data reported by the
+     * <code>{@link #characters(char[], int, int)}</code> &
+     * <code>{@link #ignorableWhitespace(char[], int, int)}</code> SAX events
+     * into a  <code>jcr:xmltext</code> child node with one
+     * <code>jcr:xmlcharacters</code> property.
+     *
+     * @throws SAXException if an error occurs
+     * @see #appendCharacters(char[], int, int)
+     */
+    private void processCharacters()
+            throws SAXException {
+        try {
+            if (textHandler != null && textHandler.length() > 0) {
+                // there is character data that needs to be added to
+                // the current node
+
+                // check for pure whitespace character data
+                Reader reader = textHandler.reader();
+                try {
+                    int ch;
+                    while ((ch = reader.read()) != -1) {
+                        if (ch > 0x20) {
+                            break;
+                        }
+                    }
+                    if (ch == -1) {
+                        // the character data consists of pure whitespace, ignore
+                        log.debug("ignoring pure whitespace character data...");
+                        // reset handler
+                        textHandler.dispose();
+                        textHandler = null;
+                        return;
+                    }
+                } finally {
+                    reader.close();
+                }
+
+                NodeInfo node =
+                        new NodeInfo(helper.getJcrName(NamespaceRegistry.NAMESPACE_JCR, "xmltext"), null, null, null);
+                TextValue[] values =
+                        new TextValue[]{textHandler};
+                ArrayList<PropInfo> props = new ArrayList<PropInfo>();
+                props.add(new PropInfo(helper.getJcrName(NamespaceRegistry.NAMESPACE_JCR, "xmlcharacters"),
+                        PropertyType.STRING, values));
+                // call Importer
+                importer.startNode(node, props);
+                importer.endNode(node);
+
+                // reset handler
+                textHandler.dispose();
+                textHandler = null;
+            }
+        } catch (IOException ioe) {
+            String msg = "internal error while processing internal buffer data";
+            log.error(msg, ioe);
+            throw new SAXException(msg, ioe);
+        } catch (RepositoryException re) {
+            throw new SAXException(re);
+        }
+    }
+
+    /**
+     * Processes the given <code>name</code>, i.e. decodes it and checks
+     * the format of the decoded name.
+     *
+     * @param nameInfo name to process
+     * @return the decoded and valid jcr name or the original name if it is
+     *         not encoded or if the resulting decoded name would be illegal.
+     */
+    private NameInfo processName(NameInfo nameInfo) throws RepositoryException {
+        String decodedLocalName = ISO9075.decode(nameInfo.getLocalName());
+        NameInfo decodedNameInfo = new NameInfo(nameInfo.getDocPrefix(), decodedLocalName);
+        if (!decodedLocalName.equals(nameInfo.getLocalName())) {
+            try {
+                JcrNameParser.checkName(decodedNameInfo.getRepoQualifiedName(), true);
+            } catch (ConstraintViolationException e) {
+                // decoded name would be illegal according to jsr 170,
+                // use encoded name as a fallback
+                log.warn("encountered illegal decoded name '" + decodedLocalName + "'", e);
+                return nameInfo;
+            }
+        }
+        return decodedNameInfo;
+    }
+
+    //-------------------------------------------------------< ContentHandler >
+
+    /**
+     * {@inheritDoc}
+     * <p/>
+     * See also {@link org.apache.jackrabbit.commons.xml.Exporter#exportProperties(Node)}
+     * regarding special handling of multi-valued properties on export.
+     */
+    @Override
+    public void startElement(String namespaceURI, String localName,
+                             String qName, Attributes atts)
+            throws SAXException {
+        // process buffered character data
+        processCharacters();
+
+        try {
+            NameInfo nameInfo = new NameInfo(qName);
+            nameInfo = processName(nameInfo);
+
+            // properties
+            String id = null;
+            String nodeTypeName = null;
+            String[] mixinTypes = null;
+
+            List<PropInfo> props = new ArrayList<PropInfo>(atts.getLength());
+            for (int i = 0; i < atts.getLength(); i++) {
+                if (atts.getURI(i).equals(NamespaceConstants.NAMESPACE_XMLNS)) {
+                    // skip namespace declarations reported as attributes
+                    // see http://issues.apache.org/jira/browse/JCR-620#action_12448164
+                    continue;
+                }
+
+                NameInfo propNameInfo = new NameInfo(atts.getQName(i));
+                propNameInfo = processName(propNameInfo);
+
+                // value(s)
+                String attrValue = atts.getValue(i);
+                TextValue[] propValues;
+
+                // always assume single-valued property for the time being
+                // until a way of properly serializing/detecting multi-valued
+                // properties on re-import is found (see JCR-325);
+                // see also DocViewSAXEventGenerator#leavingProperties(Node, int)
+                // TODO proper multi-value serialization support
+                propValues = new TextValue[1];
+                propValues[0] = new StringValue(attrValue, valueFactory, currentNamePathMapper());
+
+                if (NamespaceRegistry.NAMESPACE_JCR.equals(propNameInfo.getNamespaceUri())
+                        && "primaryType".equals(propNameInfo.getLocalName())) {
+                    // jcr:primaryType
+                    if (attrValue.length() > 0) {
+                        //TODO
+                        nodeTypeName = attrValue;
+                    }
+                } else if (NamespaceRegistry.NAMESPACE_JCR.equals(propNameInfo.getNamespaceUri())
+                        && "mixinTypes".equals(propNameInfo.getLocalName())) {
+                    // jcr:mixinTypes
+                    mixinTypes = parseNames(attrValue);
+                } else if (NamespaceRegistry.NAMESPACE_JCR.equals(propNameInfo.getNamespaceUri())
+                        && "uuid".equals(propNameInfo.getLocalName())) {
+                    // jcr:uuid
+                    if (attrValue.length() > 0) {
+                        id = attrValue;
+                    }
+                } else {
+                    props.add(new PropInfo(propNameInfo.getRepoQualifiedName(), PropertyType.UNDEFINED, propValues));
+                }
+            }
+
+            NodeInfo node =
+                    new NodeInfo(nameInfo.getRepoQualifiedName(), nodeTypeName, mixinTypes, id);
+            // all information has been collected, now delegate to importer
+            importer.startNode(node, props);
+            // push current node data onto stack
+            stack.push(node);
+        } catch (RepositoryException re) {
+            throw new SAXException(re);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void characters(char[] ch, int start, int length)
+            throws SAXException {
+        /**
+         * buffer data reported by the characters event;
+         * will be processed on the next endElement or startElement event.
+         */
+        appendCharacters(ch, start, length);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void ignorableWhitespace(char[] ch, int start, int length)
+            throws SAXException {
+        /**
+         * buffer data reported by the ignorableWhitespace event;
+         * will be processed on the next endElement or startElement event.
+         */
+        appendCharacters(ch, start, length);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void endElement(String namespaceURI, String localName, String qName)
+            throws SAXException {
+        // process buffered character data
+        processCharacters();
+
+        NodeInfo node = stack.peek();
+        try {
+            // call Importer
+            importer.endNode(node);
+        } catch (RepositoryException re) {
+            throw new SAXException(re);
+        }
+        // we're done with this node, pop it from stack
+        stack.pop();
+    }
+}

Added: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/xml/ImportHandler.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/xml/ImportHandler.java?rev=1453985&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/xml/ImportHandler.java (added)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/xml/ImportHandler.java Thu Mar  7 18:18:19 2013
@@ -0,0 +1,210 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.jcr.xml;
+
+import java.util.HashMap;
+import java.util.Map;
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.ValueFactory;
+
+import org.apache.jackrabbit.commons.NamespaceHelper;
+import org.apache.jackrabbit.oak.api.Root;
+import org.apache.jackrabbit.oak.plugins.name.NamespaceConstants;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.xml.sax.Attributes;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * An <code>ImportHandler</code> instance can be used to import serialized
+ * data in System View XML or Document View XML. Processing of the XML is
+ * handled by specialized <code>ContentHandler</code>s
+ * (i.e. <code>SysViewImportHandler</code> and <code>DocViewImportHandler</code>).
+ * <p/>
+ * The actual task of importing though is delegated to the implementation of
+ * the <code>{@link Importer}</code> interface.
+ * <p/>
+ * <b>Important Note:</b>
+ * <p/>
+ * These SAX Event Handlers expect that Namespace URI's and local names are
+ * reported in the <code>start/endElement</code> events and that
+ * <code>start/endPrefixMapping</code> events are reported
+ * (i.e. default SAX2 Namespace processing).
+ */
+public class ImportHandler extends DefaultHandler {
+
+    private static Logger log = LoggerFactory.getLogger(ImportHandler.class);
+
+    private final Importer importer;
+    private final NamespaceHelper helper;
+    private final ValueFactory valueFactory;
+    protected Locator locator;
+    private TargetImportHandler targetHandler = null;
+    private final Map<String, String> tempPrefixMap = new HashMap<String, String>();
+
+    public ImportHandler(Node importTargetNode, Root root, Session session, int uuidBehavior)
+            throws RepositoryException {
+        this.helper = new NamespaceHelper(session);
+        this.importer = new SessionImporter(importTargetNode, root, session, helper, uuidBehavior);
+        this.valueFactory = session.getValueFactory();
+    }
+
+    //---------------------------------------------------------< ErrorHandler >
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void warning(SAXParseException e) throws SAXException {
+        // log exception and carry on...
+        log.warn("warning encountered at line: " + e.getLineNumber()
+                + ", column: " + e.getColumnNumber()
+                + " while parsing XML stream", e);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void error(SAXParseException e) throws SAXException {
+        // log exception and carry on...
+        log.error("error encountered at line: " + e.getLineNumber()
+                + ", column: " + e.getColumnNumber()
+                + " while parsing XML stream: " + e.toString());
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void fatalError(SAXParseException e) throws SAXException {
+        // log and re-throw exception
+        log.error("fatal error encountered at line: " + e.getLineNumber()
+                + ", column: " + e.getColumnNumber()
+                + " while parsing XML stream: " + e.toString());
+        throw e;
+    }
+
+    //-------------------------------------------------------< ContentHandler >
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void endDocument() throws SAXException {
+        // delegate to target handler
+        if (targetHandler != null) {
+            targetHandler.endDocument();
+        }
+    }
+
+    /**
+     * Records the given namespace mapping to be included in the local
+     * namespace context. The local namespace context is instantiated
+     * in {@link #startElement(String, String, String, Attributes)} using
+     * all the the namespace mappings recorded for the current XML element.
+     * <p/>
+     * The namespace is also recorded in the persistent namespace registry
+     * unless it is already known.
+     *
+     * @param prefix namespace prefix
+     * @param uri    namespace URI
+     */
+    @Override
+    public void startPrefixMapping(String prefix, String uri)
+            throws SAXException {
+        try {
+            helper.registerNamespace(prefix, uri);
+            if (targetHandler != null) {
+                targetHandler.startPrefixMapping(prefix, uri);
+            } else {
+                tempPrefixMap.put(prefix, uri);
+            }
+        } catch (RepositoryException re) {
+            throw new SAXException(re);
+        }
+    }
+
+    @Override
+    public void endPrefixMapping(String prefix) throws SAXException {
+        if (targetHandler != null) {
+            targetHandler.endPrefixMapping(prefix);
+        } else {
+            tempPrefixMap.remove(prefix);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void startElement(String namespaceURI, String localName, String qName,
+                             Attributes atts) throws SAXException {
+        if (targetHandler == null) {
+            // the namespace of the first element determines the type of XML
+            // (system view/document view)
+            if (NamespaceConstants.NAMESPACE_SV.equals(namespaceURI)) {
+                targetHandler = new SysViewImportHandler(importer, valueFactory, helper);
+            } else {
+                targetHandler = new DocViewImportHandler(importer, valueFactory, helper);
+            }
+
+            targetHandler.startDocument();
+
+            for (Map.Entry<String, String> prefixMapping : tempPrefixMap.entrySet()) {
+                targetHandler.startPrefixMapping(prefixMapping.getKey(), prefixMapping.getValue());
+            }
+        }
+
+        // delegate to target handler
+        targetHandler.startElement(namespaceURI, localName, qName, atts);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void characters(char[] ch, int start, int length) throws SAXException {
+        // delegate to target handler
+        targetHandler.characters(ch, start, length);
+    }
+
+    /**
+     * Delegates the call to the underlying target handler and asks the
+     * handler to end the current namespace context.
+     * {@inheritDoc}
+     */
+    @Override
+    public void endElement(String namespaceURI, String localName, String qName)
+            throws SAXException {
+        targetHandler.endElement(namespaceURI, localName, qName);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setDocumentLocator(Locator locator) {
+        this.locator = locator;
+    }
+
+}

Added: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/xml/Importer.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/xml/Importer.java?rev=1453985&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/xml/Importer.java (added)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/xml/Importer.java Thu Mar  7 18:18:19 2013
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.jcr.xml;
+
+
+import java.util.List;
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.oak.spi.xml.NodeInfo;
+import org.apache.jackrabbit.oak.spi.xml.PropInfo;
+
+/**
+ * Content importer. The XML import handlers use this interface to submit
+ * the parsed content to the repository. The implementation of this class
+ * decides how the content is actually persisted; either through the
+ * transient space of a session, or directly into the workspace.
+ */
+public interface Importer {
+
+    /**
+     * Called once at the beginning of the content import.
+     *
+     * @throws RepositoryException on a repository error
+     */
+    void start() throws RepositoryException;
+
+    /**
+     * Called to start the import of a node. Information about the
+     * imported node and all it's properties are passed as arguments.
+     * Possible child nodes are imported recursively using this same
+     * method until a {@link #endNode(NodeInfo)} call is made with the
+     * same node information.
+     *
+     * @param nodeInfo  information about the node being imported
+     * @param propInfos information about the properties being imported
+     *                  (list of {@link PropInfo} instances)
+     * @throws RepositoryException on a repository error
+     */
+    void startNode(NodeInfo nodeInfo, List<PropInfo> propInfos)
+            throws RepositoryException;
+
+    /**
+     * Called to end the import of a node. This method is called after
+     * a {@link #startNode(NodeInfo, List)} call with the stame node
+     * information and after all the possible child nodes have been
+     * imported with respective startNode/endNode calls.
+     * <p/>
+     * Just like XML elements, the startNode/endNode calls are guaranteed
+     * to be properly nested and complete.
+     *
+     * @param nodeInfo information about the node being imported
+     * @throws RepositoryException on a repository error
+     */
+    void endNode(NodeInfo nodeInfo) throws RepositoryException;
+
+    /**
+     * Called once at the end of the content import.
+     *
+     * @throws RepositoryException on a repository error
+     */
+    void end() throws RepositoryException;
+}

Added: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/xml/SessionImporter.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/xml/SessionImporter.java?rev=1453985&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/xml/SessionImporter.java (added)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/xml/SessionImporter.java Thu Mar  7 18:18:19 2013
@@ -0,0 +1,539 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.jcr.xml;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Stack;
+import javax.jcr.ImportUUIDBehavior;
+import javax.jcr.ItemExistsException;
+import javax.jcr.ItemNotFoundException;
+import javax.jcr.NamespaceRegistry;
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Value;
+import javax.jcr.ValueFormatException;
+import javax.jcr.nodetype.ConstraintViolationException;
+import javax.jcr.nodetype.NodeDefinition;
+import javax.jcr.nodetype.NodeTypeManager;
+import javax.jcr.nodetype.PropertyDefinition;
+
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.jackrabbit.commons.NamespaceHelper;
+import org.apache.jackrabbit.oak.api.Root;
+import org.apache.jackrabbit.oak.namepath.GlobalNameMapper;
+import org.apache.jackrabbit.oak.namepath.NamePathMapperImpl;
+import org.apache.jackrabbit.oak.plugins.nodetype.EffectiveNodeTypeProvider;
+import org.apache.jackrabbit.oak.spi.xml.NodeInfo;
+import org.apache.jackrabbit.oak.spi.xml.PropInfo;
+import org.apache.jackrabbit.oak.spi.xml.ProtectedItemImporter;
+import org.apache.jackrabbit.oak.spi.xml.ProtectedNodeImporter;
+import org.apache.jackrabbit.oak.spi.xml.ProtectedPropertyImporter;
+import org.apache.jackrabbit.oak.spi.xml.ReferenceChangeTracker;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * <code>SessionImporter</code> ...
+ */
+public class SessionImporter implements Importer {
+
+    private static Logger log = LoggerFactory.getLogger(SessionImporter.class);
+
+    private final Session session;
+    private final Node importTargetNode;
+    private final Root root;
+    private final int uuidBehavior;
+
+    private final NamespaceHelper namespaceHelper;
+    private Stack<Node> parents;
+
+    /**
+     * helper object that keeps track of remapped uuid's and imported reference
+     * properties that might need correcting depending on the uuid mappings
+     */
+    private final ReferenceChangeTracker refTracker;
+
+    //TODO clarify how to provide ProtectedItemImporters
+    private final List<ProtectedItemImporter> pItemImporters = new ArrayList<ProtectedItemImporter>();
+    private final List<ProtectedItemImporter> pItemImportersInitialized = new ArrayList<ProtectedItemImporter>();
+
+    /**
+     * Currently active importer for protected nodes.
+     */
+    private ProtectedNodeImporter pnImporter = null;
+
+    /**
+     * Creates a new <code>SessionImporter</code> instance.
+     *
+     * @param importTargetNode the target node
+     * @param session          session
+     * @param uuidBehavior     any of the constants declared by
+     *                         {@link javax.jcr.ImportUUIDBehavior}
+     */
+    public SessionImporter(Node importTargetNode,
+                           Root root,
+                           Session session,
+                           NamespaceHelper helper,
+                           int uuidBehavior) {
+        this.importTargetNode = importTargetNode;
+        this.session = session;
+        this.root = root;
+        this.namespaceHelper = helper;
+        this.uuidBehavior = uuidBehavior;
+
+
+        refTracker = new ReferenceChangeTracker();
+
+        parents = new Stack<Node>();
+        parents.push(importTargetNode);
+
+        //TODO clarify how to provide correct NamePathMapper
+        NamePathMapperImpl namePathMapper = new NamePathMapperImpl(new GlobalNameMapper() {
+            @Override
+            protected Map<String, String> getNamespaceMap() {
+                try {
+                    return namespaceHelper.getNamespaces();
+                } catch (RepositoryException e) {
+                    log.warn("could not read namespace mappings", e);
+                    return null;
+                }
+            }
+        });
+        pItemImportersInitialized.clear();
+        for (ProtectedItemImporter importer : pItemImporters) {
+            if (importer.init(session, root, namePathMapper, false, uuidBehavior, refTracker)) {
+                pItemImportersInitialized.add(importer);
+            }
+        }
+    }
+
+    /**
+     * make sure the editing session is allowed create nodes with a
+     * specified node type (and ev. mixins),<br>
+     * NOTE: this check is not executed in a single place as the parent
+     * may change in case of
+     * {@link javax.jcr.ImportUUIDBehavior#IMPORT_UUID_COLLISION_REPLACE_EXISTING IMPORT_UUID_COLLISION_REPLACE_EXISTING}.
+     *
+     * @param parent   parent node
+     * @param nodeName the name
+     * @throws javax.jcr.RepositoryException if an error occurs
+     */
+    protected void checkPermission(Node parent, String nodeName)
+            throws RepositoryException {
+        //TODO clarify how to check permissions (is it necessary at all?)
+//        if (!session.getAccessControlManager().isGranted(session.getQPath(parent.getPath()), nodeName, Permissions.NODE_TYPE_MANAGEMENT)) {
+//            throw new AccessDeniedException("Insufficient permission.");
+//        }
+    }
+
+    protected Node createNode(Node parent,
+                              String nodeName,
+                              String nodeTypeName,
+                              String[] mixinNames)
+            throws RepositoryException {
+        Node node;
+
+
+        // add node
+        node = parent.addNode(nodeName, nodeTypeName == null ? namespaceHelper.getJcrName(NamespaceRegistry.NAMESPACE_NT, "unstructured") : nodeTypeName);
+        // add mixins
+        if (mixinNames != null) {
+            for (String mixinName : mixinNames) {
+                node.addMixin(mixinName);
+            }
+        }
+        return node;
+    }
+
+
+    protected void createProperty(Node node, PropInfo pInfo, PropertyDefinition def) throws RepositoryException {
+        // convert serialized values to Value objects
+        Value[] va = pInfo.getValues(pInfo.getTargetType(def));
+
+        // multi- or single-valued property?
+        String name = pInfo.getName();
+        int type = pInfo.getType();
+        if (va.length == 1 && !def.isMultiple()) {
+            Exception e = null;
+            try {
+                // set single-value
+                node.setProperty(name, va[0]);
+            } catch (ValueFormatException vfe) {
+                e = vfe;
+            } catch (ConstraintViolationException cve) {
+                e = cve;
+            }
+            if (e != null) {
+                // setting single-value failed, try setting value array
+                // as a last resort (in case there are ambiguous property
+                // definitions)
+                node.setProperty(name, va, type);
+            }
+        } else {
+            // can only be multi-valued (n == 0 || n > 1)
+            node.setProperty(name, va, type);
+        }
+        if (type == PropertyType.REFERENCE || type == PropertyType.WEAKREFERENCE) {
+            // store reference for later resolution
+            refTracker.processedReference(node.getProperty(name));
+        }
+    }
+
+    protected Node resolveUUIDConflict(Node parent,
+                                       String conflictingId,
+                                       NodeInfo nodeInfo)
+            throws RepositoryException {
+        Node node;
+        Node conflicting;
+        try {
+            conflicting = session.getNodeByIdentifier(conflictingId);
+        } catch (ItemNotFoundException infe) {
+            // conflicting node can't be read,
+            // most likely due to lack of read permission
+            conflicting = null;
+        }
+
+        if (uuidBehavior == ImportUUIDBehavior.IMPORT_UUID_CREATE_NEW) {
+            // create new with new uuid
+            checkPermission(parent, nodeInfo.getName());
+            node = createNode(parent, nodeInfo.getName(),
+                    nodeInfo.getPrimaryTypeName(), nodeInfo.getMixinTypeNames());
+            // remember uuid mapping
+            if (node.isNodeType(JcrConstants.MIX_REFERENCEABLE)) {
+                refTracker.put(nodeInfo.getUUID(), node.getIdentifier());
+            }
+        } else if (uuidBehavior == ImportUUIDBehavior.IMPORT_UUID_COLLISION_THROW) {
+            // if conflicting node is shareable, then clone it
+            String msg = "a node with uuid " + nodeInfo.getUUID() + " already exists!";
+            log.debug(msg);
+            throw new ItemExistsException(msg);
+        } else if (uuidBehavior == ImportUUIDBehavior.IMPORT_UUID_COLLISION_REMOVE_EXISTING) {
+            if (conflicting == null) {
+                // since the conflicting node can't be read,
+                // we can't remove it
+                String msg = "node with uuid " + conflictingId + " cannot be removed";
+                log.debug(msg);
+                throw new RepositoryException(msg);
+            }
+
+            // make sure conflicting node is not importTargetNode or an ancestor thereof
+            if (importTargetNode.getPath().startsWith(conflicting.getPath())) {
+                String msg = "cannot remove ancestor node";
+                log.debug(msg);
+                throw new ConstraintViolationException(msg);
+            }
+            // remove conflicting
+            conflicting.remove();
+            // create new with given uuid
+            checkPermission(parent, nodeInfo.getName());
+            node = createNode(parent, nodeInfo.getName(),
+                    nodeInfo.getPrimaryTypeName(), nodeInfo.getMixinTypeNames());
+        } else if (uuidBehavior == ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING) {
+            if (conflicting == null) {
+                // since the conflicting node can't be read,
+                // we can't replace it
+                String msg = "node with uuid " + conflictingId + " cannot be replaced";
+                log.debug(msg);
+                throw new RepositoryException(msg);
+            }
+
+            if (conflicting.getDepth() == 0) {
+                String msg = "root node cannot be replaced";
+                log.debug(msg);
+                throw new RepositoryException(msg);
+            }
+            // 'replace' current parent with parent of conflicting
+            parent = conflicting.getParent();
+
+            // replace child node
+            checkPermission(parent, nodeInfo.getName());
+            //TODO ordering! (what happened to replace?)
+            conflicting.remove();
+            node = createNode(parent, nodeInfo.getName(),
+                    nodeInfo.getPrimaryTypeName(), nodeInfo.getMixinTypeNames());
+        } else {
+            String msg = "unknown uuidBehavior: " + uuidBehavior;
+            log.debug(msg);
+            throw new RepositoryException(msg);
+        }
+        return node;
+    }
+
+    //-------------------------------------------------------------< Importer >
+
+    /**
+     * {@inheritDoc}
+     */
+    public void start() throws RepositoryException {
+        // nop
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void startNode(NodeInfo nodeInfo, List<PropInfo> propInfos)
+            throws RepositoryException {
+        Node parent = parents.peek();
+
+        // process node
+
+        Node node = null;
+        String id = nodeInfo.getUUID();
+        String nodeName = nodeInfo.getName();
+        String ntName = nodeInfo.getPrimaryTypeName();
+        String[] mixins = nodeInfo.getMixinTypeNames();
+
+        if (parent == null) {
+            log.debug("Skipping node: " + nodeName);
+            // parent node was skipped, skip this child node too
+            parents.push(null); // push null onto stack for skipped node
+            // notify the p-i-importer
+            if (pnImporter != null) {
+                pnImporter.startChildInfo(nodeInfo, propInfos);
+            }
+            return;
+        }
+
+        if (parent.getDefinition().isProtected()) {
+            // skip protected node
+            parents.push(null);
+            log.debug("Skipping protected node: " + nodeName);
+
+            if (pnImporter != null) {
+                // pnImporter was already started (current nodeInfo is a sibling)
+                // notify it about this child node.
+                pnImporter.startChildInfo(nodeInfo, propInfos);
+            } else {
+                // no importer defined yet:
+                // test if there is a ProtectedNodeImporter among the configured
+                // importers that can handle this.
+                // if there is one, notify the ProtectedNodeImporter about the
+                // start of a item tree that is protected by this parent. If it
+                // potentially is able to deal with it, notify it about the child node.
+                for (ProtectedItemImporter pni : pItemImporters) {
+                    if (pni instanceof ProtectedNodeImporter && ((ProtectedNodeImporter) pni).start(root.getTree(parent.getPath()))) {
+                        log.debug("Protected node -> delegated to ProtectedNodeImporter");
+                        pnImporter = (ProtectedNodeImporter) pni;
+                        pnImporter.startChildInfo(nodeInfo, propInfos);
+                        break;
+                    } /* else: p-i-Importer isn't able to deal with the protected tree.
+                     try next. and if none can handle the passed parent the
+                     tree below will be skipped */
+                }
+            }
+            return;
+        }
+
+        if (parent.hasNode(nodeName)) {
+            // a node with that name already exists...
+            Node existing = parent.getNode(nodeName);
+            NodeDefinition def = existing.getDefinition();
+            if (!def.allowsSameNameSiblings()) {
+                // existing doesn't allow same-name siblings,
+                // check for potential conflicts
+                if (def.isProtected() && existing.isNodeType(ntName)) {
+                    /*
+                     use the existing node as parent for the possible subsequent
+                     import of a protected tree, that the protected node importer
+                     may or may not be able to deal with.
+                     -> upon the next 'startNode' the check for the parent being
+                        protected will notify the protected node importer.
+                     -> if the importer is able to deal with that node it needs
+                        to care of the complete subtree until it is notified
+                        during the 'endNode' call.
+                     -> if the import can't deal with that node or if that node
+                        is the a leaf in the tree to be imported 'end' will
+                        not have an effect on the importer, that was never started.
+                    */
+                    log.debug("Skipping protected node: " + existing);
+                    parents.push(existing);
+                    return;
+                }
+                if (def.isAutoCreated() && existing.isNodeType(ntName)) {
+                    // this node has already been auto-created, no need to create it
+                    node = existing;
+                } else {
+                    // edge case: colliding node does have same uuid
+                    // (see http://issues.apache.org/jira/browse/JCR-1128)
+                    if (!(existing.getIdentifier().equals(id)
+                            && (uuidBehavior == ImportUUIDBehavior.IMPORT_UUID_COLLISION_REMOVE_EXISTING
+                            || uuidBehavior == ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING))) {
+                        throw new ItemExistsException(
+                                "Node with the same UUID exists:" + existing);
+                    }
+                    // fall through
+                }
+            }
+        }
+
+        if (node == null) {
+            // create node
+            if (id == null) {
+                // no potential uuid conflict, always add new node
+                checkPermission(parent, nodeName);
+                node = createNode(parent, nodeName, ntName, mixins);
+            } else {
+                // potential uuid conflict
+                boolean isConflicting;
+                try {
+                    // the following is a fail-fast test whether
+                    // an item exists (regardless of access control)
+                    session.getNodeByIdentifier(id);
+                    isConflicting = true;
+                } catch (ItemNotFoundException e) {
+                    isConflicting = false;
+                } catch (RepositoryException e) {
+                    log.warn("Access Control Issues?", e);
+                    isConflicting = true;
+                }
+
+                if (isConflicting) {
+                    // resolve uuid conflict
+                    node = resolveUUIDConflict(parent, id, nodeInfo);
+                    if (node == null) {
+                        // no new node has been created, so skip this node
+                        parents.push(null); // push null onto stack for skipped node
+                        log.debug("Skipping existing node " + nodeInfo.getName());
+                        return;
+                    }
+                } else {
+                    // create new with given uuid
+                    checkPermission(parent, nodeName);
+                    node = createNode(parent, nodeName, ntName, mixins);
+                }
+            }
+        }
+
+        // process properties
+
+        for (PropInfo pi : propInfos) {
+            // find applicable definition
+            //TODO find a proper way to get the EffectiveNodeTypeProvider
+            NodeTypeManager nodeTypeManager = session.getWorkspace().getNodeTypeManager();
+            if (nodeTypeManager instanceof EffectiveNodeTypeProvider) {
+                EffectiveNodeTypeProvider entp = (EffectiveNodeTypeProvider) nodeTypeManager;
+
+                //TODO find better heuristics?
+                PropertyDefinition def = pi.getPropertyDef(entp.getEffectiveNodeType(node));
+                if (def.isProtected()) {
+                    // skip protected property
+                    log.debug("Skipping protected property " + pi.getName());
+
+                    // notify the ProtectedPropertyImporter.
+                    for (ProtectedItemImporter ppi : pItemImporters) {
+                        if (ppi instanceof ProtectedPropertyImporter && ((ProtectedPropertyImporter) ppi).handlePropInfo(root.getTree(node.getPath()), pi, def)) {
+                            log.debug("Protected property -> delegated to ProtectedPropertyImporter");
+                            break;
+                        } /* else: p-i-Importer isn't able to deal with this property.
+                             try next pp-importer */
+
+                    }
+                } else {
+                    // regular property -> create the property
+                    createProperty(node, pi, def);
+                }
+            } else {
+                log.warn("missing EffectiveNodeTypeProvider");
+            }
+        }
+
+        parents.push(node);
+    }
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public void endNode(NodeInfo nodeInfo) throws RepositoryException {
+        Node parent = parents.pop();
+        if (parent == null) {
+            if (pnImporter != null) {
+                pnImporter.endChildInfo();
+            }
+        } else if (parent.getDefinition().isProtected()) {
+            if (pnImporter != null) {
+                pnImporter.end(root.getTree(parent.getPath()));
+                // and reset the pnImporter field waiting for the next protected
+                // parent -> selecting again from available importers
+                pnImporter = null;
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void end() throws RepositoryException {
+        /**
+         * adjust references that refer to uuid's which have been mapped to
+         * newly generated uuid's on import
+         */
+        // 1. let protected property/node importers handle protected ref-properties
+        //    and (protected) properties underneath a protected parent node.
+        for (ProtectedItemImporter ppi : pItemImporters) {
+            ppi.processReferences();
+        }
+
+        // 2. regular non-protected properties.
+        Iterator<Object> iter = refTracker.getProcessedReferences();
+        while (iter.hasNext()) {
+            Object ref = iter.next();
+            if (!(ref instanceof Property)) {
+                continue;
+            }
+
+            Property prop = (Property) ref;
+            // being paranoid...
+            if (prop.getType() != PropertyType.REFERENCE
+                    && prop.getType() != PropertyType.WEAKREFERENCE) {
+                continue;
+            }
+            if (prop.isMultiple()) {
+                Value[] values = prop.getValues();
+                Value[] newVals = new Value[values.length];
+                for (int i = 0; i < values.length; i++) {
+                    Value val = values[i];
+                    String original = val.getString();
+                    String adjusted = refTracker.get(original);
+                    if (adjusted != null) {
+                        newVals[i] = session.getValueFactory().createValue(
+                                session.getNodeByIdentifier(adjusted),
+                                prop.getType() != PropertyType.REFERENCE);
+                    } else {
+                        // reference doesn't need adjusting, just copy old value
+                        newVals[i] = val;
+                    }
+                }
+                prop.setValue(newVals);
+            } else {
+                Value val = prop.getValue();
+                String original = val.getString();
+                String adjusted = refTracker.get(original);
+                if (adjusted != null) {
+                    prop.setValue(session.getNodeByIdentifier(adjusted).getIdentifier());
+                }
+            }
+        }
+        refTracker.clear();
+    }
+}

Added: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/xml/StringValue.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/xml/StringValue.java?rev=1453985&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/xml/StringValue.java (added)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/xml/StringValue.java Thu Mar  7 18:18:19 2013
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.jcr.xml;
+
+
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+import javax.jcr.ValueFactory;
+
+import org.apache.jackrabbit.oak.namepath.NamePathMapper;
+import org.apache.jackrabbit.oak.spi.xml.TextValue;
+import org.apache.jackrabbit.value.ValueHelper;
+
+/**
+ * <code>StringValue</code> represents an immutable serialized value.
+ */
+class StringValue implements TextValue {
+
+    private final String value;
+    private final ValueFactory valueFactory;
+    private final NamePathMapper namePathMapper;
+
+    /**
+     * Constructs a new <code>StringValue</code> representing the given
+     * value.
+     *
+     * @param value serialized value from document
+     * @param valueFactory the ValueFactory
+     * @param namePathMapper a namePathMapper knowing the document context
+     */
+    protected StringValue(String value, ValueFactory valueFactory, NamePathMapper namePathMapper) {
+        this.value = value;
+        this.valueFactory = valueFactory;
+        this.namePathMapper = namePathMapper;
+    }
+
+    //--------------------------------------------------------< TextValue >
+
+
+    @Override
+    public String getString() {
+        return this.value;
+    }
+
+    public Value getValue(int type) throws RepositoryException {
+        String inputValue = type == PropertyType.NAME ?
+                namePathMapper.getOakName(value) :
+                type == PropertyType.PATH ?
+                        namePathMapper.getOakPath(value) :
+                        value;
+        return ValueHelper.deserialize(inputValue, type, false, valueFactory);
+    }
+
+    public void dispose() {
+        // do nothing
+    }
+
+}