Monday, September 27, 2010

RichFaces media output. Why not? Here is why.

So, few words about RichFaces. You know the general details, of course, and I'm sure Google is of more help than I am on that and many other matters. From my experience, it is very usable, easy to start with, with nice skinning mechanism and very usable default skins (unlike IceFaces, for instance). Since ADF is proprietary framework (and good luck with finding anyone willing to pay license for it), it only makes sense comparing it to IceFaces. It is more stable, has quite larger community (I didn't count, but searching for problems and answers on the web was pretty easier for RichFaces). As everything else, both IceFaces and RichFaces have a few quirks, and noting them all would take some time, so I have chosen the one that gave us the most trouble.

RichFaces mediaOutput component, as you can see in live demo, can be useful for displaying various media content you generate, as said on the linked page, "on the fly". Sure, it worked quite well in the demo, but once we started using it in our application, it turned out to be quite a burden.

I noticed something was wrong when I tested it in Internet Explorer. The damn browser still holds quite a bit of a market, although competition is numerous and, in my opinion, better. Firefox is my default browser for about five years, and I am satisfied with it, although since my recent purchase of a Macbook, Safari became my primary browser (I simply like the cover flow stuff) although Firefox is still used from time to time and is a default system browser (mostly for my home projects). But using Firefox in a corporate environment is still novelty new in my country, and some companies simply have company standards which require MS technologies, including IE (even in version 6.0!). So, you simply can't avoid browser compatibility.

Browser compatibility hurts like hell. CSS stuff has always been a challenge for browser compatibility and things didn't get better with xFaces frameworks. Because of their relying on a bundle of styles and JavaScript, it is a very sensitive matter. You will often have a very nice working page in Firefox, Chrome and even Opera, but in IE it will cause a number of errors, usually indicated only by message in lower left corner. When you open that message, you will see something saying that there is an error in ".../a4jsf/93hjd90u39j93e3e/homeiu384.js", somewhre deep in RichFaces source. Of course, other browsers can also cause similar problems, but on our project, it turned out that IE is the biggest problem, at least for working with components from RichFaces.

We had a need for pop-up window which displays contents of remote HTML or PDF files. Pop-ups in RichFaces are implemented as panels which are shown/idden using JavaScript (wrapped up in RichFaces whith convinient show() and hide() functions, as you can see in the examples). We followed the examples from livedemo website, and soon had nice working example of MediaOutput in Firefox, Chrome and Opera. Unfortunately, IE simply showed blank window. Google, RichFaces forums, Ilya and everything else didn't give any other aswer than "It works in our examples in livedemo". Sure, if our applications were that simple as demos. Don't get me wrong, your own messed up forms and can cause trouble which some browser can tolerate, and some can not. For instance, thanks to templates, includes and our carelessness, we had a case of nested forms (a big no-no), which was tolerated by Firefox, but Chrome just stopped rendering a page and showed error output. So it would be wrong to blame framework and RichFaces developers (who have done quite a job) for everything that has gone wrong. But in this case, MediaOutput did not work in IE.

We have lost quite a lot of time and finally implemented our functionality using simple HTML OBJECT. Works everywhere. In fact, RichFaces eventually transforms your mediaOutput component into OBJECT so the result is the same, only RichFaces gets sometimes lost in the way.

I still recommend RichFaces for JSF frameworks. It is good, nice looking, but it is nice to know it's limitations. But the only way to get to that is by painful experience. But that is our job. My motto, which is popular children song sang during kids games, describes our programming work: "Kolariću, paniću, pletemo se samiću, sami sebe zaplićemo, sami sebe rasplićemo...". The bold part, in Serbian, says:"we tangle ourselves, we untangle ourselves". I think you get it :)



P.S.
As for ADF, well, it has taken a few years of my life, been a good experience... I wonder if I will be using it again. It is browser-safe, obviously well tested, pricey, and, taken in account ADF bindings and data controls, good only if you have well defined and stable architecture underneath (yeah, business components).

Difference in comparin Integer between JDK 1.6.0_11 and JDK 1.6.0_18

Quite busy summer, but just to post quick info about thing that happened to me a moment ago.

I develop application on a Windows machine, test it etc. and once a day I deploy it to our Linux server for our testers. You know those situations "App works perfectly on my machine, but crashes in production"? Well, after checking parameters and other config stuff, I turned myself to my Log4j trace (yeah, should have done that first) and it turned out that certain validation of mine fails.
It compares two Integer values, in this example 37 and 37, by using != operator.

So, machine A, JDK 1.6.0_18, a == b returns true
Machine B, JDK 1.6.0_11 a==b returns false.

where a and b are Integer(37).

Safe way, of course, is using equals method, or comparing by intValue property.

A lot of interesting stuff about RichFaces in my project has happened this summer, but I leave that for some other time. Deadlines and stuff, you know :)

