OData Download API Support
diff --git a/agent/pom.xml b/agent/pom.xml
index c0ab77d..310c41a 100644
--- a/agent/pom.xml
+++ b/agent/pom.xml
@@ -86,6 +86,11 @@
         </dependency>
         <dependency>
             <groupId>org.apache.airavata</groupId>
+            <artifactId>mft-odata-transport</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.airavata</groupId>
             <artifactId>mft-common-clients</artifactId>
             <version>${project.version}</version>
         </dependency>
diff --git a/agent/src/main/java/org/apache/airavata/mft/agent/TransportMediator.java b/agent/src/main/java/org/apache/airavata/mft/agent/TransportMediator.java
index 0323437..0ff87d4 100644
--- a/agent/src/main/java/org/apache/airavata/mft/agent/TransportMediator.java
+++ b/agent/src/main/java/org/apache/airavata/mft/agent/TransportMediator.java
@@ -195,7 +195,7 @@
                 inConnector.complete();
                 outConnector.complete();
 
-                logger.info("Completed streaming ransfer for transfer {}", transferId);
+                logger.info("Completed streaming transfer for transfer {}", transferId);
 
             } else {
                 throw new Exception("No matching connector found to perform the transfer");
diff --git a/command-line/src/main/java/org/apache/airavata/mft/command/line/MainRunner.java b/command-line/src/main/java/org/apache/airavata/mft/command/line/MainRunner.java
index 072c988..b1002e9 100644
--- a/command-line/src/main/java/org/apache/airavata/mft/command/line/MainRunner.java
+++ b/command-line/src/main/java/org/apache/airavata/mft/command/line/MainRunner.java
@@ -1,5 +1,6 @@
 package org.apache.airavata.mft.command.line;
 
+import org.apache.airavata.mft.command.line.sub.odata.ODataSubCommand;
 import org.apache.airavata.mft.command.line.sub.s3.S3SubCommand;
 import org.apache.airavata.mft.command.line.sub.swift.SwiftSubCommand;
 import org.apache.airavata.mft.command.line.sub.transfer.TransferSubCommand;
@@ -8,7 +9,7 @@
 
 @Command(name = "checksum", mixinStandardHelpOptions = true, version = "checksum 4.0",
         description = "Prints the checksum (SHA-256 by default) of a file to STDOUT.",
-        subcommands = {S3SubCommand.class, TransferSubCommand.class, SwiftSubCommand.class})
+        subcommands = {S3SubCommand.class, TransferSubCommand.class, SwiftSubCommand.class, ODataSubCommand.class})
 class MainRunner {
 
     public static void main(String... args) {
diff --git a/command-line/src/main/java/org/apache/airavata/mft/command/line/sub/odata/ODataRemoteAddSubCommand.java b/command-line/src/main/java/org/apache/airavata/mft/command/line/sub/odata/ODataRemoteAddSubCommand.java
new file mode 100644
index 0000000..226d194
--- /dev/null
+++ b/command-line/src/main/java/org/apache/airavata/mft/command/line/sub/odata/ODataRemoteAddSubCommand.java
@@ -0,0 +1,76 @@
+/*
+ * 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.airavata.mft.command.line.sub.odata;
+
+import org.apache.airavata.mft.api.client.MFTApiClient;
+import org.apache.airavata.mft.common.AuthToken;
+import org.apache.airavata.mft.credential.stubs.odata.ODataSecret;
+import org.apache.airavata.mft.credential.stubs.odata.ODataSecretCreateRequest;
+import org.apache.airavata.mft.resource.stubs.odata.storage.ODataStorage;
+import org.apache.airavata.mft.resource.stubs.odata.storage.ODataStorageCreateRequest;
+import org.apache.airavata.mft.storage.stubs.storagesecret.StorageSecret;
+import org.apache.airavata.mft.storage.stubs.storagesecret.StorageSecretCreateRequest;
+import org.apache.airavata.mft.storage.stubs.storagesecret.StorageSecretServiceGrpc;
+import picocli.CommandLine;
+
+import java.util.concurrent.Callable;
+
+@CommandLine.Command(name = "add")
+public class ODataRemoteAddSubCommand implements Callable<Integer> {
+
+    @CommandLine.Option(names = {"-n", "--name"}, description = "Storage Name")
+    private String remoteName;
+
+    @CommandLine.Option(names = {"-U", "--url"}, description = "Base URL for OData Endpoint")
+    private String baseURL;
+
+    @CommandLine.Option(names = {"-u", "--user"}, description = "User Name")
+    private String userName;
+
+    @CommandLine.Option(names = {"-p", "--password"}, description = "Password")
+    private String password;
+
+
+    @Override
+    public Integer call() throws Exception {
+        AuthToken authToken = AuthToken.newBuilder().build();
+
+        MFTApiClient mftApiClient = MFTApiClient.MFTApiClientBuilder.newBuilder().build();
+
+        ODataSecret oDataSecret = mftApiClient.getSecretServiceClient().odata().createODataSecret(ODataSecretCreateRequest.newBuilder()
+                .setAuthzToken(authToken).setPassword(password).setUserName(userName).build());
+
+        System.out.println("Created the OData secret " + oDataSecret.getSecretId());
+
+        ODataStorage oDataStorage = mftApiClient.getStorageServiceClient().odata().createODataStorage(
+                ODataStorageCreateRequest.newBuilder().setName(remoteName).setBaseUrl(baseURL).build());
+
+        System.out.println("Created OData storage " + oDataStorage.getStorageId());
+
+        StorageSecretServiceGrpc.StorageSecretServiceBlockingStub storageSecretClient = mftApiClient.getStorageServiceClient().storageSecret();
+
+        StorageSecret storageSecret = storageSecretClient.createStorageSecret(StorageSecretCreateRequest.newBuilder()
+                .setStorageId(oDataStorage.getStorageId())
+                .setSecretId(oDataSecret.getSecretId())
+                .setType(StorageSecret.StorageType.ODATA).build());
+
+        System.out.println("Successfully added OData remote endpoint");
+
+        return 0;
+    }
+}
diff --git a/command-line/src/main/java/org/apache/airavata/mft/command/line/sub/odata/ODataRemoteSubCommand.java b/command-line/src/main/java/org/apache/airavata/mft/command/line/sub/odata/ODataRemoteSubCommand.java
new file mode 100644
index 0000000..3628a73
--- /dev/null
+++ b/command-line/src/main/java/org/apache/airavata/mft/command/line/sub/odata/ODataRemoteSubCommand.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.airavata.mft.command.line.sub.odata;
+
+import org.apache.airavata.mft.api.client.MFTApiClient;
+import org.apache.airavata.mft.command.line.CommandLineUtil;
+import org.apache.airavata.mft.resource.stubs.odata.storage.ODataStorage;
+import org.apache.airavata.mft.resource.stubs.odata.storage.ODataStorageListRequest;
+import org.apache.airavata.mft.resource.stubs.odata.storage.ODataStorageListResponse;
+import picocli.CommandLine;
+
+import java.util.List;
+
+@CommandLine.Command(name = "remote", subcommands = {ODataRemoteAddSubCommand.class})
+public class ODataRemoteSubCommand {
+
+    @CommandLine.Command(name = "list")
+    void listS3Resource() {
+        System.out.println("Listing S3 Resource");
+        MFTApiClient mftApiClient = MFTApiClient.MFTApiClientBuilder.newBuilder().build();
+
+        ODataStorageListResponse oDataStorageListResponse = mftApiClient.getStorageServiceClient().odata()
+                .listODataStorage(ODataStorageListRequest.newBuilder().setOffset(0).setLimit(10).build());
+
+        List<ODataStorage> storagesList = oDataStorageListResponse.getStoragesList();
+
+        int[] columnWidth = {40, 15, 55,};
+        String[][] content = new String[storagesList.size() + 1][3];
+        String[] headers = {"STORAGE ID", "NAME", "BASE URL"};
+        content[0] = headers;
+
+
+        for (int i = 1; i <= storagesList.size(); i ++) {
+            ODataStorage storage = storagesList.get(i - 1);
+            content[i][0] = storage.getStorageId();
+            content[i][1] = storage.getName();
+            content[i][2] = storage.getBaseUrl();
+        }
+
+        CommandLineUtil.printTable(columnWidth, content);
+    }
+}
diff --git a/command-line/src/main/java/org/apache/airavata/mft/command/line/sub/odata/ODataSubCommand.java b/command-line/src/main/java/org/apache/airavata/mft/command/line/sub/odata/ODataSubCommand.java
new file mode 100644
index 0000000..c2b4ae9
--- /dev/null
+++ b/command-line/src/main/java/org/apache/airavata/mft/command/line/sub/odata/ODataSubCommand.java
@@ -0,0 +1,26 @@
+/*
+ * 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.airavata.mft.command.line.sub.odata;
+
+import org.apache.airavata.mft.command.line.sub.swift.SwiftRemoteSubCommand;
+import picocli.CommandLine;
+
+@CommandLine.Command(name = "odata", description = "Manage OData resources and credentials",
+        subcommands = {ODataRemoteSubCommand.class})
+public class ODataSubCommand {
+}
diff --git a/core/src/main/java/org/apache/airavata/mft/core/ConnectorResolver.java b/core/src/main/java/org/apache/airavata/mft/core/ConnectorResolver.java
index a3910a5..ef5792c 100644
--- a/core/src/main/java/org/apache/airavata/mft/core/ConnectorResolver.java
+++ b/core/src/main/java/org/apache/airavata/mft/core/ConnectorResolver.java
@@ -36,6 +36,9 @@
             case "S3":
                 className = "org.apache.airavata.mft.transport.s3.S3IncomingConnector";
                 break;
+            case "ODATA":
+                className = "org.apache.airavata.mft.transport.odata.ODataIncomingConnector";
+                break;
         }
 
         if (className != null) {
@@ -53,6 +56,10 @@
             case "SCP":
                 className = "org.apache.airavata.mft.transport.scp.SCPOutgoingConnector";
                 break;
+            case "S3":
+                className = "org.apache.airavata.mft.transport.s3.S3OutgoingStreamingConnector";
+                break;
+
         }
 
         if (className != null) {
diff --git a/core/src/main/java/org/apache/airavata/mft/core/MetadataCollectorResolver.java b/core/src/main/java/org/apache/airavata/mft/core/MetadataCollectorResolver.java
index 9ab820a..f6ac7c8 100644
--- a/core/src/main/java/org/apache/airavata/mft/core/MetadataCollectorResolver.java
+++ b/core/src/main/java/org/apache/airavata/mft/core/MetadataCollectorResolver.java
@@ -54,6 +54,9 @@
             case "SWIFT":
                 className = "org.apache.airavata.mft.transport.swift.SwiftMetadataCollector";
                 break;
+            case "ODATA":
+                className = "org.apache.airavata.mft.transport.odata.ODataMetadataCollector";
+                break;
         }
 
         if (className != null) {
diff --git a/pom.xml b/pom.xml
index dc00c6c..b225416 100755
--- a/pom.xml
+++ b/pom.xml
@@ -150,6 +150,7 @@
         <mariadb.jdbc>2.5.1</mariadb.jdbc>
         <jclouds.version>2.5.0</jclouds.version>
         <commons.io.version>2.6</commons.io.version>
+        <apache.http.client.version>4.5.13</apache.http.client.version>
     </properties>
 
 </project>
diff --git a/services/resource-service/client/src/main/java/org/apache/airavata/mft/resource/client/StorageServiceClient.java b/services/resource-service/client/src/main/java/org/apache/airavata/mft/resource/client/StorageServiceClient.java
index 73ea91a..2e31c9d 100644
--- a/services/resource-service/client/src/main/java/org/apache/airavata/mft/resource/client/StorageServiceClient.java
+++ b/services/resource-service/client/src/main/java/org/apache/airavata/mft/resource/client/StorageServiceClient.java
@@ -7,6 +7,7 @@
 import org.apache.airavata.mft.resource.service.ftp.FTPStorageServiceGrpc;
 import org.apache.airavata.mft.resource.service.gcs.GCSStorageServiceGrpc;
 import org.apache.airavata.mft.resource.service.local.LocalStorageServiceGrpc;
+import org.apache.airavata.mft.resource.service.odata.ODataStorageServiceGrpc;
 import org.apache.airavata.mft.resource.service.s3.S3StorageServiceGrpc;
 import org.apache.airavata.mft.resource.service.scp.SCPStorageServiceGrpc;
 import org.apache.airavata.mft.resource.service.swift.SwiftStorageServiceGrpc;
@@ -63,6 +64,10 @@
         return SwiftStorageServiceGrpc.newBlockingStub(channel);
     }
 
+    public ODataStorageServiceGrpc.ODataStorageServiceBlockingStub odata() {
+        return ODataStorageServiceGrpc.newBlockingStub(channel);
+    }
+
     @Override
     public void close() throws IOException {
 
diff --git a/services/resource-service/server/src/main/java/org/apache/airavata/mft/resource/server/backend/ResourceBackend.java b/services/resource-service/server/src/main/java/org/apache/airavata/mft/resource/server/backend/ResourceBackend.java
index 91d7d9f..51a9408 100644
--- a/services/resource-service/server/src/main/java/org/apache/airavata/mft/resource/server/backend/ResourceBackend.java
+++ b/services/resource-service/server/src/main/java/org/apache/airavata/mft/resource/server/backend/ResourceBackend.java
@@ -24,6 +24,7 @@
 import org.apache.airavata.mft.resource.stubs.ftp.storage.*;
 import org.apache.airavata.mft.resource.stubs.gcs.storage.*;
 import org.apache.airavata.mft.resource.stubs.local.storage.*;
+import org.apache.airavata.mft.resource.stubs.odata.storage.*;
 import org.apache.airavata.mft.resource.stubs.s3.storage.*;
 import org.apache.airavata.mft.resource.stubs.scp.storage.*;
 import org.apache.airavata.mft.resource.stubs.swift.storage.*;
@@ -100,4 +101,10 @@
     SwiftStorage createSwiftStorage(SwiftStorageCreateRequest request) throws Exception;
     boolean updateSwiftStorage(SwiftStorageUpdateRequest request) throws Exception;
     boolean deleteSwiftStorage(SwiftStorageDeleteRequest request) throws Exception;
+
+    public ODataStorageListResponse listODataStorage(ODataStorageListRequest request) throws Exception;
+    Optional<ODataStorage> getODataStorage(ODataStorageGetRequest request) throws Exception;
+    ODataStorage createODataStorage(ODataStorageCreateRequest request) throws Exception;
+    boolean updateODataStorage(ODataStorageUpdateRequest request) throws Exception;
+    boolean deleteODataStorage(ODataStorageDeleteRequest request) throws Exception;
 }
diff --git a/services/resource-service/server/src/main/java/org/apache/airavata/mft/resource/server/backend/file/FileBasedResourceBackend.java b/services/resource-service/server/src/main/java/org/apache/airavata/mft/resource/server/backend/file/FileBasedResourceBackend.java
index 3201a20..3517d8a 100644
--- a/services/resource-service/server/src/main/java/org/apache/airavata/mft/resource/server/backend/file/FileBasedResourceBackend.java
+++ b/services/resource-service/server/src/main/java/org/apache/airavata/mft/resource/server/backend/file/FileBasedResourceBackend.java
@@ -25,6 +25,7 @@
 import org.apache.airavata.mft.resource.stubs.ftp.storage.*;
 import org.apache.airavata.mft.resource.stubs.gcs.storage.*;
 import org.apache.airavata.mft.resource.stubs.local.storage.*;
+import org.apache.airavata.mft.resource.stubs.odata.storage.*;
 import org.apache.airavata.mft.resource.stubs.s3.storage.*;
 import org.apache.airavata.mft.resource.stubs.scp.storage.*;
 import org.apache.airavata.mft.resource.stubs.swift.storage.*;
@@ -590,4 +591,29 @@
     public boolean deleteSwiftStorage(SwiftStorageDeleteRequest request) throws Exception {
         throw new UnsupportedOperationException("Operation is not supported in backend");
     }
+
+    @Override
+    public ODataStorageListResponse listODataStorage(ODataStorageListRequest request) throws Exception {
+        throw new UnsupportedOperationException("Operation is not supported in backend");
+    }
+
+    @Override
+    public Optional<ODataStorage> getODataStorage(ODataStorageGetRequest request) throws Exception {
+        throw new UnsupportedOperationException("Operation is not supported in backend");
+    }
+
+    @Override
+    public ODataStorage createODataStorage(ODataStorageCreateRequest request) throws Exception {
+        throw new UnsupportedOperationException("Operation is not supported in backend");
+    }
+
+    @Override
+    public boolean updateODataStorage(ODataStorageUpdateRequest request) throws Exception {
+        throw new UnsupportedOperationException("Operation is not supported in backend");
+    }
+
+    @Override
+    public boolean deleteODataStorage(ODataStorageDeleteRequest request) throws Exception {
+        throw new UnsupportedOperationException("Operation is not supported in backend");
+    }
 }
diff --git a/services/resource-service/server/src/main/java/org/apache/airavata/mft/resource/server/backend/sql/SQLResourceBackend.java b/services/resource-service/server/src/main/java/org/apache/airavata/mft/resource/server/backend/sql/SQLResourceBackend.java
index d2b28c4..26d0e90 100644
--- a/services/resource-service/server/src/main/java/org/apache/airavata/mft/resource/server/backend/sql/SQLResourceBackend.java
+++ b/services/resource-service/server/src/main/java/org/apache/airavata/mft/resource/server/backend/sql/SQLResourceBackend.java
@@ -27,6 +27,7 @@
 import org.apache.airavata.mft.resource.stubs.ftp.storage.*;
 import org.apache.airavata.mft.resource.stubs.gcs.storage.*;
 import org.apache.airavata.mft.resource.stubs.local.storage.*;
+import org.apache.airavata.mft.resource.stubs.odata.storage.*;
 import org.apache.airavata.mft.resource.stubs.s3.storage.*;
 import org.apache.airavata.mft.resource.stubs.scp.storage.*;
 import org.apache.airavata.mft.resource.stubs.swift.storage.*;
@@ -66,6 +67,9 @@
     @Autowired
     private StorageSecretRepository resourceSecretRepository;
 
+    @Autowired
+    private ODataStorageRepository odataStorageRepository;
+
     private DozerBeanMapper mapper = new DozerBeanMapper();
 
     @Override
@@ -146,6 +150,12 @@
                 builder.setSwiftStorage(swiftStorage.orElseThrow(() -> new Exception("Could not find a Swift storage with id "
                         + resourceEty.getStorageId() + " for resource " + resourceEty.getResourceId())));
                 break;
+            case ODATA:
+                Optional<ODataStorage> odataStorage = getODataStorage(ODataStorageGetRequest.newBuilder()
+                        .setStorageId(resourceEty.getStorageId()).build());
+                builder.setOdataStorage(odataStorage.orElseThrow(() -> new Exception("Could not find a OData storage with id "
+                        + resourceEty.getStorageId() + " for resource " + resourceEty.getResourceId())));
+                break;
         }
 
         return builder.build();
@@ -493,4 +503,37 @@
         resourceRepository.deleteByStorageIdAndStorageType(request.getStorageId(), GenericResourceEntity.StorageType.SWIFT);
         return true;
     }
+
+    @Override
+    public ODataStorageListResponse listODataStorage(ODataStorageListRequest request) throws Exception {
+        ODataStorageListResponse.Builder respBuilder = ODataStorageListResponse.newBuilder();
+        List<ODataStorageEntity> all = odataStorageRepository.findAll(PageRequest.of(request.getOffset(), request.getLimit()));
+        all.forEach(ety -> respBuilder.addStorages(mapper.map(ety, ODataStorage.newBuilder().getClass())));
+        return respBuilder.build();
+    }
+
+    @Override
+    public Optional<ODataStorage> getODataStorage(ODataStorageGetRequest request) throws Exception {
+        Optional<ODataStorageEntity> entity = odataStorageRepository.findByStorageId(request.getStorageId());
+        return entity.map(e -> mapper.map(e, ODataStorage.newBuilder().getClass()).build());
+    }
+
+    @Override
+    public ODataStorage createODataStorage(ODataStorageCreateRequest request) throws Exception {
+        ODataStorageEntity savedEntity = odataStorageRepository.save(mapper.map(request, ODataStorageEntity.class));
+        return mapper.map(savedEntity, ODataStorage.newBuilder().getClass()).build();
+    }
+
+    @Override
+    public boolean updateODataStorage(ODataStorageUpdateRequest request) throws Exception {
+        odataStorageRepository.save(mapper.map(request, ODataStorageEntity.class));
+        return true;
+    }
+
+    @Override
+    public boolean deleteODataStorage(ODataStorageDeleteRequest request) throws Exception {
+        odataStorageRepository.deleteById(request.getStorageId());
+        resourceRepository.deleteByStorageIdAndStorageType(request.getStorageId(), GenericResourceEntity.StorageType.SWIFT);
+        return true;
+    }
 }
diff --git a/services/resource-service/server/src/main/java/org/apache/airavata/mft/resource/server/backend/sql/entity/GenericResourceEntity.java b/services/resource-service/server/src/main/java/org/apache/airavata/mft/resource/server/backend/sql/entity/GenericResourceEntity.java
index 4930cf3..3e17ba2 100644
--- a/services/resource-service/server/src/main/java/org/apache/airavata/mft/resource/server/backend/sql/entity/GenericResourceEntity.java
+++ b/services/resource-service/server/src/main/java/org/apache/airavata/mft/resource/server/backend/sql/entity/GenericResourceEntity.java
@@ -15,7 +15,7 @@
     }
 
     public enum StorageType {
-        S3, SCP, LOCAL, FTP, BOX, DROPBOX, GCS, AZURE, SWIFT;
+        S3, SCP, LOCAL, FTP, BOX, DROPBOX, GCS, AZURE, SWIFT, ODATA;
     }
 
     @Id
diff --git a/services/resource-service/server/src/main/java/org/apache/airavata/mft/resource/server/backend/sql/entity/ODataStorageEntity.java b/services/resource-service/server/src/main/java/org/apache/airavata/mft/resource/server/backend/sql/entity/ODataStorageEntity.java
new file mode 100644
index 0000000..c48b41f
--- /dev/null
+++ b/services/resource-service/server/src/main/java/org/apache/airavata/mft/resource/server/backend/sql/entity/ODataStorageEntity.java
@@ -0,0 +1,65 @@
+/*
+ * 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.airavata.mft.resource.server.backend.sql.entity;
+
+import org.hibernate.annotations.GenericGenerator;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+
+@Entity
+public class ODataStorageEntity {
+
+    @Id
+    @Column(name = "S3_STORAGE_ID")
+    @GeneratedValue(generator = "uuid")
+    @GenericGenerator(name = "uuid", strategy = "uuid2")
+    private String storageId;
+
+    @Column(name = "BASE_URL")
+    private String baseUrl;
+
+    @Column(name = "STORAGE_NAME")
+    private String name;
+
+    public String getStorageId() {
+        return storageId;
+    }
+
+    public void setStorageId(String storageId) {
+        this.storageId = storageId;
+    }
+
+    public String getBaseUrl() {
+        return baseUrl;
+    }
+
+    public void setBaseUrl(String baseUrl) {
+        this.baseUrl = baseUrl;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+}
diff --git a/services/resource-service/server/src/main/java/org/apache/airavata/mft/resource/server/backend/sql/repository/ODataStorageRepository.java b/services/resource-service/server/src/main/java/org/apache/airavata/mft/resource/server/backend/sql/repository/ODataStorageRepository.java
new file mode 100644
index 0000000..2a08627
--- /dev/null
+++ b/services/resource-service/server/src/main/java/org/apache/airavata/mft/resource/server/backend/sql/repository/ODataStorageRepository.java
@@ -0,0 +1,30 @@
+/*
+ * 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.airavata.mft.resource.server.backend.sql.repository;
+
+import org.apache.airavata.mft.resource.server.backend.sql.entity.ODataStorageEntity;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.repository.CrudRepository;
+
+import java.util.List;
+import java.util.Optional;
+
+public interface ODataStorageRepository extends CrudRepository<ODataStorageEntity, String> {
+    Optional<ODataStorageEntity> findByStorageId(String storageId);
+    List<ODataStorageEntity> findAll(Pageable pageable);
+}
diff --git a/services/resource-service/server/src/main/java/org/apache/airavata/mft/resource/server/handler/ODataServiceHandler.java b/services/resource-service/server/src/main/java/org/apache/airavata/mft/resource/server/handler/ODataServiceHandler.java
new file mode 100644
index 0000000..391a9b2
--- /dev/null
+++ b/services/resource-service/server/src/main/java/org/apache/airavata/mft/resource/server/handler/ODataServiceHandler.java
@@ -0,0 +1,120 @@
+/*
+ * 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.airavata.mft.resource.server.handler;
+
+import io.grpc.Status;
+import io.grpc.stub.StreamObserver;
+import org.apache.airavata.mft.resource.server.backend.ResourceBackend;
+import org.apache.airavata.mft.resource.service.odata.ODataStorageServiceGrpc;
+import org.apache.airavata.mft.resource.stubs.odata.storage.*;
+import org.lognet.springboot.grpc.GRpcService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+
+@GRpcService
+public class ODataServiceHandler extends ODataStorageServiceGrpc.ODataStorageServiceImplBase {
+
+    private static final Logger logger = LoggerFactory.getLogger(ODataServiceHandler.class);
+
+    @Autowired
+    private ResourceBackend backend;
+
+    @Override
+    public void listODataStorage(ODataStorageListRequest request, StreamObserver<ODataStorageListResponse> responseObserver) {
+        try {
+            ODataStorageListResponse response = this.backend.listODataStorage(request);
+            responseObserver.onNext(response);
+            responseObserver.onCompleted();
+        } catch (Exception e) {
+            logger.error("Failed in retrieving OData storage list", e);
+
+            responseObserver.onError(Status.INTERNAL.withCause(e)
+                    .withDescription("Failed in retrieving OData storage list")
+                    .asRuntimeException());
+        }
+    }
+
+    @Override
+    public void getODataStorage(ODataStorageGetRequest request, StreamObserver<ODataStorage> responseObserver) {
+        try {
+            this.backend.getODataStorage(request).ifPresentOrElse(resource -> {
+                responseObserver.onNext(resource);
+                responseObserver.onCompleted();
+            }, () -> {
+                responseObserver.onError(Status.INTERNAL
+                        .withDescription("No OData storage with id " + request.getStorageId())
+                        .asRuntimeException());
+            });
+        } catch (Exception e) {
+            logger.error("Failed in retrieving OData storage with id {}", request.getStorageId(), e);
+
+            responseObserver.onError(Status.INTERNAL.withCause(e)
+                    .withDescription("Failed in retrieving OData storage with id " + request.getStorageId())
+                    .asRuntimeException());
+        }
+    }
+
+    @Override
+    public void createODataStorage(ODataStorageCreateRequest request, StreamObserver<ODataStorage> responseObserver) {
+        try {
+            responseObserver.onNext(this.backend.createODataStorage(request));
+            responseObserver.onCompleted();
+        } catch (Exception e) {
+            logger.error("Failed in creating the OData storage", e);
+
+            responseObserver.onError(Status.INTERNAL.withCause(e)
+                    .withDescription("Failed in creating the OData storage")
+                    .asRuntimeException());
+        }
+    }
+
+    @Override
+    public void updateODataStorage(ODataStorageUpdateRequest request, StreamObserver<ODataStorageUpdateResponse> responseObserver) {
+        try {
+            this.backend.updateODataStorage(request);
+            responseObserver.onNext(ODataStorageUpdateResponse.newBuilder().setStorageId(request.getStorageId()).build());
+            responseObserver.onCompleted();
+        } catch (Exception e) {
+            logger.error("Failed in updating the OData storage {}", request.getStorageId(), e);
+
+            responseObserver.onError(Status.INTERNAL.withCause(e)
+                    .withDescription("Failed in updating the OData storage with id " + request.getStorageId())
+                    .asRuntimeException());
+        }
+    }
+
+    @Override
+    public void deleteODataStorage(ODataStorageDeleteRequest request, StreamObserver<ODataStorageDeleteResponse> responseObserver) {
+        try {
+            boolean res = this.backend.deleteODataStorage(request);
+            if (res) {
+                responseObserver.onNext(ODataStorageDeleteResponse.newBuilder().setStatus(true).build());
+                responseObserver.onCompleted();
+            } else {
+                responseObserver.onError(new Exception("Failed to delete OData storage with id " + request.getStorageId()));
+            }
+        } catch (Exception e) {
+            logger.error("Failed in deleting the OData storage {}", request.getStorageId(), e);
+
+            responseObserver.onError(Status.INTERNAL.withCause(e)
+                    .withDescription("Failed in deleting the OData storage with id " + request.getStorageId())
+                    .asRuntimeException());
+        }
+    }
+}
diff --git a/services/resource-service/server/src/main/resources/application.properties b/services/resource-service/server/src/main/resources/application.properties
index 7cee619..fb3d6e8 100644
--- a/services/resource-service/server/src/main/resources/application.properties
+++ b/services/resource-service/server/src/main/resources/application.properties
@@ -15,6 +15,7 @@
 # limitations under the License.
 #
 
+spring.datasource.url=jdbc:h2:~/mft_resourcedb;DB_CLOSE_ON_EXIT=FALSE
 server.port=8089
 grpc.port=7002
 grpc.enableReflection=true
diff --git a/services/resource-service/server/src/main/resources/distribution/conf/application.properties b/services/resource-service/server/src/main/resources/distribution/conf/application.properties
index 4f02479..992025a 100644
--- a/services/resource-service/server/src/main/resources/distribution/conf/application.properties
+++ b/services/resource-service/server/src/main/resources/distribution/conf/application.properties
@@ -14,6 +14,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
+spring.datasource.url=jdbc:h2:~/mft_resourcedb;DB_CLOSE_ON_EXIT=FALSE
 
 server.port=8089
 grpc.port=7002
diff --git a/services/resource-service/stub/src/main/proto/odata/ODataStorage.proto b/services/resource-service/stub/src/main/proto/odata/ODataStorage.proto
new file mode 100644
index 0000000..a987319
--- /dev/null
+++ b/services/resource-service/stub/src/main/proto/odata/ODataStorage.proto
@@ -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.
+ */
+
+syntax = "proto3";
+
+option java_multiple_files = true;
+package org.apache.airavata.mft.resource.stubs.odata.storage;
+
+message ODataStorage {
+    string storageId = 1;
+    string baseUrl = 2;
+    string name = 3;
+}
+
+message ODataStorageListRequest {
+    int32 offset = 1;
+    int32 limit = 2;
+}
+
+message ODataStorageListResponse {
+    repeated ODataStorage storages = 1;
+}
+
+message ODataStorageGetRequest {
+    string storageId = 1;
+}
+
+message ODataStorageCreateRequest {
+    string baseUrl = 1;
+    string storageId = 2;
+    string name = 3;
+}
+
+message ODataStorageUpdateRequest {
+    string storageId = 1;
+    string baseUrl = 2;
+    string name = 3;
+}
+
+message ODataStorageUpdateResponse {
+    string storageId = 1;
+}
+
+message ODataStorageDeleteRequest {
+    string storageId = 1;
+}
+
+message ODataStorageDeleteResponse {
+    bool status = 1;
+}
\ No newline at end of file
diff --git a/services/resource-service/stub/src/main/proto/odata/ODataStorageService.proto b/services/resource-service/stub/src/main/proto/odata/ODataStorageService.proto
new file mode 100644
index 0000000..ec0060c
--- /dev/null
+++ b/services/resource-service/stub/src/main/proto/odata/ODataStorageService.proto
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto3";
+
+option java_multiple_files = true;
+package org.apache.airavata.mft.resource.service.odata;
+
+import "odata/ODataStorage.proto";
+
+service ODataStorageService {
+    
+    // Storage
+
+    rpc listODataStorage (org.apache.airavata.mft.resource.stubs.odata.storage.ODataStorageListRequest) returns (org.apache.airavata.mft.resource.stubs.odata.storage.ODataStorageListResponse);
+
+    rpc getODataStorage (org.apache.airavata.mft.resource.stubs.odata.storage.ODataStorageGetRequest) returns
+    (org.apache.airavata.mft.resource.stubs.odata.storage.ODataStorage);
+
+    rpc createODataStorage (org.apache.airavata.mft.resource.stubs.odata.storage.ODataStorageCreateRequest) returns
+    (org.apache.airavata.mft.resource.stubs.odata.storage.ODataStorage);
+
+    rpc updateODataStorage (org.apache.airavata.mft.resource.stubs.odata.storage.ODataStorageUpdateRequest) returns
+    (org.apache.airavata.mft.resource.stubs.odata.storage.ODataStorageUpdateResponse);
+
+    rpc deleteODataStorage (org.apache.airavata.mft.resource.stubs.odata.storage.ODataStorageDeleteRequest) returns
+    (org.apache.airavata.mft.resource.stubs.odata.storage.ODataStorageDeleteResponse);
+}
\ No newline at end of file
diff --git a/services/resource-service/stub/src/main/proto/resource/ResourceService.proto b/services/resource-service/stub/src/main/proto/resource/ResourceService.proto
index c79f1e3..48def08 100644
--- a/services/resource-service/stub/src/main/proto/resource/ResourceService.proto
+++ b/services/resource-service/stub/src/main/proto/resource/ResourceService.proto
@@ -29,6 +29,7 @@
 import "s3/S3Storage.proto";
 import "scp/SCPStorage.proto";
 import "swift/SwiftStorage.proto";
+import "odata/ODataStorage.proto";
 import "CredCommon.proto";
 
 message FileResource {
@@ -58,6 +59,7 @@
         org.apache.airavata.mft.resource.stubs.box.storage.BoxStorage boxStorage = 10;
         org.apache.airavata.mft.resource.stubs.azure.storage.AzureStorage azureStorage = 11;
         org.apache.airavata.mft.resource.stubs.swift.storage.SwiftStorage swiftStorage = 12;
+        org.apache.airavata.mft.resource.stubs.odata.storage.ODataStorage odataStorage = 13;
     }
 }
 
@@ -83,6 +85,7 @@
         GCS = 6;
         AZURE = 7;
         SWIFT = 8;
+        ODATA = 9;
     }
 
     StorageType storageType = 5;
