Monday, August 03, 2015

ADF 12.1.3 : Implementing Default Table Filter Values

In one of my projects I ran into a requirement where the end user needs to be presented with default values in the table filters. This sounds like it is a common requirement, which is easy to implement. However it proved to be not so common, as it is not in the documentation nor are there any Blogpost to be found that talk about this feature. In this blogpost I describe how to implement this.

The Use Case Explained
Users of the application would typically enter today's date in a table filter in order to get all data that is valid for today. They do this each and every time. In order to facilitate them I want to have the table filter pre-filled with today's date (at the moment of writing July 31st 2015).


So whenever the page is displayed, it should display 'today' in the table filter and execute the query accordingly. The problem is to get the value in the filter without the user typing it. Lets first take a look at how the ADF Search and Filters are implemented by the framework.

Implementation of Default ADF Table Filter explained

When you drag and drop a collection from the Data control and drop it as a Table with filtering enabled, several things happen.

The table component will have a 'filterModel' attribute (line 9) and the 'filterVisible' property (line 11) is set to true. The columns in the table have the 'filterable' property set to true (line 13).

1:          <af:table value="#{bindings.AllEmployees.collectionModel}" var="row"  
2:                   rows="#{bindings.AllEmployees.rangeSize}"  
3:                   emptyText="#{bindings.AllEmployees.viewable ? 'No data to display.' : 'Access Denied.'}"  
4:                   rowBandingInterval="0"  
5:                   selectedRowKeys="#{bindings.AllEmployees.collectionModel.selectedRow}"  
6:                   selectionListener="#{bindings.AllEmployees.collectionModel.makeCurrent}"  
7:                   rowSelection="single"  
8:                   fetchSize="#{bindings.AllEmployees.rangeSize}"  
9:                   filterModel="#{bindings.AllEmployeesQuery.queryDescriptor}"  
10:                   queryListener="#{bindings.AllEmployeesQuery.processQuery}"  
11:                   filterVisible="true" varStatus="vs" id="t2">  
12:                <af:column sortProperty="#{bindings.AllEmployees.hints.EmployeeId.name}"  
13:                      filterable="true"  
14:                      headerText="#{bindings.AllEmployees.hints.EmployeeId.label}"  
15:                      id="c5">  
16:                  <af:inputText value="#{row.bindings.EmployeeId.inputValue}"  
17:                         ....etc  

Also in the pageDefinition behind this page a 'searchRegion' executable was created. This 'searchRegion' is used by the table component for its filterModel and queryListener.
1:    <iterator Binds="AllEmployees" RangeSize="25" DataControl="HrServiceDataControl" id="AllEmployeesIterator"/>  
2:    <searchRegion Binds="AllEmployeesIterator" Criteria=""  
3:           Customizer="oracle.jbo.uicli.binding.JUSearchBindingCustomizer" id="AllEmployeesQuery"/>  

Finally note that according to the documentation, 'In addition any column that wants to support filtering must have filterable="true" set along with the sortyProperty="...". The "sortProperty" attribute is the key for the filter field in the model.'
When I run the page, filtering works out of the box. There is nothing extra that I need to do. However if I want to have programatic access to the filter in order to set default values there is a lot more to do. As a matter of fact, I need to have access to the queryDescriptor.

The Implementation of the Default Filter Values
To get access to the queryDescriptor, I have to create a managed bean definition and a Java class with a method where I can lookup the 'searchRegion' binding and work with it. The managed bean is defined in the taskflow config file.
1:    <managed-bean>  
2:     <managed-bean-name>EmpTable</managed-bean-name>  
3:     <managed-bean-class>com.blogspot.lucbors.tablefilter.view.beans.LucsTableBean</managed-bean-class>  
4:     <managed-bean-scope>pageFlow</managed-bean-scope>  

The Java Class, called LucsTableBean, needs at least one method to implement the functionality. In this method first I need to get the queryDescriptor from the 'searchRegion' binding. I know the name of that binding so I can easily look it up. Next I can get the 'FilterableQueryDescriptor' directly from that search binding. The method FilterableQueryDescriptor.getFilterConjunctionCriterion() returns the map of parameters involved in searching.
Unfortunately it’s not enough to to use this method to get the FilterConjunctionCriterion. I have to iterate over the ConjunctionCriterion and work with them one by one. I have to check which type of ConjunctionCriterion I get from the iterator as well, as there are two different ones:

  • AttributeCriterion
  • ConjunctionCriterion

Only the AttributeCriterion can be set, the ConjunctionCriterion represents a group of AttributeCriterion. For this use case I need to change the value of the 'HireDate'. Therefor I check if the current attribute is 'HireDate' and if so, I get to the point where I can assign the default value. After making the necessary changes I return this customized query descriptor in order for the table to use it.
So finally the Java Method for deriving the QueryDescriptor ends up to be like below, where
1) in line 2,3,4 I find the search binding and the queryDescriptor.
2) in lines 5-9 I find the List of criterion (if any).
3) in line 11-12 I set the value of Today in the CriterionAttribute, if it is the HireDate attribute.
4) Because I only want to do this on initial display (it is a default search) I set an indicator (in line 16) and proces the query automatically (line 19) with the default search criteria.
1:    public FilterableQueryDescriptor getCustomQueryDescriptor() {  
2:      String bindingEl = "#{bindings.AllEmployeesQuery}";  
3:      FacesCtrlSearchBinding sbinding = (FacesCtrlSearchBinding) JSFUtils.resolveExpression(bindingEl);  
4:      FilterableQueryDescriptor fqd = (FilterableQueryDescriptor) sbinding.getQueryDescriptor();  
5:      if (fqd != null && fqd.getFilterConjunctionCriterion() != null && isInitialQuery()) {  
6:        ConjunctionCriterion cc = fqd.getFilterConjunctionCriterion();  
7:        List<Criterion> lc = cc.getCriterionList();  
8:        for (Criterion c : lc) {  
9:          if (c instanceof AttributeCriterion) {  
10:            AttributeCriterion ac = (AttributeCriterion) c;  
11:            if ((ac.getAttribute().getName().equalsIgnoreCase("HireDate")) && (ac.getValue() == null)) {  
12:              ac.setValue(getToday());  // we need date without time so lets call getToday()  
13:            }  
14:          }  
15:        }  
16:        setInitialQuery(false);  
17:        RichTable tbl = getTable();  
18:        QueryEvent queryEvent = new QueryEvent(tbl, fqd);  
19:        sbinding.processQuery(queryEvent);  
20:      }  
21:      return fqd;  
22:    }  
23:    public Date getToday(){  
24:      Date today = new Date();      
25:      // Get Calendar object set to the date and time of the given Date object  
26:      Calendar cal = Calendar.getInstance();  
27:      cal.setTime(today);  
28:      // Set time fields to zero  
29:      cal.set(Calendar.HOUR_OF_DAY, 0);  
30:      cal.set(Calendar.MINUTE, 0);  
31:      cal.set(Calendar.SECOND, 0);  
32:      cal.set(Calendar.MILLISECOND, 0);  
33:      // Put it back in the Date object  
34:      today = cal.getTime();  
35:      return today;  
36:    }  

