Showing posts with label ADF. Show all posts
Showing posts with label ADF. Show all posts

Wednesday, April 11, 2018

Java SOAP WebService Proxy: SOAPHandler to print SOAP messages

Problem: We all know about creating webservice proxy from jdeveloper. Proxy generates few classes based on WSDL and associated XSD and allow us to invoke webservice as if we are simply calling some java method. Complexity of creating soap message and sending over network and receiving output is hidden from us. One of the problem we face many a time is to view SOAP request and response messages. If we can it would be very helpful for debugging.
In this blog I will be using SOAP handler to view SOAP messages.

Solution: Solution which I am going to show is using SOAP handler feature of web-service proxy.

Using proxy is very simple, we just work with normal java classes and complexity of invoking webservice is completely hidden. SOAP Handler is java class (with some guidelines) that can be injected with proxy and proxy will make sure to call methods of this class before sending a request and after receiving message.

SOAP Handler class must implement SOAPHandler<SOAPMessageContext> class.
A Sample SOAPHandler class can look like

import java.util.Collections;
import java.util.Set;

import javax.xml.namespace.QName;
import javax.xml.soap.SOAPException;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;

public class CalculatorSOAPHandler implements SOAPHandler<SOAPMessageContext> {
    public Set<QName> getHeaders() {
        return Collections.emptySet();
    }

    public boolean handleMessage(SOAPMessageContext messageContext) {
                Boolean outboundProperty = (Boolean)
            messageContext.get (MessageContext.MESSAGE_OUTBOUND_PROPERTY);

        if (outboundProperty.booleanValue()) {
            System.out.println("Request message:");
        } else {
            System.out.println("Response message:");
        }
        
        try {
            System.out.println(messageContext.getMessage().getSOAPPart().getEnvelope().toString());
        } catch (SOAPException e) {
            e.printStackTrace();
        }


        return true;
    }

    public boolean handleFault(SOAPMessageContext messageContext) {
        return true;
    }

    public void close(MessageContext messageContext) {
    }
}


Now we need to inject this class in proxy or in other words we need to configure it with proxy so that it knows about this class. There are couple of ways to do that.
1. Specify SOAP handler while creating proxy in Proxy creating wizard.

In above diagram we are specifying a SOAP handler. We have not selected any port so handler will be registered against all web-service port. Jdev will not create any class. You must create class separately.

When we generate proxy, there is one most important class which has @WebServiceClient annotation. Above step will create a handler entry in that class as

@HandlerChain(file = "MyServiceProxy-HandlerChain.xml")

It will also create a HandlerChain.xml file, which will have entry of handler.
<ns0:handler-chains xmlns:ns0="http://java.sun.com/xml/ns/javaee">
    <ns0:handler-chain>
        <ns0:service-name-pattern xmlns:ns1="http://xmlns.oracle.com/SIH/SIHParentProcess/BPELParentProcess">ns1:bpelparentprocess_client_ep</ns0:service-name-pattern>
        <ns0:handler>
            <ns0:handler-name>CalculatorSOAPHandler</ns0:handler-name>
            <ns0:handler-class>com.san.wsproxy.client.CalculatorSOAPHandler</ns0:handler-class>
        </ns0:handler>
    </ns0:handler-chain>
</ns0:handler-chains>



Now when you run service you will see that handleMessage method of SOAP handler is getting called and its printing SOAP header and body.

Another way to associate SOAP handler class with proxy is simply modify client code.
2. With proxy generation you get a client class. It has a main method, which shows how to run a webservice. You can use this class to add security (WS-Security) header in soap message by setting OWSM policy. Similarly you can use this class also to associate handler with proxy.

Your client method would look something like

    public static void main(String[] args) {
        Calculator calculator = new Calculator();
        CalculatorSoap calculatorSoap = calculator.getCalculatorSoap();
        // Add your code to call the desired methods.
       
        Binding binding = ((BindingProvider)calculatorSoap).getBinding();
        List handlerList = binding.getHandlerChain();
        handlerList.add(new CalculatorSOAPHandler());
        binding.setHandlerChain(handlerList);
             
             
        System.out.println(calculatorSoap.add(5, 6));
       
    }