diff --git a/services/resource-service/stub/src/main/proto/resourcesecretmap/StorageSecretMap.proto b/services/resource-service/stub/src/main/proto/resourcesecretmap/StorageSecretMap.proto
index 7be8ae0..81599bd 100644
--- a/services/resource-service/stub/src/main/proto/resourcesecretmap/StorageSecretMap.proto
+++ b/services/resource-service/stub/src/main/proto/resourcesecretmap/StorageSecretMap.proto
@@ -36,6 +36,7 @@
         GCS = 6;
         AZURE = 7;
         SWIFT = 8;
+        ODATA = 9;
     }
     StorageType type = 4;
 }
diff --git a/services/secret-service/client/src/main/java/org/apache/airavata/mft/secret/client/SecretServiceClient.java b/services/secret-service/client/src/main/java/org/apache/airavata/mft/secret/client/SecretServiceClient.java
index a93a590..e686d54 100644
--- a/services/secret-service/client/src/main/java/org/apache/airavata/mft/secret/client/SecretServiceClient.java
+++ b/services/secret-service/client/src/main/java/org/apache/airavata/mft/secret/client/SecretServiceClient.java
@@ -23,6 +23,7 @@
 import org.apache.airavata.mft.credential.service.dropbox.DropboxSecretServiceGrpc;
 import org.apache.airavata.mft.credential.service.ftp.FTPSecretServiceGrpc;
 import org.apache.airavata.mft.credential.service.gcs.GCSSecretServiceGrpc;