NOTE: I created a utility method 'getToday' in order to get a date without a time. Otherwise, due to the time component, the query return no results.

For the table to use this FilterableQueryDescriptor, I need to change the value of the filterModel attribute of the table component from the default:
1:  filterModel="#{bindings.AllEmployeesQuery.queryDescriptor}"  
to my custom one:
1:  filterModel="#{pageFlowScope.EmpTable.customQueryDescriptor}"  

I also changed to binding attribute for the table so I can work with table programatically as I do in line 18 of the getCustomQueryDescriptor() method. This all works like a charm.

Making it Generic
It is nice that I can now set a default value for the HireDate, but both the Attribute Name and the Search Value are hardcoded. Besides that, this only works for this one single table. The solution here is to make the managed Bean class more generic and configure it dynamically, so it can work for any table and any default search criteria.
Lets take a look at what things I want to configure dynamically:
  • First and for all I want to use this on multiple tables, so the name of the searchBinding must be dynamic
  • Next I want to be able to use multiple different default filter criteria for each different table so I need to configure the defaultFilterCriteria dynamic
To make this work is I have to implement these as properties in my 'LucsTableBean' class so I can configure them in my bean definition. So I create these two properties and generate the getters and setters for it.
1:    // The map with filterCriteria. Injected as managed property.  
2:    private Map<String, Object> defaultFilterCriteria;  
3:    // The name of the querybinding. Injected as managed property.  
4:    private String queryBindingName;  

Note that the 'defaultFilterCriteria' is a Map with a 'String' as key and an 'Object' as value. In that way i can put virtually everything in it. The trick now is to give the Key the same value as the Name of the Attribute in AttributeCriteria. In this way I can get the filter value from my defaultFilterCriteria map by calling the get on the map with the Name of the Attribute. I know it reads a bit difficult, but here is the code.
Where I previously used:
1:  if ((ac.getAttribute().getName().equalsIgnoreCase("HireDate"))  
I can now get the filterValue directly by using the following:
1:  // check if a default filter exists for this attribute  
2:  Object filter = defaultFilterCriteria.get(ac.getAttribute().getName());  
It will give me the Value that I want to use as default.

In the config file I can now set the values of the managed properties for queryBindingName and defaultFilterCriteria.
The value for the queryBindingName is 'AllEmployeesQuery' and for the defaultFilterCriteria I enter 'HireDate' as key, and '31/7/2015' as value. That should do the trick.
1:    <managed-bean>  
2:     <managed-bean-name>EmpTable</managed-bean-name>  
3:     <managed-bean-class>com.blogspot.lucbors.tablefilter.view.beans.LucsTableBean</managed-bean-class>  
4:     <managed-bean-scope>pageFlow</managed-bean-scope>  
5:     <managed-property>  
6:      <property-name>queryBindingName</property-name>  
7:      <value>AllEmployeesQuery</value>  
8:     </managed-property>  
9:      <managed-property>  
10:      <property-name>defaultFilterCriteria</property-name>        
11:     <map-entries>  
12:       <map-entry>  
13:        <key>HireDate</key>  
14:        <value>31/7/2015</value>  
15:       </map-entry>  
16:      </map-entries>  
17:     </managed-property>  
18:    </managed-bean>  

Now I am ready to change the method to work with those managed properties. First of all I need to lookup the search binding, which previously was hardcoded. It is rather simple to do that. Just change the hardcoded value into the managed property's value, and now I can get the FilterableQueryDescriptor from any search binding.
1:    public FilterableQueryDescriptor getCustomQueryDescriptor() {  
2:      String bindingEl = "#{bindings." + queryBindingName + "}";  
3:      FacesCtrlSearchBinding sbinding = (FacesCtrlSearchBinding) JSFUtils.resolveExpression(bindingEl);  
4:      FilterableQueryDescriptor fqd = (FilterableQueryDescriptor) sbinding.getQueryDescriptor();  
Now I need to figure out a way to work with the filtercriteria. I know it is a map, and I know that I will only use it if it contains values. Lets continue to the part where I am looping the List of Criterion. For each and every AttributeCriterion I want to check if there i a filterValue in the map, and if so, I want to apply that Value. In case of a Date I have to do some tricks because I cannot just parse a String into a Date. All other values (as far as I have tested) work fine.

So what is in the code below?
1) After checking if it is an AttributeCriterion, I use the 'Name' of the attribute to lookup the filtervalue in the map with defaultFilterCriteria (Line 5).
2) If the filter has a value, I use a little trick to find out if I am working with a Date. (lines 7-9)
3) If it is a Date, I need to format and parse it, before I can set it as filterValue (line13)
4) If it is not a Date, I simply use the value as is (line 19).
1:          for (Criterion c : lc) {  
2:            if (c instanceof AttributeCriterion) {  
3:              AttributeCriterion ac = (AttributeCriterion) c;  
4:              // check if a default filter exists for this attribute  
5:              Object filter = defaultFilterCriteria.get(ac.getAttribute().getName());  
6:              if (filter != null) {  
7:                // OK, this might not be optimal, but I try here to get the default component type  
8:                // if it is an input date, we need to parse the String value to a Date  
9:                if (ac.getComponentType(null) == AttributeDescriptor.ComponentType.inputDate) {  
10:                  SimpleDateFormat formatter = new SimpleDateFormat("dd/MM/yyyy");  
11:                  try {  
12:                      Date date = formatter.parse(filter.toString());  
13:                      ac.setValue(date);  
14:                    }  
15:                  } catch (ParseException e) {  
16:                    e.printStackTrace();  
17:                  }  
18:                } else {  
19:                  ac.setValue(filter);  
20:                }  
21:              }  
22:            }  
23:          }  
Now all is ready to use any kind of default filter value, on multiple tables. All is based on a 'simple' class and can be configured in the managed bean definition.

