Pages

Showing posts with label jsf. Show all posts
Showing posts with label jsf. Show all posts

Saturday, March 31, 2012

JCache on NoSQL MySQL Cluster 7.2 ( Memcached )

With the release of MySQL Cluster 7.2 and the support for the native Memcached API we can also use the HA cluster for NoSQL besides JPA, SQL.
In this blogpost I will try out this NoSQL feature with JCache ( alias JSR 107 or javax.cache , it will be part of Java EE 7 and it will also work in Java 6 ).  JCache defines a standard Java Caching API for use by developers and a standard SPI (“Service Provider Interface”) for use by implementers. Coherence of Oracle will also support JCache. For more information see Greg Luck's blog.

With JCache and MySQL Cluster alone we can't get this example running. We also need to have Memcached JCache provider.  Leen Toelen already did the hard work, he made one which uses spymemcached as memcache client. So for this we need to download his code at github, after this we also need to download the latest spymemcached jars.

The code of Leen will work with JCache version 0.4, so we need to download the 0.4 version jars at https://oss.sonatype.org/index.html#nexus-search;quick~javax-cache . You can find the source code at JSR107 github repositories. For the JCache provider you also need to download the CDI-API jar.

For more information on Memcached or MySQL you can read this great blog of clusterdb, he explains it really well, like
  • How to setup your MySQL and Memcached environment
  • What is memcached
  • How it works 
  • Let it work on your existing tables 
or you can read the MySQL Cluster 7.2 whitepaper which can be downloaded at MySQL.com

So we start by downloading MySQL Cluster 7.2 and configuring this cluster. I won't explain this here, there are a lot of great blogs or guides which can help you with this.

After we got the cluster running we need to create the memcached database.

Also we need to have at least 10 API or MYSQLD entries in the config.ini of the cluster. After this change you need to reload this config file with the ndb_mgmd daemon.


[NDBD DEFAULT]
NoOfReplicas=2
DataDir=/usr/cluster/data
DataMemory=80M
IndexMemory=18M

[MYSQLD DEFAULT]
[NDB_MGMD DEFAULT]
DataDir= /usr/cluster/data

[TCP DEFAULT]

# Management Server
[NDB_MGMD]
NodeId=1
HostName=172.16.0.20 # IP address of this server

# Storage Nodes
[NDBD]
NodeId=2
HostName=172.16.0.21 # IP address of storage-node-1
DataDir= /usr/cluster/data

[NDBD]
NodeId=3
HostName=172.16.0.22 # IP address of storage-node-2
DataDir= /usr/cluster/data

[MYSQLD]
[MYSQLD]
[MYSQLD]
[MYSQLD]
[MYSQLD]
[MYSQLD]
[MYSQLD]
[MYSQLD]
[MYSQLD]
[MYSQLD]
[MYSQLD]
[MYSQLD]
[MYSQLD]
[MYSQLD]
[MYSQLD]
[MYSQLD]
[MYSQLD]
[MYSQLD]
[MYSQLD]
[MYSQLD]
[MYSQLD]


Then we need to create the ndbmemcache database

mysql -p < /usr/share/mysql/memcache-api/ndb_memcache_metadata.sql
mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| ndbinfo            |
| ndbmemcache        |
| performance_schema |
| test               |
+--------------------+


We can start memcached on the cluster nodes (what you like  ) and it needs to connect to the NDB management service.


/usr/sbin/memcached -E /usr/lib64/ndb_engine.so -u mysql -e "connectstring=mgt.alfa.local:1186;role=db-only" -vv

you should see a output like  this.

17-Mar-2012 21:12:55 CET NDB Memcache 5.5.19-ndb-7.2.4 started [NDB 7.2.4; MySQL 5.5.19]
Contacting primary management server (mgt.alfa.local:1186) ... 
Connected to "mgt.alfa.local:1186" as node id 6.
Retrieved 3 key prefixes for server role "db-only".
The default behavior is that: 
    GET uses NDB only
    SET uses NDB only
    DELETE uses NDB only.
The 2 explicitly defined key prefixes are "b:" (demo_table_large) and "t:" (demo_table_tabs)
Connected to "172.16.0.20" as node id 7.
Server started with 4 threads.
Priming the pump ... 
Connected to "172.16.0.20" as node id 8.
Scheduler: using 2 connections to cluster 0
Scheduler: starting for 1 cluster; c0,f0,t1
done [0.677 sec].
Loaded engine: NDB Memcache 5.5.19-ndb-7.2.4
Supplying the following features: compare and swap, persistent storage, LRU
<49 server listening (auto-negotiate)
<50 server listening (auto-negotiate)
<51 send buffer was 126976, now 268435456
<52 send buffer was 126976, now 268435456
<51 server listening (udp)
<52 server listening (udp)
<51 server listening (udp)
<52 server listening (udp)
<51 server listening (udp)
<52 server listening (udp)
<51 server listening (udp)
<52 server listening (udp)


When we go to the cluster management console ( ndb_mgm)  and type show, we should see something like this

ndb_mgm> show
Cluster Configuration
---------------------
[ndbd(NDB)] 2 node(s)
id=2 @172.16.0.21  (mysql-5.5.19 ndb-7.2.4, Nodegroup: 0, Master)
id=3 @172.16.0.22  (mysql-5.5.19 ndb-7.2.4, Nodegroup: 0)

[ndb_mgmd(MGM)] 1 node(s)
id=1 @172.16.0.20  (mysql-5.5.19 ndb-7.2.4)

[mysqld(API)] 24 node(s)
id=4 @172.16.0.21  (mysql-5.5.19 ndb-7.2.4)
id=5 @172.16.0.22  (mysql-5.5.19 ndb-7.2.4)
id=6 @172.16.0.21  (mysql-5.5.19 ndb-7.2.4)
id=7 @172.16.0.21  (mysql-5.5.19 ndb-7.2.4)
id=8 @172.16.0.21  (mysql-5.5.19 ndb-7.2.4)
id=9 @172.16.0.22  (mysql-5.5.19 ndb-7.2.4)
id=10 @172.16.0.22  (mysql-5.5.19 ndb-7.2.4)
id=11 @172.16.0.22  (mysql-5.5.19 ndb-7.2.4)
id=12 (not connected, accepting connect from any host)
id=13 (not connected, accepting connect from any host)


We are ready to do some test in java and we start with spymemcached java library ( in the memcachedclient I connect to my two memcached servers and use the default port 11211).
Just create a MemcachedClient and do your set or get operations.