Thats all, Now if you run service, you will see that SOAP messages are getting printed on console.



Below is the print of messages
Calling handleMessage
<env:Header xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"/>

<S:Body xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
  <Add xmlns="http://tempuri.org/">
    <intA>5</intA>
    <intB>6</intB>
  </Add>
</S:Body>

Calling handleMessage
<env:Header xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"/>

<soap:Body xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <AddResponse xmlns="http://tempuri.org/">
    <AddResult>11</AddResult>
  </AddResponse>
</soap:Body>


Instead system.out.println, you can add ADF logger also to print messages. In that case your messages would appear in weblogic log as well.

Monday, July 31, 2017

Making ADF page Readonly

Problem description: Sometime we found that we want to make ADF page readonly based on certain condition. One option is apply EL expression on all input fields of ADF page. For example you can write EL as readonly="#{pageFlowScope.pPageMode=='View'}. By this expression we are saying make input field read only if page mode is view. If there are multiple input fields, we need to apply it on all those fields. I would say if number of fields are less, its manageable but at times fields are more and it becomes very difficult to maintain this condition. In future also some developer may add new field but then forget to add this condition. So at times it becomes error pron and we need a centralized solution to make a page readonly. In this blog I am showing a generic method, which makes page input/action items disabled/readonly.

Solution

1. Have a bean for page and write below method

private void makeComponentHierarchyReadOnly(UIComponent component) {

        try {
            Method method1 =
                component.getClass().getMethod("setDisabled", boolean.class);
            if (method1 != null) {
                method1.invoke(component, true);
            }
         

        } catch (NoSuchMethodException e) {
            try {
                Method method =
                    component.getClass().getMethod("setReadOnly", boolean.class);
                if (method != null) {
                    method.invoke(component, true);
                }
            } catch (Exception e1) {
                // e.printStackTrace();//silently eat this exception.
            }


        } catch (Exception e) {
            // e.printStackTrace();//silently eat this exception.
        }
        List<UIComponent> childComponents = component.getChildren();
        for (UIComponent comp : childComponents) {
            makeComponentHierarchyReadOnly(comp);
        }

    }

Above method is taking UI component as input. It iterates over each child upto n level and try to disable them or if disable property is not there then it tries to make it readonly.

NOTE: Disable will also disable links/button and all action items as well.

2. You can call above code at appropriate time: Mostly we want to call above code at page load but at times you may have View/Edit button and on click of that you want to call it. If you want to call it at page load you can take approach 3 as described in blog http://sanjeev-technology.blogspot.in/2017/02/adf-execute-code-before-page-load.html

If you want to call on button click then you can simply have actionListener and call above method from there. For example
public void makePageReadOnly(ActionEvent actionEvent) {
        // Add event code here...
        makeComponentHierarchyReadOnly(this.getMyPageRootComponent());

    }

getMyPageRootComponent is a getter for a UI component under which you want everything to be readonly. To get this component, you just need to bind it with bean.

Thats all. Thank you for your time.



Tuesday, April 25, 2017

ADF: Popup gets closed automatically

Problem description: I found at times people complaint that ADF popup gets closed automatically.
In this blog I am trying to figure out possible cause of popup getting closed.

Possible cause: There are following four scenarios when a popup gets closed.
          1. User selects default buttons or close icon of dialog
          2. User programmatically closes popup using hide function
          3. User hit enter/escape key
          4. Popup or its parent component gets refreshed.

In first two points popup getting closed is desired behavior.

In third case when you use default buttons of popup, it gets closed with enter/escape keys. If you don't want you can use custom buttons as suggested in blog https://blogs.oracle.com/jheadstart/entry/adf_faces_how_to_prevent_closi

Point 4 is most problamatic, when user is not doing anything to close popup but it gets closed automatically. This happens when as a ppr we refresh popup or any of its parent component.
Lets take a scenario:
You have an inputText on popup the moment you enter its value and tab out your popup is getting closed. This may be because you are refreshing popup or any of its parent component. When you do so popup gets closed automatically. Your popup or any parent component of it might have partialtrigger property pointing to inputText. Or you may have valuechangeListener on inputText and you programmatically using addPartialTarget trying to refresh a component, which is having popup as child component in hierarchy.

