Tuesday, December 29, 2009

EJB and ADF Faces RC: How to Create a SelectOneChoice with Value Bound to an Object Property

There has been a lot of blog post with regards to creating SelectOneChoice in ADF Faces RC but all are bound to a data control based on ADF Business Components . In this post, I will try show you how to create a selectOneChoice component if you want to bound the value to an object property (not just mapping simple data types like String, Long, etc.).
Please see below a demonstrative code snippet being ConversionRate as the object to be edited or created and the CurrencyFrom and CurrencyTo are the object attributes of ConversionRate that will be implemented as SelectOneChoice:
<af:selectOneChoice value="#{bindings.findConversionRateByIdIterator.currentRow.dataProvider.currencyFrom}"
                       label="CurrencyFrom"
                       required="true" id="soc1"
                       autoSubmit="true">
   <f:selectItems value="#{myBackingBean.currencyItems}"
                     id="si1"/>
</af:selectOneChoice>
<af:selectOneChoice value="#{bindings.findConversionRateByIdIterator.currentRow.dataProvider.currencyTo}"
                          label="CurrencyTo"
                          required="true" id="soc2"
                          autoSubmit="true">
     <f:selectItems value="#{myBackingBean.currencyItems}"
                       id="si2"/>
</af:selectOneChoice>
The trick on this is the "currentRow.dataProvider" thing. Replace the "findConversionRateByIdIterator" above with your own applicable iterator.

Please see below a dummy backing bean class to enforce illustration of the idea:
package blogspot.soadev.view.backing;

import java.util.ArrayList;
import java.util.List;
import oracle.binding.BindingContainer;
import oracle.binding.OperationBinding;
import javax.faces.model.SelectItem;

public class MyBackingBean {
  private List<SelectItem> currencyItems;
  public List<SelectItem> getCurrencyItems() {
      if (currencyItems == null) {
          List<Currency> currencyList = getCurrencyList();
          currencyItems = new ArrayList<SelectItem>();
          for (Currency currency : currencyList) {
              currencyItems.add(new SelectItem(currency, currency.getCode()));
          }
      }
      return currencyItems;
  }
    public List<Currency> getCurrencyList() {
        //findAllCurrencies is methodAction binding
        return (List<Currency>)getOperationBinding("findAllCurrencies").execute();
    }

  public OperationBinding getOperationBinding(String operation) {
    BindingContainer bindings =
      (BindingContainer)JSFUtils.resolveExpression("#{bindings}");
    return bindings.getOperationBinding(operation);
  }
}
class Currency {
  private String code;
  private String description;

  public void setCode(String code) {
    this.code = code;
  }

  public String getCode() {
    return code;
  }

  public void setDescription(String description) {
    this.description = description;
  }

  public String getDescription() {
    return description;
  }
}
class ConversionRate{
  private Currency currencyFrom;
  private Currency currencyTo;
  private Double rate;

  public void setCurrencyFrom(Currency currencyFrom) {
    this.currencyFrom = currencyFrom;
  }

  public Currency getCurrencyFrom() {
    return currencyFrom;
  }

  public void setCurrencyTo(Currency currencyTo) {
    this.currencyTo = currencyTo;
  }

  public Currency getCurrencyTo() {
    return currencyTo;
  }

  public void setRate(Double rate) {
    this.rate = rate;
  }

  public Double getRate() {
    return rate;
  }
}

Saturday, December 26, 2009

EJB Security: Logging the user that invokes a session bean method

If you have a requirement to log the user who created or updated a certain record. You can do so by getting the CallerPrincipal object in the SessionContext of an EJB session bean. The SessionContext can be injected in a session bean using the @Resource annotation. Please see below a sample session bean with an injected SessionContext plus the call to get the CallerPrincipal object:
package oracle;

import java.util.List;
import javax.annotation.Resource;
import javax.ejb.Local;
import javax.ejb.Remote;
import javax.ejb.SessionContext;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

@Stateless(name = "HRFacade", mappedName = "HR_EJB_JPA_App-EJBModel-HRFacade")
@Remote
@Local
public class HRFacadeBean implements HRFacade, HRFacadeLocal {
    @PersistenceContext(unitName="EJBModel")
    private EntityManager em;
    @Resource
    private SessionContext context;

    public HRFacadeBean() {
    }
    public Employee mergeEmployee(Employee employee) {
        String username = context.getCallerPrincipal().getName();
        employee.setUpdatedBy(username);
        return em.merge(employee);
    }
...
}