With this as result
2012-03-31 17:29:33.190 INFO net.spy.memcached.MemcachedConnection:  Added {QA sa=/172.16.0.21:11211, #Rops=0, #Wops=0, #iq=0, topRop=null, topWop=null, toWrite=0, interested=0} to connect queue
2012-03-31 17:29:33.205 INFO net.spy.memcached.MemcachedConnection:  Added {QA sa=/172.16.0.22:11211, #Rops=0, #Wops=0, #iq=0, topRop=null, topWop=null, toWrite=0, interested=0} to connect queue
2012-03-31 17:29:33.221 INFO net.spy.memcached.MemcachedConnection:  Connection state changed for sun.nio.ch.SelectionKeyImpl@10c832d2
2012-03-31 17:29:33.221 WARN net.spy.memcached.MemcachedConnection:  Could not redistribute to another node, retrying primary node for greetings.
2012-03-31 17:29:33.221 INFO net.spy.memcached.MemcachedConnection:  Connection state changed for sun.nio.ch.SelectionKeyImpl@47808199
There is no message
Process exited with exit code 0.

Run it again.

2012-03-31 17:30:47.662 INFO net.spy.memcached.MemcachedConnection:  Added {QA sa=/172.16.0.21:11211, #Rops=0, #Wops=0, #iq=0, topRop=null, topWop=null, toWrite=0, interested=0} to connect queue
2012-03-31 17:30:47.678 INFO net.spy.memcached.MemcachedConnection:  Added {QA sa=/172.16.0.22:11211, #Rops=0, #Wops=0, #iq=0, topRop=null, topWop=null, toWrite=0, interested=0} to connect queue
2012-03-31 17:30:47.694 INFO net.spy.memcached.MemcachedConnection:  Connection state changed for sun.nio.ch.SelectionKeyImpl@10c832d2
2012-03-31 17:30:47.694 INFO net.spy.memcached.MemcachedConnection:  Connection state changed for sun.nio.ch.SelectionKeyImpl@47808199
Hello World!
Process exited with exit code 0.

We can see that the greetings key with its value is stored into the database ( you can also configure that this key is stored in memory instead of the database )


When we change the  key to b:greetings then the value will be stored in the large demo table which optimized for bigger values ( max 3mb) .

From clusterdb blogpost.
By default, the normal limit of 14K per row still applies when using the Memcached API; however, the standard configuration treats any key-value pair with a key-pefix of “b:” differently and will allow the value to be up to 3 Mb (note the default limit imposed by the Memcached server is 1 Mb and so you’d also need to raise that). Internally the contents of this value will be split between 1 row in ndbmemcache.demo_table_large and one or more rows in ndbmemcache.external_values.



Now let's try the same with JCache.

First create a javax.cache.spi.CachingProvider file in the following folder META-INF\services
This file must contain the JCache provider class name, in this case  net.spy.memcached.jcache.SpyCachingProvider
Then we need to create a SpyCachingProvider, set the Java parameter and configure the CacheManager & Cache.


In combination with CDI you can also use annotations. Enable CDI and inject this bean in your class.


Sunday, January 1, 2012

JSF 2.0 Managed Bean Annotations and CDI on WebLogic 12c

WebLogic 12c now supports Java 6 so we can now try out the JSF 2.0 Managed Bean annotations together with CDI JSR-299. In this blogpost I will use OEPE 12c as my IDE and deploy everything on WebLogic 12c. Off course I will tell you my experiences to get all this working in Eclipse and WebLogic 12c.

First let's start with the JSF 2.0 Managed Bean Annotations.

We need to remove the managed bean definitions from the faces-config.xml file. Then make sure that you don't use the metadata-complete attribute ( metadata-complete="true" ) on the faces-config element, this will disable the search for JSF Managed Beans.


Then we can add the JSF Managed Bean annotations to the java class. We can use @ManagedBean ( javax.faces.bean ) together with the right scope annotation like RequestScoped, SessionScoped or ViewScoped ( CDI does not have this View Scope ). You will see this bean in the Faces Configuration View.



Next part of this blog is about CDI, this will be more tricky, it is possible but does not work so well in OEPE and WebLogic. I didn't have these problems with the same OEPE, Code and using Glassfish 3.11.

So to enable CDI we need to add a beans.xml file to the WEB-INF folder. Like this with a empty beans element, this will trigger CDI.

When we try to run the JSF application again then you probably will hit this NPE in the jboss Weld framework.


<javax.enterprise.resource.webcontainer.jsf.renderkit> <BEA-000000> <javax.faces.FacesException
javax.faces.FacesException
at com.sun.faces.context.ExceptionHandlerImpl.handle(ExceptionHandlerImpl.java:142)
at com.sun.faces.lifecycle.Phase.doPhase(Phase.java:119)
at com.sun.faces.lifecycle.LifecycleImpl.render(LifecycleImpl.java:139)
at org.apache.myfaces.extensions.validator.core.startup.ExtValLifecycleWrapper.render(ExtValLifecycleWrapper.java:79)
at javax.faces.webapp.FacesServlet.service(FacesServlet.java:594)
Truncated. see log file for complete stacktrace
Caused By: java.lang.NullPointerException
at org.jboss.weld.el.ELCreationalContextStack.getCreationalContextStore(ELCreationalContextStack.java:33)
at org.jboss.weld.el.WeldValueExpression.getValue(WeldValueExpression.java:47)




The solution is to remove the JSP entries and rename your files from jspx to xhtml.
From this.

<?xml version="1.0" encoding="iso-8859-1"?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.0"
          xmlns:f="http://java.sun.com/jsf/core" 
          xmlns:h="http://java.sun.com/jsf/html"
          xmlns:trh="http://myfaces.apache.org/trinidad/html"
          xmlns:tr="http://myfaces.apache.org/trinidad">
  <jsp:directive.page contentType="text/html;charset=utf-8"/>
  <f:view>
    <tr:document title="Bean and ExtVal validation">
      <tr:form>

To this, where we define the used jsf namespaces on the html element. 


Next we can try to create a new managed bean where we will use the Named annotation together with the SessionScoped annotation of javax.enterprise.context


We can also inject an EJB or a Managed Bean with the Inject annotation.


I can also replace @EJB with @Inject but then I should move my EJBs to my Dynamic Web Project else I will get an Weld error.  Probably with OEPE you have an different JPA project and this ejb.jar will be deployed together with the WAR in an EAR. 
I didn't test this but you need to enable CDI on this ejb project or EAR.

That's not all you also need to change the WebLogic Publishing mode of OEPE. This should be Publish as an exploded archive else Inject won't work. ( Thanks to Steve Button for the tip. ).

This is the error I got WELD-001408 Unsatisfied dependencies for type [DataBeanCDI] with qualifiers [@Default] at injection point [[field] @Inject private nl.amis.web.beans.DataBean.dataBeanCDI]

This is solved in the WebLogic 12c (12.1.1) patch release of March 2012, so no need for an exploded archive

And now the most annoying bug, the CDI managed beans will work only once on WebLogic 12c. When you deploy or run your application the second time then the CDI Managed bean can't be found. The solution is you need to restart the WebLogic Server. This makes development with CDI almost impossible.

This is also solved in the WebLogic 12c (12.1.1) patch release of March 2012

I also added and removed a patch which makes everything even a little better


So, I removed with the BSU utility the following patch
13603813 SU Patch [53JP]: MERGE LABEL REQUEST ON TOP OF 12.1.1. FOR CDI BUGS

and  installed

13893259 SU Patch [QPXR]: MERGE LABEL REQUEST ON TOP OF 12.1.1.0.0 FOR BUGS 13482794 13563205 13572075 13572176

Also enabling fastswap in the weblogic-application.xml ( ear project ) can help you in solving weld errors.


Here you can find my example code.

Tuesday, December 27, 2011

Using Bean Validation together with ExtVal in JPA and JSF

With the release of WebLogic 12c we can finally try out the native support for Bean Validation ( JSR-303) in JPA & JSF. With JSR-303 we can use this validation framework on the back-end side ( EJB Session Bean ) and on the managed bean of the JSF Web applications, so one framework which can do it all. It will save us a lot of time in making business rules and no need for validators on our JSF UI Components.
To make this even better I will combine this with Apache MyFaces Extensions Validator. This framework can be used with JSF ( there is a generic library and a special one for Trinidad ) and its supports the Bean Validation. ExtVal also has some extra features.



  • Type-safe group validation
  • Model validation
  • Severity aware validation
  • Client-side validation
  • Sorted violation messages
  • Dependency injection support for constraint validators
  • Mapped constraint source (e.g. for using DTO's with BV)
  • Support of @Valid


In this blog I won't give you all the possible validations options you can have in the Bean Validation or in ExtVal framework but I will try to give you a jumpstart, how to setup this up and get everything working.

Before I start I got this working with WebLogic 12c and use OEPE 12c as my IDE. For the JSF part I use Apache MyFaces Trinidad 2.0.0 which support JSF 2.0.

Let's start with the JPA part.

Here I have created a department entity which is based on the department table of the HR demo schema in the Oracle database.

On the departmentName attribute I added some validation annotations like NotNull , Size and Pattern.


On the Pattern annotation I added a custom resource bundle message ( use {} ), I only do it for pattern annotation because Size or NotNull will have it's own default message in JSF.

Bean validation is the default now so we would see the Eclipselink error anymore. So when we try to persist an entity which violates these annotations we get an error like this.

With a javax.validation.ConstraintViolationException on a prePersist callback event. This error won't give you a lot of information about the error.

You got the option to disable the Bean Validation in Eclipselink by setting the validation-mode to NONE. This way you will get the constraint error.


Like this.

So how we can retrieve these validations errors. For example I can do this on the client side before I invoke the EJB Session bean or do it inside the Session Facade methods.

We need to use the resource annotation to retrieve the Validator.
(@Resource Validator validator; )
In the department persist we can pass on the department entity to the validatior.
Set<ConstraintViolation<Department>> violations = validator.validate(department);
And loop through the violations with this as result.

error size: 2
invalid value for: 'departmentName': Name must between 2 and 30 characters
invalid value for: 'departmentName': {departmentNameValidation}



Now we can go the JSF part.
Here I do the same but then from a managed bean which is called from a commandButton.



I changed the violations to Faces Messages and skip the persist part.


This is all standard Bean Validation stuff so let's check out the ExtVal part.

I added the following libraries to the lib folder of the WEB-INF

The jsr303-tck and the validation-api jars are from Hibernate Validator and the rest is from MyFaces ExtVal. Where I remove the myfaces-extval-generic-support-2.0.5.jar because I could use the trinidad one.

To test the validation I made a simple JSF Trinidad page where you can see there are no validator or convertors defined.



In the Trinidad table I show all the departments and change for example the department Name to 1 char which violates the Size annotation.

When I use invalid characters for the department Name then I get the resource bundle message.

To make this resourcebundle work I need to set a context parameter in the web.xml which points to our own resourcebundle.

<context-param>
    <param-name>org.apache.myfaces.extensions.validator.CUSTOM_MESSAGE_BUNDLE</param-name>
    <param-value>resources.application</param-value>
</context-param>

The last part is to show you the ExtVal part. I used this managed bean which are used in the JSF page.


departmentName use the Bean Validation framework and departmentLocation use the ExtVal framework.
With this as result.


When you want to know more about ExtVal or Bean Validation you definitely should read this example chapter of Bart Kummel's Book about MyFaces Development.

Here is the example project on github. https://github.com/biemond/OEPE_examples/tree/master/beanValidation


Sunday, January 23, 2011

Some handy code for your managed Beans ( ADF & JSF )

Back in 2009, I already a made a blogpost about some handy code which you can use in your ADF Web Application. You can say this blogspot is part 2 and here I will show you the code, I use most  in my own managed Beans.

I start with FacesContext class, with this class you can use to find a JSF Component, change the Locale, get the ELContext, Add a message to your view and get the ExternalContext
The ExternalContext class, with this you can retrieve all the java init & context (web.xml) parameters, the Request & Session parameters and your web application url.
AdfFacesContext class, you can use this class for Partial Page Rendering ( PPR), get the PageFlowScope and ViewScope variables
ADFContext class, with this you can get all the memory scopes variables even the application scope variables, ELContext and the SecurityContext.
SecurityContext class, retrieve the current user and its roles.
BindingContext, BindingContainer and DCBindingContainer class. These classes are well known when you want to retrieve the ADF pagedef objects.
The last class is ControllerContext, which you can use to retrieve the exceptions

Saturday, December 5, 2009

ADF Data push with Active Data Service

With Patch Set 1 of JDeveloper 11G we can push data to the JSF page. This is called Active Data Service and this works with the following JSF component
  • activeCommandToolbarButton
  • activeImage
  • activeOutputText
  • table
  • tree
  • All DVT components
One requirement for this feature is you need to have a Datasource who supports data push. For example the BAM server of the Soa Suite support this. For other cases you need to use the Active Data Proxy framework. In this blog I made a demo with 3 different working examples , The first is an example of Oracle this in the fusion demo and can be downloaded here , the second is the one of Matthias Wessendorf and the last is the one of the Oracle ADS documentation example which works with a EJB datacontol.

In the next weeks I will make an ADS example with EJB and JMS, The ADS Page components will then listen on a topic for EJB data change events and refreshes the page with push .

But first let me show what you need to do.
Configure the adf-config.xml , located in the .adf\META-INF folder ( workspace level). With this ADF file you can configure the push parameters, study these parameters because they can influence the application performance.


<?xml version="1.0" encoding="windows-1252" ?>
<adf-config xmlns="http://xmlns.oracle.com/adf/config"
xmlns:sec="http://xmlns.oracle.com/adf/security/config"
xmlns:ads="http://xmlns.oracle.com/adf/activedata/config">
<sec:adf-security-child xmlns="http://xmlns.oracle.com/adf/security/config">
<CredentialStoreContext credentialStoreClass="oracle.adf.share.security.providers.jps.CSFCredentialStore"
credentialStoreLocation="../../src/META-INF/jps-config.xml"/>
</sec:adf-security-child>
<ads:adf-activedata-config xmlns="http://xmlns.oracle.com/adf/activedata/config">
<!--
transport allows three settings:
* streaming (default)
* polling
* long-polling
-->
<transport>long-polling</transport>
<latency-threshold>10000</latency-threshold>
<keep-alive-interval>10000</keep-alive-interval>
<polling-interval>3000</polling-interval>
<max-reconnect-attempt-time>1800000</max-reconnect-attempt-time>
<reconnect-wait-time>10000</reconnect-wait-time>
</ads:adf-activedata-config>
</adf-config>

Then create in the .adf\META-INF folder a new folder called services and put a new file in called adf-config.properties and add the following content.
http\://xmlns.oracle.com/adf/activedata/config=oracle.adfinternal.view.faces.activedata.ActiveDataConfiguration$ActiveDataConfigCallback

Go to your JSF page and drag and drop for example departments from your datacontrol to your page. Choose a readonly table ( when you want to use inputtext instead of outputtext JSF components then keep in mind you need to reset the uicomponent else it won't get the new value ) and don't use Filtering on the table.
This is how your JSF can look like.

<af:table value="#{bindings.departmentsFindAll.collectionModel}"
var="row"
rows="#{bindings.departmentsFindAll.rangeSize}"
emptyText="#{bindings.departmentsFindAll.viewable ? 'No data to display.' : 'Access Denied.'}"
fetchSize="#{bindings.departmentsFindAll.rangeSize}"
rowBandingInterval="0"
selectedRowKeys="#{bindings.departmentsFindAll.collectionModel.selectedRow}"
selectionListener="#{bindings.departmentsFindAll.collectionModel.makeCurrent}"
rowSelection="single" id="t3">
<af:column sortProperty="departmentId" sortable="true"
headerText="#{bindings.departmentsFindAll.hints.departmentId.label}"
id="c8">
<af:outputText value="#{row.departmentId}" id="ot9">
<af:convertNumber groupingUsed="false"
pattern="#{bindings.departmentsFindAll.hints.departmentId.format}"/>
</af:outputText>
</af:column>
<af:column sortProperty="departmentName" sortable="true"
headerText="#{bindings.departmentsFindAll.hints.departmentName.label}"
id="c9">
<af:outputText value="#{row.departmentName}" id="ot8"/>
</af:column>
</af:table>

Here the pagedef of the page ,we need to have a handle to the departmentsFindAll tree in our Active Data Proxy framework.

<?xml version="1.0" encoding="UTF-8" ?>
<pageDefinition xmlns="http://xmlns.oracle.com/adfm/uimodel"
version="11.1.1.55.36" id="ADStablePageDef"
Package="nl.whitehorses.app.view.pageDefs">
<parameters/>
<executables>
<iterator Binds="root" RangeSize="25" DataControl="CountrySessionEJBLocal"
id="CountrySessionEJBLocalIterator"/>
<accessorIterator MasterBinding="CountrySessionEJBLocalIterator"
Binds="departmentsFindAll" RangeSize="25"
DataControl="CountrySessionEJBLocal"
BeanClass="nl.whitehorses.model2.Departments"
id="departmentsFindAllIterator"/>
</executables>
<bindings>

<tree IterBinding="departmentsFindAllIterator" id="departmentsFindAll">
<nodeDefinition DefName="nl.whitehorses.model2.Departments"
Name="departmentsFindAll0">
<AttrNames>
<Item Value="departmentId"/>
<Item Value="departmentName"/>
</AttrNames>
</nodeDefinition>
</tree>
</bindings>
</pageDefinition>

Now we can make our own DepartmentModel where we add Active Data Service to it. We need the departmentsFindAll tree attribute from the pagedef.

package nl.whitehorses.app.ads;

import oracle.adf.model.BindingContext;
import oracle.adf.model.binding.DCBindingContainer;
import oracle.adf.view.rich.model.ActiveCollectionModelDecorator;

import oracle.adf.view.rich.model.ActiveDataModel;

import oracle.adfinternal.view.faces.model.binding.FacesCtrlHierBinding;


import org.apache.myfaces.trinidad.model.CollectionModel;

public class DepartmentModel extends ActiveCollectionModelDecorator {

private MyActiveDataModel _activeDataModel = new MyActiveDataModel();
private CollectionModel _model = null;


public CollectionModel getCollectionModel() {
if (_model == null) {
DCBindingContainer dcBindings = (DCBindingContainer)BindingContext.getCurrent().getCurrentBindingsEntry();
FacesCtrlHierBinding treeData = (FacesCtrlHierBinding)dcBindings.getControlBinding("departmentsFindAll");
_model = treeData.getCollectionModel();
}
return _model;
}

public ActiveDataModel getActiveDataModel() {
return _activeDataModel;
}

public MyActiveDataModel getMyActiveDataModel() {
return _activeDataModel;
}

}

Make your own active ActiveDataModel in which we start a thread where we fire change events.


package nl.whitehorses.app.ads;

import java.util.Collection;

import java.util.concurrent.atomic.AtomicInteger;
import oracle.adf.view.rich.activedata.BaseActiveDataModel;
import oracle.adf.view.rich.event.ActiveDataUpdateEvent;

public class MyActiveDataModel extends BaseActiveDataModel
{
protected void startActiveData(Collection<Object> rowKeys, int startChangeCount)
{
_listenerCount.incrementAndGet();
if (_listenerCount.get() == 1)
{
System.out.println("start up");

Runnable dataChanger = new Runnable()
{
public void run()
{
System.out.println("MyThread starting.");
try
{
Thread.sleep(2000);
System.out.println("thread running");
Change chg = new Change();
chg.triggerDataChange(MyActiveDataModel.this);
}
catch (Exception exc)
{
System.out.println("MyThread exceptioned out.");
}
System.out.println("MyThread terminating.");
}
};
Thread newThrd = new Thread(dataChanger);
newThrd.start();
}
}

protected void stopActiveData(Collection<Object> rowKeys)
{
_listenerCount.decrementAndGet();
if (_listenerCount.get() == 0)
{
System.out.println("tear down");
}
}

public int getCurrentChangeCount()
{
return _currEventId.get();
}

public void bumpChangeCount()
{
_currEventId.incrementAndGet();
}

public void dataChanged(ActiveDataUpdateEvent event)
{
fireActiveDataUpdate(event);
}

private final AtomicInteger _listenerCount = new AtomicInteger(0);
private final AtomicInteger _currEventId = new AtomicInteger();
}




For demo purposes we send some change events. For this we need to know the Key of the department iterator and the attribute which changed. See the ADS help page for other events like insert ,delete etc..


package nl.whitehorses.app.ads;

import oracle.adf.view.rich.event.ActiveDataEntry;

import oracle.adf.view.rich.event.ActiveDataUpdateEvent;

import oracle.adfinternal.view.faces.activedata.ActiveDataEventUtil;
import oracle.adfinternal.view.faces.activedata.JboActiveDataEventUtil;


public class Change {
public Change() {

}

public void triggerDataChange(MyActiveDataModel model) throws Exception {


for ( int i = 0 ; i < 10 ; i++) {
try {
Thread.sleep(4000);
} catch (InterruptedException ie) {
ie.printStackTrace();
}


model.bumpChangeCount();

ActiveDataUpdateEvent event =
ActiveDataEventUtil.buildActiveDataUpdateEvent(ActiveDataEntry.ChangeType.UPDATE,
model.getCurrentChangeCount(),
JboActiveDataEventUtil.convertKeyPath(new Object[] { new Long(10) , new Integer(0) }),
null,
new String[] { "departmentName" },
new Object[] { "Administration" });

model.dataChanged(event);

try {
Thread.sleep(2000);
} catch (InterruptedException ie) {
ie.printStackTrace();
}

model.bumpChangeCount();
event =
ActiveDataEventUtil.buildActiveDataUpdateEvent(ActiveDataEntry.ChangeType.UPDATE,
model.getCurrentChangeCount(),
JboActiveDataEventUtil.convertKeyPath(new Object[] { new Long(30), new Integer(0) }),
null,
new String[] { "departmentName" },
new Object[] { "Purchasing" });

model.dataChanged(event);

}
}
}

We need to add the DepartmentModel class as backing bean in the adfc-config or faces-config xml.

<managed-bean>
<managed-bean-name>DepartmentModel</managed-bean-name>
<managed-bean-class>nl.whitehorses.app.ads.DepartmentModel</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>


And at last change the value attribute of the af:table to our managed bean
af:table value="#{DepartmentModel}" var="row"

Here you can download my demo workspace

Saturday, March 14, 2009

Some handy code for backing beans ( ADF & JSF )

Here some code which you can use in your backing beans, I use this code all the time. With this you can retrieve the data or actions from the ADF page definition or create new uicomponents with ADF definitions and some JSF methods.

I'll keep this page up to date. Let me know if you have some code.

// print the roles of the current user
for ( String role : ADFContext.getCurrent().getSecurityContext().getUserRoles() ) {
System.out.println("role "+role);
}


// get the ADF security context and test if the user has the role users
SecurityContext sec = ADFContext.getCurrent().getSecurityContext();
if ( sec.isUserInRole("users") ) {
}
// is the user valid
public boolean isAuthenticated() {
return ADFContext.getCurrent().getSecurityContext().isAuthenticated();
}
// return the user
public String getCurrentUser() {
return ADFContext.getCurrent().getSecurityContext().getUserName();
}


// get the binding container
BindingContainer bindings = BindingContext.getCurrent().getCurrentBindingsEntry();

// get an ADF attributevalue from the ADF page definitions
AttributeBinding attr = (AttributeBinding)bindings.getControlBinding("test");
attr.setInputValue("test");

// get an Action or MethodAction
OperationBinding method = bindings.getOperationBinding("methodAction");
method.execute();
List errors = method.getErrors();

method = bindings.getOperationBinding("methodAction");
Map paramsMap = method.getParamsMap();
paramsMap.put("param","value") ;
method.execute();


// Get the data from an ADF tree or table
DCBindingContainer dcBindings = (DCBindingContainer)BindingContext.getCurrent().getCurrentBindingsEntry();

FacesCtrlHierBinding treeData = (FacesCtrlHierBinding)bc.getControlBinding("tree");
Row[] rows = treeData.getAllRowsInRange();

// Get a attribute value of the current row of iterator
DCIteratorBinding iterBind= (DCIteratorBinding)dcBindings.get("testIterator");
String attribute = (String)iterBind.getCurrentRow().getAttribute("field1");

// Get the error
String error = iterBind.getError().getMessage();


// refresh the iterator
bindings.refreshControl();
iterBind.executeQuery();
iterBind.refresh(DCIteratorBinding.RANGESIZE_UNLIMITED);

// Get all the rows of a iterator
Row[] rows = iterBind.getAllRowsInRange();
TestData dataRow = null;
for (Row row : rows) {
dataRow = (TestData)((DCDataRow)row).getDataProvider();
}

// Get the current row of a iterator , a different way
FacesContext ctx = FacesContext.getCurrentInstance();
ExpressionFactory ef = ctx.getApplication().getExpressionFactory();
ValueExpression ve = ef.createValueExpression(ctx.getELContext(), "#{bindings.testIter.currentRow.dataProvider}", TestHead.class);
TestHead test = (TestHead)ve.getValue(ctx.getELContext());

// Get a session bean
FacesContext ctx = FacesContext.getCurrentInstance();
ExpressionFactory ef = ctx.getApplication().getExpressionFactory();
ValueExpression ve = ef.createValueExpression(ctx.getELContext(), "#{testSessionBean}", TestSession.class);
TestSession test = (TestSession)ve.getValue(ctx.getELContext());

// main jsf page
DCBindingContainer dc = (DCBindingContainer)BindingContext.getCurrent().getCurrentBindingsEntry();
// taskflow binding
DCTaskFlowBinding tf = (DCTaskFlowBinding)dc.findExecutableBinding("dynamicRegion1");
// pagedef of a page fragment
JUFormBinding form = (JUFormBinding) tf.findExecutableBinding("regions_employee_regionPageDef");
// handle to binding container of the region.
DCBindingContainer dcRegion = form;



// return a methodexpression like a control flow case action or ADF pagedef action
private MethodExpression getMethodExpression(String name) {
Class [] argtypes = new Class[1];
argtypes[0] = ActionEvent.class;
FacesContext facesCtx = FacesContext.getCurrentInstance();
Application app = facesCtx.getApplication();
ExpressionFactory elFactory = app.getExpressionFactory();
ELContext elContext = facesCtx.getELContext();
return elFactory.createMethodExpression(elContext,name,null,argtypes);
}

//
RichCommandMenuItem menuPage1 = new RichCommandMenuItem();
menuPage1.setId("page1");
menuPage1.setText("Page 1");
menuPage1.setActionExpression(getMethodExpression("page1"));

RichCommandButton button = new RichCommandButton();
button.setValueExpression("disabled",getValueExpression("#{!bindings."+item+".enabled}"));
button.setId(item);
button.setText(item);
MethodExpression me = getMethodExpression("#{bindings."+item+".execute}");
button.addActionListener(new MethodExpressionActionListener(me));
footer.getChildren().add(button);


// get a value
private ValueExpression getValueExpression(String name) {
FacesContext facesCtx = FacesContext.getCurrentInstance();
Application app = facesCtx.getApplication();
ExpressionFactory elFactory = app.getExpressionFactory();
ELContext elContext = facesCtx.getELContext();
return elFactory.createValueExpression(elContext, name, Object.class);
}
// an example how to use this
RichInputText input = new RichInputText();
input.setValueExpression("value",getValueExpression("#{bindings."+item+".inputValue}"));
input.setValueExpression("label",getValueExpression("#{bindings."+item+".hints.label}"));
input.setId(item);
panelForm.getChildren().add(input);



// catch an exception and show it in the jsf page
catch(Exception e) {
FacesMessage msg = new FacesMessage(FacesMessage.SEVERITY_ERROR, e.getMessage(), "");
FacesContext.getCurrentInstance().addMessage(null, msg);
}


FacesMessage msg = new FacesMessage(FacesMessage.SEVERITY_WARN, msgHead , msgDetail);
facesContext.addMessage(uiComponent.getClientId(facesContext), msg);


// reset all the child uicomponents
private void resetValueInputItems(AdfFacesContext adfFacesContext,
UIComponent component){
List<UIComponent> items = component.getChildren();
for ( UIComponent item : items ) {

resetValueInputItems(adfFacesContext,item);

if ( item instanceof RichInputText ) {
RichInputText input = (RichInputText)item;
if ( !input.isDisabled() ) {
input.resetValue() ;
adfFacesContext.addPartialTarget(input);
};
} else if ( item instanceof RichInputDate ) {
RichInputDate input = (RichInputDate)item;
if ( !input.isDisabled() ) {
input.resetValue() ;
adfFacesContext.addPartialTarget(input);
};
}
}
}

// redirect to a other url
ExternalContext ectx = FacesContext.getCurrentInstance().getExternalContext();
HttpServletResponse response = (HttpServletResponse)ectx.getResponse();
String url = ectx.getRequestContextPath()+"/adfAuthentication?logout=true&end_url=/faces/start.jspx";

try {
response.sendRedirect(url);
} catch (Exception ex) {
ex.printStackTrace();
}

// PPR refresh a jsf component
AdfFacesContext.getCurrentInstance().addPartialTarget(UIComponent);


// find a jsf component
private UIComponent getUIComponent(String name) {
FacesContext facesCtx = FacesContext.getCurrentInstance();
return facesCtx.getViewRoot().findComponent(name) ;
}


// get the adf bc application module
private OEServiceImpl getAm(){
FacesContext fc = FacesContext.getCurrentInstance();
Application app = fc.getApplication();
ExpressionFactory elFactory = app.getExpressionFactory();
ELContext elContext = fc.getELContext();
ValueExpression valueExp =
elFactory.createValueExpression(elContext, "#{data.OEServiceDataControl.dataProvider}",
Object.class);
return (OEServiceImpl)valueExp.getValue(elContext);
}


// change the locale
Locale newLocale = new Locale(this.language);
FacesContext context = FacesContext.getCurrentInstance();
context.getViewRoot().setLocale(newLocale);


// get the stacktrace of a not handled exception
private ControllerContext cc = ControllerContext.getInstance();

public String getStacktrace() {
if ( cc.getCurrentViewPort().getExceptionData()!=null ) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
cc.getCurrentViewPort().getExceptionData().printStackTrace(pw);
return sw.toString();
}
return null;
}


// get the selected rows from a table component
RowKeySet selection = resultTable.getSelectedRowKeys();
Object[] keys = selection.toArray();
List<Long> receivers = new ArrayList<Long>(keys.length);
for ( Object key : keys ) {
User user = modelFriends.get((Integer)key);
}

// get selected Rows of a table 2
for (Object facesRowKey : table.getSelectedRowKeys()) {
table.setRowKey(facesRowKey);
Object o = table.getRowData();
JUCtrlHierNodeBinding rowData = (JUCtrlHierNodeBinding)o;
Row row = rowData.getRow();
Test testRow = (Test)((DCDataRow)row).getDataProvider() ;
}

Wednesday, February 25, 2009

Change language / Locale in ADF

Sometimes you need to support multiple languages in your ADF application. This probably means that you need to change labels, listbox values and maybe need to change the number format layout like the decimal and number grouping separator. In this example I will show you how you can achieve this. Here are some pics of the JDeveloper 11g example application I made. You can change the language by selecting an language in the listbox and press the refresh button or you can use a language.
Here we go from dutch to english.
In this example I am using resourcebundles to change the values / labels to the right language. You can use properties or java files for the resourcebundles. To use these resourcebundles in our application we have to configure this in the faces-config.xml or do this in the JSF page.
The JSF page I made for this demo

<?xml version='1.0' encoding='windows-1252'?>
<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"
xmlns:c="http://java.sun.com/jsp/jstl/core">
<c:set var="viewcontrollerBundle"
value="#{adfBundle['nl.whitehorses.locale.view.ViewControllerBundle']}"/>
<jsp:directive.page contentType="text/html;charset=windows-1252"/>
<f:view>
<af:document>
<af:messages/>
<af:form>
<af:panelFormLayout>
<af:panelGroupLayout layout="horizontal">
<af:selectOneChoice label="Locale" id="choice"
valueChangeListener="#{UserPreferences.localeChangeListener}"
value="#{UserPreferences.language}"
autoSubmit="true">
<af:selectItem label="Dutch" value="nl"/>
<af:selectItem label="English" value="en"/>
</af:selectOneChoice>
<af:commandButton text="Refresh"/>
<af:spacer width="10" height="10"/>
<af:commandButton text="Dutch" disabled="#{UserPreferences.language == 'nl'}"
actionListener="#{UserPreferences.localeChangeListener}">
<af:setActionListener to="#{UserPreferences.language}" from="nl"/>
</af:commandButton>
<af:commandButton text="English" disabled="#{UserPreferences.language == 'en'}"
actionListener="#{UserPreferences.localeChangeListener}">
<af:setActionListener to="#{UserPreferences.language}" from="en"/>
</af:commandButton>
</af:panelGroupLayout>
<af:spacer width="10" height="20"/>
<af:outputLabel value="#{viewcontrollerBundle.OUTPUT}"/>
<af:outputLabel value="#{msg.OUTPUT}"/>
<af:outputLabel value="#{jmsg.java_output}"/>
<af:outputLabel value="100000">
<af:convertNumber/>
</af:outputLabel>
<af:selectOneChoice label="Label" value="2">
<af:forEach items="#{bindings.lovDataResult.rangeSet}" var="li">
<af:selectItem label="#{li.lovLabel}" value="#{li.lovValue}"/>
</af:forEach>
</af:selectOneChoice>
</af:panelFormLayout>
</af:form>
</af:document>
</f:view>
</jsp:root>

I use the c:set component in the JSF page to define the resourcebundle, but when you want to use this resourcebundle in more then one page you also can define this in the faces-config.xml.
Here is my faces-config.xml which I use in this demo.

<?xml version="1.0" encoding="windows-1252"?>
<faces-config version="1.2" xmlns="http://java.sun.com/xml/ns/javaee">
<application>
<default-render-kit-id>oracle.adf.rich</default-render-kit-id>
<locale-config>
<default-locale>nl</default-locale>
<supported-locale>nl</supported-locale>
<supported-locale>en</supported-locale>
</locale-config>
<resource-bundle>
<base-name>nl.whitehorses.locale.view.locale</base-name>
<var>msg</var>
</resource-bundle>
<resource-bundle>
<base-name>nl.whitehorses.locale.view.JavaLocale</base-name>
<var>jmsg</var>
</resource-bundle>
</application>
</faces-config>

In the faces-config we can define what our default locale is. The resourcebundle without an underscore and country code should contain the labels which matches the default locale. In this example I also support english so I need to create a xxxx_en.properties or xxx_en.java file which contain the english values. After this we need define the main resourcebundles ( without _en ) and under which variable name it is known.

Here you see an project overview of the different resourcebundles I used in this example

To format number values with the right decimal and number grouping separator we can use the trinidad-config.xml. Here we can use an EL expression to define the separator for the right Locale.

<?xml version="1.0" encoding="windows-1252"?>
<trinidad-config xmlns="http://myfaces.apache.org/trinidad/config">
<skin-family>blafplus-rich</skin-family>
<number-grouping-separator>#{UserPreferences.language=='nl' ? '.' : ','}</number-grouping-separator>
<decimal-separator>#{UserPreferences.language=='nl' ? ',' : '.'}</decimal-separator>
</trinidad-config>

To see the effect of these separators we need to use af:convertNumber on an inputtext or an outputtext.

In the JSF and the trinidad-config.xml I use this backing bean to set or read the selected language.
In this backing bean I also call an ADF MethodAction which refreshes the listbox with the right locale values.

package nl.whitehorses.bean;

import java.io.Serializable;

import java.util.Locale;

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

import oracle.adf.model.BindingContext;
import oracle.adf.model.binding.DCBindingContainer;

import oracle.binding.OperationBinding;

public class UserPreferences implements Serializable {

private String language;

public UserPreferences() {
language = Locale.getDefault().getLanguage();
}

private void changeLocale(String language){
System.out.println("changeLocale "+language);
this.language = language;
Locale newLocale = new Locale(this.language);
FacesContext context = FacesContext.getCurrentInstance();
context.getViewRoot().setLocale(newLocale);

// refresh lov method action
DCBindingContainer bc = (DCBindingContainer)BindingContext.getCurrent().getCurrentBindingsEntry();
OperationBinding lovOper = bc.getOperationBinding("getLovData");
lovOper.execute();

}

public void localeChangeListener(ValueChangeEvent valueChangeEvent) {
changeLocale(valueChangeEvent.getNewValue().toString());
}

public void localeChangeListener(ActionEvent actionEvent) {
changeLocale(this.language);
}

public void setLanguage(String language) {
System.out.println("setLanguage "+language);

this.language = language;
}

public String getLanguage() {
return language;
}
}

Here an example of a java resourcebundle.

package nl.whitehorses.locale.view;

import java.util.ListResourceBundle;

public class JavaLocale_en extends ListResourceBundle {

public Object[][] getContents() {
return contents;
}
private Object[][] contents =
{ { "java_output", "java output" }
, { "java_output2","java output 2" }, };
}

Here you can download my 11g workspace

Friday, February 6, 2009

Display detail records in a dynamic ADF FormLayout

Sometimes you have to make a CRUD page on master detail tables which don't have fixed columns like the tables in the HR demo schema. The detail table has for example only a foreign key to the master table and a type and value column. The detail values are now stored vertically and not horizontal in columns. In this blog I will show how to make a dynamic custom form with inputtexts for each master.
Here is an picture of the category and category_items table I use in this blog. In this example I got for example a category U2 with the some items records like Style and Country and a category week with the weekdays as items.
I will use an local EJB as model because this gives me the Category and CategoryItems classes which I can use in the backing bean and the EJB session bean has a nice merge method which I can use when I send back the changed category.
On the EJB Session bean I created an ADF datacontrol so I can call method actions in the page definition and these methods actions will fill the method iterators.

In the left site of the page I will display all the categories and on the right I have a dynamic task flow region. You can click on a Id this will display the detail form.

When I click on my car then I will call an method action to retrieve this category class. With the items of this category I will open the category items task flow and use af:forEach to display all the items

If I select an other category I first remove the previous inputtext components and display the new items
When I change an item I can press the save button. This will call the merge method action and passes the category with the changed items to the ejb session bean, EJB will update the items values.

Here is the ddl of my examples tables.


create table CATEGORY
(
ID NUMBER not null,
NAME VARCHAR2(30) not null
)
;
alter table CATEGORY
add constraint CATEGORY_PK primary key (ID);

insert into CATEGORY (ID, NAME)
values (1, 'U2');
insert into CATEGORY (ID, NAME)
values (2, 'SEAT LEON');
insert into CATEGORY (ID, NAME)
values (3, 'WEEK');
commit;

create table CATEGORY_ITEMS
(
CAT_ID NUMBER(4) not null,
NAME VARCHAR2(40) not null,
VALUE VARCHAR2(40) not null
)
;
alter table CATEGORY_ITEMS
add constraint CATEGORY_ITEMS_PK primary key (CAT_ID, NAME);
alter table CATEGORY_ITEMS
add constraint CATEGORY_ITEMS_CATEGORY_FK1 foreign key (CAT_ID)
references CATEGORY (ID);

insert into CATEGORY_ITEMS (CAT_ID, NAME, VALUE)
values (1, 'Style', 'Pop');
insert into CATEGORY_ITEMS (CAT_ID, NAME, VALUE)
values (1, 'Country', 'Ireland');
insert into CATEGORY_ITEMS (CAT_ID, NAME, VALUE)
values (1, 'Albums', 'War,Rattle and hum');
insert into CATEGORY_ITEMS (CAT_ID, NAME, VALUE)
values (2, 'Color', 'Black');
insert into CATEGORY_ITEMS (CAT_ID, NAME, VALUE)
values (2, '4WD', 'No');
insert into CATEGORY_ITEMS (CAT_ID, NAME, VALUE)
values (2, 'HorsePower', '140');
insert into CATEGORY_ITEMS (CAT_ID, NAME, VALUE)
values (2, 'Transmission', 'Automatic');
insert into CATEGORY_ITEMS (CAT_ID, NAME, VALUE)
values (3, 'day1', 'Monday');
insert into CATEGORY_ITEMS (CAT_ID, NAME, VALUE)
values (3, 'day2', 'Tuesday');
insert into CATEGORY_ITEMS (CAT_ID, NAME, VALUE)
values (3, 'day3', 'Wednesday');
insert into CATEGORY_ITEMS (CAT_ID, NAME, VALUE)
values (3, 'day4', 'Thursday');
insert into CATEGORY_ITEMS (CAT_ID, NAME, VALUE)
values (3, 'day5', 'Friday');
insert into CATEGORY_ITEMS (CAT_ID, NAME, VALUE)
values (3, 'day6', 'Saturday');
insert into CATEGORY_ITEMS (CAT_ID, NAME, VALUE)
values (3, 'day7', 'Sunday');
commit;


My backing which calls the method action and retrieves the Category from the method iterator.

package nl.whitehorses.dynamic.beans;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.faces.event.ActionEvent;

import nl.whitehorses.dynamic.form.model.entities.Category;

import nl.whitehorses.dynamic.form.model.entities.CategoryItems;

import oracle.adf.controller.TaskFlowId;
import oracle.adf.model.BindingContext;
import oracle.adf.model.bean.DCDataRow;
import oracle.adf.model.binding.DCBindingContainer;

import oracle.adf.model.binding.DCIteratorBinding;

import oracle.adf.view.rich.component.rich.layout.RichPanelFormLayout;

import oracle.adfinternal.view.faces.model.binding.FacesCtrlActionBinding;

import oracle.jbo.Row;

public class Main {

private Long catId;
private DCBindingContainer dc;
private Category category;
private String emptyTaskFlowId = "/WEB-INF/empty-task-flow-definition.xml#empty-task-flow-definition";
private String categoryTaskFlowId = "/WEB-INF/category-task-flow-definition.xml#category-task-flow-definition";
private String taskFlowId;
List catItemList;
private RichPanelFormLayout categoryItemForm;

public Main() {
dc = (DCBindingContainer)BindingContext.getCurrent().getCurrentBindingsEntry();
}

public void categoryTableLink(ActionEvent actionEvent) {
// Add event code here...

// the category pk is already set
// now get the category
FacesCtrlActionBinding categoryFindByKey = (FacesCtrlActionBinding)dc.getControlBinding("queryCategoryFindByKey");
categoryFindByKey.execute();

// adf executed the method action
// now get the result and put this in the category variable
DCIteratorBinding categoryBinding= (DCIteratorBinding)dc.get("queryCategoryFindByKeyIter");
Row[] categoryRows = categoryBinding.getAllRowsInRange();
for (Row row : categoryRows) {
category = (Category)((DCDataRow)row).getDataProvider();
}


// remove old childern when a other category is selected
if ( categoryItemForm != null ) {
int size = categoryItemForm.getChildren().size();
for ( int i = 0 ; i < size ; i++ ) {
categoryItemForm.getChildren().remove(0);
}
}
catItemList = category.getCategoryItemsList();
// set category taskflow
taskFlowId = categoryTaskFlowId;
}

public void saveCategoryItems(ActionEvent actionEvent) {
// Add event code here...
FacesCtrlActionBinding merge = (FacesCtrlActionBinding)dc.getControlBinding("mergeEntity");
merge.execute();
}

public TaskFlowId getDynamicTaskFlowId() {
if ( taskFlowId == null ) taskFlowId = emptyTaskFlowId;
return TaskFlowId.parse(taskFlowId);
}


public void setCatId(Long catId) {
this.catId = catId;
}

public Long getCatId() {
return catId;
}

public String getCategoryFragment() {
return category.getName();
}

public void setCatItemsList(List catItemList) {
this.catItemList = catItemList;
}

public List getCatItemsList() {
return catItemList;
}

public void setCategoryItemForm(RichPanelFormLayout categoryItemForm) {
this.categoryItemForm = categoryItemForm;
}

public RichPanelFormLayout getCategoryItemForm() {
return categoryItemForm;
}

public void setCategory(Category category) {
this.category = category;
}

public Category getCategory() {
return category;
}
}

and as last the category items jsf page code

<?xml version='1.0' encoding='windows-1252'?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.1"
xmlns:af="http://xmlns.oracle.com/adf/faces/rich"
xmlns:f="http://java.sun.com/jsf/core">
<af:subform>
<af:panelHeader text="Category #{MainBean.categoryFragment}">
<af:panelFormLayout binding="#{MainBean.categoryItemForm}">
<af:forEach var="row" items="#{MainBean.catItemsList}">
<af:inputText label="#{row.name}" value="#{row.value}"/>
</af:forEach>
<f:facet name="footer">
<af:commandButton text="Save"
actionListener="#{MainBean.saveCategoryItems}"/>
</f:facet>
</af:panelFormLayout>
</af:panelHeader>
</af:subform>
</jsp:root>


Here is my 11g example workspace

Saturday, November 22, 2008

JDeveloper 11g PDF previewer

I made a new version of PDF previewer in JDeveloper 11g and add some nice new features to it. Like showing a printable page and go to a particular page.
And I also add some technical features to it like a jsf template and stretchable panels so it resizes itself

Here is an example of the printable page.

Download the 11g project here

Tuesday, September 23, 2008

Google Maps for JSF (GMaps4JSF) in JDeveloper 11G

A new version of GMaps4JSF was released. GMaps4JSF is a Google maps jsf component. This blog entry will show you how you can use it in your own 11g application.
In this example you can hide and show markers. Add markers from a backing bean and add an click event on these markers so when you click on a marker you will get an alert.

The first step to get this jsf component in JDeveloper 11g is to download the GMaps4JSF jar and import this jsp tag library in your own project.

You need your own api key. Use this url http://code.google.com/apis/maps/signup.html to get the api key
Now we are ready to use it. Change my api key in the javascript and use your own.
GMaps4JSF is a good start but it is not ready yet. You still need to write your own javascript and you can not click on a marker to see a description.For this you need to add an eventhandler and use javascript to fire something.

Here is the code for the JSF page. Very important that the method of the clientlistener starts with renderMap and add the id of the maps components to it else it won't work and the ADF splashscreen will not go away.

<?xml version='1.0' encoding='windows-1252'?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.0"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:af="http://xmlns.oracle.com/adf/faces/rich"
xmlns:yui="http://code.google.com/p/gmaps4jsf/">
<jsp:directive.page contentType="text/html;charset=windows-1252"/>
<f:view>
<af:document title="test" id="document22">
<af:clientListener method="renderMapmapPrincipal();" type="load"/>
<f:facet name="metaContainer">
<f:verbatim>
<![CDATA[
<script type="text/javascript" src="http://maps.google.com/maps?file=api&amp;v=2&amp;key=ABQIAAAAM7FSGSscPTbXiOt1No2LPRSLP72-OZgzlwHUle6cA--KWDlXYxSMtxkbiwjRJ9xjiVAYHIVo1d0VkA">
</script>
]]>
</f:verbatim>
</f:facet>
<af:form id="form22">
<af:panelGroupLayout>
<f:verbatim>
<![CDATA[
<script type="text/javascript">
function hideMarker() {
office1.hide();
office2.hide();
}

function showMarker() {
office1.show();
office2.show();
}

function marker1ClickHandler() {
alert("You clicked on the headoffice");
}

function marker2ClickHandler() {
alert("You clicked on a office");
}

</script>
]]>
</f:verbatim>
<af:panelHeader text="Ordina Offices with GMaps4JSF">
<af:panelSplitter inlineStyle="width:750px; height:550px;" splitterPosition="110">
<f:facet name="first">
<af:panelGroupLayout layout="vertical"
xmlns:af="http://xmlns.oracle.com/adf/faces/rich">
<f:verbatim>
<input type="button" value="Hide Offices"
onclick="hideMarker();"/>
<input type="button" value="Show Offices"
onclick="showMarker();"/>
</f:verbatim>
<af:panelGroupLayout/>
</af:panelGroupLayout>
</f:facet>
<f:facet name="second">
<yui:map id="mapPrincipal" width="500px" height="500px" zoom="7"
binding="#{GoogleBean.map}">
<yui:mapControl name="GLargeMapControl"
position="G_ANCHOR_BOTTOM_RIGHT"/>
<yui:mapControl name="GMapTypeControl"/>
</yui:map>
</f:facet>
</af:panelSplitter>
</af:panelHeader>
</af:panelGroupLayout>
</af:form>
</af:document>
</f:view>
</jsp:root>

The backing bean I used

package nl.ordina.google.backing;

import com.googlecode.gmaps4jsf.component.map.Map;
import com.googlecode.gmaps4jsf.component.marker.Marker;
import com.googlecode.gmaps4jsf.component.eventlistener.EventListener;

public class GoogleBean {
private Map map;

public void setMap(Map map) {
this.map = map;
map.setLatitude("52.05");
map.setLongitude("5.11");


Marker mark = new Marker();
mark.setLatitude("52.05");
mark.setLongitude("5.11");
mark.setJsVariable("office1");
mark.setId("mark1");
EventListener event = new EventListener();
event.setEventName("click");
event.setJsFunction("marker1ClickHandler");
mark.getChildren().add(event);
map.getChildren().add(mark);

Marker mark2 = new Marker();
mark2.setLatitude("53.19");
mark2.setLongitude("6.53");
mark2.setJsVariable("office2");
mark.setId("mark2");
EventListener event2 = new EventListener();
event2.setEventName("click");
event2.setJsFunction("marker2ClickHandler");
mark2.getChildren().add(event2);
map.getChildren().add(mark2);

}

public Map getMap() {
return map;
}

}

faces-config.xml

<?xml version="1.0" encoding="windows-1252"?>
<faces-config version="1.2" xmlns="http://java.sun.com/xml/ns/javaee">
<application>
<default-render-kit-id>oracle.adf.rich</default-render-kit-id>
</application>
<managed-bean>
<managed-bean-name>GoogleBean</managed-bean-name>
<managed-bean-class>nl.ordina.google.backing.GoogleBean</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
</managed-bean>
</faces-config>

Saturday, September 6, 2008

Use Flex in JSF with Exadel Fiji

Maybe you already saw this news at the serverside or by James Ward but Exadel just released Fiji. With Fiji you can use Flex applications in a JSF page and interact with the other JSF component or backing beans. I already tried this too but Exadel got it working with a lot of great options. You can develop now better looking and richer JSF applications with cool video or charts components. These flex applications can be a part of the jsf application without using blazeds or lifecycle. And of course you can use Flex to build complex parts of your jsf page ( like Charts, Drag and Drop, Video or Trees) and you can do it a lot faster and it is easier.

Here can you see the exadel jsf flex demos or read more about Fiji at the Exadel product page .

Fiji options
1) use f:param to pass values to the flex application
2) use HTTPService to retrieve any value of a backing bean method.
3) use DataService to retrieve the result using AMF format.
4) Invoking Ajax Request to send events to other parts of the jsf page.
5) Access to the flex api from the jsf page
6) New Flex Charts components in JSF without making a flex application

A little example
You can pass parameters to the flex application. Just use f:param and el

<fiji:swf src="/simpleHello/simpleHello.swf" id="simpleHello"
bgcolor="#FFFFFF" width="320" height="180">
<f:param name="userName" value="#{bean.simpleHelloUserName}" />
</fiji:swf>

And retrieve the parameters in Flex just use Application.application.parameters.userName

<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
<mx:Form x="22" y="10" width="280">
<mx:Label id="userName" styleName="text" text="{Application.application.parameters.userName}" />
</mx:Form>
</mx:Application>