Most of the time this happens accidentally. You decide to refresh UI component on base page and forget that same UI component is having popup also as child component. As a good practice I can suggest that we should move all our popups in single panelGroupLayout and keep them somewhere close to root components. For example

PanelGroupLayout -container
    PanelGroupLayout - main  [Keep your main page content here]
    PanelGroupLayout - popus  [Keep all your popups together here]
        af:popup1
        af:popup2

Keeping popups out of main page ensure that you can freely refresh main page components while working on popups.
One exception to this rule I see is when you have inline popups. For example you have a table and every row is showing some information in popup (may be on hover) and you have added popup inside a column with contentDelivery=immediate to make sure that popup launches quick with making a server trip. In such cases you have to add popup inside and refer #{row.myAttribute} kind of expression's EL in those.


Thats all.
  

Wednesday, April 19, 2017

Change webservice endpoint at runtime

Problem Description: In ADF we can create a webservice proxy and if we see the main class which extends Service has references of wsdl like
Now if we have dev/uat/production environment and for each environment we need to change endpoint, it could be very bad to change url manually and then generate ear. We need a way to dynamically change url at runtime.


Solutions
What we can do is keep service class as it is. We can use Client class, which we use to create instance of Servce and while creating instance we can provide url

Let say my client class is HCMUserDetailService. I change its constructor to accept url from outside.

    public HCMUserDetailService(String wsdlLocation, String username, String password) {
        super();
        userDetailsService_Service =  new UserDetailsService_Service(wsdlLocation, new QName("http://xmlns.oracle.com/apps/hcm/people/roles/userDetailsServiceV2/",
                    "UserDetailsService"));
        SecurityPoliciesFeature securityFeatures =     new SecurityPoliciesFeature(new String[] { "oracle/wss_username_token_over_ssl_client_policy" });
        userDetailService =      userDetailsService_Service.getUserDetailsServiceSoapHttpPort(securityFeatures);
        Map<String, Object> reqContext =  ((BindingProvider)userDetailService).getRequestContext();
        reqContext.put(BindingProvider.USERNAME_PROPERTY, username);
        reqContext.put(BindingProvider.PASSWORD_PROPERTY, password);
        objectFactory = new ObjectFactory();
             
       
    }