Friday, December 25, 2009

Integrating JSR 303 Bean Validation with Oracle ADF 11g

In this post, I will try to show you how we were able to successfully integrate the JSR 303 Bean Validation into our ADF 11g Application built with EJB/JPA. The result is a centralized area to define validation constraints using annotations which can be invoked from any layer of the application.

Below is the screen-shot of a sample edit page created with the JSR 303 Bean Validation:
To apply JSR 303 Bean Validation a project, we need to do the following steps:
  1. Download the JSR 303 reference implementation from Hibernate.
  2. Add the 5 jar files into your Model project's Libraries and Classpath (Project Properties).
  3. Annotate your entities with validation annotations like the sample code snippet below:
    ...
    import javax.validation.constraints.DecimalMax;
    import javax.validation.constraints.Max;
    import javax.validation.constraints.Size;
    ...
    public class Employee implements Serializable {
        @Id
        @Column(name="EMPLOYEE_ID", nullable = false)
        private Long employeeId;
        @Column(name="FIRST_NAME", length = 20)
        @Size(min = 3, max = 20)
        private String firstName;
        @Column(name="LAST_NAME", nullable = false, length = 25)
        @Size(min = 3, max = 20)
        private String lastName;
        @Max(1000000)
        private Double salary;
        @Column(name="COMMISSION_PCT")
        @DecimalMax("1.00")
        private Double commissionPct;
    ...
    
  4. Create a utility class that will handle extraction of validation messages like the one below:
    package com.blogspot.soadev.util;
    
    import java.util.Set;
    import javax.validation.ConstraintViolation;
    import javax.validation.Validation;
    import javax.validation.ValidatorFactory;
    
    public class MyUtils {
        public static javax.validation.Validator getValidator() {
    
            ValidatorFactory factory =
                Validation.buildDefaultValidatorFactory();
            return factory.getValidator();
        }
    
        public static String extractConstraintViolationMessages(Set violations) {
            StringBuilder builder = new StringBuilder();
            for (Object obj : violations) {
                if (obj instanceof ConstraintViolation) {
                    builder.append(((ConstraintViolation)obj).getMessage());
                    builder.append("\n");
                }
            }
            return builder.toString();
        }
        //This method returns a set of validation constraint errors
    
        public static Set validateEntity(Object object) {
            javax.validation.Validator validator = getValidator();
            return validator.validate(object);
        }
        // returns null if no violation
    
        public static String getEntityValidationErrorMsg(Object object) {
            Set violations = validateEntity(object);
            if (violations.size() > 0) {
                return extractConstraintViolationMessages(violations);
            }
            return null;
        }
    }
    
  5. In the ViewController project, create the following class that implements javax.faces.validator.Validator.
    package com.blogspot.soadev.view.validator;
    import com.blogspot.soadev.util.MyUtils;
    import com.blogspot.soadev.view.util.JSFUtils;
    import java.util.Set;
    import javax.el.ELException;
    import javax.faces.FacesException;
    import javax.faces.application.FacesMessage;
    import javax.faces.component.EditableValueHolder;
    import javax.faces.component.UIComponent;
    import javax.faces.context.FacesContext;
    import javax.faces.validator.Validator;
    import javax.faces.validator.ValidatorException;
    import javax.validation.ConstraintViolation;
    import javax.validation.Validation;
    
    public class BeanValidator implements Validator {
        private static final String DOT_REGEX = "[.]";
        private static final String BINDINGS = "bindings";
        private static final String INPUT_VALUE = "inputValue";
        private static final String NAME = "name";
        private static final String EMPTY_STRING = "";
        private static final String END_CURLY_BRACKET = "}";
        private static final String DATA_PROVIDER = "currentRow.dataProvider";
        private static final String DOT = ".";
    
        public BeanValidator() {
            super();
        }
    
        public void validate(FacesContext facesContext, UIComponent component,
                             Object object) throws ValidatorException {
            if (component instanceof EditableValueHolder) {
                // Validate input component
                EditableValueHolder input = (EditableValueHolder)component;
                
                try {
                    String expression =
                        component.getValueExpression("value").getExpressionString();
                    if (null != expression) {
                        Set<ConstraintViolation<Object>> constraintViolations =
                            validateValue(object, expression);
    
                        if (constraintViolations.size() > 0) {
                            input.setValid(false);
                            // send all validation messages.
                            String msg =
                                MyUtils.extractConstraintViolationMessages(constraintViolations);
                            FacesMessage fm =
                                new FacesMessage(FacesMessage.SEVERITY_ERROR, msg,
                                                 null);
                            throw new ValidatorException(fm);
                        }
                    }
                } catch (ELException e) {
                    throw new FacesException(e);
                }
            }
    
        }
    
        private Set<ConstraintViolation<Object>> validateValue(Object object,
                                                                                 String expression) {
            Object targetObject = getTargetObject(expression);
            String property = getProperty(expression);
            javax.validation.Validator validator =
                Validation.buildDefaultValidatorFactory().getValidator();
            Set constraintViolations =
                validator.validateValue(targetObject.getClass(), property, object);
            return constraintViolations;
        }
        public String getProperty(String expression){
            if (expression.contains(BINDINGS) && expression.contains(INPUT_VALUE)){
               expression =
                    expression.replaceFirst(INPUT_VALUE, NAME);       
                return JSFUtils.resolveExpression(expression).toString();
            }
            String [] tokens = expression.split(DOT_REGEX);
            String result  = tokens[tokens.length-1];
            result = result.replaceAll(END_CURLY_BRACKET, EMPTY_STRING);
            return result;
        }
        
        public Object getTargetObject(String expression){
            if (expression.contains(BINDINGS) && expression.contains(INPUT_VALUE)){
                expression =
                    expression.replaceFirst(INPUT_VALUE, DATA_PROVIDER);
                return JSFUtils.resolveExpression(expression);
            }
                String [] tokens = expression.split(DOT_REGEX);
                StringBuilder builder = new StringBuilder(tokens[0]);
                for (int i = 1; i < tokens.length - 1; i++){
                    builder.append(DOT);
                    builder.append(tokens[i]);
                }
                builder.append(END_CURLY_BRACKET);
                return JSFUtils.resolveExpression(builder.toString());        
        }
    }
    
  6. Add the JSR303BeanValidator class above to the list of validators in faces-config.xml
  7. Apply the JSR303BeanValidator to your input components in ADF Faces.
    <af:inputText value="#{bindings.firstName.inputValue}"
                                        label="#{bindings.firstName.hints.label}"
                                        required="#{bindings.firstName.hints.mandatory}"
                                        columns="#{bindings.firstName.hints.displayWidth}"
                                        maximumLength="#{bindings.firstName.hints.precision}"
                                        shortDesc="#{bindings.firstName.hints.tooltip}"
                                        id="it2" autoSubmit="true">
                            <f:validator validatorId="JSR303BeanValidator"/>
                          </af:inputText>
                          <af:inputText value="#{bindings.lastName.inputValue}"
                                        label="#{bindings.lastName.hints.label}"
                                        required="#{bindings.lastName.hints.mandatory}"
                                        columns="#{bindings.lastName.hints.displayWidth}"
                                        maximumLength="#{bindings.lastName.hints.precision}"
                                        shortDesc="#{bindings.lastName.hints.tooltip}"
                                        id="it4" autoSubmit="true">
                            <f:validator validatorId="JSR303BeanValidator"/>
                          </af:inputText>
                          <af:inputText value="#{bindings.salary.inputValue}"
                                        label="#{bindings.salary.hints.label}"
                                        required="#{bindings.salary.hints.mandatory}"
                                        columns="#{bindings.salary.hints.displayWidth}"
                                        maximumLength="#{bindings.salary.hints.precision}"
                                        shortDesc="#{bindings.salary.hints.tooltip}"
                                        id="it3" autoSubmit="true">
                            <f:validator validatorId="JSR303BeanValidator"/>
                            <af:convertNumber groupingUsed="false"
                                              pattern="#{bindings.salary.format}"/>
                          </af:inputText>
    
  8. Run your project and try to input invalid values. To display the validation error messages after you tab out of a component, ensure to set the "AutoSubmit" attribute of the component to true.


To know more about JSR 303, click here and here.

Related Posts

ADF Security: The Two Most Useful Security Expression - #{securityContext.regionViewable[''] and #{securityContext.taskFlowViewable['']

If you have a requirement to conditionally display a tab, a link, or a toolbar button to prevent navigation to a protected page allowed only for some specific roles then I bet that the following security expression will satisfy your need:
1)#{securityContext.regionViewable['your.page.targetPageDef']}

