Tuesday, November 3, 2015

Oracle Cloud JCS-SX: Sending Email

Recently I was trying to deploy an ADF application on Oracle PaaS (JCS-SX) environemnt. I wanted to send email using our smtp server. I wrote java mail apis but it failed with AccessControlException as java mail apis are blocked in JCSSX. But then there is another way that works  -->  ''APEX_MAIL" apis

With Oracle JCS environment we get ODC (Oracle Database Cloud) as well. We can use it to send notification. We can use APEX_MAIL package on this database to send email. Good thing it does not need any smtp server either.

As described in http://sanjeev-technology.blogspot.in/2015/04/cloud-sending-email-from-oracle-paas.html, we can write below code to send email using apex_mail


declare
    l_to  varchar2(200)  := 'fromemail@email.com';  -- change to your email address;
    l_from varchar2(200) :=  'toemail@email.com'; -- change to a real senders email address
    l_body      varchar2(4000) := 'To view the content of this message, please use an HTML enabled mail client.' ;
    l_body_html varchar2(4000)  := '<html><body><p> This is test email </p> </body></html>';
    l_subject varchar2(200);
begin
    APEX_MAIL.SEND (
        p_to        => l_to,
        p_from      => l_from,
        p_body      => l_body,
        p_body_html => l_body_html,
        p_subj      => l_subject );

    APEX_MAIL.PUSH_QUEUE;

end;


If we call above api from Java, it will not work. The limitation is that we must be in APEX context to use above apis. To overcome that we need to set security-group-id first. To set security-group-id, you must know your workspace. To know workspace, you can run below sql in SQL Workshop.

select apex_util.find_workspace((select apex_application.get_security_group_id from dual)) from dual;


Once you have workspace name, you can change above code so that it can work from outside APEX context. In this blog I am going to create a REST service to send email and they I am going to invoke that service from Chrome - Advanced Rest Client Application.

Here are the steps to do it.

1. Open ODC (Oracle Database Cloud) console and navigate to SQL Workshop. Select REST Services.

2. Click on Create button to create a new REST service
While creating REST service in ODC you need to define three things
a. Module: Define module as shown below

b. Template: Define template as shown below

c. Resource Handler: Define Resource Handler as shown below

With that your REST service is ready. 

Now lets call it from outside APEX context. As it is REST service, I will be using 'Advanced REST client' of chrome browser. You can download this plugin and install in browser.


Below are screenshots for SUCCESS and ERROR

As I can invoke it from Advanced REST client I should be invoke this service from any third party including java application. We can deploy such application in Oracle PaaS JCS-SX and send email.


Last thing I would like to do is call this REST service from ADF application and deploy ADF application on JCS-SX environment. I would not go in details about how to deploy and create ADF application for cloud. 

1. I have a jspx page, which looks like
<?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:panelGroupLayout id="pgl1" layout="vertical">
          <af:panelFormLayout id="pfl1">
            <af:inputText label="To" id="it1"
                          value="#{pageFlowScope.pMessageTo}"/>
            <af:inputText label="From" id="it2"
                          value="#{pageFlowScope.pMessageFrom}"/>
            <af:inputText label="Subject" id="it3"
                          value="#{pageFlowScope.pMessageSubject}"/>
            <af:inputText label="Body" id="rte1"
                               value="#{pageFlowScope.pMessageBody}" rows="5"
                          columns="40"/>
            <f:facet name="footer">
              <af:panelGroupLayout id="pgl2" halign="center"
                                   styleClass="AFStretchWidth">
                <af:commandButton text="Send" id="cb1"
                                  actionListener="#{backingBeanScope.MessageBean.sendMessage}"/>
              </af:panelGroupLayout>
            </f:facet>
          </af:panelFormLayout>
        </af:panelGroupLayout>
      </af:form>
    </af:document>
  </f:view>
</jsp:root>

2. Create a bean (MessageBean) and associate it with backingbean scope

3. Write below code in bean:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;

import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;

import javax.el.ELContext;
import javax.el.ExpressionFactory;
import javax.el.ValueExpression;

import javax.faces.context.FacesContext;
import javax.faces.event.ActionEvent;

public class MessageBean {
    public MessageBean() {
        super();
    }
    
    
    