Sunday, June 6, 2010

JBoss and RichFaces: jsf libs and errors

Definitely: just following manual is just not enough. So we waste our working days on stuff like this...

Recently I started working on JBoss/RichFaces platform for a new application. I was following the instructions from RichFaces website, and added the jars they said were necessary. It worked well on Tomcat, but it didnt go that wel on JBoss. It resulted in exceptions like this:

ConfigurationException: CONFIGURATION FAILED! null at com.sun.faces.config.
ConfigManager.initialize(ConfigManager.java:213)...


with a long stack trace, of course... Stack trace didn't point to any configuration error, just this null pointer exception. So, after some Google-ing, I saw that it is a common problem, some are lucky to get a few pointers from stack trace, but some just get this null and are stuck. Fortunately, a few guys pointed out that the problem might be with the jfs libraries.

So, what really happens is that libraries that I have put in WEB-INF/lib folder conflict with JBoss JSF libraries already present on server. Libraries in question are jsf-api, jsf-impl and jsf-facelets jars. Problems might arise from the fact that versions of user provided and server provided libraries are not the same, or that interfaces of some required libraries are not appropriate for some other in combination.

So, my first step was to replace those jars with the one present in JBoss server libs (simple search through jars in JBoss with TotalCommander gives you location of libs, packed in JBoss war-s or some similar archive ). It didn't quite work. Obviously, if i deploy app war with the same libraries, the problem remains.

So, I then removed the jsf libs from web-inf/lib. It worked to a point where I started using jsf functions in my code (like FacesContext.getCurrentInstace()....) , where I needed jsf jars in project. But I don't want tohose jars in my war. So, what to do?

Solution is quite simple. Instead of placing jars yourself, you just add JBoss Server LIbrary to your project's build path in Eclipse. (Properties->Configure Build Path...). Your project has all required dependencies for development, and deployed app has the same enviroment like during deployment. You can check through libraries to see if there are any other unnecessary duplicates.




So,

Thursday, February 25, 2010

Eclipselink: table sequence generator and concurrency - Part Three

Thanks to eager readers, I finally got to finish this :D

So far, last two posts showed that there is a problem with sequencing in eclipselink regarding concurrent access and that there is a possibility of implementing custom sequence generator. We decided to to our own implementation of table generated sequence.

So, as we said, we have our own sequence generator class which is, interestingly, called CustomSequenceGenerator:

As you can see, getGeneratedValue method is used to get concrete value. There an instance of CustomSequenceManager class is used. Of course, it is used in our

public class CustomSequenceGenerator extends Sequence implements SessionCustomizer {
private Logger logger = Logging.getLogger(CustomSequenceGenerator.class);

private CustomSequenceManager customSequenceManager;
public CustomSequenceGenerator() {
super();
logger.debug("CustomSequenceGenerator()");
customSequenceManager = new CustomSequenceManager();
logger.debug("new CustomSequenceManager()");
}

public CustomSequenceGenerator(String name) {
super(name);
logger.debug("CustomSequenceGenerator(String name)");
customSequenceManager = new CustomSequenceManager();
logger.debug("new CustomSequenceManager()");
}

@Override
public Object getGeneratedValue(Accessor accessor,
AbstractSession writeSession, String seqName) {
logger.debug("getGeneratedValue()");
Long nextValue = customSequenceManager.getNextValue(seqName);
logger.debug("nextValue = " + nextValue);
return nextValue;
}

@Override
public Vector getGeneratedVector(Accessor accessor,
AbstractSession writeSession, String seqName, int size) {
return null;
}

@Override
protected void onConnect() {
}

@Override
protected void onDisconnect() {
}

@Override
public boolean shouldAcquireValueAfterInsert() {
return false;
}

@Override
public boolean shouldOverrideExistingValue(String seqName,
Object existingValue) {
return existingValue == null;
}

@Override
public boolean shouldUseTransaction() {
return false;
}

@Override
public boolean shouldUsePreallocation() {
return false;
}

public void customize(Session session) throws Exception {
logger.debug("customize(Session session) ");
CustomSequenceGenerator sequence = new CustomSequenceGenerator("BASE_ENTITY_SEQ");
logger.debug("new CustomSequenceGenerator('BASE_ENTITY_SEQ')");
session.getLogin().addSequence(sequence);
logger.debug("session.getLogin().addSequence(sequence)");
}

}


Now, let's see CUstomSequenceManager class. What we do is just simulate the sequencing as we know it in Oracle.

