Skip to content

Commit bfe825c

Browse files
author
Himani Chauhan
committed
feat(migration): batch BPA findings with paging and unified helper behavior
1 parent f95c20e commit bfe825c

18 files changed

Lines changed: 2012 additions & 1052 deletions

plugins/aem/cloud-service/skills/best-practices/references/asset-manager-create.md

Lines changed: 127 additions & 108 deletions
Large diffs are not rendered by default.
Lines changed: 163 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
1-
# Asset Manager Path B: Delete → HTTP Assets API
1+
# Asset Manager Path B: Delete
22

3-
For files using deprecated `removeAssetForBinary()`.
3+
For files using `AssetManager.removeAssetForBinary(binaryFilePath, doSave)` — an API that
4+
**does not exist on AEM as a Cloud Service**.
45

5-
This deprecated API is replaced with the **HTTP Assets API** `DELETE /api/assets{path}`.
6+
On AEMaaCS, asset deletion splits two ways:
7+
8+
| Where is the deleter running? | Use |
9+
|------------------------------|-----|
10+
| **Inside the AEM JVM** (servlet, Sling job, scheduler, workflow step) | Service-user `ResourceResolver` + `resolver.delete(resource)` + `resolver.commit()`. **Never** call AEM's own HTTP API from inside the JVM. |
11+
| **Outside AEM** (ingestion worker, integration backend, CLI) | HTTP Assets API `DELETE /api/assets{path}` with an **IMS / dev-console bearer token** (never a static password). |
612

713
---
814

915
## Complete Example: Before and After
1016

11-
### Before (Legacy AssetManager API)
17+
### Before (removed API)
1218

1319
```java
1420
package com.example.servlets;
@@ -32,9 +38,9 @@ public class DeleteAssetServlet extends SlingAllMethodsServlet {
3238
@Override
3339
protected void doDelete(SlingHttpServletRequest request, SlingHttpServletResponse response)
3440
throws ServletException, IOException {
35-
41+
3642
String binaryFilePath = request.getParameter("path");
37-
43+
3844
try {
3945
boolean isDeleted = assetManager.removeAssetForBinary(binaryFilePath, true);
4046
response.setContentType("text/plain");
@@ -54,189 +60,229 @@ public class DeleteAssetServlet extends SlingAllMethodsServlet {
5460
}
5561
```
5662

57-
### After (Cloud Service Compatible - Client-Side)
63+
### After — in-JVM delete with a service-user resolver (recommended)
5864

59-
**Client-side JavaScript:**
60-
```javascript
61-
async function deleteAsset(assetPath, host, credentials) {
62-
try {
63-
const response = await axios.delete(`${host}/api/assets${assetPath}`, {
64-
auth: {
65-
username: credentials.username,
66-
password: credentials.password
67-
}
68-
});
69-
return response.status === 200 || response.status === 204;
70-
} catch (error) {
71-
if (error.response?.status === 404) {
72-
return false; // Asset not found
73-
}
74-
throw error;
75-
}
76-
}
77-
```
65+
Server-side code that already has a trusted `ResourceResolver` should delete assets directly.
66+
No HTTP, no credentials, no self-loopback.
7867

