Milesoft Cloudinary Extension (cloudinary-ext)

1. Overview & Purpose

The Cloudinary Extension (cloudinary-ext) is a lightweight, unified, and highly secure wrapper library around the Cloudinary Java SDK designed specifically for the Milesoft ecosystem. It simplifies the secure upload, classification, query, and transformation of media assets while ensuring absolute architectural consistency across all downstream services and applications.

Key Benefits & Core Capabilities:

  • Classification-Based Uploads: Native support for both Public (globally viewable) and Authenticated (signed, token-restricted) media storage.
  • Tag & Folder Scoping: Easy organization of media assets into structured virtual directories with rich tag metadata, allowing for advanced search queries.
  • Idempotent URL Transformations: Programmatic utility methods for responsive image operations (shrinking, padding, trimming, resizing) that seamlessly modify Cloudinary URLs without corrupting pre-existing parameters.
  • In-Memory Serializers: Out-of-the-box convenience utilities to convert standard Java BufferedImage assets into raw uploadable byte streams.

2. Installation & Dependency Configuration

The Cloudinary Extension is hosted securely inside our Google Cloud Artifact Registry repository.

To configure your application to resolve and pull this dependency:

2.1 Gradle Setup (build.gradle)

Ensure you apply the Google Cloud Artifact Registry plugin and configure the Maven repository block:

plugins {
    id "com.google.cloud.artifactregistry.gradle-plugin" version "2.2.5"
    id "java"
}

repositories {
    mavenLocal()
    mavenCentral()
    maven {
        url "artifactregistry://us-west2-maven.pkg.dev/milesoft-repo/repo-maven"
    }
}

dependencies {
    // Milesoft Commons foundation (required by cloudinary-ext)
    implementation "io.milesoft:commons:2.0.1"
    
    // Cloudinary Integration Extension
    implementation "io.milesoft:cloudinary-ext:2.0.1"
}

3. Configuring Cloudinary in Spring Boot

To enable proper dependency injection of the CloudinaryService within a downstream Spring Boot microservice, define it as a managed @Bean in a @Configuration class.

3.1 Externalize Application Properties

Define your non-sensitive parameters in your properties files (such as application.yml or application.properties) and externalize your secure API credentials into Google Cloud Secret Manager.

cloudinary:
  cloudName: "milesoft-production"

secrets:
  cloudinary:
    # Key targeting the Google Cloud Secret Manager secret name
    apiKey: "prod-cloudinary-api-key"
    apiSecret: "prod-cloudinary-api-secret"

3.2 Secure Bean Configuration

Leverage the platform's SecretManagerService to load the API credentials securely at application startup:

import static com.google.common.base.Preconditions.checkNotNull;

import io.milesoft.cloudinary.services.CloudinaryService;
import io.milesoft.secrets.services.SecretManagerService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class CloudinaryConfig {

    @Bean
    public CloudinaryService cloudinaryService(
            SecretManagerService secretManagerService,
            @Value("${cloudinary.cloudName}") String cloudName,
            @Value("${secrets.cloudinary.apiKey}") String secretApiKey,
            @Value("${secrets.cloudinary.apiSecret}") String secretApiSecret) {

        checkNotNull(secretManagerService, "secretManagerService must not be null");

        // Resolve secure API keys from Secret Manager dynamically
        final String apiKey = secretManagerService.loadSecret(secretApiKey);
        final String apiSecret = secretManagerService.loadSecret(secretApiSecret);

        return new CloudinaryService(cloudName, apiKey, apiSecret);
    }
}

4. Domain & Data Types

The library defines a strict, type-safe representation of images and their access rules.

4.1 Image Type Classification (ImageType)

Determines the access tier and signature rules applied to the asset when uploading to and deleting from Cloudinary.

public enum ImageType {
    Public,         // Anyone with the publicId or secureUrl can view the image.
    Authenticated   // Access requires signed requests; URLs are short-lived and secure.
}

4.2 Immutable Image Model (Image)

A standard Java record mapping the resulting upload payload. Built-in preconditions guarantee that all field properties are fully populated, non-null, and non-blank.

public record Image(ImageType type, String publicId, String secureUrl) {
    // Automated validation occurs during record construction
}

5. API Reference Guide

5.1 CloudinaryService

The central orchestration service used for interacting with the Cloudinary REST APIs.

Method Signatures Return Type Description
uploadImage(String url, String folder, ImageType type) Image Uploads a publicly-hosted remote URL asset into a virtual folder.
uploadImage(String url, String folder, ImageType type, String tag) Image Uploads a remote URL asset and associates it with a searchable tag.
uploadImage(byte[] bytes, String folder, ImageType type) Image Uploads raw in-memory binary image data directly.
uploadImage(byte[] bytes, String folder, ImageType type, String tag) Image Uploads in-memory binary image data and associates it with a searchable tag.
findByFolderAndTag(String folder, String tag) List<Image> Runs a search query to retrieve all assets matching a folder and tag.
deleteImage(String publicId, ImageType type) boolean Deletes a stored asset and invalidates caching proxies. Returns true if successful.

