Quick Start

Use the information in this topic to help you get started with Oracle AI Data Platform Workbench.

Prerequisites

Prerequisite More Information
OCI CLI installed  
OCI Config file and API Key Pair Default path: ~/.oci/config
Browser-based or --no-browser capable environment for oci session authenticate
IAM permissions to manage AI Data Platform Workbench workspaces IAM Policies for Oracle AI Data Platform Workbench
Network access to your AI Data Platform Workbench endpoint Example: https://aidp.<oci-region>.oci.oraclecloud.com

Sample Code

This sample shows you how to run AidpClient.java with Oracle Cloud Infrastructure (OCI) configuration file authentication.

When you run AidpClient.java it performs several actions in the following order:
  1. Creates a workspace.
  2. Waits until the workspace can be used.
  3. Creates a cluster in that workspace.
  4. Waits until the cluster is ACTIVE.
  5. Creates a notebook folder.
  6. Creates a notebook in that folder.
  7. Uploads notebook content.
  8. Creates an empty job.
  9. Updates the job to attach the cluster and notebook task.
  10. Runs the job.
  11. Polls the job run until it reaches a terminal state.
  12. Prints the final result.

Sample Code
Minimal authentication and signed client setup from the Java sample:
package org.example;

/**
 * This is an automatically generated code sample.
 * To make this code sample work in your Oracle Cloud tenancy,
 * replace all placeholder values (OCIDs, endpoint, names, and sample payload values)
 * with values that match your environment and use case.
 */
package org.example;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.oracle.bmc.Region;
import com.oracle.bmc.auth.SessionTokenAuthenticationDetailsProvider;
import com.oracle.bmc.http.Priorities;
import com.oracle.bmc.http.client.HttpClient;
import com.oracle.bmc.http.client.HttpRequest;
import com.oracle.bmc.http.client.HttpResponse;
import com.oracle.bmc.http.client.Method;
import com.oracle.bmc.http.client.jersey.JerseyHttpProvider;
import com.oracle.bmc.http.signing.RequestSigningFilter;
import com.oracle.bmc.io.internal.WrappedByteArrayInputStream;

import java.io.IOException;
import java.net.URI;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.function.Function;

import javax.ws.rs.core.MediaType;

public final class AidpWorkflowClient {
    private static final String TOKEN_FILE_PATH = "/Users/daldu/.oci/sessions/DEFAULT/token";
    private static final Region REGION = Region.US_PHOENIX_1;
    private static final String TENANT_OCID =
            "ocid1.tenancy.oc1..aaaaaaaa7ayxuw32vjb64hbxtouarftwtwb2uat5x5mf4hu7cvzaesfrebrq";
    private static final String AIDP_OCID = <AIDP_OCID>
    private static final String AIDP_ENDPOINT = "https://aidpprod.us-phoenix-1.oci.oraclecloud.com";
    private static final String API_VERSION = "20260430";

    private static final String WORKSPACE_NAME_PREFIX = "aidp_workflow_ws_";
    private static final String WORKSPACE_DESCRIPTION = "AIDP workflow sample workspace";
    private static final String DEFAULT_CATALOG_KEY = "default";

    private static final String CLUSTER_NAME_PREFIX = "aidp_workflow_cluster_";
    private static final String NOTEBOOK_FOLDER_PREFIX = "aidp_workflow_notebook_dir_";
    private static final String NOTEBOOK_NAME_PREFIX = "aidp_workflow_notebook_";
    private static final String JOB_NAME_PREFIX = "aidp_workflow_job_";
    private static final String WORKSPACE_ROOT = "/Workspace";
    private static final String JOB_PATH = "/Workspace/jobs";

