Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/AbstractStore.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/AbstractStore.java (revision 1558843) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/AbstractStore.java (working copy) @@ -23,6 +23,7 @@ import java.util.UUID; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import org.apache.jackrabbit.oak.cache.CacheLIRS; @@ -160,4 +161,9 @@ return type.isInstance(object) && ((Record) object).getStore() == this; } + @Nullable + @Override + public ExternalBlob readBlob(String reference) { + return null; + } } Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/ExternalBlob.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/ExternalBlob.java (revision 0) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/ExternalBlob.java (working copy) @@ -0,0 +1,34 @@ +/* + * 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.segment; + +import org.apache.jackrabbit.oak.api.Blob; + +/** + * Marks a blob that is external. + */ +public interface ExternalBlob extends Blob { + + /** + * Return a reference to this external blob. + * + * @return reference + */ + public String getReference(); +} Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/Segment.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/Segment.java (revision 1558843) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/Segment.java (working copy) @@ -462,6 +462,19 @@ } } + ExternalBlob readBlob(int offset) { + int pos = pos(offset, 1); + + int length = (data.get(pos++) & 0x1f) << 8 + | (data.get(pos++) & 0xff); + + byte[] bytes = new byte[length]; + ByteBuffer buffer = data.duplicate(); + buffer.position(pos); + buffer.get(bytes); + return store.readBlob(new String(bytes, Charsets.UTF_8)); + } + //------------------------------------------------------------< Object >-- @Override Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/SegmentBlob.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/SegmentBlob.java (revision 1558843) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/SegmentBlob.java (working copy) @@ -21,20 +21,41 @@ import org.apache.jackrabbit.oak.api.Blob; import org.apache.jackrabbit.oak.plugins.memory.AbstractBlob; +import java.io.InputStream; + class SegmentBlob extends Record implements Blob { + private boolean external; + + SegmentBlob(Segment segment, RecordId id, boolean external) { + super(segment, id); + + this.external = external; + } + SegmentBlob(Segment segment, RecordId id) { super(segment, id); + + int n = segment.readByte(getOffset()) & 0xff; + external = ((n & 0xe0) == 0xe0); } @Override @Nonnull - public SegmentStream getNewStream() { + public InputStream getNewStream() { + if (external) { + return getSegment().readBlob(getOffset()).getNewStream(); + } return getSegment().readStream(getOffset()); } @Override public long length() { - SegmentStream stream = getNewStream(); + if (external) { + return getSegment().readBlob(getOffset()).length(); + } + + SegmentStream stream = (SegmentStream) getNewStream(); + try { return stream.getLength(); } finally { Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/SegmentStore.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/SegmentStore.java (revision 1558843) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/SegmentStore.java (working copy) @@ -16,6 +16,7 @@ */ package org.apache.jackrabbit.oak.plugins.segment; +import javax.annotation.Nullable; import java.util.UUID; public interface SegmentStore { @@ -51,4 +52,12 @@ */ boolean isInstance(Object object, Class type); + /** + * Read a blob from external storage. + * + * @param reference blob reference + * @return external blob + */ + @Nullable + ExternalBlob readBlob(String reference); } Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/SegmentWriter.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/SegmentWriter.java (revision 1558843) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/SegmentWriter.java (working copy) @@ -484,6 +484,28 @@ } /** + * Write a reference to an external blob. + * + * @param reference reference + * @return record id + */ + private synchronized RecordId writeValueRecord(String reference) { + byte[] data = reference.getBytes(Charsets.UTF_8); + int length = data.length; + + checkArgument(length < 8192); + + RecordId id = prepare(RecordType.VALUE, 2 + length); + int len = length | 0xE000; + buffer[position++] = (byte) (len >> 8); + buffer[position++] = (byte) len; + + System.arraycopy(data, 0, buffer, position, length); + position += length; + return id; + } + + /** * Writes a block record containing the given block of bytes. * * @param bytes source buffer @@ -640,13 +662,20 @@ } public SegmentBlob writeBlob(Blob blob) throws IOException { - if (store.isInstance(blob, SegmentBlob.class)) { + if (blob instanceof ExternalBlob) { + return writeBlob((ExternalBlob) blob); + } else if (store.isInstance(blob, SegmentBlob.class)) { return (SegmentBlob) blob; } else { return writeStream(blob.getNewStream()); } } + private SegmentBlob writeBlob(ExternalBlob blob) { + RecordId id = writeValueRecord(blob.getReference()); + return new SegmentBlob(dummySegment, id, true); + } + /** * Writes a stream value record. The given stream is consumed * and closed by this method. @@ -666,7 +695,7 @@ Closeables.close(stream, threw); } } - return new SegmentBlob(dummySegment, id); + return new SegmentBlob(dummySegment, id, false); } private RecordId internalWriteStream(InputStream stream) Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/FileBlob.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/FileBlob.java (revision 0) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/FileBlob.java (working copy) @@ -0,0 +1,57 @@ +/* + * 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.segment.file; + +import org.apache.jackrabbit.oak.plugins.segment.ExternalBlob; + +import javax.annotation.Nonnull; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +public class FileBlob implements ExternalBlob { + + private final String path; + + public FileBlob(String path) { + this.path = path; + } + + public String getReference() { + return path; + } + + @Nonnull + @Override + public InputStream getNewStream() { + try { + return new FileInputStream(getFile()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public long length() { + return getFile().length(); + } + + private File getFile() { + return new File(path); + } +} Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/FileStore.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/FileStore.java (revision 1558843) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/FileStore.java (working copy) @@ -36,11 +36,13 @@ import java.util.concurrent.atomic.AtomicReference; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import org.apache.jackrabbit.oak.plugins.segment.AbstractStore; import org.apache.jackrabbit.oak.plugins.segment.Journal; import org.apache.jackrabbit.oak.plugins.segment.RecordId; import org.apache.jackrabbit.oak.plugins.segment.Segment; +import org.apache.jackrabbit.oak.plugins.segment.ExternalBlob; import org.apache.jackrabbit.oak.spi.state.NodeBuilder; import org.apache.jackrabbit.oak.spi.state.NodeState; import org.slf4j.Logger; @@ -338,4 +340,9 @@ super.deleteSegment(segmentId); } + @Nullable + @Override + public ExternalBlob readBlob(String reference) { + return new FileBlob(reference); + } }