Ad Code

Sunday, October 25, 2020

Register Sling Servlet or Any Service Dynamically in AEM

 Hello Everyone,

While working on a project last week, I went through a very interesting use case and so many learnings while solving that. This blog is shared with you about that learning process I have been through.


Problem Statement: I was working on an integration tool with AEM, in which we are having many servlets registered with paths . Few of the servlets are being called by a third party and few are for internal calls.


All the servlets start with a specific prefix. Let’s suppose: /bin/aem-integration/events, /bin/aem-integration/tasks etc. So the prefix for all the servlets is "/bin/aem-integration" and it was as a constant in the code.


But while using that tool, a client requested to provide them the flexibility to define the prefix as per their requirement. So that, while they made calls from any external system to AEM, they could make their own choice of prefix or may be a very environment specific prefix like for dev it will be /bin/aem-integration/dev/tasks but for stage it will be /bin/aem-integration/stage/tasks. The requirement looks easier but while implementing, it was quite challenging.


How I approach this problem:

So if I mention the above problem precisely, it is registering the servlets based on dynamic paths.


How can we do that? 

Let’s suppose I have a servlet "RunModeServlet" using SlingSettings as a Reference and I want to register this servlet dynamically. So how to do that.



1.First I create a service "ServletRegistration" having an OSGI Configuration asking for Endpoint Prefix Configuration, because every service is a component, so on the activate method of the component we need to register all the servlets dynamically  and unregister and re-register with new values, if the author makes any change in configuration.



Now if you are creating reference of a component via new RunModeServlet() and if in the RunModeServlet is using any @Reference annotation, all the services injections will be null. To solve this, pass the service reference via the constructor to the servlet like shown in the code.


Note: In this case in the ServletRegistration class need to have all the service injections using @Reference annotation and need to pass to the servlets via the constructor new RunModeServlet(slingSettings).


2. In the RunModeServlet, we remove all the annotations and get the services via the constructor in place of @Reference annotation.


Note: This approach works well but if you are registering many servlets dynamically, you always need to manage all the constructors for every servlet and pass the service reference from ServletRegistration OSGi service. To see so many service injections in this service (ServletRegistration ) can be overwhelming.

Is there any alternative way to handle it?
The answer is yes.
Use a componentFactory to create instances of a component.
[Preferred Approach]

1. Use this on the component/servlet you want to create programmatically:

@Component(factory= "aem.servlet.runModeServlet")

public class RunModeServlet extends SlingAllMethodsServlet {


 @Reference

   private SlingSettingsService slingSettingsService;

@Override

protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response){

}

}


2. Then in another component/service (ServletRegistration) you can get the ComponentFactory:

@Reference(target = "(component.factory=aem.servlet.runModeServlet)")

private ComponentFactory runModeServletFactory;


3. Create an instance from it and register the servlet like this.

Dictionary<String, Object> propertiesMap = getPropertiesMap(runModeServletPaths, "json", "GET");

RunModeServlet runModeServlet = (RunModeServlet) runModeServletFactory.newInstance(null).getInstance();

bundleContext.registerService(Servlet.class, runModeServlet, propertiesMap);



Note: The way we have registered a servlet, you can register any service, filter or any authenticationHandler dynamically.

Dictionary<String, Object> propertiesMap = new Hashtable<>();

propertiesMap.put(AuthenticationHandler.PATH_PROPERTY, new String[]{endpoint_prefix, DamConstants.MOUNTPOINT_ASSETS});

propertiesMap.put(Constants.SERVICE_RANKING, 100000);

propertiesMap.put(Constants.SERVICE_DESCRIPTION, "Custom – Authentication Handler");

bundleContext.registerService(AuthenticationHandler.class, customAuthenticationHandler, propertiesMap);


I hope you find it interesting and useful.


Thanks and Happy sharing.


Tuesday, September 15, 2020

How to Use Encrypted Password for OOTB Configurations

Hello Everyone, 

It’s been so long since I wrote something. So here is a new blog about some issues I faced
recently and what are my leanings from there.

1. Encrypted Password for OOTB Configurations: We always talk about making
passwords in an encrypted value as part of the code base to secure the password.
To tackle this issue, we always talk about CryptoSupport in AEM. So basically if there is a custom configuration and a need to use an encrypted password, I can use the concept of cryptosupport in AEM.

