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
1420package 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
8369package 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;
8971import org.apache.sling.api.SlingHttpServletRequest ;
9072import 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 ;
9278import 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 ;
9382import org.slf4j.Logger ;
9483import org.slf4j.LoggerFactory ;
9584
9685import javax.servlet.Servlet ;
9786import javax.servlet.ServletException ;
9887import 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})
10493public 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 )
176201boolean isAssetDeleted = assetManager. removeAssetForBinary(binaryFilePath, doSave);
177202if (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
212255import 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 ;
228268import org.osgi.service.component.annotations.Component ;
229269import 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