+import org.apache.airavata.mft.credential.service.odata.ODataSecretServiceGrpc;
 import org.apache.airavata.mft.credential.service.s3.S3SecretServiceGrpc;
 import org.apache.airavata.mft.credential.service.scp.SCPSecretServiceGrpc;
 import org.apache.airavata.mft.credential.service.swift.SwiftSecretServiceGrpc;
@@ -70,6 +71,10 @@
         return SwiftSecretServiceGrpc.newBlockingStub(channel);
     }
 
+    public ODataSecretServiceGrpc.ODataSecretServiceBlockingStub odata() {
+        return ODataSecretServiceGrpc.newBlockingStub(channel);
+    }
+
     @Override
     public void close() throws IOException {
         this.channel.shutdown();
diff --git a/services/secret-service/server/src/main/java/org/apache/airavata/mft/secret/server/backend/SecretBackend.java b/services/secret-service/server/src/main/java/org/apache/airavata/mft/secret/server/backend/SecretBackend.java
index 7da5e14..2fbdb31 100644
--- a/services/secret-service/server/src/main/java/org/apache/airavata/mft/secret/server/backend/SecretBackend.java
+++ b/services/secret-service/server/src/main/java/org/apache/airavata/mft/secret/server/backend/SecretBackend.java
@@ -22,6 +22,7 @@
 import org.apache.airavata.mft.credential.stubs.dropbox.*;
 import org.apache.airavata.mft.credential.stubs.ftp.*;
 import org.apache.airavata.mft.credential.stubs.gcs.*;
+import org.apache.airavata.mft.credential.stubs.odata.*;
 import org.apache.airavata.mft.credential.stubs.s3.*;
 import org.apache.airavata.mft.credential.stubs.scp.*;
 import org.apache.airavata.mft.credential.stubs.swift.*;
@@ -72,4 +73,9 @@
     SwiftSecret createSwiftSecret(SwiftSecretCreateRequest request) throws Exception;
     boolean updateSwiftSecret(SwiftSecretUpdateRequest request) throws Exception;
     boolean deleteSwiftSecret(SwiftSecretDeleteRequest request) throws Exception;
+
+    Optional<ODataSecret> getODataSecret(ODataSecretGetRequest request) throws Exception;
+    ODataSecret createODataSecret(ODataSecretCreateRequest request) throws Exception;
+    boolean updateODataSecret(ODataSecretUpdateRequest request) throws Exception;
+    boolean deleteODataSecret(ODataSecretDeleteRequest request) throws Exception;
 }
