JAMES-2525 introduces ObjectStorageBlobsDAOBuilder

Project: http://git-wip-us.apache.org/repos/asf/james-project/repo
Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/d9aca59e
Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/d9aca59e
Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/d9aca59e

Branch: refs/heads/master
Commit: d9aca59ebc04e26811512e0e5b98a2ba03995378
Parents: a8824e7
Author: Jean Helou <j...@codamens.fr>
Authored: Fri Aug 31 11:46:41 2018 +0200
Committer: Benoit Tellier <btell...@linagora.com>
Committed: Fri Oct 5 18:11:43 2018 +0700

----------------------------------------------------------------------
 .../objectstorage/ObjectStorageBlobsDAO.java    |  41 ++--
 .../ObjectStorageBlobsDAOBuilder.java           |  59 ++++++
 .../ObjectStorageConfiguration.java             | 153 --------------
 .../swift/SwiftTempAuthObjectStorage.java       | 201 +++++++++++++++++++
 .../objectstorage/DockerSwiftExtension.java     |   2 +-
 .../ObjectStorageBlobsDAOContract.java          |  28 +++
 .../ObjectStorageBlobsDAOTest.java              |  46 ++---
 .../ObjectStorageConfigurationTest.java         | 112 -----------
 ...empAuthObjectStorageBlobsDAOBuilderTest.java | 104 ++++++++++
 ...tTempAuthObjectStorageConfigurationTest.java | 116 +++++++++++
 10 files changed, 542 insertions(+), 320 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/james-project/blob/d9aca59e/server/blob/blob-objectstorage/src/main/java/org/apache/james/blob/objectstorage/ObjectStorageBlobsDAO.java
----------------------------------------------------------------------
diff --git 
a/server/blob/blob-objectstorage/src/main/java/org/apache/james/blob/objectstorage/ObjectStorageBlobsDAO.java
 
b/server/blob/blob-objectstorage/src/main/java/org/apache/james/blob/objectstorage/ObjectStorageBlobsDAO.java
index 5cb5c77..6501978 100644
--- 
a/server/blob/blob-objectstorage/src/main/java/org/apache/james/blob/objectstorage/ObjectStorageBlobsDAO.java
+++ 
b/server/blob/blob-objectstorage/src/main/java/org/apache/james/blob/objectstorage/ObjectStorageBlobsDAO.java
@@ -27,49 +27,34 @@ import java.util.concurrent.CompletableFuture;
 import org.apache.commons.io.IOUtils;
 import org.apache.james.blob.api.BlobId;
 import org.apache.james.blob.api.BlobStore;
-import org.apache.james.blob.api.HashBlobId;
 import org.apache.james.blob.api.ObjectStoreException;
-import org.jclouds.ContextBuilder;
+import org.apache.james.blob.objectstorage.swift.SwiftTempAuthObjectStorage;
 import org.jclouds.blobstore.domain.Blob;
 import org.jclouds.blobstore.options.CopyOptions;
-import org.jclouds.logging.slf4j.config.SLF4JLoggingModule;
-import org.jclouds.openstack.swift.v1.blobstore.RegionScopedBlobStoreContext;
 
 import com.github.fge.lambdas.Throwing;
 import com.google.common.base.Preconditions;
-import com.google.common.collect.ImmutableSet;
 import com.google.common.hash.Hashing;
 import com.google.common.hash.HashingInputStream;
-import com.google.inject.Inject;
-import com.google.inject.Module;
 
