Expand my Community achievements bar.

SOLVED

Need to referenced pages when deactivating the page. Similar to activating.

Avatar

Level 2

Hi,

 

When am activating a page it is asking to publish referenced pages, assets, etc.
Is there any way to get similar popup for deactivation also. Means need to deactivate referenced pages and assets when we deactivate the page.

 

 

Thanks in advance.

Topics

Topics help categorize Community content and increase your ability to discover relevant content.

1 Accepted Solution

Avatar

Correct answer by
Level 10

Hi @Niveshchandra,

The problem

As @VeenaVikraman alluded to, before you get to the technical part for the implementation, you must first determine exactly what need to be done. Let's take the following example where you have one asset that is referenced on two pages:

references.png

The current OOTB behavior if you deactivate Page A does not include the deactivation of the asset, and this is the right approach! If you also deactivated the asset you would break Page B.

So if you were to implement a system where upon deactivation or deletion, you would have to search not only the references held in the page you are deactivating, but also the references of those references and so on, to make sure that you don't break anything. Depending on how well you organise your content, this may not be a big deal, but if you tend to re-use assets in multiple places and have many links between pages, you can see how you may end up having to traverse the entire website every time you activate or deactivate.

The technical solution

However, provided you still want to go ahead, you would then need to create this reference checking algorithm yourself, since it does not exist OOTB. It could be done as a servlet or a workflow step, depending on your exact need but either way it would be done in the Java backend.

In order to find the references for a given page, asset or any other resource, you can use ReferenceAggregator. Here is an example servlet I wrote:

@Component(service = Servlet.class,
        property = {
                "sling.servlet.methods=" + HttpConstants.METHOD_GET,
                "sling.servlet.paths=" + "/bin/demo"
        })
@ServiceDescription("Simple Demo Servlet")
public class SimpleServlet extends SlingSafeMethodsServlet {

    private static final long serialVersionUID = 1L;

    @Reference
    private ReferenceAggregator referenceAggregator;

    @Override
    protected void doGet(final SlingHttpServletRequest req,
                         final SlingHttpServletResponse resp) throws ServletException, IOException {

        final ResourceResolver resourceResolver = req.getResourceResolver();

        final Map<String, Object> result = new HashMap<>();
        result.put("image", getReferences(req, "/content/dam/demo/image.jpg"));
        result.put("page 1", getReferences(req, "/content/demo/us/en/1"));
        result.put("page 2", getReferences(req, "/content/demo/us/en/2"));


        final String output = new ObjectMapper().writeValueAsString(result);
        resp.setContentType("application/json");
        resp.getWriter().write(output);
    }

    private List<Object> getReferences(final SlingHttpServletRequest request, final String path) {
        final Resource resource = request.getResourceResolver().getResource(path);
        return Arrays.stream(referenceAggregator.createReferenceList(resource).toArray())
                .map(object -> new ReferenceVO((com.adobe.granite.references.Reference) object))
                .collect(Collectors.toList());
    }

    @Data
    public class ReferenceVO {
        private String type;
        private String source;
        private String target;

        public ReferenceVO(final com.adobe.granite.references.Reference reference) {
            this.type = reference.getType();
            this.source = reference.getSource().getPath();
            this.target = reference.getTarget().getPath();
        }
    }
}

Then I:

  1. Created an image asset
  2. Create page 1 and added the image
  3. Created page 2
  4. Added a link on page 1 that points to page 2

Here is the output of the servlet in JSON:

{
    "image": [
        {
            "type": "contentfragment",
            "source": "/content/dam/demo/image.jpg",
            "target": "/content/demo/us/en/1"
        },
        {
            "type": "product",
            "source": "/content/dam/demo/image.jpg",
            "target": "/content/demo/us/en/1"
        },
        {
            "type": "incomingLinks",
            "source": "/content/dam/demo/image.jpg",
            "target": "/content/demo/us/en/1"
        },
        {
            "type": "siteReference",
            "source": "/content/dam/demo/image.jpg",
            "target": "/content/demo/us/en/1"
        },
        {
            "type": "liveCopy",
            "source": "/content/dam/demo/image.jpg",
            "target": "/content/dam/demo/live-copy"
        }
    ],
    "page 2": [
        {
            "type": "contentfragment",
            "source": "/content/demo/us/en/2",
            "target": "/content/demo/us/en/1"
        },
        {
            "type": "product",
            "source": "/content/demo/us/en/2",
            "target": "/content/demo/us/en/1"
        },
        {
            "type": "incomingLinks",
            "source": "/content/demo/us/en/2",
            "target": "/content/demo/us/en/1"
        },
        {
            "type": "assetLanguageCopy",
            "source": "/content/demo/us/en/2",
            "target": "/content/demo/us/en/2"
        },
        {
            "type": "languageCopy",
            "source": "/content/demo/us/en/2",
            "target": "/content/demo/us/en/2"
        }
    ],
    "page 1": [
        {
            "type": "assetLanguageCopy",
            "source": "/content/demo/us/en/1",
            "target": "/content/demo/us/en/1"
        },
        {
            "type": "languageCopy",
            "source": "/content/demo/us/en/1",
            "target": "/content/demo/us/en/1"
        }
    ]
}