diff --git a/services/secret-service/server/src/main/java/org/apache/airavata/mft/secret/server/backend/file/FileBasedSecretBackend.java b/services/secret-service/server/src/main/java/org/apache/airavata/mft/secret/server/backend/file/FileBasedSecretBackend.java
index dc3dcc0..501d95e 100644
--- a/services/secret-service/server/src/main/java/org/apache/airavata/mft/secret/server/backend/file/FileBasedSecretBackend.java
+++ b/services/secret-service/server/src/main/java/org/apache/airavata/mft/secret/server/backend/file/FileBasedSecretBackend.java
@@ -22,6 +22,7 @@
 import org.apache.airavata.mft.credential.stubs.dropbox.*;
 import org.apache.airavata.mft.credential.stubs.ftp.*;
 import org.apache.airavata.mft.credential.stubs.gcs.*;
+import org.apache.airavata.mft.credential.stubs.odata.*;
 import org.apache.airavata.mft.credential.stubs.s3.*;
 import org.apache.airavata.mft.credential.stubs.scp.*;
 import org.apache.airavata.mft.credential.stubs.swift.*;
@@ -362,4 +363,24 @@
         throw new UnsupportedOperationException("Operation is not supported in backend");
     }
 
+    @Override
+    public Optional<ODataSecret> getODataSecret(ODataSecretGetRequest request) throws Exception {
+        throw new UnsupportedOperationException("Operation is not supported in backend");
+    }
+
+    @Override
+    public ODataSecret createODataSecret(ODataSecretCreateRequest request) throws Exception {
+        throw new UnsupportedOperationException("Operation is not supported in backend");
+    }
+
+    @Override
+    public boolean updateODataSecret(ODataSecretUpdateRequest request) throws Exception {
+        throw new UnsupportedOperationException("Operation is not supported in backend");
+    }
+
+    @Override
+    public boolean deleteODataSecret(ODataSecretDeleteRequest request) throws Exception {
+        throw new UnsupportedOperationException("Operation is not supported in backend");
+    }
+
 }
