Thursday, December 13, 2012

SelectBooleanCheckbox & boolean/Boolean.

I was just using SelectBooleanCheckbox component to store selected boolean value in a request scoped bean, as shown below:

<af:selectBooleanCheckbox id="sbc1" text="#{ethuiBundle.SEARCH}" autoSubmit="true" valueChangeListener="#{DetailsViewBean.searchValueChange}" value="#{DetailsViewBean.search}"/>

What I observed is that when I used primitive boolean type, then value change listener gets fired only when the value switches to true, or when its checked. When its unchecked, then value change listener is not called. Even the setter of the boolean attribute in the bean is not called when the checkbox is unchecked but setter is called when checkbox is checked.

    public void setSearch(boolean search) {
        this.search = search;
        System.out.println("LKAPOOR: SEARCH = " + search);
    }

    public boolean getSearch() {
        return search;
    }

    public void searchValueChange(ValueChangeEvent valueChangeEvent) {
        // Add event code here...
        System.out.println("LKAPOOR:: VALUE CHANGE EVENT, NEW VALUE = " + valueChangeEvent.getNewValue());
        System.out.println("LKAPOOR:: VALUE CHANGE EVENT, OLD VALUE = " + valueChangeEvent.getOldValue());
    }


When I googled for this, found the following forum post:
https://forums.oracle.com/forums/thread.jspa?threadID=944994

After reading this, modified the above code as below:

    public void setSearch(Boolean search) {
        this.search = search;
        System.out.println("LKAPOOR: SEARCH = " + search);
    }

    public Boolean getSearch() {
        return search;
    }

    public void searchValueChange(ValueChangeEvent valueChangeEvent) {
        // Add event code here...
        System.out.println("LKAPOOR:: VALUE CHANGE EVENT, NEW VALUE = " + valueChangeEvent.getNewValue());
        System.out.println("LKAPOOR:: VALUE CHANGE EVENT, OLD VALUE = " + valueChangeEvent.getOldValue());
    }


By using Boolean type instead of primitive boolean type, value change listener and setter is getting called for each event (checked and unchecked). I was not aware of this so thought of documenting and sharing this.

Thursday, December 6, 2012


Things To Know About PS_TXN - ORA-00001: unique constraint violated - Using Multiple Schemas For A Single ADF Web Application.

Let me share with you an issue that we faced just recently. We had a requirement where we needed to show data from multiple schemas in our ADF Web application. Earlier, we were using a single schema for all the data in the database, but now some of our tables were moved to a new schema. Because of this new schema requirement, we had to think of its consequences on activation and passivation. As listed here:


 The ADF internally used PS_TXN table to store passivation information. Here is the relevant text from the above link:

The passivated XML snapshot is written to a BLOB column in a table named PS_TXN, using a connection specified by the jbo.server.internal_connection property. Each time a passivation record is saved, it is assigned a unique passivation snapshot ID based on the sequence number taken from the PS_TXN_SEQ sequence. The ADF session cookie held by the application module data control in the ADF binding context remembers the latest passivation snapshot ID that was created on its behalf and remembers the previous ID that was used.

The following image shows how PS_TXN table’s data looks like:


In PS_TXN table, the COLLID column initially gets its value from PS_TXN_SEQ sequence, but after that its value is increased by the internal ADF generator. Because of this, when we use more than one schema and if each schema is having its own PS* objects then we can face a bug that is there in the framework, as listed in https://forums.oracle.com/forums/thread.jspa?threadID=2316855

If you are facing the following error while using multiple schemas, then you will be hitting that framework bug:

Caused By: java.sql.SQLIntegrityConstraintViolationException: ORA-00001: unique constraint (*******SCHEMA.PS_TXN_PK) violated

A workaround to this problem is to clean the PS_TXN tables regularly. This workaround is listed in the document, ADF 11g: How to Debug "JBO-28030: Could not insert row into table PS_TXN" [ID 1065862.1], available in support.oracle.com. Here is the summary of that document:

Cause

For some reason, your sequence PS_TXN_SEQ returns a value that was already inserted in to the PS_TXN table. This has caused a violation of the unique constraint (ICXPRODCS.PS_TXN_PK)

Solution

Cleaning up the table will help solve this issue.

The solution in this scenario is to use only one PS_TXN_SEQUENCE for all the schemas. So, if we have 2 schemas, then in the second schema instead of creating a new sequence, you can create a synonym that points to the sequence created in the first schema. Then this issue is avoided.

The best way to avoid this issue is to use just one PS_TXN table and PS_TXN_SEQ sequence, preferable in a separate schema independent from the current application specific schema.

