Monday, April 14, 2014

My Shot on Using JMeter to Load Test Oracle ADF Applications

In this post, I will give my shot on using Apache JMeter to load test an Oracle ADF application.
In an ADF Insider demo Using Apache JMeter to load test an ADF applications, Chris Muir described the the use of JMeter as "very configuration heavy" because of the manual settings of the HTTP Request sampler path and parameters to their expression counterparts like "${afrLoop}" to set the values from the responses that were earlier caught by the extractors. This could become a pain especially that Oracle ADF applications has tremendous amount of ajax calls, each of which is a separate HTTP Request with relevant dynamic parameters, even on a simple interaction with the application. I thought of -what if we could eliminate such a excruciating step??? Without it, the use of JMeter with ADF could become a breeze. The challenge was accepted and now I am sharing the solution.

The solution is based from the test plan that is shared by Chris, but I added three BeanShell PreProcessors to automate the setting of the ADF parameters when they are needed and the automatic replacement of jsessionId and afr.ctrl-state in the HTTP Request Path. With these pre-procesors in place, there is no need to modify each recorded HTTP Request Sampler path and parameters. Just record and run.




Some notes:
To run use jmeter on ADF 12c you need to set the afr.Loop extractor Regex to: _afrLoop',\s*'([0-9]{13,16})
in JDeveloper 11.1.1.7 the afr.Loop extractor Regex should be: _afrLoop\",\ "([-_0-9A-Za-z]{13,16})
You can download my jmeter test plan here.

Thursday, May 2, 2013

ADF With No Bindings: SortableFilterableCollectionModel Implementation

Not using ADF bindings but wanted to have a sortable table? This post is for you.
I am glad to share a sortable CollectionModel implementation that is so elegantly simple. :)

This model supports in-memory sorting and filtering. The filtering concept based on groovy will be discussed on a subsequent post.
package soadev.ext.trinidad.model;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import javax.el.ELContext;

import javax.faces.context.FacesContext;

import org.apache.myfaces.trinidad.model.CollectionModel;
import org.apache.myfaces.trinidad.model.SortCriterion;


public class SortableFilterableModel extends CollectionModel {
    private List wrappedData;
    private List<Integer> sortedFilteredIndexList;
    private Integer baseIndex;
    private SortCriterion sortCriterion = null;

    public SortableFilterableModel(List wrappedData) {
        super();
        this.wrappedData = wrappedData;
        sortedFilteredIndexList = new ArrayList<Integer>();
        for (int i = 0; i < wrappedData.size(); i++) {
            sortedFilteredIndexList.add(i);
        }
    }

    public Object getRowKey() {
        return isRowAvailable() ? baseIndex : null;
    }

    public void setRowKey(Object object) {
        baseIndex = object == null ? -1 : ((Integer)object);
    }

    public boolean isRowAvailable() {
        return sortedFilteredIndexList.indexOf(baseIndex) != -1;
    }

    public int getRowCount() {
        return sortedFilteredIndexList.size();
    }

    public Object getRowData() {
        return wrappedData.get(baseIndex);
    }

    public int getRowIndex() {
        return sortedFilteredIndexList.indexOf(baseIndex);
    }

    public void setRowIndex(int i) {
        if(i < 0 || i >= sortedFilteredIndexList.size()){
            baseIndex = -1;
        }else{
            baseIndex =  sortedFilteredIndexList.get(i);
        }
        
    }

    public Object getWrappedData() {
        return wrappedData;
    }

    public void setWrappedData(Object object) {
        this.wrappedData = (List)object;
    }

    public List<Integer> getSortedFilteredIndexList() {
        return sortedFilteredIndexList;
    }

    @Override
    public boolean isSortable(String property) {
        try {
            Object data = wrappedData.get(0);
            Object propertyValue = evaluateProperty(data, property);

            // when the value is null, we don't know if we can sort it.
            // by default let's support sorting of null values, and let the user
            // turn off sorting if necessary:
            return (propertyValue instanceof Comparable) ||
                (propertyValue == null);
        } catch (RuntimeException e) {
            e.printStackTrace();
            return false;
        }
    }

    private Object evaluateProperty(Object base, String property) {

        ELContext elCtx = FacesContext.getCurrentInstance().getELContext();
        //simple property -> resolve value directly
        if (!property.contains(".")) {
            return elCtx.getELResolver().getValue(elCtx, base, property);
        }
        int index = property.indexOf('.');
        Object newBase =
            elCtx.getELResolver().getValue(elCtx, base, property.substring(0,
                                                                           index));

        return evaluateProperty(newBase, property.substring(index + 1));
    }

