Adobe AEM (CQ) 5.6 Remove renditions workflow - workflow

I'm looking for a way (preferably a workflow) that removes/cleans renditions!
My problem is that over time I have loads of images with renditions that are no longer used.
Is there a good way to clean this and "reclaim" my disk space? :)

Though i would like to suggest you sling servlet route to remove as you will have more control over what should be deleted and from which path.
You can reuse some of the code from below as well.
I created a sample program a few weeks back to remove renditions except the original one whenever a new image was added and i was using workflows:
The code below was a component. A workflow was created and this class was then added as a process step to a workflow and the same workflow was set in any launcher and event type was created.
Basically, i used Query builder api and workflow api and was able to achieve the same. If you use servlet way as suggested you can take path as a parameter and then use query builder api to locate the renditions folder and then iterate over the same and remove the nodes.
Sample values that will be extracted via query builder:
http://localhost:4502/bin/querybuilder.json?path=%2fcontent%2fdam%2fgeometrixx%2ficons&property=jcr%3aprimaryType&property.1_value=nt%3afolder
public void execute(WorkItem item, WorkflowSession wfsession, MetaDataMap args)
throws WorkflowException {
try {
resourceResolver = resourceResolverFactory.getAdministrativeResourceResolver(null);
WorkflowData workflowData = item.getWorkflowData();
String path = workflowData.getPayload().toString();
path = path.replace("/jcr:content/renditions", "");
session = resourceResolver.adaptTo(Session.class);
Map<String, String> map = new HashMap<String, String>();
map.put("path", path);
map.put("property", "jcr:primaryType");
map.put("property.1_value", "nt:folder");
Query query = builder.createQuery(PredicateGroup.create(map), session);
SearchResult result = query.getResult();
List<Hit> hits = result.getHits();
Resource renditionResource = resourceResolver.resolve(hits.get(0).getPath());
Iterator<Resource> reneditionIterator = renditionResource.listChildren();
while(reneditionIterator.hasNext()){
Resource specificResource= reneditionIterator.next();
Node renditionNode = specificResource.adaptTo(Node.class);
if(!renditionNode.getName().equals("original")){
renditionNode.remove();
}
}
} catch (LoginException e) {
e.printStackTrace();
}
Servlet
ResourceResolver resourceResolver = slingHTTPrequest.getResourceResolver();
String path = slingHTTPrequest.getParameter("path");
session = resourceResolver.adaptTo(Session.class);
Map<String, String> map = new HashMap<String, String>();
map.put("path", path);
map.put("property", "jcr:primaryType");
map.put("property.1_value", "nt:folder");
Query query = builder.createQuery(PredicateGroup.create(map), session);
SearchResult result = query.getResult();
List<Hit> hits = result.getHits();
for(Hit hit: hits){
Resource renditionResource = resourceResolver.resolve(hit.getPath());
Iterator<Resource> reneditionIterator = renditionResource.listChildren();
while(reneditionIterator.hasNext()){
Resource specificResource= reneditionIterator.next();
Node renditionNode = specificResource.adaptTo(Node.class);
LoggerUtil.debugLog(this.getClass(),"Node name will be {}",renditionNode.getName());
if(!renditionNode.getName().equals("original")){
LoggerUtil.debugLog(this.getClass(), "removing rendition, parent node name is{}",renditionNode.getParent().getParent().getParent().getName());
renditionNode.remove();
}
}
}

Related

AEM 6.3 Cannot create groups with service user

Hoping someone on here can help me out of a conundrum.
We are trying to remove all Admin sessions from our application, but are stuck with a few due to JCR Access Denied exceptions. Specifically, when we try to create AEM groups or users with a service user we get an Access Denied exception. Here is a piece of code written to isolate the problem:
private void testUserCreation2() {
String groupName = "TestingGroup1";
Session session = null;
ResourceResolver resourceResolver = null;
String createdGroupName = null;
try {
Map<String, Object> param = new HashMap<String, Object>();
param.put(ResourceResolverFactory.SUBSERVICE, "userManagementService");
resourceResolver = resourceResolverFactory.getServiceResourceResolver(param);
session = resourceResolver.adaptTo(Session.class);
// Create UserManager Object
final UserManager userManager = AccessControlUtil.getUserManager(session);
// Create a Group
LOGGER.info("Attempting to create group: "+groupName+" with user "+session.getUserID());
if (userManager.getAuthorizable(groupName) == null) {
Group createdGroup = userManager.createGroup(new Principal() {
#Override
public String getName() {
return groupName;
}
}, "/home/groups/testing");
createdGroupName = createdGroup.getPath();
session.save();
LOGGER.info("Group successfully created: "+createdGroupName);
} else {
LOGGER.info("Group already exists");
}
} catch (Exception e) {
LOGGER.error("Error while attempting to create group.",e);
} finally {
if (session != null && session.isLive()) {
session.logout();
}
if (resourceResolver != null)
resourceResolver.close();
}
}
Notice that I'm using a subservice name titled userManagementService, which maps to a user titled fwi-admin-user. Since fwi-admin-user is a service user, I cannot add it to the administrators group (This seems to be a design limitation on AEM). However, I have confirmed that the user has full permissions to the entire repository via the useradmin UI.
Unfortunately, I still get the following error when I invoke this code:
2020-06-22 17:46:56.017 INFO
[za.co.someplace.forms.core.servlets.IntegrationTestServlet]
Attempting to create group: TestingGroup1 with user fwi-admin-user
2020-06-22 17:46:56.025 ERROR
[za.co.someplace.forms.core.servlets.IntegrationTestServlet] Error
while attempting to create group. javax.jcr.AccessDeniedException:
OakAccess0000: Access denied at
org.apache.jackrabbit.oak.api.CommitFailedException.asRepositoryException(CommitFailedException.java:231)
at
org.apache.jackrabbit.oak.api.CommitFailedException.asRepositoryException(CommitFailedException.java:212)
at
org.apache.jackrabbit.oak.jcr.delegate.SessionDelegate.newRepositoryException(SessionDelegate.java:670)
at
org.apache.jackrabbit.oak.jcr.delegate.SessionDelegate.save(SessionDelegate.java:496)
Is this an AEM bug, or am I doing something wrong here?
Thanks in advance
So it seems the bug is actually in the old useradmin interface. It was not allowing me to add my system user into the admninistrators group, but this is possible in the new touch UI admin interface.

Adobe Experience Manager - How can we get currentPage url into Datasource

Below is datasource.jsp for adding dynamic dropdown into my customComponent dialog, I'm using this datasource in my customComponent as below mentioned field.
Here my requirement is need to get url values to dropdown when my customComponent used in any page(this page having urls). So here i need that currentPage url where i have used my customComponent.
Please help me to get that page url where i have used my customComponent to this datasource.
<%
request.setAttribute(DataSource.class.getName(), EmptyDataSource.instance());
ResourceResolver resolver = resource.getResourceResolver();
//Create an ArrayList to hold data
List<Resource> fakeResourceList = new ArrayList<Resource>();
ValueMap vm = null;
Resource childResource = resourceResolver.getResource(currentPage.getPath()+"/jcr:content/node/path");
if(childResource!=null){
Node childNode = childResource.adaptTo(Node.class);
Node childLinks = childNode.getNode("childnode");
if(childLinks!=null){
NodeIterator childrenNodes = childLinks.getNodes();
while(childrenNodes.hasNext()) {
vm = new ValueMapDecorator(new HashMap<String, Object>());
Node next = childrenNodes.nextNode();
String label = next.getProperty("label").getValue().getString();
String path = next.getProperty("url").getValue().getString();
vm.put("text",label);
vm.put("value",path.substring(1));
fakeResourceList.add(new ValueMapResource(resolver, new ResourceMetadata(), "nt:unstructured", vm));
}
}
} else {
vm = new ValueMapDecorator(new HashMap<String, Object>());
vm.put("text","NoValue");
vm.put("value","");
fakeResourceList.add(new ValueMapResource(resolver, new ResourceMetadata(), "nt:unstructured", vm));
}
DataSource ds = new SimpleDataSource(fakeResourceList.iterator());
request.setAttribute(DataSource.class.getName(), ds);
%>
In my customComponent dialog content.xml which is using above datasource as it's sling:resourceType.
<dataSourceTest
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/form/select"
fieldDescription="Provide ID"
fieldLabel="Anchor"
name="./datasourceTest">
<datasource
jcr:primaryType="nt:unstructured"
sling:resourceType="/apps/mysite/components/page/datasource"/>
</dataSourceTest>
The SlingHttpServletRequest (in general) provides an instance of SlingBindings, which contains a reference to "currentPage" (I am using the static field WCMBindings.CURRENT_PAGE [dependency: groupId: com.adobe.cq.sightly, artifactId: cq-wcm-sightly-extension, version: 1.2.30] in my example).
The Optional I am using in my example is a Java 8 class which can be used to avoid too many checks for null references.
final Optional<Page> optional = Optional.ofNullable(request)
.map(req -> (SlingBindings) req.getAttribute(SlingBindings.class.getName()))
.map(b -> (Page) b.get(WCMBindings.CURRENT_PAGE));
A simplified/explicit example would be
Page getCurrentPageFromRequest(#Nonnull final SlingHTTPRequest request) {
final SlingBindings bindings = (SlingBindings) request.getAttribute(SlingBindings.class.getName())
if (bindings == null) {
return null;
}
return (Page) bindings.get(WCMBindings.CURRENT_PAGE);
}
In the code for the datasource's resourceType, you can get the URL of the current page by reading the CONTENTPATH attribute and using the resource resolver to get a resource which you can then adapt to a Node. The node will be the content item for the dialog. Confirmed to work in AEM 6.2.
// get list of child nodes of the current component node
Resource res = resourceResolver.getResource((String) request.getAttribute(Value.CONTENTPATH_ATTRIBUTE));
Node node = res.adaptTo(Node.class);
log.info(node.getPath());
source: https://rmengji.wordpress.com/2016/07/16/aem-6-2-touch-ui-dropdown-pulling-data-dynamically-using-sightly/

How to transform PageRequest object to query string?

I'm writing an RESTful API which consumes another RESTful Data API, and I'm using Spring Data.
The client side send the page request using query params like:
http://api.mysite.com/products?page=1&size=20&sort=id,asc&sort=name,desc
And I transform the params to PageRequest object and transfer it to the Service Layer.
In the service, I would like to use TestTemplate to interact with the Data API which use a URL, and How can I transform the PageRequest object to a query string like
page=1&size=20&sort=id,asc&sort=name,desc
then I can request data like:
restTemplate.getForEntity("http://data.mysite.com/products?page=1&size=20&sort=id,asc&sort=name,desc",String.class)
I know I am a bit late on this answer, but I too was not able to found an already implemented way to do this. I ended up developing my own routine for doing this:
public static String encodeURLComponent(String component){
try {
return URLEncoder.encode(component, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("You are dumm enough that you cannot spell UTF-8, are you?");
}
}
public static String queryStringFromPageable(Pageable p){
StringBuilder ans = new StringBuilder();
ans.append("page=");
ans.append(encodeURLComponent(p.getPageNumber() + ""));
// No sorting
if (p.getSort() == null)
return ans.toString();
// Sorting is specified
for(Sort.Order o : p.getSort()){
ans.append("&sort=");
ans.append(encodeURLComponent(o.getProperty()));
ans.append(",");
ans.append(encodeURLComponent(o.getDirection().name()));
}
return ans.toString();
}
It is not complete, there probably are a couple of details that I am missing, but for my user case (and I think for most of them) this works.
you can pass as new :
new PageRequest(int page,int size)
And in repository layer you can write as:
Page<User> findByName(String name,Pageable page)
in place of Pageable page you have to pass new PageRequest(int page,int size)
For ascending order you can refer this:
List<Todo> findByTitleOrderByTitleAsc(String title);
I think you just need to iterate through the request parameters from your other API request, and then pass all the parameters values into the new Url for a new API request.
sudo code could be:
//all query params can be found in Request.QueryString
var queryParams = Request.QueryString;
private string ParseIntoQuery(NameValueCollection values)
{
//parse the identified parameters into a query string format
// (i.e. return "?paramName=paramValue&paramName2=paramValue2" etc.)
}
in your code, you will then do this:
restTemplate.getForEntity(urlAuthority + ParseIntoQuery(Request.QueryString));
simple as that. Hope that answers?
UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromHttpUrl("http://data.mysite.com/products");
addRequestParam(uriComponentsBuilder, "page", pageable.getPageNumber());
addRequestParam(uriComponentsBuilder, "size", pageable.getPageSize());
String uri = uriComponentsBuilder.toUriString() + sortToString(pageable.getSort());
addRequestParam method:
private static void addRequestParam(UriComponentsBuilder uriBuilder, String parameterName, Object parameter) {
if (parameter != null) {
uriBuilder.queryParam(parameterName, parameter);
}
}
implement sortToString(Sort sort) method as #Shalen. You have to obtain something like this: &sort=name,asc&sort=name2,desc. Return "" if Sort is null;

Lock a page with current loggedin user in CQ

I have been working on a code to lock a page with the current logged-in user (author). I have attached a listener on the property changed event. The page is getting locked as admin instead of the current user.
Below is the code snippet that i am using to lock the page.
resourceResolver = resourceResolverFactory.getAdministrativeResourceResolver(null);
Resource res = resourceResolver.getResource(lockablePagePath);
if(res != null) {
LockManager lockManager = jrSession.getWorkspace().getLockManager();
Node lockableNode = jrSession.getNode(lockablePagePath + "/jcr:content");
if (lockManager.isLocked(lockableNode.getPath())) {
LOG.error("Page/Node is alrady Locked by ");
} else {
lockManager.lock(lockableNode.getPath(), true, false, 1000, userId);
//jrSession.save();
}
}
Kindly suggest.
You are mixing Sling (ResourceResolver) and JCR (Session) API here. Based on my experience I would recommend sticking to one (preferably Sling, as it's a higher level of abstraction).
The jrSession you're using most likely belongs to the admin user. You might need to impersonare (Session.impersonate(Credentials creds)) the user you want to lock the page.
Using Sling API, I would do something like this:
Map<String, Object> map = new HashMap<String, Object>();
map.put(ResourceResolverFactory.USER_IMPERSONATION, userId);
resourceResolver = resourceResolverFactory.getAdministrativeResourceResolver(map);
PageManager pageManager = resourceResolver.adaptTo(PageManager.class);
Page page = pageManager.getPage(lockablePagePath);
if (page.isLocked()) {
//log error
} else {
page.lock();
}
What I do here is, I retrieve an administrative resourceResolver that impersonates a given (current) user.
I've never tried it, but that's the way I'd approach the problem given the documentation.

Update records using Salesforce REST API - Patch method not working

I'm trying to update a record residing in salesforce table. Im using the Java HttpClient REST API to do the same. Getting an error when we use PATCH to update a record in Salesforce.
PostMethod post = new PostMethod(
instanceUrl + "/services/data/v20.0/sobjects/" +
objectName + "/" + Id + "?_HttpMethod=PATCH"
);
[{"message":"HTTP Method 'PATCH' not allowed. Allowed are HEAD,GET,POST","errorCode":"METHOD_NOT_ALLOWED"}]
Also tried doing the following:
PostMethod post = new PostMethod(
instanceUrl + "/services/data/v20.0/sobjects/" + objectName + "/" + Id)
{
public String getName() { return "PATCH";
}
};
This also returns the same error. We are using apache tomcat with commons-httpclient-3.1.jar library. Please advise on how this can be done.
Please check if you you're using the right implementation of PATCH method, see: Insert or Update (Upsert) a Record Using an External ID.
Also check if your REST URL is correct, probably your objectId is not passed in correctly from Javascript.
The ObjectName is the Name of an Salesforce table i.e. 'Contact'. And Id is the Id of a specific record you want to update in the table.
Similar:
Can I update without an ID?
for Drupal: How to deal with SalesforceException: HTTP Method 'PATCH' not allowed. Allowed are HEAD,GET,POST
As I think you're aware, commons httpclient 3.1 does not have the PATCH method, and the library has been end-of-lifed. In your code above, you're trying to add the HTTP method as a query parameter, which doesn't really make sense.
As seen on SalesForce Developer Board, you can do something like this instead:
HttpClient httpclient = new HttpClient();
PostMethod patch = new PostMethod(url) {
#Override
public String getName() {
return "PATCH";
}
};
ObjectMapper mapper = new ObjectMapper();
StringRequestEntity sre = new StringRequestEntity(mapper.writeValueAsString(data), "application/json", "UTF-8");
patch.setRequestEntity(sre);
httpclient.executeMethod(patch);
This allows you to PATCH without switching out your httpclient library.
I created this method to send the patch request via the Java HttpClient class.
I am using JDK V.13
private static VarHandle Modifiers; // This method and var handler are for patch method
private static void allowMethods(){
// This is the setup for patch method
System.out.println("Ignore following warnings, they showed up cause we are changing some basic variables.");
try {
var lookUp = MethodHandles.privateLookupIn(Field.class, MethodHandles.lookup());
Modifiers = lookUp.findVarHandle(Field.class, "modifiers", int.class);
} catch (IllegalAccessException | NoSuchFieldException e) {
e.printStackTrace();
}
try {
Field methodField = HttpURLConnection.class.getDeclaredField("methods");
methodField.setAccessible(true);
int mods = methodField.getModifiers();
if (Modifier.isFinal(mods)) {
Modifiers.set(methodField, mods & ~Modifier.FINAL);
}
String[] oldMethods = (String[])methodField.get(null);
Set<String> methodsSet = new LinkedHashSet<String>(Arrays.asList(oldMethods));
methodsSet.addAll(Collections.singletonList("PATCH"));
String[] newMethods = methodsSet.toArray(new String[0]);
methodField.set(null, newMethods);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
}