5.2 CloudinaryUtils

A thread-safe static utility containing responsive transformation logic. It parses Cloudinary image URLs and injects specific transformation instructions. The operations are completely idempotent: they automatically strip existing modifiers before appending new ones.

Utility Methods Return Type Transformation Applied
shrink(String imageUrl) String Appends thumbnail shrink operations (t_Shrink).
trim(String imageUrl) String Appends automatic transparent border-trim operations (e_trim).
trimAndShrink(String imageUrl) String Appends both trim and thumbnail shrink operations (e_trim/t_Shrink).
resizeContains(String imageUrl, int w, int h) String Resizes the asset to fit entirely inside bounding boxes (w_[w],h_[h],c_fit).
trimAndResizeContains(String imageUrl, int w, int h) String Combines transparent border trimming with bounding-box scaling.
toBytes(BufferedImage bufferedImage) byte[] Compresses and serializes in-memory Java images into portable png byte arrays.

6. Practical Real-World Code Examples

These integration examples represent actual, battle-tested production scenarios found across the Milesoft codebase.

Example A: Upload and Clean Up Temporary Assets

This pattern demonstrates processing an in-memory image (such as an avatar or temporary document thumbnail), uploading it to a specified folder, and subsequently removing the resource once it is no longer required.

import io.milesoft.cloudinary.domain.Image;
import io.milesoft.cloudinary.enums.ImageType;
import io.milesoft.cloudinary.services.CloudinaryService;
import io.milesoft.cloudinary.utils.CloudinaryUtils;
import org.springframework.stereotype.Service;
import java.awt.image.BufferedImage;

@Service
public class TemporaryAssetService {

    private final CloudinaryService cloudinaryService;
    private static final String FOLDER_TEMP = "temp-thumbnails";

    public TemporaryAssetService(CloudinaryService cloudinaryService) {
        this.cloudinaryService = cloudinaryService;
    }

    public String uploadTemporaryThumbnail(BufferedImage thumbnailImage) {
        // 1. Convert standard Java BufferedImage to PNG bytes
        byte[] imageBytes = CloudinaryUtils.toBytes(thumbnailImage);

        // 2. Upload to Cloudinary as a Public asset
        Image uploadedAsset = cloudinaryService.uploadImage(
                imageBytes, 
                FOLDER_TEMP, 
                ImageType.Public, 
                "transient-thumbnail"
        );

        return uploadedAsset.secureUrl();
    }

    public void cleanUpThumbnail(String publicId) {
        // 3. Securely destroy the asset when no longer needed
        boolean success = cloudinaryService.deleteImage(publicId, ImageType.Public);
        if (success) {
            System.out.println("Successfully removed temporary thumbnail: " + publicId);
        }
    }
}

Example B: Batch Removal of Tenant Imagery

This routine shows how to query all uploaded assets matching a specific business workspace or tenant tag and securely prune them.

import io.milesoft.cloudinary.domain.Image;
import io.milesoft.cloudinary.enums.ImageType;
import io.milesoft.cloudinary.services.CloudinaryService;
import io.milesoft.commons.utils.StreamUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

@Slf4j
@Service
public class ImageService {

    private final CloudinaryService cloudinaryService;

    public ImageService(CloudinaryService cloudinaryService) {
        this.cloudinaryService = cloudinaryService;
    }

    public void purgeTenantMedia(String folder, String tenantTag) {
        // Retrieve all image references matching both the folder and user's tag
        StreamUtils.stream(cloudinaryService.findByFolderAndTag(folder, tenantTag))
                .forEach(image -> {
                    try {
                        // Purge the image from storage and clear caching proxies
                        cloudinaryService.deleteImage(image.publicId(), ImageType.Public);
                        log.info("Successfully deleted image: {}", image.publicId());
                    } catch (Exception e) {
                        log.warn("Unable to delete image asset " + image.publicId(), e);
                    }
                });
    }
}

Example C: Responsive Layout Optimization (CloudinaryUtils)

When presenting uploaded profile avatars or catalog graphics to different user agents (mobile viewports vs. desktop displays), use URL transformers to serve optimized, trimmed, and scaled assets:

public class UserInterfaceController {

    public String renderProfileThumbnail(String rawCloudinaryUrl) {
        // Optimization: Strip transparent padding and downscale to a standard 200x200 container
        return CloudinaryUtils.trimAndResizeContains(rawCloudinaryUrl, 200, 200);
    }
}