You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by am...@apache.org on 2017/03/24 09:31:18 UTC
svn commit: r1788387 [1/2] - in /jackrabbit/oak/trunk: ./
oak-blob-cloud-azure/ oak-blob-cloud-azure/src/
oak-blob-cloud-azure/src/main/ oak-blob-cloud-azure/src/main/java/
oak-blob-cloud-azure/src/main/java/org/
oak-blob-cloud-azure/src/main/java/org/...
Author: amitj
Date: Fri Mar 24 09:31:18 2017
New Revision: 1788387
URL: http://svn.apache.org/viewvc?rev=1788387&view=rev
Log:
OAK-4933: Create a data store implementation that integrates with Microsoft Azure Blob Storage
- New module oak-blob-cloud-azure with DataStore implementation for Azure BlobStorage
- Integration tests
Added:
jackrabbit/oak/trunk/oak-blob-cloud-azure/
jackrabbit/oak/trunk/oak-blob-cloud-azure/pom.xml (with props)
jackrabbit/oak/trunk/oak-blob-cloud-azure/src/
jackrabbit/oak/trunk/oak-blob-cloud-azure/src/main/
jackrabbit/oak/trunk/oak-blob-cloud-azure/src/main/java/
jackrabbit/oak/trunk/oak-blob-cloud-azure/src/main/java/org/
jackrabbit/oak/trunk/oak-blob-cloud-azure/src/main/java/org/apache/
jackrabbit/oak/trunk/oak-blob-cloud-azure/src/main/java/org/apache/jackrabbit/
jackrabbit/oak/trunk/oak-blob-cloud-azure/src/main/java/org/apache/jackrabbit/oak/
jackrabbit/oak/trunk/oak-blob-cloud-azure/src/main/java/org/apache/jackrabbit/oak/blob/
jackrabbit/oak/trunk/oak-blob-cloud-azure/src/main/java/org/apache/jackrabbit/oak/blob/cloud/
jackrabbit/oak/trunk/oak-blob-cloud-azure/src/main/java/org/apache/jackrabbit/oak/blob/cloud/azure/
jackrabbit/oak/trunk/oak-blob-cloud-azure/src/main/java/org/apache/jackrabbit/oak/blob/cloud/azure/blobstorage/
jackrabbit/oak/trunk/oak-blob-cloud-azure/src/main/java/org/apache/jackrabbit/oak/blob/cloud/azure/blobstorage/AbstractAzureDataStoreService.java (with props)
jackrabbit/oak/trunk/oak-blob-cloud-azure/src/main/java/org/apache/jackrabbit/oak/blob/cloud/azure/blobstorage/AzureBlobStoreBackend.java (with props)
jackrabbit/oak/trunk/oak-blob-cloud-azure/src/main/java/org/apache/jackrabbit/oak/blob/cloud/azure/blobstorage/AzureConstants.java (with props)
jackrabbit/oak/trunk/oak-blob-cloud-azure/src/main/java/org/apache/jackrabbit/oak/blob/cloud/azure/blobstorage/AzureDataStore.java (with props)
jackrabbit/oak/trunk/oak-blob-cloud-azure/src/main/java/org/apache/jackrabbit/oak/blob/cloud/azure/blobstorage/AzureDataStoreService.java (with props)
jackrabbit/oak/trunk/oak-blob-cloud-azure/src/main/java/org/apache/jackrabbit/oak/blob/cloud/azure/blobstorage/Utils.java (with props)
jackrabbit/oak/trunk/oak-blob-cloud-azure/src/test/
jackrabbit/oak/trunk/oak-blob-cloud-azure/src/test/java/
jackrabbit/oak/trunk/oak-blob-cloud-azure/src/test/java/org/
jackrabbit/oak/trunk/oak-blob-cloud-azure/src/test/java/org/apache/
jackrabbit/oak/trunk/oak-blob-cloud-azure/src/test/java/org/apache/jackrabbit/
jackrabbit/oak/trunk/oak-blob-cloud-azure/src/test/java/org/apache/jackrabbit/oak/
jackrabbit/oak/trunk/oak-blob-cloud-azure/src/test/java/org/apache/jackrabbit/oak/blob/
jackrabbit/oak/trunk/oak-blob-cloud-azure/src/test/java/org/apache/jackrabbit/oak/blob/cloud/
jackrabbit/oak/trunk/oak-blob-cloud-azure/src/test/java/org/apache/jackrabbit/oak/blob/cloud/azure/
jackrabbit/oak/trunk/oak-blob-cloud-azure/src/test/java/org/apache/jackrabbit/oak/blob/cloud/azure/blobstorage/
jackrabbit/oak/trunk/oak-blob-cloud-azure/src/test/java/org/apache/jackrabbit/oak/blob/cloud/azure/blobstorage/AzureDataStoreTest.java (with props)
jackrabbit/oak/trunk/oak-blob-cloud-azure/src/test/java/org/apache/jackrabbit/oak/blob/cloud/azure/blobstorage/AzureDataStoreUtils.java (with props)
jackrabbit/oak/trunk/oak-blob-cloud-azure/src/test/java/org/apache/jackrabbit/oak/blob/cloud/azure/blobstorage/TestAzureDS.java (with props)
jackrabbit/oak/trunk/oak-blob-cloud-azure/src/test/java/org/apache/jackrabbit/oak/blob/cloud/azure/blobstorage/TestAzureDSWithSmallCache.java (with props)
jackrabbit/oak/trunk/oak-blob-cloud-azure/src/test/java/org/apache/jackrabbit/oak/blob/cloud/azure/blobstorage/TestAzureDsCacheOff.java (with props)
jackrabbit/oak/trunk/oak-blob-cloud-azure/src/test/resources/
jackrabbit/oak/trunk/oak-blob-cloud-azure/src/test/resources/azure.properties (with props)
jackrabbit/oak/trunk/oak-blob-cloud-azure/src/test/resources/logback-test.xml (with props)
Modified:
jackrabbit/oak/trunk/oak-doc/src/site/markdown/osgi_config.md
jackrabbit/oak/trunk/oak-doc/src/site/markdown/plugins/blobstore.md
jackrabbit/oak/trunk/oak-parent/pom.xml
jackrabbit/oak/trunk/pom.xml
Added: jackrabbit/oak/trunk/oak-blob-cloud-azure/pom.xml
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-blob-cloud-azure/pom.xml?rev=1788387&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-blob-cloud-azure/pom.xml (added)
+++ jackrabbit/oak/trunk/oak-blob-cloud-azure/pom.xml Fri Mar 24 09:31:18 2017
@@ -0,0 +1,174 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ 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.
+ -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>oak-parent</artifactId>
+ <groupId>org.apache.jackrabbit</groupId>
+ <version>1.8-SNAPSHOT</version>
+ <relativePath>../oak-parent/pom.xml</relativePath>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>oak-blob-cloud-azure</artifactId>
+ <name>Oak Azure Cloud Blob Store</name>
+ <packaging>bundle</packaging>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-scr-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <configuration>
+ <instructions>
+ <Export-Package>
+ org.apache.jackrabbit.oak.blob.cloud.azure.blobstorage
+ </Export-Package>
+ <DynamicImport-Package>sun.io</DynamicImport-Package>
+ <Embed-Dependency>
+ azure-storage,
+ azure-keyvault-core
+ </Embed-Dependency>
+ </instructions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <!-- ====================================================================== -->
+ <!-- D E P E N D E N C I E S -->
+ <!-- ====================================================================== -->
+ <dependencies>
+ <!-- Optional OSGi dependencies, used only when running within OSGi -->
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.core</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.compendium</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>biz.aQute.bnd</groupId>
+ <artifactId>bndlib</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.scr.annotations</artifactId>
+ <scope>provided</scope>
+ </dependency>
+
+ <!-- JCR and Jackrabbit dependencies -->
+ <dependency>
+ <groupId>javax.jcr</groupId>
+ <artifactId>jcr</artifactId>
+ <version>2.0</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.jackrabbit</groupId>
+ <artifactId>jackrabbit-jcr-commons</artifactId>
+ <version>${jackrabbit.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.jackrabbit</groupId>
+ <artifactId>jackrabbit-data</artifactId>
+ <version>${jackrabbit.version}</version>
+ </dependency>
+
+ <!-- Dependencies to other Oak components -->
+ <dependency>
+ <groupId>org.apache.jackrabbit</groupId>
+ <artifactId>oak-commons</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.jackrabbit</groupId>
+ <artifactId>oak-blob</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.jackrabbit</groupId>
+ <artifactId>oak-core</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <!-- Azure Blob Storage dependency -->
+ <dependency>
+ <groupId>com.microsoft.azure</groupId>
+ <artifactId>azure-storage</artifactId>
+ <version>5.0.0</version>
+ </dependency>
+ <dependency>
+ <groupId>com.microsoft.azure</groupId>
+ <artifactId>azure-keyvault-core</artifactId>
+ <version>0.9.7</version>
+ </dependency>
+
+ <!-- Test dependencies -->
+ <dependency>
+ <groupId>org.apache.jackrabbit</groupId>
+ <artifactId>jackrabbit-data</artifactId>
+ <version>${jackrabbit.version}</version>
+ <classifier>tests</classifier>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.jackrabbit</groupId>
+ <artifactId>oak-core</artifactId>
+ <version>${project.version}</version>
+ <classifier>tests</classifier>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>jul-to-slf4j</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>ch.qos.logback</groupId>
+ <artifactId>logback-classic</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.testing.osgi-mock</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <version>1.10.19</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+</project>
Propchange: jackrabbit/oak/trunk/oak-blob-cloud-azure/pom.xml
------------------------------------------------------------------------------
svn:eol-style = native
Added: jackrabbit/oak/trunk/oak-blob-cloud-azure/src/main/java/org/apache/jackrabbit/oak/blob/cloud/azure/blobstorage/AbstractAzureDataStoreService.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-blob-cloud-azure/src/main/java/org/apache/jackrabbit/oak/blob/cloud/azure/blobstorage/AbstractAzureDataStoreService.java?rev=1788387&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-blob-cloud-azure/src/main/java/org/apache/jackrabbit/oak/blob/cloud/azure/blobstorage/AbstractAzureDataStoreService.java (added)
+++ jackrabbit/oak/trunk/oak-blob-cloud-azure/src/main/java/org/apache/jackrabbit/oak/blob/cloud/azure/blobstorage/AbstractAzureDataStoreService.java Fri Mar 24 09:31:18 2017
@@ -0,0 +1,72 @@
+/*
+ * 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.blob.cloud.azure.blobstorage;
+
+import org.apache.jackrabbit.core.data.DataStore;
+import org.apache.jackrabbit.core.data.DataStoreException;
+import org.apache.jackrabbit.oak.plugins.blob.AbstractSharedCachingDataStore;
+import org.apache.jackrabbit.oak.plugins.blob.datastore.AbstractDataStoreService;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.component.ComponentContext;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.Properties;
+
+public abstract class AbstractAzureDataStoreService extends AbstractDataStoreService {
+ private static final String DESCRIPTION = "oak.datastore.description";
+
+ private ServiceRegistration delegateReg;
+
+ @Override
+ protected DataStore createDataStore(ComponentContext context, Map<String, Object> config) {
+ Properties properties = new Properties();
+ properties.putAll(config);
+
+ AzureDataStore dataStore = new AzureDataStore();
+ dataStore.setStatisticsProvider(getStatisticsProvider());
+ dataStore.setProperties(properties);
+
+ Dictionary<String, Object> props = new Hashtable<String, Object>();
+ props.put(Constants.SERVICE_PID, dataStore.getClass().getName());
+ props.put(DESCRIPTION, getDescription());
+
+ delegateReg = context.getBundleContext().registerService(new String[] {
+ AbstractSharedCachingDataStore.class.getName(),
+ AbstractSharedCachingDataStore.class.getName()
+ }, dataStore , props);
+
+ return dataStore;
+ }
+
+ protected void deactivate() throws DataStoreException {
+ if (delegateReg != null) {
+ delegateReg.unregister();
+ }
+ super.deactivate();
+ }
+
+ @Override
+ protected String[] getDescription() {
+ return new String[] {"type=AzureBlob"};
+ }
+}
Propchange: jackrabbit/oak/trunk/oak-blob-cloud-azure/src/main/java/org/apache/jackrabbit/oak/blob/cloud/azure/blobstorage/AbstractAzureDataStoreService.java
------------------------------------------------------------------------------
svn:eol-style = native
Added: jackrabbit/oak/trunk/oak-blob-cloud-azure/src/main/java/org/apache/jackrabbit/oak/blob/cloud/azure/blobstorage/AzureBlobStoreBackend.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-blob-cloud-azure/src/main/java/org/apache/jackrabbit/oak/blob/cloud/azure/blobstorage/AzureBlobStoreBackend.java?rev=1788387&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-blob-cloud-azure/src/main/java/org/apache/jackrabbit/oak/blob/cloud/azure/blobstorage/AzureBlobStoreBackend.java (added)
+++ jackrabbit/oak/trunk/oak-blob-cloud-azure/src/main/java/org/apache/jackrabbit/oak/blob/cloud/azure/blobstorage/AzureBlobStoreBackend.java Fri Mar 24 09:31:18 2017
@@ -0,0 +1,789 @@
+/*
+ * 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.blob.cloud.azure.blobstorage;
+
+import com.google.common.base.Function;
+import com.google.common.base.Strings;
+import com.google.common.collect.AbstractIterator;
+import com.google.common.collect.Lists;
+import com.microsoft.azure.storage.RequestOptions;
+import com.microsoft.azure.storage.ResultContinuation;
+import com.microsoft.azure.storage.ResultSegment;
+import com.microsoft.azure.storage.RetryPolicy;
+import com.microsoft.azure.storage.StorageException;
+import com.microsoft.azure.storage.blob.BlobListingDetails;
+import com.microsoft.azure.storage.blob.BlobRequestOptions;
+import com.microsoft.azure.storage.blob.CloudBlob;
+import com.microsoft.azure.storage.blob.CloudBlobContainer;
+import com.microsoft.azure.storage.blob.CloudBlobDirectory;
+import com.microsoft.azure.storage.blob.CloudBlockBlob;
+import com.microsoft.azure.storage.blob.CopyStatus;
+import com.microsoft.azure.storage.blob.ListBlobItem;
+import org.apache.jackrabbit.core.data.DataIdentifier;
+import org.apache.jackrabbit.core.data.DataRecord;
+import org.apache.jackrabbit.core.data.DataStoreException;
+import org.apache.jackrabbit.oak.commons.PropertiesUtil;
+import org.apache.jackrabbit.oak.spi.blob.AbstractDataRecord;
+import org.apache.jackrabbit.oak.spi.blob.AbstractSharedBackend;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.URISyntaxException;
+import java.util.EnumSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Properties;
+import java.util.Queue;
+
+import static java.lang.Thread.currentThread;
+
+public class AzureBlobStoreBackend extends AbstractSharedBackend {
+
+ private static final Logger LOG = LoggerFactory.getLogger(AzureBlobStoreBackend.class);
+
+ private static final String META_DIR_NAME = "META";
+ private static final String META_KEY_PREFIX = META_DIR_NAME + "/";
+
+ private static final long BUFFERED_STREAM_THRESHHOLD = 1024 * 1024;
+
+ private Properties properties;
+ private String containerName;
+ private String connectionString;
+ private int concurrentRequestCount = 1;
+ private RetryPolicy retryPolicy;
+ private Integer requestTimeout;
+
+ private String secret;
+
+ public void setProperties(final Properties properties) {
+ this.properties = properties;
+ }
+
+ protected CloudBlobContainer getAzureContainer() throws DataStoreException {
+ CloudBlobContainer container = Utils.getBlobContainer(connectionString, containerName);
+ RequestOptions requestOptions = container.getServiceClient().getDefaultRequestOptions();
+ if (retryPolicy != null) {
+ requestOptions.setRetryPolicyFactory(retryPolicy);
+ }
+ if (requestTimeout != null) {
+ requestOptions.setTimeoutIntervalInMs(requestTimeout);
+ }
+ return container;
+ }
+
+ @Override
+ public void init() throws DataStoreException {
+ ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
+ long start = System.currentTimeMillis();
+ try {
+ Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
+ LOG.debug("Started backend initialization");
+
+ if (null == properties) {
+ try {
+ properties = Utils.readConfig(Utils.DEFAULT_CONFIG_FILE);
+ }
+ catch (IOException e) {
+ throw new DataStoreException("Unable to initialize Azure Data Store from " + Utils.DEFAULT_CONFIG_FILE, e);
+ }
+ }
+ secret = properties.getProperty("secret");
+
+ try {
+ Utils.setProxyIfNeeded(properties);
+ containerName = (String) properties.get(AzureConstants.AZURE_BLOB_CONTAINER_NAME);
+ connectionString = Utils.getConnectionStringFromProperties(properties);
+ concurrentRequestCount = PropertiesUtil.toInteger(properties.get(AzureConstants.AZURE_BLOB_CONCURRENT_REQUESTS_PER_OPERATION), 1);
+ LOG.info("Using concurrentRequestsPerOperation={}", concurrentRequestCount);
+ retryPolicy = Utils.getRetryPolicy((String)properties.get(AzureConstants.AZURE_BLOB_MAX_REQUEST_RETRY));
+ if (properties.getProperty(AzureConstants.AZURE_BLOB_REQUEST_TIMEOUT) != null) {
+ requestTimeout = PropertiesUtil.toInteger(properties.getProperty(AzureConstants.AZURE_BLOB_REQUEST_TIMEOUT), RetryPolicy.DEFAULT_CLIENT_RETRY_COUNT);
+ }
+
+ CloudBlobContainer azureContainer = getAzureContainer();
+
+ if (azureContainer.createIfNotExists()) {
+ LOG.info("New container created. containerName={}", containerName);
+ } else {
+ LOG.info("Reusing existing container. containerName={}", containerName);
+ }
+ LOG.debug("Backend initialized. duration={}",
+ +(System.currentTimeMillis() - start));
+ }
+ catch (StorageException e) {
+ throw new DataStoreException(e);
+ }
+ }
+ finally {
+ Thread.currentThread().setContextClassLoader(contextClassLoader);
+ }
+ }
+
+ @Override
+ public InputStream read(DataIdentifier identifier) throws DataStoreException {
+ if (null == identifier) throw new NullPointerException("identifier");
+
+ String key = getKeyName(identifier);
+ long start = System.currentTimeMillis();
+ ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
+ try {
+ Thread.currentThread().setContextClassLoader(
+ getClass().getClassLoader());
+ CloudBlockBlob blob = getAzureContainer().getBlockBlobReference(key);
+ if (!blob.exists()) {
+ throw new DataStoreException(String.format("Trying to read missing blob. identifier=%s", key));
+ }
+
+ InputStream is = blob.openInputStream();
+ LOG.debug("Got input stream for blob. identifier={} duration={}", key, (System.currentTimeMillis() - start));
+ return is;
+ }
+ catch (StorageException e) {
+ LOG.info("Error reading blob. identifier=%s", key);
+ throw new DataStoreException(String.format("Cannot read blob. identifier=%s", key), e);
+ }
+ catch (URISyntaxException e) {
+ LOG.debug("Error reading blob. identifier=%s", key);
+ throw new DataStoreException(String.format("Cannot read blob. identifier=%s", key), e);
+ } finally {
+ if (contextClassLoader != null) {
+ Thread.currentThread().setContextClassLoader(contextClassLoader);
+ }
+ }
+ }
+
+ @Override
+ public void write(DataIdentifier identifier, File file) throws DataStoreException {
+ if (null == identifier) {
+ throw new NullPointerException("identifier");
+ }
+ if (null == file) {
+ throw new NullPointerException("file");
+ }
+ String key = getKeyName(identifier);
+ long start = System.currentTimeMillis();
+ ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
+
+ try {
+ Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
+
+ long len = file.length();
+ LOG.debug("Blob write started. identifier={} length={}", key, len);
+ CloudBlockBlob blob = getAzureContainer().getBlockBlobReference(key);
+ if (!blob.exists()) {
+ BlobRequestOptions options = new BlobRequestOptions();
+ options.setConcurrentRequestCount(concurrentRequestCount);
+ boolean useBufferedStream = len < BUFFERED_STREAM_THRESHHOLD;
+ final InputStream in = useBufferedStream ? new BufferedInputStream(new FileInputStream(file)) : new FileInputStream(file);
+ try {
+ blob.upload(in, len, null, options, null);
+ LOG.debug("Blob created. identifier={} length={} duration={} buffered={}", key, len, (System.currentTimeMillis() - start), useBufferedStream);
+ } finally {
+ in.close();
+ }
+ return;
+ }
+
+ blob.downloadAttributes();
+ if (blob.getProperties().getLength() != len) {
+ throw new DataStoreException("Length Collision. identifier=" + key +
+ " new length=" + len +
+ " old length=" + blob.getProperties().getLength());
+ }
+ LOG.trace("Blob already exists. identifier={} lastModified={}", key, blob.getProperties().getLastModified().getTime());
+ blob.startCopy(blob);
+ //TODO: better way of updating lastModified (use custom metadata?)
+ if (!waitForCopy(blob)) {
+ throw new DataStoreException(
+ String.format("Cannot update lastModified for blob. identifier=%s status=%s",
+ key, blob.getCopyState().getStatusDescription()));
+ }
+ LOG.debug("Blob updated. identifier={} lastModified={} duration={}", key,
+ blob.getProperties().getLastModified().getTime(), (System.currentTimeMillis() - start));
+ }
+ catch (StorageException e) {
+ LOG.info("Error writing blob. identifier={}", key, e);
+ throw new DataStoreException(String.format("Cannot write blob. identifier=%s", key), e);
+ }
+ catch (URISyntaxException | IOException e) {
+ LOG.debug("Error writing blob. identifier={}", key, e);
+ throw new DataStoreException(String.format("Cannot write blob. identifier=%s", key), e);
+ } catch (InterruptedException e) {
+ LOG.debug("Error writing blob. identifier={}", key, e);
+ throw new DataStoreException(String.format("Cannot copy blob. identifier=%s", key), e);
+ } finally {
+ if (null != contextClassLoader) {
+ Thread.currentThread().setContextClassLoader(contextClassLoader);
+ }
+ }
+ }
+
+ private static boolean waitForCopy(CloudBlob blob) throws StorageException, InterruptedException {
+ boolean continueLoop = true;
+ CopyStatus status = CopyStatus.PENDING;
+ while (continueLoop) {
+ blob.downloadAttributes();
+ status = blob.getCopyState().getStatus();
+ continueLoop = status == CopyStatus.PENDING;
+ // Sleep if retry is needed
+ if (continueLoop) {
+ Thread.sleep(500);
+ }
+ }
+ return status == CopyStatus.SUCCESS;
+ }
+
+ @Override
+ public byte[] getOrCreateReferenceKey() throws DataStoreException {
+ try {
+ if (!Strings.isNullOrEmpty(secret)) {
+ return secret.getBytes("UTF-8");
+ }
+ LOG.warn("secret not defined");
+ return super.getOrCreateReferenceKey();
+ } catch (UnsupportedEncodingException e) {
+ throw new DataStoreException(e);
+ }
+ }
+
+ @Override
+ public DataRecord getRecord(DataIdentifier identifier) throws DataStoreException {
+ if (null == identifier) {
+ throw new NullPointerException("identifier");
+ }
+ String key = getKeyName(identifier);
+ long start = System.currentTimeMillis();
+ ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
+ try {
+ Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
+
+ CloudBlockBlob blob = getAzureContainer().getBlockBlobReference(key);
+ if (blob.exists()) {
+ blob.downloadAttributes();
+ AzureBlobStoreDataRecord record = new AzureBlobStoreDataRecord(
+ this,
+ connectionString,
+ containerName,
+ new DataIdentifier(getIdentifierName(blob.getName())),
+ blob.getProperties().getLastModified().getTime(),
+ blob.getProperties().getLength());
+ LOG.debug("Data record read for blob. identifier={} duration={} record={}",
+ key, (System.currentTimeMillis() - start), record);
+ return record;
+ } else {
+ LOG.debug("Blob not found. identifier={} duration={}",
+ key, (System.currentTimeMillis() - start));
+ throw new DataStoreException(String.format("Cannot find blob. identifier=%s", key));
+ }
+ }catch (StorageException e) {
+ LOG.info("Error getting data record for blob. identifier={}", key, e);
+ throw new DataStoreException(String.format("Cannot retrieve blob. identifier=%s", key), e);
+ }
+ catch (URISyntaxException e) {
+ LOG.debug("Error getting data record for blob. identifier={}", key, e);
+ throw new DataStoreException(String.format("Cannot retrieve blob. identifier=%s", key), e);
+ } finally {
+ if (contextClassLoader != null) {
+ Thread.currentThread().setContextClassLoader(contextClassLoader);
+ }
+ }
+ }
+
+ @Override
+ public Iterator<DataIdentifier> getAllIdentifiers() throws DataStoreException {
+ return new RecordsIterator<DataIdentifier>(
+ new Function<AzureBlobInfo, DataIdentifier>() {
+ @Override
+ public DataIdentifier apply(AzureBlobInfo input) {
+ return new DataIdentifier(getIdentifierName(input.getName()));
+ }
+ }
+ );
+ }
+
+
+
+ @Override
+ public Iterator<DataRecord> getAllRecords() throws DataStoreException {
+ final AbstractSharedBackend backend = this;
+ return new RecordsIterator<DataRecord>(
+ new Function<AzureBlobInfo, DataRecord>() {
+ @Override
+ public DataRecord apply(AzureBlobInfo input) {
+ return new AzureBlobStoreDataRecord(
+ backend,
+ connectionString,
+ containerName,
+ new DataIdentifier(getIdentifierName(input.getName())),
+ input.getLastModified(),
+ input.getLength());
+ }
+ }
+ );
+ }
+
+ @Override
+ public boolean exists(DataIdentifier identifier) throws DataStoreException {
+ long start = System.currentTimeMillis();
+ String key = getKeyName(identifier);
+ ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
+ try {
+ Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
+
+ boolean exists =getAzureContainer().getBlockBlobReference(key).exists();
+ LOG.debug("Blob exists={} identifier={} duration={}", exists, key, (System.currentTimeMillis() - start));
+ return exists;
+ }
+ catch (Exception e) {
+ throw new DataStoreException(e);
+ }
+ finally {
+ if (null != contextClassLoader) {
+ Thread.currentThread().setContextClassLoader(contextClassLoader);
+ }
+ }
+ }
+
+ @Override
+ public void close() throws DataStoreException {
+ LOG.info("AzureBlobBackend closed.");
+ }
+
+ @Override
+ public void deleteRecord(DataIdentifier identifier) throws DataStoreException {
+ if (null == identifier) throw new NullPointerException("identifier");
+
+ String key = getKeyName(identifier);
+ long start = System.currentTimeMillis();
+ ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
+ try {
+ Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
+
+ boolean result = getAzureContainer().getBlockBlobReference(key).deleteIfExists();
+ LOG.debug("Blob {}. identifier={} duration={}",
+ result ? "deleted" : "delete requested, but it does not exist (perhaps already deleted)",
+ key, (System.currentTimeMillis() - start));
+ }
+ catch (StorageException e) {
+ LOG.info("Error deleting blob. identifier={}", key, e);
+ throw new DataStoreException(e);
+ }
+ catch (URISyntaxException e) {
+ throw new DataStoreException(e);
+ } finally {
+ if (contextClassLoader != null) {
+ Thread.currentThread().setContextClassLoader(contextClassLoader);
+ }
+ }
+ }
+
+ @Override
+ public void addMetadataRecord(InputStream input, String name) throws DataStoreException {
+ if (null == input) {
+ throw new NullPointerException("input");
+ }
+ if (Strings.isNullOrEmpty(name)) {
+ throw new IllegalArgumentException("name");
+ }
+ long start = System.currentTimeMillis();
+ ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
+ try {
+ Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
+
+ addMetadataRecordImpl(input, name, -1L);
+ LOG.debug("Metadata record added. metadataName={} duration={}", name, (System.currentTimeMillis() - start));
+ }
+ finally {
+ if (null != contextClassLoader) {
+ Thread.currentThread().setContextClassLoader(contextClassLoader);
+ }
+ }
+ }
+
+ @Override
+ public void addMetadataRecord(File input, String name) throws DataStoreException {
+ if (null == input) {
+ throw new NullPointerException("input");
+ }
+ if (Strings.isNullOrEmpty(name)) {
+ throw new IllegalArgumentException("name");
+ }
+ long start = System.currentTimeMillis();
+ ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
+ try {
+ Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
+
+ addMetadataRecordImpl(new FileInputStream(input), name, input.length());
+ LOG.debug("Metadata record added. metadataName={} duration={}", name, (System.currentTimeMillis() - start));
+ }
+ catch (FileNotFoundException e) {
+ throw new DataStoreException(e);
+ }
+ finally {
+ if (null != contextClassLoader) {
+ Thread.currentThread().setContextClassLoader(contextClassLoader);
+ }
+ }
+ }
+
+ private void addMetadataRecordImpl(final InputStream input, String name, long recordLength) throws DataStoreException {
+ try {
+ CloudBlobDirectory metaDir = getAzureContainer().getDirectoryReference(META_DIR_NAME);
+ CloudBlockBlob blob = metaDir.getBlockBlobReference(name);
+ blob.upload(input, recordLength);
+ }
+ catch (StorageException e) {
+ LOG.info("Error adding metadata record. metadataName={} length={}", name, recordLength, e);
+ throw new DataStoreException(e);
+ }
+ catch (URISyntaxException | IOException e) {
+ throw new DataStoreException(e);
+ }
+ }
+
+ @Override
+ public DataRecord getMetadataRecord(String name) {
+ ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
+ long start = System.currentTimeMillis();
+ try {
+ Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
+
+ CloudBlobDirectory metaDir = getAzureContainer().getDirectoryReference(META_DIR_NAME);
+ CloudBlockBlob blob = metaDir.getBlockBlobReference(name);
+ if (!blob.exists()) {
+ LOG.warn("Trying to read missing metadata. metadataName={}", name);
+ return null;
+ }
+ blob.downloadAttributes();
+ long lastModified = blob.getProperties().getLastModified().getTime();
+ long length = blob.getProperties().getLength();
+ AzureBlobStoreDataRecord record = new AzureBlobStoreDataRecord(this,
+ connectionString,
+ containerName, new DataIdentifier(name),
+ lastModified,
+ length,
+ true);
+ LOG.debug("Metadata record read. metadataName={} duration={} record={}", name, (System.currentTimeMillis() - start), record);
+ return record;
+
+ } catch (StorageException e) {
+ LOG.info("Error reading metadata record. metadataName={}", name, e);
+ throw new RuntimeException(e);
+ } catch (Exception e) {
+ LOG.debug("Error reading metadata record. metadataName={}", name, e);
+ throw new RuntimeException(e);
+ } finally {
+ if (null != contextClassLoader) {
+ Thread.currentThread().setContextClassLoader(contextClassLoader);
+ }
+ }
+ }
+
+ @Override
+ public List<DataRecord> getAllMetadataRecords(String prefix) {
+ if (null == prefix) {
+ throw new NullPointerException("prefix");
+ }
+ long start = System.currentTimeMillis();
+ final List<DataRecord> records = Lists.newArrayList();
+ ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
+ try {
+ Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
+
+ CloudBlobDirectory metaDir = getAzureContainer().getDirectoryReference(META_DIR_NAME);
+ for (ListBlobItem item : metaDir.listBlobs(prefix)) {
+ if (item instanceof CloudBlob) {
+ CloudBlob blob = (CloudBlob) item;
+ records.add(new AzureBlobStoreDataRecord(
+ this,
+ connectionString,
+ containerName,
+ new DataIdentifier(stripMetaKeyPrefix(blob.getName())),
+ blob.getProperties().getLastModified().getTime(),
+ blob.getProperties().getLength(),
+ true));
+ }
+ }
+ LOG.debug("Metadata records read. recordsRead={} metadataFolder={} duration={}", records.size(), prefix, (System.currentTimeMillis() - start));
+ }
+ catch (StorageException e) {
+ LOG.info("Error reading all metadata records. metadataFolder={}", prefix, e);
+ }
+ catch (DataStoreException | URISyntaxException e) {
+ LOG.debug("Error reading all metadata records. metadataFolder={}", prefix, e);
+ }
+ finally {
+ if (null != contextClassLoader) {
+ Thread.currentThread().setContextClassLoader(contextClassLoader);
+ }
+ }
+ return records;
+ }
+
+ @Override
+ public boolean deleteMetadataRecord(String name) {
+ long start = System.currentTimeMillis();
+ ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
+ try {
+ Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
+
+ CloudBlockBlob blob = getAzureContainer().getBlockBlobReference(addMetaKeyPrefix(name));
+ boolean result = blob.deleteIfExists();
+ LOG.debug("Metadata record {}. metadataName={} duration={}",
+ result ? "deleted" : "delete requested, but it does not exist (perhaps already deleted)",
+ name, (System.currentTimeMillis() - start));
+ return result;
+
+ }
+ catch (StorageException e) {
+ LOG.info("Error deleting metadata record. metadataName={}", name, e);
+ }
+ catch (DataStoreException | URISyntaxException e) {
+ LOG.debug("Error deleting metadata record. metadataName={}", name, e);
+ }
+ finally {
+ if (contextClassLoader != null) {
+ Thread.currentThread().setContextClassLoader(contextClassLoader);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void deleteAllMetadataRecords(String prefix) {
+ if (null == prefix) {
+ throw new NullPointerException("prefix");
+ }
+ long start = System.currentTimeMillis();
+ ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
+ try {
+ Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
+
+ CloudBlobDirectory metaDir = getAzureContainer().getDirectoryReference(META_DIR_NAME);
+ int total = 0;
+ for (ListBlobItem item : metaDir.listBlobs(prefix)) {
+ if (item instanceof CloudBlob) {
+ if (((CloudBlob)item).deleteIfExists()) {
+ total++;
+ }
+ }
+ }
+ LOG.debug("Metadata records deleted. recordsDeleted={} metadataFolder={} duration={}",
+ total, prefix, (System.currentTimeMillis() - start));
+
+ }
+ catch (StorageException e) {
+ LOG.info("Error deleting all metadata records. metadataFolder={}", prefix, e);
+ }
+ catch (DataStoreException | URISyntaxException e) {
+ LOG.debug("Error deleting all metadata records. metadataFolder={}", prefix, e);
+ }
+ finally {
+ if (null != contextClassLoader) {
+ Thread.currentThread().setContextClassLoader(contextClassLoader);
+ }
+ }
+ }
+
+
+ /**
+ * Get key from data identifier. Object is stored with key in ADS.
+ */
+ private static String getKeyName(DataIdentifier identifier) {
+ String key = identifier.toString();
+ return key.substring(0, 4) + Utils.DASH + key.substring(4);
+ }
+
+ /**
+ * Get data identifier from key.
+ */
+ private static String getIdentifierName(String key) {
+ if (!key.contains(Utils.DASH)) {
+ return null;
+ } else if (key.contains(META_KEY_PREFIX)) {
+ return key;
+ }
+ return key.substring(0, 4) + key.substring(5);
+ }
+
+ private static String addMetaKeyPrefix(final String key) {
+ return META_KEY_PREFIX + key;
+ }
+
+ private static String stripMetaKeyPrefix(String name) {
+ if (name.startsWith(META_KEY_PREFIX)) {
+ return name.substring(META_KEY_PREFIX.length());
+ }
+ return name;
+ }
+
+ private static class AzureBlobInfo {
+ private final String name;
+ private final long lastModified;
+ private final long length;
+
+ public AzureBlobInfo(String name, long lastModified, long length) {
+ this.name = name;
+ this.lastModified = lastModified;
+ this.length = length;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public long getLastModified() {
+ return lastModified;
+ }
+
+ public long getLength() {
+ return length;
+ }
+
+ public static AzureBlobInfo fromCloudBlob(CloudBlob cloudBlob) {
+ return new AzureBlobInfo(cloudBlob.getName(),
+ cloudBlob.getProperties().getLastModified().getTime(),
+ cloudBlob.getProperties().getLength());
+ }
+ }
+
+ private class RecordsIterator<T> extends AbstractIterator<T> {
+ // Seems to be thread-safe (in 5.0.0)
+ ResultContinuation resultContinuation;
+ boolean firstCall = true;
+ final Function<AzureBlobInfo, T> transformer;
+ final Queue<AzureBlobInfo> items = Lists.newLinkedList();
+
+ public RecordsIterator (Function<AzureBlobInfo, T> transformer) {
+ this.transformer = transformer;
+ }
+
+ @Override
+ protected T computeNext() {
+ if (items.isEmpty()) {
+ loadItems();
+ }
+ if (!items.isEmpty()) {
+ return transformer.apply(items.remove());
+ }
+ return endOfData();
+ }
+
+ private boolean loadItems() {
+ long start = System.currentTimeMillis();
+ ClassLoader contextClassLoader = currentThread().getContextClassLoader();
+ try {
+ currentThread().setContextClassLoader(getClass().getClassLoader());
+
+ CloudBlobContainer container = Utils.getBlobContainer(connectionString, containerName);
+ if (!firstCall && (resultContinuation == null || !resultContinuation.hasContinuation())) {
+ LOG.trace("No more records in container. containerName={}", container);
+ return false;
+ }
+ firstCall = false;
+ ResultSegment<ListBlobItem> results = container.listBlobsSegmented(null, false, EnumSet.noneOf(BlobListingDetails.class), null, resultContinuation, null, null);
+ resultContinuation = results.getContinuationToken();
+ for (ListBlobItem item : results.getResults()) {
+ if (item instanceof CloudBlob) {
+ items.add(AzureBlobInfo.fromCloudBlob((CloudBlob)item));
+ }
+ }
+ LOG.debug("Container records batch read. batchSize={} containerName={} duration={}",
+ results.getLength(), containerName, (System.currentTimeMillis() - start));
+ return results.getLength() > 0;
+ }
+ catch (StorageException e) {
+ LOG.info("Error listing blobs. containerName={}", containerName, e);
+ }
+ catch (DataStoreException e) {
+ LOG.debug("Cannot list blobs. containerName={}", containerName, e);
+ } finally {
+ if (contextClassLoader != null) {
+ currentThread().setContextClassLoader(contextClassLoader);
+ }
+ }
+ return false;
+ }
+ }
+
+ static class AzureBlobStoreDataRecord extends AbstractDataRecord {
+ final String connectionString;
+ final String containerName;
+ final long lastModified;
+ final long length;
+ final boolean isMeta;
+
+ public AzureBlobStoreDataRecord(AbstractSharedBackend backend, String connectionString, String containerName,
+ DataIdentifier key, long lastModified, long length) {
+ this(backend, connectionString, containerName, key, lastModified, length, false);
+ }
+
+ public AzureBlobStoreDataRecord(AbstractSharedBackend backend, String connectionString, String containerName,
+ DataIdentifier key, long lastModified, long length, boolean isMeta) {
+ super(backend, key);
+ this.connectionString = connectionString;
+ this.containerName = containerName;
+ this.lastModified = lastModified;
+ this.length = length;
+ this.isMeta = isMeta;
+ }
+
+ @Override
+ public long getLength() throws DataStoreException {
+ return length;
+ }
+
+ @Override
+ public InputStream getStream() throws DataStoreException {
+ String id = getKeyName(getIdentifier());
+ CloudBlobContainer container = Utils.getBlobContainer(connectionString, containerName);
+ if (isMeta) {
+ id = addMetaKeyPrefix(getIdentifier().toString());
+ }
+ try {
+ return container.getBlockBlobReference(id).openInputStream();
+ } catch (StorageException | URISyntaxException e) {
+ throw new DataStoreException(e);
+ }
+ }
+
+ @Override
+ public long getLastModified() {
+ return lastModified;
+ }
+
+ @Override
+ public String toString() {
+ return "AzureBlobStoreDataRecord{" +
+ "identifier=" + getIdentifier() +
+ ", length=" + length +
+ ", lastModified=" + lastModified +
+ ", containerName='" + containerName + '\'' +
+ '}';
+ }
+ }
+}
Propchange: jackrabbit/oak/trunk/oak-blob-cloud-azure/src/main/java/org/apache/jackrabbit/oak/blob/cloud/azure/blobstorage/AzureBlobStoreBackend.java
------------------------------------------------------------------------------
svn:eol-style = native
Added: jackrabbit/oak/trunk/oak-blob-cloud-azure/src/main/java/org/apache/jackrabbit/oak/blob/cloud/azure/blobstorage/AzureConstants.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-blob-cloud-azure/src/main/java/org/apache/jackrabbit/oak/blob/cloud/azure/blobstorage/AzureConstants.java?rev=1788387&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-blob-cloud-azure/src/main/java/org/apache/jackrabbit/oak/blob/cloud/azure/blobstorage/AzureConstants.java (added)
+++ jackrabbit/oak/trunk/oak-blob-cloud-azure/src/main/java/org/apache/jackrabbit/oak/blob/cloud/azure/blobstorage/AzureConstants.java Fri Mar 24 09:31:18 2017
@@ -0,0 +1,64 @@
+/*
+ * 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.blob.cloud.azure.blobstorage;
+
+public final class AzureConstants {
+ /**
+ * Azure Stoage Account name
+ */
+ public static final String AZURE_STORAGE_ACCOUNT_NAME = "accessKey";
+
+ /**
+ * Azure Stoage Account Key
+ */
+ public static final String AZURE_STORAGE_ACCOUNT_KEY = "secretKey";
+
+ /**
+ * Azure Blob Storage container name
+ */
+ public static final String AZURE_BLOB_CONTAINER_NAME = "container";
+
+ /**
+ * Azure Blob Storage request timeout.
+ */
+ public static final String AZURE_BLOB_REQUEST_TIMEOUT = "socketTimeout";
+
+ /**
+ * Azure Blob Storage maximum retries per request.
+ */
+ public static final String AZURE_BLOB_MAX_REQUEST_RETRY = "maxErrorRetry";
+
+ /**
+ * Azure Blob Storage maximum connections per operation (default 1)
+ */
+ public static final String AZURE_BLOB_CONCURRENT_REQUESTS_PER_OPERATION = "maxConnections";
+
+ /**
+ * Constant to set proxy host.
+ */
+ public static final String PROXY_HOST = "proxyHost";
+
+ /**
+ * Constant to set proxy port.
+ */
+ public static final String PROXY_PORT = "proxyPort";
+
+ private AzureConstants() { }
+}
Propchange: jackrabbit/oak/trunk/oak-blob-cloud-azure/src/main/java/org/apache/jackrabbit/oak/blob/cloud/azure/blobstorage/AzureConstants.java
------------------------------------------------------------------------------
svn:eol-style = native
Added: jackrabbit/oak/trunk/oak-blob-cloud-azure/src/main/java/org/apache/jackrabbit/oak/blob/cloud/azure/blobstorage/AzureDataStore.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-blob-cloud-azure/src/main/java/org/apache/jackrabbit/oak/blob/cloud/azure/blobstorage/AzureDataStore.java?rev=1788387&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-blob-cloud-azure/src/main/java/org/apache/jackrabbit/oak/blob/cloud/azure/blobstorage/AzureDataStore.java (added)
+++ jackrabbit/oak/trunk/oak-blob-cloud-azure/src/main/java/org/apache/jackrabbit/oak/blob/cloud/azure/blobstorage/AzureDataStore.java Fri Mar 24 09:31:18 2017
@@ -0,0 +1,59 @@
+/*
+ * 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.blob.cloud.azure.blobstorage;
+
+import org.apache.jackrabbit.oak.plugins.blob.AbstractSharedCachingDataStore;
+import org.apache.jackrabbit.oak.spi.blob.AbstractSharedBackend;
+import org.apache.jackrabbit.oak.spi.blob.SharedBackend;
+
+import java.util.Properties;
+
+public class AzureDataStore extends AbstractSharedCachingDataStore {
+
+ private int minRecordLength = 16*1024;
+
+ protected Properties properties;
+
+ @Override
+ protected AbstractSharedBackend createBackend() {
+ AzureBlobStoreBackend backend = new AzureBlobStoreBackend();
+ if (null != properties) {
+ backend.setProperties(properties);
+ }
+ return backend;
+ }
+
+ public void setProperties(final Properties properties) {
+ this.properties = properties;
+ }
+
+ public SharedBackend getBackend() {
+ return backend;
+ }
+
+ @Override
+ public int getMinRecordLength() {
+ return minRecordLength;
+ }
+
+ public void setMinRecordLength(int minRecordLength) {
+ this.minRecordLength = minRecordLength;
+ }
+}
Propchange: jackrabbit/oak/trunk/oak-blob-cloud-azure/src/main/java/org/apache/jackrabbit/oak/blob/cloud/azure/blobstorage/AzureDataStore.java
------------------------------------------------------------------------------
svn:eol-style = native
Added: jackrabbit/oak/trunk/oak-blob-cloud-azure/src/main/java/org/apache/jackrabbit/oak/blob/cloud/azure/blobstorage/AzureDataStoreService.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-blob-cloud-azure/src/main/java/org/apache/jackrabbit/oak/blob/cloud/azure/blobstorage/AzureDataStoreService.java?rev=1788387&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-blob-cloud-azure/src/main/java/org/apache/jackrabbit/oak/blob/cloud/azure/blobstorage/AzureDataStoreService.java (added)
+++ jackrabbit/oak/trunk/oak-blob-cloud-azure/src/main/java/org/apache/jackrabbit/oak/blob/cloud/azure/blobstorage/AzureDataStoreService.java Fri Mar 24 09:31:18 2017
@@ -0,0 +1,28 @@
+/*
+ * 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.blob.cloud.azure.blobstorage;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.ConfigurationPolicy;
+
+@Component(policy = ConfigurationPolicy.REQUIRE, name = AzureDataStoreService.NAME, metatype = true)
+public class AzureDataStoreService extends AbstractAzureDataStoreService {
+ public static final String NAME = "org.apache.jackrabbit.oak.plugins.blob.datastore.AzureDataStore";
+}
Propchange: jackrabbit/oak/trunk/oak-blob-cloud-azure/src/main/java/org/apache/jackrabbit/oak/blob/cloud/azure/blobstorage/AzureDataStoreService.java
------------------------------------------------------------------------------
svn:eol-style = native
Added: jackrabbit/oak/trunk/oak-blob-cloud-azure/src/main/java/org/apache/jackrabbit/oak/blob/cloud/azure/blobstorage/Utils.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-blob-cloud-azure/src/main/java/org/apache/jackrabbit/oak/blob/cloud/azure/blobstorage/Utils.java?rev=1788387&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-blob-cloud-azure/src/main/java/org/apache/jackrabbit/oak/blob/cloud/azure/blobstorage/Utils.java (added)
+++ jackrabbit/oak/trunk/oak-blob-cloud-azure/src/main/java/org/apache/jackrabbit/oak/blob/cloud/azure/blobstorage/Utils.java Fri Mar 24 09:31:18 2017
@@ -0,0 +1,141 @@
+/*
+ * 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.blob.cloud.azure.blobstorage;
+
+import com.google.common.base.Strings;
+import com.microsoft.azure.storage.CloudStorageAccount;
+import com.microsoft.azure.storage.OperationContext;
+import com.microsoft.azure.storage.RetryExponentialRetry;
+import com.microsoft.azure.storage.RetryNoRetry;
+import com.microsoft.azure.storage.RetryPolicy;
+import com.microsoft.azure.storage.StorageException;
+import com.microsoft.azure.storage.blob.CloudBlobClient;
+import com.microsoft.azure.storage.blob.CloudBlobContainer;
+import org.apache.jackrabbit.core.data.DataStoreException;
+import org.apache.jackrabbit.oak.commons.PropertiesUtil;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.InetSocketAddress;
+import java.net.Proxy;
+import java.net.SocketAddress;
+import java.net.URISyntaxException;
+import java.security.InvalidKeyException;
+import java.util.Properties;
+
+public final class Utils {
+
+ public static final String DEFAULT_CONFIG_FILE = "azure.properties";
+
+ public static final String DASH = "-";
+
+ /**
+ * private constructor so that class cannot initialized from outside.
+ */
+ private Utils() {
+ }
+
+ /**
+ * Create CloudBlobClient from properties.
+ *
+ * @param connectionString connectionString to configure @link {@link CloudBlobClient}
+ * @return {@link CloudBlobClient}
+ */
+ public static CloudBlobClient getBlobClient(final String connectionString) throws URISyntaxException, InvalidKeyException {
+ CloudStorageAccount account = CloudStorageAccount.parse(connectionString);
+ CloudBlobClient client = account.createCloudBlobClient();
+ return client;
+ }
+
+ public static CloudBlobContainer getBlobContainer(final String connectionString, final String containerName) throws DataStoreException {
+ try {
+ CloudBlobClient client = Utils.getBlobClient(connectionString);
+ return client.getContainerReference(containerName);
+ } catch (InvalidKeyException | URISyntaxException | StorageException e) {
+ throw new DataStoreException(e);
+ }
+ }
+
+ public static void setProxyIfNeeded(final Properties properties) {
+ String proxyHost = properties.getProperty(AzureConstants.PROXY_HOST);
+ String proxyPort = properties.getProperty(AzureConstants.PROXY_PORT);
+
+ if (!Strings.isNullOrEmpty(proxyHost) &&
+ Strings.isNullOrEmpty(proxyPort)) {
+ int port = Integer.parseInt(proxyPort);
+ SocketAddress proxyAddr = new InetSocketAddress(proxyHost, port);
+ Proxy proxy = new Proxy(Proxy.Type.HTTP, proxyAddr);
+ OperationContext.setDefaultProxy(proxy);
+ }
+ }
+
+ public static RetryPolicy getRetryPolicy(final String maxRequestRetry) {
+ int retries = PropertiesUtil.toInteger(maxRequestRetry, -1);
+ if (retries < 0) {
+ return null;
+ }
+ if (retries == 0) {
+ return new RetryNoRetry();
+ }
+ return new RetryExponentialRetry(RetryPolicy.DEFAULT_CLIENT_BACKOFF, retries);
+ }
+
+
+ public static String getConnectionStringFromProperties(Properties properties) {
+ return getConnectionString(
+ properties.getProperty(AzureConstants.AZURE_STORAGE_ACCOUNT_NAME, ""),
+ properties.getProperty(AzureConstants.AZURE_STORAGE_ACCOUNT_KEY, ""));
+ }
+
+ public static String getConnectionString(final String accountName, final String accountKey) {
+ return String.format(
+ "DefaultEndpointsProtocol=https;AccountName=%s;AccountKey=%s",
+ accountName,
+ accountKey
+ );
+ }
+
+ /**
+ * Read a configuration properties file. If the file name ends with ";burn",
+ * the file is deleted after reading.
+ *
+ * @param fileName the properties file name
+ * @return the properties
+ * @throws java.io.IOException if the file doesn't exist
+ */
+ public static Properties readConfig(String fileName) throws IOException {
+ if (!new File(fileName).exists()) {
+ throw new IOException("Config file not found. fileName=" + fileName);
+ }
+ Properties prop = new Properties();
+ InputStream in = null;
+ try {
+ in = new FileInputStream(fileName);
+ prop.load(in);
+ } finally {
+ if (in != null) {
+ in.close();
+ }
+ }
+ return prop;
+ }
+}
Propchange: jackrabbit/oak/trunk/oak-blob-cloud-azure/src/main/java/org/apache/jackrabbit/oak/blob/cloud/azure/blobstorage/Utils.java
------------------------------------------------------------------------------
svn:eol-style = native