As you can see there are MANY references held by AEM be default. This is the case because the references come from Reference Provider that are registered in many different modules/components so you need to decide which types are important to you. In this case it's the "incomingLink" types. If we filter out the rest we get a much clearer vision of what is going on:

{
    "image": [
        {
            "type": "incomingLinks",
            "source": "/content/dam/demo/image.jpg",
            "target": "/content/demo/us/en/1"
        }
    ],
    "page 2": [
        {
            "type": "incomingLinks",
            "source": "/content/demo/us/en/2",
            "target": "/content/demo/us/en/1"
        }
    ],
    "page 1": []
}

As you can see, that is an accurate representation of the situation I created in steps 1 - 4 above 

However, there is yet another speedbump: references in AEM are uni-directional (as the "incomingLinks" name suggests). So you'll notice that even though page 1 has the image on it, it is in fact the image resource that holds the link! This makes sense if you consider the dependency relationship between pages and assets: you can safely delete page 1 without breaking anything, but you cannot safely delete the image, so it's the image that holds the reference. This will be a problem for you, since you would have to instead parse all components on page 1, and discover the references to assets yourself.

Conclusion

This feature is going to be quite a tricky one to put in place. If I were you, I would make sure that it is really a feature that your users would benefit from, and be very careful to design an algorithm for following all references and dependencies. You have a lot of work in front of you!

View solution in original post

4 Replies

Avatar

Community Advisor

The basic reason I think this feature is not available OOTB is because , the assets and pages might be referenced in many places and so unpublishing ( I mean deactivating ) won't be as easy as publishing a new unpublished asset or references. But then if you need to bring in that functionality for your project, the beauty of sling is everything is customizable. You just need to write some custom functionality to achieve this. 

Disclaimer I have not tried that customization yet, so I am not sure about things we need to do at this point to achieve this requirement if this is a real need in your project. 

Avatar

Employee Advisor

Right now, the functionality you are looking for does not exist OOTB and you will need to create custom listeners which listen to the deactivation event and proceed as per your business requirement.

Avatar

Correct answer by
Level 10

Hi @Niveshchandra,

The problem

As @VeenaVikraman alluded to, before you get to the technical part for the implementation, you must first determine exactly what need to be done. Let's take the following example where you have one asset that is referenced on two pages:

references.png

The current OOTB behavior if you deactivate Page A does not include the deactivation of the asset, and this is the right approach! If you also deactivated the asset you would break Page B.

So if you were to implement a system where upon deactivation or deletion, you would have to search not only the references held in the page you are deactivating, but also the references of those references and so on, to make sure that you don't break anything. Depending on how well you organise your content, this may not be a big deal, but if you tend to re-use assets in multiple places and have many links between pages, you can see how you may end up having to traverse the entire website every time you activate or deactivate.

The technical solution

However, provided you still want to go ahead, you would then need to create this reference checking algorithm yourself, since it does not exist OOTB. It could be done as a servlet or a workflow step, depending on your exact need but either way it would be done in the Java backend.

In order to find the references for a given page, asset or any other resource, you can use ReferenceAggregator. Here is an example servlet I wrote:

@Component(service = Servlet.class,
        property = {
                "sling.servlet.methods=" + HttpConstants.METHOD_GET,
                "sling.servlet.paths=" + "/bin/demo"
        })
@ServiceDescription("Simple Demo Servlet")
public class SimpleServlet extends SlingSafeMethodsServlet {

    private static final long serialVersionUID = 1L;

    @Reference
    private ReferenceAggregator referenceAggregator;

