Added:
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/binary/BinaryAccessIT.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/binary/BinaryAccessIT.java?rev=1838617&view=auto
==============================================================================
---
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/binary/BinaryAccessIT.java
(added)
+++
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/binary/BinaryAccessIT.java
Wed Aug 22 08:51:41 2018
@@ -0,0 +1,876 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.jackrabbit.oak.jcr.binary;
+
+import static junit.framework.TestCase.assertNull;
+import static junit.framework.TestCase.assertTrue;
+import static
org.apache.jackrabbit.oak.jcr.binary.util.BinaryAccessTestUtils.getBinary;
+import static
org.apache.jackrabbit.oak.jcr.binary.util.BinaryAccessTestUtils.httpGet;
+import static
org.apache.jackrabbit.oak.jcr.binary.util.BinaryAccessTestUtils.httpPut;
+import static
org.apache.jackrabbit.oak.jcr.binary.util.BinaryAccessTestUtils.isFailedHttpPut;
+import static
org.apache.jackrabbit.oak.jcr.binary.util.BinaryAccessTestUtils.isSuccessfulHttpPut;
+import static
org.apache.jackrabbit.oak.jcr.binary.util.BinaryAccessTestUtils.putBinary;
+import static
org.apache.jackrabbit.oak.jcr.binary.util.BinaryAccessTestUtils.storeBinary;
+import static
org.apache.jackrabbit.oak.jcr.binary.util.BinaryAccessTestUtils.storeBinaryAndRetrieve;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+import java.net.HttpURLConnection;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import javax.jcr.AccessDeniedException;
+import javax.jcr.Binary;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.jackrabbit.api.JackrabbitValueFactory;
+import org.apache.jackrabbit.api.ReferenceBinary;
+import org.apache.jackrabbit.api.binary.BinaryDownload;
+import org.apache.jackrabbit.api.binary.BinaryDownloadOptions;
+import org.apache.jackrabbit.api.binary.BinaryUpload;
+import org.apache.jackrabbit.oak.blob.cloud.s3.S3DataStore;
+import org.apache.jackrabbit.oak.fixture.NodeStoreFixture;
+import org.apache.jackrabbit.oak.jcr.binary.util.Content;
+import
org.apache.jackrabbit.oak.plugins.blob.datastore.directaccess.ConfigurableDataRecordAccessProvider;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.Iterables;
+
+/**
+ * Integration test for direct binary GET/PUT via HTTP, that requires a fully
working data store
+ * (such as S3) for each {@link AbstractBinaryAccessIT#dataStoreFixtures()
configured fixture}.
+ * The data store in question must support direct GET/PUT access via a URI.
+ *
+ * Data store must be configured through e.g. aws.properties.
+ *
+ * Run this IT in maven using either:
+ *
+ * single test:
+ * mvn clean test -Dtest=BinaryAccessIT
+ *
+ * as part of all integration tests:
+ * mvn -PintegrationTesting clean install
+ */
+@RunWith(Parameterized.class)
+public class BinaryAccessIT extends AbstractBinaryAccessIT {
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ private static final String FILE_PATH = "/file";
+ private static final int REGULAR_WRITE_EXPIRY = 60*5; // seconds
+ private static final int REGULAR_READ_EXPIRY = 60*5; // seconds
+
+ public BinaryAccessIT(NodeStoreFixture fixture) {
+ // reuse NodeStore (and DataStore) across all tests in this class
+ super(fixture, true);
+ }
+
+ private JackrabbitValueFactory uploadProvider;
+ private JackrabbitValueFactory anonymousUploadProvider;
+
+ @Before
+ public void cleanRepoContents() throws RepositoryException {
+ Session anonymousSession = getAnonymousSession();
+ uploadProvider = (JackrabbitValueFactory)
getAdminSession().getValueFactory();
+ anonymousUploadProvider = (JackrabbitValueFactory)
anonymousSession.getValueFactory();
+
+ if (getAdminSession().nodeExists(FILE_PATH)) {
+ getAdminSession().getNode(FILE_PATH).remove();
+ getAdminSession().save();
+ }
+ }
+
+ // F1 - basic test
+ @Test
+ public void testUpload() throws Exception {
+ // enable writable URI feature
+ getConfigurableHttpDataRecordProvider()
+ .setDirectUploadURIExpirySeconds(REGULAR_WRITE_EXPIRY);
+
+ Content content = Content.createRandom(256);
+
+ assertTrue(getAdminSession().getValueFactory() instanceof
JackrabbitValueFactory);
+
+ BinaryUpload upload =
uploadProvider.initiateBinaryUpload(content.size(), 1);
+ assertNotNull(upload);
+
+ // very small test binary
+ assertTrue(content.size() < upload.getMaxPartSize());
+
+ URI uri = upload.getUploadURIs().iterator().next();
+ assertNotNull(uri);
+
+ log.info("- uploading binary via PUT to {}", uri.toString());
+ int code = httpPut(uri, content.size(), content.getStream());
+
+ assertTrue("PUT to pre-signed URI failed",
+ isSuccessfulHttpPut(code,
getConfigurableHttpDataRecordProvider()));
+
+ Binary writeBinary =
uploadProvider.completeBinaryUpload(upload.getUploadToken());
+ putBinary(getAdminSession(), FILE_PATH, writeBinary);
+
+ Binary readBinary = getBinary(getAdminSession(), FILE_PATH);
+ content.assertEqualsWith(readBinary.getStream());
+ }
+
+ // F3 - Multi-part upload
+ @Test
+ public void testMultiPartUpload() throws Exception {
+ getConfigurableHttpDataRecordProvider()
+ .setDirectUploadURIExpirySeconds(REGULAR_WRITE_EXPIRY);
+
+ assertTrue(getAdminSession().getValueFactory() instanceof
JackrabbitValueFactory);
+
+ // 25MB is a good size to ensure chunking is done
+ Content content = Content.createRandom(1024 * 1024 * 25);
+ BinaryUpload upload =
uploadProvider.initiateBinaryUpload(content.size(), 50);
+ assertNotNull(upload);
+
+ List<URI> uris = new ArrayList<>();
+ Iterables.addAll(uris, upload.getUploadURIs());
+
+ // this follows the upload algorithm from BinaryUpload
+ if (content.size() / upload.getMaxPartSize() > uris.size()) {
+ fail("exact binary size was provided but implementation failed to
provide enough upload URIs");
+ }
+ if (content.size() < upload.getMinPartSize()) {
+ // single upload
+ content.httpPUT(uris.get(0));
+ } else {
+ // multipart upload
+ final long basePartSize = (long) Math.ceil(content.size() /
(double) uris.size());
+
+ long offset = 0;
+ for (URI uri : uris) {
+ final long partSize = Math.min(basePartSize, content.size() -
offset);
+
+ int code = content.httpPUT(uri, offset, partSize);
+ assertTrue(isSuccessfulHttpPut(code,
getConfigurableHttpDataRecordProvider()));
+
+ offset += partSize;
+ // fail safe check, shouldn't be necessary
+ if (offset >= content.size()) {
+ break;
+ }
+ }
+ }
+
+ Binary writeBinary =
uploadProvider.completeBinaryUpload(upload.getUploadToken());
+ putBinary(getAdminSession(), FILE_PATH, writeBinary);
+
+ Binary readBinary = getBinary(getAdminSession(), FILE_PATH);
+ content.assertEqualsWith(readBinary.getStream());
+ }
+
+ // F8 - test reading getBinary().toInputStream() once uploaded
+ @Test
+ public void testStreamBinaryThroughJCRAfterURIWrite() throws Exception {
+ // enable writable URI feature
+ getConfigurableHttpDataRecordProvider()
+ .setDirectUploadURIExpirySeconds(REGULAR_WRITE_EXPIRY);
+
+ // 1. add binary and upload
+ Content content = Content.createRandom(256);
+ BinaryUpload upload =
uploadProvider.initiateBinaryUpload(content.size(), 10);
+ int code = content.httpPUT(upload.getUploadURIs().iterator().next());
+ assertTrue(isSuccessfulHttpPut(code,
getConfigurableHttpDataRecordProvider()));
+
+ Binary binaryWrite =
uploadProvider.completeBinaryUpload(upload.getUploadToken());
+ storeBinary(getAdminSession(), FILE_PATH, binaryWrite);
+
+ // 2. stream through JCR and validate it's the same
+ Session session = createAdminSession();
+ try {
+ Binary binaryRead = getBinary(session, FILE_PATH);
+ content.assertEqualsWith(binaryRead.getStream());
+ } finally {
+ session.logout();
+ }
+ }
+
+ // F10 - GET Binary when created via repo
+ @Test
+ public void testGetBinary() throws Exception {
+ getConfigurableHttpDataRecordProvider()
+ .setDirectDownloadURIExpirySeconds(REGULAR_READ_EXPIRY);
+
+ // Must be larger than the minimum file size, to keep it from being
inlined in the node store.
+ Content content = Content.createRandom(1024*20);
+
+ // make sure to test getting a fresh Binary
+ Binary binary = storeBinaryAndRetrieve(getAdminSession(), FILE_PATH,
content);
+ assertTrue(binary instanceof BinaryDownload);
+
+ URI downloadURI = ((BinaryDownload)
binary).getURI(BinaryDownloadOptions.DEFAULT);
+ assertNotNull("HTTP download URI is null", downloadURI);
+ content.assertEqualsWith(httpGet(downloadURI));
+
+ // different way to retrieve binary
+ // TODO: also test multivalue binary prop
+ binary = getAdminSession().getNode(FILE_PATH)
+ .getNode(JcrConstants.JCR_CONTENT)
+ .getProperty(JcrConstants.JCR_DATA).getValue().getBinary();
+ downloadURI = ((BinaryDownload)
binary).getURI(BinaryDownloadOptions.DEFAULT);
+ assertNotNull("HTTP download URI is null", downloadURI);
+ }
+
+ // F9 - GET Binary for binary after write using direct PUT
+ @Test
+ public void testGetBinaryAfterPut() throws Exception {
+ // enable writable and readable URI feature
+ ConfigurableDataRecordAccessProvider provider =
getConfigurableHttpDataRecordProvider();
+ provider.setDirectUploadURIExpirySeconds(REGULAR_WRITE_EXPIRY);
+ provider.setDirectDownloadURIExpirySeconds(REGULAR_READ_EXPIRY);
+
+ // 1. add binary and upload
+ Content content = Content.createRandom(1024*20);
+ BinaryUpload upload =
uploadProvider.initiateBinaryUpload(content.size(), 10);
+ int code = content.httpPUT(upload.getUploadURIs().iterator().next());
+ assertTrue(isSuccessfulHttpPut(code,
getConfigurableHttpDataRecordProvider()));
+ Binary writeBinary =
uploadProvider.completeBinaryUpload(upload.getUploadToken());
+ storeBinary(getAdminSession(), FILE_PATH, writeBinary);
+
+ // 2. read binary, get the URI
+ Binary binary = getBinary(getAdminSession(), FILE_PATH);
+ URI downloadURI =
((BinaryDownload)(binary)).getURI(BinaryDownloadOptions.DEFAULT);
+
+ // 3. GET on URI and verify contents are the same
+ content.assertEqualsWith(httpGet(downloadURI));
+ }
+
+ @Test
+ public void testGetSmallBinaryReturnsNull() throws Exception {
+ getConfigurableHttpDataRecordProvider()
+ .setDirectDownloadURIExpirySeconds(REGULAR_READ_EXPIRY);
+
+ // Must be smaller than the minimum file size, (inlined binary)
+ Content content = Content.createRandom(256);
+ Binary binary = storeBinaryAndRetrieve(getAdminSession(), FILE_PATH,
content);
+
+ URI downloadURI =
((BinaryDownload)(binary)).getURI(BinaryDownloadOptions.DEFAULT);
+ assertNull(downloadURI);
+ }
+
+ @Test
+ public void testGetBinaryWithSpecificMediaType() throws Exception {
+ getConfigurableHttpDataRecordProvider()
+ .setDirectDownloadURIExpirySeconds(REGULAR_READ_EXPIRY);
+
+ Content content = Content.createRandom(1024*20);
+ Binary binary = storeBinaryAndRetrieve(getAdminSession(), FILE_PATH,
content);
+
+ String expectedMediaType = "image/png";
+ BinaryDownloadOptions downloadOptions = BinaryDownloadOptions
+ .builder()
+ .withMediaType(expectedMediaType)
+ .build();
+ URI downloadURI = ((BinaryDownload)(binary)).getURI(downloadOptions);
+
+ HttpURLConnection conn = (HttpURLConnection)
downloadURI.toURL().openConnection();
+ String mediaType = conn.getHeaderField("Content-Type");
+ assertNotNull(mediaType);
+ assertEquals(expectedMediaType, mediaType);
+
+ // Verify response content
+ assertEquals(200, conn.getResponseCode());
+ content.assertEqualsWith(conn.getInputStream());
+ }
+
+ @Test
+ public void testGetBinaryWithSpecificMediaTypeAndEncoding() throws
Exception {
+ getConfigurableHttpDataRecordProvider()
+ .setDirectDownloadURIExpirySeconds(REGULAR_READ_EXPIRY);
+
+ Content content = Content.createRandom(1024*20);
+ Binary binary = storeBinaryAndRetrieve(getAdminSession(), FILE_PATH,
content);
+
+ String expectedMediaType = "text/plain";
+ String expectedCharacterEncoding = "utf-8";
+ BinaryDownloadOptions downloadOptions = BinaryDownloadOptions
+ .builder()
+ .withMediaType(expectedMediaType)
+ .withCharacterEncoding(expectedCharacterEncoding)
+ .build();
+ URI downloadURI = ((BinaryDownload)(binary)).getURI(downloadOptions);
+
+ HttpURLConnection conn = (HttpURLConnection)
downloadURI.toURL().openConnection();
+ String mediaType = conn.getHeaderField("Content-Type");
+ assertNotNull(mediaType);
+ assertEquals(String.format("%s; charset=%s", expectedMediaType,
expectedCharacterEncoding),
+ mediaType);
+
+ // Verify response content
+ assertEquals(200, conn.getResponseCode());
+ content.assertEqualsWith(conn.getInputStream());
+ }
+
+ @Test
+ public void testGetBinaryWithCharacterEncodingOnly() throws Exception {
+ getConfigurableHttpDataRecordProvider()
+ .setDirectDownloadURIExpirySeconds(REGULAR_READ_EXPIRY);
+
+ Content content = Content.createRandom(1024*20);
+ Binary binary = storeBinaryAndRetrieve(getAdminSession(), FILE_PATH,
content);
+
+ String expectedCharacterEncoding = "utf-8";
+ BinaryDownloadOptions downloadOptions = BinaryDownloadOptions
+ .builder()
+ .withCharacterEncoding(expectedCharacterEncoding)
+ .build();
+ URI downloadURI = ((BinaryDownload)(binary)).getURI(downloadOptions);
+
+ HttpURLConnection conn = (HttpURLConnection)
downloadURI.toURL().openConnection();
+ String mediaType = conn.getHeaderField("Content-Type");
+ // application/octet-stream is the default Content-Type if none is
+ // set in the signed URI
+ assertEquals("application/octet-stream", mediaType);
+
+ // Verify response content
+ assertEquals(200, conn.getResponseCode());
+ content.assertEqualsWith(conn.getInputStream());
+ }
+
+ @Test
+ public void testGetBinaryWithSpecificFileName() throws Exception {
+ getConfigurableHttpDataRecordProvider()
+ .setDirectDownloadURIExpirySeconds(REGULAR_READ_EXPIRY);
+
+ Content content = Content.createRandom(1024*20);
+ Binary binary = storeBinaryAndRetrieve(getAdminSession(), FILE_PATH,
content);
+
+ String expectedName = "beautiful landscape.png";
+ BinaryDownloadOptions downloadOptions = BinaryDownloadOptions
+ .builder()
+ .withFileName(expectedName)
+ .build();
+ URI downloadURI = ((BinaryDownload)(binary)).getURI(downloadOptions);
+
+ HttpURLConnection conn = (HttpURLConnection)
downloadURI.toURL().openConnection();
+ String contentDisposition = conn.getHeaderField("Content-Disposition");
+ assertNotNull(contentDisposition);
+ String encodedName = new
String(expectedName.getBytes(StandardCharsets.UTF_8));
+ assertEquals(
+ String.format("inline; filename=\"%s\"; filename*=UTF-8''%s",
+ expectedName, encodedName),
+ contentDisposition
+ );
+
+ // Verify response content
+ assertEquals(200, conn.getResponseCode());
+ content.assertEqualsWith(conn.getInputStream());
+ }
+
+ @Test
+ public void testGetBinaryWithSpecificFileNameAndDispositionType() throws
Exception {
+ getConfigurableHttpDataRecordProvider()
+ .setDirectDownloadURIExpirySeconds(REGULAR_READ_EXPIRY);
+
+ Content content = Content.createRandom(1024*20);
+ Binary binary = storeBinaryAndRetrieve(getAdminSession(), FILE_PATH,
content);
+
+ String expectedName = "beautiful landscape.png";
+ BinaryDownloadOptions downloadOptions = BinaryDownloadOptions
+ .builder()
+ .withFileName(expectedName)
+ .withDispositionTypeAttachment()
+ .build();
+ URI downloadURI = ((BinaryDownload)(binary)).getURI(downloadOptions);
+
+ HttpURLConnection conn = (HttpURLConnection)
downloadURI.toURL().openConnection();
+ String contentDisposition = conn.getHeaderField("Content-Disposition");
+ assertNotNull(contentDisposition);
+ String encodedName = new
String(expectedName.getBytes(StandardCharsets.UTF_8));
+ assertEquals(
+ String.format("attachment; filename=\"%s\";
filename*=UTF-8''%s",
+ expectedName, encodedName),
+ contentDisposition
+ );
+
+ // Verify response content
+ assertEquals(200, conn.getResponseCode());
+ content.assertEqualsWith(conn.getInputStream());
+ }
+
+ @Test
+ public void testGetBinaryWithDispositionType() throws Exception {
+ getConfigurableHttpDataRecordProvider()
+ .setDirectDownloadURIExpirySeconds(REGULAR_READ_EXPIRY);
+
+ Content content = Content.createRandom(1024*20);
+ Binary binary = storeBinaryAndRetrieve(getAdminSession(), FILE_PATH,
content);
+
+ BinaryDownloadOptions downloadOptions = BinaryDownloadOptions
+ .builder()
+ .withDispositionTypeInline()
+ .build();
+ URI downloadURI = ((BinaryDownload)(binary)).getURI(downloadOptions);
+
+ HttpURLConnection conn = (HttpURLConnection)
downloadURI.toURL().openConnection();
+ String contentDisposition = conn.getHeaderField("Content-Disposition");
+ // Should be no header since filename was not set and disposition type
+ // is "inline"
+ assertNull(contentDisposition);
+
+ // Verify response content
+ assertEquals(200, conn.getResponseCode());
+ content.assertEqualsWith(conn.getInputStream());
+
+ downloadOptions = BinaryDownloadOptions
+ .builder()
+ .withDispositionTypeAttachment()
+ .build();
+ downloadURI = ((BinaryDownload)(binary)).getURI(downloadOptions);
+
+ conn = (HttpURLConnection) downloadURI.toURL().openConnection();
+ contentDisposition = conn.getHeaderField("Content-Disposition");
+ // Content-Disposition should exist now because "attachment" was set
+ assertNotNull(contentDisposition);
+ assertEquals("attachment", contentDisposition);
+ }
+
+ @Test
+ public void testGetBinarySetsAllHeaders() throws Exception {
+ getConfigurableHttpDataRecordProvider()
+ .setDirectDownloadURIExpirySeconds(REGULAR_READ_EXPIRY);
+
+ Content content = Content.createRandom(1024*20);
+ Binary binary = storeBinaryAndRetrieve(getAdminSession(), FILE_PATH,
content);
+
+ String expectedMediaType = "image/png";
+ String expectedCharacterEncoding = "utf-8";
+ String expectedName = "beautiful landscape.png";
+ BinaryDownloadOptions downloadOptions = BinaryDownloadOptions
+ .builder()
+ .withMediaType(expectedMediaType)
+ .withCharacterEncoding(expectedCharacterEncoding)
+ .withFileName(expectedName)
+ .withDispositionTypeAttachment()
+ .build();
+ URI downloadURI = ((BinaryDownload)(binary)).getURI(downloadOptions);
+
+ HttpURLConnection conn = (HttpURLConnection)
downloadURI.toURL().openConnection();
+ String mediaType = conn.getHeaderField("Content-Type");
+ assertNotNull(mediaType);
+ assertEquals(
+ String.format("%s; charset=%s", expectedMediaType,
expectedCharacterEncoding),
+ mediaType);
+
+ String contentDisposition = conn.getHeaderField("Content-Disposition");
+ assertNotNull(contentDisposition);
+ String encodedName = new
String(expectedName.getBytes(StandardCharsets.UTF_8));
+ assertEquals(
+ String.format("attachment; filename=\"%s\";
filename*=UTF-8''%s",
+ expectedName, encodedName),
+ contentDisposition
+ );
+
+ String cacheControl = conn.getHeaderField("Cache-Control");
+ assertNotNull(cacheControl);
+ assertEquals(String.format("private, max-age=%d, immutable",
REGULAR_READ_EXPIRY), cacheControl);
+
+ // Verify response content
+ assertEquals(200, conn.getResponseCode());
+ content.assertEqualsWith(conn.getInputStream());
+ }
+
+ @Test
+ public void testGetBinaryDefaultOptions() throws Exception {
+ getConfigurableHttpDataRecordProvider()
+ .setDirectDownloadURIExpirySeconds(REGULAR_READ_EXPIRY);
+
+ Content content = Content.createRandom(1024*20);
+ Binary binary = storeBinaryAndRetrieve(getAdminSession(), FILE_PATH,
content);
+
+ URI downloadURI =
((BinaryDownload)(binary)).getURI(BinaryDownloadOptions.DEFAULT);
+
+ HttpURLConnection conn = (HttpURLConnection)
downloadURI.toURL().openConnection();
+ String mediaType = conn.getHeaderField("Content-Type");
+ assertNotNull(mediaType);
+ assertEquals("application/octet-stream", mediaType);
+
+ String contentDisposition = conn.getHeaderField("Content-Disposition");
+ assertNull(contentDisposition);
+
+ String cacheControl = conn.getHeaderField("Cache-Control");
+ assertNotNull(cacheControl);
+ assertEquals(String.format("private, max-age=%d, immutable",
REGULAR_READ_EXPIRY), cacheControl);
+
+ // Verify response content
+ assertEquals(200, conn.getResponseCode());
+ content.assertEqualsWith(conn.getInputStream());
+ }
+
+ // A6 - Client MUST only get permission to add a blob referenced in a JCR
binary property
+ // where the user has JCR set_property permission.
+ @Test
+ @Ignore("OAK-7602") // michid FIXME OAK-7602
+ public void testUnprivilegedSessionCannotUploadBinary() throws Exception {
+ // enable writable URI feature
+ getConfigurableHttpDataRecordProvider()
+ .setDirectUploadURIExpirySeconds(REGULAR_WRITE_EXPIRY);
+
+ try {
+ anonymousUploadProvider.initiateBinaryUpload(1024*20, 10);
+ fail();
+ }
+ catch (AccessDeniedException e) { }
+ }
+
+ // A2 - disable write URIs entirely
+ @Test
+ public void testDisableDirectHttpUpload() throws Exception {
+ // disable in data store config by setting expiry to zero
+ getConfigurableHttpDataRecordProvider()
+ .setDirectUploadURIExpirySeconds(0);
+
+ Content content = Content.createRandom(256);
+ BinaryUpload upload =
uploadProvider.initiateBinaryUpload(content.size(), 10);
+
+ assertNotNull(upload);
+ assertFalse(upload.getUploadURIs().iterator().hasNext());
+ }
+
+ // A2 - disable get URIs entirely
+ @Test
+ public void testDisableDirectHttpDownload() throws Exception {
+ getConfigurableHttpDataRecordProvider()
+ .setDirectDownloadURIExpirySeconds(0);
+
+ Content content = Content.createRandom(1024*20);
+ Binary binary = storeBinaryAndRetrieve(getAdminSession(), FILE_PATH,
content);
+
+ URI downloadURI =
((BinaryDownload)(binary)).getURI(BinaryDownloadOptions.DEFAULT);
+ assertNull(downloadURI);
+ }
+
+ // A2/A3 - configure short expiry time, wait, ensure upload fails after
expired
+ @Test
+ public void testPutURIExpires() throws Exception {
+ // short timeout
+ getConfigurableHttpDataRecordProvider()
+ .setDirectUploadURIExpirySeconds(1);
+
+ Content content = Content.createRandom(1024*20);
+ BinaryUpload upload =
uploadProvider.initiateBinaryUpload(content.size(), 10);
+
+ // wait to pass timeout: 2 seconds
+ Thread.sleep(2 * 1000);
+
+ // ensure PUT fails with 403 or anything 400+
+ assertTrue(content.httpPUT(upload.getUploadURIs().iterator().next())
>= HttpURLConnection.HTTP_BAD_REQUEST);
+ }
+
+ // F2 - transfer accelerator (S3 only feature)
+ @Test
+ public void testTransferAcceleration() throws Exception {
+ ConfigurableDataRecordAccessProvider provider =
getConfigurableHttpDataRecordProvider();
+
+ // This test is S3 specific
+ if (provider instanceof S3DataStore) {
+
+ provider.setDirectUploadURIExpirySeconds(REGULAR_WRITE_EXPIRY);
+ provider.setDirectDownloadURIExpirySeconds(REGULAR_READ_EXPIRY);
+ provider.setBinaryTransferAccelerationEnabled(true);
+
+ BinaryUpload upload = uploadProvider.initiateBinaryUpload(1024 *
20, 1);
+ URI uri = upload.getUploadURIs().iterator().next();
+ assertNotNull(uri);
+
+ log.info("accelerated URI: {}", uri.toString());
+ assertTrue(uri.getHost().endsWith(".s3-accelerate.amazonaws.com"));
+
+ provider.setBinaryTransferAccelerationEnabled(false);
+ upload = uploadProvider.initiateBinaryUpload(1024*20, 1);
+ uri = upload.getUploadURIs().iterator().next();
+ assertNotNull(uri);
+
+ log.info("non-accelerated URI: {}", uri.toString());
+
assertFalse(uri.getHost().endsWith(".s3-accelerate.amazonaws.com"));
+ }
+ }
+
+ // A1 - get put URI, change it and try uploading it
+ @Test
+ public void testModifiedPutURIFails() throws Exception {
+ // enable writable URI feature
+ getConfigurableHttpDataRecordProvider()
+ .setDirectUploadURIExpirySeconds(REGULAR_WRITE_EXPIRY);
+ Content content = Content.createRandom(1024*20);
+ BinaryUpload upload =
uploadProvider.initiateBinaryUpload(content.size(), 1);
+ URI uri = upload.getUploadURIs().iterator().next();
+ URI changedURI = new URI(
+ String.format("%s://%s/%sX?%s", // NOTE the injected "X" in
the URI filename
+ uri.getScheme(),
+ uri.getHost(),
+ uri.getPath(),
+ uri.getQuery())
+ );
+ int code = content.httpPUT(changedURI);
+ assertTrue(isFailedHttpPut(code));
+ }
+
+ // A1 - get put URI, upload, then try reading from the same URI
+ @Test
+ public void testCannotReadFromPutURI() throws Exception {
+ // enable writable URI and readable URI feature
+ ConfigurableDataRecordAccessProvider provider =
getConfigurableHttpDataRecordProvider();
+ provider.setDirectUploadURIExpirySeconds(REGULAR_WRITE_EXPIRY);
+ provider.setDirectDownloadURIExpirySeconds(REGULAR_READ_EXPIRY);
+
+ Content content = Content.createRandom(1024*20);
+ BinaryUpload upload =
uploadProvider.initiateBinaryUpload(content.size(), 1);
+ URI uri = upload.getUploadURIs().iterator().next();
+ int code = content.httpPUT(uri);
+ assertTrue(isSuccessfulHttpPut(code,
getConfigurableHttpDataRecordProvider()));
+
+ HttpURLConnection conn = (HttpURLConnection)
uri.toURL().openConnection();
+ code = conn.getResponseCode();
+ assertTrue(isFailedHttpPut(code));
+ }
+
+ // A1 - add binary via JCR, then get put URI, and modify to try to upload
over first binary
+ @Test
+ public void testCannotModifyExistingBinaryViaPutURI() throws Exception {
+ // enable writable URI and readable URI feature
+ ConfigurableDataRecordAccessProvider provider =
getConfigurableHttpDataRecordProvider();
+ provider.setDirectUploadURIExpirySeconds(REGULAR_WRITE_EXPIRY);
+ provider.setDirectDownloadURIExpirySeconds(REGULAR_READ_EXPIRY);
+
+ Content content = Content.createRandom(1024*20);
+ Binary binary = storeBinaryAndRetrieve(getAdminSession(), FILE_PATH,
content);
+
+ URI downloadURI =
((BinaryDownload)(binary)).getURI(BinaryDownloadOptions.DEFAULT);
+ assertNotNull(downloadURI);
+
+ Content moreContent = Content.createRandom(1024*20);
+ uploadProvider.initiateBinaryUpload(moreContent.size(), 1);
+ HttpURLConnection conn = (HttpURLConnection)
downloadURI.toURL().openConnection();
+ conn.setRequestMethod("PUT");
+ conn.setDoOutput(true);
+ IOUtils.copy(moreContent.getStream(), conn.getOutputStream());
+
+ int code = conn.getResponseCode();
+ assertTrue(isFailedHttpPut(code));
+
+ content.assertEqualsWith(httpGet(downloadURI));
+ }
+
+ // D1 - immutable after initial upload
+ @Test
+ public void testUploadedBinaryIsImmutable() throws Exception {
+ // enable writable URI feature
+ getConfigurableHttpDataRecordProvider()
+ .setDirectUploadURIExpirySeconds(REGULAR_WRITE_EXPIRY);
+
+ // 1. upload and store first binary
+ Content content = Content.createRandom(1024*20);
+ BinaryUpload upload =
uploadProvider.initiateBinaryUpload(content.size(), 1);
+ assertNotNull(upload);
+ int code = content.httpPUT(upload.getUploadURIs().iterator().next());
+ assertTrue(isSuccessfulHttpPut(code,
getConfigurableHttpDataRecordProvider()));
+ Binary uploadedBinary =
uploadProvider.completeBinaryUpload(upload.getUploadToken());
+ storeBinary(getAdminSession(), FILE_PATH, uploadedBinary);
+
+ Binary binary1 = getBinary(getAdminSession(), FILE_PATH);
+
+ // 2. upload different binary content, but store at the same JCR
location
+ Content moreContent = Content.createRandom(1024*21);
+ upload = uploadProvider.initiateBinaryUpload(moreContent.size(), 1);
+ assertNotNull(upload);
+ code = moreContent.httpPUT(upload.getUploadURIs().iterator().next());
+ assertTrue(isSuccessfulHttpPut(code,
getConfigurableHttpDataRecordProvider()));
+ uploadedBinary =
uploadProvider.completeBinaryUpload(upload.getUploadToken());
+ storeBinary(getAdminSession(), FILE_PATH, uploadedBinary);
+
+ Binary binary2 = getBinary(getAdminSession(), FILE_PATH);
+
+ // 3. verify they have different references
+ assertTrue(binary1 instanceof ReferenceBinary);
+ assertTrue(binary2 instanceof ReferenceBinary);
+ assertNotEquals(((ReferenceBinary) binary1).getReference(),
((ReferenceBinary) binary2).getReference());
+ }
+
+ // D2 - unique identifiers
+ @Test
+ public void testUploadPathsAreUnique() throws Exception {
+ // Every upload destination should be unique with no regard to file
content
+ // The content is not read by the Oak code so no deduplication can be
performed
+ getConfigurableHttpDataRecordProvider()
+ .setDirectUploadURIExpirySeconds(REGULAR_WRITE_EXPIRY);
+
+ Content content = Content.createRandom(1024*20);
+
+ BinaryUpload upload1 =
uploadProvider.initiateBinaryUpload(content.size(), 1);
+ assertNotNull(upload1);
+ BinaryUpload upload2 =
uploadProvider.initiateBinaryUpload(content.size(), 1);
+ assertNotNull(upload2);
+
+ assertNotEquals(upload1.getUploadURIs().iterator().next().toString(),
+ upload2.getUploadURIs().iterator().next().toString());
+ }
+
+ // D3 - do not delete directly => copy nt:file node, delete one, ensure
binary still there
+ @Test
+ public void testBinaryNotDeletedWithNode() throws Exception {
+ getConfigurableHttpDataRecordProvider()
+ .setDirectUploadURIExpirySeconds(REGULAR_WRITE_EXPIRY);
+
+ Content content = Content.createRandom(1024*20);
+
+ BinaryUpload upload =
uploadProvider.initiateBinaryUpload(content.size(), 1);
+ int code = content.httpPUT(upload.getUploadURIs().iterator().next());
+ assertTrue(isSuccessfulHttpPut(code,
getConfigurableHttpDataRecordProvider()));
+ Binary binary =
uploadProvider.completeBinaryUpload(upload.getUploadToken());
+ storeBinary(getAdminSession(), FILE_PATH+"2", binary);
+
+ storeBinary(getAdminSession(), FILE_PATH, binary);
+
+ getAdminSession().getNode(FILE_PATH+"2").remove();
+ getAdminSession().save();
+
+ Binary savedBinary = getBinary(getAdminSession(), FILE_PATH);
+ assertNotNull(savedBinary);
+ assertTrue(binary instanceof ReferenceBinary);
+ assertTrue(savedBinary instanceof ReferenceBinary);
+ assertEquals(((ReferenceBinary) binary).getReference(),
+ ((ReferenceBinary) savedBinary).getReference());
+ }
+
+ // D5 - blob ref not persisted in NodeStore until binary uploaded and
immutable
+ // NOTE: not test needed for this, as the API guarantees the Binary is
only returned after
+ // the blob was persisted in completeBinaryUpload() and client code
is responsible
+ // for writing it to the JCR
+
+ // more tests
+
+ @Test
+ public void testInitiateHttpUploadWithZeroSizeFails() throws
RepositoryException {
+ getConfigurableHttpDataRecordProvider()
+ .setDirectUploadURIExpirySeconds(REGULAR_WRITE_EXPIRY);
+ try {
+ uploadProvider.initiateBinaryUpload(0, 1);
+ fail();
+ }
+ catch (IllegalArgumentException e) { }
+ }
+
+ @Test
+ public void testInitiateHttpUploadWithZeroURIsFails() throws
RepositoryException {
+ getConfigurableHttpDataRecordProvider()
+ .setDirectUploadURIExpirySeconds(REGULAR_WRITE_EXPIRY);
+ try {
+ uploadProvider.initiateBinaryUpload(1024 * 20, 0);
+ fail();
+ }
+ catch (IllegalArgumentException e) { }
+ }
+
+ @Test
+ public void testInitiateHttpUploadWithUnsupportedNegativeNumberURIsFails()
+ throws RepositoryException {
+ getConfigurableHttpDataRecordProvider()
+ .setDirectUploadURIExpirySeconds(REGULAR_WRITE_EXPIRY);
+ try {
+ uploadProvider.initiateBinaryUpload(1024 * 20, -2);
+ fail();
+ }
+ catch (IllegalArgumentException e) { }
+ }
+
+ @Test
+ public void testInitiateHttpUploadWithUnlimitedURIs() throws
RepositoryException {
+ getConfigurableHttpDataRecordProvider()
+ .setDirectUploadURIExpirySeconds(REGULAR_WRITE_EXPIRY);
+ BinaryUpload upload = uploadProvider.initiateBinaryUpload(1024 * 1024
* 1024, -1);
+ assertNotNull(upload);
+ assertTrue(Iterables.size(upload.getUploadURIs()) > 50);
+ // 50 is our default expected client max -
+ // this is to make sure we will give as many as needed
+ // if the client doesn't specify their own limit
+ }
+
+ @Test
+ public void testInitiateHttpUploadLargeSinglePut() throws
RepositoryException {
+ getConfigurableHttpDataRecordProvider()
+ .setDirectUploadURIExpirySeconds(REGULAR_WRITE_EXPIRY);
+ BinaryUpload upload = uploadProvider.initiateBinaryUpload(1024 * 1024
* 100, 1);
+ assertNotNull(upload);
+ assertEquals(1, Iterables.size(upload.getUploadURIs()));
+ }
+
+ @Test
+ public void testInitiateHttpUploadTooLargeForSinglePut() throws
RepositoryException {
+ getConfigurableHttpDataRecordProvider()
+ .setDirectUploadURIExpirySeconds(REGULAR_WRITE_EXPIRY);
+ try {
+ uploadProvider.initiateBinaryUpload(1024L * 1024L * 1024L * 10L,
1);
+ fail();
+ }
+ catch (IllegalArgumentException e) { }
+ }
+
+ @Test
+ public void testInitiateHttpUploadTooLargeForUpload() throws
RepositoryException {
+ getConfigurableHttpDataRecordProvider()
+ .setDirectUploadURIExpirySeconds(REGULAR_WRITE_EXPIRY);
+ try {
+ uploadProvider.initiateBinaryUpload(1024L * 1024L * 1024L * 1024L
* 10L, -1);
+ fail();
+ }
+ catch (IllegalArgumentException e) { }
+ }
+
+ @Test
+ public void testInitiateHttpUploadTooLargeForRequestedNumURIs() throws
RepositoryException {
+ getConfigurableHttpDataRecordProvider()
+ .setDirectUploadURIExpirySeconds(REGULAR_WRITE_EXPIRY);
+ try {
+ uploadProvider.initiateBinaryUpload(1024L * 1024L * 1024L * 10L,
10);
+ fail();
+ }
+ catch (IllegalArgumentException e) { }
+ }
+
+ @Test
+ public void testCompleteHttpUploadWithInvalidTokenFails() throws
RepositoryException {
+ getConfigurableHttpDataRecordProvider()
+ .setDirectUploadURIExpirySeconds(REGULAR_WRITE_EXPIRY);
+
+ BinaryUpload upload = uploadProvider.initiateBinaryUpload(256, 10);
+ assertNotNull(upload);
+
+ try {
+ uploadProvider.completeBinaryUpload(upload.getUploadToken() + "X");
+ fail();
+ }
+ catch (IllegalArgumentException e) { }
+ }
+}
Propchange:
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/binary/BinaryAccessIT.java
------------------------------------------------------------------------------
svn:eol-style = native
Added:
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/binary/BinaryAccessTest.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/binary/BinaryAccessTest.java?rev=1838617&view=auto
==============================================================================
---
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/binary/BinaryAccessTest.java
(added)
+++
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/binary/BinaryAccessTest.java
Wed Aug 22 08:51:41 2018
@@ -0,0 +1,310 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.jackrabbit.oak.jcr.binary;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.concurrent.TimeUnit;
+
+import javax.jcr.Binary;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+import javax.jcr.ValueFactory;
+import javax.jcr.observation.Event;
+import javax.jcr.observation.ObservationManager;
+
+import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.jackrabbit.api.JackrabbitSession;
+import org.apache.jackrabbit.api.JackrabbitValueFactory;
+import org.apache.jackrabbit.api.binary.BinaryDownload;
+import org.apache.jackrabbit.api.binary.BinaryDownloadOptions;
+import org.apache.jackrabbit.api.binary.BinaryUpload;
+import org.apache.jackrabbit.api.security.user.Authorizable;
+import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.jackrabbit.oak.Oak;
+import org.apache.jackrabbit.oak.api.Blob;
+import org.apache.jackrabbit.oak.api.blob.BlobAccessProvider;
+import org.apache.jackrabbit.oak.api.blob.BlobDownloadOptions;
+import org.apache.jackrabbit.oak.api.blob.BlobUpload;
+import org.apache.jackrabbit.oak.fixture.NodeStoreFixture;
+import org.apache.jackrabbit.oak.jcr.AbstractRepositoryTest;
+import org.apache.jackrabbit.oak.jcr.Jcr;
+import
org.apache.jackrabbit.oak.jcr.binary.fixtures.datastore.FileDataStoreFixture;
+import
org.apache.jackrabbit.oak.jcr.binary.fixtures.nodestore.SegmentMemoryNodeStoreFixture;
+import org.apache.jackrabbit.oak.jcr.binary.util.BinaryAccessTestUtils;
+import org.apache.jackrabbit.oak.jcr.binary.util.Content;
+import org.apache.jackrabbit.oak.spi.state.NodeStore;
+import org.apache.jackrabbit.test.LogPrintWriter;
+import org.apache.jackrabbit.test.api.observation.EventResult;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This is a unit test for the direct binary access JCR API extension.
+ * It uses a mock of the underlying BlobAccessProvider.
+ *
+ * For a full integration test against real binary cloud storage,
+ * see BinaryAccessIt.
+ */
+@RunWith(Parameterized.class)
+public class BinaryAccessTest extends AbstractRepositoryTest {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(BinaryAccessTest.class);
+
+ @Parameterized.Parameters(name = "{0}")
+ public static Iterable<?> dataStoreFixtures() {
+ Collection<NodeStoreFixture> fixtures = new ArrayList<>();
+ fixtures.add(new SegmentMemoryNodeStoreFixture(new
FileDataStoreFixture()));
+ return fixtures;
+ }
+
+ public BinaryAccessTest(NodeStoreFixture fixture) {
+ super(fixture);
+ }
+
+ private static final String FILE_PATH = "/file";
+
+ private static final long SEGMENT_INLINE_SIZE = 16 * 1024;
+
+ private static final String DOWNLOAD_URL =
"http://expected.com/dummy/url/for/test/download";
+
+ private static URI expectedDownloadURI() {
+ return toURI(DOWNLOAD_URL);
+ }
+
+ private static final String UPLOAD_TOKEN = "super-safe-encrypted-token";
+
+ private static final String UPLOAD_URL =
"http://expected.com/dummy/url/for/test/upload";
+
+ private static URI expectedUploadURI() {
+ return toURI(UPLOAD_URL);
+ }
+
+ private static URI toURI(String url) {
+ try {
+ return new URI(url);
+ } catch (URISyntaxException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ protected Content blobContent;
+
+ /**
+ * Adjust JCR repository creation to register a mock BlobAccessProvider in
Whiteboard
+ * so it can be picked up by oak-jcr.
+ */
+ @Override
+ protected Repository createRepository(NodeStore nodeStore) {
+ Oak oak = new Oak(nodeStore);
+ oak.getWhiteboard().register(BlobAccessProvider.class, new
MockBlobAccessProvider(), Collections.emptyMap());
+ return initJcr(new Jcr(oak)).createRepository();
+ }
+
+ private class MockBlobAccessProvider implements BlobAccessProvider {
+
+ @Override
+ public @Nullable BlobUpload initiateBlobUpload(long maxSize, int
maxURIs) throws IllegalArgumentException {
+ return new BlobUpload() {
+ @Override
+ public @NotNull String getUploadToken() {
+ return UPLOAD_TOKEN;
+ }
+
+ @Override
+ public long getMinPartSize() {
+ return 0;
+ }
+
+ @Override
+ public long getMaxPartSize() {
+ return 10 * 1024 * 1024;
+ }
+
+ @Override
+ public @NotNull Collection<URI> getUploadURIs() {
+ Collection<URI> uris = new ArrayList<>();
+ uris.add(expectedUploadURI());
+ return uris;
+ }
+ };
+ }
+
+ @Override
+ public @Nullable Blob completeBlobUpload(@NotNull String uploadToken)
throws IllegalArgumentException {
+ if (!UPLOAD_TOKEN.equals(uploadToken)) {
+ return null;
+ }
+
+ // this returns the binary content set on the "blobContent" member
+ // as a simple way to mock some binary "storage"
+ return new Blob() {
+
+ @Override
+ public @NotNull InputStream getNewStream() {
+ return blobContent.getStream();
+ }
+
+ @Override
+ public long length() {
+ return blobContent.size();
+ }
+
+ @Override
+ public String getReference() {
+ return "super-secure-key#" + getContentIdentity();
+ }
+
+ @Override
+ public String getContentIdentity() {
+ return DigestUtils.md5Hex(blobContent.toString());
+ }
+ };
+ }
+
+ @Override
+ public @Nullable URI getDownloadURI(@NotNull Blob blob,
+ @NotNull BlobDownloadOptions
blobDownloadOptions) {
+ return expectedDownloadURI();
+ }
+ }
+
+ @Test
+ public void testBinaryDownload() throws RepositoryException {
+ Content content = Content.createRandom(SEGMENT_INLINE_SIZE * 2);
+ Binary binary =
BinaryAccessTestUtils.storeBinaryAndRetrieve(getAdminSession(), FILE_PATH,
content);
+
+ assertTrue(binary instanceof BinaryDownload);
+
+ BinaryDownload binaryDownload = (BinaryDownload) binary;
+ URI uri = binaryDownload.getURI(BinaryDownloadOptions.DEFAULT);
+
+ // we only need test that the we get a URI back (from our mock) to
validate oak-jcr's inner workings
+ assertNotNull(uri);
+ assertEquals(expectedDownloadURI(), uri);
+ }
+
+ @Test
+ public void testBinaryUpload() throws RepositoryException, IOException {
+ Content content = Content.createRandom(SEGMENT_INLINE_SIZE * 2);
+
+ ValueFactory vf = getAdminSession().getValueFactory();
+ assertTrue(vf instanceof JackrabbitValueFactory);
+
+ JackrabbitValueFactory valueFactory = (JackrabbitValueFactory) vf;
+
+ // 1. test initiate
+ BinaryUpload binaryUpload =
valueFactory.initiateBinaryUpload(content.size(), 1);
+
+ assertNotNull(binaryUpload);
+ assertEquals(UPLOAD_TOKEN, binaryUpload.getUploadToken());
+
+ // 2. simulate an "upload"
+ blobContent = content;
+
+ // 3. test complete
+ Binary binary =
valueFactory.completeBinaryUpload(binaryUpload.getUploadToken());
+
+ assertNotNull(binary);
+ assertEquals(content.size(), binary.getSize());
+
+ // 4. test that we can use this binary in JCR
+ BinaryAccessTestUtils.storeBinary(getAdminSession(), FILE_PATH,
binary);
+
+ binary = BinaryAccessTestUtils.getBinary(getAdminSession(), FILE_PATH);
+ content.assertEqualsWith(binary.getStream());
+ }
+
+ @Test
+ public void testEvent() throws Exception {
+ BinaryAccessTestUtils.storeBinaryAndRetrieve(getAdminSession(),
FILE_PATH, Content.createRandom(0));
+
+ ObservationManager obsMgr =
getAdminSession().getWorkspace().getObservationManager();
+ EventResult result = new EventResult(new LogPrintWriter(LOG));
+ obsMgr.addEventListener(result, Event.PROPERTY_CHANGED, FILE_PATH,
true, null, null, false);
+
+ Content content = Content.createRandom(SEGMENT_INLINE_SIZE * 2);
+ BinaryAccessTestUtils.storeBinaryAndRetrieve(getAdminSession(),
FILE_PATH, content);
+
+ Event[] events = result.getEvents(TimeUnit.SECONDS.toMillis(5));
+ assertEquals(1, events.length);
+
+ assertEquals(Event.PROPERTY_CHANGED, events[0].getType());
+ Value afterValue = (Value) events[0].getInfo().get("afterValue");
+ assertNotNull(afterValue);
+ Binary binary = afterValue.getBinary();
+ content.assertEqualsWith(binary.getStream());
+
+ assertTrue(binary instanceof BinaryDownload);
+
+ BinaryDownload binaryDownload = (BinaryDownload) binary;
+ URI uri = binaryDownload.getURI(BinaryDownloadOptions.DEFAULT);
+
+ assertNotNull(uri);
+ assertEquals(expectedDownloadURI(), uri);
+ }
+
+ @Test
+ public void testAuthorizableProperty() throws Exception {
+ assertTrue(getAdminSession() instanceof JackrabbitSession);
+ JackrabbitSession session = (JackrabbitSession) getAdminSession();
+ UserManager userMgr = session.getUserManager();
+ ValueFactory vf = session.getValueFactory();
+
+ Content content = Content.createRandom(SEGMENT_INLINE_SIZE * 2);
+ Binary binary =
BinaryAccessTestUtils.storeBinaryAndRetrieve(getAdminSession(), FILE_PATH,
content);
+
+ Authorizable auth = userMgr.getAuthorizable(session.getUserID());
+ assertNotNull(auth);
+
+ auth.setProperty("avatar", vf.createValue(binary));
+ if (!userMgr.isAutoSave()) {
+ session.save();
+ }
+
+ Value[] values = auth.getProperty("avatar");
+ assertNotNull(values);
+ assertEquals(1, values.length);
+ binary = values[0].getBinary();
+
+ content.assertEqualsWith(binary.getStream());
+
+ assertTrue(binary instanceof BinaryDownload);
+
+ BinaryDownload binaryDownload = (BinaryDownload) binary;
+ URI uri = binaryDownload.getURI(BinaryDownloadOptions.DEFAULT);
+
+ assertNotNull(uri);
+ assertEquals(expectedDownloadURI(), uri);
+ }
+}
Propchange:
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/binary/BinaryAccessTest.java
------------------------------------------------------------------------------
svn:eol-style = native
Added:
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/binary/BinaryAccessUnsupportedIT.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/binary/BinaryAccessUnsupportedIT.java?rev=1838617&view=auto
==============================================================================
---
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/binary/BinaryAccessUnsupportedIT.java
(added)
+++
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/binary/BinaryAccessUnsupportedIT.java
Wed Aug 22 08:51:41 2018
@@ -0,0 +1,123 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.jackrabbit.oak.jcr.binary;
+
+import static
org.apache.jackrabbit.oak.jcr.binary.util.BinaryAccessTestUtils.storeBinaryAndRetrieve;
+import static org.junit.Assert.assertNull;
+
+import java.net.URI;
+import java.util.Collection;
+import java.util.Collections;
+
+import javax.jcr.Binary;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+
+import com.google.common.collect.Lists;
+import org.apache.jackrabbit.api.JackrabbitValueFactory;
+import org.apache.jackrabbit.api.binary.BinaryDownload;
+import org.apache.jackrabbit.api.binary.BinaryDownloadOptions;
+import org.apache.jackrabbit.api.binary.BinaryUpload;
+import org.apache.jackrabbit.oak.api.blob.BlobAccessProvider;
+import org.apache.jackrabbit.oak.fixture.NodeStoreFixture;
+import org.apache.jackrabbit.oak.jcr.AbstractRepositoryTest;
+import org.apache.jackrabbit.oak.jcr.Jcr;
+import
org.apache.jackrabbit.oak.jcr.binary.fixtures.datastore.FileDataStoreFixture;
+import
org.apache.jackrabbit.oak.jcr.binary.fixtures.nodestore.DocumentMemoryNodeStoreFixture;
+import
org.apache.jackrabbit.oak.jcr.binary.fixtures.nodestore.SegmentMemoryNodeStoreFixture;
+import org.apache.jackrabbit.oak.jcr.binary.util.Content;
+import org.apache.jackrabbit.oak.spi.blob.BlobStore;
+import org.apache.jackrabbit.oak.spi.state.NodeStore;
+import org.apache.jackrabbit.oak.spi.whiteboard.DefaultWhiteboard;
+import org.apache.jackrabbit.oak.spi.whiteboard.Whiteboard;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runners.Parameterized;
+
+/**
+ * Test binary upload / download capabilities of the {@link
JackrabbitValueFactory} interface when the underlying
+ * implementation does not support these features. If the underlying doesn't
support these features the implementation
+ * will return null when the methods are called and clients are expected to
check for null to determine if the
+ * features are supported.
+ */
+public class BinaryAccessUnsupportedIT extends AbstractRepositoryTest {
+ private JackrabbitValueFactory uploadProvider;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static Iterable<?> dataStoreFixtures() {
+ Collection<NodeStoreFixture> fixtures = Lists.newArrayList();
+
+ // Create a fixture using FileDataStore. FileDataStore doesn't
support the direct access features so
+ // it should be a valid real-world example of how the API should
behave when the implementation doesn't
+ // have the feature support.
+ FileDataStoreFixture fds = new FileDataStoreFixture();
+ fixtures.add(new SegmentMemoryNodeStoreFixture(fds));
+ fixtures.add(new DocumentMemoryNodeStoreFixture(fds));
+
+ return fixtures;
+ }
+
+ public BinaryAccessUnsupportedIT(NodeStoreFixture fixture) {
+ super(fixture);
+ }
+
+ @Before
+ public void setup() throws RepositoryException {
+ uploadProvider = (JackrabbitValueFactory)
getAdminSession().getValueFactory();
+ }
+
+ @Override
+ protected Repository createRepository(NodeStore nodeStore) {
+ Whiteboard wb = new DefaultWhiteboard();
+
+ BlobStore blobStore = getNodeStoreComponent(BlobStore.class);
+ if (blobStore != null && blobStore instanceof BlobAccessProvider) {
+ wb.register(BlobAccessProvider.class, (BlobAccessProvider)
blobStore,
+ Collections.emptyMap());
+
+ }
+
+ return initJcr(new Jcr(nodeStore).with(wb)).createRepository();
+ }
+
+ @Test
+ public void testInitiateUploadUnsupportedReturnsNull() throws Exception {
+ BinaryUpload upload = uploadProvider.initiateBinaryUpload(1024*20, 10);
+ assertNull(upload);
+ }
+
+ @Test
+ public void testCompleteUploadUnsupportedReturnsNull() throws Exception {
+ Binary binary = uploadProvider.completeBinaryUpload("fake_token");
+ assertNull(binary);
+ }
+
+ @Test
+ public void testGetDownloadURIUnsupportedReturnsNull() throws Exception {
+ Content content = Content.createRandom(1024*20);
+ Binary binary = storeBinaryAndRetrieve(getAdminSession(), "/my_path",
content);
+
+ // the returned binary could not be implementing BinaryDownload...
+ if (binary instanceof BinaryDownload) {
+ // ...or implement it but return null on getURI()
+ URI downloadURI = ((BinaryDownload)
binary).getURI(BinaryDownloadOptions.DEFAULT);
+ assertNull(downloadURI);
+ }
+ }
+}
Propchange:
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/binary/BinaryAccessUnsupportedIT.java
------------------------------------------------------------------------------
svn:eol-style = native
Added:
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/binary/fixtures/datastore/AzureDataStoreFixture.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/binary/fixtures/datastore/AzureDataStoreFixture.java?rev=1838617&view=auto
==============================================================================
---
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/binary/fixtures/datastore/AzureDataStoreFixture.java
(added)
+++
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/binary/fixtures/datastore/AzureDataStoreFixture.java
Wed Aug 22 08:51:41 2018
@@ -0,0 +1,143 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.jackrabbit.oak.jcr.binary.fixtures.datastore;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+import java.util.UUID;
+
+import org.apache.jackrabbit.core.data.DataStore;
+import org.apache.jackrabbit.core.data.DataStoreException;
+import org.apache.jackrabbit.oak.blob.cloud.azure.blobstorage.AzureConstants;
+import org.apache.jackrabbit.oak.blob.cloud.azure.blobstorage.AzureDataStore;
+import org.apache.jackrabbit.oak.blob.cloud.azure.blobstorage.Utils;
+import org.apache.jackrabbit.oak.fixture.NodeStoreFixture;
+import org.apache.jackrabbit.oak.jcr.binary.fixtures.nodestore.FixtureUtils;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.microsoft.azure.storage.StorageException;
+import com.microsoft.azure.storage.blob.CloudBlobContainer;
+
+/**
+ * Fixture for AzureDataStore based on an azure.properties config file. It
creates
+ * a new temporary Azure Blob Container for each DataStore created.
+ *
+ * Note: when using this, it's highly recommended to reuse the NodeStores
across multiple tests (using
+ * {@link
org.apache.jackrabbit.oak.jcr.AbstractRepositoryTest#AbstractRepositoryTest(NodeStoreFixture,
boolean) AbstractRepositoryTest(fixture, true)})
+ * otherwise it will be slower and can lead to out of memory issues if there
are many tests.
+ *
+ * <p>
+ * Test buckets are named "direct-binary-test-...". If some did not get
cleaned up, you can
+ * list them using the aws cli with this command:
+ * <pre>
+ * az storage container list --output table | grep direct-binary-test-
+ * </pre>
+ *
+ * And after checking, delete them all in one go with this command:
+ * <pre>
+ * az storage container list --output table | grep direct-binary-test- |
cut -d " " -f 1 | xargs -n 1 -I {} sh -c 'az storage container delete -n {}'
+ * </pre>
+ */
+public class AzureDataStoreFixture implements DataStoreFixture {
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ @Nullable
+ private final Properties azProps;
+ private Map<DataStore, CloudBlobContainer> containers = new HashMap<>();
+
+ public AzureDataStoreFixture() {
+ azProps = FixtureUtils.loadDataStoreProperties("azure.config",
"azure.properties", ".azure");
+ }
+
+ @Override
+ public boolean isAvailable() {
+ if (azProps == null) {
+ log.warn("Skipping Azure DataStore fixture because no AZ
properties file was found given by " +
+ "'azure.config' system property or named 'azure.properties' or
'~/.azure/azure.properties'.");
+ return false;
+ }
+ return true;
+ }
+
+ @NotNull
+ @Override
+ public DataStore createDataStore() {
+ if (!isAvailable() || azProps == null) {
+ throw new AssertionError("createDataStore() called but this
fixture is not available");
+ }
+
+ // Create a temporary container that will be removed at test completion
+ String containerName = "direct-binary-test-" +
UUID.randomUUID().toString();
+
+ log.info("Creating Azure test blob container {}", containerName);
+
+ String connectionString =
Utils.getConnectionStringFromProperties(azProps);
+ try {
+ CloudBlobContainer container =
Utils.getBlobContainer(connectionString, containerName);
+ container.createIfNotExists();
+
+ // create new properties since azProps is shared for all created
DataStores
+ Properties clonedAzProps = new Properties(azProps);
+
clonedAzProps.setProperty(AzureConstants.AZURE_BLOB_CONTAINER_NAME,
container.getName());
+
+ // setup Oak DS
+ AzureDataStore dataStore = new AzureDataStore();
+ dataStore.setProperties(clonedAzProps);
+ dataStore.setStagingSplitPercentage(0);
+
+ containers.put(dataStore, container);
+ return dataStore;
+
+ } catch (DataStoreException | StorageException e) {
+ throw new AssertionError("Azure DataStore fixture fails because of
issue with Azure config or connection", e);
+ }
+ }
+
+ @Override
+ public void dispose(DataStore dataStore) {
+ if (dataStore == null) {
+ return;
+ }
+
+ try {
+ dataStore.close();
+ } catch (DataStoreException e) {
+ log.warn("Issue while disposing DataStore", e);
+ }
+
+ CloudBlobContainer container = containers.get(dataStore);
+ if (container != null) {
+ log.info("Removing Azure test blob container {}",
container.getName());
+ try {
+ // For Azure, you can just delete the container and all
+ // blobs it in will also be deleted
+ container.delete();
+ } catch (StorageException e) {
+ log.warn("Unable to delete Azure Blob container {}",
container.getName());
+ }
+
+ containers.remove(dataStore);
+ }
+ }
+}
Propchange:
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/binary/fixtures/datastore/AzureDataStoreFixture.java
------------------------------------------------------------------------------
svn:eol-style = native
Added:
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/binary/fixtures/datastore/DataStoreFixture.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/binary/fixtures/datastore/DataStoreFixture.java?rev=1838617&view=auto
==============================================================================
---
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/binary/fixtures/datastore/DataStoreFixture.java
(added)
+++
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/binary/fixtures/datastore/DataStoreFixture.java
Wed Aug 22 08:51:41 2018
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.jackrabbit.oak.jcr.binary.fixtures.datastore;
+
+import org.apache.jackrabbit.core.data.DataStore;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * DataStore fixture for parametrized tests. To be used inside
NodeStoreFixture implementations.
+ */
+public interface DataStoreFixture {
+
+ /**
+ * Create a new DataStore instance. This might include setting up a
temporary folder,
+ * bucket, container... of the underlying storage solution. Return null if
this
+ * DataStore is not available because of missing configuration or setup
issues
+ * (implementation shall log such warnings/errors).
+ *
+ * Calling DataStore.init() is left to the client.
+ */
+ @NotNull
+ DataStore createDataStore();
+
+ /**
+ * Dispose a DataStore. This can include removing the temporary test
folder, bucket etc.
+ */
+ void dispose(DataStore dataStore);
+
+ /**
+ * Return whether this fixture is available, for example if the necessary
configuration is present.
+ */
+ boolean isAvailable();
+}
Propchange:
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/binary/fixtures/datastore/DataStoreFixture.java
------------------------------------------------------------------------------
svn:eol-style = native
Added:
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/binary/fixtures/datastore/FileDataStoreFixture.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/binary/fixtures/datastore/FileDataStoreFixture.java?rev=1838617&view=auto
==============================================================================
---
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/binary/fixtures/datastore/FileDataStoreFixture.java
(added)
+++
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/binary/fixtures/datastore/FileDataStoreFixture.java
Wed Aug 22 08:51:41 2018
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.jackrabbit.oak.jcr.binary.fixtures.datastore;
+
+import org.apache.jackrabbit.core.data.DataStore;
+import org.apache.jackrabbit.core.data.FileDataStore;
+import org.jetbrains.annotations.NotNull;
+
+public class FileDataStoreFixture implements DataStoreFixture {
+
+ @NotNull
+ @Override
+ public DataStore createDataStore() {
+ return new FileDataStore();
+ }
+
+ @Override
+ public void dispose(DataStore dataStore) {
+ // nothing to do, using directory inside repository that gets cleaned
up by NodeStoreFixture dispose
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return true;
+ }
+}
Propchange:
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/binary/fixtures/datastore/FileDataStoreFixture.java
------------------------------------------------------------------------------
svn:eol-style = native
Added:
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/binary/fixtures/datastore/S3DataStoreFixture.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/binary/fixtures/datastore/S3DataStoreFixture.java?rev=1838617&view=auto
==============================================================================
---
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/binary/fixtures/datastore/S3DataStoreFixture.java
(added)
+++
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/binary/fixtures/datastore/S3DataStoreFixture.java
Wed Aug 22 08:51:41 2018
@@ -0,0 +1,169 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.jackrabbit.oak.jcr.binary.fixtures.datastore;
+
+import java.util.Properties;
+import java.util.UUID;
+
+import org.apache.jackrabbit.core.data.DataStore;
+import org.apache.jackrabbit.core.data.DataStoreException;
+import org.apache.jackrabbit.oak.blob.cloud.s3.S3Backend;
+import org.apache.jackrabbit.oak.blob.cloud.s3.S3Constants;
+import org.apache.jackrabbit.oak.blob.cloud.s3.S3DataStore;
+import org.apache.jackrabbit.oak.blob.cloud.s3.Utils;
+import org.apache.jackrabbit.oak.fixture.NodeStoreFixture;
+import org.apache.jackrabbit.oak.jcr.binary.fixtures.nodestore.FixtureUtils;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.amazonaws.services.s3.AmazonS3;
+import com.amazonaws.services.s3.model.BucketAccelerateConfiguration;
+import com.amazonaws.services.s3.model.BucketAccelerateStatus;
+import com.amazonaws.services.s3.model.CreateBucketRequest;
+import com.amazonaws.services.s3.model.ObjectListing;
+import com.amazonaws.services.s3.model.S3ObjectSummary;
+import com.amazonaws.services.s3.model.SetBucketAccelerateConfigurationRequest;
+
+/**
+ * Fixture for S3DataStore based on an aws.properties config file. It creates
+ * a new temporary Azure Blob Container for each DataStore created.
+ *
+ * <p>
+ * Note: when using this, it's highly recommended to reuse the NodeStores
across multiple tests (using
+ * {@link
org.apache.jackrabbit.oak.jcr.AbstractRepositoryTest#AbstractRepositoryTest(NodeStoreFixture,
boolean) AbstractRepositoryTest(fixture, true)})
+ * otherwise it will be slower and can lead to out of memory issues if there
are many tests.
+ *
+ * <p>
+ * Test buckets are named "direct-binary-test-...". If some did not get
cleaned up, you can
+ * list them using the aws cli with this command:
+ * <pre>
+ * aws s3 ls | grep direct-binary-test-
+ * </pre>
+ *
+ * And after checking, delete them all in one go with this command:
+ * <pre>
+ * aws s3 ls | grep direct-binary-test- | cut -f 3 -d " " | xargs -n 1 -I
{} sh -c 'aws s3 rb s3://{} || exit 1'
+ * </pre>
+ */
+public class S3DataStoreFixture implements DataStoreFixture {
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ @Nullable
+ private final Properties s3Props;
+
+ public S3DataStoreFixture() {
+ s3Props = FixtureUtils.loadDataStoreProperties("s3.config",
"aws.properties", ".aws");
+ }
+
+ @Override
+ public boolean isAvailable() {
+ if (s3Props == null) {
+ log.warn("Skipping S3 DataStore fixture because no S3 properties
file was found given by " +
+ "'s3.config' system property or named 'aws.properties' or
'~/.aws/aws.properties'.");
+ return false;
+ }
+ return true;
+ }
+
+ @NotNull
+ @Override
+ public DataStore createDataStore() {
+ if (s3Props == null) {
+ throw new AssertionError("createDataStore() called but this
fixture is not available");
+ }
+
+ AmazonS3 s3Client = Utils.openService(s3Props);
+
+ // Create a temporary bucket that will be removed at test completion
+ String bucketName = "direct-binary-test-" +
UUID.randomUUID().toString();
+
+ log.info("Creating S3 test bucket {}", bucketName);
+ CreateBucketRequest createBucket = new CreateBucketRequest(bucketName);
+ s3Client.createBucket(createBucket);
+
+ log.info("Enabling S3 acceleration for bucket {}", bucketName);
+ s3Client.setBucketAccelerateConfiguration(
+ new SetBucketAccelerateConfigurationRequest(bucketName,
+ new BucketAccelerateConfiguration(
+ BucketAccelerateStatus.Enabled
+ )
+ )
+ );
+
+ s3Client.shutdown();
+
+ // create new properties since azProps is shared for all created
DataStores
+ Properties clonedS3Props = new Properties(s3Props);
+ clonedS3Props.setProperty(S3Constants.S3_BUCKET,
createBucket.getBucketName());
+
+ // setup Oak DS
+ S3DataStore dataStore = new S3DataStore();
+ dataStore.setProperties(clonedS3Props);
+ dataStore.setStagingSplitPercentage(0);
+
+ log.info("s3props: " + s3Props.toString());
+
+ return dataStore;
+ }
+
+ @Override
+ public void dispose(DataStore dataStore) {
+ if (dataStore != null && dataStore instanceof S3DataStore) {
+ try {
+ dataStore.close();
+ } catch (DataStoreException e) {
+ log.warn("Issue while disposing DataStore", e);
+ }
+
+ S3DataStore s3DataStore = (S3DataStore) dataStore;
+ String bucketName = ((S3Backend)
s3DataStore.getBackend()).getBucket();
+
+ if (s3Props == null) {
+ // should be impossible if we created the client successfully
in createDataStore()
+ log.warn("Could not cleanup and remove S3 bucket {}",
bucketName);
+ return;
+ }
+
+ AmazonS3 s3Client = Utils.openService(s3Props);
+
+ // For S3, you have to empty the bucket before removing the bucket
itself
+ log.info("Emptying S3 test bucket {}", bucketName);
+
+ ObjectListing listing = s3Client.listObjects(bucketName);
+ while (true) {
+ for (S3ObjectSummary summary : listing.getObjectSummaries()) {
+ s3Client.deleteObject(bucketName, summary.getKey());
+ }
+ if (! listing.isTruncated()) {
+ break;
+ }
+ listing = s3Client.listNextBatchOfObjects(listing);
+ }
+
+ log.info("Removing S3 test bucket {}", bucketName);
+ s3Client.deleteBucket(bucketName);
+
+ s3Client.shutdown();
+ }
+ }
+}
Propchange:
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/binary/fixtures/datastore/S3DataStoreFixture.java
------------------------------------------------------------------------------
svn:eol-style = native
Added:
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/binary/fixtures/nodestore/DocumentMemoryNodeStoreFixture.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/binary/fixtures/nodestore/DocumentMemoryNodeStoreFixture.java?rev=1838617&view=auto
==============================================================================
---
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/binary/fixtures/nodestore/DocumentMemoryNodeStoreFixture.java
(added)
+++
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/binary/fixtures/nodestore/DocumentMemoryNodeStoreFixture.java
Wed Aug 22 08:51:41 2018
@@ -0,0 +1,132 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.jackrabbit.oak.jcr.binary.fixtures.nodestore;
+
+import java.io.File;
+import java.io.IOException;
+import javax.jcr.RepositoryException;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.jackrabbit.core.data.DataStore;
+import org.apache.jackrabbit.oak.fixture.NodeStoreFixture;
+import
org.apache.jackrabbit.oak.jcr.binary.fixtures.datastore.DataStoreFixture;
+import org.apache.jackrabbit.oak.jcr.util.ComponentHolder;
+import org.apache.jackrabbit.oak.plugins.blob.datastore.DataStoreBlobStore;
+import org.apache.jackrabbit.oak.plugins.document.DocumentNodeStore;
+import org.apache.jackrabbit.oak.plugins.document.DocumentNodeStoreBuilder;
+import org.apache.jackrabbit.oak.spi.blob.BlobStore;
+import org.apache.jackrabbit.oak.spi.state.NodeStore;
+import org.jetbrains.annotations.Nullable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.HashBasedTable;
+import com.google.common.collect.Table;
+
+/**
+ * Creates a repository with
+ * - DocumentNodeStore, storing data in-memory
+ * - an optional DataStore provided by DataStoreFixture
+ */
+public class DocumentMemoryNodeStoreFixture extends NodeStoreFixture
implements ComponentHolder {
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ private final DataStoreFixture dataStoreFixture;
+
+ private final Table<NodeStore, String, Object> components =
HashBasedTable.create();
+
+ public DocumentMemoryNodeStoreFixture(@Nullable DataStoreFixture
dataStoreFixture) {
+ this.dataStoreFixture = dataStoreFixture;
+ }
+
+ @Override
+ public boolean isAvailable() {
+ // if a DataStore is configured, it must be available for our
NodeStore to be available
+ return dataStoreFixture == null || dataStoreFixture.isAvailable();
+ }
+
+ @Override
+ public NodeStore createNodeStore() {
+ try {
+ log.info("Creating NodeStore using " + toString());
+
+ DocumentNodeStoreBuilder<?> documentNodeStoreBuilder =
DocumentNodeStoreBuilder.newDocumentNodeStoreBuilder();
+
+ File dataStoreFolder = null;
+ BlobStore blobStore = null;
+ DataStore dataStore = null;
+ if (dataStoreFixture != null) {
+ dataStore = dataStoreFixture.createDataStore();
+
+ // init with a new folder inside a temporary one
+ dataStoreFolder = FixtureUtils.createTempFolder();
+ dataStore.init(dataStoreFolder.getAbsolutePath());
+
+ blobStore = new DataStoreBlobStore(dataStore);
+ documentNodeStoreBuilder.setBlobStore(blobStore);
+ }
+
+ NodeStore nodeStore = documentNodeStoreBuilder.build();
+
+ // track all main components
+ if (dataStore != null) {
+ components.put(nodeStore, DataStore.class.getName(),
dataStore);
+ components.put(nodeStore, DataStore.class.getName() +
":folder", dataStoreFolder);
+ }
+ if (blobStore != null) {
+ components.put(nodeStore, BlobStore.class.getName(),
blobStore);
+ }
+
+ return nodeStore;
+ } catch (IOException | RepositoryException e) {
+ throw new AssertionError("Cannot create test repo fixture " +
toString(), e);
+ }
+ }
+
+ @Override
+ public void dispose(NodeStore nodeStore) {
+ try {
+ if (nodeStore instanceof DocumentNodeStore) {
+ ((DocumentNodeStore)nodeStore).dispose();
+ }
+
+ DataStore dataStore = (DataStore) components.get(nodeStore,
DataStore.class.getName());
+ if (dataStore != null && dataStoreFixture != null) {
+ dataStoreFixture.dispose(dataStore);
+
+ File dataStoreFolder = (File) components.get(nodeStore,
DataStore.class.getName() + ":folder");
+ FileUtils.deleteQuietly(dataStoreFolder);
+ }
+ } finally {
+ components.row(nodeStore).clear();
+ }
+ }
+
+ @Override
+ public String toString() {
+ // for nice Junit parameterized test labels
+ return FixtureUtils.getFixtureLabel(this, dataStoreFixture);
+ }
+
+ @Override
+ public <T> T get(NodeStore nodeStore, String componentName) {
+ return (T) components.get(nodeStore, componentName);
+ }
+}
Propchange:
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/binary/fixtures/nodestore/DocumentMemoryNodeStoreFixture.java
------------------------------------------------------------------------------
svn:eol-style = native
Added:
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/binary/fixtures/nodestore/FixtureUtils.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/binary/fixtures/nodestore/FixtureUtils.java?rev=1838617&view=auto
==============================================================================
---
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/binary/fixtures/nodestore/FixtureUtils.java
(added)
+++
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/binary/fixtures/nodestore/FixtureUtils.java
Wed Aug 22 08:51:41 2018
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.jackrabbit.oak.jcr.binary.fixtures.nodestore;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.util.Properties;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.jackrabbit.oak.fixture.NodeStoreFixture;
+import
org.apache.jackrabbit.oak.jcr.binary.fixtures.datastore.DataStoreFixture;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Utility methods for NodeStoreFixtures and DataStoreFixtures.
+ */
+public abstract class FixtureUtils {
+
+ /** Return a nice label for jUnit Parameterized tests for fixtures */
+ public static String getFixtureLabel(NodeStoreFixture fixture,
+ DataStoreFixture dataStoreFixture) {
+
+ String nodeStoreName = fixture.getClass().getSimpleName();
+ String name = StringUtils.removeEnd(nodeStoreName, "Fixture");
+ if (dataStoreFixture != null) {
+ String dataStoreName = dataStoreFixture.getClass().getSimpleName();
+ return name + "_" + StringUtils.removeEnd(dataStoreName,
"Fixture");
+ }
+ return name;
+
+ }
+
+ /** Create a temporary folder inside the maven build folder "target". */
+ public static File createTempFolder() throws IOException {
+ // create temp folder inside maven's "target" folder
+ File parent = new File("target");
+ File tempFolder = File.createTempFile("junit", "", parent);
+ tempFolder.delete();
+ tempFolder.mkdir();
+ return tempFolder;
+ }
+
+ /**
+ * Load data store *.properties from path in system property, local file
or file inside user home directory.
+ * Returns null if no file was found.
+ */
+ @Nullable
+ public static Properties loadDataStoreProperties(String systemProperty,
+ String defaultFileName,
+ String homeFolderName) {
+ Properties props = new Properties();
+ try {
+ File file = new File(System.getProperty(systemProperty,
defaultFileName));
+ if (!file.exists()) {
+ file = Paths.get(System.getProperty("user.home"),
homeFolderName, defaultFileName).toFile();
+ }
+ props.load(new FileReader(file));
+ } catch (IOException e) {
+ return null;
+ }
+ return props;
+ }
+}
Propchange:
jackrabbit/oak/trunk/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/binary/fixtures/nodestore/FixtureUtils.java
------------------------------------------------------------------------------
svn:eol-style = native