OCI Object Storage

The Helidon SE OCI Object Storage integration provides a reactive API to files stored in Oracle cloud.

Deprecated

The custom Helidon SE OCI clients documented here are deprecated. It is recommended that you use the OCI Java SDK directly, in particular the Async clients. For more information see:

Experimental

Helidon integration with Oracle Cloud Infrastructure is still experimental and not intended for production use. APIs and features have not yet been fully tested and are subject to change.

Maven Coordinates

To enable OCI Object Storage add the following dependency to your project’s pom.xml (see Managing Dependencies).

        <dependency>
            <groupId>io.helidon.integrations.oci</groupId>
            <artifactId>helidon-integrations-oci-objectstorage</artifactId>
        </dependency>
Copied

Setting up the Object Storage

In order to use the OCI Object Storage integration, the following setup should be made:

Config ociConfig = config.get("oci");

OciObjectStorageRx ociObjectStorage = OciObjectStorageRx.create(ociConfig);
Copied

Current configuration requires ~/.oci/config to be available in the home folder. This configuration file can be downloaded from OCI.

Routing should be added to the WebServer, in our case pointing to /file:

String bucketName = ociConfig.get("objectstorage").get("bucket").asString().get();

        WebServer.builder()
                .config(config.get("server"))
                .routing(Routing.builder()
                                 .register("/files", new ObjectStorageService(ociObjectStorage, bucketName)))
                .build()
                .start()
                .await()
Copied

Additionally, in application.yaml OCI properties should be specified:

oci:
    properties:
      compartment-ocid: "ocid<1>tenancy.oc<1>.<..>"
      objectstorage-namespace: "<...>"
      objectstorage-bucket: "<...>"
Copied

The exact values are available in OCI object storage and bucket properties.

OCI Bucket

Using the Object Storage

In the Service we must specify the mapping for CRUD operations with the files and their handlers:

@Override
public void update(Routing.Rules rules) {
    rules.get("/file/{file-name}", this::download)
            .post("/file/{file-name}", this::upload)
            .delete("/file/{file-name}", this::delete)
            .get("/rename/{old-name}/{new-name}", this::rename);
}
Copied

Upload file

To upload a file to OCI Object Storage using the PUT method:

private void upload(ServerRequest req, ServerResponse res) {
    OptionalLong contentLength = req.headers().contentLength();
    if (contentLength.isEmpty()) {
        req.content().forEach(DataChunk::release);
        res.status(Http.Status.BAD_REQUEST_400).send("Content length must be defined");
        return;
    }

    String objectName = req.path().param("file-name");

    PutObject.Request request = PutObject.Request.builder() 
            .objectName(objectName)
            .bucket(bucketName)
            .contentLength(contentLength.getAsLong());

    req.headers().contentType().ifPresent(request::requestMediaType); 

    objectStorage.putObject(request,
                            req.content())
            .forSingle(response -> res.send(response.requestId())) 
            .exceptionally(res::send);
}
Copied
  • Create the Request using PutObject.Request.builder()
  • Define MediaType
  • Execute the request to OCI in asynchronous way and put the result in response object

Download file

To download a file from OCI Object Storage using the GET method:

private void download(ServerRequest req, ServerResponse res) {
    String objectName = req.path().param("file-name");

    objectStorage.getObject(GetObject.Request.builder()
                                    .bucket(bucketName)
                                    .objectName(objectName)) 
            .forSingle(apiResponse -> {
                Optional<GetObjectRx.Response> entity = apiResponse.entity(); 
                if (entity.isEmpty()) {
                    res.status(Http.Status.NOT_FOUND_404).send(); 
                } else {
                    GetObjectRx.Response response = entity.get();
                    // copy the content length header to response
                    apiResponse.headers()
                            .first(Http.Header.CONTENT_LENGTH)
                            .ifPresent(res.headers()::add);
                    res.send(response.publisher()); 
                }
            })
            .exceptionally(res::send);
}
Copied
  • Use getObject function to make asynchronous request to OCI Object Storage
  • The result is of type Optional
  • Whenever the result is empty, return status 404
  • Get the response, set headers and return the result as a Publisher

Rename file

To rename an existing file in the OCI bucket, submit a GET method with two parameters:

private void rename(ServerRequest req, ServerResponse res) {
    String oldName = req.path().param("old-name");
    String newName = req.path().param("new-name");

    objectStorage.renameObject(RenameObject.Request.builder()
                                       .bucket(bucketName)
                                       .objectName(oldName)
                                       .newObjectName(newName)) 
            .forSingle(it -> res.send("Renamed to " + newName)) 
            .exceptionally(res::send);
}
Copied
  • Use renameObject function and configure a RenameObject.Request.builder() to submit the rename request
  • The request is made in asynchronous way; a Single is returned

Delete file

Finally, to delete a file, DELETE request should be used:

private void delete(ServerRequest req, ServerResponse res) {
    String objectName = req.path().param("file-name");

    objectStorage.deleteObject(DeleteObject.Request.builder()
                                       .bucket(bucketName)
                                       .objectName(objectName)) 
            .forSingle(response -> res.status(response.status()).send())
            .exceptionally(res::send);
}
Copied
  • Use deleteObject function and configure a DeleteObject.Request.builder() to submit the delete request
  • The request is made in asynchronous way; a Single is returned

Object Storage Health Check

If your Helidon application depends on Object Storage accessibility, you may consider setting up a health check to verify connectivity with an OCI bucket. To do so, first add the following dependency in your pom file:

<dependency>
    <groupId>io.helidon.integrations.oci</groupId>
    <artifactId>helidon-integrations-oci-objectstorage-health</artifactId>
</dependency>
Copied

In order to register the new health check in Helidon SE, create an instance of HealthSupport and configure it as shown next:

HealthSupport health = HealthSupport.builder()
    .addLiveness(OciObjectStorageHealthCheck.builder()
                                            .ociObjectStorage(ociObjectStorage)
                                            .bucket(bucketName)
                                            .namespace(namespace)
                                            .build())
    .build();
Copied

where ociObjectStorage, bucketName and namespace are as required for any other Object Storage access. Finally, include your newly created HealthSupport object as part of your application’s routing:

Routing routing = Routing.builder()
                         .register(health)
                         // other routes here
                         .build();
Copied

When executed, this health check will ping the bucket to make sure it is accessible in your environment. For more information about health checks see Health Checks.