For this we need to understand where these PS* objects are created and how can this be customized. As explained in the “Controlling Where the Temporary Tables Are Created “ section of http://www.oracle.com/technetwork/developer-tools/jdev/overview/bc4j-temp-tables-087270.html, the jbo.server.internal_connection property of AM is used to specify where these objects are created. Here is the summary of this link:

The BC4J framework recognizes a configuration property named   jbo.server.internal_connection to give the developer control over what database connection/schema should be used for the creation of the PCOLL temporary tables described below. If the value of this configuration parameter is not set by the developer (which is the default situation) then the framework will create the temporary tables using the credentials of the current application database connection. To keep the temporary information separate, it will use a different connection instance from the connection pool, but the database credentials will be the same as the current user.

 So, we can create a new schema just to store the PS* objects. After creating a new schema ( and creating PS* objects in that schema either manually or should be created automatically when passivation needs to be done), we can create a new Data Source to point to the new schema. After that, we just need to modify the jbo.server.internal_connection property of AM to give the JNDI of the new DS so that PS* objects of the new schema are used. We can test the fix by disabling the application module pooling to enable frequent passivation.

References:
http://docs.oracle.com/cd/E14571_01/web.1111/b31974/bcstatemgmt.htm
http://oraclefusionfacts.blogspot.in/2011/09/ora-01031-insufficient-privileges-jbo.html
https://forums.oracle.com/forums/thread.jspa?threadID=2316855
http://www.oracle.com/technetwork/developer-tools/jdev/overview/bc4j-temp-tables-087270.html

Wednesday, February 29, 2012

Showing XML Payload In a Popup.

If you also have a requirement where you need to show XML content/payload stored in a CLOB column in the database on an ADF UI, like in a popup as shown below:



Then here are the steps you need to follow to configure a popup to show XML content nicely on the UI:

  1. Create a VO that contains that CLOB attribute.
  2. Create a popup with Content Delivery property set to 'LazyUncached'.
  3. Inside popup, put a Dialog (or panelWindow if you like with Modal property set to true).
  4. Inside Dialog, put a PanelFormLayout component.
  5. Inside PanelFormLayout component, drag the CLOB attribute as inputText component. Configure the inputText component as follows:    
    1. Set Read-Only as true, if you dont want the user to edit it, as in my case. 
    2. Set the Rows property to any value between 5 to 40 depending on how much payload you want to show on screen at once. 
    3. Set the Columns property to any value between 5 to 40 depending on how much characters in a row you want to show on screen at once. 
    4. Set Wrap property to 'soft'. 
    5. Set the Converter property to a customised converted, like ClobConverter (code shown below).
  6. Use ShowPopupBehavior operation as child of link/button to render the popup.

Here is how that popup code looks like:   

          <af:popup childCreation="deferred" autoCancel="disabled" id="p1" contentDelivery="lazyUncached">
            <af:dialog id="d1" type="cancel">
              <f:facet name="buttonBar"/>
              <af:panelBox text="B2b Payload*" id="pb1">
                <f:facet name="toolbar"/>
                <af:panelFormLayout id="pfl1">
                  <f:facet name="footer"/>
                  <af:inputText value="#{bindings.B2bPayload.inputValue}" label="#{bindings.B2bPayload.hints.label}"
                                required="#{bindings.B2bPayload.hints.mandatory}"
                                columns="100"
                                maximumLength="#{bindings.B2bPayload.hints.precision}"
                                shortDesc="#{bindings.B2bPayload.hints.tooltip}" id="it1" converter="ClobConverter"
                                readOnly="true" rows="21" wrap="soft">
                    <f:validator binding="#{bindings.B2bPayload.validator}"/>
                  </af:inputText>
                </af:panelFormLayout>
              </af:panelBox>
            </af:dialog>
          </af:popup>

    The ClobConverter looks like as shown below:

package com.emerson.eth.adf.view.converter;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;

import javax.faces.convert.ConverterException;

import oracle.jbo.domain.ClobDomain;

public class ClobConverter implements Converter {
    public ClobConverter() {
        super();
    }

    public Object getAsObject(FacesContext context,
                              UIComponent component, String value) {
        if (context == null || component == null) {
            throw new NullPointerException("FacesContext and UIComponent can not be null");
        }

        if (value == null) {
            return null;
        }
        try {
            return new ClobDomain(value);
        } catch (Exception ex) {
            final String message =
                String.format("Unable to convert boolean value \"%s\" into a oracle.jbo.domain.Number",
                              value);
            throw new ConverterException(message, ex);
        }
    }