    private static final int MAX_RETRIES = 5;
    private static final long INITIAL_BACKOFF_MS = 1_000;
    private static final int BACKOFF_MULTIPLIER = 2;
    private static final long POLL_INTERVAL_MS = 10_000;
    private static final long POLL_TIMEOUT_MS = 20 * 60_000L;

    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
    private static final ObjectMapper PRETTY_MAPPER =
            new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT);

    private AidpWorkflowClient() {
    }

    public static void main(String[] args) {
        SessionTokenAuthenticationDetailsProvider authProvider = null;
        try {
            /**
         	* Create a default authentication provider that uses the DEFAULT
         	* profile in the configuration file.
         	* Refer to <see href="https://docs.cloud.oracle.com/en-us/iaas/Content/API/Concepts/sdkconfig.htm#SDK_and_CLI_Configuration_File>the public documentation</see> on how to prepare a configuration file.
         	*/
            final ConfigFileReader.ConfigFile configFile = ConfigFileReader.parseDefault()
			final AuthenticationDetailsProvider provider = new ConfigFileAuthenticationDetailsProvider(configFile);
            RequestSigningFilter requestSigningFilter = RequestSigningFilter.fromAuthProvider(authProvider);
            String suffix = Long.toString(System.currentTimeMillis());
            String workspaceName = WORKSPACE_NAME_PREFIX + suffix;
            String clusterName = CLUSTER_NAME_PREFIX + suffix;
            String notebookFolderName = NOTEBOOK_FOLDER_PREFIX + suffix;
            String notebookName = NOTEBOOK_NAME_PREFIX + suffix + ".ipynb";
            String jobName = JOB_NAME_PREFIX + suffix;

            String workspaceKey = createWorkspace(
                    requestSigningFilter,
                    workspaceName,
                    WORKSPACE_DESCRIPTION,
                    DEFAULT_CATALOG_KEY);
            waitForWorkspaceReady(requestSigningFilter, workspaceKey);

            String clusterKey = createCluster(requestSigningFilter, workspaceKey, clusterName);
            waitForClusterActive(requestSigningFilter, workspaceKey, clusterKey);

            String notebookApiPath =
                    createNotebook(requestSigningFilter, workspaceKey, notebookFolderName, notebookName);
            updateNotebookContent(requestSigningFilter, workspaceKey, notebookApiPath);

            String jobKey = createJob(requestSigningFilter, workspaceKey, jobName, JOB_PATH);
            updateJobWithNotebookTask(requestSigningFilter, workspaceKey, jobKey, jobName, clusterKey, notebookApiPath);
            waitForJobTasksAttached(requestSigningFilter, workspaceKey, jobKey);

            String jobRunKey = runJob(requestSigningFilter, workspaceKey, jobKey);
            ResponsePayload finalJobRun = waitForJobRunTerminal(requestSigningFilter, workspaceKey, jobRunKey);
            String finalStatus = extractStateStatus(finalJobRun.body());
            String taskMessage = extractFirstTaskStateMessage(finalJobRun.body());

            System.out.println("Final job run status: " + finalStatus);
            if (taskMessage != null && !taskMessage.isBlank()) {
                System.out.println("Task state message: " + taskMessage);
            }

            if (!"SUCCESS".equalsIgnoreCase(finalStatus)) {
                throw new IllegalStateException("Job run did not succeed: " + taskMessage);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (authProvider != null) {
                authProvider.close();
            }
        }
    }

    static String buildWorkspacesBaseUrl(String endpoint, String apiVersion, String aidpOcid) {
        return String.format("%s/%s/aiDataPlatforms/%s/workspaces", endpoint, apiVersion, aidpOcid);
    }

    static String buildClustersBaseUrl(String endpoint, String apiVersion, String aidpOcid, String workspaceKey) {
        return String.format("%s/clusters", buildWorkspaceBaseUrl(endpoint, apiVersion, aidpOcid, workspaceKey));
    }

    static String buildJobsBaseUrl(String endpoint, String apiVersion, String aidpOcid, String workspaceKey) {
        return String.format("%s/jobs", buildWorkspaceBaseUrl(endpoint, apiVersion, aidpOcid, workspaceKey));
    }

    static String buildWorkspaceObjectsBaseUrl(
            String endpoint,
            String apiVersion,
            String aidpOcid,
            String workspaceKey) {
        return String.format("%s/objects", buildWorkspaceBaseUrl(endpoint, apiVersion, aidpOcid, workspaceKey));
    }

    static Map<String, String> buildWorkspaceObjectCreateHeaders(String folderName) {
        return Map.of(
                "path", toWorkspaceAbsolutePath(folderName),
                "type", "FOLDER",
                "is-overwrite", "true");
    }

    static String buildJobRunsBaseUrl(String endpoint, String apiVersion, String aidpOcid, String workspaceKey) {
        return String.format("%s/jobRuns", buildWorkspaceBaseUrl(endpoint, apiVersion, aidpOcid, workspaceKey));
    }

    static String buildNotebookContentsUrl(
            String endpoint,
            String apiVersion,
            String aidpOcid,
            String workspaceKey,
            String encodedPath) {
        return String.format(
                "%s/notebook/api/contents/%s",
                buildWorkspaceBaseUrl(endpoint, apiVersion, aidpOcid, workspaceKey),
                encodedPath);
    }

    static String extractText(String responseBody, String fieldName) {
        if (responseBody == null || responseBody.isBlank()) {
            return null;
        }
        try {
            JsonNode node = OBJECT_MAPPER.readTree(responseBody);
            return node.hasNonNull(fieldName) ? node.get(fieldName).asText() : null;
        } catch (IOException e) {
            return null;
        }
    }

    static String buildWorkspaceCreatePayload(String displayName, String description, String defaultCatalogKey) {
        Map<String, Object> payload = new LinkedHashMap<>();
        payload.put("displayName", displayName);
        payload.put("freeformTags", null);
        payload.put("definedTags", null);
        payload.put("description", description);
        payload.put("properties", null);
        payload.put("defaultCatalogKey", defaultCatalogKey);
        payload.put("networkConfigurationDetails", null);
        return toJson(payload);
    }

    static String encodeNotebookPath(String rawPath) {
        return rawPath.replace("/", "%2F");
    }

    static String buildNotebookCreatePayload() {
        Map<String, Object> payload = new LinkedHashMap<>();
        payload.put("copy_from", null);
        payload.put("ext", ".ipynb");
        payload.put("type", "notebook");
        payload.put("freeformTags", null);
        payload.put("definedTags", null);
        return toJson(payload);
    }

    static String buildNotebookContentPayload(String notebookName, String notebookPath, Map<String, ?> content) {
        return toJson(Map.of(
                "name", notebookName,
                "path", notebookPath,
                "type", "notebook",
                "content", content,
                "format", "json"));
    }

    static String buildNotebookRenamePayload(String notebookPath) {
        return toJson(Map.of("path", notebookPath));
    }

    static boolean isUsableClusterState(String state) {
        return "ACTIVE".equalsIgnoreCase(state);
    }

    static boolean isTerminalBadClusterState(String state) {
        return "FAILED".equalsIgnoreCase(state) || "DELETED".equalsIgnoreCase(state);
    }

    static String buildClusterCreatePayload(String displayName) {
        return toJson(Map.of(
                "displayName", displayName,
                "driverConfig", Map.of(
                        "driverShape", "intel.generic",
                        "driverShapeConfig", Map.of(
                                "ocpus", 1,
                                "memoryInGBs", 16)),
                "workerConfig", Map.of(
                        "workerShape", "intel.generic",
                        "workerShapeConfig", Map.of(
                                "ocpus", 1,
                                "memoryInGBs", 16),
                        "minWorkerCount", 1,
                        "maxWorkerCount", 10),
                "autoTerminationMinutes", 120,
                "clusterRuntimeConfig", Map.of(
                        "type", "SPARK",
                        "sparkVersion", "3.5.0",
                        "sparkAdvancedConfigurations", Map.of(),
                        "sparkEnvVariables", Map.of(),
                        "initScripts", List.of())));
    }

    static String buildEmptyJobCreatePayload(String name, String path) {
        return toJson(Map.of(
                "name", name,
                "path", path,
                "description", "workflow sample job",
                "maxConcurrentRuns", 1));
    }

    static String buildJobUpdatePayload(String name, String path, String clusterKey, String notebookPath) {
        return toJson(Map.of(
                "name", name,
                "path", path,
                "description", "workflow sample job",
                "maxConcurrentRuns", 1,
                "jobClusters", List.of(Map.of("clusterKey", clusterKey)),
                "tasks", List.of(Map.of(
                        "type", "NOTEBOOK_TASK",
                        "taskKey", "TASK1",
                        "dependsOn", List.of(),
                        "runIf", "ALL_SUCCESS",
                        "maxRetries", 0,
                        "isRetryOnTimeout", false,
                        "notebookPath", notebookPath,
                        "cluster", Map.of("clusterKey", clusterKey),
                        "parameters", List.of()))));
    }

    static String buildJobRunPayload(String jobKey) {
        return toJson(Map.of(
                "jobKey", jobKey,
                "parameters", List.of()));
    }

    static boolean isTerminalJobRunState(String state) {
        return List.of("SUCCESS", "FAILED", "ERROR", "CANCELED", "TIMED_OUT").contains(state);
    }

    static String extractStateStatus(String responseBody) {
        return extractJsonPointerText(responseBody, "/state/status");
    }

    static String extractStateMessage(String responseBody) {
        return extractJsonPointerText(responseBody, "/state/stateMessage");
    }

    static boolean jobHasTasks(String responseBody) {
        if (responseBody == null || responseBody.isBlank()) {
            return false;
        }
        try {
            JsonNode tasks = OBJECT_MAPPER.readTree(responseBody).get("tasks");
            return tasks != null && tasks.isArray() && !tasks.isEmpty();
        } catch (IOException e) {
            return false;
        }
    }

    private static SessionTokenAuthenticationDetailsProvider buildAuthProvider() throws IOException {
        return SessionTokenAuthenticationDetailsProvider.builder()
                .region(REGION)
                .tenantId(TENANT_OCID)
                .sessionTokenFilePath(TOKEN_FILE_PATH)
                .build();
    }

    private static String createWorkspace(
            RequestSigningFilter requestSigningFilter,
            String displayName,
            String description,
            String defaultCatalogKey) {
        String baseUrl = buildWorkspacesBaseUrl(AIDP_ENDPOINT, API_VERSION, AIDP_OCID);
        ResponsePayload response = executeWithRetry(
                requestSigningFilter,
                baseUrl,
                client -> client.createRequest(Method.POST)
                        .header("Accept", MediaType.APPLICATION_JSON)
                        .header("Content-Type", MediaType.APPLICATION_JSON)
                        .body(buildWorkspaceCreatePayload(displayName, description, defaultCatalogKey)),
                "Create workspace");
        String workspaceKey = extractKeyOrId(response.body());
        if (workspaceKey == null || workspaceKey.isBlank()) {
            throw new IllegalStateException("Workspace key is empty; create workspace failed.");
        }
        System.out.println("Workspace key: " + workspaceKey);
        return workspaceKey;
    }

    private static ResponsePayload getWorkspace(RequestSigningFilter requestSigningFilter, String workspaceKey) {
        String baseUrl = buildWorkspaceBaseUrl(AIDP_ENDPOINT, API_VERSION, AIDP_OCID, workspaceKey);
        return executeWithRetry(
                requestSigningFilter,
                baseUrl,
                client -> client.createRequest(Method.GET)
                        .header("Accept", MediaType.APPLICATION_JSON),
                "Get workspace " + workspaceKey);
    }

    private static String createCluster(RequestSigningFilter requestSigningFilter, String workspaceKey, String displayName) {
        String baseUrl = buildClustersBaseUrl(AIDP_ENDPOINT, API_VERSION, AIDP_OCID, workspaceKey);
        ResponsePayload response = executeWithRetry(
                requestSigningFilter,
                baseUrl,
                client -> client.createRequest(Method.POST)
                        .header("Accept", MediaType.APPLICATION_JSON)
                        .header("Content-Type", MediaType.APPLICATION_JSON)
                        .body(buildClusterCreatePayload(displayName)),
                "Create cluster");
        String clusterKey = extractKeyOrId(response.body());
        if (clusterKey == null || clusterKey.isBlank()) {
            throw new IllegalStateException("Cluster key is empty; create cluster failed.");
        }
        System.out.println("Cluster key: " + clusterKey);
        return clusterKey;
    }

    private static ResponsePayload getCluster(RequestSigningFilter requestSigningFilter, String workspaceKey, String clusterKey) {
        String baseUrl = buildClusterBaseUrl(AIDP_ENDPOINT, API_VERSION, AIDP_OCID, workspaceKey, clusterKey);
        return executeWithRetry(
                requestSigningFilter,
                baseUrl,
                client -> client.createRequest(Method.GET)
                        .header("Accept", MediaType.APPLICATION_JSON),
                "Get cluster " + clusterKey);
    }

    private static String createNotebook(
            RequestSigningFilter requestSigningFilter,
            String workspaceKey,
            String folderName,
            String notebookName) {
        createNotebookFolder(requestSigningFilter, workspaceKey, folderName);
        String parentPath = toWorkspaceAbsolutePath(folderName);
        String baseUrl = buildNotebookContentsUrl(
                AIDP_ENDPOINT,
                API_VERSION,
                AIDP_OCID,
                workspaceKey,
                encodeNotebookPath(parentPath));
        ResponsePayload response = executeWithRetry(
                requestSigningFilter,
                baseUrl,
                client -> client.createRequest(Method.POST)
                        .header("Accept", MediaType.APPLICATION_JSON)
                        .header("Content-Type", MediaType.APPLICATION_JSON)
                        .body(buildNotebookCreatePayload()),
                "Create notebook");
        String notebookPath = extractText(response.body(), "path");
        if (notebookPath == null || notebookPath.isBlank()) {
            throw new IllegalStateException("Notebook path is empty; create notebook failed.");
        }
        return renameNotebook(requestSigningFilter, workspaceKey, notebookPath, parentPath + "/" + notebookName);
    }

    private static void updateNotebookContent(
            RequestSigningFilter requestSigningFilter,
            String workspaceKey,
            String notebookApiPath) {
        String notebookName = lastPathSegment(notebookApiPath);
        String baseUrl = buildNotebookContentsUrl(
                AIDP_ENDPOINT,
                API_VERSION,
                AIDP_OCID,
                workspaceKey,
                encodeNotebookPath(notebookApiPath));
        executeWithRetry(
                requestSigningFilter,
                baseUrl,
                client -> client.createRequest(Method.PUT)
                        .header("Accept", MediaType.APPLICATION_JSON)
                        .header("Content-Type", MediaType.APPLICATION_JSON)
                        .body(buildNotebookContentPayload(
                                notebookName,
                                notebookApiPath,
                                buildSampleNotebookContent())),
                "Update notebook content");
    }

    private static void createNotebookFolder(
            RequestSigningFilter requestSigningFilter,
            String workspaceKey,
            String folderName) {
        String baseUrl = buildWorkspaceObjectsBaseUrl(
                AIDP_ENDPOINT,
                API_VERSION,
                AIDP_OCID,
                workspaceKey);
        Map<String, String> headers = buildWorkspaceObjectCreateHeaders(folderName);
        ResponsePayload response = executeWithRetry(
                requestSigningFilter,
                baseUrl,
                client -> {
                    HttpRequest request = client.createRequest(Method.POST)
                            .header("Accept", "*/*")
                            .header("Content-Type", MediaType.APPLICATION_OCTET_STREAM);
                    headers.forEach(request::header);
                    return request.body(new WrappedByteArrayInputStream(new byte[0]), 0L);
                },
                "Create notebook folder");
        if (response.status() != 200 && response.status() != 201) {
            throw new IllegalStateException("Notebook folder creation failed with status: " + response.status());
        }
        System.out.println("Notebook folder: " + folderName);
    }

    private static String renameNotebook(
            RequestSigningFilter requestSigningFilter,
            String workspaceKey,
            String notebookApiPath,
            String targetNotebookPath) {
        String baseUrl = buildNotebookContentsUrl(
                AIDP_ENDPOINT,
                API_VERSION,
                AIDP_OCID,
                workspaceKey,
                encodeNotebookPath(notebookApiPath));
        ResponsePayload response = executeWithRetry(
                requestSigningFilter,
                baseUrl,
                client -> client.createRequest(Method.PATCH)
                        .header("Accept", MediaType.APPLICATION_JSON)
                        .header("Content-Type", MediaType.APPLICATION_JSON)
                        .body(buildNotebookRenamePayload(targetNotebookPath)),
                "Rename notebook");
        String renamedNotebookPath = extractText(response.body(), "path");
        if (renamedNotebookPath == null || renamedNotebookPath.isBlank()) {
            throw new IllegalStateException("Notebook rename returned an empty path.");
        }
        System.out.println("Notebook API path: " + renamedNotebookPath);
        return renamedNotebookPath;
    }

    private static String createJob(
            RequestSigningFilter requestSigningFilter,
            String workspaceKey,
            String jobName,
            String jobPath) {
        String baseUrl = buildJobsBaseUrl(AIDP_ENDPOINT, API_VERSION, AIDP_OCID, workspaceKey);
        ResponsePayload response = executeWithRetry(
                requestSigningFilter,
                baseUrl,
                client -> client.createRequest(Method.POST)
                        .header("Accept", MediaType.APPLICATION_JSON)
                        .header("Content-Type", MediaType.APPLICATION_JSON)
                        .body(buildEmptyJobCreatePayload(jobName, jobPath)),
                "Create empty job");
        String jobKey = extractKeyOrId(response.body());
        if (jobKey == null || jobKey.isBlank()) {
            throw new IllegalStateException("Job key is empty; create job failed.");
        }
        System.out.println("Job key: " + jobKey);
        return jobKey;
    }

    private static void updateJobWithNotebookTask(
            RequestSigningFilter requestSigningFilter,
            String workspaceKey,
            String jobKey,
            String jobName,
            String clusterKey,
            String notebookApiPath) {
        String baseUrl = buildJobBaseUrl(AIDP_ENDPOINT, API_VERSION, AIDP_OCID, workspaceKey, jobKey);
        executeWithRetry(
                requestSigningFilter,
                baseUrl,
                client -> client.createRequest(Method.PUT)
                        .header("Accept", MediaType.APPLICATION_JSON)
                        .header("Content-Type", MediaType.APPLICATION_JSON)
                        .body(buildJobUpdatePayload(
                                jobName,
                                JOB_PATH,
                                clusterKey,
                                normalizeNotebookPathForJob(notebookApiPath))),
                "Update job " + jobKey + " with notebook task");
    }

    private static ResponsePayload getJob(RequestSigningFilter requestSigningFilter, String workspaceKey, String jobKey) {
        String baseUrl = buildJobBaseUrl(AIDP_ENDPOINT, API_VERSION, AIDP_OCID, workspaceKey, jobKey);
        return executeWithRetry(
                requestSigningFilter,
                baseUrl,
                client -> client.createRequest(Method.GET)
                        .header("Accept", MediaType.APPLICATION_JSON),
                "Get job " + jobKey);
    }

    private static String runJob(RequestSigningFilter requestSigningFilter, String workspaceKey, String jobKey) {
        String baseUrl = buildJobRunsBaseUrl(AIDP_ENDPOINT, API_VERSION, AIDP_OCID, workspaceKey);
        ResponsePayload response = executeWithRetry(
                requestSigningFilter,
                baseUrl,
                client -> client.createRequest(Method.POST)
                        .header("Accept", MediaType.APPLICATION_JSON)
                        .header("Content-Type", MediaType.APPLICATION_JSON)
                        .body(buildJobRunPayload(jobKey)),
                "Run job " + jobKey);
        String jobRunKey = extractKeyOrId(response.body());
        if (jobRunKey == null || jobRunKey.isBlank()) {
            throw new IllegalStateException("Job run key is empty; run job failed.");
        }
        System.out.println("Job run key: " + jobRunKey);
        return jobRunKey;
    }

    private static ResponsePayload getJobRun(RequestSigningFilter requestSigningFilter, String workspaceKey, String jobRunKey) {
        String baseUrl = buildJobRunBaseUrl(AIDP_ENDPOINT, API_VERSION, AIDP_OCID, workspaceKey, jobRunKey);
        return executeWithRetry(
                requestSigningFilter,
                baseUrl,
                client -> client.createRequest(Method.GET)
                        .header("Accept", MediaType.APPLICATION_JSON),
                "Get job run " + jobRunKey);
    }

    private static void waitForWorkspaceReady(RequestSigningFilter requestSigningFilter, String workspaceKey)
            throws InterruptedException {
        long deadline = System.currentTimeMillis() + POLL_TIMEOUT_MS;
        while (System.currentTimeMillis() < deadline) {
            ResponsePayload response = getWorkspace(requestSigningFilter, workspaceKey);
            if (response.status() == 200) {
                return;
            }
            Thread.sleep(POLL_INTERVAL_MS);
        }
        throw new IllegalStateException("Workspace did not become ready: " + workspaceKey);
    }

    private static void waitForClusterActive(
            RequestSigningFilter requestSigningFilter,
            String workspaceKey,
            String clusterKey) throws InterruptedException {
        long deadline = System.currentTimeMillis() + POLL_TIMEOUT_MS;
        while (System.currentTimeMillis() < deadline) {
            ResponsePayload response = getCluster(requestSigningFilter, workspaceKey, clusterKey);
            String state = extractText(response.body(), "state");
            if (response.status() == 200 && isUsableClusterState(state)) {
                return;
            }
            if (isTerminalBadClusterState(state)) {
                throw new IllegalStateException("Cluster entered terminal state: " + state);
            }
            Thread.sleep(POLL_INTERVAL_MS);
        }
        throw new IllegalStateException("Cluster did not become active: " + clusterKey);
    }

    private static void waitForJobTasksAttached(
            RequestSigningFilter requestSigningFilter,
            String workspaceKey,
            String jobKey) throws InterruptedException {
        long deadline = System.currentTimeMillis() + POLL_TIMEOUT_MS;
        while (System.currentTimeMillis() < deadline) {
            ResponsePayload response = getJob(requestSigningFilter, workspaceKey, jobKey);
            if (response.status() == 200 && jobHasTasks(response.body())) {
                return;
            }
            Thread.sleep(POLL_INTERVAL_MS);
        }
        throw new IllegalStateException("Job update did not attach any tasks: " + jobKey);
    }

    private static ResponsePayload waitForJobRunTerminal(
            RequestSigningFilter requestSigningFilter,
            String workspaceKey,
            String jobRunKey) throws InterruptedException {
        long deadline = System.currentTimeMillis() + POLL_TIMEOUT_MS;
        while (System.currentTimeMillis() < deadline) {
            ResponsePayload response = getJobRun(requestSigningFilter, workspaceKey, jobRunKey);
            String state = extractStateStatus(response.body());
            if (response.status() == 200 && isTerminalJobRunState(state)) {
                return response;
            }
            Thread.sleep(POLL_INTERVAL_MS);
        }
        throw new IllegalStateException("Job run did not reach terminal state: " + jobRunKey);
    }

    private static HttpClient buildSignedClient(RequestSigningFilter requestSigningFilter, String baseUrl) {
        return JerseyHttpProvider.getInstance()
                .newBuilder()
                .registerRequestInterceptor(Priorities.AUTHENTICATION, requestSigningFilter)
                .baseUri(URI.create(baseUrl))
                .build();
    }

    private static ResponsePayload executeWithRetry(
            RequestSigningFilter requestSigningFilter,
            String baseUrl,
            Function<HttpClient, HttpRequest> requestFactory,
            String operation) {
        int attempt = 1;
        long backoff = INITIAL_BACKOFF_MS;

        while (true) {
            try (HttpClient client = buildSignedClient(requestSigningFilter, baseUrl);
                 HttpResponse response = requestFactory.apply(client).execute().toCompletableFuture().get()) {
                int status = response.status();
                String body = response.textBody().toCompletableFuture().get();
                Map<String, List<String>> headers = response.headers();
                boolean retryable = status == 429 || status == 500;
                if (retryable && attempt < MAX_RETRIES) {
                    System.out.printf(
                            "Retryable status %d on %s (attempt %d/%d). Backing off %d ms.%n",
                            status,
                            operation,
                            attempt,
                            MAX_RETRIES,
                            backoff);
                    Thread.sleep(backoff);
                    attempt++;
                    backoff *= BACKOFF_MULTIPLIER;
                    continue;
                }
                logResponse(operation, status, headers, body);
                return new ResponsePayload(status, body, headers);
            } catch (ExecutionException e) {
                Throwable cause = e.getCause() != null ? e.getCause() : e;
                if (attempt < MAX_RETRIES) {
                    System.out.printf(
                            "Retryable exception on %s (attempt %d/%d): %s. Backing off %d ms.%n",
                            operation,
                            attempt,
                            MAX_RETRIES,
                            cause.getMessage(),
                            backoff);
                    try {
                        Thread.sleep(backoff);
                    } catch (InterruptedException ie) {
                        Thread.currentThread().interrupt();
                        throw new RuntimeException("Interrupted during " + operation, ie);
                    }
                    attempt++;
                    backoff *= BACKOFF_MULTIPLIER;
                    continue;
                }
                throw new RuntimeException("Request failed during " + operation, cause);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                throw new RuntimeException("Interrupted during " + operation, e);
            }
        }
    }

    private static String extractKeyOrId(String responseBody) {
        String key = extractText(responseBody, "key");
        return key != null ? key : extractText(responseBody, "id");
    }

    private static String extractFirstTaskStateMessage(String responseBody) {
        if (responseBody == null || responseBody.isBlank()) {
            return null;
        }
        try {
            JsonNode taskRunSummaryMap = OBJECT_MAPPER.readTree(responseBody).path("taskRunSummaryMap");
            if (taskRunSummaryMap.isObject()) {
                var fields = taskRunSummaryMap.fields();
                if (fields.hasNext()) {
                    JsonNode taskState = fields.next().getValue().path("state").path("stateMessage");
                    if (!taskState.isMissingNode() && !taskState.isNull()) {
                        return taskState.asText();
                    }
                }
            }
        } catch (IOException e) {
            return extractStateMessage(responseBody);
        }
        return extractStateMessage(responseBody);
    }

    private static String extractJsonPointerText(String responseBody, String pointer) {
        if (responseBody == null || responseBody.isBlank()) {
            return null;
        }
        try {
            JsonNode node = OBJECT_MAPPER.readTree(responseBody).at(pointer);
            return node.isMissingNode() || node.isNull() ? null : node.asText();
        } catch (IOException e) {
            return null;
        }
    }

    private static String toJson(Map<String, ?> payload) {
        try {
            return OBJECT_MAPPER.writeValueAsString(payload);
        } catch (JsonProcessingException e) {
            throw new IllegalArgumentException("Unable to serialize payload", e);
        }
    }

    private static String buildWorkspaceBaseUrl(String endpoint, String apiVersion, String aidpOcid, String workspaceKey) {
        return String.format("%s/%s/dataLakes/%s/workspaces/%s", endpoint, apiVersion, aidpOcid, workspaceKey);
    }

    private static String buildClusterBaseUrl(
            String endpoint,
            String apiVersion,
            String aidpOcid,
            String workspaceKey,
            String clusterKey) {
        return String.format("%s/%s", buildClustersBaseUrl(endpoint, apiVersion, aidpOcid, workspaceKey), clusterKey);
    }

    private static String buildJobBaseUrl(
            String endpoint,
            String apiVersion,
            String aidpOcid,
            String workspaceKey,
            String jobKey) {
        return String.format("%s/%s", buildJobsBaseUrl(endpoint, apiVersion, aidpOcid, workspaceKey), jobKey);
    }

    private static String buildJobRunBaseUrl(
            String endpoint,
            String apiVersion,
            String aidpOcid,
            String workspaceKey,
            String jobRunKey) {
        return String.format("%s/%s", buildJobRunsBaseUrl(endpoint, apiVersion, aidpOcid, workspaceKey), jobRunKey);
    }

    private static String normalizeNotebookPathForJob(String notebookApiPath) {
        return notebookApiPath.startsWith("/") ? notebookApiPath : "/" + notebookApiPath;
    }

    private static String toWorkspaceAbsolutePath(String path) {
        if (path.startsWith("/")) {
            return path;
        }
        return WORKSPACE_ROOT + "/" + path;
    }

    private static String lastPathSegment(String notebookApiPath) {
        int lastSlash = notebookApiPath.lastIndexOf('/');
        return lastSlash >= 0 ? notebookApiPath.substring(lastSlash + 1) : notebookApiPath;
    }

    private static Map<String, Object> buildSampleNotebookContent() {
        Map<String, Object> cell = new LinkedHashMap<>();
        cell.put("cell_type", "code");
        cell.put("source", "print(\"aidp workflow sample\")");
        cell.put("metadata", Map.of("trusted", true));
        cell.put("outputs", List.of());
        cell.put("execution_count", null);

        Map<String, Object> content = new LinkedHashMap<>();
        content.put("metadata", Map.of(
                "kernelspec", Map.of(
                        "display_name", "Python 3",
                        "language", "python",
                        "name", "python3"),
                "language_info", Map.of(
                        "file_extension", ".py",
                        "mimetype", "text/x-python",
                        "name", "python",
                        "nbconvert_exporter", "python",
                        "pygments_lexer", "ipython3",
                        "version", "3.6.8")));
        content.put("nbformat", 4);
        content.put("nbformat_minor", 5);
        content.put("cells", List.of(cell));
        return content;
    }

    private static void logResponse(String operation, int status, Map<String, List<String>> headers, String body) {
        System.out.println("=== " + operation + " ===");
        System.out.println("Status: " + status);
        System.out.println(headers);
        if (body != null && !body.isBlank()) {
            System.out.println("--- Body ---");
            System.out.println(prettyIfJson(body));
        }
    }

    private static String prettyIfJson(String body) {
        String trimmed = body.trim();
        boolean looksLikeJson = (trimmed.startsWith("{") && trimmed.endsWith("}"))
                || (trimmed.startsWith("[") && trimmed.endsWith("]"));

        if (!looksLikeJson) {
            return body;
        }

        try {
            JsonNode node = PRETTY_MAPPER.readTree(trimmed);
            return PRETTY_MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(node);
        } catch (Exception e) {
            return body;
        }
    }

    private static String encodeQueryParam(String value) {
        return URLEncoder.encode(value, StandardCharsets.UTF_8);
    }

    private record ResponsePayload(int status, String body, Map<String, List<String>> headers) {
    }
}