Before you start asking 'what about tomorrow?'
I was expecting the question, so let me answer it before you can ask: 'What about tomorrow?'. The way I implemented the defaultFilterValue for HireDate works for 'today' but not for 'tomorrow':
1:      <managed-property>  
2:      <property-name>defaultFilterCriteria</property-name>  
3:      <map-entries>       
4:       <map-entry>  
5:        <key>HireDate</key>  
6:        <value>31/7/2015</value>  
7:       </map-entry>  
8:      </map-entries>  
9:     </managed-property>  
So how can configure the value in such a way that I get the current date into the filter? It proved to be pretty simple.
The map is defined as a 'String, Object' type map, so I can actually put whatever I want in the value. So let's say that I want to have the HireDate's filter value to be 'TODAY'. Why not define it in exactly that way:
1:      <managed-property>  
2:      <property-name>defaultFilterCriteria</property-name>  
3:      <map-entries>       
4:       <map-entry>  
5:        <key>HireDate</key>  
6:        <value>TODAY</value>  
7:       </map-entry>  
8:      </map-entries>  
9:     </managed-property>  
Now in my class, 'defaultFilterCriteria.get(ac.getAttribute().getName())' will return TODAY for the HireDate attribute.
In that case I simply set the value of the filter to 'TODAYs' date (line 4,5). I could even make it work for YESTERDAY, TOMORROW, LASTYEAR and so on....
1:                if (ac.getComponentType(null) == AttributeDescriptor.ComponentType.inputDate) {  
2:                  SimpleDateFormat formatter = new SimpleDateFormat("dd/MM/yyyy");  
3:                  try {  
4:                    if (filter.toString().equalsIgnoreCase("TODAY")) {  
5:                      ac.setValue(getToday());  
6:                    } else {  
7:                      Date date = formatter.parse(filter.toString());  
8:                      ac.setValue(date);  
9:                    }  
10:                  } catch (ParseException e) {  
11:                    e.printStackTrace();  
12:                  }  
13:                } else {  
14:                  ac.setValue(filter);  
15:                }  

A Final Example
With all logic in place I will now show you how to work with two tables, that use their own instance of the LucsTableBean, and have their own specific managed properties. No need to write any Java code, I simply define the properties for the two managed bean instances in the config file.
1:    <managed-bean>  
2:      <managed-bean-name>EmpTable</managed-bean-name>  
3:      <managed-bean-class>com.blogspot.lucbors.tablefilter.view.beans.LucsTableBean</managed-bean-class>  
4:      <managed-bean-scope>pageFlow</managed-bean-scope>  
5:      <managed-property>  
6:        <property-name>queryBindingName</property-name>  
7:        <value>AllEmployeesQuery</value>  
8:      </managed-property>  
9:      <managed-property>  
10:        <property-name>defaultFilterCriteria</property-name>  
11:        <map-entries>  
12:          <map-entry>  
13:            <key>HireDate</key>  
14:            <value>TODAY</value>  
15:          </map-entry>  
16:        </map-entries>  
17:      </managed-property>  
18:    </managed-bean>  
19:    <managed-bean>  
20:      <managed-bean-name>AdminTable</managed-bean-name>  
21:      <managed-bean-class>com.blogspot.lucbors.tablefilter.view.beans.LucsTableBean</managed-bean-class>  
22:      <managed-bean-scope>pageFlow</managed-bean-scope>  
23:      <managed-property>  
24:        <property-name>queryBindingName</property-name>  
25:        <value>EmployeesInAdministrationDepartmentQuery</value>  
26:      </managed-property>  
27:      <managed-property>  
28:        <property-name>defaultFilterCriteria</property-name>  
29:        <map-entries>  
30:          <map-entry>  
31:            <key>HireDate</key>  
32:            <value>7/6/1994</value>  
33:          </map-entry>  
34:          <map-entry>  
35:            <key>FirstName</key>  
36:            <value>S</value>  
37:          </map-entry>  
38:        </map-entries>  
39:      </managed-property>  
40:    </managed-bean>  

And of course make sure that the tables have the 'binding' attribute and the 'filterModel' set to the appropriate values. When running the page with the two tables, you will see that each has it's own default filter values, exactly as were defined.

