This post will describe solution how to share custom user data in ADF BC. There is a way to access HTTP session scope directly from ADF BC - Bad Practice for Session Scope Access in ADF BC, while you can follow this approach in exceptional cases, this is not recommended from enterprise architecture point of view. Mainly because you may face snow ball effect - there will be more and more ADF BC and ADF UI dependency introduced over time and at the end will make system not maintainable. Solution is generic and will cover following requirements:
1. Custom user data will be initialized for every AM instance automatically
2. Custom user data will be preserved between passivation/activation requests
Download sample application - UserDataADFBCSample.zip. Implemented solution is based on such architecture:
Where on the higher level we have user preferences variables stored in HTTP session. For each Data Control, where we want associated Application Module to be initialized with custom variables for User Data, is defined custom Data Control factory class. Data Control factory class is calling generic Application Module implementation class, where it invokes generic method to populate User Data. Every AM extends from generic Application Module implementation class - this means it gets User Data set. There are standard methods for passivation and activation overriden inside generic Application Module implementation class - this allows to preserve custom User Data between passivation/activation requests.
From developer perspective, in order to use this solution for specific AM - only one configuration thing is required - set custom Data Control factory class, the rest will be handled by implemented generic classes. Select Data Control usage from DataBindings.cpx:
Set Data Control factory class to our custom class:
I will describe now, what happens behind the scenes - how this solution works based on proposed architecture.
1. Make sure Model project is configured with Base Classes, specifically in this case with generic Application Module class (responsible for extended activation/passivation process and User Data initialization):
Generic Application Module class overrides ADF BC framework passivateState(...) method - its the place, where we store custom variables from User Data along with standard ADF BC data into PS_TXN table:
activateState(...) method is overriden as well - to initialize activated custom variables back into User Data:
Generic User Data initialization method is pretty simple in this example, it stores only one variable:
Every AM should extend from generic class. For test purpose, I have implemented custom method to print User Data entry - you will see that it will be preserved across passivation/activation requests thanks to the Application Module implementation generic class - CustomAMImpl:
2. There is custom Data Control factory class defined. This class substitutes Data Control class with our own custom class:
Custom Data Control class overrides only one method - setSessionCookie(...). This method is called by the framework only once, when Data Control gets initialized (Data Control may get initialized multiple times during the same session, if you are using isolated Task Flows (not recommended)). From setSessionCookie(...) method, we are getting instance of generic Application Module implementation class (by casting Data Provider) and initializing User Data with user preferences variable value stored in HTTP session scope. This is completely generic, because action is done through generic class without touching underlying AM for selected Data Control:
3. Time to test. You should test with AM pooling OFF - means will simulate stress test environment and force AM instance to passivate/activate on each request:
With jbo detail logging enabled, we can see that passivation request stores into PS_TXN table along with standard ADF BC data, our own custom User Data also (because overriden method in CustomAMImpl):
Tags for custom entry and other data is declared inside CustomAMImpl. There is limitation of max 10 entries for custom User Data passivation is set, you can increase it (its safer to control this, in case someone will start to enter hundreds of entries into User Data):
When fragment is loaded, based on our test - passivation/activation happens (AM pooling is OFF). With default implementation of User Data - values will be lost, but now CustomAMImpl takes care for this. Press Print Uset Data Entry to test if variable from User Data is retrieved correctly:
Variable data is printed:
4. Task Flow isolated mode. This solution works for Task Flow isolated mode:
When task flow is opened in isolated mode - setSessionCookie(...) method from Data Control class gets invoked again:
This mean, there is new fresh AM instance created of the same Application Module. But, because setSessionCookie(...) method is called - user data gets initialized for that new instance. User data can be initialized not only during login process, but during session lifetime as well.
We can test this by opening isolated task flow:
Print user data variable:
Data is printed successfully:
5. Test without passivation/activation for custom User Data. Comment out custom code for User Data passivation inside CustomAMImpl class:
There will be no custom data added into passivation package:
User data will be NULL, after passivation/activation request:
1. Custom user data will be initialized for every AM instance automatically
2. Custom user data will be preserved between passivation/activation requests
Download sample application - UserDataADFBCSample.zip. Implemented solution is based on such architecture:
Where on the higher level we have user preferences variables stored in HTTP session. For each Data Control, where we want associated Application Module to be initialized with custom variables for User Data, is defined custom Data Control factory class. Data Control factory class is calling generic Application Module implementation class, where it invokes generic method to populate User Data. Every AM extends from generic Application Module implementation class - this means it gets User Data set. There are standard methods for passivation and activation overriden inside generic Application Module implementation class - this allows to preserve custom User Data between passivation/activation requests.
From developer perspective, in order to use this solution for specific AM - only one configuration thing is required - set custom Data Control factory class, the rest will be handled by implemented generic classes. Select Data Control usage from DataBindings.cpx:
Set Data Control factory class to our custom class:
I will describe now, what happens behind the scenes - how this solution works based on proposed architecture.
1. Make sure Model project is configured with Base Classes, specifically in this case with generic Application Module class (responsible for extended activation/passivation process and User Data initialization):
Generic Application Module class overrides ADF BC framework passivateState(...) method - its the place, where we store custom variables from User Data along with standard ADF BC data into PS_TXN table:
activateState(...) method is overriden as well - to initialize activated custom variables back into User Data:
Generic User Data initialization method is pretty simple in this example, it stores only one variable:
Every AM should extend from generic class. For test purpose, I have implemented custom method to print User Data entry - you will see that it will be preserved across passivation/activation requests thanks to the Application Module implementation generic class - CustomAMImpl:
2. There is custom Data Control factory class defined. This class substitutes Data Control class with our own custom class:
Custom Data Control class overrides only one method - setSessionCookie(...). This method is called by the framework only once, when Data Control gets initialized (Data Control may get initialized multiple times during the same session, if you are using isolated Task Flows (not recommended)). From setSessionCookie(...) method, we are getting instance of generic Application Module implementation class (by casting Data Provider) and initializing User Data with user preferences variable value stored in HTTP session scope. This is completely generic, because action is done through generic class without touching underlying AM for selected Data Control:
3. Time to test. You should test with AM pooling OFF - means will simulate stress test environment and force AM instance to passivate/activate on each request:
With jbo detail logging enabled, we can see that passivation request stores into PS_TXN table along with standard ADF BC data, our own custom User Data also (because overriden method in CustomAMImpl):
Tags for custom entry and other data is declared inside CustomAMImpl. There is limitation of max 10 entries for custom User Data passivation is set, you can increase it (its safer to control this, in case someone will start to enter hundreds of entries into User Data):
When fragment is loaded, based on our test - passivation/activation happens (AM pooling is OFF). With default implementation of User Data - values will be lost, but now CustomAMImpl takes care for this. Press Print Uset Data Entry to test if variable from User Data is retrieved correctly:
Variable data is printed:
4. Task Flow isolated mode. This solution works for Task Flow isolated mode:
When task flow is opened in isolated mode - setSessionCookie(...) method from Data Control class gets invoked again:
This mean, there is new fresh AM instance created of the same Application Module. But, because setSessionCookie(...) method is called - user data gets initialized for that new instance. User data can be initialized not only during login process, but during session lifetime as well.
We can test this by opening isolated task flow:
Print user data variable:
Data is printed successfully:
5. Test without passivation/activation for custom User Data. Comment out custom code for User Data passivation inside CustomAMImpl class:
There will be no custom data added into passivation package:
User data will be NULL, after passivation/activation request:























12 comments:
I'm still trying to figure out what is the username and password for logging into the example app. Please help.
Hi,
Username/password is standard for my blog samples: redsam/welcome1. If you want, you can add your username in jazn-data.xml
Regards,
Andrejus
Hi Andrejus,
I have a doubt. can we access the userData in the session of ApplicationModule say AM1 from ApplicationModule AM2?
Hi,
By design in ADF, User Data exists only in scope of one AM instance. Its why I outline solution architecture in this post, how to initialize User Data in multiple AM's.
Regards,
Andrejus
Hi Andrejus,
I have an application module inside which i am validating user inside a method. i want to create a user session object for each user that log in to the system. all my user credentials and roles are stored in the database tables. I want to make an object of this.
so if i create it in one of the application module .. so is it not possible to use the values in other application modules? what is the alternate method for storing the user session object?
Hi,
I would suggest to return value from that custom AM method into ViewController HTTP session scope.
Then you can initialize each AM User Data through custom Data Control Factory class - as per this blog.
Regards,
Andrejus
Hey Andrejus,
I have a use case where I need to load the session data more than just the one time setSessionCookie() is called. In fact I would like to fetch this data and put it into the userData map each time the AM is activated to ensure he AM has up to date data.
The use case goes something like this:
1) AM is instantiated for the first time, creating the Data Control and thus calling setSessionCookie to load the session data into the userData map.
2) AM performs some tasks and is passivated, storing the userData as per his blog.
3) AM is reactivated to perform some further tasks, but now has out of date session data in it's userData map since setSessionCookie will not be called again.
How would you recommend approaching this use case?
Hi,
I believe you can use same solution from the blog, just extend it a bit - add login of updating userData into prepareSession() method in AM Impl class. Its a place, where I'm initializing userData each time during activation.
setSessionCookie is used to load default data. But from prepareSession() you can modify it during each activation if needed.
Regards,
Andrejus
There is one problem in method activateState, for read only first element from jbo.custom.userdata
It should be like this to read all elements in tag jbo.custom.userdata
if (parent != null) {
NodeList nl = parent.getElementsByTagName(USER_DATA);
if (nl != null) {
for (int i = 0; i < nl.getLength(); i++) {
NodeList childNodes = nl.item(i).getChildNodes();
for (int nodesIndex = 0; nodesIndex < childNodes.getLength(); nodesIndex++) {
Node node = childNodes.item(nodesIndex);
if (node != null && node.getAttributes() != null) {
NamedNodeMap attrs = node.getAttributes();
if (attrs.getLength() == 2) {
Node userDataElementKey = attrs.getNamedItem(USER_DATA_ELEMENT_KEY);
Node userDataElementValue = attrs.getNamedItem(USER_DATA_ELEMENT_VALUE);
if (userDataElementKey != null && userDataElementValue != null) {
String key = userDataElementKey.getNodeValue();
String value = userDataElementValue.getNodeValue();
this.getSession().getUserData().put(key, value);
}
}
}
}
}
}
}
Yes, thanks for update. I was aware of it, I think it was fixed in this post: http://andrejusb.blogspot.com/2012/08/solution-to-control-global.html
Andrejus
Hi Andrejus,
we have the use case that we need to access some session data in our AM. So we tried it this way. However we need those session data in the prepareSession method of the AM.
It seems that the prepareSession is already called when the AM is instantiated, and our data is not set in the AM at that time.
So what would be your approach to get the data in the prepareSession without violating the MVC pattern.
I would need to test this.
Andrejus
Post a Comment