79-
### After (Cloud Service Compatible - Server-Side Java)
80-
81-
**If servlet must remain:**
8268
```java
8369
package com.example.servlets;
8470

85-
import org.apache.http.client.HttpClient;
86-
import org.apache.http.client.methods.HttpDelete;
87-
import org.apache.http.impl.client.HttpClientBuilder;
88-
import org.osgi.service.component.annotations.Component;
8971
import org.apache.sling.api.SlingHttpServletRequest;
9072
import org.apache.sling.api.SlingHttpServletResponse;
91-
import org.apache.sling.api.servlets.SlingAllMethodsServlet;
73+
import org.apache.sling.api.resource.LoginException;
74+
import org.apache.sling.api.resource.PersistenceException;
75+
import org.apache.sling.api.resource.Resource;
76+
import org.apache.sling.api.resource.ResourceResolver;
77+
import org.apache.sling.api.resource.ResourceResolverFactory;
9278
import org.apache.sling.api.servlets.ServletResolverConstants;
79+
import org.apache.sling.api.servlets.SlingAllMethodsServlet;
80+
import org.osgi.service.component.annotations.Component;
81+
import org.osgi.service.component.annotations.Reference;
9382
import org.slf4j.Logger;
9483
import org.slf4j.LoggerFactory;
9584

9685
import javax.servlet.Servlet;
9786
import javax.servlet.ServletException;
9887
import java.io.IOException;
99-
import java.util.Base64;
88+
import java.util.Collections;
10089

10190
@Component(service = Servlet.class, property = {
102-
ServletResolverConstants.SLING_SERVLET_PATHS + "=/bin/deleteasset"
91+
ServletResolverConstants.SLING_SERVLET_PATHS + "=/bin/deleteasset"
10392
})
10493
public class DeleteAssetServlet extends SlingAllMethodsServlet {
10594

10695
private static final Logger LOG = LoggerFactory.getLogger(DeleteAssetServlet.class);
10796

97+
@Reference
98+
private ResourceResolverFactory resolverFactory;
99+
108100
@Override
109101
protected void doDelete(SlingHttpServletRequest request, SlingHttpServletResponse response)
110102
throws ServletException, IOException {
111-
103+
112104
String assetPath = request.getParameter("path");
113-
if (assetPath == null || assetPath.isEmpty()) {
105+
if (assetPath == null || assetPath.isEmpty() || !assetPath.startsWith("/content/dam/")) {
114106
response.setStatus(400);
115-
response.getWriter().write("{\"error\":\"path parameter required\"}");
107+
response.setContentType("application/json");
108+
response.getWriter().write("{\"error\":\"valid /content/dam path parameter required\"}");
116109
return;
117110
}
118111

119-
try {
120-
// Use HTTP Assets API
121-
String host = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort();
122-
String apiUrl = host + "/api/assets" + assetPath;
123-
124-
HttpClient client = HttpClientBuilder.create().build();
125-
HttpDelete deleteRequest = new HttpDelete(apiUrl);
126-
127-
// Add authentication header (use service user credentials)
128-
String auth = Base64.getEncoder().encodeToString(
129-
(request.getUserPrincipal().getName() + ":password").getBytes()
130-
);
131-
deleteRequest.setHeader("Authorization", "Basic " + auth);
132-
133-
org.apache.http.HttpResponse httpResponse = client.execute(deleteRequest);
134-
int statusCode = httpResponse.getStatusLine().getStatusCode();
135-
136-
response.setContentType("application/json");
137-
if (statusCode == 200 || statusCode == 204) {
138-
response.getWriter().write("{\"success\":true,\"message\":\"Asset deleted: " + assetPath + "\"}");
139-
} else if (statusCode == 404) {
112+
try (ResourceResolver resolver = resolverFactory.getServiceResourceResolver(
113+
Collections.singletonMap(ResourceResolverFactory.SUBSERVICE, "asset-admin-service"))) {
114+
115+
Resource resource = resolver.getResource(assetPath);
116+
if (resource == null) {
140117
response.setStatus(404);
141-
response.getWriter().write("{\"error\":\"Asset not found: " + assetPath + "\"}");
142-
} else {
143-
response.setStatus(statusCode);
144-
response.getWriter().write("{\"error\":\"Delete failed with status: " + statusCode + "\"}");
118+
response.setContentType("application/json");
119+
response.getWriter().write("{\"error\":\"asset not found\"}");
120+
return;
145121
}
146-
147-
} catch (Exception e) {
148-
LOG.error("Error deleting asset: {}", assetPath, e);
122+
123+
resolver.delete(resource);
124+
resolver.commit();
125+
126+
response.setContentType("application/json");
127+
response.getWriter().write("{\"success\":true}");
128+
LOG.info("Deleted asset at {}", assetPath);
129+
130+
} catch (LoginException e) {
131+
LOG.error("Could not open service resolver for subservice 'asset-admin-service'", e);
132+
response.setStatus(500);
133+
response.setContentType("application/json");
134+
response.getWriter().write("{\"error\":\"internal error\"}");
135+
} catch (PersistenceException e) {
136+
LOG.error("Commit failed while deleting asset {}", assetPath, e);
149137
response.setStatus(500);
150-
response.getWriter().write("{\"error\":\"Internal server error\"}");
138+
response.setContentType("application/json");
139+
response.getWriter().write("{\"error\":\"internal error\"}");
151140
}
152141
}
153142
}
154143
```
155144