-class ObjectStorageBlobsDAO implements BlobStore {
+public class ObjectStorageBlobsDAO implements BlobStore {
     private static final InputStream EMPTY_STREAM = new 
ByteArrayInputStream(new byte[0]);
-    private static final Iterable<Module> JCLOUDS_MODULES = 
ImmutableSet.of(new SLF4JLoggingModule());
+
 
     private final BlobId.Factory blobIdFactory;
-    private final org.jclouds.blobstore.BlobStore blobStore;
+
     private final ContainerName containerName;
+    private final org.jclouds.blobstore.BlobStore blobStore;
 
-    @Inject
-    public ObjectStorageBlobsDAO(ContainerName containerName, 
HashBlobId.Factory blobIdFactory,
-                                 ObjectStorageConfiguration 
objectStorageConfiguration) {
+    ObjectStorageBlobsDAO(ContainerName containerName, BlobId.Factory 
blobIdFactory,
+                          org.jclouds.blobstore.BlobStore blobStore) {
         this.blobIdFactory = blobIdFactory;
         this.containerName = containerName;
+        this.blobStore = blobStore;
+    }
 
-        RegionScopedBlobStoreContext blobStoreContext = 
ContextBuilder.newBuilder("openstack-swift")
-            .endpoint(objectStorageConfiguration.getEndpoint().toString())
-            .credentials(
-                objectStorageConfiguration.getSwiftIdentity().asString(),
-                objectStorageConfiguration.getCredentials().value())
-            .overrides(objectStorageConfiguration.getOverrides())
-            .modules(JCLOUDS_MODULES)
-            .buildView(RegionScopedBlobStoreContext.class);
-
-        blobStore = objectStorageConfiguration
-            .getRegion()
-            .map(region -> blobStoreContext.getBlobStore(region.value()))
-            .orElse(blobStoreContext.getBlobStore());
+    public static ObjectStorageBlobsDAOBuilder 
builder(SwiftTempAuthObjectStorage.Configuration testConfig) {
+        return SwiftTempAuthObjectStorage.daoBuilder(testConfig);
     }
 
     @Override
@@ -90,7 +75,8 @@ class ObjectStorageBlobsDAO implements BlobStore {
 
     private void updateBlobId(BlobId from, BlobId to) {
         String containerName = this.containerName.value();
-        blobStore.copyBlob(containerName, from.asString(), containerName, 
to.asString(), CopyOptions.NONE);
+        blobStore.copyBlob(containerName, from.asString(), containerName, 
to.asString(),
+            CopyOptions.NONE);
         blobStore.removeBlob(containerName, from.asString());
     }
 
@@ -126,3 +112,4 @@ class ObjectStorageBlobsDAO implements BlobStore {
 
     }
 }
+

http://git-wip-us.apache.org/repos/asf/james-project/blob/d9aca59e/server/blob/blob-objectstorage/src/main/java/org/apache/james/blob/objectstorage/ObjectStorageBlobsDAOBuilder.java
----------------------------------------------------------------------
diff --git 
a/server/blob/blob-objectstorage/src/main/java/org/apache/james/blob/objectstorage/ObjectStorageBlobsDAOBuilder.java
 
b/server/blob/blob-objectstorage/src/main/java/org/apache/james/blob/objectstorage/ObjectStorageBlobsDAOBuilder.java
new file mode 100644
index 0000000..1f1f0e7
--- /dev/null
+++ 
b/server/blob/blob-objectstorage/src/main/java/org/apache/james/blob/objectstorage/ObjectStorageBlobsDAOBuilder.java
@@ -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.james.blob.objectstorage;
+
+import java.util.function.Supplier;
+
+import org.apache.james.blob.api.BlobId;
+import org.jclouds.blobstore.BlobStore;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+
+public class ObjectStorageBlobsDAOBuilder {
+    private final Supplier<BlobStore> supplier;
+    private ContainerName containerName;
+    private BlobId.Factory blobIdFactory;
+
+    public ObjectStorageBlobsDAOBuilder(Supplier<BlobStore> supplier) {
+        this.supplier = supplier;
+    }
+
+    public ObjectStorageBlobsDAOBuilder container(ContainerName containerName) 
{
+        this.containerName = containerName;
+        return this;
+    }
+
+    public ObjectStorageBlobsDAOBuilder blobIdFactory(BlobId.Factory 
blobIdFactory) {
+        this.blobIdFactory = blobIdFactory;
+        return this;
+    }
+
+    public ObjectStorageBlobsDAO build() {
+        Preconditions.checkState(containerName != null);
+        Preconditions.checkState(blobIdFactory != null);
+        return new ObjectStorageBlobsDAO(containerName, blobIdFactory, 
supplier.get());
+    }
+
+    @VisibleForTesting
+    Supplier<BlobStore> getSupplier() {
+        return supplier;
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/d9aca59e/server/blob/blob-objectstorage/src/main/java/org/apache/james/blob/objectstorage/ObjectStorageConfiguration.java
----------------------------------------------------------------------
diff --git 
a/server/blob/blob-objectstorage/src/main/java/org/apache/james/blob/objectstorage/ObjectStorageConfiguration.java
 
b/server/blob/blob-objectstorage/src/main/java/org/apache/james/blob/objectstorage/ObjectStorageConfiguration.java
deleted file mode 100644
index 58ef7a2..0000000
--- 
a/server/blob/blob-objectstorage/src/main/java/org/apache/james/blob/objectstorage/ObjectStorageConfiguration.java
+++ /dev/null
@@ -1,153 +0,0 @@
-/****************************************************************
- * 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.james.blob.objectstorage;
-
-import java.net.URI;
-import java.util.Optional;
-import java.util.Properties;
-
-import org.jclouds.openstack.keystone.config.KeystoneProperties;
-import org.jclouds.openstack.swift.v1.reference.TempAuthHeaders;
-
-import com.google.common.base.Preconditions;
-
-public class ObjectStorageConfiguration {
-    public static class Builder {
-        private URI endpoint;
-        private Credentials credentials;
-        private Optional<Region> region;
-        private Optional<UserHeaderName> userHeaderName;
-        private Optional<PassHeaderName> passHeaderName;
-        private UserName userName;
-        private TenantName tenantName;
-
-        public Builder() {
-            region = Optional.empty();
-            userHeaderName = Optional.empty();
-            passHeaderName = Optional.empty();
-        }
-
-        public ObjectStorageConfiguration.Builder endpoint(URI endpoint) {
-            this.endpoint = endpoint;
-            return this;
-        }
-
-        public ObjectStorageConfiguration.Builder identity(SwiftIdentity 
swiftIdentity) {
-            this.tenantName = swiftIdentity.getTenant();
-            this.userName = swiftIdentity.getUserName();
-            return this;
-        }
-
-        public ObjectStorageConfiguration.Builder tenantName(TenantName 
tenantName) {
-            this.tenantName = tenantName;
-            return this;
-        }
-
-        public ObjectStorageConfiguration.Builder userName(UserName userName) {
-            this.userName = userName;
-            return this;
-        }
-
-
-        public ObjectStorageConfiguration.Builder credentials(Credentials 
credentials) {
-            this.credentials = credentials;
-            return this;
-        }
-
-        public ObjectStorageConfiguration.Builder region(Region region) {
-            this.region = Optional.of(region);
-            return this;
-        }
-
-        public ObjectStorageConfiguration.Builder 
tempAuthHeaderUserName(UserHeaderName tmpAuthHeaderUser) {
-            userHeaderName = Optional.of(tmpAuthHeaderUser);
-            return this;
-        }
-
-        public ObjectStorageConfiguration.Builder 
tempAuthHeaderPassName(PassHeaderName tmpAuthHeaderPass) {
-            passHeaderName = Optional.of(tmpAuthHeaderPass);
-            return this;
-        }
-
-        public ObjectStorageConfiguration build() {
-            Preconditions.checkState(endpoint != null);
-            Preconditions.checkState(tenantName != null);
-            Preconditions.checkState(userName != null);
-            Preconditions.checkState(credentials != null);
-            SwiftIdentity swiftIdentity = SwiftIdentity.of(tenantName, 
userName);
-            return new ObjectStorageConfiguration(
-                endpoint,
-                swiftIdentity,
-                credentials,
-                region,
-                userHeaderName,
-                passHeaderName);
-        }
-    }
-
-    private final URI endpoint;
-    private final Optional<Region> region;
-    private final SwiftIdentity swiftIdentity;
-    private final Credentials credentials;
-    private final Optional<UserHeaderName> userHeaderName;
-    private final Optional<PassHeaderName> passHeaderName;
-
-    private ObjectStorageConfiguration(URI endpoint,
-                                       SwiftIdentity swiftIdentity,
-                                       Credentials credentials,
-                                       Optional<Region> region,
-                                       Optional<UserHeaderName> userHeaderName,
-                                       Optional<PassHeaderName> 
passHeaderName) {
-        this.endpoint = endpoint;
-        this.region = region;
-        this.userHeaderName = userHeaderName;
-        this.passHeaderName = passHeaderName;
-        this.swiftIdentity = swiftIdentity;
-        this.credentials = credentials;
-    }
-
-    public URI getEndpoint() {
-        return endpoint;
-    }
-
-    public SwiftIdentity getSwiftIdentity() {
-        return swiftIdentity;
-    }
-
-    public Credentials getCredentials() {
-        return credentials;
-    }
-
-    public Properties getOverrides() {
-        Properties properties = new Properties();
-        properties.setProperty(KeystoneProperties.CREDENTIAL_TYPE, 
"tempAuthCredentials");
-        userHeaderName.ifPresent(tmpAuthHeaderUser ->
-            properties.setProperty(TempAuthHeaders.TEMP_AUTH_HEADER_USER, 
tmpAuthHeaderUser.value())
-        );
-        passHeaderName.ifPresent(tmpAuthHeaderPass ->
-            properties.setProperty(TempAuthHeaders.TEMP_AUTH_HEADER_PASS, 
tmpAuthHeaderPass.value())
-        );
-        return properties;
-    }
-
-    public Optional<Region> getRegion() {
-        return region;
-    }
-}

http://git-wip-us.apache.org/repos/asf/james-project/blob/d9aca59e/server/blob/blob-objectstorage/src/main/java/org/apache/james/blob/objectstorage/swift/SwiftTempAuthObjectStorage.java
----------------------------------------------------------------------
diff --git 
a/server/blob/blob-objectstorage/src/main/java/org/apache/james/blob/objectstorage/swift/SwiftTempAuthObjectStorage.java
 
b/server/blob/blob-objectstorage/src/main/java/org/apache/james/blob/objectstorage/swift/SwiftTempAuthObjectStorage.java
new file mode 100644
index 0000000..c03e9f9
--- /dev/null
+++ 
b/server/blob/blob-objectstorage/src/main/java/org/apache/james/blob/objectstorage/swift/SwiftTempAuthObjectStorage.java
@@ -0,0 +1,201 @@
+/****************************************************************
+ * 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.james.blob.objectstorage.swift;
+
+import java.net.URI;
+import java.util.Optional;
+import java.util.Properties;
+import java.util.function.Supplier;
+
+import org.apache.james.blob.objectstorage.Credentials;
+import org.apache.james.blob.objectstorage.ObjectStorageBlobsDAOBuilder;
+import org.apache.james.blob.objectstorage.PassHeaderName;
+import org.apache.james.blob.objectstorage.Region;
+import org.apache.james.blob.objectstorage.SwiftIdentity;
+import org.apache.james.blob.objectstorage.TenantName;
+import org.apache.james.blob.objectstorage.UserHeaderName;
+import org.apache.james.blob.objectstorage.UserName;
+import org.jclouds.ContextBuilder;
+import org.jclouds.blobstore.BlobStore;
+import org.jclouds.logging.slf4j.config.SLF4JLoggingModule;
+import org.jclouds.openstack.keystone.config.KeystoneProperties;
+import org.jclouds.openstack.swift.v1.blobstore.RegionScopedBlobStoreContext;
+import org.jclouds.openstack.swift.v1.reference.TempAuthHeaders;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableSet;
+import com.google.inject.Module;
+
+public class SwiftTempAuthObjectStorage {
+    private static final Iterable<Module> JCLOUDS_MODULES =
+        ImmutableSet.of(new SLF4JLoggingModule());
+
+    public static ObjectStorageBlobsDAOBuilder daoBuilder(Configuration 
testConfig) {
+        return new ObjectStorageBlobsDAOBuilder(new 
BlobStoreBuilder(testConfig));
+    }
+
+    public static Configuration.Builder configBuilder() {
+        return new Configuration.Builder();
+    }
+
+    private static class BlobStoreBuilder implements Supplier<BlobStore> {
+        private final Configuration testConfig;
+
+        private BlobStoreBuilder(Configuration testConfig) {
+            this.testConfig = testConfig;
+        }
+
+        public BlobStore get() {
+            RegionScopedBlobStoreContext blobStoreContext = contextBuilder()
+                .endpoint(testConfig.getEndpoint().toString())
+                .credentials(testConfig.getSwiftIdentity().asString(), 
testConfig.getCredentials().value())
+                .overrides(testConfig.getOverrides())
+                .modules(JCLOUDS_MODULES)
+                .buildView(RegionScopedBlobStoreContext.class);
+
+            return testConfig.getRegion()
+                .map(Region::value)
+                .map(blobStoreContext::getBlobStore)
+                .orElseGet(blobStoreContext::getBlobStore);
+        }
+
+        private ContextBuilder contextBuilder() {
+            return ContextBuilder.newBuilder("openstack-swift");
+        }
+    }
+
+    public static final class Configuration {
+        public static class Builder {
+            private URI endpoint;
+            private UserName userName;
+            private TenantName tenantName;
+            private Credentials credentials;
+            private Optional<Region> region;
+            private Optional<UserHeaderName> userHeaderName;
+            private Optional<PassHeaderName> passHeaderName;
+
+            private Builder() {
+                region = Optional.empty();
+                userHeaderName = Optional.empty();
+                passHeaderName = Optional.empty();
+            }
+
+            public Builder endpoint(URI endpoint) {
+                this.endpoint = endpoint;
+                return this;
+            }
+
+            public Builder identity(SwiftIdentity identity) {
+                this.tenantName = identity.getTenant();
+                this.userName = identity.getUserName();
+                return this;
+            }
+
+            public Builder tenantName(TenantName tenantName) {
+                this.tenantName = tenantName;
+                return this;
+            }
+
+            public Builder userName(UserName username) {
+                this.userName = username;
+                return this;
+            }
+
+            public Builder credentials(Credentials credentials) {
+                this.credentials = credentials;
+                return this;
+            }
+
+            public Builder region(Region region) {
+                this.region = Optional.of(region);
+                return this;
+            }
+
+            public Builder tempAuthHeaderUserName(UserHeaderName 
tmpAuthHeaderUser) {
+                userHeaderName = Optional.of(tmpAuthHeaderUser);
+                return this;
+            }
+
+            public Builder tempAuthHeaderPassName(PassHeaderName 
tmpAuthHeaderPass) {
+                passHeaderName = Optional.of(tmpAuthHeaderPass);
+                return this;
+            }
+
+            public Configuration build() {
+                Preconditions.checkState(endpoint != null);
+                Preconditions.checkState(tenantName != null);
+                Preconditions.checkState(userName != null);
+                Preconditions.checkState(credentials != null);
+                SwiftIdentity identity = SwiftIdentity.of(tenantName, 
userName);
+                return new Configuration(endpoint, identity, credentials, 
region,
+                    userHeaderName, passHeaderName);
+            }
+        }
+
+        private final URI endpoint;
+        private final SwiftIdentity identity;
+        private final Optional<Region> region;
+        private final Credentials credentials;
+        private final Optional<UserHeaderName> userHeaderName;
+        private final Optional<PassHeaderName> passHeaderName;
+
+        private Configuration(URI endpoint,
+                              SwiftIdentity identity,
+                              Credentials credentials,
+                              Optional<Region> region,
+                              Optional<UserHeaderName> userHeaderName,
+                              Optional<PassHeaderName> passHeaderName) {
+            this.endpoint = endpoint;
+            this.identity = identity;
+            this.region = region;
+            this.userHeaderName = userHeaderName;
+            this.passHeaderName = passHeaderName;
+            this.credentials = credentials;
+        }
+
+        public URI getEndpoint() {
+            return endpoint;
+        }
+
+        public SwiftIdentity getSwiftIdentity() {
+            return identity;
+        }
+
+        public Credentials getCredentials() {
+            return credentials;
+        }
+
+        public Properties getOverrides() {
+            Properties properties = new Properties();
+            properties.setProperty(KeystoneProperties.CREDENTIAL_TYPE, 
"tempAuthCredentials");
+            userHeaderName.ifPresent(tmpAuthHeaderUser ->
+                properties.setProperty(TempAuthHeaders.TEMP_AUTH_HEADER_USER, 
tmpAuthHeaderUser.value())
+            );
+            passHeaderName.ifPresent(tmpAuthHeaderPass ->
+                properties.setProperty(TempAuthHeaders.TEMP_AUTH_HEADER_PASS, 
tmpAuthHeaderPass.value())
+            );
+            return properties;
+        }
+
+        public Optional<Region> getRegion() {
+            return region;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/d9aca59e/server/blob/blob-objectstorage/src/test/java/org/apache/james/blob/objectstorage/DockerSwiftExtension.java
----------------------------------------------------------------------
diff --git 
a/server/blob/blob-objectstorage/src/test/java/org/apache/james/blob/objectstorage/DockerSwiftExtension.java
 
b/server/blob/blob-objectstorage/src/test/java/org/apache/james/blob/objectstorage/DockerSwiftExtension.java
index 9d77b13..48b2d3a 100644
--- 
a/server/blob/blob-objectstorage/src/test/java/org/apache/james/blob/objectstorage/DockerSwiftExtension.java
+++ 
b/server/blob/blob-objectstorage/src/test/java/org/apache/james/blob/objectstorage/DockerSwiftExtension.java
@@ -36,7 +36,7 @@ import org.testcontainers.containers.Container;
 import org.testcontainers.containers.GenericContainer;
 import org.testcontainers.containers.wait.strategy.WaitAllStrategy;
 
-class DockerSwiftExtension implements ParameterResolver, BeforeAllCallback,
+public class DockerSwiftExtension implements ParameterResolver, 
BeforeAllCallback,
     AfterAllCallback {
     private static final Logger LOGGER = 
LoggerFactory.getLogger(DockerSwiftExtension.class);
     private static final String SWIFT_DOCKER_IMAGE = 
"jeantil/openstack-keystone-swift:pike";

http://git-wip-us.apache.org/repos/asf/james-project/blob/d9aca59e/server/blob/blob-objectstorage/src/test/java/org/apache/james/blob/objectstorage/ObjectStorageBlobsDAOContract.java
----------------------------------------------------------------------
diff --git 
a/server/blob/blob-objectstorage/src/test/java/org/apache/james/blob/objectstorage/ObjectStorageBlobsDAOContract.java
 
b/server/blob/blob-objectstorage/src/test/java/org/apache/james/blob/objectstorage/ObjectStorageBlobsDAOContract.java
new file mode 100644
index 0000000..9b9d86d
--- /dev/null
+++ 
b/server/blob/blob-objectstorage/src/test/java/org/apache/james/blob/objectstorage/ObjectStorageBlobsDAOContract.java
@@ -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.james.blob.objectstorage;
+
+import org.jclouds.blobstore.BlobStore;
+
+public interface ObjectStorageBlobsDAOContract {
+    default BlobStore buildBlobStore(ObjectStorageBlobsDAOBuilder 
objectStorageBlobsDAOBuilder){
+        return objectStorageBlobsDAOBuilder.getSupplier().get();
+    }
+}

http://git-wip-us.apache.org/repos/asf/james-project/blob/d9aca59e/server/blob/blob-objectstorage/src/test/java/org/apache/james/blob/objectstorage/ObjectStorageBlobsDAOTest.java
----------------------------------------------------------------------
diff --git 
a/server/blob/blob-objectstorage/src/test/java/org/apache/james/blob/objectstorage/ObjectStorageBlobsDAOTest.java
 
b/server/blob/blob-objectstorage/src/test/java/org/apache/james/blob/objectstorage/ObjectStorageBlobsDAOTest.java
index ddef841..a712580 100644
--- 
a/server/blob/blob-objectstorage/src/test/java/org/apache/james/blob/objectstorage/ObjectStorageBlobsDAOTest.java
+++ 
b/server/blob/blob-objectstorage/src/test/java/org/apache/james/blob/objectstorage/ObjectStorageBlobsDAOTest.java
@@ -19,18 +19,13 @@
 
 package org.apache.james.blob.objectstorage;
 
-import java.net.URI;
-import java.util.Properties;
 import java.util.UUID;
 
 import org.apache.james.blob.api.BlobId;
 import org.apache.james.blob.api.BlobStore;
 import org.apache.james.blob.api.BlobStoreContract;
 import org.apache.james.blob.api.HashBlobId;
-import org.jclouds.ContextBuilder;
-import org.jclouds.blobstore.BlobStoreContext;
-import org.jclouds.openstack.keystone.config.KeystoneProperties;
-import org.jclouds.openstack.swift.v1.reference.TempAuthHeaders;
+import org.apache.james.blob.objectstorage.swift.SwiftTempAuthObjectStorage;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.extension.ExtendWith;
@@ -42,24 +37,25 @@ public class ObjectStorageBlobsDAOTest implements 
BlobStoreContract {
     private static final Credentials PASSWORD = Credentials.of("testing");
     private static final SwiftIdentity SWIFT_IDENTITY = 
SwiftIdentity.of(TENANT_NAME, USER_NAME);
 
-    private URI swiftEndpoint;
     private ContainerName containerName;
     private org.jclouds.blobstore.BlobStore blobStore;
+    private SwiftTempAuthObjectStorage.Configuration testConfig;
 
     @BeforeEach
     void setUp(DockerSwift dockerSwift) throws Exception {
-        final Properties overrides = new Properties();
-        overrides.setProperty(KeystoneProperties.CREDENTIAL_TYPE, 
"tempAuthCredentials");
-        overrides.setProperty(TempAuthHeaders.TEMP_AUTH_HEADER_USER, 
"X-Storage-User");
-        overrides.setProperty(TempAuthHeaders.TEMP_AUTH_HEADER_PASS, 
"X-Storage-Pass");
-        swiftEndpoint = dockerSwift.swiftEndpoint();
-        BlobStoreContext blobStoreContext = 
ContextBuilder.newBuilder("openstack-swift")
-            .endpoint(swiftEndpoint.toString())
-            .credentials(SWIFT_IDENTITY.asString(), PASSWORD.value())
-            .overrides(overrides)
-            .buildView(BlobStoreContext.class);
-        blobStore = blobStoreContext.getBlobStore();
         containerName = ContainerName.of(UUID.randomUUID().toString());
+        testConfig = SwiftTempAuthObjectStorage.configBuilder()
+            .endpoint(dockerSwift.swiftEndpoint())
+            .identity(SWIFT_IDENTITY)
+            .credentials(PASSWORD)
+            .tempAuthHeaderUserName(UserHeaderName.of("X-Storage-User"))
+            .tempAuthHeaderPassName(PassHeaderName.of("X-Storage-Pass"))
+            .build();
+        blobStore = ObjectStorageBlobsDAO
+            .builder(testConfig)
+            .container(containerName)
+            .blobIdFactory(new HashBlobId.Factory())
+            .getSupplier().get();
         blobStore.createContainerInLocation(null, containerName.value());
     }
 
@@ -71,15 +67,11 @@ public class ObjectStorageBlobsDAOTest implements 
BlobStoreContract {
 
     @Override
     public BlobStore testee() {
-        ObjectStorageConfiguration testConfig =
-            new ObjectStorageConfiguration.Builder()
-                .endpoint(swiftEndpoint)
-                .identity(SWIFT_IDENTITY)
-                .credentials(PASSWORD)
-                .tempAuthHeaderUserName(UserHeaderName.of("X-Storage-User"))
-                .tempAuthHeaderPassName(PassHeaderName.of("X-Storage-Pass"))
-                .build();
-        return new ObjectStorageBlobsDAO(containerName, new 
HashBlobId.Factory(), testConfig);
+        return ObjectStorageBlobsDAO
+            .builder(testConfig)
+            .container(containerName)
+            .blobIdFactory(new HashBlobId.Factory())
+            .build();
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/james-project/blob/d9aca59e/server/blob/blob-objectstorage/src/test/java/org/apache/james/blob/objectstorage/ObjectStorageConfigurationTest.java
----------------------------------------------------------------------
diff --git 
a/server/blob/blob-objectstorage/src/test/java/org/apache/james/blob/objectstorage/ObjectStorageConfigurationTest.java
 
b/server/blob/blob-objectstorage/src/test/java/org/apache/james/blob/objectstorage/ObjectStorageConfigurationTest.java
deleted file mode 100644
index 9c51394..0000000
--- 
a/server/blob/blob-objectstorage/src/test/java/org/apache/james/blob/objectstorage/ObjectStorageConfigurationTest.java
+++ /dev/null
@@ -1,112 +0,0 @@
-/****************************************************************
- * 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.james.blob.objectstorage;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-
-import java.net.URI;
-
-import org.junit.jupiter.api.Test;
-
-class ObjectStorageConfigurationTest {
-
-    private static final TenantName TENANT_NAME = TenantName.of("fake");
-    private static final UserName USER_NAME = UserName.of("fake");
-    private static URI ENDPOINT = URI.create("http://example.com";);
-    private static Credentials CREDENTIALS = Credentials.of("fake");
-    private static SwiftIdentity SWIFT_IDENTITY = 
SwiftIdentity.of(TenantName.of("fake"),
-        UserName.of("fake"));
-
-    @Test
-    void enpointIsMandatoryToBuildConfiguration() throws Exception {
-        ObjectStorageConfiguration.Builder builder = new 
ObjectStorageConfiguration.Builder();
-        builder
-            .tenantName(TENANT_NAME)
-            .userName(USER_NAME)
-            .credentials(CREDENTIALS);
-
-        
assertThatThrownBy(builder::build).isInstanceOf(IllegalStateException.class);
-    }
-
-    @Test
-    void tenantNameIsMandatoryToBuildConfiguration() throws Exception {
-        ObjectStorageConfiguration.Builder builder = new 
ObjectStorageConfiguration.Builder();
-        builder
-            .endpoint(ENDPOINT)
-            .userName(USER_NAME)
-            .credentials(CREDENTIALS);
-
-        
assertThatThrownBy(builder::build).isInstanceOf(IllegalStateException.class);
-    }
-
-    @Test
-    void userNameIsMandatoryToBuildConfiguration() throws Exception {
-        ObjectStorageConfiguration.Builder builder = new 
ObjectStorageConfiguration.Builder();
-        builder
-            .endpoint(ENDPOINT)
-            .tenantName(TENANT_NAME)
-            .credentials(CREDENTIALS);
-
-        
assertThatThrownBy(builder::build).isInstanceOf(IllegalStateException.class);
-    }
-
-    @Test
-    void credentialsIsMandatoryToBuildConfiguration() throws Exception {
-        ObjectStorageConfiguration.Builder builder = new 
ObjectStorageConfiguration.Builder();
-        builder
-            .endpoint(ENDPOINT)
-            .tenantName(TENANT_NAME)
-            .userName(USER_NAME);
-
-        
assertThatThrownBy(builder::build).isInstanceOf(IllegalStateException.class);
-    }
-
-    @Test
-    void configurationIsBuiltWhenAllMandatoryParamsAreProvided() throws 
Exception {
-        ObjectStorageConfiguration.Builder builder = new 
ObjectStorageConfiguration.Builder();
-        builder
-            .endpoint(ENDPOINT)
-            .tenantName(TENANT_NAME)
-            .userName(USER_NAME)
-            .credentials(CREDENTIALS);
-
-        ObjectStorageConfiguration build = builder.build();
-
-        assertThat(build.getEndpoint()).isEqualTo(ENDPOINT);
-        assertThat(build.getSwiftIdentity()).isEqualTo(SWIFT_IDENTITY);
-        assertThat(build.getCredentials()).isEqualTo(CREDENTIALS);
-    }
-
-    @Test
-    void identityCanReplaceTenantAndUserName() throws Exception {
-        ObjectStorageConfiguration.Builder builder = new 
ObjectStorageConfiguration.Builder();
-        builder
-            .endpoint(ENDPOINT)
-            .identity(SWIFT_IDENTITY)
-            .credentials(CREDENTIALS);
-
-        ObjectStorageConfiguration build = builder.build();
-
-        assertThat(build.getEndpoint()).isEqualTo(ENDPOINT);
-        assertThat(build.getSwiftIdentity()).isEqualTo(SWIFT_IDENTITY);
-        assertThat(build.getCredentials()).isEqualTo(CREDENTIALS);
-    }
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/james-project/blob/d9aca59e/server/blob/blob-objectstorage/src/test/java/org/apache/james/blob/objectstorage/swift/SwiftTempAuthObjectStorageBlobsDAOBuilderTest.java
----------------------------------------------------------------------
diff --git 
a/server/blob/blob-objectstorage/src/test/java/org/apache/james/blob/objectstorage/swift/SwiftTempAuthObjectStorageBlobsDAOBuilderTest.java
 
b/server/blob/blob-objectstorage/src/test/java/org/apache/james/blob/objectstorage/swift/SwiftTempAuthObjectStorageBlobsDAOBuilderTest.java
new file mode 100644
index 0000000..6ebdae4
--- /dev/null
+++ 
b/server/blob/blob-objectstorage/src/test/java/org/apache/james/blob/objectstorage/swift/SwiftTempAuthObjectStorageBlobsDAOBuilderTest.java
@@ -0,0 +1,104 @@
+/****************************************************************
+ * 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.james.blob.objectstorage.swift;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.james.blob.api.BlobId;
+import org.apache.james.blob.api.HashBlobId;
+import org.apache.james.blob.objectstorage.ContainerName;
+import org.apache.james.blob.objectstorage.DockerSwift;
+import org.apache.james.blob.objectstorage.DockerSwiftExtension;
+import org.apache.james.blob.objectstorage.ObjectStorageBlobsDAO;
+import org.apache.james.blob.objectstorage.ObjectStorageBlobsDAOBuilder;
+import org.apache.james.blob.objectstorage.ObjectStorageBlobsDAOContract;
+import org.jclouds.blobstore.BlobStore;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+@ExtendWith(DockerSwiftExtension.class)
+class SwiftTempAuthObjectStorageBlobsDAOBuilderTest implements 
ObjectStorageBlobsDAOContract {
+
+    private static final TenantName TENANT_NAME = TenantName.of("test");
+    private static final UserName USER_NAME = UserName.of("tester");
+    private static final Credentials PASSWORD = Credentials.of("testing");
+    private static final Identity SWIFT_IDENTITY = Identity.of(TENANT_NAME, 
USER_NAME);
+    private ContainerName containerName;
+    private URI endpoint;
+    private SwiftTempAuthObjectStorage.Configuration testConfig;
+
+    @BeforeEach
+    void setUp(DockerSwift dockerSwift) throws Exception {
+        containerName = ContainerName.of(UUID.randomUUID().toString());
+        endpoint = dockerSwift.swiftEndpoint();
+        testConfig = SwiftTempAuthObjectStorage.configBuilder()
+            .endpoint(endpoint)
+            .identity(SWIFT_IDENTITY)
+            .credentials(PASSWORD)
+            .tempAuthHeaderUserName(UserHeaderName.of("X-Storage-User"))
+            .tempAuthHeaderPassName(PassHeaderName.of("X-Storage-Pass"))
+            .build();
+    }
+
+    @Test
+    void containerNameIsMandatoryToBuildBlobsDAO() throws Exception {
+        ObjectStorageBlobsDAOBuilder builder = ObjectStorageBlobsDAO
+            .builder(testConfig)
+            .blobIdFactory(new HashBlobId.Factory());
+
+        
assertThatThrownBy(builder::build).isInstanceOf(IllegalStateException.class);
+    }
+
+    @Test
+    void blobIdFactoryIsMandatoryToBuildBlobsDAO() throws Exception {
+        ObjectStorageBlobsDAOBuilder builder = ObjectStorageBlobsDAO
+            .builder(testConfig)
+            .container(containerName);
+
+        
assertThatThrownBy(builder::build).isInstanceOf(IllegalStateException.class);
+    }
+
+    @Test
+    void builtBlobsDAOCanStoreAndRetrieve() throws Exception {
+        ObjectStorageBlobsDAOBuilder builder = ObjectStorageBlobsDAO
+            .builder(testConfig)
+            .container(containerName)
+            .blobIdFactory(new HashBlobId.Factory());
+
+        BlobStore blobStore = buildBlobStore(builder);
+        blobStore.createContainerInLocation(null, containerName.value());
+        ObjectStorageBlobsDAO dao = builder.build();
+        byte[] bytes = "content".getBytes(StandardCharsets.UTF_8);
+        CompletableFuture<BlobId> save = dao.save(bytes);
+        InputStream inputStream = save.thenApply(dao::read).get(10, 
TimeUnit.SECONDS);
+        assertThat(inputStream).hasSameContentAs(new 
ByteArrayInputStream(bytes));
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/james-project/blob/d9aca59e/server/blob/blob-objectstorage/src/test/java/org/apache/james/blob/objectstorage/swift/SwiftTempAuthObjectStorageConfigurationTest.java
----------------------------------------------------------------------
diff --git 
a/server/blob/blob-objectstorage/src/test/java/org/apache/james/blob/objectstorage/swift/SwiftTempAuthObjectStorageConfigurationTest.java
 
b/server/blob/blob-objectstorage/src/test/java/org/apache/james/blob/objectstorage/swift/SwiftTempAuthObjectStorageConfigurationTest.java
new file mode 100644
index 0000000..86c087a
--- /dev/null
+++ 
b/server/blob/blob-objectstorage/src/test/java/org/apache/james/blob/objectstorage/swift/SwiftTempAuthObjectStorageConfigurationTest.java
@@ -0,0 +1,116 @@
+/****************************************************************
+ * 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.james.blob.objectstorage.swift;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import java.net.URI;
+
+import org.apache.james.blob.objectstorage.Credentials;
+import org.apache.james.blob.objectstorage.SwiftIdentity;
+import org.apache.james.blob.objectstorage.TenantName;
+import org.apache.james.blob.objectstorage.UserName;
+import org.junit.jupiter.api.Test;
+
+class SwiftTempAuthObjectStorageConfigurationTest {
+
+    private static final TenantName TENANT_NAME = TenantName.of("fake");
+    private static final UserName USER_NAME = UserName.of("fake");
+    private static URI ENDPOINT = URI.create("http://example.com";);
+    private static Credentials CREDENTIALS = Credentials.of("fake");
+    private static SwiftIdentity SWIFT_IDENTITY = 
SwiftIdentity.of(TenantName.of("fake"),
+        UserName.of("fake"));
+
+    @Test
+    void enpointIsMandatoryToBuildConfiguration() throws Exception {
+        SwiftTempAuthObjectStorage.Configuration.Builder builder =
+            SwiftTempAuthObjectStorage.configBuilder()
+            .tenantName(TENANT_NAME)
+            .userName(USER_NAME)
+            .credentials(CREDENTIALS);
+
+        
assertThatThrownBy(builder::build).isInstanceOf(IllegalStateException.class);
+    }
+
+    @Test
+    void tenantNameIsMandatoryToBuildConfiguration() throws Exception {
+        SwiftTempAuthObjectStorage.Configuration.Builder builder =
+            SwiftTempAuthObjectStorage.configBuilder()
+            .endpoint(ENDPOINT)
+            .userName(USER_NAME)
+            .credentials(CREDENTIALS);
+
+        
assertThatThrownBy(builder::build).isInstanceOf(IllegalStateException.class);
+    }
+
+    @Test
+    void userNameIsMandatoryToBuildConfiguration() throws Exception {
+        SwiftTempAuthObjectStorage.Configuration.Builder builder =
+            SwiftTempAuthObjectStorage.configBuilder()
+            .endpoint(ENDPOINT)
+            .tenantName(TENANT_NAME)
+            .credentials(CREDENTIALS);
+
+        
assertThatThrownBy(builder::build).isInstanceOf(IllegalStateException.class);
+    }
+
+    @Test
+    void credentialsIsMandatoryToBuildConfiguration() throws Exception {
+        SwiftTempAuthObjectStorage.Configuration.Builder builder =
+            SwiftTempAuthObjectStorage.configBuilder()
+            .endpoint(ENDPOINT)
+            .tenantName(TENANT_NAME)
+            .userName(USER_NAME);
+
+        
assertThatThrownBy(builder::build).isInstanceOf(IllegalStateException.class);
+    }
+
+    @Test
+    void configurationIsBuiltWhenAllMandatoryParamsAreProvided() throws 
Exception {
+        SwiftTempAuthObjectStorage.Configuration.Builder builder =
+            SwiftTempAuthObjectStorage.configBuilder()
+            .endpoint(ENDPOINT)
+            .tenantName(TENANT_NAME)
+            .userName(USER_NAME)
+            .credentials(CREDENTIALS);
+
+        SwiftTempAuthObjectStorage.Configuration build = builder.build();
+
+        assertThat(build.getEndpoint()).isEqualTo(ENDPOINT);
+        assertThat(build.getSwiftIdentity()).isEqualTo(SWIFT_IDENTITY);
+        assertThat(build.getCredentials()).isEqualTo(CREDENTIALS);
+    }
+
+    @Test
+    void identityCanReplaceTenantAndUserName() throws Exception {
+        SwiftTempAuthObjectStorage.Configuration.Builder builder =
+            SwiftTempAuthObjectStorage.configBuilder()
+            .endpoint(ENDPOINT)
+            .identity(SWIFT_IDENTITY)
+            .credentials(CREDENTIALS);
+
+        SwiftTempAuthObjectStorage.Configuration build = builder.build();
+
+        assertThat(build.getEndpoint()).isEqualTo(ENDPOINT);
+        assertThat(build.getSwiftIdentity()).isEqualTo(SWIFT_IDENTITY);
+        assertThat(build.getCredentials()).isEqualTo(CREDENTIALS);
+    }
+}
\ No newline at end of file


---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscr...@james.apache.org
For additional commands, e-mail: server-dev-h...@james.apache.org

Reply via email to