But there are few OSGi Configurations in AEM like “JDBC ODBC Connection Pool” or “Adobe Granite SAML 2.0 Authentication Handler” having password fields, So can we use cryptosupport concept here too??

So the answer is yes, you can keep encrypted value in the “password fields”  directly in these configurations and for the decryption part, you don’t need to worry. AEM will take care of it.To encrypt a password, go through the cryptosupport console in AEM.

Conclusion: We need to understand this very clearly that if we define a field as "Password type" either in OOTB or custom configurations, we don’t need to worry about decryption part, just configure the encrypted value in the field and AEM will understand that because it is a password field, it may need to decrypt it.
But if your field is a plain text, then you have to take care of the decryption part as mentioned in the previous post.
Fig1: For a password field, no need to write logic for decryption.

2. AEM SAML Configuration in the Code Repository Issue: Recently while working on a project, I configured AEM SAML integration on the author instance and it just works fine. Now this was the time to put the config in the code repository. So,

a) I put that config in the code repository (at /apps/<project-folder>/config.author.dev/com.adobe.granite.auth.saml.SamlAuthenticationHandler-myproject.xml

b) Deleted the manually created SAML configuration in Felix Console and performed a build and I was able to see the SAML config added by code.

c) I hit the Author URL and it doesn't take me to the IDP login page.
d) I go to felix, open SAML config, click Save (without touching anything else) and I hit Author URL again and now it takes me to the IDP login page.

Exactly the same issue I found on Adobe Community and the solution work for me.

Solution:
a) Name the file as: "com.adobe.granite.auth.saml.SamlAuthenticationHandler-<Project_Identifier>.config" (Make sure it is a regular file in Eclipse or IntelliJ); no need to add extension ".xml" at the end.
b) Inside this file, just add the configuration like a regular text. Please see below:
# Configuration created by Apache Sling JCR Installer
// Storing keyStorePassword as an encrypted manner.
keyStorePassword="{41bdcd34d9a34ae1c68bafa6b7b647443c429ad97e00a9f2cb5f876b2433}"
service.ranking=I"5002"
idpHttpRedirect=B"false"
createUser=B"true"
defaultRedirectUrl="/content/project/en/aem-assets.html"
userIDAttribute="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"
assertionConsumerServiceURL=""
defaultGroups=["contributors"]
signatureMethod="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
idpCertAlias="certalias___xxxxxxxxxxxx"
addGroupMemberships=B"true"
path=["/content/projects"]
digestMethod="http://www.w3.org/2001/04/xmlenc#sha256"
synchronizeAttributes=""
clockTolerance=I"60"
groupMembershipAttribute="groupMembership"
idpUrl="IDP URL GOES HERE"
logoutUrl="logouturl"
serviceProviderEntityId="service_provider_entity_id"
handleLogout=B"true"
spPrivateKeyAlias=""
useEncryption=B"false"
nameIdFormat="urn:oasis:names:tc:SAML:2.0:nameid-format:transient"

You can also copy the content of the file from CRXDE, after you manually save the OSGi config thru /system/console/configMgr.

Note: This solution really worked for me,but logically it didn’t make any sense to me as while working on another project on AEM 6.5.4, the xml file used to work for me. I face this issue only  in AEM 6.5.5 till now, So can’t really comment if this is an upgrade issue or something else.
So,If you face this issue in any of the AEM versions, you can directly try this solution.

I hope you find it a good read.Thanks and Happy Learning.

Wednesday, April 22, 2020

AEM Workflow: Request for Deletion Workflow in AEM

Hello Everyone,

Request for Deletion is an AEM OOTB Workflow. In this blog, we will talk about,
1. What is a request for deletion workflow?
2. How it works?
3. What are challenges with this workflow?

Request for Deletion Workflow: This workflow gets initiated when the content-authors don’t have replicate permissions and try to delete a page.

Request for Deletion OOTB workflow has four steps:

Fig1: OOTB Request for Deletion Workflow 

Step1: When an author delete a page, "Request for Deletion" step gets executed and it initiates a notification to the administrators by default (you can change it to approver group).
Step2: When the approver approves the request, the page gets deactivated from the publisher.
Step3: Again a request goes to the administrator or content-approvers to take the approval to delete the page in the author instance.
Step4: When the request gets approved, the page gets deleted from the author server.

Product Bug: When the author deletes the page, a request for deletion notification is visible to the approver inbox, but after the approver approves the request and workflow completes deactivation of page step, the workflow itself aborted after step2.