156-
**Key Changes:**
157-
- ✅ Removed `AssetManager.removeAssetForBinary()` calls
158-
- ✅ Migrated to HTTP Assets API `DELETE /api/assets{path}`
159-
- ✅ Removed Felix SCR → OSGi DS annotations
160-
- ✅ Replaced `System.out/err` → SLF4J Logger
161-
- ✅ Used HttpClient for server-side API calls
162-
- ✅ Proper error handling and status codes
145+
Supporting Repoinit / service-user mapping (in `ui.config`):
146+
147+
```
148+
create service user asset-admin-service
149+
150+
set ACL for asset-admin-service
151+
allow jcr:read,rep:write,jcr:removeNode,jcr:removeChildNodes on /content/dam
152+
end
153+
```
154+
155+
```json
156+
{
157+
"user.mapping": [
158+
"com.example.mybundle:asset-admin-service=[asset-admin-service]"
159+
]
160+
}
161+
```
162+
163+
### After — external-caller delete via the HTTP Assets API
164+
165+
If the deleter is **outside** the AEM JVM (integration backend, CLI, worker), call the HTTP API
166+
with an IMS or dev-console bearer token — not with a hardcoded password.
167+
168+
```javascript
169+
import axios from 'axios';
170+
171+
export async function deleteAsset({ host, assetPath, bearerToken }) {
172+
const response = await axios.delete(`${host}/api/assets${assetPath}`, {
173+
headers: { Authorization: `Bearer ${bearerToken}` },
174+
validateStatus: s => s === 200 || s === 204 || s === 404
175+
});
176+
return response.status !== 404;
177+
}
178+
```
179+
180+
**Key changes:**
181+
182+
- Removed use of `AssetManager.removeAssetForBinary()` (the API is gone on AEMaaCS).
183+
- Server-side deletion goes through `ResourceResolver#delete` + `commit`, using a service user
184+
provisioned via Repoinit.
185+
- No hardcoded `":password"` anywhere. External callers use an IMS / dev-console token.
186+
- Removed Felix SCR → OSGi DS.
187+
- Replaced `System.out` / `System.err` / `printStackTrace` with SLF4J.
163188

164189
---
165190

166191
## Pattern prerequisites
167192

168-
Read [aem-cloud-service-pattern-prerequisites.md](aem-cloud-service-pattern-prerequisites.md) for Java/OSGi hygiene. Asset delete scope follows **this file** and `asset-manager.md` only.
169-
170-
## D1: Replace removeAssetForBinary with HTTP Assets API
193+
Read [aem-cloud-service-pattern-prerequisites.md](aem-cloud-service-pattern-prerequisites.md)
194+
for Java/OSGi hygiene. Asset delete scope follows this file and
195+
[`asset-manager.md`](asset-manager.md) only.
171196

172-
**Remove deprecated API usage:**
197+
## D1: Replace `removeAssetForBinary` with `ResourceResolver#delete` (in-JVM)
173198

174199
```java
175-
// BEFORE (deprecated)
200+
// BEFORE (removed API)
176201
boolean isAssetDeleted = assetManager.removeAssetForBinary(binaryFilePath, doSave);
177202
if (isAssetDeleted) {
178203
response.getWriter().write("Asset deleted successfully: " + binaryFilePath);
179204
} else {
180205
response.getWriter().write("Failed to delete asset.");
181206
}
182207

183-
// AFTER (Cloud Service — use HTTP Assets API)
184-
// Option A: Call HTTP API from Java (requires HttpClient/HttpURLConnection)
185-
// DELETE {host}/api/assets{path}
186-
// with basic auth
187-
//
188-
// Option B: Migrate to client-side delete using HTTP API:
189-
// const response = await axios.delete(`${host}/api/assets${assetPath}`, {
190-
// auth: { username, password }
191-
// });
208+
// AFTER — in-JVM
209+
try (ResourceResolver resolver = resolverFactory.getServiceResourceResolver(
210+
Collections.singletonMap(ResourceResolverFactory.SUBSERVICE, "asset-admin-service"))) {
211+
212+
Resource resource = resolver.getResource(assetPath); // repository path, not binary path
213+
if (resource == null) {
214+
// asset already gone
215+
return;
216+
}
217+
resolver.delete(resource);
218+
resolver.commit();
219+
} catch (LoginException e) {
220+
LOG.error("Could not open service resolver for subservice 'asset-admin-service'", e);
221+
} catch (PersistenceException e) {
222+
LOG.error("Commit failed while deleting asset {}", assetPath, e);
223+
}
192224
```
193225

