Pages

Showing posts with label Tips & Tricks. Show all posts
Showing posts with label Tips & Tricks. Show all posts

The Struggles of Personalization in ADF

A Mike Heeren & Richard Olrichs co-production

ADF comes with the out-of-the-box features of personalization. This means that whenever you configure personalisation, users can persist changes they make to the application across sessions and personalize their experience with the application. We have seen that this feature can also confuse some of our users, so it is not always wise to use this. It depends on the use case you have. However, when recently implementing personalization on an ADF 12.2.1.2 application, we had a couple of issues regarding persisting these personalizations to the MDS.

We felt that most of the blogs we came across while implementing these features, share the joyful out-of-the-box configuration. Just select some of the checkboxes in the properties and you are done, ready to enjoy your beer and have your designers and product owners cheer for you.
Sometimes however, real life applications at customers do not match the out-of-the-box configuration and things can become a little bit more tricky than you might expect.

Let's start at the top, and go through some of the steps you will always need within your application for personalization to work. You need to have authentication and authorization set up. If you also want to follow our struggles, we have posted a sample application at the end of this blog to show both the problems as well as the solutions.

Getting started with customizations

The basic configuration of customization in ADF is pretty simple. We start with a simple ADF application (with authentication already configured), and select the Project Properties > ADF View. Here we check Enable user customization and Across sessions using MDS, as seen below:



When enabling user customizations, the following files are edited:
In the adf-config.xml file the following lines are added:

 
  oracle.adf.view.rich.change.MDSDocumentChangeManager
 


In the web.xml file the javax.faces.FACELETS_RESOURCE_RESOLVER context-param is changed from oracle.adfinternal.view.faces.facelets.rich.AdfFaceletsResourceResolver to oracle.adfinternal.view.faces.facelets.rich.MDSFaceletsResourceResolver, and the following blocks are added:

 adflibResources
 oracle.adf.library.webapp.ResourceServlet

