woensdag 13 november 2013

How to run your ADF 11.1.x application under IE 11: a workaround

Introduction

Recently Microsoft has released its new version 11 of Internet Explorer. When developing ADF-applications the most obvious question is of course: will my application still work? The short answer is: NO, you have to wait until Oracle brings out a new patch. When you try to run your application, you will get a popup-message about the fact that you are using a non-supported browser version, and your application will not work the way you expect. Running the browser in compatibility-mode might work in some situations, but users also get annoying popups, and in many cases the layout and the behaviour of the application is not the way it should be.

But there is a workaround. We introduced this workaround in an  ADF 11.1.1.6 application that also had to run under IE10. This is a non-certified combination and running it results in strange client-behavior of the application. E.g. the movement of a splitter results in a complete blank screen as long as you hold the mouse down. As probably in many production environments, we are not (yet) able to migrate to ADF 11.1.1.7, and since the application runs in many different sites, we are not able to control the browser versions. We found a solution that works pretty good. And the good news is: it also seems to work for Internet Explorer 11!
A sample of the solution can be downloaded from IEFilterDemo.zip .


The problem

When looking for a solution I found some examples that were based on the idea of adding a new header to the HttpServletResponse. E.g.

       HttpServletResponse response = (HttpServletResponse)ectx.getResponse();  
       response.addHeader("X-UA-Compatible", "IE=EmulateIE9");  

However, this does not work. When ADF receive a request, one of the things that happen is that, based on the request-headers, it determines the user-agent. If this agent is an MSIE agent, the first thing that ADF writes in the response is an meta-tag that contains the browser version. So a request from IE 10 will always result in a response containing an IE 10 meta-tag:


 <html class="p_AFMaximized" lang="nl-NL" dir="ltr">  
  <head>  
    <meta http-equiv="X-UA-Compatible" content="IE=10.0">  
    ....  

Any attempt to add your own response-header to make the IE browser behave like an older version will be ignored.

IE 11 makes it even worse. This version doesn't send a user-agent header containing a reference to MSIE. It sends a header that should make applications believe the request comes from a 'Gecko-like' browser. The idea is that IE 11 is compatible with these browsers.


user-agent header from IE 11:
Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko


The solution

The solution is quite simple: we have to make ADF believe that the request comes from an older version of IE, so in return ADF will write the correct meta-tag into the response, and IE will act accordingly.

This can be achieved by creating and adding a custom filter. The filter determines the user-agent, and in case of IE 10 or 11, it wraps the request in our own version of a HttpServletRequestWrapper. The WrapperRequest has a method getHeader() in which we control what string is returned when the "user-agent" header is requested.
When we place our filter in the top of the filter-chain, the request that ADF receives, is our WrappedRequest. When ADF tries to determine the user-agent, it will receive the answer given by our WrapperRequest, which makes ADF think we are using an IE 9 or IE 10 browser.

 package view;  
   
 import java.io.IOException;  
   
 import javax.servlet.Filter;  
 import javax.servlet.FilterChain;  
 import javax.servlet.FilterConfig;  
 import javax.servlet.ServletException;  
 import javax.servlet.ServletRequest;  
 import javax.servlet.ServletResponse;  
 import javax.servlet.http.HttpServletRequest;  
 import javax.servlet.http.HttpServletRequestWrapper;  
   
   
 public class IECompatibleFilter implements Filter {  
   public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {  
   
     String userAgentStr = ((HttpServletRequest)request).getHeader("user-agent");  
     // Check to see if we have to do with a IE request. If so, we return a wrapped request.  
     if (userAgentStr != null && (userAgentStr.contains("MSIE 1") || 
                                     userAgentStr.contains("Trident"))) {  
       ServletRequest wrappedRequest = new WrapperRequest((HttpServletRequest)request);  
       chain.doFilter(wrappedRequest, response);  
     } else {  
       chain.doFilter(request, response);  
     }  
   }  
   
   public void destroy() {  
   }  
   
   public void init(FilterConfig arg0) throws ServletException {  
   }  
   
   private class WrapperRequest extends HttpServletRequestWrapper {  
     public WrapperRequest(HttpServletRequest request) {  
       super(request);  
     }  
   
     public String getHeader(String name) {  
       // IE 10: replace 'MSIE 10.x' into 'MSIE 9.x' for ADF 11.1.1.6 and below  
       HttpServletRequest request = (HttpServletRequest)getRequest();  
       if ("user-agent".equalsIgnoreCase(name) && request.getHeader("user-agent").contains("MSIE 10")) {  
         return request.getHeader("user-agent").replaceAll("MSIE [^;]*;", "MSIE 9.0;");  
       }  
       // IE 11: replace the whole agent-string into an MSIE 9.0 string for ADF 11.1.1.6 and below  
       // or MSIE 10.0 for ADF 11.1.1.7 or higher  
       if ("user-agent".equalsIgnoreCase(name) && request.getHeader("user-agent").contains("Trident")) {  
         //Choose your preferred version  
         String newAgentStr = "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.2; Trident/6.0)";  
         return newAgentStr;  
       }  
       return request.getHeader(name);  
     }  
   }  
 }  
   