Summary
Working with filters is very valuable for end users. In order to it even more user friendly, we can supply them with default filter values. This functionality unfortunately is not out of the box, but after reading this blogpost, you now have a way to implement it. I use a 'simple' Java class with only one method. The Class can be used as a managed bean and it's properties can be set at the bean configuration. This enables me to work with a map with filterValues which can be configured for each individual bean instance. The difficulty lies in the ADF implementation of the FilterableQueryDescriptor and its criteria.
NOTE:The implementation of FilterableQueryDescriptor has changed from ADF 11.x to ADF 12.1.x For instance getFilterCriteria() has been deprecated. I did not test the solution proposed in this blogpost in 11.x, but I think I can safely assume that this is only working in 12.1.x and not in 11.

Resources
The code for this blogpost can be found on github.

1) ADF Table Component Documentation
2) Filter reset (12.1.x)
3) Date Range Filter (12.1.x)

Thursday, July 30, 2015

IoT Hackathon Part III : Some enhancements to the sensor example

In my previous post I showed you how to setup a simple weather-station using Raspberry Pi, GroovyPi sensors and Python. It worked very well, but there is definitely room for improvement. In this short post I describe some of these improvements. First you will learn how to start the weather-station when you reboot the Pi. Next I will show you how to create some decent log info.

Autostart
The weather-station works like a charm, but if the Raspi is rebooted, you need to manually restart the python script. That is not the way I want this to work, I want the weather-station start on reboot. Actually this is very simple. The Raspberry Pi uses Linux as OS and thus we can use the crontab to schedule when to start tasks. With the @reboot directive, a task starts on reboot. The only thin we need to do is to add a line to the crontab that tells that we want to start our weather-station on each and every reboot. You can open te crontab for editing by issuing the following command:
 $ sudo crontab -e  
Next you simply add the following line of code to the crontab and you are good to go.
 @reboot python /home/pi/Desktop/Lucs_projects/weatherstation/weatherstation.py &  
The “&” at the end of the line means the command is run in the background and it won’t stop the system booting up. It is as simple as that.

Logging
In my initial setup I used a print statement to send the reading to the console.
 print "temp =", temp, "C\thumadity =", hum,"%"  
However, when the weather-station is started on reboot, instead of from the command prompt, there is no console and there is now way that we can see the data. For that reason I decided to add some logging to the Python script. For this we can use the logging library. This enables you to create a logger, create a loghandler to write info to file and console, and also to add some formatting to your logstatements. All of this is explained in the Python Documentation. So first import the logging library, next create a logger and some loghandlers, optionally add some formatting and your logging is ready to use.
 import logging  
 import datetime
  
 # lets create a logger 
 logger = logging.getLogger('weather.logger')  
 logger.setLevel('DEBUG')

 # create a log handler to log to file   
 file_log_handler = logging.FileHandler('/home/pi/Desktop/Lucs_projects/weatherstation/weather.log')  
 logger.addHandler(file_log_handler)  

 # create a log handler to log to the console
 stderr_log_handler = logging.StreamHandler()  
 logger.addHandler(stderr_log_handler)  

 #now add some formatting (note the import of datetime is required)
 formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')  
 file_log_handler.setFormatter(formatter)  
 stderr_log_handler.setFormatter(formatter)  

 #Now we can write use the logger  
 logger.info("temp ="+ t + "C\thumidity ="+ h + "%")   
When the weather-station is started on reboot, the log statements can be found in 'weather.log'. When you start from command-line, log statements are visible in both, the console and the 'weather.log' file.

Resources
1) Start at reboot
2) Python Logging

Tuesday, June 30, 2015

ODTUG KScope15: One week in a nutshell

Last week I visited ODTUG KScope15 in Hollywood Florida. In this post I will share my findings and hope that you can benefit from it.
My general impression of the conference is that it was to hot for me in Florida. At least to hot for a conference. If you can sit on the beach whole day long, it is a great place to be, but hey, I had to work.... From a content perspective, there was a whole lot of Mobile and Cloud, and just a tiny bit of ADF. We need to work on this for next year as per request of the attendees who really were asking for more ADF content.

Anyway, here is my week in a nutshell.
Saturday
Not much to do after an 18 hour trip. So when I arrived at the hotel the only thing to do was enjoy the view and get some Cuban food. What better place to do that then in Southern Florida. The food was good and I went there again during the week.


Sunday
The Sunday symposium was a very good one from an Oracle ADF Developer Perspective. The Sunday Symposium took a deep look at the impact of the cloud's platform on the role and capabilities of application developers. Oracle showed the latest and upcoming products for web and mobile application development and deployment in the cloud. There was also room for empowering business users with development capabilities through the cloud, with products such as MAX and ABCS (see later). There was a demo of MAX, aka Mobile App Accelerator. MAX is a part of Oracle Mobile Cloud Service in a demo. where a business user can actually create real mobile apps without coding.

The final session of the day was really worthwhile. In this session Brian Fry showed Oracle Application Builder Cloud (it is as easy as ABC) for the very first time publicly. Oracle ABCS enables you to drag-and-drop your way to enterprise-ready JavaScript and HTML web applications.

More details on the Sunday Symposium can be found here.

Monday
On Monday I attended an IoT hands-on lab. It used an APEX front end to show results, but for me the main content (IoT) was more important. I actually built my own thing and connected it to an Oracle APEX application. I used a circuit board, wires, capacitors, a wifi device, LEDs and a sensor. I wired them together into a real THING, connected to the internet and start communicating with an Oracle database. Because of the closed API for the REST Service they used I had to build an APEX application to control the thing and report on its data. Had it been an open API, I would have created an ADF or MAF app to do the exact same thing.

During Lunch I sat down at the MAF and ADF table discussing with my peers and also with Oracle Product Management. We also discussed the big announcements made by Oracle where from my perspective Mobile Cloud is the most interesting one. However there are many other clouds that we need to look into. Makes you wonder: Usually when I am that much in the clouds, I tend to call it foggy. I hope Oracle's Cloud Strategy is not foggy, but cristal clear. I know they have a plan, and I know there is a lot of work todo. We discussed the overlap we see in Mobile Cloud and Integration Cloud and also in Application Builder Cloud. Not sure if Oracle will take parts of those clouds and bundle them into a new one, or actually transfer stuff from MCS and ABCS to ICS, where it belongs. We will see.