    private String sendNotification(String to, String from, String textBody, String htmlBody, String subject){
        String output = null;
        try {

                
            URL url = new URL("https://<oracle_cloud_server>.com/apex/myservice/sendmail/");
            HttpURLConnection conn = (HttpURLConnection)url.openConnection();
            conn.setRequestMethod("POST");
            conn.setRequestProperty("Accept", "application/json");            
            conn.setRequestProperty( "User-Agent", "Mozilla/5.0" );
            conn.setRequestProperty("Content-Type", "application/json; charset=UTF-8");            
            conn.setDoOutput(true);
            conn.setDoInput(true);
            
            String payload =   "{\"TO\":\""+to+"\"," +    "\"FROM\":\""+from+"\"," +    "\"TEXT_BODY\":\""+textBody+"\", " +
                                "\"HTML_BODY\":\""+htmlBody+"\", " +     "\"SUBJECT\":\""+subject+"\"}";
            conn.setRequestProperty("Content-Length", String.valueOf(payload.getBytes().length));
            
            OutputStream writer = conn.getOutputStream();
            writer.write(payload.getBytes());
            writer.flush();
            writer .close();
            System.out.println(payload);
            if (conn.getResponseCode() != 200) {
                throw new RuntimeException("Failed : HTTP error code : " +
                                           conn.getResponseCode());
            }

            BufferedReader br =  new BufferedReader(new InputStreamReader((conn.getInputStream())));


            System.out.println("Output from Server .... \n");
            while ((output = br.readLine()) != null) {
                System.out.println(output);
            }           
            conn.disconnect();

        } catch (MalformedURLException e) {
            e.printStackTrace();

        } catch (IOException e) {
            e.printStackTrace();
        }

        return output;
    }

    public void sendMessage(ActionEvent actionEvent) {
        // Add event code here...
        String to = (String)evaluateEL("#{pageFlowScope.pMessageTo}");
        String from = (String)evaluateEL("#{pageFlowScope.pMessageFrom}");
        String subject = (String)evaluateEL("#{pageFlowScope.pMessageSubject}");
        String body = (String)evaluateEL("#{pageFlowScope.pMessageBody}");
        
        sendNotification(to, from, null, body, subject);
    }
            
    public static Object evaluateEL(String el) {
    FacesContext facesContext = FacesContext.getCurrentInstance();
    ELContext elContext = facesContext.getELContext();
    ExpressionFactory expressionFactory =
    facesContext.getApplication().getExpressionFactory();
    ValueExpression exp =
    expressionFactory.createValueExpression(elContext, el,
    Object.class);

    return exp.getValue(elContext);
    }

}

Deploy this application on cloud and see the output as below





NOTE: There are lots shortcomings in this ADF code. You can use RichTextEditor for email. In this code you can't have special characters as I am just appending your message and forming json object. You may want to create a java object with properties (to,from,subject,textBody,htmlBody) and then convert it to json string using a third part library (jackson etc). You also may want to move sendNotification to model side and expose it as datacontrol. You also may want to remove hardcoding of service url and request properties. Definitely you want to do better error handling. BUT then this is just to a Blog entry.

5 comments:

vikram said...

Hello Sanjeev,
Thanks For this post,Its very useful.I have implemented the same using the above techniques and is working in paas.But i am not able to use newline character or hyperlink in email body .Any help please

Dayanand Chowdhary said...
This comment has been removed by the author.
Kapil Gajre said...

Hi Sanjeev,

Thank you for the post!! This is really helpful.
Unfortunately i am getting below error when i try to consume this rest webservice from ADF jdeveloper.

Please help!

Thank you,
Kapil

Kapil Gajre said...

java.io.IOException: Connection closed, EOF detected
at weblogic.socket.JSSEFilterImpl.handleUnwrapResults(JSSEFilterImpl.java:637)
at weblogic.socket.JSSEFilterImpl.unwrapAndHandleResults(JSSEFilterImpl.java:515)
at weblogic.socket.JSSEFilterImpl.doHandshake(JSSEFilterImpl.java:96)
at weblogic.socket.JSSEFilterImpl.doHandshake(JSSEFilterImpl.java:75)
at weblogic.socket.JSSESocket.startHandshake(JSSESocket.java:219)
at weblogic.net.http.HttpsClient.New(HttpsClient.java:563)
at weblogic.net.http.HttpsClient.New(HttpsClient.java:534)

Kunal Oza said...

can we send attachment in email with APEX API ??