Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* © 2026 SAP SE or an SAP affiliate company and cds-feature-ai contributors.
*/
package com.sap.cds.feature.aicore.core;

/** Element name constants for AICore CDS entities, mirroring the CDS model definitions. */
public final class AICoreElements {

private AICoreElements() {}

public static final class Deployment {

private Deployment() {}

public static final String ID = "id";
public static final String DEPLOYMENT_URL = "deploymentUrl";
public static final String CONFIGURATION_ID = "configurationId";
public static final String CONFIGURATION_NAME = "configurationName";
public static final String EXECUTABLE_ID = "executableId";
public static final String SCENARIO_ID = "scenarioId";
public static final String STATUS = "status";
public static final String STATUS_MESSAGE = "statusMessage";
public static final String TARGET_STATUS = "targetStatus";
public static final String LAST_OPERATION = "lastOperation";
public static final String LATEST_RUNNING_CONFIGURATION_ID = "latestRunningConfigurationId";
public static final String TTL = "ttl";
public static final String CREATED_AT = "createdAt";
public static final String MODIFIED_AT = "modifiedAt";
public static final String SUBMISSION_TIME = "submissionTime";
public static final String START_TIME = "startTime";
public static final String COMPLETION_TIME = "completionTime";
public static final String RESOURCE_GROUP = "resourceGroup";
}

public static final class ResourceGroup {

private ResourceGroup() {}

public static final String RESOURCE_GROUP_ID = "resourceGroupId";
public static final String TENANT_ID = "tenantId";
public static final String STATUS = "status";
public static final String STATUS_MESSAGE = "statusMessage";
public static final String CREATED_AT = "createdAt";
public static final String LABELS = "labels";
}

public static final class Configuration {

private Configuration() {}

public static final String ID = "id";
public static final String NAME = "name";
public static final String EXECUTABLE_ID = "executableId";
public static final String SCENARIO_ID = "scenarioId";
public static final String CREATED_AT = "createdAt";
public static final String PARAMETER_BINDINGS = "parameterBindings";
public static final String INPUT_ARTIFACT_BINDINGS = "inputArtifactBindings";
public static final String RESOURCE_GROUP = "resourceGroup";
}

public static final class Label {

private Label() {}

public static final String KEY = "key";
public static final String VALUE = "value";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import com.sap.cds.services.cds.CqnService;
import com.sap.cloud.sdk.services.openapi.apache.apiclient.ApiClient;
import io.github.resilience4j.retry.Retry;
import java.util.Map;

public interface AICoreService extends CqnService {

Expand All @@ -24,16 +23,4 @@ public interface AICoreService extends CqnService {
boolean isMultiTenancyEnabled();

Retry getRetry();

String getDefaultResourceGroup();

String getResourceGroupPrefix();

Map<String, String> getTenantResourceGroupCache();

Map<String, String> getResourceGroupDeploymentCache();

void clearTenantCache(String tenantId);

String resolveResourceGroupFromKeys(Map<String, Object> keys);
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,24 @@ public class AICoreServiceImpl extends AbstractCqnService implements AICoreServi
private final AiCoreService sdkService;

public AICoreServiceImpl(String name, CdsRuntime runtime, boolean multiTenancyEnabled) {
this(
name,
runtime,
multiTenancyEnabled,
new DeploymentApi(),
new ConfigurationApi(),
new ResourceGroupApi(),
new AiCoreService());
}

public AICoreServiceImpl(
String name,
CdsRuntime runtime,
boolean multiTenancyEnabled,
DeploymentApi deploymentApi,
ConfigurationApi configurationApi,
ResourceGroupApi resourceGroupApi,
AiCoreService sdkService) {
super(name, runtime);
this.multiTenancyEnabled = multiTenancyEnabled;
CdsEnvironment env = runtime.getEnvironment();
Expand All @@ -83,10 +101,10 @@ public AICoreServiceImpl(String name, CdsRuntime runtime, boolean multiTenancyEn
this.tenantResourceGroupCache = newCache();
this.resourceGroupDeploymentCache = newCache();
this.deploymentLocks = newCache();
this.deploymentApi = new DeploymentApi();
this.configurationApi = new ConfigurationApi();
this.resourceGroupApi = new ResourceGroupApi();
this.sdkService = new AiCoreService();
this.deploymentApi = deploymentApi;
this.configurationApi = configurationApi;
this.resourceGroupApi = resourceGroupApi;
this.sdkService = sdkService;
}

private static <V> Cache<String, V> newCache() {
Expand Down Expand Up @@ -156,22 +174,18 @@ public Retry getRetry() {
return retry;
}

@Override
public String getDefaultResourceGroup() {
return defaultResourceGroup;
}

@Override
public String getResourceGroupPrefix() {
return resourceGroupPrefix;
}

@Override
public Map<String, String> getTenantResourceGroupCache() {
return tenantResourceGroupCache.asMap();
}

@Override
public Map<String, String> getResourceGroupDeploymentCache() {
return resourceGroupDeploymentCache.asMap();
}
Expand All @@ -192,7 +206,6 @@ public ResourceGroupApi getResourceGroupApi() {
return resourceGroupApi;
}

@Override
public String resolveResourceGroupFromKeys(Map<String, Object> keys) {
if (keys.containsKey("resourceGroup_resourceGroupId")) {
return (String) keys.get("resourceGroup_resourceGroupId");
Expand All @@ -205,7 +218,6 @@ public String resolveResourceGroupFromKeys(Map<String, Object> keys) {
return resourceGroupForTenant(tenantId);
}

@Override
public void clearTenantCache(String tenantId) {
String resourceGroupId = tenantResourceGroupCache.asMap().remove(tenantId);
if (resourceGroupId != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ public void afterSubscribe(SubscribeEventContext context) {
} catch (Exception e) {
throw new ServiceException(
ErrorStatuses.SERVER_ERROR,
"Failed to create AI Core resources for tenant: " + tenantId,
"Failed to create AI Core resources for tenant: {}",
tenantId,
e);
Comment on lines 42 to 46

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: The ServiceException constructor is being called with the signature (ErrorStatus, String messageTemplate, Object... args) where the last argument is both the tenantId vararg and the Throwable cause. Depending on how the ServiceException constructor is overloaded, the exception e may be treated as a plain message argument (i.e., e.toString() interpolated into {}), not as the cause — meaning the original stack trace would be lost in the logged/serialized error.

Verify that ServiceException has an overload that unambiguously accepts a trailing Throwable cause separately from the vararg message parameters. If not, pass the cause explicitly using the dedicated two-argument form or cast to Throwable to resolve the overload correctly.


Please provide feedback on the review comment by checking the appropriate box:

  • 🌟 Awesome comment, a human might have missed that.
  • ✅ Helpful comment
  • 🤷 Neutral
  • ❌ This comment is not helpful

}
}
Expand Down Expand Up @@ -79,7 +80,9 @@ private void deleteResourceGroupForTenant(String tenantId) {
}
throw new ServiceException(
ErrorStatuses.SERVER_ERROR,
"Failed to delete AI Core resource group " + resourceGroupId + " for tenant " + tenantId,
"Failed to delete AI Core resource group {} for tenant {}",
resourceGroupId,
tenantId,
e);
}
}
Expand All @@ -103,7 +106,8 @@ private String resolveResourceGroupId(String tenantId) {
} catch (OpenApiRequestException e) {
throw new ServiceException(
ErrorStatuses.SERVER_ERROR,
"Failed to look up AI Core resource group for tenant " + tenantId,
"Failed to look up AI Core resource group for tenant {}",
tenantId,
e);
}
List<BckndResourceGroup> resources = result.getResources();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,27 +68,22 @@ public Retry getRetry() {
return retry;
}

@Override
public String getDefaultResourceGroup() {
return defaultResourceGroup;
}

@Override
public String getResourceGroupPrefix() {
return resourceGroupPrefix;
}

@Override
public Map<String, String> getTenantResourceGroupCache() {
return tenantResourceGroupCache;
}

@Override
public Map<String, String> getResourceGroupDeploymentCache() {
return resourceGroupDeploymentCache;
}

@Override
public void clearTenantCache(String tenantId) {
String resourceGroupId = tenantResourceGroupCache.remove(tenantId);
if (resourceGroupId != null) {
Expand All @@ -99,7 +94,6 @@ public void clearTenantCache(String tenantId) {
}
}

@Override
public String resolveResourceGroupFromKeys(Map<String, Object> keys) {
if (keys.containsKey("resourceGroup_resourceGroupId")) {
return (String) keys.get("resourceGroup_resourceGroupId");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,10 @@

import com.sap.cds.feature.aicore.core.AICoreServiceImpl;
import com.sap.cds.services.handler.EventHandler;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

abstract class AbstractCrudHandler implements EventHandler {

Expand All @@ -34,7 +32,7 @@ protected static Map<String, Object> merge(Map<String, Object> keys, Map<String,
}

protected static <T, R> List<R> mapResources(List<T> resources, Function<T, R> mapper) {
if (resources == null) return new ArrayList<>();
return resources.stream().map(mapper).collect(Collectors.toList());
if (resources == null) return List.of();
return resources.stream().map(mapper).toList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
*/
package com.sap.cds.feature.aicore.core.handler;

import static com.sap.cds.feature.aicore.core.AICoreElements.Deployment;

import com.sap.ai.sdk.core.client.DeploymentApi;
import com.sap.ai.sdk.core.model.AiDeploymentModificationRequest;
import com.sap.ai.sdk.core.model.AiDeploymentTargetStatus;
Expand Down Expand Up @@ -40,7 +42,7 @@ public void onResourceGroupForTenant(EventContext context) {
@On(event = "stop", entity = AICoreService.DEPLOYMENTS)
public void onStop(EventContext context) {
Map<String, Object> keys = asMap(context.get("keys"));
String deploymentId = (String) keys.get("id");
String deploymentId = (String) keys.get(Deployment.ID);
String resourceGroupId = resolveResourceGroup(keys);

DeploymentApi api = service.getDeploymentApi();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
*/
package com.sap.cds.feature.aicore.core.handler;

import static com.sap.cds.feature.aicore.core.AICoreElements.Configuration;
import static com.sap.cds.feature.aicore.core.AICoreElements.Label;

import com.sap.ai.sdk.core.client.ConfigurationApi;
import com.sap.ai.sdk.core.model.AiConfiguration;
import com.sap.ai.sdk.core.model.AiConfigurationBaseData;
Expand Down Expand Up @@ -54,12 +57,12 @@ public void onRead(CdsReadEventContext context) {
keys,
values);

String id = (String) keys.get("id");
String id = (String) keys.get(Configuration.ID);
if (id != null) {
AiConfiguration config = configurationApi.get(resourceGroupId, id);
context.setResult(List.of(toMap(config, resourceGroupId)));
} else {
String scenarioId = (String) values.get("scenarioId");
String scenarioId = (String) values.get(Configuration.SCENARIO_ID);
AiConfigurationList result =
configurationApi.query(resourceGroupId, scenarioId, null, null, null, null, null, null);
List<Map<String, Object>> results =
Expand All @@ -77,9 +80,9 @@ public void onCreate(CdsCreateEventContext context) {

for (Map<String, Object> entry : entries) {
String resourceGroupId = resolveResourceGroup(entry);
String name = (String) entry.get("name");
String executableId = (String) entry.get("executableId");
String scenarioId = (String) entry.get("scenarioId");
String name = (String) entry.get(Configuration.NAME);
String executableId = (String) entry.get(Configuration.EXECUTABLE_ID);
String scenarioId = (String) entry.get(Configuration.SCENARIO_ID);

AiConfigurationBaseData request =
AiConfigurationBaseData.create()
Expand All @@ -89,22 +92,22 @@ public void onCreate(CdsCreateEventContext context) {

@SuppressWarnings("unchecked")
List<Map<String, Object>> paramBindings =
(List<Map<String, Object>>) entry.get("parameterBindings");
(List<Map<String, Object>>) entry.get(Configuration.PARAMETER_BINDINGS);
if (paramBindings != null) {
List<AiParameterArgumentBinding> sdkBindings =
paramBindings.stream()
.map(
p ->
AiParameterArgumentBinding.create()
.key((String) p.get("key"))
.value((String) p.get("value")))
.key((String) p.get(Label.KEY))
.value((String) p.get(Label.VALUE)))
.toList();
request.parameterBindings(sdkBindings);
}

var response = configurationApi.create(resourceGroupId, request);
CdsData result = CdsData.create(entry);
result.put("id", response.getId());
result.put(Configuration.ID, response.getId());
results.add(result);
logger.debug(
"Created configuration {} in resource group {}", response.getId(), resourceGroupId);
Expand All @@ -114,36 +117,36 @@ public void onCreate(CdsCreateEventContext context) {

private CdsData toMap(AiConfiguration config, String resourceGroupId) {
CdsData data = CdsData.create();
data.put("id", config.getId());
data.put("name", config.getName());
data.put("executableId", config.getExecutableId());
data.put("scenarioId", config.getScenarioId());
data.put("createdAt", config.getCreatedAt());
data.put(Configuration.ID, config.getId());
data.put(Configuration.NAME, config.getName());
data.put(Configuration.EXECUTABLE_ID, config.getExecutableId());
data.put(Configuration.SCENARIO_ID, config.getScenarioId());
data.put(Configuration.CREATED_AT, config.getCreatedAt());
if (config.getParameterBindings() != null) {
List<CdsData> bindings =
config.getParameterBindings().stream()
.map(
b -> {
CdsData bm = CdsData.create();
bm.put("key", b.getKey());
bm.put("value", b.getValue());
bm.put(Label.KEY, b.getKey());
bm.put(Label.VALUE, b.getValue());
return bm;
})
.toList();
data.put("parameterBindings", bindings);
data.put(Configuration.PARAMETER_BINDINGS, bindings);
}
if (config.getInputArtifactBindings() != null) {
List<CdsData> bindings =
config.getInputArtifactBindings().stream()
.map(
b -> {
CdsData bm = CdsData.create();
bm.put("key", b.getKey());
bm.put(Label.KEY, b.getKey());
bm.put("artifactId", b.getArtifactId());
return bm;
})
.toList();
data.put("inputArtifactBindings", bindings);
data.put(Configuration.INPUT_ARTIFACT_BINDINGS, bindings);
}
data.putPath("resourceGroup.resourceGroupId", resourceGroupId);
return data;
Expand Down
Loading
Loading