    public String getAsString(FacesContext context,
                              UIComponent component, Object value) {
        if (context == null || component == null) {
            throw new NullPointerException("FacesContext and UIComponent can not be null");
        }
        return value.toString();
    }
}

You need to register this converter in the faces-config.xml to make it visible in the inputText component configuration wizard, as shown below:

<?xml version="1.0" encoding="windows-1252"?>
<faces-config version="2.0" xmlns="http://java.sun.com/xml/ns/javaee">
  <application>
    <default-render-kit-id>oracle.adf.rich</default-render-kit-id>
  </application>
  <converter>
    <converter-id>ClobConverter</converter-id>
    <converter-class>com.emerson.eth.adf.view.converter.ClobConverter</converter-class>
  </converter>
</faces-config>

NOTE:

For XML to appear in the UI, you also need to disabled the wrapping of LOBs done by the Web Logic Server at the data source level. To do this, un-check the 'wrap data type' check box available at:
WLS Home -> Data Sources -> DS used by the app -> Connection Pool -> Advanced.


Saturday, February 11, 2012

OWC Discussions Admin UI's Login Problem.

If you have just installed Oracle WebCenter 11.1.1.5 then you will also face the problem where you will not be allowed to login to OWC Discussion's Admin screen with admin user like weblogic, and wont be able to post any new message as well. Initially I thought that's its an installation problem, but there is a solution available for this issue. The following forum post by George Maggessy explains step by step how to fix this:

http://george.maggessy.com/2009/08/oracle-discussions-configuration.html

It helped me so sharing/posting it( to recall later if required ).


Monday, January 2, 2012

Sending E-Mail From an ADF Application Deployed on WebLogic Server.

In this post, I list the steps to send e-mails (using SMTP) from an ADF application deployed on Weblogic server. I have used JavaMail API to send e-mail on the click of a button. The steps needed to send e-mail using Java Mail API are:

1. Download JavaMail API zip, and add the mail.jar as a library to your project.
2. Create a new Mail Session, and set its JNDI Name.
3. Set appropriate properties in your mail session.
4. Access the mail session using JNDI.
5. Fetch the properties set in the mail session in your code, like from-user, from-user-password etc to send the e-mail.

The following image shows the UI page used for this post to send e-mails:



After entering the To Email ID, Subject, and Mail Text, when the Send E-Mail button is clicked, the email is send from its action listener.

To create the above UI, I created a Custom Application:




And selected ADF Faces technology in the project features section of the wizard, as shown below:



Click Finish to create the Custom App with ADF Faces compatible project with default settings.
After that, add the mail.jar to the list of project libraries as shown below:



Now, create a new page, as shown below:



In the above figure, BasePageBean is the managed bean that contains the code for the "Send E-Mail" button's action listener.
Here is the code of BasePageBean class:

package project1;

import com.sun.mail.smtp.SMTPTransport;

import java.util.Date;
import java.util.Properties;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;
import javax.faces.event.ActionEvent;

import javax.mail.Message;
import javax.mail.Session;

import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;

import oracle.adf.view.rich.component.rich.input.RichInputText;

import javax.naming.InitialContext;

public class BasePageBean {
    private RichInputText emailIdInputText;
    private RichInputText emailSubjectInputText;
    private RichInputText mailText;
   
    String emailID = null;
    String emailSubject = null;
    String emailText = null;

    public BasePageBean() {
    }

    public void setEmailIdInputText(RichInputText emailIdInputText) {
        this.emailIdInputText = emailIdInputText;
    }

    public RichInputText getEmailIdInputText() {
        return emailIdInputText;
    }

    public void setEmailSubjectInputText(RichInputText emailSubjectInputText) {
        this.emailSubjectInputText = emailSubjectInputText;
    }

    public RichInputText getEmailSubjectInputText() {
        return emailSubjectInputText;
    }