    @Override
    protected void doGet(final SlingHttpServletRequest req,
                         final SlingHttpServletResponse resp) throws ServletException, IOException {

        final ResourceResolver resourceResolver = req.getResourceResolver();

        final Map<String, Object> result = new HashMap<>();
        result.put("image", getReferences(req, "/content/dam/demo/image.jpg"));
        result.put("page 1", getReferences(req, "/content/demo/us/en/1"));
        result.put("page 2", getReferences(req, "/content/demo/us/en/2"));


        final String output = new ObjectMapper().writeValueAsString(result);
        resp.setContentType("application/json");
        resp.getWriter().write(output);
    }

    private List<Object> getReferences(final SlingHttpServletRequest request, final String path) {
        final Resource resource = request.getResourceResolver().getResource(path);
        return Arrays.stream(referenceAggregator.createReferenceList(resource).toArray())
                .map(object -> new ReferenceVO((com.adobe.granite.references.Reference) object))
                .collect(Collectors.toList());
    }

    @Data
    public class ReferenceVO {
        private String type;
        private String source;
        private String target;

        public ReferenceVO(final com.adobe.granite.references.Reference reference) {
            this.type = reference.getType();
            this.source = reference.getSource().getPath();
            this.target = reference.getTarget().getPath();
        }
    }
}

Then I:

  1. Created an image asset
  2. Create page 1 and added the image
  3. Created page 2
  4. Added a link on page 1 that points to page 2

Here is the output of the servlet in JSON:

{
    "image": [
        {
            "type": "contentfragment",
            "source": "/content/dam/demo/image.jpg",
            "target": "/content/demo/us/en/1"
        },
        {
            "type": "product",
            "source": "/content/dam/demo/image.jpg",
            "target": "/content/demo/us/en/1"
        },
        {
            "type": "incomingLinks",
            "source": "/content/dam/demo/image.jpg",
            "target": "/content/demo/us/en/1"
        },
        {
            "type": "siteReference",
            "source": "/content/dam/demo/image.jpg",
            "target": "/content/demo/us/en/1"
        },
        {
            "type": "liveCopy",
            "source": "/content/dam/demo/image.jpg",
            "target": "/content/dam/demo/live-copy"
        }
    ],
    "page 2": [
        {
            "type": "contentfragment",
            "source": "/content/demo/us/en/2",
            "target": "/content/demo/us/en/1"
        },
        {
            "type": "product",
            "source": "/content/demo/us/en/2",
            "target": "/content/demo/us/en/1"
        },
        {
            "type": "incomingLinks",
            "source": "/content/demo/us/en/2",
            "target": "/content/demo/us/en/1"
        },
        {
            "type": "assetLanguageCopy",
            "source": "/content/demo/us/en/2",
            "target": "/content/demo/us/en/2"
        },
        {
            "type": "languageCopy",
            "source": "/content/demo/us/en/2",
            "target": "/content/demo/us/en/2"
        }
    ],
    "page 1": [
        {
            "type": "assetLanguageCopy",
            "source": "/content/demo/us/en/1",
            "target": "/content/demo/us/en/1"
        },
        {
            "type": "languageCopy",
            "source": "/content/demo/us/en/1",
            "target": "/content/demo/us/en/1"
        }
    ]
}

As you can see there are MANY references held by AEM be default. This is the case because the references come from Reference Provider that are registered in many different modules/components so you need to decide which types are important to you. In this case it's the "incomingLink" types. If we filter out the rest we get a much clearer vision of what is going on:

{
    "image": [
        {
            "type": "incomingLinks",
            "source": "/content/dam/demo/image.jpg",
            "target": "/content/demo/us/en/1"
        }
    ],
    "page 2": [
        {
            "type": "incomingLinks",
            "source": "/content/demo/us/en/2",
            "target": "/content/demo/us/en/1"
        }
    ],
    "page 1": []
}

As you can see, that is an accurate representation of the situation I created in steps 1 - 4 above 

However, there is yet another speedbump: references in AEM are uni-directional (as the "incomingLinks" name suggests). So you'll notice that even though page 1 has the image on it, it is in fact the image resource that holds the link! This makes sense if you consider the dependency relationship between pages and assets: you can safely delete page 1 without breaking anything, but you cannot safely delete the image, so it's the image that holds the reference. This will be a problem for you, since you would have to instead parse all components on page 1, and discover the references to assets yourself.

Conclusion

This feature is going to be quite a tricky one to put in place. If I were you, I would make sure that it is really a feature that your users would benefit from, and be very careful to design an algorithm for following all references and dependencies. You have a lot of work in front of you!

Avatar

Community Advisor
Agree. The complexity is gonna be huge and performance will get hit if you have a huge repo of content. So be careful