    @Override
    public List<SortCriterion> getSortCriteria() {
        if (sortCriterion == null) {
            return Collections.emptyList();
        } else {
            return Collections.singletonList(sortCriterion);
        }
    }

    @Override
    public void setSortCriteria(List<SortCriterion> criteria) {
        if ((criteria == null) || (criteria.isEmpty())) {
            sortCriterion = null;
            // restore unsorted order:
            Collections.sort(sortedFilteredIndexList); //returns original order but still same filter
        } else {
            SortCriterion sc = criteria.get(0);
            sortCriterion = sc;
            _sort(sortCriterion.getProperty(), sortCriterion.isAscending());
        }
    }

    private void _sort(String property, boolean isAscending) {

        if (getRowCount() == 0) {
            return;
        }
        if (sortedFilteredIndexList!= null && !sortedFilteredIndexList.isEmpty()) {
            Comparator<Integer> comp = new Comp(property);
            if (!isAscending)
                comp = new Inverter<Integer>(comp);
            Collections.sort(sortedFilteredIndexList, comp);
        }
    }

    private final class Comp implements Comparator<Integer> {
        public Comp(String property) {
            _prop = property;
        }

        public int compare(Integer x, Integer y) {
            Object instance1 = wrappedData.get(x);
            Object value1 = evaluateProperty(instance1, _prop);

            Object instance2 = wrappedData.get(y);
            Object value2 = evaluateProperty(instance2, _prop);

            if (value1 == null)
                return (value2 == null) ? 0 : -1;

            if (value2 == null)
                return 1;

            if (value1 instanceof Comparable) {
                return ((Comparable<Object>)value1).compareTo(value2);
            } else {
                // if the object is not a Comparable, then
                // the best we can do is string comparison:
                return value1.toString().compareTo(value2.toString());
            }
        }
        private final String _prop;
    }

    private static final class Inverter<T> implements Comparator<T> {
        public Inverter(Comparator<T> comp) {
            _comp = comp;
        }

        public int compare(T o1, T o2) {
            return _comp.compare(o2, o1);
        }

        private final Comparator<T> _comp;
    }
}
sample page
<?xml version='1.0' encoding='UTF-8'?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.1"
          xmlns:f="http://java.sun.com/jsf/core"
          xmlns:h="http://java.sun.com/jsf/html"
          xmlns:af="http://xmlns.oracle.com/adf/faces/rich">
  <jsp:directive.page contentType="text/html;charset=UTF-8"/>
  <f:view>
    <af:document id="d1">
      <af:form id="f1">
        <af:panelCollection id="pc1">
          <f:facet name="menus"/>
          <f:facet name="toolbar">
            <af:toolbar id="t2">
              <af:commandToolbarButton text="Action" id="ctb1"
                                       actionListener="#{viewScope.sortableFilterableForm.action}"/>
            </af:toolbar>
          </f:facet>
          <f:facet name="statusbar"/>
          <af:table var="row" rowBandingInterval="0" id="t1"
                    value="#{viewScope.sortableFilterableForm.model}"
                    rowSelection="multiple"
                    binding="#{viewScope.sortableFilterableForm.table}"
                    selectedRowKeys="#{viewScope.sortableFilterableForm.selection}"
                    queryListener="#{viewScope.sortableFilterableForm.tableFilter}"
                    filterModel="#{viewScope.sortableFilterableForm.descriptor}"
                    filterVisible="true" emptyText="no result found">
            <af:column sortable="true" headerText="Job Id" align="start"
                       id="c2" filterable="true" sortProperty="jobId">
              <af:outputText value="#{row.jobId}" id="ot1"/>
            </af:column>
            <af:column sortable="true" headerText="Job Title" align="start"
                       id="c4" filterable="true" sortProperty="jobTitle">
              <af:outputText value="#{row.jobTitle}" id="ot4"/>
            </af:column>
            <af:column sortable="true" headerText="Max Salary" align="start"
                       id="c1" filterable="true" sortProperty="maxSalary">
              <af:outputText value="#{row.maxSalary}" id="ot3"/>
            </af:column>
            <af:column sortable="true" headerText="Min Salary" align="start"
                       id="c3" filterable="true" sortProperty="minSalary">
              <af:outputText value="#{row.minSalary}" id="ot2"/>
            </af:column>
            <af:column sortable="true" headerText="Job Type" align="start"
                       id="c5" filterable="true" sortProperty="jobType.color">
              <af:outputText value="#{row.jobType.color}" id="ot5"/>
            </af:column>
          </af:table>
        </af:panelCollection>
      </af:form>
    </af:document>
  </f:view>
</jsp:root>

This is somewhat derived from the trinidad SortableModel implementation but even made simpler.