...

 adflibResources
 /adflib/*

...

 ADFLibraryFilter
 oracle.adf.library.webapp.LibraryFilter

...

 ADFLibraryFilter
 /*
 FORWARD
 REQUEST

...

 oracle.adf.jsp.provider.0
 oracle.mds.jsp.MDSJSPProviderHelper


 org.apache.myfaces.trinidad.CHANGE_PERSISTENCE
 oracle.adf.view.rich.change.FilteredPersistenceChangeManager


Finally, the following block will be added to the project .jpr file:

 
 


After this we need to configure the adf-config.xml file and add the oracle.adf.share.config.UserCC Customization Class on the MDS tab. This is a default customization class that is shipped with ADF. You can also write your own, but that is more of a use case for customization than it is for personalization. In our case we’ll just configure the application using the UserCC:



Now you need to configure the components that you want the end user to be able to personalize. This can be done in the View tab. Select the ADF Faces Components Tag Library, because we want to personalize the table and column components, these are default component from ADF Faces. By default all attributes will be persisted, you can uncheck them if you do not wish to persist these:



After the above steps are configured, you can give a fancy demo on your demo application, and everybody is happy. However, in our production application, there were still a few issues to tackle, we struggled with some of these.

Struggle 1: Task flows from libraries combined with a file based MDS on a Windows machine.


Our application was not a simple MVC application, but like many application out there, we used ADF libraries to include taskflows from library projects and included them in a bigger main application. When we turned on personalisation on components from task flows which come from libraries instead of directly from the application, they were not correctly persisted to the file based MDS on Windows machines.

When running the application via JDeveloper (in our example application by right clicking default.jsf in the ViewController project, and selecting Run), we see that the task flow that comes directly from the ViewController project (the table on the left of the screen), behaves as expected. However, when personalizing the table from the task flow that comes from the imported library (so from the ViewControllerLibrary project), we see the following warning in the log files.

    

We can verify that the preferences were persisted to the MDS for the left table, but not for the right table, by opening the application (with the same user) in another browser:



Unfortunately, this issue occurs when using task flows from libraries in combination with using a file based MDS on a Windows machine. Weblogic is not able to create a file path, which contains !/ (which is used to indicate that the resource is part of a library).

Luckily, in our case all other DTAP environments don’t use a file based MDS, but a database MDS. In the database MDS, we don’t have the file path issues, so there we won’t see this issue. So on all environments except the (local) Integrated WLS, we don’t see these warning logs, and we will see both table personalizations being persisted. It might take you some time to realise this, if you do not want to deploy to Dev or Test without having the Personalization working on your local machine.

Struggle 2: Deploying the application as EAR instead of via JDeveloper


Deploying both from JDeveloper as well as creating an EAR file seems to work. However, there was still some struggle there as well. The personalisation was working when deploying via JDeveloper, but when we would build in EAR from the application via JDeveloper, and deploy it manually to the Integrated WLS via the console, none of the settings were persisted to the MDS, and we saw the following warning in the log files:

     

By opening the application in different browsers again, we can also confirm that neither of customizations the tables is persisted in the MDS now:



When Personalization across sessions with the MDS is configured, JDeveloper always creates a metadata store usages in the adf-config file at deployment time. This configuration is named MAR_TargetRepos.

It does not matter that we have configured a different metadata store usages within the adf-config. If the MAR_TargetRepos is not present while creating the EAR file, it will be added to the adf-config file. The only solution that we found to this, is by naming our metadata store usages to match the expected default, then it will not override or add anything.


      
            
      
 
  
   
    
    
   
  
 


Note that we did not set deploy-target to true, because if we do this, our custom MAR_TargetRepos will be overridden with the default implementation again, when building the EAR file. This default implementation does not contain the FileMetadataStore implementation:



Because we want to override the default MAR_TargetRepos, so the personalization will also work when deploying the EAR instead of deploying via JDeveloper, we do not set the deploy-target to true so the default (false) will be used. In this case the FileMetadataStore configuration will be preserved in the EAR file.

If we deploy the new EAR file, we see the same behaviour as we did when deploying it via JDeveloper. Also, when we use the personalization functions on the screen, we will see files being created within the PersDef folder within the %TEMP% environment variable.

This FileMetadataStore configuration is changed (back) to a DBMetaDataStore configuration by an ANT build script, before deploying on the different DTAP environments instead of the Integrated WLS environment. An example of such ANT script can be found below.


  
  
  
  
 
  
  
  
  
  
 
 
   
   
 
 
  


Be sure that the WLS_HOME environment variable is set in the system environment variables, and the ear.location property is replaced in the ANT file when you want to use the above example.

Struggle 3: Suddenly our application does persisting during the session.


We have configured the adf-config to persist only certain components and attributes across the session. This works very nice and clear, however, suddenly all the other components also persist their state, just not across the session, but during the session.

It is possible that this is not what you want, it certainly was not what we expected or had in mind for our application, but there is nothing much we can do about it. It would have made more sense to turn this off for all the components and only persist those that were configured to be persisted.

Luckily the ADF components have an attribute persist and dontPersist on them. When reading the documentation on these attributes, it sounds exactly like what we need for our application! Before adding the dontPersist attribute to the hundreds of components we have in the application, we decide to test it on a couple. What we found out was very unpleasing, basically these attributes could be used for documentation purpose or for fun, but it certainly did nothing concerning persistence.

We decided to create our own custom class to adjust the framework and get this working. To achieve this, the context-param org.apache.myfaces.trinidad.CHANGE_PERSISTENCE can be adjusted to a custom class.

At first we tried to extend the oracle.adf.view.rich.change.FilteredPersistenceChangeManager class, which is the class ADF uses by default. However, unfortunately this class is declared final. We decided to create a class that extends the org.apache.myfaces.trinidad.change.SessionChangeManager class, and use the FilteredPersistenceChangeManager as an instance variable. This may not be the prettiest solution, but it serves our purpose:
package nl.whitehorses.personalization.changemanager;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;

import oracle.adf.view.rich.change.FilteredPersistenceChangeManager;

import org.apache.myfaces.trinidad.change.AttributeComponentChange;
import org.apache.myfaces.trinidad.change.ChangeManager;
import org.apache.myfaces.trinidad.change.ComponentChange;
import org.apache.myfaces.trinidad.change.DocumentChange;
import org.apache.myfaces.trinidad.change.SessionChangeManager;

public class CustomChangeManager extends SessionChangeManager {
 private final FilteredPersistenceChangeManager fpcmInstance = new FilteredPersistenceChangeManager();

 @Override
 public void addComponentChange(final FacesContext context, final UIComponent component, final ComponentChange change) {
     if (component == null || component.getAttributes() == null) {
         return;
     }
     final String[] persistArray = (String[]) component.getAttributes().get("persist");
     if (persistArray == null) {
         return;
     }
     for (final String persistVal : persistArray) {
         if (persistVal != null && change instanceof AttributeComponentChange && ("ALL".equals(persistVal) || ((AttributeComponentChange) change).getAttributeName().equals(persistVal))) {
             fpcmInstance.addComponentChange(context, component, change);
         }
     }     
 }

 @Override
 public void addDocumentChange(final FacesContext context, final UIComponent component, final DocumentChange change) {
     fpcmInstance.addDocumentChange(context, component, change);
 }

 @Override
 public boolean supportsDocumentPersistence(final FacesContext context) {
     return fpcmInstance.supportsDocumentPersistence(context);
 }

 @Override
 public ChangeManager.ChangeOutcome addDocumentChangeWithOutcome(final FacesContext context, final UIComponent component, final DocumentChange change) {
     return fpcmInstance.addDocumentChangeWithOutcome(context, component, change);
 }
}

As you can see, the logic for the persist attribute has been implemented in the addComponentChange method. The addComponentChange method from the FilteredPersistenceChangeManager instance, will only be called when the component contains the persist attribute which is set to ALL. Besides the addComponentChange method, all other (public) methods from the FilteredPersistenceChangeManager have been implemented to use the instance variable as well.
Conclusion

After some struggles and adjustments to the implementation and configuration of the application, we got personalization to work in our real world application used by customers. We overcame the struggles, but this was not as easy as the blogs on the internet made us believe beforehand. We hope that sharing this experience, might save you for some of the troubles we had.

To give you some more insight in the code and the struggles, we have created a (simple) demo application AdfPersonalization, to reproduce the issues we had, and which we used to solve them.

This application consists of the default Model and ViewController projects. Also, we added a ViewControllerLibrary project. This ViewControllerLibrary project is imported as a library by the ViewController project.

The AdfPersonalization application can be deployed to Weblogic in multiple ways:

  • Using the ‘Run’ button in JDeveloper.
  • Using Application > Deploy > … to IntegratedWebLogicServer in JDeveloper. This can be done to verify that struggle 2 is no longer an issue.
  • Using Application > Deploy > … to EAR, followed by running the replace-persdef-repo ANT target from the build.xml file. Afterwards the EAR can be deployed to a Weblogic servers, which is capable using a database based MDS. This can be done to verify struggle 1 is no longer an issue.
  • The source of this project can be downloaded via AdfPersonalization.zip.

Resources




JDeveloper Shortcut Keys: Reformat All Files in your Project

When writing Java code, it is a good practice to reformat your code before committing it to a source control system. In JDeveloper, you can put this in your save actions, however, JDeveloper will then also reformat all your XML code, this is not always what you want, plus it is a lot slower than reformatting only the Java code.

When in your Java file, you can use the right mouse menu, or the shortcut keys to reformat the java file you are currently working on, this is 'Ctrl' + 'Alt' + 'L' on windows or '⌘' + '⇧' + 'L' on mac.

However, when you change something in your code style, you probably do not want to go by every file individually to reformat the code. Good thing is that JDeveloper can also reformat a whole project at once.
This is not really well documented and it is not in the right mouse window either, but when you select a project in your Application Window and press the shorcut key for reformat, JDeveloper will reformat the whole selected project for you.


JDeveloper Shortcut Keys: Select in Application Window

In more complex projects with a lot of files, you can sometimes lose track of where the files are located. This can be helpful if you have your files saved in a logical package structure. After looking up a file, for example by using the Go to File shortcut key, it might be helpful to know where this file is located.

Well, in JDeveloper there is a shortcut key to find the file in your Application Window. This is 'Alt' + 'Home' on windows or '⌘' + 'Home' on mac. 

As you can see the file is selected and displayed in the Application Window.


Collapse All in the Flow Trace

With more complex BPM and/or BPEL processes, debugging the flow trace can be kind of slow. However, if you know what you are looking for, there might be a trick to help you get where you want to be quicker.

The flow trace can be very long and scrolling through it, or even scrolling all the way to the end can take quite some time. But there is a right mouse menu if you click the icons on the left. So if you mouse click the mediator we see, called ‘RequestHandler’, there will be a popup menu: 

If you select the ‘Collapse All Below’ option in the menu, it will as you might expect, not only collapse the mediator, but it will collapse all the children and their children. Meaning, once collapsed, you can expand them one by one, going to the correct subprocess where you want to be. 


This means, in complex processes, you can get to where you want to be a lot quicker, because the flow trace performance a lot better with all the items collapsed.  


JDeveloper Shortcut Keys: Go To File

I have been noticing that a lot of developers are either struggling with finding files in JDeveloper or taking a long time to open all the packages and browsing to the files. However, JDeveloper has a lot of shortcut keys that come in handy.

First of, a shortcut key to 'Go To Java Type', I think most developers know this one. It is the 'ctrl' + '-' on windows or '⌘' + 'J' on mac, you will get this window:

However, did you also know the search is CamelCaseSensitive? It is a very neat trick, imagine you have connected your ADF sources and want to look up the PageFlowScopeMap in the 'org.apache.myfaces.trinidadinternal.context' package.
You can open the 'Go to Java Type' screen and start typing page, but a lot of java classes will come up:

As said the search box understands CamelCase, so if you instead start typing the fist capital of each CamelCase word, in this case 'PFSM' (PageFlowScopeMap) JDeveloper will understand:

How cool is that?

Now this is not where it ends, instead of only searching for Java Types, you can search over all your files in JDeveloper, using the shortcut key: 'ctrl' + 'alt' + '-' on windows or '⌥' + '⌘' + '-' on mac. The following window will popup:

In here you can search for any file, but again you can also search CamelCase, for example, in case I want to search my ViewControllerBundle.properties file:

Now, in both windows you can also use wildcards (*), however the CamelCase will not work in combination with a wild card anymore.