In above code constructor accepts a wsdl url as input and use that to create service instance using UserDetailsService_Service(wsdlLocation,new QName....


Now anybody needs to invoke service using this proxy we can pass wsdl location as input.

User of this client can read wsdl from
1. web.xml context parameter and then use deployment plan to change value of web.xml
2. from database table.

That way wsdl url is not hardcoded and it will get changed dyanamically.

Thats all.

Friday, November 11, 2016

ADF: Token replacement for resource bundle af:formatNamed

Problem Description: There is a common usecase to have a translatable string in resource bundle with tokens. In this blog I want to show how to use such strings using af:formatNamed apis

Let say we have a string in bundle as

WELCOME_MESSAGE=Hello {USER_NAME}. Welcome to Benefits Application.

We want to replace username token at run-time.

Solution: You can simply use af:formatNamed apis to do so

<af:outputText value="#{af:formatNamed(viewcontrollerBundle.WELCOME_MESSAGE,'USER_NAME','Sanjeev')}" id="ot1"/>

In above you make 'Sanjeev' to an expression like securityContext.userName to show actual username.
<af:outputText value="#{af:formatNamed(viewcontrollerBundle.WELCOME_MESSAGE,'USER_NAME',securityContext.userName)}" id="ot1"/>



If you have two tokens you can use af:formatNamed2, It can replace two tokens
WELCOME_MESSAGE=Hello {USER_NAME}. Welcome to {APPLICATION_NAME}.

<af:outputText value="#{af:formatNamed2(viewcontrollerBundle.WELCOME_MESSAGE,'USER_NAME','Sanjeev', APPLICATION_NAME, 'Benefits Application')}" id="ot1"/>

Similarly for 3 tokens, use af:formatNamed3 and 4 tokens use af:formatNamed4 apis

Token name in bundle must be surrounded by curly braces as {USER_NAME}


What if I don't use token but simply break strings in multiple parts. For example instead of
WELCOME_MESSAGE=Hello {USER_NAME}. Welcome to Benefits Application.
I create a bundle as
WELCOME_MESSAGE_PART1=Hello
WELCOME_MESSAGE_PART2=Welcome to Benefits Application.

Now in expression I write it like
<af:outputText value="#{viewcontrollerBundle.WELCOME_MESSAGE_PART1} Sanjeev. #{viewcontrollerBundle.WELCOME_MESSAGE_PART2}"
                         id="ot2"/>

If you run application you may not find any difference but I suggest NOT to do that. This approach has some hidden problems

a. Your translator will not be able to understand whole message. Breaking message in parts can be very difficult to translate and may end up having a completely different meaning.
b. Also I have my doubts that it may not work properly with RTL language. Need to verify this point.






Thursday, October 20, 2016

ADF: Know if VO attribute is currently changed

Problem description: We have isAttributeChanged(String) method available in VORowImpl, which can tell if a particular attribute in vo row has changed or not. Problem with this method is if we change attribute once and then restore its value back, method still returns that attribute has changed. I would not say this is bug as this might be requirement at times to know if attribute is ever changed. but at times we are interested in knowing if attribute's current value is different from the database value.
In this blog I want to come up with another method isAttributeCurrentlyChanged(String), which can compare database value with current value and let us know if attribute is actually changed at this time. If value is restored, we should get false as there is no effective change.

Solution: Idea is to use getPostedAttribute method of entity to get database value and compare it with attribute's current value to know if its actually changed. We also want to add this method to be available on each vo row so we would be writing our own base classes and introduce method in those.

1. Create an Entity Impl (say SanEntityImpl) base class and introduce a method getDataBaseValue as

import oracle.jbo.server.EntityImpl;
public class SanEntityImpl extends EntityImpl {

    public Object getDataBaseValue(String attributeName){
        return getPostedAttribute(this.getAttributeIndexOf(attributeName));
    }
}

2. Create a View Row Impl (say SanViewRowImpl) base class as 

import oracle.jbo.server.AttributeDefImpl;
import oracle.jbo.server.ViewAttributeDefImpl;
import oracle.jbo.server.ViewRowImpl;

public class SanViewRowImpl extends ViewRowImpl {
  
    
    public boolean isEOBasedAttribute(String voAttributeName){
        ViewAttributeDefImpl voAttrDef = (ViewAttributeDefImpl)this.getViewDef().findAttributeDef(voAttributeName);
        byte attributeKind = voAttrDef.getAttributeKind();
        if(AttributeDefImpl.ATTR_PERSISTENT == attributeKind){
            return true;
        }
        else{
            return false;
        }
        
    }
    
    public boolean isTransientAttribute(String voAttributeName){
        ViewAttributeDefImpl voAttrDef = (ViewAttributeDefImpl)this.getViewDef().findAttributeDef(voAttributeName);
        byte attributeKind = voAttrDef.getAttributeKind();
        if(AttributeDefImpl.ATTR_TRANSIENT == attributeKind){
            return true;
        }
        else{
            return false;
        }
    }
    
    public Object getDatabaseValue(String voAttributeName){
        if(this.isEOBasedAttribute(voAttributeName)){
            ViewAttributeDefImpl voAttrDef = (ViewAttributeDefImpl)this.getViewDef().findAttributeDef(voAttributeName);
            AttributeDefImpl eoAttrDef = voAttrDef.getEntityAttributeDef();//voAttrDef.getReferenceAttribute()
            String entityAttributeName = eoAttrDef.getName();
            return ((SanEntityImpl)this.getEntity(this.getViewDef().getEntityIndex(voAttrDef.getEntityReference()))).getDataBaseValue(entityAttributeName);
            
        }
        else if(this.isTransientAttribute(voAttributeName)){
            return null;//Better if we can return initial value of attribute
        }
        else{
            return this.getAttribute(voAttributeName); 
        }
        
        
    }
    
    public boolean isAttributeCurrentlyChanged(String voAttributeName){
        Object dbValue = this.getDatabaseValue(voAttributeName);
        Object ctValue = this.getAttribute(voAttributeName);
        if(dbValue == null && ctValue == null){
            return false;
        }
        else if(dbValue == null && ctValue != null){
            return true;
        }
        else if(dbValue != null && ctValue == null){
            return true;
        }
        else if(dbValue.equals(ctValue)){
                return false;
        }
        else{
            return true;
        }
    }
   
}


3. Configure these classes as base classes in project properties



4. Test behavior of isAttributeChanged and isAttributeCurrentlyChanged

    Create EmployeeEO, EmployeeVO based on HR schema and add it in AM. Generate AMImpl and have below method in AM to verify


    public static void main(String[] args){
        String amDef = "com.san.adf.model.am.HRAppModule"; //Replace it with your AM
        String config = "HRAppModuleLocal";  //Replace it with your AM configuration
        HRAppModuleImpl am = (HRAppModuleImpl) Configuration.createRootApplicationModule(amDef, config);
        
        ViewObjectImpl empVO = am.getEmployeeVO();
        empVO.executeQuery();        
        SanViewRowImpl empRow = (SanViewRowImpl)empVO.first();
        
        
        String attrValue = null;
        String attributeName = "FirstName";
        if(empRow != null){
            
            
            attrValue = (String)empRow.getAttribute(attributeName);
            
            //When there is no change
            System.out.println("Without any change");
            System.out.println("isAttributeChanged: " + empRow.isAttributeChanged(attributeName));
            System.out.println("isAttributeCurrentlyChanged: " + empRow.isAttributeCurrentlyChanged(attributeName));
            
            //Change value of attribute and verify
            System.out.println("Changing attribute value to Sanjeev");
            empRow.setAttribute(attributeName, "Sanjeev");            
            System.out.println("isAttributeChanged: " + empRow.isAttributeChanged(attributeName));
            System.out.println("isAttributeCurrentlyChanged: " + empRow.isAttributeCurrentlyChanged(attributeName));
            
            
            //Restore value of attribute and verify
            System.out.println("Restoring attribute value");
            empRow.setAttribute(attributeName, attrValue);            
            System.out.println("isAttributeChanged: " + empRow.isAttributeChanged(attributeName));
            System.out.println("isAttributeCurrentlyChanged: " + empRow.isAttributeCurrentlyChanged(attributeName));
        }
        
        
        Configuration.releaseRootApplicationModule(am, true);
    }


If you run the AMImpl, output is something like this.

Without any change
isAttributeChanged: false
isAttributeCurrentlyChanged: false

Changing attribute value to Sanjeev
isAttributeChanged: true
isAttributeCurrentlyChanged: true

Restoring attribute value
isAttributeChanged: true
isAttributeCurrentlyChanged: false

We can see that after restoring value isAttributeCurrentlyChanged returns false, which means no change. 

Other utility method added in base class VORowImpl are 
isEOBasedAttribute: This method returns true if attribute is database driven.
isTransientAttribute: This method returns true if attribute is transient
getDatabaseValue: This attribute returns value of attribute from database.

Thats all.

Saturday, June 4, 2016

ADF: Colorful Panel Accordion (ShowDetail Item headers)

Problem Description: In ADF we can mostly change styles of all components. Recently in one of forum post it was asked to do styling of ADF panel accordion to have different colors of header. Like below



Problem here is if I style panel-accordion provides a way to style its header. But that style will be applied to all headers. So I can have yellow headers but all will become yellow. How can we have different headers with different colors.

Solution: Lets go from simple to complex

What if we just need to make all headers yellow

<af:panelAccordion id="pa1" >
          <af:showDetailItem text="showDetailItem 1" id="sdi1"/>
          <af:showDetailItem text="showDetailItem 2" id="sdi2"/>
          <af:showDetailItem text="showDetailItem 3" id="sdi3"/>
          <af:showDetailItem text="showDetailItem 4" id="sdi4"/>
  </af:panelAccordion>

af|panelAccordion::header{
  background: yellow none no-repeat fixed center !important; 
}

af|panelAccordion::header-subsequent{
  background: yellow none no-repeat fixed center !important; 
}

af|panelAccordion::header-disclose {
  background: yellow none no-repeat fixed center !important; 
}


Above settings will impact all panel accordions. What if we want it for only specific panel accordion
We can add a class with panelAccordion and refer that in css

<af:panelAccordion id="pa1" styleClass="colorfulAccordian">
          <af:showDetailItem text="showDetailItem 1" id="sdi1"/>
          <af:showDetailItem text="showDetailItem 2" id="sdi2"/>
          <af:showDetailItem text="showDetailItem 3" id="sdi3"/>
          <af:showDetailItem text="showDetailItem 4" id="sdi4"/>
        </af:panelAccordion>

.colorfulAccordian af|panelAccordion::header{
  background: yellow none no-repeat fixed center !important; 
}

.colorfulAccordian af|panelAccordion::header-subsequent{
  background: yellow none no-repeat fixed center !important; 
}

.colorfulAccordian af|panelAccordion::header-disclose {
  background: yellow none no-repeat fixed center !important; 
}


Till here everything is great and Oracle provides selectors to style accordion headers. Now what if we want to style each accordion header differently.

There is no direct way to apply different different styles for each panel accordion headers. They are governed by one style only af|panelAccordion::header (or header-susequent/header-disclose). Any change in this style will affect all headers equally.

To style individual headers we need to use css styling in combination of adf selectors. We need to use nth child selector of css to select one particular header (h1) tag.


.colorfulAccordian af|panelAccordion::header{
  background: #000000 none no-repeat fixed center !important;
}


.colorfulAccordian af|panelAccordion::header-subsequent{
  background: #000000 none no-repeat fixed center !important;
}

.colorfulAccordian.af|panelAccordion h1:nth-child(2) {
  background-color: yellow !important;
}

.colorfulAccordian.af|panelAccordion h1:nth-child(4), af|panelAccordion h1:nth-child(5){
  background-color: red !important;

}
.colorfulAccordian.af|panelAccordion h1:nth-child(6), af|panelAccordion h1:nth-child(7){
  background-color: green !important;

}

.colorfulAccordian.af|panelAccordion h1:nth-child(8), af|panelAccordion h1:nth-child(9){
  background-color: blue !important;


}

Above styles will give you required output. 
Similarly if you want to style only one tab heading of a panelTab



<af:panelTabbed id="pt1" styleClass="colorfulTab">
          <af:showDetailItem text="showDetailItem 5" id="sdi5"/>
          <af:showDetailItem text="showDetailItem 6" id="sdi6"/>
        </af:panelTabbed>

and you want to style only first tab


.colorfulTab.af|panelTabbed af|panelTabbed::viewable-tabs > div:nth-child(1) af|panelTabbed::tab-text-link {
  color: yellow !important;
}


To style only second tab

.colorfulTab.af|panelTabbed af|panelTabbed::viewable-tabs > div:nth-child(2) af|panelTabbed::tab-text-link {
  color: yellow !important;
}


We have to use nth-child selector of css because Oracle by default style all panelAccordion headers (or panelTab's tab) with one single style. There is no way to provide different styles for different headers. Ideally I would have loved to have an attribute in panelAccordion like headerStyle, which I can set as comma separated classnames. Then framework could have taken care to apply those class on individual headers. I just need to provide styles on those classes.


Disclaimer: This solution is not upgrade safe as there is no guarantee that header tags (h1) will be placed same way in html hierarchy as it is today. If ADF framework changes h1 tag in html then whole implementation will fail and you may need to write a new class.








Tuesday, February 9, 2016

ADF 12c: Debugging Groovy

Oracle has provided a new feature in 12.1.3 to debug a groovy code. Feature is very much required and very simple to use.

Ideally you just need to select Groovy Line at design time and then run debugger. Jdeveloper will stop while running that line of groovy and you will be able to check values of local variables in Jdeveloper data window.

One thing that you need to notice is Dont use source mode to select groovy expression. Use Declarative mode (overview tab). In below diagram I am showing how EO validation is selected.

As a result when you run debugger you will see application stops when groovy is getting executed and you can go line by line from there, while viewing local variables in data window of debugger.


Even you get handle of this variable, which can be used to view other values.


Simple but very very useful feature in ADF. So no more println(varname)

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.