In the after noon I had to work on a proposal for a Customer so no more sessions till 6.00 pm when the Oracle ACE reception started. It was outside near the pool but still it was very hot so we had to drink plenty of 'water'. After that I continued to the ADF Community night where we used the Wii to play games.

Tuesday
This was the day of my presentation. "Real Life MAF: The things you don't learn from Oracle's Developer Guide". I promised attendees to provide links to my demo's and also to my slides. So here they are:

1 and 2 are available here whereas you can find 4 here.
  1. Loading Images in the background
  2. Action Complete (Programatically showing popup) and fragment demo
    1. Files involved in action complete:
      1. Complaints.amx
      2. fha.js
      3. FlightAppBean.java
  3. Real Sliding Springboard (is provided as Sample App with Oracle MAF)
  4. Local Notifications
The whole presentation is available at slideshare.
After my session I had to run to the Lunch and Learn panel where I was in with co-ACE(D)s Lonneke Dikmans, Debra Lilley, Mia Urman and John Flack. As you can see, the women have the majority, which I think is not necessarily a bad thing. We discussed with the audience what they expect of the next ODTUG KScope, which by the way will be in Chicago! Min observation was that there was too much cloud and mobile and too little ADF. So here is something that we should think about for next conference. Next I joined Lonneke's session called "SOA made Simple". Using a waitress as an analogy for a service bus really makes ordering breakfast a whole other experience.

My afternoon ended with a hacking session with one of the attendees in my session. It was mainly about background threads and refreshing the UI in an MAF App. The only thing you need to do that is to call to flushDataChangeEvent() and it will refresh the UI with all changes from that background thread.
 AdfmfJavaUtilities.flushDataChangeEvent()  
I was even able to refresh the UI from two separate background threads. I will blog about that in a separate post.

My day ended with a great diner with a customer, or better a friend at GG's, a very nice place to have good seafood.

Wednesday
Today was actually a day where I followed several sessions. I started with some support (and coffee) for Lonneke who was talking about Choosing the Right Mobile Architecture. One bummer was the Oracle Mobile Cloud Service Hands On lab, which in the end was not a hands on, but a demo. Unfortunately even for Oracle Product Management there was no way to get access to MCS instances that can be used by the audience. I was really looking forward to get my hands on MCS but this ws not possible. After lunch I attended two more sessions. One of Joe Huang, outbound PM for Oracle MAF.He was talking about "New Core and Sync Services in the Oracle Mobile Application Framework: What Does It Mean for Developers". Nothing really new here for me. The other session of the afternoon was by Raghu Srinivasan. He is Director of Development in the Cloud and Mobile Development Tools, and responsible for Eclipse tooling (OEPE). His session was called "REST Easy with Oracle MAF: Building RESTful MAF Applications in Eclipse (OEPE)". It was a great session where I got to see the power of MAF in eclipse. I mainly (or should I say only) use JDeveloper but this was een eye opener. Amongst a lot of other things he showed how to use a RAML definition to generate all Java you need to work with REST/JSON services. I am convinced now. From now on I will use OEPE more for MAF Development. Great features!! A good tutorial if you just get started can be found here.

The day ended with the white party at Nikki Beach. Great food, great venue, but from the way I look at it, it was a pretty long trip (1 hour one way) for the party.


Thursday
It's a wrap. No time to attend the closing session. We had to be at the airport at 11:30. It was a great KScope again. I met some old friends and found some new ones. I missed several people who I hope to see soon. And I hope to be back next year.

Monday, June 29, 2015

IoT Hackathon Part II : Overview of Grove Pi sensors

In September (15th and 16th) we will be organising an IoT Hackathon together with Oracle. I preparation of that I will write several post here concerning IoT. This post gives you an Overview of Grove Pi sensors and some of the fun things that you can do with it. See also this two minute tech tip that is an intro to this post.

The GrovePi+ Starterkit
Connecting sensors to the Internet of Things (IOT) is really easy! No need for soldering or breadboards: plug in your Grove sensors and start programming directly. GrovePi+ is an easy-to-use and modular system for hardware hacking with the Raspberry Pi and the Internet of Things.

Here is what the kit contains:

  • The GrovePi+ Board
  • 12 Grove sensors
  • Grove cables for connecting the sensors to the GrovePi+ board.

The following sensors and lights are in the kit:
  • Grove – Sound Sensor
  • Grove – Temperature and Humidity
  • Grove – Light Sensors
  • Grove – Relay
  • Grove – Button
  • Grove – Ultrasonic Ranger
  • Grove – Rotary Angle Sensor
  • Grove – LCD RGB Backlight
  • Grove – Red LED
  • Grove – Buzzer
  • Grove – Blue LED
  • Grove – Green LED


It also contains a GrovePi+ Guidebook what all the stuff you need to setup your Sensorkit. Make sure to go trough this guidebook before you start. It can really save you some time.


Preparing your Raspi
The first step with your new GrovePi is to get it working with the Raspberry Pi. There are several ways to getting the Grove Pi communicating with the Raspberry Pi. You can configure your own image, download and use the modified Raspian image, or get an SD card from Dexter industries. All methods are described here.

I choose to use the modified Raspian image that is optimized for use with the GrovePi kit. A complete and precise description of all the steps involved in setting up the SD card can be found here. It worked for me, but be careful not to get fooled. Do not ignore this line SD card setup manual : Installation could take many minutes, and up to an hour depending on the speed of your SD card device and quality of the SD Card. Unfortunately, there is no indication of progress.


It really took me a long time. Because I was oblivious, I aborted the process several times, only to find that the SD card setup really was not finished.... It honestly took 1 hour and 20 minutes to finish the SD card creation. But once it was done, it worked great.
Don't forget to continue with the final step in the manual, expanding the filesystem on the SD card.

