Hi Bart!

Resource providers are mighty, but tricky things indeed. Please find my 
>remarks below.

Cheers,
Olaf

-----Original Message-----
From: Bart Wulteputte [mailto:bart.wultepu...@gmail.com] 
Sent: Donnerstag, 8. Juni 2017 22:50
To: users@sling.apache.org
Subject: Resource provided by custom ResourceProvider crashes upon calling 
hasChildren()

Hi,

While trying to implement a POC and I ran into a strange error, and I hope you 
guys can help identify whether this is a bug, or if I'm just doing something 
wrong.

I'm playing around with custom resource providers to pull external data into 
sling. I don't do anything crazy in there (yet), but it's just as a poc. I want 
to combine it with custom resource implementations as well.

My resource provider is registered on path "/content/data" and basically 
handles anything on this path. Currently I'm just building a virtual data 
structure. Inside this virtual structure some paths will have a very specific 
ResourceProvider implementation tied to said path which retrieve the resource 
info from an external system when calling listChildren. So for example 
"/content/data/2001/external/app" could have a more specific resource provider 
registered here (which should work based on provider priority). Unfortunately, 
I can't seem to get part 1 (building the virtual
structure) working without some hacks.

*My resource provider implementation looks like this:*

@Override
public Resource getResource(@Nonnull final ResolveContext resolveContext, 
@Nonnull final String path, @Nonnull final ResourceContext resourceContext, 
final Resource parent) {
    return new SyntheticResource(resolveContext.getResourceResolver(),
path, SyntheticResource.RESOURCE_TYPE_NON_EXISTING);
}


> Resources must never provide their own children - this is the resource 
> provider's responsibility, see ResourceProvider#listChildren(ResolveContext 
> ctx, Resource parent). The reason is that resource providers can be nested, 
> i.e. the child of a resource may be provided by a different resource 
> provider. Thus, the code below should be situated in the before mentioned 
> method of your resource provider.

@Override
public Iterator<Resource> listChildren(@Nonnull final ResolveContext 
resolveContext, @Nonnull final Resource resource) {
    final ResourceResolver resourceResolver = 
resolveContext.getResourceResolver();
    final List<Resource> list = new ArrayList<>();
    // search data basically returns a list of child paths
    // e.g. /content/data/2000, /content/data/2001, ...
    // since these are 'children' the resolving ends up in this ResourceProvider
    // which yields a new SyntheticResource on the given path (for now)
    for (String path : searchData.childrenOf(resource)) {
        final Resource childRes = resourceResolver.getResource(path);
        if (childRes != null) {


> You can never re-provide a resolved resource: A resource has resource 
> meta-data containing, amongst others, the resolution path. Also, a resource 
> is always tied to its resource resolver via resource#getResourceProvider(). 
> Thus, you must create a new (Synthetic) resource here and add it to the list. 
> For instance, you could extend SyntheticResource to create your own resource 
> wrapper, and delegate Resource#adaptTo to your wrapped resource.
             
            list.add(childRes);
        }
    }
    return list.isEmpty() ? null : list.iterator(); }



*The code producing my error:*

Resource r=resourceResolver.getResource("/content/data");
r.hasChildren();


*My Error:*

java.lang.UnsupportedOperationException: ResourceMetadata is locked at
org.apache.sling.api.resource.ResourceMetadata.checkReadOnly(ResourceMetadata.java:367)
at
org.apache.sling.api.resource.ResourceMetadata.put(ResourceMetadata.java:379)
at
org.apache.sling.api.resource.ResourceMetadata.setResolutionPath(ResourceMetadata.java:276)
at
org.apache.sling.resourceresolver.impl.helper.UniqueResourceIterator.seek(UniqueResourceIterator.java:51)
at
org.apache.sling.resourceresolver.impl.helper.UniqueResourceIterator.seek(UniqueResourceIterator.java:30)
at
org.apache.sling.resourceresolver.impl.helper.AbstractIterator.hasNext(AbstractIterator.java:33)
at
org.apache.sling.resourceresolver.impl.helper.ResourceIteratorDecorator.hasNext(ResourceIteratorDecorator.java:45)
...


*My analysis so far:*

Because I use resourceResolver.getResource() inside the listChildren method of 
my custom resource provider, I pass through some internal resolving which do 
some decorating on the resource and the iterators. This in itself is not a 
problem, but one of those decorators (the
ResourceDecoratorTracker) locks the ResourceMedatadata object - which is a 
problem as other decorators like ResourceIteratorDecorator try to update data 
(in this case it tries to set/update the resolutionPath of the fetched 
resource's ResourceMetadata to the path of said resource during the execution 
of 'next()'  - which seems a tad odd). And here we end up trying to modify a 
locked object.


This feels like a bug. I can get around it by creating my own ResourceMetadata 
class which extends ResourceMetadata and overrides the
lock() method to do nothing and passing that to the SyntheticResource upon 
creation. But this feels like hacking. Second option: I don't use the 
resourceResolver to get the resource, but instead create new SyntheticResource 
objects in the listChildren of my ResourceProvider directly (in the same way i 
would do 'getResource').

Preferably I want to pass through the appropriate ResourceProvider (since the 
intend is to have more specific resource providers mounted on paths inside this 
virtual structure). In theory this should work, and in practise it does as well 
(if I hack it a bit as described before).

So the main question is, can I do this in a non hackish way? And is this a bug?

Reply via email to