Author: reschke
Date: Fri Feb 13 17:22:26 2015
New Revision: 1659616

URL: http://svn.apache.org/r1659616
Log:
OAK-2506 - port RDB support back to Oak 1.0

Part 1: add core classes and low level test cases

Added:
    
jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/
    
jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBBlobStore.java
   (with props)
    
jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBConnectionHandler.java
   (with props)
    
jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBCreator.java
   (with props)
    
jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDataSourceFactory.java
   (with props)
    
jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentSerializer.java
   (with props)
    
jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentStore.java
   (with props)
    
jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBJDBCTools.java
   (with props)
    
jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBOptions.java
   (with props)
    
jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBRow.java
   (with props)
    
jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/package-info.java
   (with props)
    
jackrabbit/oak/branches/1.0/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/AbstractMultiDocumentStoreTest.java
   (with props)
    
jackrabbit/oak/branches/1.0/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/MultiDocumentStoreTest.java
   (with props)
    
jackrabbit/oak/branches/1.0/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/rdb/
    
jackrabbit/oak/branches/1.0/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBBlobStoreFriend.java
   (with props)
    
jackrabbit/oak/branches/1.0/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentSerializerTest.java
   (with props)
Modified:
    jackrabbit/oak/branches/1.0/oak-core/pom.xml
    
jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/NodeDocument.java
    
jackrabbit/oak/branches/1.0/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/AbstractDocumentStoreTest.java
    
jackrabbit/oak/branches/1.0/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/BasicDocumentStoreTest.java
    
jackrabbit/oak/branches/1.0/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/document/DocumentStoreFixture.java

Modified: jackrabbit/oak/branches/1.0/oak-core/pom.xml
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-core/pom.xml?rev=1659616&r1=1659615&r2=1659616&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.0/oak-core/pom.xml (original)
+++ jackrabbit/oak/branches/1.0/oak-core/pom.xml Fri Feb 13 17:22:26 2015
@@ -286,6 +286,12 @@
     </dependency>
 
     <dependency>
+      <groupId>commons-dbcp</groupId>
+      <artifactId>commons-dbcp</artifactId>
+      <version>1.4</version>
+    </dependency>
+
+    <dependency>
       <groupId>com.h2database</groupId>
       <artifactId>h2</artifactId>
       <version>1.4.182</version>

Modified: 
jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/NodeDocument.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/NodeDocument.java?rev=1659616&r1=1659615&r2=1659616&view=diff
==============================================================================
--- 
jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/NodeDocument.java
 (original)
+++ 
jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/NodeDocument.java
 Fri Feb 13 17:22:26 2015
@@ -124,7 +124,7 @@ public final class NodeDocument extends
      * overlap with un-merged branch commits.
      * Key: revision, value: always true
      */