Maven Dependency Example Code

If a client project only needs the dependencies used by the provided sample, start a Maven dependency file with this code:

<dependencies>
  <dependency>
    <groupId>com.oracle.oci.sdk</groupId>
    <artifactId>oci-java-sdk-common</artifactId>
    <version>3.44.1</version>
  </dependency>
  <dependency>
    <groupId>com.oracle.oci.sdk</groupId>
    <artifactId>oci-java-sdk-common-httpclient-jersey</artifactId>
    <version>3.44.1</version>
  </dependency>
  <dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.17.1</version>
  </dependency>
  <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-simple</artifactId>
    <version>1.7.36</version>
  </dependency>
</dependencies>

Task 1: Set Constants

In the Sample code above, set these constants for your environment:

  • TOKEN_FILE_PATH
  • REGION
  • TENANCY_OCID
  • AIDP_OCID
  • AIDP_ENDPOINT
  • API_VERSION

Task 2: Rename Resources (Optional)

You can change these resource names to more descriptive names if needed:

  • WORKSPACE_NAME_PREFIX
  • CLUSTER_NAME_PREFIX
  • NOTEBOOK_FOLDER_PREFIX
  • NOTEBOOK_NAME_PREFIX
  • JOB_NAME_PREFIX

Task 3: Provide Details for the Created Resources