Because of this issue, the page is getting deactivated from publish but the page is not getting deleted from author CRXDE and because of that all the pages authors deleted in author instance are not visible in Sites console but still sitting in CRXDE.
Fig2: Request for Deletion workflow history to show aborted scenario
I raised it as a day care and this is what they replied.
Solution: “I looked at the code that gets triggered in this event (an event handler). What it actually does is, trigger a Request for deletion workflow with system user wcm-workflow-service, when the user deleting the page does not have enough permission.
Now if you provide delete and replicate permission to that user "wcm-workflow-service" at the /content level the workflow is getting completed successfully.
Always remember that Request for deletion workflow has two participant steps, so you need to approve twice, once for deactivating the page, then for the deletion of the resource step.”

And the above mentioned solution worked for me.
Fig2: Request for Deletion workflow history after fixing permissions
Note: I face the above issue in AEM 6.5.2, so I am not sure what all AEM version has this
issue.So check accordingly.

Client Concern:
Deletion is a very crucial activity which can lead to loss of content/pages if done unintentionally.
That’s why AEM provided approvals at two levels. But in our project, client concern was
that it’s quite annoying for the content-authors, when they just delete a unpublished page
from the author, for this also it asks for two level approvals.

So here we removed the second level approval by considering that if approver approved
at the first step, it means that he really doesn't want that page/content in author as well as
publish.
And i think it really make sense too as it is very difficult to manage so many approvals also.

Fig4:  Workflow model after removing the approval step
Note: Always remember, before you hand over the production server to content-authors, always decide the workflow for request for deletion, make the authors aware about it and test it once because after authors start authoring, and you face this kind of product bug, where pages still exist in author after deletion, you have to fix them by going to the crx and this is hectic task also for a developer and authors.

I hope you find it a good read.Thanks and Happy Learning.

Tuesday, April 21, 2020

Troubleshooting in AEM with real time use cases


Hello Everyone,

In this blog, I will talk about some of the real time issues, I faced while working on a project.
Here we will talk about the problem statement and how to tackle these problems.

1. The multiple PDFs were not able to move to a specific location:  In AEM, ideally you can select multiple PDF’s and move to a specific location. While working on a project in AEM 6.5.2, The authors in this project were able to move a PDF to a certain location but they were not able to move multiple PDFs in one go.

When I found the issue, I checked the console and realized that the OOTB JS
(/libs/dam/gui/coral/components/admin/moveassetwizard/clientlibs.js) is breaking.

I raised this as a Product Bug to Day Care and below is the solution which they provide me.
“This is an issue with 6.5.1 and 6.5.2 which should be fixed in 6.5.3 release”.

Workaround:
The present workaround is to replace line 251 at
/libs/dam/gui/coral/components/admin/moveassetwizard/clientlibs/js/wizard.js
with the below code.
Old code snippet:
var newName = $(".rename-item-name").val().toLowerCase();

New code Snippet:
var newName = $(".rename-item-name").val();

you can create an overlay for the above mentioned JS file and later uninstall it when you plan to install 6.5.3.

2. While using reference components, the authors are not able to decide which component they need to refer in the pathbrowser.

There is a real time scenario where we need to use the same component in several pages and for that particular use case reference component is very helpful. 

Let's suppose we are having some tables which are being shown in many pages. Now suppose the author created a page "P" to put all the tables and try to reference the tables in actual pages from that centralized page.

Now as the page "P" is having so many table components, so while using reference
components to point to a specific table, it is difficult to identify by the content-authors which table they need to point, because in the pathbrowser, the component node name is being shown.

Fig1: Reference Component dialog for choosing a table component

While debugging out the pathbrowser behavior, I figured out that in
“/libs/granite/ui/components/foundation/form/pathbrowser/render.jsp”, line No 42,
the pathbrowser shows the “jcr:title” of the resource value and if not available then shows
resource name.
Fig2: PathBrowser component render.jsp logic
So to solve this issue;  We can provided a title field with name property “jcr;title”
in table component so that if the authors can provide a “title” to the table to define what this
table is about, then in the pathbrowser, the title will be visible and authors are able to
point to the correct component while using reference component.

You can do it for all the components by making a generic tab, so that whenever authors
face this issue, they can immediately provide a “jcr:title” to the component.
Fig3: After providing jcr:title the view in pathbrowser in reference component