Chris Muir has a nice blog about conditionally displaying the global tabs of the ADF UI Shell in which I was able to comment: ADF UI Shell + ADF Security.

But if your requirement is to conditionally display a menu or a toolbar that will launch a bounded task flow (just like the in the dynamic UI Shell) then the following security expression is what you'll need:
2) #{securityContext.taskFlowViewable['/WEB-INF/yourTaskFlow.xml#yourTaskFlow']}

The expressions above will return true if you have the applicable permission, so you can use it in the rendered property of your button, tab, link, or menu. But if your requirement is to disable a component then use "!" the negation operator.

It is also likely that you will need to check the permission programmatically just like when you are a creating a dynamic tree menu based on user roles. Please see the methods below for the translation of the EL security expressions above to pure Java:
import oracle.adf.controller.security.TaskFlowPermission;
import oracle.adf.share.ADFContext;
import oracle.adf.share.security.SecurityContext;
import oracle.adf.share.security.authorization.RegionPermission;
//class declaration
...
    public boolean isRegionViewable(String pageDef) {
        if (pageDef == null) {
            return false;
        }
        RegionPermission permission =
            new RegionPermission(pageDef, RegionPermission.VIEW_ACTION);
        SecurityContext ctx = ADFContext.getCurrent().getSecurityContext();
        return ctx.hasPermission(permission);
    }

    public boolean isTaskFlowViewable(String taskflowId) {
        if (taskflowId == null) {
            return false;
        }
        TaskFlowPermission permission =
            new TaskFlowPermission(taskflowId, TaskFlowPermission.VIEW_ACTION);
        SecurityContext ctx = ADFContext.getCurrent().getSecurityContext();
        return ctx.hasPermission(permission);
    }
