From 9b388458d3a6f250cae0e2701c30a9b777e20974 Mon Sep 17 00:00:00 2001 From: vibhutikumar <160819926+vibhutikumar07@users.noreply.github.com> Date: Fri, 21 Aug 2026 16:43:56 +0530 Subject: [PATCH 1/9] Release_v1.10.2 updated release version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c188cfe7..ab4efefd 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ - 1.10.1-SNAPSHOT + 1.10.2 21 ${java.version} ${java.version} From 4cd039956d8de525535e33154d0e76035a48aca7 Mon Sep 17 00:00:00 2001 From: vibhutikumar <160819926+vibhutikumar07@users.noreply.github.com> Date: Fri, 21 Aug 2026 16:48:55 +0530 Subject: [PATCH 2/9] Update action.yml --- .github/actions/newrelease/action.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/actions/newrelease/action.yml b/.github/actions/newrelease/action.yml index 04296d55..96598079 100644 --- a/.github/actions/newrelease/action.yml +++ b/.github/actions/newrelease/action.yml @@ -34,8 +34,8 @@ runs: #./ensure-license.sh git config --global user.name 'github-actions[bot]' git config --global user.email 'github-actions[bot]@users.noreply.github.com' - git checkout -b develop + git checkout -b Release_v1.10.2 git add cap-notebook/version.txt git commit -am "Update version to $VERSION" - git push --set-upstream origin develop + git push --set-upstream origin Release_v1.10.2 shell: bash From 8b0de77086a5f68fc9123334ea01d6397898bd34 Mon Sep 17 00:00:00 2001 From: vibhutikumar <160819926+vibhutikumar07@users.noreply.github.com> Date: Fri, 21 Aug 2026 17:04:32 +0530 Subject: [PATCH 3/9] Update main-build-and-deploy-oss.yml --- .github/workflows/main-build-and-deploy-oss.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main-build-and-deploy-oss.yml b/.github/workflows/main-build-and-deploy-oss.yml index 74bfb732..c99c8f12 100644 --- a/.github/workflows/main-build-and-deploy-oss.yml +++ b/.github/workflows/main-build-and-deploy-oss.yml @@ -9,7 +9,7 @@ on: types: [ "released" ] permissions: - contents: read + contents: write jobs: From 3d17c8957578bb587d451150ed4024690f2a9c88 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 21 Aug 2026 11:40:28 +0000 Subject: [PATCH 4/9] Update version to 1.10.2 --- cap-notebook/version.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 cap-notebook/version.txt diff --git a/cap-notebook/version.txt b/cap-notebook/version.txt new file mode 100644 index 00000000..5ad2491c --- /dev/null +++ b/cap-notebook/version.txt @@ -0,0 +1 @@ +1.10.2 From 5f64111275922295cfe3a82792c69cd0deffee64 Mon Sep 17 00:00:00 2001 From: PujaDeshmukh17 Date: Mon, 31 Aug 2026 20:47:54 +0530 Subject: [PATCH 5/9] Fix for uploading issue --- .../helper/AttachmentsHandlerUtils.java | 61 +++++++++++++++---- 1 file changed, 49 insertions(+), 12 deletions(-) diff --git a/sdm/src/main/java/com/sap/cds/sdm/handler/applicationservice/helper/AttachmentsHandlerUtils.java b/sdm/src/main/java/com/sap/cds/sdm/handler/applicationservice/helper/AttachmentsHandlerUtils.java index f5bc8ee9..b9f71050 100644 --- a/sdm/src/main/java/com/sap/cds/sdm/handler/applicationservice/helper/AttachmentsHandlerUtils.java +++ b/sdm/src/main/java/com/sap/cds/sdm/handler/applicationservice/helper/AttachmentsHandlerUtils.java @@ -194,7 +194,7 @@ private static void processAttachmentPaths( List attachmentPaths) { for (String attachmentPath : attachmentPaths) { String entityPath = buildEntityPath(entity, targetEntity, attachmentPath); - String actualPath = buildActualPath(entity, compositionName, attachmentPath); + String actualPath = buildActualPath(entity, compositionName, targetEntity, attachmentPath); // Only add the mapping if both paths are non-null and the key doesn't already exist // This preserves direct attachment mappings from being overwritten by nested ones @@ -272,8 +272,9 @@ private static String buildEntityPath( // Direct attachment: use parent entity path entityPath = parentEntity.getQualifiedName() + "." + attachmentPart; } else { - // Nested attachment: use target entity path to ensure uniqueness - entityPath = targetEntity.getQualifiedName() + "." + attachmentPart; + // Nested attachment: use attachmentPath as-is — it already encodes the owning entity + // (e.g. "AdminService.Sections.attachments"), ensuring uniqueness at any depth. + entityPath = attachmentPath; } return entityPath; } @@ -284,19 +285,34 @@ private static String buildEntityPath( } private static String buildActualPath( - CdsEntity parentEntity, String compositionPropertyName, String attachmentPath) { + CdsEntity parentEntity, + String compositionPropertyName, + CdsEntity targetEntity, + String attachmentPath) { try { String[] pathParts = attachmentPath.split("\\."); if (pathParts.length >= 3) { - // Get the attachment part (last part) String attachmentPart = pathParts[pathParts.length - 1]; - - // Build the new path using parent entity qualified name + composition property name - return parentEntity.getQualifiedName() - + "." - + compositionPropertyName - + "." - + attachmentPart; + String ownerEntityQN = attachmentPath.substring(0, attachmentPath.lastIndexOf('.')); + + if (ownerEntityQN.equals(targetEntity.getQualifiedName())) { + return parentEntity.getQualifiedName() + + "." + + compositionPropertyName + + "." + + attachmentPart; + } else { + String intermediatePath = findPathToEntity(targetEntity, ownerEntityQN, new HashSet<>()); + if (intermediatePath != null) { + return parentEntity.getQualifiedName() + + "." + + compositionPropertyName + + "." + + intermediatePath + + "." + + attachmentPart; + } + } } } catch (Exception e) { logger.warn(SDMUtils.getErrorMessage("FETCH_ATTACHMENT_COMPOSITION_ERROR"), e.getMessage()); @@ -304,6 +320,27 @@ private static String buildActualPath( return null; } + private static String findPathToEntity( + CdsEntity fromEntity, String toEntityQN, Set visited) { + if (visited.contains(fromEntity.getQualifiedName())) return null; + visited.add(fromEntity.getQualifiedName()); + List comps = fromEntity.compositions().collect(java.util.stream.Collectors.toList()); + for (Object comp : comps) { + com.sap.cds.reflect.CdsElement element = (com.sap.cds.reflect.CdsElement) comp; + if (!element.getType().isAssociation()) continue; + CdsAssociationType assocType = (CdsAssociationType) element.getType(); + CdsEntity target = assocType.getTarget(); + if (toEntityQN.equals(target.getQualifiedName())) { + return element.getName(); + } + String subPath = findPathToEntity(target, toEntityQN, visited); + if (subPath != null) { + return element.getName() + "." + subPath; + } + } + return null; + } + private static List> findNestedAttachments( Map entity, String attachmentKey, String parentKey, String currentParentKey) { List> result = new ArrayList<>(); From 87b3348d962df4c109322f1edbbe11c92804d1c7 Mon Sep 17 00:00:00 2001 From: PujaDeshmukh17 Date: Mon, 31 Aug 2026 21:10:45 +0530 Subject: [PATCH 6/9] Pom version update --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ab4efefd..41601bdb 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ - 1.10.2 + 1.10.3-SNAPSHOT 21 ${java.version} ${java.version} From 8d252fdd8e8aadd3d170275f1d99bb975f5e6131 Mon Sep 17 00:00:00 2001 From: PujaDeshmukh17 Date: Tue, 1 Sep 2026 10:20:02 +0530 Subject: [PATCH 7/9] Updated logs --- .../com/sap/cds/sdm/handler/TokenHandler.java | 72 +++++++++++++++++-- .../com/sap/cds/sdm/persistence/DBQuery.java | 6 ++ .../cds/sdm/service/ReadAheadInputStream.java | 24 ++++++- .../sap/cds/sdm/service/SDMServiceImpl.java | 50 +++++++++++-- 4 files changed, 140 insertions(+), 12 deletions(-) diff --git a/sdm/src/main/java/com/sap/cds/sdm/handler/TokenHandler.java b/sdm/src/main/java/com/sap/cds/sdm/handler/TokenHandler.java index ced6701f..f2919d3f 100644 --- a/sdm/src/main/java/com/sap/cds/sdm/handler/TokenHandler.java +++ b/sdm/src/main/java/com/sap/cds/sdm/handler/TokenHandler.java @@ -56,6 +56,7 @@ public String toString(byte[] bytes) { } public SDMCredentials getSDMCredentials() { + logger.debug("START: getSDMCredentials - loading SDM credentials from service binding"); Map uaaCredentials = getUaaCredentials(); Map uaa = (Map) uaaCredentials.get("uaa"); SDMCredentials sdmCredentials = new SDMCredentials(); @@ -63,17 +64,27 @@ public SDMCredentials getSDMCredentials() { sdmCredentials.setUrl(uaaCredentials.get("uri").toString()); sdmCredentials.setClientId(uaa.get("clientid").toString()); sdmCredentials.setClientSecret(uaa.get("clientsecret").toString()); + logger.debug("END: getSDMCredentials - SDM URL: {}", sdmCredentials.getUrl()); return sdmCredentials; } public Map getUaaCredentials() { + logger.debug("START: getUaaCredentials - scanning service bindings for 'sdm' tag"); List allServiceBindings = DefaultServiceBindingAccessor.getInstance().getServiceBindings(); + logger.debug("Total service bindings found: {}", allServiceBindings.size()); ServiceBinding sdmBinding = allServiceBindings.stream() .filter(binding -> binding.getTags().contains("sdm")) .findFirst() - .orElseThrow(() -> new IllegalStateException("SDM binding not found")); + .orElseThrow( + () -> { + logger.error( + "No service binding with 'sdm' tag found among {} bindings", + allServiceBindings.size()); + return new IllegalStateException("SDM binding not found"); + }); + logger.debug("END: getUaaCredentials - SDM binding found"); return sdmBinding.getCredentials(); } @@ -83,10 +94,19 @@ public HttpClient getHttpClient( String subdomain, String type) { + logger.debug( + "START: getHttpClient - type: {}, subdomain: {}, connectionPoolConfig: {}", + type, + subdomain, + connectionPoolConfig != null ? "configured" : "null(using defaults)"); + Map uaaCredentials; if (binding != null && !binding.getCredentials().isEmpty()) { + logger.debug("getHttpClient - using credentials from provided ServiceBinding"); uaaCredentials = binding.getCredentials(); } else { + logger.debug( + "getHttpClient - binding not provided or empty, fetching from service binding registry"); uaaCredentials = getUaaCredentials(); } @@ -94,26 +114,35 @@ public HttpClient getHttpClient( ClientCredentials clientCredentials = new ClientCredentials(uaa.get(CLIENT_ID).toString(), uaa.get(CLIENT_SECRET).toString()); + logger.debug("getHttpClient - clientId: {}", uaa.get(CLIENT_ID).toString()); String baseTokenUrl = uaa.get(SDM_TOKEN_ENDPOINT).toString(); + logger.debug("getHttpClient - base token URL: {}", baseTokenUrl); if (subdomain != null && !subdomain.isEmpty()) { String providerSubdomain = baseTokenUrl.substring(baseTokenUrl.indexOf("/") + 2, baseTokenUrl.indexOf(".")); baseTokenUrl = baseTokenUrl.replace(providerSubdomain, subdomain); + logger.debug( + "getHttpClient - token URL adjusted for subdomain '{}': {}", subdomain, baseTokenUrl); } + String sdmTargetUrl = uaaCredentials.get(SDM_URL).toString(); + logger.debug("getHttpClient - SDM target URL: {}", sdmTargetUrl); + DefaultHttpDestination destination; if (NAMED_USER_FLOW.equals(type)) { + logger.debug("getHttpClient - building NAMED_USER (token exchange) destination"); destination = - OAuth2DestinationBuilder.forTargetUrl(uaaCredentials.get(SDM_URL).toString()) + OAuth2DestinationBuilder.forTargetUrl(sdmTargetUrl) .withTokenEndpoint(baseTokenUrl) .withClient(clientCredentials, OnBehalfOf.NAMED_USER_CURRENT_TENANT) .property( SDMConstants.SDM_DESTINATION_KEY, SDMConstants.SDM_TOKEN_EXCHANGE_DESTINATION) .build(); } else { + logger.debug("getHttpClient - building TECHNICAL_USER (client credentials) destination"); destination = - OAuth2DestinationBuilder.forTargetUrl(uaaCredentials.get(SDM_URL).toString()) + OAuth2DestinationBuilder.forTargetUrl(sdmTargetUrl) .withTokenEndpoint(baseTokenUrl) .withClient(clientCredentials, OnBehalfOf.TECHNICAL_USER_CURRENT_TENANT) .property( @@ -130,20 +159,36 @@ public HttpClient getHttpClient( builder.timeoutMilliseconds((int) timeout.toMillis()); builder.maxConnectionsPerRoute(SDMConstants.MAX_CONNECTIONS); builder.maxConnectionsTotal(SDMConstants.MAX_CONNECTIONS); + logger.debug( + "getHttpClient - using default connection pool: timeout={}ms, maxConnPerRoute={}," + + " maxConnTotal={}", + timeout.toMillis(), + SDMConstants.MAX_CONNECTIONS, + SDMConstants.MAX_CONNECTIONS); } else { builder.timeoutMilliseconds((int) connectionPoolConfig.getTimeout().toMillis()); builder.maxConnectionsPerRoute(connectionPoolConfig.getMaxConnectionsPerRoute()); builder.maxConnectionsTotal(connectionPoolConfig.getMaxConnections()); + logger.debug( + "getHttpClient - using configured connection pool: timeout={}ms, maxConnPerRoute={}," + + " maxConnTotal={}", + connectionPoolConfig.getTimeout().toMillis(), + connectionPoolConfig.getMaxConnectionsPerRoute(), + connectionPoolConfig.getMaxConnections()); } - return builder.build().createHttpClient(destination); + HttpClient httpClient = builder.build().createHttpClient(destination); + logger.debug("END: getHttpClient - HttpClient created for type: {}", type); + return httpClient; } public HttpClient getHttpClientForAuthoritiesFlow( CdsProperties.ConnectionPool connectionPoolConfig, String user) { + logger.debug("START: getHttpClientForAuthoritiesFlow - user: {}", user); Optional destinations = getHttpDestination(user); if (destinations.isPresent()) { + logger.debug("getHttpClientForAuthoritiesFlow - HttpDestination resolved for user: {}", user); DefaultHttpClientFactory.DefaultHttpClientFactoryBuilder builder = DefaultHttpClientFactory.builder(); @@ -152,25 +197,40 @@ public HttpClient getHttpClientForAuthoritiesFlow( builder.timeoutMilliseconds((int) timeout.toMillis()); builder.maxConnectionsPerRoute(SDMConstants.MAX_CONNECTIONS); builder.maxConnectionsTotal(SDMConstants.MAX_CONNECTIONS); + logger.debug( + "getHttpClientForAuthoritiesFlow - using default pool: timeout={}ms", + timeout.toMillis()); } else { builder.timeoutMilliseconds((int) connectionPoolConfig.getTimeout().toMillis()); builder.maxConnectionsPerRoute(connectionPoolConfig.getMaxConnectionsPerRoute()); builder.maxConnectionsTotal(connectionPoolConfig.getMaxConnections()); + logger.debug( + "getHttpClientForAuthoritiesFlow - using configured pool: timeout={}ms", + connectionPoolConfig.getTimeout().toMillis()); } - return builder.build().createHttpClient(destinations.get()); + HttpClient httpClient = builder.build().createHttpClient(destinations.get()); + logger.debug("END: getHttpClientForAuthoritiesFlow - HttpClient created for user: {}", user); + return httpClient; } + logger.warn( + "getHttpClientForAuthoritiesFlow - no HttpDestination found for user: {}," + + " returning null", + user); return null; } private Optional getHttpDestination(String userName) { + logger.debug("START: getHttpDestination - resolving destination for user: {}", userName); HttpDestination httpDestination; try { httpDestination = ServiceBindingDestinationLoader.defaultLoaderChain() .getDestination(getSDMDestinationOptions(userName)); + logger.debug("END: getHttpDestination - destination resolved for user: {}", userName); } catch (Exception exception) { - logger.error("Error with fetching httpdestination " + exception.getCause()); + logger.error( + "Error with fetching httpdestination for user {}: {}", userName, exception.getCause()); httpDestination = null; } return Optional.ofNullable(httpDestination); diff --git a/sdm/src/main/java/com/sap/cds/sdm/persistence/DBQuery.java b/sdm/src/main/java/com/sap/cds/sdm/persistence/DBQuery.java index 33f5dc9e..219c4f2e 100644 --- a/sdm/src/main/java/com/sap/cds/sdm/persistence/DBQuery.java +++ b/sdm/src/main/java/com/sap/cds/sdm/persistence/DBQuery.java @@ -558,6 +558,11 @@ public Map getPropertiesForID( PersistenceService persistenceService, String id, Map properties) { + logger.debug( + "getPropertiesForID - entity: {}, id: {}, fetching properties: {}", + attachmentEntity.getQualifiedName(), + id, + properties.keySet()); CqnSelect q = Select.from(attachmentEntity) .columns(properties.keySet().toArray(new String[0])) @@ -571,6 +576,7 @@ public Map getPropertiesForID( Object value = result.rowCount() > 0 ? result.list().get(0).get(property) : null; propertyValueMap.put(mapKey, value != null ? value.toString() : null); } + logger.debug("getPropertiesForID - id: {}, result map: {}", id, propertyValueMap); return propertyValueMap; } diff --git a/sdm/src/main/java/com/sap/cds/sdm/service/ReadAheadInputStream.java b/sdm/src/main/java/com/sap/cds/sdm/service/ReadAheadInputStream.java index 0ef108b3..b9fcf1b1 100644 --- a/sdm/src/main/java/com/sap/cds/sdm/service/ReadAheadInputStream.java +++ b/sdm/src/main/java/com/sap/cds/sdm/service/ReadAheadInputStream.java @@ -49,6 +49,7 @@ private void preloadChunks() { executor.submit( () -> { try { + int chunkCount = 0; while (totalBytesRead.get() < totalSize) { AtomicReference bufferRef = new AtomicReference<>(new byte[CHUNK_SIZE]); AtomicLong bytesReadAtomic = new AtomicLong(0); @@ -58,21 +59,40 @@ private void preloadChunks() { long bytesRead = bytesReadAtomic.get(); if (bytesRead > 0) { totalBytesRead.addAndGet(bytesRead); + chunkCount++; // Trim buffer if last chunk is smaller if (bytesRead < CHUNK_SIZE) { byte[] trimmedBuffer = new byte[(int) bytesRead]; System.arraycopy(bufferRef.get(), 0, trimmedBuffer, 0, (int) bytesRead); bufferRef.set(trimmedBuffer); + logger.debug( + "preloadChunks - chunk {} trimmed to {} bytes (last chunk)", + chunkCount, + bytesRead); + } else { + logger.debug( + "preloadChunks - chunk {} loaded: {} bytes, totalBytesRead: {}", + chunkCount, + bytesRead, + totalBytesRead.get()); } // Ensure last chunk is enqueued chunkQueue.put(bufferRef.get()); + logger.debug( + "preloadChunks - chunk {} enqueued, queueSize: {}", + chunkCount, + chunkQueue.size()); // Only mark as last chunk after enqueuing the last chunk if (totalBytesRead.get() >= totalSize) { lastChunkLoaded.set(true); - logger.info("Last chunk successfully queued and marked."); + logger.info( + "Last chunk successfully queued and marked. Total chunks: {}," + + " totalBytesRead: {}", + chunkCount, + totalBytesRead.get()); break; } } else { @@ -203,7 +223,7 @@ private synchronized void loadNextChunk() throws IOException { @Override public synchronized int read() throws IOException { - logger.info( + logger.debug( "ReadAheadInputStream.read() called by " + Thread.currentThread().getStackTrace()[2]); if (position.get() >= currentBufferSize) { if (lastChunkLoaded.get()) return -1; // EOF diff --git a/sdm/src/main/java/com/sap/cds/sdm/service/SDMServiceImpl.java b/sdm/src/main/java/com/sap/cds/sdm/service/SDMServiceImpl.java index 15699f18..0d9b162b 100644 --- a/sdm/src/main/java/com/sap/cds/sdm/service/SDMServiceImpl.java +++ b/sdm/src/main/java/com/sap/cds/sdm/service/SDMServiceImpl.java @@ -132,9 +132,19 @@ private void executeHttpPost( CmisDocument cmisDocument, Map finalResponse) throws ServiceException { + logger.debug( + "executeHttpPost - POST URL: {}, file: {}", + uploadFile.getURI(), + cmisDocument.getFileName()); try (var response = (CloseableHttpResponse) httpClient.execute(uploadFile)) { + logger.debug( + "executeHttpPost - response status: {}", response.getStatusLine().getStatusCode()); formResponse(cmisDocument, finalResponse, response); } catch (IOException e) { + logger.error( + "executeHttpPost - IOException for file {}: {}", + cmisDocument.getFileName(), + e.getMessage()); throw new ServiceException( SDMUtils.getErrorMessage("ERROR_IN_SETTING_TIMEOUT"), e.getMessage()); } @@ -152,11 +162,14 @@ private void formResponse( try { String responseString = EntityUtils.toString(response.getEntity()); int responseCode = response.getStatusLine().getStatusCode(); + logger.debug("formResponse - responseCode: {}, file: {}", responseCode, name); + logger.debug("formResponse - raw response body: {}", responseString); if (responseCode == 201 || responseCode == 200) { status = "success"; JSONObject jsonResponse = new JSONObject(responseString); JSONObject succinctProperties = jsonResponse.getJSONObject("succinctProperties"); objectId = succinctProperties.getString("cmis:objectId"); + logger.debug("formResponse - upload success, objectId: {}", objectId); } else { if (responseCode == 409) { JSONObject jsonResponse = new JSONObject(responseString); @@ -165,17 +178,22 @@ private void formResponse( objectId = succinctProperties.getString("cmis:objectId"); if ("Malware Service Exception: Virus found in the file!".equals(message)) { status = "virus"; + logger.debug("formResponse - virus detected for file: {}", name); } else { status = "duplicate"; + logger.debug("formResponse - duplicate file detected: {}", name); } } else if ((responseCode == 403) && (responseString.equals("User does not have required scope"))) { status = "unauthorized"; + logger.warn("formResponse - unauthorized (403) for file: {}", name); } else { JSONObject jsonResponse = new JSONObject(responseString); String message = jsonResponse.getString("message"); status = "fail"; error = message; + logger.warn( + "formResponse - upload failed, responseCode: {}, message: {}", responseCode, message); } } // Construct the final response @@ -186,6 +204,7 @@ private void formResponse( if (!objectId.isEmpty()) { finalResponse.put("objectId", objectId); } + logger.debug("formResponse - END status: {}, objectId: {}", status, objectId); } catch (Exception e) { throw new ServiceException(e.getMessage()); } @@ -312,6 +331,11 @@ public int updateAttachments( return 200; // No updates needed, return success } + logger.debug( + "updateAttachments - PATCH URL: {}, request body keys: {}", + sdmUrl, + updateRequestBody.keySet()); + MultipartEntityBuilder builder = MultipartEntityBuilder.create(); SDMUtils.assembleRequestBodySecondaryTypes( builder, updateRequestBody, objectId); // Adding Secondary Properties to the request body @@ -320,14 +344,17 @@ public int updateAttachments( updateRequest.setEntity(builder.build()); try (var response = (CloseableHttpResponse) httpClient.execute(updateRequest)) { - if (response.getStatusLine().getStatusCode() == 400) { + int statusCode = response.getStatusLine().getStatusCode(); + logger.debug("updateAttachments - response status: {}", statusCode); + if (statusCode == 400) { String responseString = EntityUtils.toString(response.getEntity()); + logger.warn("updateAttachments - 400 Bad Request body: {}", responseString); JSONObject jsonResponse = new JSONObject(responseString); String message = jsonResponse.getString("message"); throw new ServiceException(message); } - logger.debug("END: updateAttachments - status: {}", response.getStatusLine().getStatusCode()); - return response.getStatusLine().getStatusCode(); + logger.debug("END: updateAttachments - status: {}", statusCode); + return statusCode; } catch (IOException e) { logger.error("Error updating attachments: {}", e.getMessage(), e); throw new ServiceException(SDMUtils.getErrorMessage("COULD_NOT_UPDATE_THE_ATTACHMENT"), e); @@ -537,8 +564,10 @@ public String getFolderIdByPath( + parentId + "?cmisselector=object"; HttpGet getFolderRequest = new HttpGet(sdmUrl); + logger.debug("getFolderIdByPath - GET URL: {}", sdmUrl); try (var response = (CloseableHttpResponse) httpClient.execute(getFolderRequest)) { int responseCode = response.getStatusLine().getStatusCode(); + logger.debug("getFolderIdByPath - response code: {}", responseCode); if (responseCode == 200) { JSONObject jsonObject = new JSONObject(EntityUtils.toString(response.getEntity())); folderId = @@ -546,6 +575,8 @@ public String getFolderIdByPath( .getJSONObject("properties") .getJSONObject("cmis:objectId") .getString("value"); + } else if (responseCode == 404) { + logger.debug("getFolderIdByPath - folder not found for path: {}", parentId); } else if (responseCode == 403) { throw new ServiceException(SDMUtils.getErrorMessage("USER_NOT_AUTHORISED_ERROR")); } @@ -575,15 +606,18 @@ public String createFolder( builder.addTextBody("succinct", "true", ContentType.TEXT_PLAIN); HttpEntity multipart = builder.build(); createFolderRequest.setEntity(multipart); + logger.debug("createFolder - POST URL: {}, folder name: {}", sdmUrl, parentId); try (var response = (CloseableHttpResponse) httpClient.execute(createFolderRequest)) { int responseCode = response.getStatusLine().getStatusCode(); String responseBody = EntityUtils.toString(response.getEntity()); + logger.debug("createFolder - response code: {}", responseCode); if (responseCode == 201) { logger.debug("END: createFolder - folder created successfully"); return responseBody; } else if (responseCode == 403) { throw new ServiceException(SDMUtils.getErrorMessage("USER_NOT_AUTHORISED_ERROR")); } else { + logger.error("createFolder - error response body: {}", responseBody); throw new ServiceException( SDMUtils.getErrorMessage("FAILED_TO_CREATE_FOLDER") + ". " + responseBody); } @@ -840,6 +874,11 @@ public Map copyAttachment( builder.addTextBody("succinct", "true"); HttpEntity multipart = builder.build(); uploadFile.setEntity(multipart); + logger.debug( + "copyAttachment - POST URL: {}, sourceId: {}, targetFolderId: {}", + sdmUrl, + cmisDocument.getObjectId(), + cmisDocument.getFolderId()); try (var response = (CloseableHttpResponse) httpClient.execute(uploadFile)) { // Handle response entity @@ -847,12 +886,15 @@ public Map copyAttachment( String responseBody = entity != null ? EntityUtils.toString(entity, StandardCharsets.UTF_8) : ""; - if (response.getStatusLine().getStatusCode() == 201) { + int responseCode = response.getStatusLine().getStatusCode(); + logger.debug("copyAttachment - response code: {}", responseCode); + if (responseCode == 201) { logger.debug("END: copyAttachment - copy successful"); return processCopyAttachmentResponse(responseBody, customPropertiesInSDM); } // On error, throw exception with error information + logger.error("copyAttachment - error response body: {}", responseBody); JSONObject errorJson = new JSONObject(responseBody); String exceptionType = errorJson.optString("exception"); String errorMessage = errorJson.optString("message"); From 153e19ee665a325a46e7bb3f6c552ae93333e13e Mon Sep 17 00:00:00 2001 From: Rashmi Angadi Date: Mon, 7 Sep 2026 10:50:30 +0530 Subject: [PATCH 8/9] Nexus File Download issue because of uploading status in Saved state --- pom.xml | 2 +- .../SDMReadAttachmentsHandler.java | 2 +- .../common/SDMApplicationHandlerHelper.java | 21 ++++++- .../com/sap/cds/sdm/utilities/SDMUtils.java | 57 +++++++++++++------ 4 files changed, 61 insertions(+), 21 deletions(-) diff --git a/pom.xml b/pom.xml index ab4efefd..43e0f514 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ - 1.10.2 + 1.10.3.1-SNAPSHOT 21 ${java.version} ${java.version} diff --git a/sdm/src/main/java/com/sap/cds/sdm/handler/applicationservice/SDMReadAttachmentsHandler.java b/sdm/src/main/java/com/sap/cds/sdm/handler/applicationservice/SDMReadAttachmentsHandler.java index 241e2853..c203f9a8 100644 --- a/sdm/src/main/java/com/sap/cds/sdm/handler/applicationservice/SDMReadAttachmentsHandler.java +++ b/sdm/src/main/java/com/sap/cds/sdm/handler/applicationservice/SDMReadAttachmentsHandler.java @@ -133,7 +133,7 @@ public void processBefore(CdsReadEventContext context) throws IOException { return; } setErrorMessagesInCache(context); - if (context.getTarget().getAnnotationValue(SDMConstants.ANNOTATION_IS_MEDIA_DATA, false)) { + if (SDMApplicationHandlerHelper.isMediaEntity(context.getTarget())) { try { // update the uploadStatus of all blank attachments with success this is for existing // attachments diff --git a/sdm/src/main/java/com/sap/cds/sdm/handler/common/SDMApplicationHandlerHelper.java b/sdm/src/main/java/com/sap/cds/sdm/handler/common/SDMApplicationHandlerHelper.java index 5d8d692d..a4b25156 100644 --- a/sdm/src/main/java/com/sap/cds/sdm/handler/common/SDMApplicationHandlerHelper.java +++ b/sdm/src/main/java/com/sap/cds/sdm/handler/common/SDMApplicationHandlerHelper.java @@ -13,14 +13,31 @@ public final class SDMApplicationHandlerHelper { private static final String ANNOTATION_IS_MEDIA_DATA = "_is_media_data"; /** - * Checks if the entity is a media entity. A media entity is an entity that is annotated with the - * annotation "_is_media_data". + * Checks if the entity is a media entity. A media entity is one that carries the "_is_media_data" + * annotation (set by the CAP attachments plugin on DB-backed attachment entities), or — as a + * fallback for service-layer-only draft entities whose annotation may not be propagated — one + * that has both the SDM-specific "objectId" element and the "content" element that is + * characteristic of the sap.attachments.Attachments aspect. * * @param baseEntity The entity to check * @return true if the entity is a media entity, false otherwise */ public static boolean isMediaEntity(CdsStructuredType baseEntity) { boolean isMedia = baseEntity.getAnnotationValue(ANNOTATION_IS_MEDIA_DATA, false); + if (!isMedia) { + // Fallback for service-layer-only entities (e.g. SupplierBidTermValuesServiceEntity + // attachments) whose inline Composition of Attachments does not receive the + // _is_media_data annotation at runtime. Presence of both "objectId" (SDM-specific + // extension) and "content" (core MediaData field) is a reliable structural signal. + isMedia = + baseEntity.findElement("objectId").isPresent() + && baseEntity.findElement("content").isPresent(); + if (isMedia) { + logger.debug( + "Entity {} identified as media entity via structural fallback (objectId + content)", + baseEntity.getQualifiedName()); + } + } logger.debug("Entity {} isMediaEntity: {}", baseEntity.getQualifiedName(), isMedia); return isMedia; } diff --git a/sdm/src/main/java/com/sap/cds/sdm/utilities/SDMUtils.java b/sdm/src/main/java/com/sap/cds/sdm/utilities/SDMUtils.java index bbe5b9c1..89572bd5 100644 --- a/sdm/src/main/java/com/sap/cds/sdm/utilities/SDMUtils.java +++ b/sdm/src/main/java/com/sap/cds/sdm/utilities/SDMUtils.java @@ -398,9 +398,9 @@ public static Map getSecondaryPropertiesWithInvalidDefinition( title = titleAnnotation.get().getValue().toString(); } else { title = element.getName(); /* - * This is in case the user has not specified a title for the column in the cds - * file (which is optional) - */ + * This is in case the user has not specified a title for the column in the cds + * file (which is optional) + */ } invalidProperties.put(key, title); } @@ -570,7 +570,19 @@ private static void retrieveAnnotations(CdsElement cdsElement, AttachmentInfo at } private static List getKeyElementNames(CdsEntity entity) { - return entity.elements().filter(CdsElement::isKey).map(CdsElement::getName).toList(); + return entity + .elements() + .filter(CdsElement::isKey) + .map( + e -> { + String name = e.getName(); + // _drafts entity key columns have an "up__" prefix (e.g. up__bidUUID, + // up__bookItem_ID). + // The CQN where clause uses the bare field names (bidUUID, bookItem_ID). + // Strip the prefix so the contains-check in fetchUPIDFromCQN matches correctly. + return name.startsWith("up__") ? name.substring(4) : name; + }) + .toList(); } /** @@ -586,26 +598,37 @@ public static String fetchUPIDFromCQN(CqnSelect select, CdsEntity parentEntity) String upID = null; ObjectMapper mapper = new ObjectMapper(); JsonNode root = mapper.readTree(select.toString()); + logger.debug( + "fetchUPIDFromCQN - CQN from.ref: {}", root.path("SELECT").path("from").path("ref")); JsonNode refArray = root.path("SELECT").path("from").path("ref"); - JsonNode secondLast = refArray.get(refArray.size() - 2); - JsonNode whereArray; - if (secondLast != null) { - whereArray = secondLast.path("where"); - } else { - whereArray = refArray; + // Get the actual key field names from the parent entity + List keyElementNames = getKeyElementNames(parentEntity); + + // Scan ref nodes from right to left (excluding the last, which is the attachment entity + // itself) to find the first node that has a where clause containing a matching key. + // This handles deep navigation paths like: + // SourcingEvents(id=...) / items(id=...) / terms(id=...) / initialSlice / itemValue / + // attachments + // where intermediate nodes (initialSlice, itemValue) have no where clause. + JsonNode whereArray = null; + for (int r = refArray.size() - 2; r >= 0; r--) { + JsonNode refNode = refArray.get(r); + if (refNode == null) continue; + JsonNode candidate = refNode.path("where"); + if (candidate != null && !candidate.isMissingNode() && candidate.size() > 0) { + whereArray = candidate; + break; + } } - // If where condition is not present or empty, return null (valid scenario for - // select without - // filter) - if (whereArray == null || whereArray.isMissingNode() || whereArray.size() == 0) { + // If no ref node with a where clause was found, return null (valid scenario for + // select without filter) + if (whereArray == null) { + logger.debug("END: fetchUPIDFromCQN - upID: null (no where clause found in ref chain)"); return null; } - // Get the actual key field names from the parent entity - List keyElementNames = getKeyElementNames(parentEntity); - for (int i = 0; i < whereArray.size(); i++) { JsonNode node = whereArray.get(i); From 79881fceb42b22091d18d942cfd6fbb073ac7ad3 Mon Sep 17 00:00:00 2001 From: Rashmi Angadi Date: Tue, 8 Sep 2026 11:07:08 +0530 Subject: [PATCH 9/9] Non Draft Support --- pom.xml | 2 +- .../SDMReadAttachmentsHandler.java | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 43e0f514..ae1735a1 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ - 1.10.3.1-SNAPSHOT + 1.10.3.2-SNAPSHOT 21 ${java.version} ${java.version} diff --git a/sdm/src/main/java/com/sap/cds/sdm/handler/applicationservice/SDMReadAttachmentsHandler.java b/sdm/src/main/java/com/sap/cds/sdm/handler/applicationservice/SDMReadAttachmentsHandler.java index c203f9a8..5a77a7ec 100644 --- a/sdm/src/main/java/com/sap/cds/sdm/handler/applicationservice/SDMReadAttachmentsHandler.java +++ b/sdm/src/main/java/com/sap/cds/sdm/handler/applicationservice/SDMReadAttachmentsHandler.java @@ -147,6 +147,21 @@ public void processBefore(CdsReadEventContext context) throws IOException { repoValue.getIsAsyncVirusScanEnabled()); Optional attachmentDraftEntity = context.getModel().findEntity(context.getTarget().getQualifiedName() + "_drafts"); + logger.debug( + "Draft entity: {}", + attachmentDraftEntity.isPresent() + ? attachmentDraftEntity.get().getQualifiedName() + : "No draft entity"); + Optional attachmentActiveEntity = + context.getModel().findEntity(context.getTarget().getQualifiedName()); + logger.debug( + "Active entity: {}", + attachmentActiveEntity.isPresent() + ? attachmentActiveEntity.get().getQualifiedName() + : "No active entity"); + if (attachmentDraftEntity.isEmpty() && attachmentActiveEntity.isPresent()) { + attachmentDraftEntity = attachmentActiveEntity; + } String upIdKey = "", upID = ""; if (attachmentDraftEntity.isPresent()) { upIdKey = SDMUtils.getUpIdKey(attachmentDraftEntity.get());