diff --git a/services/secret-service/server/src/main/java/org/apache/airavata/mft/secret/server/backend/sql/SQLSecretBackend.java b/services/secret-service/server/src/main/java/org/apache/airavata/mft/secret/server/backend/sql/SQLSecretBackend.java
index 4cea50d..fff66d8 100644
--- a/services/secret-service/server/src/main/java/org/apache/airavata/mft/secret/server/backend/sql/SQLSecretBackend.java
+++ b/services/secret-service/server/src/main/java/org/apache/airavata/mft/secret/server/backend/sql/SQLSecretBackend.java
@@ -22,17 +22,20 @@
 import org.apache.airavata.mft.credential.stubs.dropbox.*;
 import org.apache.airavata.mft.credential.stubs.ftp.*;
 import org.apache.airavata.mft.credential.stubs.gcs.*;
+import org.apache.airavata.mft.credential.stubs.odata.*;
 import org.apache.airavata.mft.credential.stubs.s3.*;
 import org.apache.airavata.mft.credential.stubs.scp.*;
 import org.apache.airavata.mft.credential.stubs.swift.*;
 import org.apache.airavata.mft.secret.server.backend.SecretBackend;
 import org.apache.airavata.mft.secret.server.backend.sql.entity.FTPSecretEntity;
+import org.apache.airavata.mft.secret.server.backend.sql.entity.ODataSecretEntity;
 import org.apache.airavata.mft.secret.server.backend.sql.entity.S3SecretEntity;
 import org.apache.airavata.mft.secret.server.backend.sql.entity.SCPSecretEntity;
 import org.apache.airavata.mft.secret.server.backend.sql.entity.swift.SwiftAuthCredentialSecretEntity;
 import org.apache.airavata.mft.secret.server.backend.sql.entity.swift.SwiftPasswordSecretEntity;
 import org.apache.airavata.mft.secret.server.backend.sql.entity.swift.SwiftSecretEntity;
 import org.apache.airavata.mft.secret.server.backend.sql.repository.FTPSecretRepository;
+import org.apache.airavata.mft.secret.server.backend.sql.repository.ODataSecretRepository;
 import org.apache.airavata.mft.secret.server.backend.sql.repository.S3SecretRepository;
 import org.apache.airavata.mft.secret.server.backend.sql.repository.SCPSecretRepository;
 import org.apache.airavata.mft.secret.server.backend.sql.repository.swift.SwiftAuthCredentialSecretRepository;
@@ -67,6 +70,9 @@
     @Autowired
     private SwiftAuthCredentialSecretRepository swiftAuthCredentialSecretRepository;
 
+    @Autowired
+    private ODataSecretRepository odataSecretRepository;
+
     private DozerBeanMapper mapper = new DozerBeanMapper();
 
     @Override
@@ -333,4 +339,28 @@
         ftpSecretRepository.deleteById(request.getSecretId());
         return true;
     }
+
+    @Override
+    public Optional<ODataSecret> getODataSecret(ODataSecretGetRequest request) throws Exception {
+        Optional<ODataSecretEntity> secretEty = odataSecretRepository.findBySecretId(request.getSecretId());
+        return secretEty.map(odataSecretEntity -> mapper.map(odataSecretEntity, ODataSecret.newBuilder().getClass()).build());
+    }
+
+    @Override
+    public ODataSecret createODataSecret(ODataSecretCreateRequest request) throws Exception {
+        ODataSecretEntity savedEntity = odataSecretRepository.save(mapper.map(request, ODataSecretEntity.class));
+        return mapper.map(savedEntity, ODataSecret.newBuilder().getClass()).build();
+    }
+
+    @Override
+    public boolean updateODataSecret(ODataSecretUpdateRequest request) throws Exception {
+        odataSecretRepository.save(mapper.map(request, ODataSecretEntity.class));
+        return true;
+    }
+
+    @Override
+    public boolean deleteODataSecret(ODataSecretDeleteRequest request) throws Exception {
+        odataSecretRepository.deleteById(request.getSecretId());
+        return true;
+    }
 }