194-
**HTTP API delete example (client-side):**
226+
Notes:
227+
228+
- `assetPath` must be the **repository path** (e.g. `/content/dam/site/file.jpg`) — the old
229+
"binary path" concept does not exist on AEMaaCS.
230+
- The service user behind the `SUBSERVICE` name must have
231+
`jcr:removeNode` / `jcr:removeChildNodes` on the asset tree (Repoinit rule above).
232+
- Do not call AEM's own HTTP API (`/api/assets`) from inside the JVM — it adds an extra hop,
233+
requires credentials you shouldn't store, and breaks idempotency.
234+
235+
## D2: For external callers, use the HTTP Assets API with a bearer token
236+
195237
```javascript
196-
const response = await axios.delete(`${host}/api/assets${assetPath}`, {
197-
auth: { username, password }
238+
await axios.delete(`${host}/api/assets${assetPath}`, {
239+
headers: { Authorization: `Bearer ${bearerToken}` }
198240
});
199241
```
200242

201-
**ResourceResolver + logging:** Apply [aem-cloud-service-pattern-prerequisites.md](aem-cloud-service-pattern-prerequisites.md) for any remaining servlet or service code.
243+
Where `bearerToken` comes from:
202244

203-
## D2: Update imports
245+
- **Adobe Developer Console / IMS** service credentials (server-to-server), **or**
246+
- A short-lived user token obtained via the AEM login flow.
204247

205-
**Remove (when deprecated AssetManager delete usage is removed):**
206-
```java
207-
import com.day.cq.dam.api.AssetManager; // if no longer needed
208-
```
248+
**Never** hardcode usernames and passwords in source or pass `":password"` strings.
249+
250+
## D3: Update imports
251+
252+
**Remove** (when all `AssetManager` delete calls are gone):
209253

210-
**Keep (if AssetManager still used for other operations):**
211254
```java
212255
import com.day.cq.dam.api.AssetManager;
213256
```
214257

215-
**Add (for logging):**
216-
```java
217-
import org.slf4j.Logger;
218-
import org.slf4j.LoggerFactory;
219-
```
258+
**Keep** if the file still uses `AssetManager.getAsset(...)` for reads.
220259

221-
**Remove (Felix SCR, if migrated):**
222-
```java
223-
import org.apache.felix.scr.annotations.*;
224-
```
260+
**Add** (for the in-JVM delete path):
225261

226-
**Add (OSGi DS, if migrated):**
227262
```java
263+
import org.apache.sling.api.resource.LoginException;
264+
import org.apache.sling.api.resource.PersistenceException;
265+
import org.apache.sling.api.resource.Resource;
266+
import org.apache.sling.api.resource.ResourceResolver;
267+
import org.apache.sling.api.resource.ResourceResolverFactory;
228268
import org.osgi.service.component.annotations.Component;
229269
import org.osgi.service.component.annotations.Reference;
230-
import org.apache.sling.api.servlets.ServletResolverConstants;
231-
import javax.servlet.Servlet;
270+
import org.slf4j.Logger;
271+
import org.slf4j.LoggerFactory;
272+
import java.util.Collections;
232273
```
233274

275+
Remove Felix SCR imports per [scr-to-osgi-ds.md](scr-to-osgi-ds.md).
276+
234277
---
235278

236-
# Validation
279+
## Validation
237280

238-
- [ ] No `removeAssetForBinary(binaryFilePath, doSave)` calls remain
239-
- [ ] HTTP Assets API `DELETE /api/assets{path}` used (client or server)
240-
- [ ] [aem-cloud-service-pattern-prerequisites.md](aem-cloud-service-pattern-prerequisites.md) satisfied for SCR, resolver, logging
241-
- [ ] `@Reference` AssetManager removed if no longer needed for delete flows
242-
- [ ] Code compiles: `mvn clean compile`
281+
- [ ] No `removeAssetForBinary(` calls remain.
282+
- [ ] In-JVM deletes use `resolver.delete(resource)` + `resolver.commit()` with a service-user resolver; the servlet no longer calls AEM's own HTTP API.
283+
- [ ] Service user + mapping provisioned via Repoinit in `ui.config`, with ACLs granting `jcr:removeNode` / `jcr:removeChildNodes` on the asset tree.
284+
- [ ] No hardcoded credentials (`":password"`, plain-text basic auth strings) anywhere.
285+
- [ ] External-caller examples use a bearer token — not basic auth with a literal password.
286+
- [ ] [aem-cloud-service-pattern-prerequisites.md](aem-cloud-service-pattern-prerequisites.md) satisfied for SCR, resolver, logging.
287+
- [ ] `@Reference AssetManager` removed if no longer needed for delete flows.
288+
- [ ] Code compiles: `mvn clean compile`.

0 commit comments

Comments
 (0)