Creating your first thing
With the new and optimized SD card up and running, you can now go and create you first thing
Again, I found that there are many samples available. I decided to work with the Temperature and Humidity sensor. A very nice and simple setup which is described here.

I prefer to work with python because it is a very simple language which is easy to learn. I could simply copy and paste the code. The only change I had to make was to change the parameter module_type from 1 to 0 because I am using the DHT sensor and not the DHT pro. This is also part of the description. I also changed the filename to wheatherstation.py

 # grovepi_lcd_dht.py  
 # dht(pin,module_type), change module_type number to use other kind of dht  
 # module_type:  
 #       DHT11 0  
 #       DHT22 1  
 #       DHT21 2  
 #       DHT2301 3  
 from grovepi import *  
 from grove_rgb_lcd import *  
 dht_sensor_port = 7          # Connect the DHt sensor to port 7  
 while True:  
      try:  
           [ temp,hum ] = dht(dht_sensor_port,0)          #Get the temperature and Humidity from the DHT sensor  
           print "temp =", temp, "C\thumadity =", hum,"%"        
           t = str(temp)  
           h = str(hum)  
           setRGB(0,128,64)  
           setRGB(0,255,0)  
           setText("Temp:" + t + "C   " + "Humidity :" + h + "%")                 
      except (IOError,TypeError) as e:  
           print "Error"  

Now you are ready to start your program and the see the reading on your LCD
 sudo python weatherstation.py  


Some notes to help you when you run into errors:
1) If you get import errors you probably run into one of the weird and slightly confusing things about the awesome library of GrovePi scripts. A lot of these scripts import other scripts that are in the same directory. We need grovepi and grove_rgb_lcd as imports, but in order to do this without errors I had to copy grove_rgb_lcd.py into the same directory as my weatherstation script.
2) If you see some weird reading (like I did, humidity = 1800%), you probably did not change the module type parameter to indicate what DHT sensor you are using, which is explained here.

Autostart ?
Now the weatherstation works like a charm, but if the Raspi is rebooted, you need to manually restart the python script. That is not the way I want this to work. In a next post I will describe how to make the weatherstation start on reboot.

IoT Hackathon Part I : Setting up your Raspberry Pi

In September (15th and 16th) we will be organising an IoT Hackathon together with Oracle. I will write several post here concerning IoT, starting with this one where I tell you how to do the default setup of your Raspberry PI. See also this two minute tech tip that is an intro to this post.

For configuring your raspberry Pi there are several options. I prefer to use a keyboard, monitor and mouse so I can actually see what I am doing. The Pi has several USB ports and also an HDMI port.



So if you have a monitor, keyboard and a mouse available, you can plug those into your Raspberry Pi and you are all set. Connect to the power and the Pi will boot into an initial setup screen that allows you to select your preferred OS. That is of course only if you inserted an SD card in the Pi that contains the various available OS's. Select your preferred OS and click install so that the Operating System gets installed. After a short while, the installation is complete.



Once the install has finished you need to confirm by clicking OK, the system reboots, and you will automatically enter the Raspberry Pi Configuration Tool (raspi-config)


One of the things you should do is to enable SSH so you can actually enter your raspi from a different computer. I'm not sure if it is enabled by default, but just go into the raspi-config, select Advanced Options (8) and go into A4 SSH to enable it.
You can also change the default password (which is raspberry) and the default name of the device (which is raspberrypi).

Reboot one more time to activate the changes, and you are ready to start programming.

If you are happy with commandline interface, you can go wild on that one, but if you prefer to work in a GUI, you will be able to change that by using raspi-config. Open raspi-config by typing:
 sudo raspi-config  
The config menu opens, and you can change the boot option by selecting option(3). So enter this option, change the setting and reboot the Raspberry Pi. It will enter GUI mode and you will have a nice Graphical UI that you can work with.


That will be all for now. I will continue posting about our IoT Hackathon. More info can be found here. 


Sunday, June 21, 2015

ODTUG KScope15: Sunday Symposium

Sunday symposium took of at 8:30. Unfortunately i had to skip the first two session due to some work That had to be done first. The first session I really got to attend was Brian Fry's session on Oracles Developer Cloud Service.

JDeveloper, Eclipse or Netbeans was the way you would work with Oracle Developer Cloud. Now there is a new feature (not publicly available) that is a browser based IDE. It is a full development environment. It is a very flexible, responsive and quick UI. it has an integrated out of the box GIT repository. It can be used for both JavaScript, and Java EE and java SE. Oracle uses the DevCS to build the DevCS with over 145 developers using 27 GIT repositories. So oracle now is drinking their own champagne, which sounds much better then eating their own dog food by the way.  


The demo was nice and showed the power of the Javascript editor and the integrated support with GIT. also the Java editor looked very promising. We will have to sit and wait for this to be publicly available.

After lunch Lynn Munsinger, talked about Mobile Cloud Service aka MCS. She explained some of the concepts of mobile development and Mobile Backend as A Service. (MBaaS). Somewhere during this session there was a small hint about MCS being released maybe tomorrow during a live webcast. Next Lynn showed Oracle Mobile Cloud Service in a demo. finally as a bonus, there was a demo of MAX, aka Mobile App Accelerator. MAX is a part of Oracle MCS where a business user can actually create real mobile apps without coding. I have seen this before, but now it is going really close to being production. It might even be part of the first MCS release, that might be release tomorrow.

The final session of the day was really worthwhile. In this session Brian Fry showed Oracle Application Builder Cloud (it is as easy as ABC) for the very first time publicly.



ABCS is a tool for the Citizen Developer. It is targeting non technical business users to rapidly build web and mobile apps. The key features of ABCS are the following (amongst others):

