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
+ }
+
+}