    public void sendMail(ActionEvent actionEvent) throws Exception{
        // Add event code here...
        String emailID = getEmailID();
        String subject = getEmailSubject();
        String text = getEmailText();
       
        InitialContext ic = new InitialContext();
        Session session = (Session) ic.lookup("mail/NewMailSession");

        Properties props = session.getProperties();
        System.out.println("LKAPOOR: PROPERTIES LIST: ");
        props.list(System.out);

        String  to = emailID;
        if(to==null || to.equals(""))
            to = "lalitkapoor.mailid@gmail.com";
       
        if(subject==null || subject.equals(""))
            subject = "Test Mail";

        String mailhost = props.getProperty("mail.smtp.host");
        System.out.println("LKAPOOR:: mailhost = " + mailhost);
        String user = props.getProperty("mail.smtp.user");
        System.out.println("LKAPOOR:: user = " + user);
        String password = props.getProperty("mail.smtp.password");
        System.out.println("LKAPOOR:: password = " + password);
        String protocol = props.getProperty("mail.transport.protocol");
        System.out.println("LKAPOOR:: protocol = " + protocol);
       
        String authorization = props.getProperty("mail.smtps.auth");
        String mailDisabled = props.getProperty("mail.disable");
        String verboseProp = props.getProperty("mail.verbose");
        String debugProp = props.getProperty("mail.debug");
       
        boolean sentDisabled = false;
        if(mailDisabled.equals("true"))
            sentDisabled = true;
       
        if(!sentDisabled){
           
            boolean auth = false;
            if(authorization.equals("true"))
                auth = true;
           
            boolean verbose = false;
            if(verboseProp.equals("true"))
                verbose = true;
   
            String mailer = "smtpsend";
           
            if(debugProp.equals("true"))
                session.setDebug(true);
            else
                session.setDebug(false);
   
            System.out.println("LKAPOOR: session initialized.");
                 
            Message msg = new MimeMessage(session);
           
            msg.setFrom();      
           
            msg.setRecipients(Message.RecipientType.TO, InternetAddress.parse(to, false));
           
            System.out.println("LKAPOOR: recipient set.");
           
            msg.setSubject(subject);
            msg.setText(text);
   
            msg.setHeader("X-Mailer", mailer);
            msg.setSentDate(new Date());
   
            System.out.println("LKAPOOR: Metadata set.");
   
            SMTPTransport t = (SMTPTransport)session.getTransport(protocol);
   
            System.out.println("LKAPOOR: Gettting Transport.");
   
            try {
                System.out.println("LKAPOOR: Before connect via authorization.");
                t.connect(mailhost, user, password);
                t.sendMessage(msg, msg.getAllRecipients());
                System.out.println("LKAPOOR: message sent.");
            } finally {
                    if (verbose)
                        System.out.println("Response: " + t.getLastServerResponse());
                    t.close();
            }
           
            System.out.println("\nMail was sent successfully.");
        }else{
            System.out.println("Mail Sending is disabled.");
        }
       
    }

    public void setMailText(RichInputText mailText) {
        this.mailText = mailText;
    }

    public RichInputText getMailText() {
        return mailText;
    }

    public void setEmailID(String emailID) {
        this.emailID = emailID;
    }

    public String getEmailID() {
        return emailID;
    }

    public void setEmailSubject(String emailSubject) {
        this.emailSubject = emailSubject;
    }

    public String getEmailSubject() {
        return emailSubject;
    }

    public void setEmailText(String emailText) {
        this.emailText = emailText;
    }

    public String getEmailText() {
        return emailText;
    }
}

The sendMail() action listener accesses the Mail Session using the JNDI URL "mail/NewMailSession".
To access this session, we need to configure a Mail Session on our WebLogic server.
To configure the Mail Session, open the WLS Home page, and click the Mail Session link:



The "Summary of Mail Sessions" page comes up. Click New to create a new Mail Session, and enter the following details:



As shown above, the action listener using the same string as JNDI URL which is entered as JNDI Name for the mail session.
Using this Mail Session, we can access the properties entered in the JavaMail Properties section in our action listener. I have entered the following properties to send an email using SMTP protocol:

mail.smtp.auth=true
mail.debug=true
mail.smtp.host=smtp.gmail.com
mail.smtp.user=lalitkapoor.mailid@gmail.com
mail.smtp.password=XXXXXXX ;)
mail.transport.protocol=smtp
mail.smtp.port=465
mail.disable=false
mail.verbose=true
mail.smtp.socketFactory.class=javax.net.ssl.SSLSocketFactory

So, basically, I am sending an email to myself in this example.
Click Save to deploy the changes, and run the application.
After entering the details, when you click the 'Send E-Mail' button, the e-mail is send, and here is how that mail looks like:



That's it.
When the provider doesn't support SSL for SMTP connections, like in my case, then we will hit the following error:

DEBUG SMTP: exception reading response: javax.net.ssl.SSLException: Unrecognized SSL message, plaintext connection?
Exception in thread "main" javax.mail.MessagingException: Exception reading response;   
nested exception is:
  javax.net.ssl.SSLException: Unrecognized SSL message, plaintext connection?

To avoid this, remove "mail.smtp.socketFactory.class=javax.net.ssl.SSLSocketFactory" property from mail session and it will work fine.

Reference:
http://www.coderanch.com/t/443237/java/java/JavaMail-security