3. While login via a user which is part of "content-authrs" group, the “Modified By”

for Asset is visible as “External User” but not as the actual person name:
Usually to set up content-authors permission for a specific project, we take reference
from OOTB content-authors and then add more permissions based on the additional
hierarchies for the specific project.

But there can be some permissions which AEM OOTB content-authors don’t have but you need to provide them.
For the assets, content authors are not able to see who modified the image last, but can
see “External User” as ” Modified By”  for all the images.
Fig4: The "Modified By" as "Extenal User"  while logging from content-authors


Solution: I go through the code and found out that the column preview ‘s HTML is
getting rendered from here:
/libs/dam/gui/coral/components/admin/contentrenderer/column/columnpreview/columnpreview.jsp
Fig5: The logic from where modified by is visible in Assets columnnview


So basically to fetch the value of "Modified by" the code is trying to get the formatted Name which can only be accessible when the author has "/home/users" permission.
To solve this issue, you need to give read permission to “/home/users” hierarchy to content-authors and it will start showing the actual user who modified the assets last.
Fig6: After fixing the permission, the "Modified By" is showing "Actual User"

I hope you find it a good read.Thanks and Happy Learning.

Saturday, April 18, 2020

Issues in sorting the results based on Published Date in AEM

Hello Everyone,

Problem Statement:
Need to sort a page hierarchy based on the last published date. So the page which is
being published last, should be visible first.

The problem looks very simple but it was not that simple. Using an AEM query, pages can be sorted based on "cq:lastReplicated" and it works perfectly fine for the author. But when the code is being deployed in the publish server, the query was not giving same results as author.

Here is the query to sort pages based on cq:lastReplicated:
path=/content/we-retail
type=cq:Page
orderBy=@jcr:content/cq:lastRelicated
orderby.sort=dec

Why the query was not providing the same results in Publish as author?
The publish server doesn't have "cq:lastReplicated" property and that’s how the logic is getting failed.

What I tried?
Approach 1: After a bit research, I found out that "cq:lastReplicationAction, cq:lastReplicated, cq:lastReplicatedBy properties are not carried over the publish by OOTB AEM design". In order to carry these properties, disable this component "com.day.cq.replication.impl.ReplicationPropertiesFilterFactory" in the author instance.
Fig1: com.day.cq.replication.impl.replicationPropertiesFactory Component
The components gets enabled when the server restarts.To make it not getting enabled on server restart, Use "ACS AEM Commons -OSGi Component disabler" OSGi configuration to disable it permanently.
Fig2: ACS AEM Commons-OSGiComponent Disabler COnfiguration
After this, when a page gets activated, all the replication properties started to be carried over to the publishing server and it seems that the problem is solved now, but still the results were not same as expected.

Why this approach didn't work out?

1. If a new page gets created in the author at 2:00 AM and publish the page then there is no cq:lastreplicated in the publish server for the first time.

2. If you have a page being published at 2:00 AM and now you are again publishing it at 3:00, the lastReplicated Date in the publish server will be 2:00 AM because when a node gets replicated from author, first it goes it to publish and then it update the date in author. So by this, publishers were not having the replicated date matched by the author but one step behind the author.
cq:lastReplicated Date in author ≠  cq:lastReplicated Date in publish

So making "cq:lastReplicated" property available to publish didn’t solve the problem.

Approach 2: Then I realized that the "cq:lastReplicated" date in the author is the same as "cq:lastModified" date in publish. Because for publish when it gets the data, it consider that as last modified for the node.Ideally these values will be the same (ignoring seconds), if a page gets published and it immediately goes to the publish server.

Note: publish server don't take the cq:lastModified date from author, but it updates it's own date based on when it gets the data.
It means cq:lastModified date is not carried over to publish from author.

So then how we can conclude this:
cq:lastReplicated Date in author   cq: lastModified date in publish

Conclusive Points: So now there are two ways:
1. Either you can make your query based on the "cq:lastModified" date for both author/publish  but you need to make the author aware that in author, results are based on last modified but in publish, it will be based on like the last Published date.
2. Or you can make your query based on the run modes. If run mode is author make the query based on "cq:lastreplicated" date. But in case of publish, make the query based on the "cq:last Modified" date.

So that's how Approach 2 solved the issue.

I hope you find it a good read.Thanks and Happy Learning.