The following control the resources created by the sample code:

  • WORKSPACE_DESCRIPTION
  • DEFAULT_CATALOG_KEY
  • JOB_PATH

Task 4: Set Retry and Polling Behavior

Modify the settings for the following variables to control retry and polling behavior in the sample code:

  • MAX_RETRIES
  • INITIAL_BACKOFF_MS
  • POLL_INTERVAL_MS
  • POLL_TIMEOUT_MS

Task 5: Validate your ~/.oci/config

Make sure your ~/.oci/config has a valid profile, like the example below:

[DEFAULT]
user=ocid1.user.oc1..<your_user_ocid>
tenancy=ocid1.tenancy.oc1..<your_tenancy_ocid>
region=us-phoenix-1
fingerprint=<your_api_key_fingerprint>
key_file=/Users/<you>/.oci/oci_api_key.pem

Task 6: Build and Run

Build and run your sample code:

mvn -DskipTests clean compile
mvn -DskipTests -Dexec.mainClass=org.example.AidpWorkflowClient exec:java

A successful run typically prints:

  • List workspaces returns Status: 200.
  • Create workspace returns Status: 2xx and a workspace key in the body. The workspace key can be reused in follow-on calls.
  • Get/Update/Delete workspace returns Status: 2xx.

Troubleshooting

Common outcomes from unsuccessful runs include:
  • 401 Unauthorized: invalid key/fingerprint, wrong profile, or missing private key file.
  • 403 Forbidden: caller is authenticated but lacks required IAM policies..
  • 404 Not Found: incorrect endpoint, API version, or AI Data Platform Workbench OCID.
  • 429 / 500: transient service errors; retry/backoff is already built into the sample.