diff --git a/services/secret-service/server/src/main/java/org/apache/airavata/mft/secret/server/backend/sql/entity/ODataSecretEntity.java b/services/secret-service/server/src/main/java/org/apache/airavata/mft/secret/server/backend/sql/entity/ODataSecretEntity.java
new file mode 100644
index 0000000..ed11155
--- /dev/null
+++ b/services/secret-service/server/src/main/java/org/apache/airavata/mft/secret/server/backend/sql/entity/ODataSecretEntity.java
@@ -0,0 +1,65 @@
+/*
+ * 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.airavata.mft.secret.server.backend.sql.entity;
+
+import org.hibernate.annotations.GenericGenerator;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+
+@Entity
+public class ODataSecretEntity {
+
+    @Id
+    @Column(name = "SECRET_ID")
+    @GeneratedValue(generator = "uuid")
+    @GenericGenerator(name = "uuid", strategy = "uuid2")
+    private String secretId;
+
+    @Column(name = "USER_NAME")
+    private String userName;
+
+    @Column(name = "PASSWORD")
+    private String password;
+
+    public String getSecretId() {
+        return secretId;
+    }
+
+    public void setSecretId(String secretId) {
+        this.secretId = secretId;
+    }
+
+    public String getUserName() {
+        return userName;
+    }
+
+    public void setUserName(String userName) {
+        this.userName = userName;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+}
diff --git a/services/secret-service/server/src/main/java/org/apache/airavata/mft/secret/server/backend/sql/repository/ODataSecretRepository.java b/services/secret-service/server/src/main/java/org/apache/airavata/mft/secret/server/backend/sql/repository/ODataSecretRepository.java
new file mode 100644
index 0000000..566a24c
--- /dev/null
+++ b/services/secret-service/server/src/main/java/org/apache/airavata/mft/secret/server/backend/sql/repository/ODataSecretRepository.java
@@ -0,0 +1,27 @@
+/*
+ * 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.airavata.mft.secret.server.backend.sql.repository;
+
+import org.apache.airavata.mft.secret.server.backend.sql.entity.ODataSecretEntity;
+import org.springframework.data.repository.CrudRepository;
+
+import java.util.Optional;
+
+public interface ODataSecretRepository extends CrudRepository<ODataSecretEntity, String> {
+    Optional<ODataSecretEntity> findBySecretId(String secretId);
+}
diff --git a/services/secret-service/server/src/main/java/org/apache/airavata/mft/secret/server/backend/sql/repository/S3SecretRepository.java b/services/secret-service/server/src/main/java/org/apache/airavata/mft/secret/server/backend/sql/repository/S3SecretRepository.java
index 016b790..56fbc2b 100644
--- a/services/secret-service/server/src/main/java/org/apache/airavata/mft/secret/server/backend/sql/repository/S3SecretRepository.java
+++ b/services/secret-service/server/src/main/java/org/apache/airavata/mft/secret/server/backend/sql/repository/S3SecretRepository.java
@@ -7,5 +7,5 @@
 import java.util.Optional;
 
 public interface S3SecretRepository extends CrudRepository<S3SecretEntity, String> {
-    Optional<S3SecretEntity> findBySecretId(String resourceId);
+    Optional<S3SecretEntity> findBySecretId(String secretId);
 }
diff --git a/services/secret-service/server/src/main/java/org/apache/airavata/mft/secret/server/handler/ODataServiceHandler.java b/services/secret-service/server/src/main/java/org/apache/airavata/mft/secret/server/handler/ODataServiceHandler.java
new file mode 100644
index 0000000..14fcfbe
--- /dev/null
+++ b/services/secret-service/server/src/main/java/org/apache/airavata/mft/secret/server/handler/ODataServiceHandler.java
@@ -0,0 +1,99 @@
+/*
+ * 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.airavata.mft.secret.server.handler;
+
+import io.grpc.Status;
+import io.grpc.stub.StreamObserver;
+import org.apache.airavata.mft.credential.service.odata.ODataSecretServiceGrpc;
+import org.apache.airavata.mft.credential.stubs.odata.*;
+import org.apache.airavata.mft.secret.server.backend.SecretBackend;
+import org.lognet.springboot.grpc.GRpcService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+
+@GRpcService
+public class ODataServiceHandler extends ODataSecretServiceGrpc.ODataSecretServiceImplBase {
+
+    private static final Logger logger = LoggerFactory.getLogger(ODataServiceHandler.class);
+
+    @Autowired
+    private SecretBackend backend;
+    @Override
+    public void getODataSecret(ODataSecretGetRequest request, StreamObserver<ODataSecret> responseObserver) {
+        try {
+            this.backend.getODataSecret(request).ifPresentOrElse(secret -> {
+                responseObserver.onNext(secret);
+                responseObserver.onCompleted();
+            }, () -> {
+                responseObserver.onError(Status.INTERNAL
+                        .withDescription("No OData Secret with id " + request.getSecretId())
+                        .asRuntimeException());
+            });
+
+        } catch (Exception e) {
+            logger.error("Error in retrieving OData Secret with id " + request.getSecretId(), e);
+            responseObserver.onError(Status.INTERNAL.withCause(e)
+                    .withDescription("Error in retrieving OData Secret with id " + request.getSecretId())
+                    .asRuntimeException());
+        }
+        super.getODataSecret(request, responseObserver);
+    }
+
+    @Override
+    public void createODataSecret(ODataSecretCreateRequest request, StreamObserver<ODataSecret> responseObserver) {
+        try {
+            ODataSecret odataSecret = this.backend.createODataSecret(request);
+            responseObserver.onNext(odataSecret);
+            responseObserver.onCompleted();
+        } catch (Exception e) {
+            logger.error("Error in creating OData Secret", e);
+            responseObserver.onError(Status.INTERNAL.withCause(e)
+                    .withDescription("Error in creating OData Secret")
+                    .asRuntimeException());
+        }
+    }
+
+    @Override
+    public void updateODataSecret(ODataSecretUpdateRequest request, StreamObserver<ODataSecretUpdateResponse> responseObserver) {
+        try {
+            this.backend.updateODataSecret(request);
+            responseObserver.onNext(ODataSecretUpdateResponse.newBuilder().setSecretId(request.getSecretId()).build());
+            responseObserver.onCompleted();
+        } catch (Exception e) {
+            logger.error("Error in updating OData Secret with id {}", request.getSecretId(), e);
+            responseObserver.onError(Status.INTERNAL.withCause(e)
+                    .withDescription("Error in updating OData Secret with id " + request.getSecretId())
+                    .asRuntimeException());
+        }
+    }
+
+    @Override
+    public void deleteODataSecret(ODataSecretDeleteRequest request, StreamObserver<ODataSecretDeleteResponse> responseObserver) {
+        try {
+            this.backend.deleteODataSecret(request);
+            responseObserver.onNext(ODataSecretDeleteResponse.newBuilder().setStatus(true).build());
+            responseObserver.onCompleted();
+        } catch (Exception e) {
+            logger.error("Error in deleting OData Secret with id {}", request.getSecretId(), e);
+            responseObserver.onError(Status.INTERNAL.withCause(e)
+                    .withDescription("Error in deleting OData Secret with id " + request.getSecretId())
+                    .asRuntimeException());
+        }
+    }
+}
diff --git a/services/secret-service/server/src/main/resources/application.properties b/services/secret-service/server/src/main/resources/application.properties
index 2c8df2e..4f483bf 100644
--- a/services/secret-service/server/src/main/resources/application.properties
+++ b/services/secret-service/server/src/main/resources/application.properties
@@ -14,6 +14,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
+spring.datasource.url=jdbc:h2:~/mft_secretdb;DB_CLOSE_ON_EXIT=FALSE
 
 server.port=8081
 grpc.port=7003
diff --git a/services/secret-service/server/src/main/resources/distribution/conf/application.properties b/services/secret-service/server/src/main/resources/distribution/conf/application.properties
index 10ebcc5..2737054 100644
--- a/services/secret-service/server/src/main/resources/distribution/conf/application.properties
+++ b/services/secret-service/server/src/main/resources/distribution/conf/application.properties
@@ -15,6 +15,8 @@
 # limitations under the License.
 #
 
+spring.datasource.url=jdbc:h2:~/mft_secretdb;DB_CLOSE_ON_EXIT=FALSE
+
 server.port=8081
 grpc.port=7003
 
diff --git a/services/secret-service/stub/src/main/proto/odata/ODataCredential.proto b/services/secret-service/stub/src/main/proto/odata/ODataCredential.proto
new file mode 100644
index 0000000..5f5ca96
--- /dev/null
+++ b/services/secret-service/stub/src/main/proto/odata/ODataCredential.proto
@@ -0,0 +1,60 @@
+/*
+ * 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.
+ */
+
+syntax = "proto3";
+
+option java_multiple_files = true;
+package org.apache.airavata.mft.credential.stubs.odata;
+
+import "CredCommon.proto";
+
+message ODataSecret {
+    string secretId = 1;
+    string userName = 2;
+    string password = 3;
+}
+
+message ODataSecretGetRequest {
+    string secretId = 1;
+    org.apache.airavata.mft.common.AuthToken authzToken = 2;
+}
+
+message ODataSecretCreateRequest {
+    string userName = 1;
+    string password = 2;
+    org.apache.airavata.mft.common.AuthToken authzToken = 3;
+}
+
+message ODataSecretUpdateRequest {
+    string secretId = 1;
+    string userName = 2;
+    string password = 3;
+    org.apache.airavata.mft.common.AuthToken authzToken = 4;
+}
+
+message ODataSecretUpdateResponse {
+    string secretId = 1;
+}
+
+message ODataSecretDeleteRequest {
+    string secretId = 1;
+    org.apache.airavata.mft.common.AuthToken authzToken = 2;
+}
+
+message ODataSecretDeleteResponse {
+    bool status = 1;
+}
diff --git a/services/secret-service/stub/src/main/proto/odata/ODataSecretService.proto b/services/secret-service/stub/src/main/proto/odata/ODataSecretService.proto
new file mode 100644
index 0000000..268f0f0
--- /dev/null
+++ b/services/secret-service/stub/src/main/proto/odata/ODataSecretService.proto
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+
+syntax = "proto3";
+
+option java_multiple_files = true;
+package org.apache.airavata.mft.credential.service.odata;
+
+import "odata/ODataCredential.proto";
+
+service ODataSecretService {
+    rpc getODataSecret (org.apache.airavata.mft.credential.stubs.odata.ODataSecretGetRequest) returns
+                                                        (org.apache.airavata.mft.credential.stubs.odata.ODataSecret);
+
+    rpc createODataSecret (org.apache.airavata.mft.credential.stubs.odata.ODataSecretCreateRequest) returns
+                                                        (org.apache.airavata.mft.credential.stubs.odata.ODataSecret);
+
+    rpc updateODataSecret (org.apache.airavata.mft.credential.stubs.odata.ODataSecretUpdateRequest) returns
+    (org.apache.airavata.mft.credential.stubs.odata.ODataSecretUpdateResponse);
+
+    rpc deleteODataSecret (org.apache.airavata.mft.credential.stubs.odata.ODataSecretDeleteRequest) returns
+    (org.apache.airavata.mft.credential.stubs.odata.ODataSecretDeleteResponse);
+}
\ No newline at end of file
diff --git a/transport/odata-transport/pom.xml b/transport/odata-transport/pom.xml
new file mode 100644
index 0000000..706b214
--- /dev/null
+++ b/transport/odata-transport/pom.xml
@@ -0,0 +1,53 @@
+<?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>mft-transport</artifactId>
+        <groupId>org.apache.airavata</groupId>
+        <version>0.01-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>mft-odata-transport</artifactId>
+
+    <properties>
+        <maven.compiler.source>11</maven.compiler.source>
+        <maven.compiler.target>11</maven.compiler.target>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.airavata</groupId>
+            <artifactId>mft-core</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+            <version>${apache.http.client.version}</version>
+        </dependency>
+    </dependencies>
+</project>
\ No newline at end of file
diff --git a/transport/odata-transport/src/main/java/org/apache/airavata/mft/transport/odata/ODataIncomingConnector.java b/transport/odata-transport/src/main/java/org/apache/airavata/mft/transport/odata/ODataIncomingConnector.java
new file mode 100644
index 0000000..3626885
--- /dev/null
+++ b/transport/odata-transport/src/main/java/org/apache/airavata/mft/transport/odata/ODataIncomingConnector.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.airavata.mft.transport.odata;
+
+import org.apache.airavata.mft.core.api.ConnectorConfig;
+import org.apache.airavata.mft.core.api.IncomingStreamingConnector;
+import org.apache.airavata.mft.credential.stubs.odata.ODataSecret;
+import org.apache.airavata.mft.credential.stubs.odata.ODataSecretGetRequest;
+import org.apache.airavata.mft.resource.client.ResourceServiceClient;
+import org.apache.airavata.mft.resource.client.ResourceServiceClientBuilder;
+import org.apache.airavata.mft.resource.stubs.common.GenericResource;
+import org.apache.airavata.mft.resource.stubs.common.GenericResourceGetRequest;
+import org.apache.airavata.mft.resource.stubs.odata.storage.ODataStorage;
+import org.apache.airavata.mft.secret.client.SecretServiceClient;
+import org.apache.airavata.mft.secret.client.SecretServiceClientBuilder;
+import org.apache.http.HttpEntity;
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.UsernamePasswordCredentials;
+import org.apache.http.client.CredentialsProvider;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.BasicCredentialsProvider;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.InputStream;
+
+public class ODataIncomingConnector implements IncomingStreamingConnector {
+
+    private static final Logger logger = LoggerFactory.getLogger(ODataIncomingConnector.class);
+
+    private CloseableHttpResponse response;
+    CloseableHttpClient client;
+
+    private GenericResource resource;
+    private ODataStorage odataStorage;
+
+    @Override
+    public void init(ConnectorConfig cc) throws Exception {
+        try (ResourceServiceClient resourceClient = ResourceServiceClientBuilder
+                .buildClient(cc.getResourceServiceHost(), cc.getResourceServicePort())) {
+
+            resource = resourceClient.get().getGenericResource(GenericResourceGetRequest.newBuilder()
+                    .setAuthzToken(cc.getAuthToken())
+                    .setResourceId(cc.getResourceId()).build());
+        }
+
+        if (resource.getStorageCase() != GenericResource.StorageCase.ODATASTORAGE) {
+            logger.error("Invalid storage type {} specified for resource {}", resource.getStorageCase(), cc.getResourceId());
+            throw new Exception("Invalid storage type specified for resource " + cc.getResourceId());
+        }
+
+        odataStorage = resource.getOdataStorage();
+
+        try (SecretServiceClient secretClient = SecretServiceClientBuilder.buildClient(
+                cc.getSecretServiceHost(), cc.getSecretServicePort())) {
+
+            ODataSecret oDataSecret = secretClient.odata().getODataSecret(ODataSecretGetRequest.newBuilder()
+                    .setAuthzToken(cc.getAuthToken())
+                    .setSecretId(cc.getCredentialToken()).build());
+
+            CredentialsProvider provider = new BasicCredentialsProvider();
+            UsernamePasswordCredentials credentials
+                    = new UsernamePasswordCredentials(oDataSecret.getUserName(), oDataSecret.getPassword());
+            provider.setCredentials(AuthScope.ANY, credentials);
+
+            client = HttpClientBuilder.create().setDefaultCredentialsProvider(provider).build();
+        }
+    }
+
+    @Override
+    public InputStream fetchInputStream() throws Exception {
+
+        HttpGet httpGet = new HttpGet(odataStorage.getBaseUrl() +
+                "/Products('" + resource.getFile().getResourcePath() +"')/$value");
+        response = client.execute(httpGet);
+        int statusCode = response.getStatusLine().getStatusCode();
+        logger.info("Received status code {} for resource path {}", statusCode, resource.getFile().getResourcePath());
+
+        HttpEntity entity = response.getEntity();
+        return entity.getContent();
+    }
+
+    @Override
+    public InputStream fetchInputStream(String childPath) throws Exception {
+        throw new UnsupportedOperationException("No child path structures available for OData");
+    }
+
+    @Override
+    public void complete() throws Exception {
+        if (response != null) {
+            response.close();
+        }
+
+        if (client != null) {
+            client.close();
+        }
+    }
+}
diff --git a/transport/odata-transport/src/main/java/org/apache/airavata/mft/transport/odata/ODataMetadataCollector.java b/transport/odata-transport/src/main/java/org/apache/airavata/mft/transport/odata/ODataMetadataCollector.java
new file mode 100644
index 0000000..63c8cca
--- /dev/null
+++ b/transport/odata-transport/src/main/java/org/apache/airavata/mft/transport/odata/ODataMetadataCollector.java
@@ -0,0 +1,203 @@
+/*
+ * 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.airavata.mft.transport.odata;
+
+import org.apache.airavata.mft.common.AuthToken;
+import org.apache.airavata.mft.core.DirectoryResourceMetadata;
+import org.apache.airavata.mft.core.FileResourceMetadata;
+import org.apache.airavata.mft.core.api.MetadataCollector;
+import org.apache.airavata.mft.credential.stubs.odata.ODataSecret;
+import org.apache.airavata.mft.credential.stubs.odata.ODataSecretGetRequest;
+import org.apache.airavata.mft.resource.client.ResourceServiceClient;
+import org.apache.airavata.mft.resource.client.ResourceServiceClientBuilder;
+import org.apache.airavata.mft.resource.stubs.common.GenericResource;
+import org.apache.airavata.mft.resource.stubs.common.GenericResourceGetRequest;
+import org.apache.airavata.mft.resource.stubs.odata.storage.ODataStorage;
+import org.apache.airavata.mft.secret.client.SecretServiceClient;
+import org.apache.airavata.mft.secret.client.SecretServiceClientBuilder;
+import org.apache.http.HttpEntity;
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.UsernamePasswordCredentials;
+import org.apache.http.client.CredentialsProvider;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.BasicCredentialsProvider;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.util.EntityUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import java.io.StringReader;
+import java.time.Instant;
+import java.util.Optional;
+
+public class ODataMetadataCollector implements MetadataCollector {
+
+    private static final Logger logger = LoggerFactory.getLogger(ODataMetadataCollector.class);
+
+    private String resourceServiceHost;
+    private int resourceServicePort;
+    private String secretServiceHost;
+    private int secretServicePort;
+
+    @Override
+    public void init(String resourceServiceHost, int resourceServicePort, String secretServiceHost, int secretServicePort) {
+        this.resourceServiceHost = resourceServiceHost;
+        this.resourceServicePort = resourceServicePort;
+        this.secretServiceHost = secretServiceHost;
+        this.secretServicePort = secretServicePort;
+    }
+
+    private CloseableHttpClient getHttpClient(ODataSecret oDataSecret) {
+        CredentialsProvider provider = new BasicCredentialsProvider();
+        UsernamePasswordCredentials credentials
+                = new UsernamePasswordCredentials(oDataSecret.getUserName(), oDataSecret.getPassword());
+        provider.setCredentials(AuthScope.ANY, credentials);
+
+        return HttpClientBuilder.create().setDefaultCredentialsProvider(provider).build();
+    }
+
+    @Override
+    public FileResourceMetadata getFileResourceMetadata(AuthToken authZToken, String resourceId, String credentialToken) throws Exception {
+        return findFileResourceMetadata(authZToken, resourceId, credentialToken)
+                .orElseThrow(() -> new Exception("Could not find a file resource entry for resource id " + resourceId));
+    }
+
+    @Override
+    public FileResourceMetadata getFileResourceMetadata(AuthToken authZToken, String parentResourceId, String resourcePath, String credentialToken) throws Exception {
+        throw new UnsupportedOperationException("OData does not have hierarchical structures");
+    }
+
+    @Override
+    public DirectoryResourceMetadata getDirectoryResourceMetadata(AuthToken authZToken, String resourceId, String credentialToken) throws Exception {
+        throw new UnsupportedOperationException("OData does not have directory structures");
+    }
+
+    @Override
+    public DirectoryResourceMetadata getDirectoryResourceMetadata(AuthToken authZToken, String parentResourceId, String resourcePath, String credentialToken) throws Exception {
+        throw new UnsupportedOperationException("OData does not have directory structures");
+    }
+
+    @Override
+    public Boolean isAvailable(AuthToken authZToken, String resourceId, String credentialToken) throws Exception {
+        return findFileResourceMetadata(authZToken, resourceId, credentialToken).isPresent();
+    }
+
+    @Override
+    public Boolean isAvailable(AuthToken authToken, String parentResourceId, String resourcePath, String credentialToken) throws Exception {
+        throw new UnsupportedOperationException("OData does not have directory structures");
+    }
+
+    private Optional<FileResourceMetadata> findFileResourceMetadata(AuthToken authZToken, String resourceId, String credentialToken) throws Exception {
+        ResourceServiceClient resourceClient = ResourceServiceClientBuilder.buildClient(resourceServiceHost, resourceServicePort);
+        GenericResource resource = resourceClient.get().getGenericResource(GenericResourceGetRequest.newBuilder().setResourceId(resourceId).build());
+
+        if (resource.getStorageCase() != GenericResource.StorageCase.ODATASTORAGE) {
+            logger.error("Invalid storage type {} specified for resource {}", resource.getStorageCase(), resourceId);
+            throw new Exception("Invalid storage type specified for resource " + resourceId);
+        }
+
+        ODataStorage odataStorage = resource.getOdataStorage();
+
+        SecretServiceClient secretClient = SecretServiceClientBuilder.buildClient(secretServiceHost, secretServicePort);
+        ODataSecret oDataSecret = secretClient.odata().getODataSecret(
+                ODataSecretGetRequest.newBuilder().setSecretId(credentialToken).build());
+
+        try (CloseableHttpClient httpClient = getHttpClient(oDataSecret)) {
+
+            HttpGet httpGet = new HttpGet(odataStorage.getBaseUrl() +
+                    "/Products('" + resource.getFile().getResourcePath() +"')");
+
+            try (CloseableHttpResponse response = httpClient.execute(httpGet)) {
+                int statusCode = response.getStatusLine().getStatusCode();
+                if (statusCode != 200) {
+                    logger.error("Failed while invoking get product information endpoint. Got code {}", statusCode);
+                    throw new Exception("Failed while invoking get product information endpoint. Got code " + statusCode);
+                }
+                HttpEntity entity = response.getEntity();
+                String responseString = EntityUtils.toString(entity, "UTF-8");
+                return parseXML(responseString);
+            }
+        }
+    }
+
+    private Optional<FileResourceMetadata> parseXML(String xmlBody) {
+
+        try {
+            DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
+            DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
+            Document doc = dBuilder.parse(new InputSource(new StringReader(xmlBody)));
+            doc.getDocumentElement().normalize();
+
+            System.out.print("Root element: ");
+            System.out.println(doc.getDocumentElement().getNodeName());
+            NodeList properties = doc.getElementsByTagName("m:properties");
+
+            if (properties.getLength() == 1) {
+
+                FileResourceMetadata.Builder builder = FileResourceMetadata.Builder.newBuilder();
+
+                Node propertyNode = properties.item(0);
+                NodeList childNodes = propertyNode.getChildNodes();
+                for (int i = 0; i < childNodes.getLength(); i++) {
+                    Node item = childNodes.item(i);
+                    switch (item.getNodeName()) {
+                        case "d:ContentLength":
+                            builder.withResourceSize(Long.parseLong(item.getTextContent()));
+                            break;
+                        case "d:CreationDate":
+                            builder.withCreatedTime(Instant.parse(item.getTextContent() + "Z").toEpochMilli());
+                            break;
+                        case "d:ModificationDate":
+                            builder.withUpdateTime(Instant.parse(item.getTextContent() + "Z").toEpochMilli());
+                            break;
+                        case "d:Name":
+                            builder.withFriendlyName(item.getTextContent());
+                            break;
+                        case "d:Id":
+                            builder.withResourcePath(item.getTextContent());
+                            break;
+                        case "d:Checksum":
+                            NodeList checksumNodes = item.getChildNodes();
+                            for (int j = 0; j < checksumNodes.getLength(); j++) {
+                                Node item1 = checksumNodes.item(j);
+                                if (item1.getNodeName().equals("d:Value")) {
+                                    builder.withMd5sum(item1.getTextContent());
+                                }
+                            }
+                            break;
+                    }
+                }
+
+                return Optional.of(builder.build());
+            }
+
+        } catch (Exception e) {
+            logger.warn("Failed while parsing provided XML body", e);
+        }
+
+        return Optional.empty();
+    }
+}
diff --git a/transport/pom.xml b/transport/pom.xml
index c537b24..2cc9729 100755
--- a/transport/pom.xml
+++ b/transport/pom.xml
@@ -42,6 +42,7 @@
         <module>ftp-transport</module>
         <module>dropbox-transport</module>
         <module>swift-transport</module>
+        <module>odata-transport</module>
     </modules>
     <dependencies>
         <dependency>
diff --git a/transport/s3-transport/pom.xml b/transport/s3-transport/pom.xml
index 9142055..4892b47 100644
--- a/transport/s3-transport/pom.xml
+++ b/transport/s3-transport/pom.xml
@@ -37,7 +37,13 @@
         <dependency>
             <groupId>org.apache.airavata</groupId>
             <artifactId>mft-core</artifactId>
-            <version>0.01-SNAPSHOT</version>
+            <version>${project.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>io.github.ci-cmg</groupId>
+            <artifactId>aws-s3-outputstream</artifactId>
+            <version>1.1.0</version>
         </dependency>
 
         <dependency>
diff --git a/transport/s3-transport/src/main/java/org/apache/airavata/mft/transport/s3/S3OutgoingStreamingConnector.java b/transport/s3-transport/src/main/java/org/apache/airavata/mft/transport/s3/S3OutgoingStreamingConnector.java
new file mode 100644
index 0000000..a039855
--- /dev/null
+++ b/transport/s3-transport/src/main/java/org/apache/airavata/mft/transport/s3/S3OutgoingStreamingConnector.java
@@ -0,0 +1,140 @@
+/*
+ * 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.airavata.mft.transport.s3;
+
+import edu.colorado.cires.cmg.s3out.AwsS3ClientMultipartUpload;
+import edu.colorado.cires.cmg.s3out.MultipartUploadRequest;
+import edu.colorado.cires.cmg.s3out.S3ClientMultipartUpload;
+import edu.colorado.cires.cmg.s3out.S3OutputStream;
+import org.apache.airavata.mft.core.api.ConnectorConfig;
+import org.apache.airavata.mft.core.api.OutgoingStreamingConnector;
+import org.apache.airavata.mft.credential.stubs.s3.S3Secret;
+import org.apache.airavata.mft.credential.stubs.s3.S3SecretGetRequest;
+import org.apache.airavata.mft.resource.client.ResourceServiceClient;
+import org.apache.airavata.mft.resource.client.ResourceServiceClientBuilder;
+import org.apache.airavata.mft.resource.stubs.common.GenericResource;
+import org.apache.airavata.mft.resource.stubs.common.GenericResourceGetRequest;
+import org.apache.airavata.mft.resource.stubs.s3.storage.S3Storage;
+import org.apache.airavata.mft.secret.client.SecretServiceClient;
+import org.apache.airavata.mft.secret.client.SecretServiceClientBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
+import software.amazon.awssdk.auth.credentials.AwsCredentials;
+import software.amazon.awssdk.auth.credentials.AwsSessionCredentials;
+import software.amazon.awssdk.regions.Region;
+import software.amazon.awssdk.services.s3.S3Client;
+
+import java.io.OutputStream;
+import java.net.URI;
+
+/** NOTE: This implementation uses 3rd party buffering of output stream
+ * https://github.com/CI-CMG/aws-s3-outputstream until Amazon SDK supports
+ * https://github.com/aws/aws-sdk-java-v2/issues/3128 **/
+
+public class S3OutgoingStreamingConnector implements OutgoingStreamingConnector {
+
+    private static final Logger logger = LoggerFactory.getLogger(S3OutgoingStreamingConnector.class);
+
+    private GenericResource resource;
+    private S3OutputStream s3OutputStream;
+    private S3ClientMultipartUpload s3;
+
+    @Override
+    public void init(ConnectorConfig cc) throws Exception {
+        try (ResourceServiceClient resourceClient = ResourceServiceClientBuilder
+                .buildClient(cc.getResourceServiceHost(), cc.getResourceServicePort())) {
+
+            resource = resourceClient.get().getGenericResource(GenericResourceGetRequest.newBuilder()
+                    .setAuthzToken(cc.getAuthToken())
+                    .setResourceId(cc.getResourceId()).build());
+        }
+
+        if (resource.getStorageCase() != GenericResource.StorageCase.S3STORAGE) {
+            logger.error("Invalid storage type {} specified for resource {}", resource.getStorageCase(), cc.getResourceId());
+            throw new Exception("Invalid storage type specified for resource " + cc.getResourceId());
+        }
+
+        S3Storage s3Storage = resource.getS3Storage();
+
+        S3Secret s3Secret;
+
+        try (SecretServiceClient secretClient = SecretServiceClientBuilder.buildClient(
+                cc.getSecretServiceHost(), cc.getSecretServicePort())) {
+
+            s3Secret = secretClient.s3().getS3Secret(S3SecretGetRequest.newBuilder()
+                    .setAuthzToken(cc.getAuthToken())
+                    .setSecretId(cc.getCredentialToken()).build());
+
+            AwsCredentials awsCreds;
+            if (s3Secret.getSessionToken() == null || s3Secret.getSessionToken().equals("")) {
+                awsCreds = AwsBasicCredentials.create(s3Secret.getAccessKey(), s3Secret.getSecretKey());
+            } else {
+                awsCreds = AwsSessionCredentials.create(s3Secret.getAccessKey(),
+                        s3Secret.getSecretKey(),
+                        s3Secret.getSessionToken());
+            }
+
+            S3Client s3Client = S3Client.builder()
+                    .region(Region.of(s3Storage.getRegion())).endpointOverride(new URI(s3Storage.getEndpoint()))
+                    .credentialsProvider(() -> awsCreds)
+                    .build();
+
+            this.s3 = AwsS3ClientMultipartUpload.builder().s3(s3Client).build();
+
+        }
+    }
+
+    @Override
+    public void complete() throws Exception {
+        if (this.s3OutputStream != null) {
+            this.s3OutputStream.done();
+            this.s3OutputStream.close();
+        }
+    }
+
+    @Override
+    public OutputStream fetchOutputStream() throws Exception {
+        this.s3OutputStream = S3OutputStream.builder()
+                .s3(s3)
+                .uploadRequest(MultipartUploadRequest.builder()
+                        .bucket(resource.getS3Storage().getBucketName())
+                        .key(resource.getFile().getResourcePath()).build())
+                .autoComplete(false)
+                .build();
+
+        logger.info("Initialized multipart upload for file {} in bucket {}",
+                resource.getFile().getResourcePath(), resource.getS3Storage().getBucketName());
+        return this.s3OutputStream;
+    }
+
+    @Override
+    public OutputStream fetchOutputStream(String childPath) throws Exception {
+        this.s3OutputStream = S3OutputStream.builder()
+                .s3(s3)
+                .uploadRequest(MultipartUploadRequest.builder()
+                        .bucket(resource.getS3Storage().getBucketName())
+                        .key(resource.getFile().getResourcePath() + "/" + childPath).build())
+                .autoComplete(false)
+                .build();
+
+        logger.info("Initialized multipart upload for file {} child path {} in bucket {}",
+                resource.getFile().getResourcePath(),  childPath, resource.getS3Storage().getBucketName());
+        return this.s3OutputStream;
+    }
+}