- UI First Experience
- Desktop and Mobile
- Zero Install
- Codeless Drag and drop development
- No Deployment
- HTML5/Javascript UI supporting ALTA UI or Simplified UI



I certainly hope there is more to come soon.
For you the get a glimpse of what ABCS can do, you can look at the two video's I created.

Here is the first one:



Here is the second one:


Wednesday, April 22, 2015

ODTUG Mobile Day : "Going Mobile? Where is Oracle Taking You?"

Yesterday ODTUG organised the ODTUG Mobile Day in Utrecht the Netherlands. The event was supported by two local user groups, OGh and OBUG, and sponsored by eProseed NL.
This full-day event was dedicated to Oracle's next-generation mobile development, infrastructure, and security solutions: "Going Mobile? Where is Oracle Taking You?". All of this was presented by the world’s leading ACE Directors and ACEs,
  • Lonneke Dikmans – Oracle ACE Director
  • Dimitri Gielis – Oracle ACE Director
  • Roel Hartman – Oracle ACE Director
  • Mark Rittman – Oracle ACE Director
  • Mia Urman – Oracle ACE Director
  • Luc Bors – Oracle ACE
and several Oracle speakers. Session abstract are published at the ODTUG website.
These top experts in the field showed how to implement the latest mobile advancements from the newest technologies, such as Oracle Mobile Cloud infrastructure to building BI mobile dashboards to a deep dive into APEX security for mobile to the latest features in Oracle MAF.

With over 75 attendees, this event was fully booked. Sessions where well attended and with an enthusiastic audience it was fun to present at this event. All sessions focused on mobile technology applications.


In his opening keynote of Regis Louis, VP Product Management, Oracle Corporation, explained what "Going Mobile with Oracle" means and talked about Oracle's Platform and Application Strategy regarding mobile. Next, the audience split into two groups for a total of 10 paralel sessions in 2 separate tracks. One track was devoted to FMW whereas the other track was for BI and APEX.

After a great day packed with sessions, the closing keynote was for Steven Davelaar.


He showed a demonstration of an end-to-end mobile solution and discussed the overall architecture and key implementation techniques. He also shared some practical guidelines on getting started with "mobilizing" your enterprise and discuss on-premise versus cloud-based mobile back ends. Some very nice UI's and an excellent insight on how to use Oracle's cloud solution in your architecture.
At around 5:15 pm it was a wrap and people went home, happy and satisfied. We have had some really good feedback on this event and on the individual sessions as well.

Not sure where ODTUG will share the presentations, but in the meanwhile, mine can be found here:




Thanks to ODTUG, OGh and OBUG for organising this great event.




Thursday, April 02, 2015

MAF 2.1.1 : Using Local Notifications

One of the new features in version 2.1.1. of Oracle MAF are local notifications.
These notifications originate within the MAF application and are received by the same application. They are delivered to the end user through standard mechanisms supported by the mobile device platform (for example, banner, sound) and can work when the application is either in the foreground, background or not running at all.

I this post I show you an example of how to work with Local Notifications from Java. I use a simple MAF app. I will not explain how to build this app, but the source can be downloaded here. It is mainly derived from the "LocalNotificationDemo" public sample app.

Introducing Local Notifications
As with many framework features, MAF supports three ways to set Local Notifications. First you can use the device features datacontrol. To support declarative use of Local Notifications, the DeviceFeatures data control includes the addLocalNotification and cancelLocalNotification methods, which enable MAF applications to leverage a device's interface for managing notifications so end users can schedule or cancel local notifications.


Second you have the option to set Local Notifications from JavaScript. MAF allows you to manage local notifications using JavaScript APIs in the adf.mf.api.localnotification namespace. The methods add() and cancel() are available. More info on this is available from the developer guide (see resources at the end of this post).

Finally you can set Local Notifications from Java code, which is what I will explain in the remaining part of this post.

Set up the Listening Part
Because the Listening part is the same for all methods mentioned above I will start to explain this before going into detail for setting Local Notifications from Java code.
The concept of Local Notifications is from an MAF perspective not different from Push Notifications
First we need to create an eventListener that specifically listens for Local Notifications.
This class must implement oracle.adfmf.framework.event.EventListener.
In this class we must use the onMessage() method, which will fire when a notification is received.

public class MyLocalNotificationListener implements EventListener {
    public MyLocalNotificationListener() {
    }

