Due to some changes in component styling and order of elements in a column layout, I need two resources to swap places across my site’s content tree.
The structure of the component in question is as follows:
myComponent (sling:resourceType=myapp/mycomponent, other properties)
col1 (sling:resourceType=myapp/container, cq:styleIds=[1, 2, 3], other properties)
title
text
container
button
button2
col2 (sling:resourceType=myapp/container, cq:styleIds=[4, 5, 6, 7], other properties)
image
I want to programmatically swap the contents and properties of col1
and col2
, so that the following structure is achieved:
myComponent (sling:resourceType=myapp/mycomponent, other properties)
col1 (sling:resourceType=myapp/container, cq:styleIds=[4, 5, 6, 7], other properties)
image
col2 (sling:resourceType=myapp/container, cq:styleIds=[1, 2, 3], other properties)
title
text
container
button
button2
This really boils down to renaming and reordering the resources. I want to use this code in a Groovy script to automatically introduce the change across multiple content branches.
The Sling API doesn’t appear to have a rename
method (though the existence of it has been suggested by copilot and ChatGPT). I want to achieve the same effect by moving those resources instaed.
The documentation for Apache Sling has a section on moving resoruces using either the Sling POST servlet, or the Sling Java API.
Resource myResource2 = resourceResolver.getResource("/myresource2");
Map<String,Object> properties = myResource2.adaptTo(ValueMap.class);
Resource myResource3 = resourceResolver.create(null, "myresource3", properties);
resourceResolver.delete(myResource2);
resourceResolver.commit();
This appears to ignore child resources and while properties would be persisted, I’d lose child resources.
I belive this snippet is meant to illustrate the effect of calling the Sling POST servlet with specific parameters, rather than suggest the right way to move the resource.
The Javadoc for the ResourceResolver
class lists a move
method that takes two parameters, the source path, and the destination path, but that fails when I try it
Resource col1 = parentResource.getChild('col1')
Resource col2 = parentResource.getChild('col2')
def tempResourcePath = parentResource.path + "/col1_temp"
resourceResolver.move(col1.path, tempResourcePath)
it throws the following exception:
org.apache.sling.api.resource.PersistenceException: Destination resource does not exist.
The Javadoc for ResourceResolver
does explain that this is to be expected.
The resource at
destAbsPath
needs to exist, if not aPersistenceException
is thrown. If a child resource with the same name already exists atdestAbsPath
a PersistenceException is thrown.
This defeats the purpose of using the move
method to essentially rename a resource while keeping it under the same parent. I could technically use this API to individually move all descendants of col1
and col2
to a newly created resource, but that would take quite a bit more code than I would expect in this situation.
Is there an easier way to do this using the Sling API?
I haven’t found a way to do this more easily with Sling’s Java API. It is, however, easy enough to fall back to the underlying JCR API and achieve the desired result of swapping the two resources along with their properties and sub-trees (this is Groovy syntax, but the underlying APIs are the same for Java):
def swapColumns = { parentResource ->
Resource col1 = parentResource.getChild('col1')
Resource col2 = parentResource.getChild('col2')
Node col1Node = col1.adaptTo(Node.class)
Node col2Node = col2.adaptTo(Node.class)
String col1Path = col1Node.path
String col2Path = col2Node.path
String tempPath = parentResource.path + "/col1_temp"
session.move(col1Path, tempResourcePath)
session.move(col2Path, col1Path)
session.move(tempPath, col2Path)
}
This uses the javax.jcr.Session
interface. The changes will only be persisted after a session save, which is what I want in this scenario anyway.
There’s also javax.jcr.Workspace#move
which provides similar functionality, but as a workspace-level API, it automatically commits the transaction.
More on moving and copying using the JCR API.
If anyone knows a cleaner way to do this at the Sling level, please let me know.