The filter has to be defined in the web.xml-file of your project. Make sure it is defined above the trinidad-filter, because this one is used by ADF to determine the user-agent.

web.xml
 ...  
  <filter>  
   <filter-name>IECompatibleFilter</filter-name>  
   <filter-class>view.IECompatibleFilter</filter-class>  
  </filter>  
  <filter>  
   <filter-name>trinidad</filter-name>  
   <filter-class>org.apache.myfaces.trinidad.webapp.TrinidadFilter</filter-class>  
  </filter>  
 ...  
  <filter-mapping>  
   <filter-name>IECompatibleFilter</filter-name>  
   <servlet-name>Faces Servlet</servlet-name>  
  </filter-mapping>  
  <filter-mapping>  
   <filter-name>trinidad</filter-name>  
   <servlet-name>Faces Servlet</servlet-name>  
   <dispatcher>FORWARD</dispatcher>  
   <dispatcher>REQUEST</dispatcher>  
  </filter-mapping>  
 ...  


Conclusion

The above solution is not the ideal situation, but as long as we are dealing with non-supported combinations of browsers and ADF versions, it might be a workaround. The nice things is that you don't have to change your application, but you only add the filter to the web.xml. Furthermore I think the solution is not limited to ADF applications.

I have tested this solution with ADF 11.1.1.6, 11.1.1.7 and 11.1.2.3, and they all seem to work under IE 11, but of course I cannot guarantee that it will work in all situations. Give it a try. 

zondag 10 november 2013

ADF 11g: create your own audit-rules and automated fixes

Introduction

In my previous post ADF BC: performance-issues when using View Accessors and List of Values I described the problems that can occur when defining a View Accessor and a related List of Values. JDeveloper sets / changes the View Accessors property 'Row Level Bind Values', and the value set can have a negative effect on the number of queries fired, and thus on the performance of our application.

In this post I will describe a JDeveloper extension that implements an audit-rule and a fix for the Row Level Bind Values property. First I will describe how the extension is build. and after that I will describe how to use the extension in JDeveloper. The full source of the extension can be downloaded from AuditExtensions.zip


Building the extension

The extension is based on the blog Don't fear the Audit -- Part 1 and the samples found in Oracle JDeveloper 11g: Oracle IDE Extension Samples. These samples can be found in your JDeveloper installation directory; search for esdksamples.zip.
Based on the above resources, I've worked out an extension that consists of the following structure:.




AuditRulesAddin
This is the class that defines the Analyzer-classes that are part of this extension. We can include multiple Analyzers if we want to.


 public class AuditRulesAddin extends AbstractAuditAddin {  
   private static final Class[] ANALYZERS = new Class[] { RowLevelBindingAnalyzer.class };  
   
   public Class[] getAnalyzers() {  
     return ANALYZERS;  
   }  
 }  
   


Res.properties 
This is a file that contains labels and descriptions used for this extension.