...
Many thanks to John Stegeman for helping me figure this out.

Oracle UI Shell: Passing Parameters to a Bounded Task Flow

The discussion below is taken from my feedback on the ADF UI Patterns in OTN forum, I just copied it here for my reference:

Hi!

I have already figured out how to pass parameters to my bounded taskflow that I intend to launch on a separate tab. It would be the same way as how the UI Shell framework passes the TabContext object to the interested taskflows. This cannot be done though without extending the dynamicTabShellDefinition.xml. I am thinking, that this pertinent requirement can easily be supported by just pre-configuring the dynamic "parametersMap" attribute of the taskFlow binding definition to a value like "#{viewScope.parametersMap}". In this case, if I have something that I need to pass as parameter to my bounded taskflows, I just need to put the object on the parametersMap in view scope. I need to test this yet though. Below is a quotation from the Fusion Developers' Guide about the parameterMap:
The <parameterMap> element specifies an EL expression that returns a java.Util.map object. The object contains task flow input parameter name/value pairs.
I have already tested my understanding on the taskFlow binding "parametersMap" attribute above and it works! First, I added an additional parameter in line with the tabContext parameter to my custom dynamicTabShellDefinition.xml. The parameter binding section will be as follows:
<parameters>
   <parameter id="tabContext" value="${viewScope.tabContext}"/>
   <parameter id="parameterMap" value="${requestScope.parameterMap}"/>
</parameters>
Note that instead of using ${viewScope.parameterMap}, I used ${requestScope.parameterMap} because it seems that when I passed a viewScope parameter from a backingBean inside one of my bounded task flows, it will not reach the initialization of my target task flow in the separate tab. Second, I redefined all my taskFlow bindings to define the "parametersMap" attribute value to "#{bindings.parameterMap}". Please see below a redefined taskFlow definition:
<taskFlow id="r0"
    taskFlowId="${viewScope.tabContext.taskflowIds[0]}"
    activation="deferred"
    xmlns="http://xmlns.oracle.com/adf/controller/binding"
    parametersMap="#{bindings.parameterMap}">
<parameters>
<parameter id="tabContext" value="${bindings.tabContext}"
         xmlns="http://xmlns.oracle.com/adfm/uimodel"/>
</parameters>
</taskFlow>
With the steps above, I could already define any parameter that I need in the bounded taskflow: Please see below for a sample parameter definition:
<input-parameter-definition id="__2">
  <name id="__3">employeeId</name>
  <value>#{pageFlowScope.employeeId}</value>
  <required/>
</input-parameter-definition>
Just be sure that before you invoke the backing bean method that will call the TabContext addTab() method you already put the employeeId into the parameterMap and set the parameterMap into requestScope:
...
Map<String, Object> parameterMap = new HashMap<String, Object>();
parameterMap.put("employeeId",employee.getId());
JSFUtils.setRequestAttribute("parameterMap", parameterMap);//JSFUtils is a utility class from FOD
//direct or indirect call to the TabContext addTab() method here