-    static final String COLLISIONS = "_collisions";
+    public static final String COLLISIONS = "_collisions";
 
     /**
      * The modified time in seconds (5 second resolution).

Added: 
jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBBlobStore.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBBlobStore.java?rev=1659616&view=auto
==============================================================================
--- 
jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBBlobStore.java
 (added)
+++ 
jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBBlobStore.java
 Fri Feb 13 17:22:26 2015
@@ -0,0 +1,548 @@
+/*
+ * 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.plugins.document.rdb;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+import javax.sql.DataSource;
+
+import org.apache.jackrabbit.oak.commons.StringUtils;
+import org.apache.jackrabbit.oak.plugins.blob.CachingBlobStore;
+import org.apache.jackrabbit.oak.plugins.document.DocumentStoreException;
+import org.apache.jackrabbit.oak.spi.blob.AbstractBlobStore;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.AbstractIterator;
+
+public class RDBBlobStore extends CachingBlobStore implements Closeable {
+
+    /**
+     * Creates a {@linkplain RDBBlobStore} instance using the provided
+     * {@link DataSource} using the given {@link RDBOptions}.
+     */
+    public RDBBlobStore(DataSource ds, RDBOptions options) {
+        try {
+            initialize(ds, options);
+        } catch (Exception ex) {
+            throw new DocumentStoreException("initializing RDB blob store", 
ex);
+        }
+    }
+
+    /**
+     * Creates a {@linkplain RDBBlobStore} instance using the provided
+     * {@link DataSource} using default {@link RDBOptions}.
+     */
+    public RDBBlobStore(DataSource ds) {
+        this(ds, new RDBOptions());
+    }
+
+    @Override
+    public void close() {
+        if (!this.tablesToBeDropped.isEmpty()) {
+            LOG.debug("attempting to drop: " + this.tablesToBeDropped);
+            for (String tname : this.tablesToBeDropped) {
+                Connection con = null;
+                try {
+                    con = this.ch.getRWConnection();
+                    try {
+                        Statement stmt = con.createStatement();
+                        stmt.execute("drop table " + tname);
+                        stmt.close();
+                        con.commit();
+                    } catch (SQLException ex) {
+                        LOG.debug("attempting to drop: " + tname);
+                    }
+                } catch (SQLException ex) {
+                    LOG.debug("attempting to drop: " + tname);
+                } finally {
+                    try {
+                        if (con != null) {
+                            con.close();
+                        }
+                    } catch (SQLException ex) {
+                        LOG.debug("on close ", ex);
+                    }
+                }
+            }
+        }
+        this.ch = null;
+    }
+
+    @Override
+    public void finalize() {
+        if (this.ch != null && this.callStack != null) {
+            LOG.debug("finalizing RDBDocumentStore that was not disposed", 
this.callStack);
+        }
+    }
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(RDBBlobStore.class);
+
+    // blob size we need to support
+    private static final int MINBLOB = 2 * 1024 * 1024;
+
+    // ID size we need to support; is 2 * (hex) size of digest length
+    private static final int IDSIZE;
+    static {
+        try {
+            MessageDigest md = 
MessageDigest.getInstance(AbstractBlobStore.HASH_ALGORITHM);
+            IDSIZE = md.getDigestLength() * 2;
+        } catch (NoSuchAlgorithmException ex) {
+            LOG.error ("can't determine digest length for blob store", ex);
+            throw new RuntimeException(ex);
+        }
+    }
+
+    private Exception callStack;
+
+    private RDBConnectionHandler ch;
+
+    // from options
+    private String dataTable;
+    private String metaTable;
+    private Set<String> tablesToBeDropped = new HashSet<String>();
+
+    private void initialize(DataSource ds, RDBOptions options) throws 
Exception {
+
+        String tablePrefix = options.getTablePrefix();
+        if (tablePrefix.length() > 0 && !tablePrefix.endsWith("_")) {
+            tablePrefix += "_";
+        }
+        this.dataTable = tablePrefix + "DATASTORE_DATA";
+        this.metaTable = tablePrefix + "DATASTORE_META";
+
+        this.ch = new RDBConnectionHandler(ds);
+        Connection con = this.ch.getRWConnection();
+
+        try {
+            for (String baseName : new String[] { "DATASTORE_META", 
"DATASTORE_DATA" }) {
+                String tableName = tablePrefix + baseName;
+                try {
+                    PreparedStatement stmt = con.prepareStatement("select ID 
from " + tableName + " where ID = ?");
+                    stmt.setString(1, "0");
+                    stmt.executeQuery();
+                    con.commit();
+                } catch (SQLException ex) {
+                    // table does not appear to exist
+                    con.rollback();
+
+                    String dbtype = con.getMetaData().getDatabaseProductName();
+                    LOG.info("Attempting to create table " + tableName + " in 
" + dbtype);
+
+                    Statement stmt = con.createStatement();
+
+                    if (baseName.equals("DATASTORE_META")) {
+                        String ct;
+                        if ("Oracle".equals(dbtype)) {
+                            ct = "create table " + tableName + " (ID varchar(" 
+ IDSIZE
+                                    + ") not null primary key, LVL number, 
LASTMOD number)";
+                        } else {
+                            ct = "create table " + tableName + " (ID varchar(" 
+ IDSIZE
+                                    + ") not null primary key, LVL int, 
LASTMOD bigint)";
+                        }
+                        stmt.execute(ct);
+                    } else {
+                        String ct;
+                        if ("PostgreSQL".equals(dbtype)) {
+                            ct = "create table " + tableName + " (ID varchar(" 
+ IDSIZE + ") not null primary key, DATA bytea)";
+                        } else if ("DB2".equals(dbtype) || (dbtype != null && 
dbtype.startsWith("DB2/"))) {
+                            ct = "create table " + tableName + " (ID varchar(" 
+ IDSIZE + ") not null primary key, DATA blob("
+                                    + MINBLOB + "))";
+                        } else if ("MySQL".equals(dbtype)) {
+                            ct = "create table " + tableName + " (ID varchar(" 
+ IDSIZE
+                                    + ") not null primary key, DATA 
mediumblob)";
+                        } else if ("Microsoft SQL Server".equals(dbtype)) {
+                            ct = "create table " + tableName + " (ID varchar(" 
+ IDSIZE
+                                    + ") not null primary key, DATA 
varbinary(max))";
+                        } else {
+                            ct = "create table " + tableName + " (ID varchar(" 
+ IDSIZE + ") not null primary key, DATA blob)";
+                        }
+                        stmt.execute(ct);
+                    }
+
+                    stmt.close();
+
+                    con.commit();
+
+                    if (options.isDropTablesOnClose()) {
+                        tablesToBeDropped.add(tableName);
+                    }
+                }
+            }
+        } finally {
+            this.ch.closeConnection(con);
+        }
+
+        this.callStack = LOG.isDebugEnabled() ? new Exception("call stack of 
RDBBlobStore creation") : null;
+    }
+
+    private long minLastModified;
+
+    @Override
+    protected void storeBlock(byte[] digest, int level, byte[] data) throws 
IOException {
+        try {
+            storeBlockInDatabase(digest, level, data);
+        } catch (SQLException e) {
+            throw new IOException(e);
+        }
+    }
+
+    private void storeBlockInDatabase(byte[] digest, int level, byte[] data) 
throws SQLException {
+
+        String id = StringUtils.convertBytesToHex(digest);
+        cache.put(id, data);
+        Connection con = this.ch.getRWConnection();
+
+        try {
+            long now = System.currentTimeMillis();
+            PreparedStatement prep = con.prepareStatement("update " + 
metaTable + " set LASTMOD = ? where ID = ?");
+            int count;
+            try {
+                prep.setLong(1, now);
+                prep.setString(2, id);
+                count = prep.executeUpdate();
+            }
+            catch (SQLException ex) {
+                LOG.error("trying to update metadata", ex);
+                throw new RuntimeException("trying to update metadata", ex);
+            }
+            finally {
+                prep.close();
+            }
+            if (count == 0) {
+                try {
+                    prep = con.prepareStatement("insert into " + dataTable + 
"(ID, DATA) values(?, ?)");
+                    try {
+                        prep.setString(1, id);
+                        prep.setBytes(2, data);
+                        prep.execute();
+                    } finally {
+                        prep.close();
+                    }
+                } catch (SQLException ex) {
+                    // TODO: this code used to ignore exceptions here, 
assuming that it might be a case where the blob is already in the database 
(maybe this requires inspecting the exception code)
+                    String message = "insert document failed for id " + id + " 
with length " + data.length + " (check max size of datastore_data.data)";
+                    LOG.error(message, ex);
+                    throw new RuntimeException(message, ex);
+                }
+                try {
+                    prep = con.prepareStatement("insert into " + metaTable + 
"(ID, LVL, LASTMOD) values(?, ?, ?)");
+                    try {
+                        prep.setString(1, id);
+                        prep.setInt(2, level);
+                        prep.setLong(3, now);
+                        prep.execute();
+                    } finally {
+                        prep.close();
+                    }
+                } catch (SQLException e) {
+                    // already exists - ok
+                }
+            }
+        } finally {
+            con.commit();
+            this.ch.closeConnection(con);
+        }
+    }
+
+    // needed in test
+    protected byte[] readBlockFromBackend(byte[] digest) throws Exception {
+        String id = StringUtils.convertBytesToHex(digest);
+        Connection con = this.ch.getROConnection();
+        byte[] data;
+
+        try {
+            PreparedStatement prep = con.prepareStatement("select DATA from " 
+ dataTable + " where ID = ?");
+            try {
+                prep.setString(1, id);
+                ResultSet rs = prep.executeQuery();
+                if (!rs.next()) {
+                    throw new IOException("Datastore block " + id + " not 
found");
+                }
+                data = rs.getBytes(1);
+            } finally {
+                prep.close();
+            }
+        } finally {
+            con.commit();
+            this.ch.closeConnection(con);
+        }
+        return data;
+    }
+
+    @Override
+    protected byte[] readBlockFromBackend(BlockId blockId) throws Exception {
+
+        String id = StringUtils.convertBytesToHex(blockId.getDigest());
+        byte[] data = cache.get(id);
+
+        if (data == null) {
+            Connection con = this.ch.getROConnection();
+
+            try {
+                PreparedStatement prep = con.prepareStatement("select DATA 
from " + dataTable + " where ID = ?");
+                try {
+                    prep.setString(1, id);
+                    ResultSet rs = prep.executeQuery();
+                    if (!rs.next()) {
+                        throw new IOException("Datastore block " + id + " not 
found");
+                    }
+                    data = rs.getBytes(1);
+                } finally {
+                    prep.close();
+                }
+                cache.put(id, data);
+            } finally {
+                con.commit();
+                this.ch.closeConnection(con);
+            }
+        }
+        // System.out.println("    read block " + id + " blockLen: " +
+        // data.length + " [0]: " + data[0]);
+        if (blockId.getPos() == 0) {
+            return data;
+        }
+        int len = (int) (data.length - blockId.getPos());
+        if (len < 0) {
+            return new byte[0];
+        }
+        byte[] d2 = new byte[len];
+        System.arraycopy(data, (int) blockId.getPos(), d2, 0, len);
+        return d2;
+    }
+
+    @Override
+    public void startMark() throws IOException {
+        minLastModified = System.currentTimeMillis();
+        markInUse();
+    }
+
+    @Override
+    protected boolean isMarkEnabled() {
+        return minLastModified != 0;
+    }
+
+    @Override
+    protected void mark(BlockId blockId) throws Exception {
+        Connection con = this.ch.getRWConnection();
+        try {
+            if (minLastModified == 0) {
+                return;
+            }
+            String id = StringUtils.convertBytesToHex(blockId.getDigest());
+            PreparedStatement prep = con.prepareStatement("update " + 
metaTable + " set LASTMOD = ? where ID = ? and LASTMOD < ?");
+            prep.setLong(1, System.currentTimeMillis());
+            prep.setString(2, id);
+            prep.setLong(3, minLastModified);
+            prep.executeUpdate();
+            prep.close();
+        } finally {
+            con.commit();
+            this.ch.closeConnection(con);
+        }
+    }
+
+    @Override
+    public int sweep() throws IOException {
+        try {
+            return sweepFromDatabase();
+        } catch (SQLException e) {
+            throw new IOException(e);
+        }
+    }
+
+    private int sweepFromDatabase() throws SQLException {
+        Connection con = this.ch.getRWConnection();
+        try {
+            int count = 0;
+            PreparedStatement prep = con.prepareStatement("select ID from " + 
metaTable + " where LASTMOD < ?");
+            prep.setLong(1, minLastModified);
+            ResultSet rs = prep.executeQuery();
+            ArrayList<String> ids = new ArrayList<String>();
+            while (rs.next()) {
+                ids.add(rs.getString(1));
+            }
+            prep = con.prepareStatement("delete from " + metaTable + " where 
ID = ?");
+            PreparedStatement prepData = con.prepareStatement("delete from " + 
dataTable + " where ID = ?");
+            for (String id : ids) {
+                prep.setString(1, id);
+                prep.execute();
+                prepData.setString(1, id);
+                prepData.execute();
+                count++;
+            }
+            prepData.close();
+            prep.close();
+            minLastModified = 0;
+            return count;
+        } finally {
+            con.commit();
+            this.ch.closeConnection(con);
+        }
+    }
+
+    @Override
+    public boolean deleteChunks(List<String> chunkIds, long 
maxLastModifiedTime) throws Exception {
+
+        // sanity check
+        if (chunkIds.isEmpty()) {
+            // sanity check, nothing to do
+            return true;
+        }
+
+        Connection con = this.ch.getRWConnection();
+        try {
+            PreparedStatement prepMeta = null;
+            PreparedStatement prepData = null;
+
+            StringBuilder inClause = new StringBuilder();
+            int batch = chunkIds.size();
+            for (int i = 0; i < batch; i++) {
+                inClause.append('?');
+                if (i != batch - 1) {
+                    inClause.append(',');
+                }
+            }
+
+            if (maxLastModifiedTime > 0) {
+                prepMeta = con.prepareStatement("delete from " + metaTable + " 
where ID in (" + inClause.toString()
+                        + ") and LASTMOD <= ?");
+                prepMeta.setLong(batch + 1, maxLastModifiedTime);
+
+                prepData = con.prepareStatement("delete from " + dataTable + " 
where ID in (" + inClause.toString()
+                        + ") and not exists(select * from " + metaTable + " m 
where ID = m.ID and m.LASTMOD <= ?)");
+                prepData.setLong(batch + 1, maxLastModifiedTime);
+            } else {
+                prepMeta = con.prepareStatement("delete from " + metaTable + " 
where ID in (" + inClause.toString() + ")");
+                prepData = con.prepareStatement("delete from " + dataTable + " 
where ID in (" + inClause.toString() + ")");
+            }
+
+            for (int idx = 0; idx < batch; idx++) {
+                prepMeta.setString(idx + 1, chunkIds.get(idx));
+                prepData.setString(idx + 1, chunkIds.get(idx));
+            }
+
+            prepMeta.execute();
+            prepData.execute();
+            prepMeta.close();
+            prepData.close();
+        } finally {
+            con.commit();
+            this.ch.closeConnection(con);
+        }
+
+        return true;
+    }
+
+    @Override
+    public Iterator<String> getAllChunkIds(long maxLastModifiedTime) throws 
Exception {
+        return new ChunkIdIterator(this.ch, maxLastModifiedTime, metaTable);
+    }
+
+    /**
+     * Reads chunk IDs in batches.
+     */
+    private static class ChunkIdIterator extends AbstractIterator<String> {
+
+        private long maxLastModifiedTime;
+        private RDBConnectionHandler ch;
+        private static int BATCHSIZE = 1024 * 64;
+        private List<String> results = new LinkedList<String>();
+        private String lastId = null;
+        private String metaTable;
+
+        public ChunkIdIterator(RDBConnectionHandler ch, long 
maxLastModifiedTime, String metaTable) {
+            this.maxLastModifiedTime = maxLastModifiedTime;
+            this.ch = ch;
+            this.metaTable = metaTable;
+        }
+
+        @Override
+        protected String computeNext() {
+            if (!results.isEmpty()) {
+                return results.remove(0);
+            } else {
+                // need to refill
+                if (refill()) {
+                    return computeNext();
+                } else {
+                    return endOfData();
+                }
+            }
+        }
+
+        private boolean refill() {
+            StringBuffer query = new StringBuffer();
+            query.append("select ID from " + metaTable);
+            if (maxLastModifiedTime > 0) {
+                query.append(" where LASTMOD <= ?");
+                if (lastId != null) {
+                    query.append(" and ID > ?");
+                }
+            } else {
+                if (lastId != null) {
+                    query.append(" where ID > ?");
+                }
+            }
+            query.append(" order by ID");
+
+            Connection connection = null;
+            try {
+                connection = this.ch.getROConnection();
+                try {
+                    PreparedStatement prep = 
connection.prepareStatement(query.toString());
+                    int idx = 1;
+                    if (maxLastModifiedTime > 0) {
+                        prep.setLong(idx++, maxLastModifiedTime);
+                    }
+                    if (lastId != null) {
+                        prep.setString(idx++, lastId);
+                    }
+                    prep.setFetchSize(BATCHSIZE);
+                    ResultSet rs = prep.executeQuery();
+                    while (rs.next()) {
+                        lastId = rs.getString(1);
+                        results.add(lastId);
+                    }
+                    return !results.isEmpty();
+                } finally {
+                    connection.commit();
+                    this.ch.closeConnection(connection);
+                }
+            } catch (SQLException ex) {
+                LOG.debug("error executing ID lookup", ex);
+                this.ch.rollbackConnection(connection);
+                this.ch.closeConnection(connection);
+                return false;
+            }
+        }
+    }
+}

Propchange: 
jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBBlobStore.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: 
jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBConnectionHandler.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBConnectionHandler.java?rev=1659616&view=auto
==============================================================================
--- 
jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBConnectionHandler.java
 (added)
+++ 
jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBConnectionHandler.java
 Fri Feb 13 17:22:26 2015
@@ -0,0 +1,106 @@
+/*
+ * 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.plugins.document.rdb;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.sql.DataSource;
+
+import org.apache.jackrabbit.oak.plugins.document.DocumentStoreException;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Utility functions for connection handling.
+ */
+public class RDBConnectionHandler {
+
+    private final DataSource ds;
+
+    private static final org.slf4j.Logger LOG = 
LoggerFactory.getLogger(RDBConnectionHandler.class);
+
+    /**
+     * Closing a connection doesn't necessarily imply a {@link 
Connection#commit()} or {@link Connection#rollback()}.
+     * This becomes a problem when the pool implemented by the {@link 
DataSource} re-uses the connection, and may
+     * affect subsequent users of that connection. This system property allows 
to enable a check to be done upon
+     * {@link #closeConnection(Connection)} so that problems can be detected 
early rather than late.
+     * See also https://issues.apache.org/jira/browse/OAK-2337.
+     */
+    private static final boolean CHECKCONNECTIONONCLOSE = Boolean
+            
.getBoolean("org.apache.jackrabbit.oak.plugins.document.rdb.RDBConnectionHandler.CHECKCONNECTIONONCLOSE");
+
+    public RDBConnectionHandler(@Nonnull DataSource ds) {
+        this.ds = ds;
+    }
+
+    /**
+     * Obtain a {@link Connection} suitable for read-only operations.
+     */
+    public @Nonnull Connection getROConnection() throws SQLException {
+        Connection c = this.ds.getConnection();
+        c.setAutoCommit(false);
+        c.setReadOnly(true);
+        return c;
+    }
+
+    /**
+     * Obtain a {@link Connection} suitable for read-write operations.
+     */
+    public @Nonnull Connection getRWConnection() throws SQLException {
+        Connection c = this.ds.getConnection();
+        c.setAutoCommit(false);
+        c.setReadOnly(false);
+        return c;
+    }
+
+    /**
+     * Roll back the {@link Connection}.
+     */
+    public void rollbackConnection(@Nullable Connection c) {
+        if (c != null) {
+            try {
+                c.rollback();
+            } catch (SQLException ex) {
+                LOG.error("error on rollback (ignored)", ex);
+            }
+        }
+    }
+
+    /**
+     * Close the {@link Connection}.
+     */
+    public void closeConnection(Connection c) {
+        if (c != null) {
+            try {
+                if (CHECKCONNECTIONONCLOSE) {
+                    try {
+                        c.setReadOnly(!c.isReadOnly());
+                        c.setReadOnly(!c.isReadOnly());
+                    } catch (SQLException ex2) {
+                        LOG.error("got dirty connection", ex2);
+                        throw new DocumentStoreException("dirty connection on 
close", ex2);
+                    }
+                }
+                c.close();
+            } catch (SQLException ex) {
+                LOG.error("exception on connection close (ignored)", ex);
+            }
+        }
+    }
+}

Propchange: 
jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBConnectionHandler.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: 
jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBCreator.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBCreator.java?rev=1659616&view=auto
==============================================================================
--- 
jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBCreator.java
 (added)
+++ 
jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBCreator.java
 Fri Feb 13 17:22:26 2015
@@ -0,0 +1,52 @@
+/*
+ * 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.plugins.document.rdb;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+public class RDBCreator {
+
+    public static void main(String[] args) throws ClassNotFoundException, 
SQLException {
+        String url = null, user = null, pw = null, db = null;
+
+        try {
+            url = args[0];
+            user = args[1];
+            pw = args[2];
+            db = args[3];
+        } catch (IndexOutOfBoundsException ex) {
+            System.err.println("Usage: ... " + RDBCreator.class.getName() + " 
JDBC-URL username password databasename");
+            System.exit(2);
+        }
+
+        String driver = 
RDBJDBCTools.driverForDBType(RDBJDBCTools.jdbctype(url));
+        try {
+            Class.forName(driver);
+        } catch (ClassNotFoundException ex) {
+            System.err.println("Attempt to load class " + driver + " failed.");
+        }
+        Connection c = DriverManager.getConnection(url, user, pw);
+        Statement stmt = c.createStatement();
+        stmt.execute("create database " + db);
+        stmt.close();
+        c.close();
+        System.out.println("Database " + db + " created @ " + url + " using " 
+ driver);
+    }
+}

Propchange: 
jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBCreator.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: 
jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDataSourceFactory.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDataSourceFactory.java?rev=1659616&view=auto
==============================================================================
--- 
jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDataSourceFactory.java
 (added)
+++ 
jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDataSourceFactory.java
 Fri Feb 13 17:22:26 2015
@@ -0,0 +1,160 @@
+/*
+ * 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.plugins.document.rdb;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.sql.Connection;
+import java.sql.Driver;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
+import java.util.Locale;
+import java.util.logging.Logger;
+
+import javax.sql.DataSource;
+
+import org.apache.commons.dbcp.BasicDataSource;
+import org.apache.jackrabbit.oak.plugins.document.DocumentStoreException;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Factory for creating {@link DataSource}s based on a JDBC connection URL.
+ */
+public class RDBDataSourceFactory {
+
+    private static final org.slf4j.Logger LOG = 
LoggerFactory.getLogger(RDBDataSourceFactory.class);
+
+    public static DataSource forJdbcUrl(String url, String username, String 
passwd, String driverName) {
+
+        // load driver class when specified
+        if (driverName != null && !driverName.isEmpty()) {
+            LOG.info("trying to load {}", driverName);
+
+            try {
+                Class.forName(driverName);
+            } catch (ClassNotFoundException ex) {
+                LOG.error("driver " + driverName + " not loaded", ex);
+            }
+        } else {
+            // try to determine driver from JDBC URL
+            String defaultDriver = 
RDBJDBCTools.driverForDBType(RDBJDBCTools.jdbctype(url));
+            if (defaultDriver != null && !defaultDriver.isEmpty()) {
+                LOG.info("trying to load {}", defaultDriver);
+
+                try {
+                    Class.forName(defaultDriver);
+                } catch (ClassNotFoundException ex) {
+                    LOG.error("driver " + defaultDriver + " not loaded", ex);
+                }
+            }
+        }
+
+        try {
+            BasicDataSource bds = new BasicDataSource();
+            LOG.debug("Getting driver for " + url);
+            Driver d = DriverManager.getDriver(url);
+            bds.setDriverClassName(d.getClass().getName());
+            bds.setUsername(username);
+            bds.setPassword(passwd);
+            bds.setUrl(url);
+            return new CloseableDataSource(bds);
+        } catch (SQLException ex) {
+            String message = "trying to obtain driver for " + url;
+            LOG.info(message, ex);
+            throw new DocumentStoreException(message, ex);
+        }
+    }
+
+    public static DataSource forJdbcUrl(String url, String username, String 
passwd) {
+        return forJdbcUrl(url, username, passwd, null);
+    }
+
+    /**
+     * A {@link Closeable} {@link DataSource} based on a {@link 
BasicDataSource}
+     * .
+     */
+    private static class CloseableDataSource implements DataSource, Closeable {
+
+        private BasicDataSource ds;
+
+        public CloseableDataSource(BasicDataSource ds) {
+            this.ds = ds;
+        }
+
+        @Override
+        public PrintWriter getLogWriter() throws SQLException {
+            return this.ds.getLogWriter();
+        }
+
+        @Override
+        public int getLoginTimeout() throws SQLException {
+            return this.ds.getLoginTimeout();
+        }
+
+        @Override
+        public void setLogWriter(PrintWriter pw) throws SQLException {
+            this.ds.setLogWriter(pw);
+        }
+
+        @Override
+        public void setLoginTimeout(int t) throws SQLException {
+            this.ds.setLoginTimeout(t);
+        }
+
+        @Override
+        public boolean isWrapperFor(Class<?> c) throws SQLException {
+            return this.ds.isWrapperFor(c);
+        }
+
+        @Override
+        public <T> T unwrap(Class<T> c) throws SQLException {
+            return this.ds.unwrap(c);
+        }
+
+        @Override
+        public void close() throws IOException {
+            try {
+                this.ds.close();
+            } catch (SQLException ex) {
+                throw new IOException("closing data source " + this.ds, ex);
+            }
+        }
+
+        @Override
+        public Connection getConnection() throws SQLException {
+            return this.ds.getConnection();
+        }
+
+        @Override
+        public Connection getConnection(String user, String passwd) throws 
SQLException {
+            return this.ds.getConnection(user, passwd);
+        }
+
+        // needed in Java 7...
+        @SuppressWarnings("unused")
+        public Logger getParentLogger() throws SQLFeatureNotSupportedException 
{
+            throw new SQLFeatureNotSupportedException();
+        }
+
+        @Override
+        public String toString() {
+            return this.getClass().getName() + " wrapping a " + 
this.ds.toString();
+        }
+    }
+}

Propchange: 
jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDataSourceFactory.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: 
jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentSerializer.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentSerializer.java?rev=1659616&view=auto
==============================================================================
--- 
jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentSerializer.java
 (added)
+++ 
jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentSerializer.java
 Fri Feb 13 17:22:26 2015
@@ -0,0 +1,388 @@
+/*
+ * 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.plugins.document.rdb;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.zip.GZIPInputStream;
+
+import javax.annotation.Nonnull;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.jackrabbit.oak.commons.json.JsopBuilder;
+import org.apache.jackrabbit.oak.commons.json.JsopReader;
+import org.apache.jackrabbit.oak.commons.json.JsopTokenizer;
+import org.apache.jackrabbit.oak.plugins.document.Collection;
+import org.apache.jackrabbit.oak.plugins.document.Document;
+import org.apache.jackrabbit.oak.plugins.document.DocumentStore;
+import org.apache.jackrabbit.oak.plugins.document.DocumentStoreException;
+import org.apache.jackrabbit.oak.plugins.document.NodeDocument;
+import org.apache.jackrabbit.oak.plugins.document.Revision;
+import org.apache.jackrabbit.oak.plugins.document.StableRevisionComparator;
+import org.apache.jackrabbit.oak.plugins.document.UpdateOp;
+import org.apache.jackrabbit.oak.plugins.document.UpdateOp.Key;
+import org.apache.jackrabbit.oak.plugins.document.UpdateOp.Operation;
+
+/**
+ * Serialization/Parsing of documents.
+ */
+public class RDBDocumentSerializer {
+
+    private final DocumentStore store;
+    private final Set<String> columnProperties;
+
+    private static final String MODIFIED = "_modified";
+    private static final String MODCOUNT = "_modCount";
+    private static final String CMODCOUNT = "_collisionsModCount";
+    private static final String ID = "_id";
+    private static final String HASBINARY = NodeDocument.HAS_BINARY_FLAG;
+    private static final String DELETEDONCE = NodeDocument.DELETED_ONCE;
+
+    private final Comparator<Revision> comparator = 
StableRevisionComparator.REVERSE;
+
+    public RDBDocumentSerializer(DocumentStore store, Set<String> 
columnProperties) {
+        this.store = store;
+        this.columnProperties = columnProperties;
+    }
+
+    /**
+     * Serializes all non-column properties of the {@link Document} into a JSON
+     * string.
+     */
+    public String asString(@Nonnull Document doc) {
+        StringBuilder sb = new StringBuilder(32768);
+        sb.append("{");
+        boolean needComma = false;
+        for (String key : doc.keySet()) {
+            if (!columnProperties.contains(key)) {
+                if (needComma) {
+                    sb.append(",");
+                }
+                appendMember(sb, key, doc.get(key));
+                needComma = true;
+            }
+        }
+        sb.append("}");
+        return sb.toString();
+    }
+
+    /**
+     * Serializes the changes in the {@link UpdateOp} into a JSON array; each
+     * entry is another JSON array holding operation, key, revision, and value.
+     */
+    public String asString(UpdateOp update) {
+        StringBuilder sb = new StringBuilder("[");
+        boolean needComma = false;
+        for (Map.Entry<Key, Operation> change : 
update.getChanges().entrySet()) {
+            Operation op = change.getValue();
+            Key key = change.getKey();
+
+            // exclude properties that are serialized into special columns
+            if (columnProperties.contains(key.getName()) && null == 
key.getRevision())
+                continue;
+
+            // already checked
+            if (op.type == UpdateOp.Operation.Type.CONTAINS_MAP_ENTRY)
+                continue;
+
+            if (needComma) {
+                sb.append(",");
+            }
+            sb.append("[");
+            if (op.type == UpdateOp.Operation.Type.INCREMENT) {
+                sb.append("\"+\",");
+            } else if (op.type == UpdateOp.Operation.Type.SET || op.type == 
UpdateOp.Operation.Type.SET_MAP_ENTRY) {
+                sb.append("\"=\",");
+            } else if (op.type == UpdateOp.Operation.Type.MAX) {
+                sb.append("\"M\",");
+            } else if (op.type == UpdateOp.Operation.Type.REMOVE_MAP_ENTRY) {
+                sb.append("\"*\",");
+            } else {
+                throw new DocumentStoreException("Can't serialize " + 
update.toString() + " for JSON append");
+            }
+            appendString(sb, key.getName());
+            sb.append(",");
+            if (key.getRevision() != null) {
+                appendString(sb, key.getRevision().toString());
+                sb.append(",");
+            }
+            appendValue(sb, op.value);
+            sb.append("]");
+            needComma = true;
+        }
+        return sb.append("]").toString();
+    }
+
+    private static void appendMember(StringBuilder sb, String key, Object 
value) {
+        appendString(sb, key);
+        sb.append(":");
+        appendValue(sb, value);
+    }
+
+    private static void appendValue(StringBuilder sb, Object value) {
+        if (value == null) {
+            sb.append("null");
+        } else if (value instanceof Number) {
+            sb.append(value.toString());
+        } else if (value instanceof Boolean) {
+            sb.append(value.toString());
+        } else if (value instanceof String) {
+            appendString(sb, (String) value);
+        } else if (value instanceof Map) {
+            appendMap(sb, (Map<Object, Object>) value);
+        } else {
+            throw new DocumentStoreException("unexpected type: " + 
value.getClass());
+        }
+    }
+
+    private static void appendMap(StringBuilder sb, Map<Object, Object> map) {
+        sb.append("{");
+        boolean needComma = false;
+        for (Map.Entry<Object, Object> e : map.entrySet()) {
+            if (needComma) {
+                sb.append(",");
+            }
+            appendMember(sb, e.getKey().toString(), e.getValue());
+            needComma = true;
+        }
+        sb.append("}");
+    }
+
+    private static void appendString(StringBuilder sb, String s) {
+        sb.append('"');
+        JsopBuilder.escape(s, sb);
+        sb.append('"');
+    }
+
+    /**
+     * Reconstructs a {@link Document) based on the persisted {@link DBRow}.
+     */
+    public <T extends Document> T fromRow(@Nonnull Collection<T> collection, 
@Nonnull RDBRow row) throws DocumentStoreException {
+        T doc = collection.newDocument(store);
+        doc.put(ID, row.getId());
+        doc.put(MODIFIED, row.getModified());
+        doc.put(MODCOUNT, row.getModcount());
+        doc.put(CMODCOUNT, row.getCollisionsModcount());
+        if (row.hasBinaryProperties()) {
+            doc.put(HASBINARY, NodeDocument.HAS_BINARY_VAL);
+        }
+        if (row.deletedOnce()) {
+            doc.put(DELETEDONCE, Boolean.TRUE);
+        }
+
+        byte[] bdata = row.getBdata();
+        boolean blobInUse = false;
+        JsopTokenizer json;
+
+        // case #1: BDATA (blob) contains base data, DATA (string) contains
+        // update operations
+        try {
+            if (bdata != null && bdata.length != 0) {
+                String s = fromBlobData(bdata);
+                json = new JsopTokenizer(s);
+                json.read('{');
+                readDocumentFromJson(json, doc);
+                json.read(JsopReader.END);
+                blobInUse = true;
+            }
+        } catch (Exception ex) {
+            throw new DocumentStoreException(ex);
+        }
+
+        // start processing the VARCHAR data
+        try {
+            json = new JsopTokenizer(row.getData());
+
+            int next = json.read();
+
+            if (next == '{') {
+                if (blobInUse) {
+                    throw new DocumentStoreException("expected literal 
\"blob\" but found: " + row.getData());
+                }
+                readDocumentFromJson(json, doc);
+            } else if (next == JsopReader.STRING) {
+                if (!blobInUse) {
+                    throw new DocumentStoreException("did not expect \"blob\" 
here: " + row.getData());
+                }
+                if (!json.getToken().equals("blob")) {
+                    throw new DocumentStoreException("expected string literal 
\"blob\"");
+                }
+            } else {
+                throw new DocumentStoreException("unexpected token " + next + 
" in " + row.getData());
+            }
+
+            next = json.read();
+            if (next == ',') {
+                do {
+                    Object ob = readValueFromJson(json);
+                    if (!(ob instanceof List)) {
+                        throw new DocumentStoreException("expected array but 
got: " + ob);
+                    }
+                    List<List<Object>> update = (List<List<Object>>) ob;
+                    for (List<Object> op : update) {
+                        applyUpdate(doc, update, op);
+                    }
+
+                } while (json.matches(','));
+            }
+            json.read(JsopReader.END);
+
+            return doc;
+        } catch (Exception ex) {
+            throw new DocumentStoreException(ex);
+        }
+    }
+
+    private <T extends Document> void applyUpdate(T doc, List updateString, 
List<Object> op) {
+        String opcode = op.get(0).toString();
+        String key = op.get(1).toString();
+        Revision rev = null;
+        Object value = null;
+        if (op.size() == 3) {
+            value = op.get(2);
+        } else {
+            rev = Revision.fromString(op.get(2).toString());
+            value = op.get(3);
+        }
+        Object old = doc.get(key);
+
+        if ("=".equals(opcode)) {
+            if (rev == null) {
+                doc.put(key, value);
+            } else {
+                @SuppressWarnings("unchecked")
+                Map<Revision, Object> m = (Map<Revision, Object>) old;
+                if (m == null) {
+                    m = new TreeMap<Revision, Object>(comparator);
+                    doc.put(key, m);
+                }
+                m.put(rev, value);
+            }
+        } else if ("*".equals(opcode)) {
+            if (rev == null) {
+                throw new DocumentStoreException("unexpected operation " + op 
+ " in: " + updateString);
+            } else {
+                @SuppressWarnings("unchecked")
+                Map<Revision, Object> m = (Map<Revision, Object>) old;
+                if (m != null) {
+                    m.remove(rev);
+                }
+            }
+        } else if ("+".equals(opcode)) {
+            if (rev == null) {
+                Long x = (Long) value;
+                if (old == null) {
+                    old = 0L;
+                }
+                doc.put(key, ((Long) old) + x);
+            } else {
+                throw new DocumentStoreException("unexpected operation " + op 
+ " in: " + updateString);
+            }
+        } else if ("M".equals(opcode)) {
+            if (rev == null) {
+                Comparable newValue = (Comparable) value;
+                if (old == null || newValue.compareTo(old) > 0) {
+                    doc.put(key, value);
+                }
+            } else {
+                throw new DocumentStoreException("unexpected operation " + op 
+ " in: " + updateString);
+            }
+        } else {
+            throw new DocumentStoreException("unexpected operation " + op + " 
in: " + updateString);
+        }
+    }
+
+    /**
+     * Reads from an opened JSON stream ("{" already consumed) into a document.
+     */
+    private static <T extends Document> void readDocumentFromJson(@Nonnull 
JsopTokenizer json, @Nonnull T doc) {
+        if (!json.matches('}')) {
+            do {
+                String key = json.readString();
+                json.read(':');
+                Object value = readValueFromJson(json);
+                doc.put(key, value);
+            } while (json.matches(','));
+            json.read('}');
+        }
+    }
+
+    @Nonnull
+    private static Object readValueFromJson(@Nonnull JsopTokenizer json) {
+        switch (json.read()) {
+            case JsopReader.NULL:
+                return null;
+            case JsopReader.TRUE:
+                return true;
+            case JsopReader.FALSE:
+                return false;
+            case JsopReader.NUMBER:
+                return Long.parseLong(json.getToken());
+            case JsopReader.STRING:
+                return json.getToken();
+            case '{':
+                TreeMap<Revision, Object> map = new TreeMap<Revision, 
Object>(StableRevisionComparator.REVERSE);
+                while (true) {
+                    if (json.matches('}')) {
+                        break;
+                    }
+                    String k = json.readString();
+                    json.read(':');
+                    map.put(Revision.fromString(k), readValueFromJson(json));
+                    json.matches(',');
+                }
+                return map;
+            case '[':
+                List<Object> list = new ArrayList<Object>();
+                while (true) {
+                    if (json.matches(']')) {
+                        break;
+                    }
+                    list.add(readValueFromJson(json));
+                    json.matches(',');
+                }
+                return list;
+            default:
+                throw new IllegalArgumentException(json.readRawValue());
+        }
+    }
+
+    // low level operations
+
+    private static byte[] GZIPSIG = { 31, -117 };
+
+    private static String fromBlobData(byte[] bdata) {
+        try {
+            if (bdata.length >= 2 && bdata[0] == GZIPSIG[0] && bdata[1] == 
GZIPSIG[1]) {
+                // GZIP
+                ByteArrayInputStream bis = new ByteArrayInputStream(bdata);
+                GZIPInputStream gis = new GZIPInputStream(bis, 65536);
+                return IOUtils.toString(gis, "UTF-8");
+            } else {
+                return IOUtils.toString(bdata, "UTF-8");
+            }
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}

Propchange: 
jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/document/rdb/RDBDocumentSerializer.java
------------------------------------------------------------------------------
    svn:eol-style = native


Reply via email to