RowLevelBindingAnalyzer
This is the class that performs the actual analysis of the objects of your project and it reports the errors it finds when we run the audit. One analyzer class can contain multiple audit-rules. Our example only contains one rule. The header of the class defines the rules:



 public class RowLevelBindingAnalyzer extends Analyzer {  
   
   // Refers to properties file with descriptions and labels  
   private static final Localizer LOCALIZER = Localizer.instance("anubis.auditrules.Res");  
   // Defines the category for the audit rules  
   private final Category CATEGORY = new Category("category-vo-checks", LOCALIZER);  
   
   // Definition of the fix.  
   private static final String FIX_RLB = "set-rlb-value";  
   
   // Definition of the tranformation-class that performs the fix.  
   private final RowLevelBindingTransform fixRlb = new RowLevelBindingTransform(FIX_RLB, LOCALIZER);  
   
   // Definition of the actual rule, including the related tranformation-class.   
   // It is possible to define more than one rule in an analyzer-class  
   private Rule INVALID_RLB = new Rule("rule-invalid-rlb", CATEGORY, 
                                                        Severity.ERROR, LOCALIZER, fixRlb);  
      
   {  
     // Do this to make the rule enabled by default.  
     INVALID_RLB.setEnabled(true);  
   }  
   
   /**  
    * @return Define the rules that are validated by this analyzer  
    */  
   public Rule[] getRules() {  
     return new Rule[] { INVALID_RLB };  
   }  
  ....  

The class extends the class oracle.jdeveloper.audit.analyzer.Analyzer and has 4 important methods:


  • public void enter(AuditContext context, Workspace workspace)
    This methods contains the workspace (application) validation code. In our case we do not use it.
  • public void enter(AuditContext context, Project project)
    This methods contains the project validation code. In our case we do not use it.
  • public void enter(AuditContext context, Document document)

    This method contains the code to analyze and validate documents. In our case this method is used to enable the validation of XML-files containing the ViewObject definition.


   /**  
    * Enter a document.  
    * Check to see if this is a ViewObject file. If not, we can stop here.  
    * @param context  
    * @param document  
    */  
   public void enter(AuditContext context, Document document) {  
     if (document != null && document.getDocumentElement() != null) {  
       String firstNodeOfdocument = document.getDocumentElement().getNodeName();  
       if ("ViewObject".equals(firstNodeOfdocument)) {  
         String filePath = context.getUrl().getPath();  
         if (!filePath.substring(filePath.lastIndexOf("/") + 1).matches(".*xml")) {  
           setEnabled(false);  
         }  
       }  
     }  
   }  


  • public void enter(AuditContext context, Element element)

    This method contains the code to analyze and validate individual element. In our case this is the method that is doing the actual work. It determines the actual setting and counts the number of row level bound bind variables. If something is wrong, a ViolationReport is created.
   /**  
    * Enter an element.  
    * This is where we will check the value of the RowLevelBinds element and report  
    * the results back to the Editor.  
    * @param context  
    * @param element  
    */  
   public void enter(AuditContext context, Element element) {  
     if ("ViewAccessor".equals(element.getNodeName())) {  
       String currentrlb = element.getAttribute("RowLevelBinds");  
       int cntrlb = 0;  

       // Count the number of row level bind params. These params do not contain '"' (double-quote).  
       // First, get the paramMap, second, find the transient expression that holds the bind-vaiable  
       for (Node childNode = element.getFirstChild(); childNode != null; ) {  
         Node nextNode = childNode.getNextSibling();  
         if ("ParameterMap".equals(childNode.getNodeName())) {  
           for (Node param = childNode.getFirstChild(); param != null; ) {  
             Node nextChild = param.getNextSibling();  
             Node expression = param.getFirstChild();  
             if (expression != null && "TransientExpression".equals(expression.getNodeName())) {  
               String value = expression.getTextContent();  
               if (value != null && value.indexOf('"') == -1) {  
                 cntrlb++;  
               }  
             }  
             param = nextChild;  
           }  
         }  
         childNode = nextNode;  
       }
  
       String newrlb = cntrlb == 0 ? "false" : "true";  
       if ( // Empty rlb expression  
         (currentrlb == null || currentrlb.trim().length() == 0) ||  
         // rlb = true but there are no row level bound variables  
         ("true".equals(currentrlb) && cntrlb == 0) ||  
         // rlb = fals but there are row level bound variables  
         ("false".equals(currentrlb) && cntrlb != 0)) {  

             // Create a violation report for our specific rule.
             ViolationReport report = context.report(INVALID_RLB);  
             report.addParameter("currentrlb", currentrlb);  
             report.addParameter("newrlb", newrlb);  
             report.addParameter("cntrlb", cntrlb);  
       }  
     }  
   }  


RowLevelTransform
This class is called from the audit-framework to transform the XML-elements that have violated the audit-rule, and thus, for which a ViolationReport is created. It calls the RowLevelBindingFix class to do the actual fixing. See the full source for more details.

RowLevelBindingFix
This class performs the actual fixing of the XML-elements that violate the audit-rule. In our case this class determines and sets the correct value of the ViewAccessors attribute 'RowLevelBinds'. See the full source for more details.

extension.xml
This file contains the definition of the extension. It defines the classes used, and it defines our AuditRulesAddin as an addin in JDeveloper.


 <extension id="anubis.auditrules" version="1.0" esdk-version="1.0"  
       rsbundle-class="anubis.auditrules.Res"  
       xmlns="http://jcp.org/jsr/198/extension-manifest">  
  <name>Demo Audit Rules</name>  
  <dependencies>  
   <import>oracle.ide</import>  
   <import>oracle.ide.audit</import>  
   <import>oracle.ide.audit.core</import>  
   <import>oracle.ide.xmlef</import>  
   <import>oracle.jdeveloper.xmlef</import>  
   <import>oracle.jdeveloper</import>  
   <import>oracle.javacore</import>  
   <import>oracle.jdeveloper.refactoring</import>  
  </dependencies>  
  <classpaths>  
   <classpath>../../../oracle_common/modules/oracle.javatools_11.1.1/javatools-nodeps.jar</classpath>  
  </classpaths>  
  <hooks>  
   <jdeveloper-hook xmlns="http://xmlns.oracle.com/jdeveloper/1013/extension">  
    <addins>  
     <addin headless="true">anubis.auditrules.AuditRulesAddin</addin>  
    </addins>  
   </jdeveloper-hook>  
  </hooks>  
 </extension>  


Deploying and using the extension 

To use the extension in JDeveloper perform the following steps:
  • Deploy the project to a jar-file (in our case anubis.auditrules.jar). 
  • Copy the jar-file to the extension-directory of our JDeveloper installation. This directory can be found under <MIDDLEWARE-HOME>\jdeveloper\jdev\extensions. 
  • Restart JDeveloper
Now we are ready to use the extension. Suppose we take the project as described in my previous post. This project contains a Viewobject EmployeesView that contains a ViewAccessor that refers to the DepartmentsView. The property 'Row Level Bind Values' is set to 'true', which is not correct.




To run our new audit-rule perform the following steps:


  • Select the model-project in the navigator.
  • Select the menu-option 'Build -> Audit Model.jpr'
  • Select the 'Audit rules' profile and edi this profile.
  • Find the 'Anubis VO checks' category and enable it. 
  • Optionallly: disable the other categories and save the profile under a new name.




  • Run the audit. The result will show all VO's that contain ViewAccessors with an incorrect value for the Row Level Binding Values property. 




  • Fix the problem by using the 'Apply Default Fixes' icon from the toolbar. All errors that are solved will be marked.

The above example only shows one error and one fix, but when you run this rule on a large project, you can find and fix all audit-errors at once.

Conclusion

As we have seen it is possible to create our own audit-rules and related fixes. This can be of great help in improving the quality of your project. There is an increasing number of 'standard' audit-rules available in JDeveloper. However it can still be very usefull to create your own rules, especially in combination with the option of defining your own fixes.