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