public class CustomSequenceManager {
private static Logger logger = Logger .getLogger(CustomSequenceManager.class);
protected HashMap sequenceMap = new HashMap();
//Saves values for frame limits
private Long allocationSize = MyParameters.getSequenceFrameSize();
protected HashMap sequenceFrame = new HashMap();

public synchronized Long getNextValue(String seqName){
SequenceService sequenceService = new SequenceService();

Long seqCount = sequenceMap.get(seqName);
Long seqLimit = sequenceFrame.get(seqName);

if(seqCount == null || seqCount == seqLimit-1){
seqCount = sequenceService.getCustomSequenceNextValue(seqName);
if(seqCount == null){
seqCount = 0L;
}
seqLimit = seqCount + allocationSize;

sequenceFrame.put(seqName, seqLimit);
sequenceMap.put(seqName, seqCount);
}
else if(seqCount<=seqLimit-1){
seqCount++;
sequenceMap.put(seqName, seqCount);
}


return seqCount;
}
}

I guess it's easy to follow. We allocate frame of values and use the values from it. when we exceed the frame values, we get the next frame etc. We use hash map for storing current values, and we get new frame values from SequenceService.


public class SequenceService {
SequencePersist sequencePersist;

public SequenceService (){
try {
Context ctx = new InitialContext();
sequencePersist = (SequencePersist) ctx.lookup("SequencePersistBean");

} catch (javax.naming.NamingException ne) {
throw new EJBException(ne);
}
}

public UserSequence create(UserSequence s){

return sequencePersist.create(s);
}
public UserSequence modify(UserSequence s){
return sequencePersist.modify(s);
}
public UserSequence find(String seqName){
Object seqObj = sequencePersist.find(seqName);
if(seqObj == null){
return null;
}
return (UserSequence)seqObj;
}
public List findQuery(String queryString){
return sequencePersist.findQuery(queryString);
}
public Long getCustomSequenceNextValue(String seqName){
return sequencePersist.getCustomSequenceNextVal(seqName);
}

}

Nothing special here, because all the work is done behind SequencePersist interface. Here we used EJB implementation, but you can also use Spring to get bean. What is really important is in SequencePersistBean implementation, and that is database access to sequence table.

@Stateless(name="SequencePersistBean")
public class SequencePersistBean implements SequencePersistLocal,SequencePersistRemote{
@PersistenceContext(name = "my_sequence/EntityManager",
unitName = "my_sequence")
EntityManager em;
Logger logger = Logger .getLogger(SequencePersistBean.class);

public SequencePersistBean(){

}
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public UserSequence create(UserSequence s){
em.persist(s);
return (UserSequence)em.find(s.getClass(), s.getSeqName());
}
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public UserSequence modify(UserSequence s){
return (UserSequence)em.merge(s);
}
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public UserSequence find(String seqName){
Object seqObj = em.find(UserSequence.class, seqName);
if(seqObj == null){
return null;
}
return (UserSequence)seqObj;
}
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public List findQuery(String queryString){
return (List) em.createQuery(queryString).getResultList();
}
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public Long getCustomSequenceNextVal(String seqName){
Long sequenceCount = null;
try{
StoredProcedureCall spcall = new StoredProcedureCall();
spcall.setProcedureName("CC1NextSequenceVal");
spcall.addNamedArgument("SequenceName");
spcall.addNamedOutputArgument(
"NextValue", // procedure parameter name
"NextValue", // out argument field name
BigDecimal.class // Java type corresponding to type returned by procedure
);

ValueReadQuery query = new ValueReadQuery();
query.setCall(spcall);
query.addArgument("SequenceName"); // input

List args = new ArrayList();
args.add(seqName);


Session session = JpaHelper.getEntityManager(em).getActiveSession();
logger.debug("session" + session);
BigDecimal intSeq = (BigDecimal) session.executeQuery(query, args);
sequenceCount = intSeq.longValue();
}catch(RuntimeException e){
logger.fatal("getCustomSequenceNextVal", e);
}
logger.debug("sequenceCount" + sequenceCount);
return sequenceCount;
}
}


As you can see, @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) denoting methods is used to guarantee that each call is done in separate transaction.

I won't get into too much details about seuence table, because it is just imitation of Ewclipselink generated table. We use bean's methods to modify values in the table.


getCustomSequenceNextVal method is interesting, because we use stored procedure to ensure complete transaction isolation on database level. This Java code should be universal on all databases that have stord procedures and are supported by EclipseLink. The final touch is done writing stored procedure, which is DB thing. We used Oracle, so procedure is written as autonomous, on other database you should make something equivalent.

So, the essence of the matter is that you take frame of N values from DB table, store it in Java Hash map where you read and increment values. When you exceed the frame, you go to the DB to get new values. Reading through stored procedure that is autonomous will ensure that every concurrent reader gets different frame value. Basically, procedure looks in the table for row with specific SEQ_NAME. From the same row reads the value, and frame size and returns it. Then it increments value for frame, stores it, and that is it. Everything that Eclipselink table generated sequence should do, but doesn't for some reason.