    public void onMessage(Event event) {
    
       // work with the notification event
       // Here we can get the application state (for wich we can use a util method) and the payload
        String appState = stringifyAppState(event.getApplicationState());
        String payload = event.getPayload();

       // Now do whatever we want, for instance call a feature
        AdfmfContainerUtilities.gotoFeature("com.blogspot.lucbors.lnb.NotificationShower");
        
        }
    private String stringifyAppState(int appState) {
    switch(appState) {
        case Event.APPLICATION_STATE_FOREGROUND: return "FOREGROUND";
        case Event.APPLICATION_STATE_BACKGROUND: return "BACKGROUND";
        case Event.APPLICATION_STATE_NOT_RUNNING: return "NOT RUNNING";
    }
    return "UNKNOWN";
    }


After creating this Listener class it must be added as an eventSource in the start Method of the application Lifecycle Listener.
In this way, each time the app starts, we make sure the app is actually Listening to the local Notification event.
public void start()
  {
    // Listen for local notifications
    EventSource evtSource =  
            EventSourceFactory.getEventSource(EventSourceFactory.NATIVE_LOCAL_NOTIFICATION_EVENT_SOURCE_NAME);
    evtSource.addListener(new MyLocalNotificationListener());
  }

With all of this code in place, the app is ready to listen for and respond to local notification.

Creating local Notifications from Java
Creating a new Notification is pretty much straightforward. In this example we use a button to create a local Notification.

To work with Local Notifications using Java, the frameworks' utility class oracle.adfmf.framework.api.AdfmfContainerUtilities contains several methods to help you. The first one is addLocalNotification(). This method can be used to create a local notification. It needs an MafNativeLocalNotificationOptions Object.

The MafNativeLocalNotificationOptions Object contains several properties that can be used for notifications.

protected String title;
    protected String alert;
    protected LocalDateTime date;
    protected RepeatInterval repeat;
    protected int badge;
    protected String sound;
    protected String vibration;
    protected HashMap <String> payload;

So for creating a local Notification we first need to create a new MafNativeLocalNotificationOptions object, called options in the code below. Next we set the values for all, or many, of the properties such as alertTitle, alert, notifationDate, badge and maybe even sound and vibration. Also, if the notification needs a payload we need to set this payload. Once these values are properly set we can add a new notification to the app by calling addLocalNotification(). The call to this method returns a String containing the notificationId.

public void addNotificationForAction(ActionEvent actionEvent) {
        String notificationDate = "now";
        try
        {
            // Set the notification options
            MafNativeLocalNotificationOptions options = new MafNativeLocalNotificationOptions();
            options.setTitle("Just Some Reminder");
            options.setAlert("Did you Forget Something ?");
            if (date != null) {
                date.setSeconds(0); // Clear the seconds component to fire on the minute
                LocalDateTime l = LocalDateTime.ofInstant(Instant.ofEpochMilli(date.getTime())
                                                        , ZoneOffset.UTC);
                options.setDate(l);
                DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
                notificationDate = dateFormat.format(date);
            }
            options.setBadge(-1);
            if (sound) {
                options.setSound(MafNativeLocalNotificationOptions.SOUND_DEFAULT_SYSTEM);
            }
            if (vibration) {
                options.setVibration(MafNativeLocalNotificationOptions.VIBRATION_DEFAULT_SYSTEM);
            }
            // Set 3 values in the JSON payload
            HashMap<String Object> payload = new HashMap<String Object>();
            payload.put("key1", 1);
            payload.put("key2", "hello");
            payload.put("key3", true);
            options.setPayload(payload);
            
            // Add the notification
            String notificationID = AdfmfContainerUtilities.addLocalNotification(options);
            System.out.println("++++ Notification added successfully for " + notificationDate);
            System.out.println("++++ Notification ID is " + notificationID);
        }
        catch(Exception e)
        {
            System.out.println("++++ There was a problem adding notification: " + e.getMessage());
            e.printStackTrace();
        }    
    }

Once this notification is added, it will show up at the given moment; in this case the date that is set with options.setDate(). It will show the Notification with the corresponding message; in this case "Did you Forget Something ?" as set by setAlert();



Cancelling a Notification
Sometimes, a notification is set as a reminder for some action. As in the previous example, "Did you forget something", the user is reminded to do something. There are situations where the user already did what you reminded him to do. In that case we don't want to show the notification.

For that purpose we can use the cancelLocalNotification() method in the AdfmfContainerUtilities class. To cancel the notification we must provide the notification ID.

    public void cancelLocalNotification(ActionEvent actionEvent) {   
        try {
       // We use the notificationId that was set when the notification was added
       //setNotificationId( AdfmfContainerUtilities.addLocalNotification(options));
          String cancelledNotificationId = AdfmfContainerUtilities.
                 cancelLocalNotification(notificationId);
          System.out.println("Notification successfully canceled"); 
        }
        catch(AdfException e) {
          System.err.println("There was a problem cancelling notification");
        }
    }


Scheduling a Repeating Notification
If we want to schedule a notification to fire every time during a given interval we can use the setRepeat() on the MafNativeLocalNotificationOptions. An example of this could be that you want to remind the user to open his app every day to check for changes. So if we want a notification that is scheduled to fire every Day we simply need to call setRepeat() with interval Daily.

options.setRepeat(MafNativeLocalNotificationOptions.RepeatInterval.DAILY);

Just for testing and for the purpose of the demo I like to work with a Minutely interval. This makes not a lot of sense, but for testing, it is a good interval, at least compared to yearly.... The available options are displayed below:



Cancelling a Repeating Notification: A Solution for a Limitation
Now lets assume we have a clever user who by now knows that we what him to check his app every day. So here is the situations where the user already did what you reminded him to do. In that case we don't want to show the notification.

For that purpose we must also use the cancelLocalNotification() method in the AdfmfContainerUtilities class. Cancelling a notification means that the notification is COMPLETELY cancelled even if it was scheduled to fire every DAY. Once cancelled, it will no longer notify, not even the next day. So in order to make this really work, cancel today and notify tomorrow, we must create a new local notification, exactly like the one that was just cancelled with interval DAILY.
Just for testing purposes I added an application preference that I can use to indicate whether or not I really want to recreate the local notification.

    public void cancelLocalNotification(ActionEvent actionEvent) {   
        try {
       // We use the notificationId that was set when the notification was added
       //setNotificationId( AdfmfContainerUtilities.addLocalNotification(options));
          String cancelledNotificationId = AdfmfContainerUtilities.
                 cancelLocalNotification(notificationId);
          System.out.println("Notification successfully canceled"); 
          

            Boolean recreate = (Boolean)AdfmfJavaUtilities.evaluateELExpression(
            "#{preferenceScope.application.ReCreateNotificationAfterCancel.ReCreate}");
            // if recreate preference is true
            if (recreate.booleanValue()){
               addNotification();
            }
        }
        catch(AdfException e) {
          System.err.println("There was a problem cancelling notification");
        }
    }

Look at the logfile below and see what happens. First, the notification is created (1), then it is cancelled (2), and immediately recreated !


NOTE (1): The above limitation is not a limitation of Oracle MAF. This is simply how local notifications work on the Operating Systems. Although I am not 100% sure that the solution of re-creating the notification is the most optimal, for now it really seems to be the only way to make this work.

Resources
MAF Developer Guide Chapter 24
The sources of this app are available here.