Are you tired of always waiting for re-deployment whenever you change something during development?
I sure am! Recently it has even become worse, because my Enterprise JBoss Application Server takes around 4 minutes to boot, and it is not uncommon that I redeploy the application up to 20 - 30 times during one day.
That's already 80 - 120 minutes per day accumulated that I just have to wait for the application to start, and what's worse, many times I am in the flow, and really concentrated on the task at hand, then I have to redeploy, and I will unset my mind and start browsing some emails, forums, etc and totally loose my flow. It's hard to get back in that mindset again.
Wouldn't it be awesome that whenever you saved a change in your IDE, that it would instantly be picked up by the application server, and reloaded? Why do we need to reload the entire application each time? It doesn't make sense.
JRebel to the rescue!
I have known about JRebel for some time, and I knew about its awesomeness. However, when I tried to install it a few years back, it was really tedious and error prone, and I couldn't really get it working correctly. But recently JRebel has been shipped with a new configuration wizard which basically does everything for you. It took next to no time to install it and get it working, and already it is saving me a lot of time.
JRebel is just awesome, and every Java developer should (read must) use it!
This blog is mainly about Java...
Tuesday, December 14, 2010
Friday, December 3, 2010
Moments when you should sense danger in Chess
1. There has been a change in the pawn structure. Your opponent has 8 and you don't have any.
2. Your opponent begins to throw pawns at your eyes.
3. You have a position won but your opponent has a gun.
4. The Director tells you not to bother turning in your scoresheet after the game.
5. Before the game begins you notice your opponents 1st initials are 'GM'.
6. After completing your development you sense your opponent playing the endgame.
7. Just as you make your opening move your opponent announces mate in 11.
8. You don't control any squares at all.
9. Your draw offer sends all the people watching your game into uncontrollable laughter.
10. Your opponent has 3 bishops.
11. Your opponent slaps his head and cries "Noooo what a mistake!!", then few moves later, you are down a queen.
12. You announce forced mate in 7 by sacking two pieces, then you resign after the 8'th move.
13. You crush your opponent on the chess board, but the opponent crushes you with the chess board.
2. Your opponent begins to throw pawns at your eyes.
3. You have a position won but your opponent has a gun.
4. The Director tells you not to bother turning in your scoresheet after the game.
5. Before the game begins you notice your opponents 1st initials are 'GM'.
6. After completing your development you sense your opponent playing the endgame.
7. Just as you make your opening move your opponent announces mate in 11.
8. You don't control any squares at all.
9. Your draw offer sends all the people watching your game into uncontrollable laughter.
10. Your opponent has 3 bishops.
11. Your opponent slaps his head and cries "Noooo what a mistake!!", then few moves later, you are down a queen.
12. You announce forced mate in 7 by sacking two pieces, then you resign after the 8'th move.
13. You crush your opponent on the chess board, but the opponent crushes you with the chess board.
Labels:
chess
Thursday, November 4, 2010
How to add RichFaces to your existing jQuery enabled site
As of Richfaces 3.0, jQuery has been inbuilt. If you don't know what jQuery is, you can watch a video about it here.
We have been using jQuery separately in our project, but wanted to add some Richfaces components. Now since Richfaces is using jQuery, there is a conflict that appears.
They both use $ as their main function name. That might brake the behaviour, and you might see error messages in your web browser, such as:
To resolve such cases jQuery introduces the
To use the jQuery that is bundled in RichFaces, you have to load the correct script.
Then you can assign jQuery to $j for convenience. Add the following in your javascript code:
Your new code now may look something like this:
And your Richfaces component will display and work without any problems! Thats it!
We have been using jQuery separately in our project, but wanted to add some Richfaces components. Now since Richfaces is using jQuery, there is a conflict that appears.
They both use $ as their main function name. That might brake the behaviour, and you might see error messages in your web browser, such as:
this.focusKeeper.observe is not a function
[Break on this error] Richfaces.ListBase.ASC="acs";Richfaces...ner('load',this.imagesOnLoad,true);}}
ListBase.js (line 4)
and
element.dispatchEvent is not a function
[Break on this error] event.eventName=eventName;event.memo=m...nt.fireEvent(event.eventType,event);}
3_3_1....eScript (line 265)
To resolve such cases jQuery introduces the
.noConflict()
function.To use the jQuery that is bundled in RichFaces, you have to load the correct script.
<a:loadScript src="resource://jquery.js"/>
Then you can assign jQuery to $j for convenience. Add the following in your javascript code:
$j = jQuery.noConflict();
Then you have to replace all your former $()
with $j()
or the equivalents $. $[]
and
function($)
with $j. $j[] and function($j)
Your new code now may look something like this:
function showMessages() {
$j("div#messagetextPanel").fadeIn("fast");
}
And your Richfaces component will display and work without any problems! Thats it!
Thursday, October 7, 2010
Why you should NOT use the Seam Application Framework
NOT TO BE CONFUSED WITH SEAM, WHICH IS AWESOME!
I am a very experienced Seam user. I have used Seam daily now for almost 3 years.
In this blog post, I will try to convince you to avoid the Seam Application Framework, and truly understanding when it should be used, and when it shouldn't.
What is Seam Application Framework?
Seam in Action says: "Seam is an application framework for Java EE. It provides a container that manages components and leverage's those components to tie the layers of an enterprise Java application together. Nestled within the Seam code base lies a handful of classes that comprise the Seam Application Framework. This "framework within a framework" is a specialized collection of component templates that effortlessly blanket the programming requirements of garden-variety web applications. Such tasks include performing create,read,update and delete (CRUD) operations on entity instances;querying for data; and developing JSF page controllers. You may be hesitant to give this spattering of classes much notice, but I assure you that they'll carry you a long way."
According to Dan Allen, he is encouraging you to check this "framework within a framework" out. However, though he is right, that there are benefits with this framework, I will prove that there are far more disadvantages than advantages and that he should have rewritten the last line to include that the framework is only really useful for small CRUD applications, and not something to leverage and build upon.
Way too many people are using this framework
If you have a look at the Seamframework.org forums, you will see tons of questions from people having problems and issues with the Seam Application Framework. Either a lot of people are developing small CRUD based applications, or there is a growing misconception about Seam and Seam Application Framework.
There are many people that are confused about this notion. They think that Seam == Seam Application Framework, and that they have to use it. And who can blame them? The seam-gen utility is a fantastic way of getting you quickly started. However, I have always encouraged people to only use the create-project and generate-entities part of the utility, and not the generate-ui part of it, this because most people I have talked with, are not developing small CRUD based applications, but rather medium sized enterprise apps, but since you get the Seam Application Framework for free, it is quite easy to extend and use it.
PROS
I did mention that there are a few pro's about using this framework, but it depends on people truly understanding the inner workings of the framework.
I also want to note, before I get spammed with comments about how wrong I am, that you can if you know what you are doing, speed things up. Details can be found here. However, this again requires some knowledge about Seam.
CONS
Consider this very very simple example. We have a SystemLog, and we want to display the log.
We have an action class that used the EntityQuery of the Seam Application Framework.
And this normal (non tweaked) seam component.
And a simple data table iterating over the elements
Now at first glance, I think the normal seam component is much cleaner and easier to read. Remember, this is a very simple example. In a normal real life example you would have extended the component to be much more complicated, and if you want to do this using the existing framework, you will need to find out what the framework already is doing, and correctly override those methods. Otherwise, you risk of doing the same thing twice.
If you ask your self what the @MeasureCalls annotation is doing, then have a look here, and scroll down to the second example. But a short description is that it measures the time of execution for a method.
When you run this example using the SystemLogNormal and the Query component, they are pretty much equal. However, you will see that the Query component is also calling the validate() method, which takes a few milliseconds to execute.
It is correct that a few milliseconds is hardly anything noteworthy, however this is a very very simple example, with very little data, and in a more realistic scenario, the number could possibly be different. Either way, the overall time difference is that the EntityQuery object is a few milliseconds slower.
As you can see, we get three outputs of "Inside systemlog". Really there should only be one. So adding a @Factory annotation on top of it will do the trick for both versions.
Then there are the Home and Query objects, with methods like isIdDefined(), clearDirty(), isManaged(), getEjbql(), RESTRICTIONS and more confusing methods which you need to read the documentation to understand.
Conclusion
In my opinion, for first time users who are learning Seam, it is more than enough to understand and learn the framework, instead of in addition learning "a framework inside a framework". The seam-gen utility is sort of a devil in disguise. Its great for a shortcut to get started, but its evil in disguise for new comers.
Newbies think Seam Application Framework is Seam, and all components they create must extends the Home, List or Query objects. Its confusing, and in my opinion they should avoid using it unless they know all the ins and out of both Seam and the application framework.
So if you are new to Seam, start by looking at the Seam examples that comes with the bundle. They are far better to use, than generating the UI with seam-gen and going from there.
I am a very experienced Seam user. I have used Seam daily now for almost 3 years.
In this blog post, I will try to convince you to avoid the Seam Application Framework, and truly understanding when it should be used, and when it shouldn't.
What is Seam Application Framework?
Seam in Action says: "Seam is an application framework for Java EE. It provides a container that manages components and leverage's those components to tie the layers of an enterprise Java application together. Nestled within the Seam code base lies a handful of classes that comprise the Seam Application Framework. This "framework within a framework" is a specialized collection of component templates that effortlessly blanket the programming requirements of garden-variety web applications. Such tasks include performing create,read,update and delete (CRUD) operations on entity instances;querying for data; and developing JSF page controllers. You may be hesitant to give this spattering of classes much notice, but I assure you that they'll carry you a long way."
According to Dan Allen, he is encouraging you to check this "framework within a framework" out. However, though he is right, that there are benefits with this framework, I will prove that there are far more disadvantages than advantages and that he should have rewritten the last line to include that the framework is only really useful for small CRUD applications, and not something to leverage and build upon.
Way too many people are using this framework
If you have a look at the Seamframework.org forums, you will see tons of questions from people having problems and issues with the Seam Application Framework. Either a lot of people are developing small CRUD based applications, or there is a growing misconception about Seam and Seam Application Framework.
There are many people that are confused about this notion. They think that Seam == Seam Application Framework, and that they have to use it. And who can blame them? The seam-gen utility is a fantastic way of getting you quickly started. However, I have always encouraged people to only use the create-project and generate-entities part of the utility, and not the generate-ui part of it, this because most people I have talked with, are not developing small CRUD based applications, but rather medium sized enterprise apps, but since you get the Seam Application Framework for free, it is quite easy to extend and use it.
PROS
I did mention that there are a few pro's about using this framework, but it depends on people truly understanding the inner workings of the framework.
- Quick to get started
- Excellent for small models,presentation and proof of concept
- Great in-built pagination of lists
- Can @Override the methods and inherit classes to extend/tweak upon the framework
I also want to note, before I get spammed with comments about how wrong I am, that you can if you know what you are doing, speed things up. Details can be found here. However, this again requires some knowledge about Seam.
CONS
- Its a bit slower
- More abstraction which might not suit your requirements
- More difficult to use than the plain components. You need to "learn a new framework as well as Seam"
Consider this very very simple example. We have a SystemLog, and we want to display the log.
@Entity
public class SystemLog {
@Getter @Setter
@Id
@GeneratedValue
private Long id;
@Getter @Setter
@NotEmpty
private String description;
@Getter @Setter
@Column
private String category;
@Getter @Setter
@Temporal(TemporalType.TIMESTAMP)
private Date date;
@Getter @Setter
@Column
private String username;
@Getter @Setter
@Column
private String organization;
@Getter @Setter
@Column(length = 1024)
@Lob
private String details;
@Getter @Setter
@Column
private String severity;
@Getter @Setter
@Column
private String ipAddress;
}
We have an action class that used the EntityQuery of the Seam Application Framework.
@Name("systemLogQuery")
@MeasureCalls
public class SystemLogQuery extends EntityQuery<SystemLog> {
@Logger Log log;
@Override
public String getEjbql() {
return "select sys from SystemLog sys";
}
@Override
public List<SystemLog> getResultList() {
log.warn("Inside systemlog query getResultList");
return super.getResultList();
}
}
And this normal (non tweaked) seam component.
@Name("systemLogNormal")
@MeasureCalls
public class SystemLogNormal {
@Logger Log log;
@In EntityManager entityManager;
List<SystemLog> result;
public List<SystemLog> getResultList() {
log.warn("Inside systemLog normal resultList");
if(result == null)
this.result = entityManager.createQuery("FROM SystemLog").getResultList();
return result;
}
}
And a simple data table iterating over the elements
<h:dataTable value="#{systemLogNormal.resultList}" var="sys">
<h:column>
<f:facet name="header">
description
</f:facet>
#{sys.description}
</h:column>
</h:dataTable>
Now at first glance, I think the normal seam component is much cleaner and easier to read. Remember, this is a very simple example. In a normal real life example you would have extended the component to be much more complicated, and if you want to do this using the existing framework, you will need to find out what the framework already is doing, and correctly override those methods. Otherwise, you risk of doing the same thing twice.
If you ask your self what the @MeasureCalls annotation is doing, then have a look here, and scroll down to the second example. But a short description is that it measures the time of execution for a method.
When you run this example using the SystemLogNormal and the Query component, they are pretty much equal. However, you will see that the Query component is also calling the validate() method, which takes a few milliseconds to execute.
It is correct that a few milliseconds is hardly anything noteworthy, however this is a very very simple example, with very little data, and in a more realistic scenario, the number could possibly be different. Either way, the overall time difference is that the EntityQuery object is a few milliseconds slower.
13:58:24,224 WARN [SystemLogQuery] Inside systemlog query getResultList
13:58:24,228 INFO [STDOUT] Hibernate:
select
systemlog0_.id as id0_,
systemlog0_.category as category0_,
systemlog0_.date as date0_,
systemlog0_.description as descript4_0_,
systemlog0_.details as details0_,
systemlog0_.ipAddress as ipAddress0_,
systemlog0_.organization as organiza7_0_,
systemlog0_.severity as severity0_,
systemlog0_.username as username0_
from
SystemLog systemlog0_
13:58:24,239 WARN [SystemLogQuery] Inside systemlog query getResultList
13:58:24,257 WARN [SystemLogQuery] Inside systemlog query getResultList
13:58:24,334 INFO [TimingFilter] 2.201607 ms 1 EntityQuery.validate()
13:58:24,334 INFO [TimingFilter] 15.967185 ms 3 SystemLogQuery.getResultList()
13:58:31,647 WARN [SystemLogNormal] Inside systemLog normal resultList
13:58:31,648 INFO [STDOUT] Hibernate:
select
systemlog0_.id as id0_,
systemlog0_.category as category0_,
systemlog0_.date as date0_,
systemlog0_.description as descript4_0_,
systemlog0_.details as details0_,
systemlog0_.ipAddress as ipAddress0_,
systemlog0_.organization as organiza7_0_,
systemlog0_.severity as severity0_,
systemlog0_.username as username0_
from
SystemLog systemlog0_
13:58:31,662 WARN [SystemLogNormal] Inside systemLog normal resultList
13:58:31,678 WARN [SystemLogNormal] Inside systemLog normal resultList
13:58:31,712 INFO [TimingFilter] 15.740409 ms 3 SystemLogNormal.getResultList()
As you can see, we get three outputs of "Inside systemlog". Really there should only be one. So adding a @Factory annotation on top of it will do the trick for both versions.
Then there are the Home and Query objects, with methods like isIdDefined(), clearDirty(), isManaged(), getEjbql(), RESTRICTIONS and more confusing methods which you need to read the documentation to understand.
Conclusion
In my opinion, for first time users who are learning Seam, it is more than enough to understand and learn the framework, instead of in addition learning "a framework inside a framework". The seam-gen utility is sort of a devil in disguise. Its great for a shortcut to get started, but its evil in disguise for new comers.
Newbies think Seam Application Framework is Seam, and all components they create must extends the Home, List or Query objects. Its confusing, and in my opinion they should avoid using it unless they know all the ins and out of both Seam and the application framework.
So if you are new to Seam, start by looking at the Seam examples that comes with the bundle. They are far better to use, than generating the UI with seam-gen and going from there.
Labels:
Seam
Friday, September 17, 2010
Devoxx versus JavaOne
Devoxx or JavaOne?
Thats the question...
It's really not that difficult to choose.
If you are based in Europe (as our company is), then you will for sure get more value for your money attending Devoxx instead of JavaOne.
However, if you look at a technical perspective, then still Devoxx comes on top in my opinion. The opening talk is by Mark Reinhold and one of the last talks, "Java state of the Union" is by no other than James Gosling. (I don't even need to link to him, every Java developer should know who he is), and there are tons of fameous speakers: Brian Goetz, The JavaPosse, Heinz Kabutz, Richard Bair and Roberto Chinnici just to name a few. (No pun intented for the others I didn't mention).
So it shouldn't come as a big surprise that I am also attending Devoxx. If you are going, lemme know and we can hook up!
Thats the question...
It's really not that difficult to choose.
If you are based in Europe (as our company is), then you will for sure get more value for your money attending Devoxx instead of JavaOne.
However, if you look at a technical perspective, then still Devoxx comes on top in my opinion. The opening talk is by Mark Reinhold and one of the last talks, "Java state of the Union" is by no other than James Gosling. (I don't even need to link to him, every Java developer should know who he is), and there are tons of fameous speakers: Brian Goetz, The JavaPosse, Heinz Kabutz, Richard Bair and Roberto Chinnici just to name a few. (No pun intented for the others I didn't mention).
So it shouldn't come as a big surprise that I am also attending Devoxx. If you are going, lemme know and we can hook up!
Sunday, September 12, 2010
Java 7, yet another delay
Mark Reinhold has published a blog stating what has been painfully obvious to everyone following the JDK 7 development: It will yet again be delayed until mid 2012(!)
Mark is further saying that there is an alternative which they are considering, and that "is to take everything we have now, test and stabilize it, and ship that as JDK 7. We could then finish Lambda, Jigsaw, the rest of Coin, and maybe a few additional key features in a JDK 8 release which would ship fairly soon thereafter."
I couldn't agree more. The community has waited too long for Java 7 to come out. There are so many problems in the current Java version, that makes people look around for alternatives in the Java Virtual Machine.
I am certain that if Java 7 will be delayed for yet two more years, then most people by that time will move to other languages such as Scala and Grails, which doesn't have the problems Java has today.
So, to sum up. Oracle has my vote to ship whatever they have now, and then come with the rest of it with JDK 8.
Mark is further saying that there is an alternative which they are considering, and that "is to take everything we have now, test and stabilize it, and ship that as JDK 7. We could then finish Lambda, Jigsaw, the rest of Coin, and maybe a few additional key features in a JDK 8 release which would ship fairly soon thereafter."
I couldn't agree more. The community has waited too long for Java 7 to come out. There are so many problems in the current Java version, that makes people look around for alternatives in the Java Virtual Machine.
I am certain that if Java 7 will be delayed for yet two more years, then most people by that time will move to other languages such as Scala and Grails, which doesn't have the problems Java has today.
So, to sum up. Oracle has my vote to ship whatever they have now, and then come with the rest of it with JDK 8.
Monday, August 2, 2010
Migrating from JODConverter 2 to JODConverter 3 and converting PDF to PDF/A
In the previous posting I showed you how you could automate conversions of documents to PDF & PDF/A using JODConverter 2.
JODConverter 3.0.beta has been out for some time, and even though it is still beta, it is very stable. Maybe even more stable than JODConverter 2.
In this blog posting I will highlight the benefits of JODConverter 3 compared to its predecessor and show you how you can modify your code to create PDF/A documents with JODConverter 3.
To be able to convert an existing PDF document to PDF/A in OpenOffice.org, you will need to install Sun PDF Import extension!
JODConverter 2 versus 3
JODConverter 3 still uses OpenOffice.org to perform its conversion. It is still a wrapper to the OOo API. It is only a complete rewrite of the JODConverter core library which is much cleaner and easier to use.
Whats new?
I am sure there will be more features when JODConverter 3 goes final.
Configuration
All you need to do do is point your OpenOffice.org installation to the OfficeManager, and you are good to go.
This manager will use the default settings for Task Queue Timeout, Task Execution Timeout, Port Number etc but you can easily change them
If you want to utilize piping (Recommended is one process per CPU-core), you will need to set VM argument and point java.library.path to the location of $URE_LIB which on my Ubuntu machine is /usr/lib/ure/lib/
For instance:
And then you can change your OfficeManager.
ConverterService3Impl
The following codes performs all the converting. It supports a File or byte[] as input.
This is how you use it:
Lets say you have a PDF file as byte[], and you want to convert this byte to PDF/A as byte.
All you would have to do is call method:
Similarly, if you have a Document (say a OpenOffice.org writer document) and you want to convert this to PDF you would call the method:
Note that you will always get a PDF/A compliant pdf. All you need to do is change the extension from ".pdf" to ".html" and the converter would do the magic.
Here is the source. Please read the comments in the source code if you want to understand it, or just ask in the comment section below.
JODConverter 3.0.beta has been out for some time, and even though it is still beta, it is very stable. Maybe even more stable than JODConverter 2.
In this blog posting I will highlight the benefits of JODConverter 3 compared to its predecessor and show you how you can modify your code to create PDF/A documents with JODConverter 3.
To be able to convert an existing PDF document to PDF/A in OpenOffice.org, you will need to install Sun PDF Import extension!
JODConverter 2 versus 3
JODConverter 3 still uses OpenOffice.org to perform its conversion. It is still a wrapper to the OOo API. It is only a complete rewrite of the JODConverter core library which is much cleaner and easier to use.
Whats new?
- No more init script(!)
- You don't have to manually start OpenOffice.org as a service anymore. This will be handled automatic.
- You can even create multiple processes which is useful for multi-core CPU's. Best practise is one process for each CPU core.
- Automatically restart an OOo instance if it crashes.
- If for some reason your process crashes, JODConverter will detect this, and restart the process automatic. This was a hassle with JODConverter 2, as you needed to manually do this in Linux.
- Abort conversions that take too long (according to a configurable timeout parameter)
- Automatically restart an OOo instance after n conversions (workaround for OOo memory leaks)
I am sure there will be more features when JODConverter 3 goes final.
Configuration
All you need to do do is point your OpenOffice.org installation to the OfficeManager, and you are good to go.
OfficeManager officeManager = new DefaultOfficeManagerConfiguration()
.setOfficeHome("/usr/lib/openoffice")
.buildOfficeManager().start();
This manager will use the default settings for Task Queue Timeout, Task Execution Timeout, Port Number etc but you can easily change them
OfficeManager officeManager = new DefaultOfficeManagerConfiguration()
.setOfficeHome("/usr/lib/openoffice")
.setTaskExecutionTimeout(240000L)
.setTaskQueueTimeout(60000L)
.buildOfficeManager().start();
If you want to utilize piping (Recommended is one process per CPU-core), you will need to set VM argument and point java.library.path to the location of $URE_LIB which on my Ubuntu machine is /usr/lib/ure/lib/
For instance:
-Djava.library.path="/usr/lib/ure/lib"
And then you can change your OfficeManager.
OfficeManager officeManager = new DefaultOfficeManagerConfiguration()
.setOfficeHome("/usr/lib/openoffice")
.setConnectionProtocol(OfficeConnectionProtocol.PIPE)
.setPipeNames("office1","office2") //two pipes
.setTaskExecutionTimeout(240000L) //4 minutes
.setTaskQueueTimeout(60000L) // 1 minute
.buildOfficeManager().start();
ConverterService3Impl
The following codes performs all the converting. It supports a File or byte[] as input.
This is how you use it:
Lets say you have a PDF file as byte[], and you want to convert this byte to PDF/A as byte.
All you would have to do is call method:
byte[] pdfa = converterService.convertToPDFA(pdfFile);
Similarly, if you have a Document (say a OpenOffice.org writer document) and you want to convert this to PDF you would call the method:
File doc = new File("myDocument.odt");
File pdfDocument = converterService.convert(doc, ".pdf");
Note that you will always get a PDF/A compliant pdf. All you need to do is change the extension from ".pdf" to ".html" and the converter would do the magic.
Here is the source. Please read the comments in the source code if you want to understand it, or just ask in the comment section below.
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ConnectException;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.ejb.Local;
import javax.ejb.Stateless;
import lombok.Cleanup;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.artofsolving.jodconverter.OfficeDocumentConverter;
import org.artofsolving.jodconverter.document.DefaultDocumentFormatRegistry;
import org.artofsolving.jodconverter.document.DocumentFamily;
import org.artofsolving.jodconverter.document.DocumentFormat;
import org.artofsolving.jodconverter.document.DocumentFormatRegistry;
/**
* This service converts files from one thing to another ie ODT to PDF, DOC to ODT etc
* @author Shervin Asgari
*
*/
@Stateless
@Local(ConverterService.class)
public class ConverterService3Impl implements ConverterService {
private static final String PDF_EXTENSION = ".pdf";
private static final String PDF = "pdf";
// Uncomment these when we want to use them
// private final int PDFXNONE = 0;
private final int PDFX1A2001 = 1;
// private final int PDFX32002 = 2;
// private final int PDFA1A = 3;
// private final int PDFA1B = 4;
@Logger //Your favourite logger (ie Log4J) could be injected here
private Log log;
public File convert(File inputFile, String extension) throws IOException, ConnectException {
if (inputFile == null) {
throw new IOException("The document to be converted is null");
}
Pattern p = Pattern.compile("^.?pdf$", Pattern.CASE_INSENSITIVE);
Matcher m = p.matcher(extension);
OfficeDocumentConverter converter;
//If inputfile is a PDF you will need to use another FormatRegistery, namely DRAWING
if(FilenameUtils.isExtension(inputFile.getName(), PDF) && m.find()) {
DocumentFormatRegistry formatRegistry = new DefaultDocumentFormatRegistry();
formatRegistry.getFormatByExtension(PDF).setInputFamily(DocumentFamily.DRAWING);
converter = new OfficeDocumentConverter(officeManager, formatRegistry);
} else {
converter = new OfficeDocumentConverter(officeManager);
}
String inputExtension = FilenameUtils.getExtension(inputFile.getName());
File outputFile = File.createTempFile(FilenameUtils.getBaseName(inputFile.getName()), extension);
try {
long startTime = System.currentTimeMillis();
//If both input and output file is PDF
if (FilenameUtils.isExtension(inputFile.getName(), PDF) && m.matches()) {
//We need to add the DocumentFormat with DRAW
converter.convert(inputFile, outputFile, toFormatPDFA_DRAW());
} else if(FilenameUtils.isExtension(outputFile.getName(), PDF)) {
converter.convert(inputFile, outputFile, toFormatPDFA());
} else {
converter.convert(inputFile, outputFile);
}
long conversionTime = System.currentTimeMillis() - startTime;
log.info(String.format("successful conversion: %s [%db] to %s in %dms", inputExtension, inputFile.length(), extension, conversionTime));
return outputFile;
} catch (Exception exception) {
log.error(String.format("failed conversion: %s [%db] to %s; %s; input file: %s", inputExtension, inputFile.length(), extension, exception, inputFile.getName()));
exception.printStackTrace();
throw new IOException("Converting failed");
} finally {
//outputFile.deleteOnExit();
//inputFile.deleteOnExit();
}
}
/**
* Convert pdf file to pdf/a
* You will need to install OpenOffice extension (pdf viewer) to get it working
* @param pdf
* @return Byte array
* @throws IOException
*/
public byte[] convertToPDFA(byte[] pdfByte) throws IOException, ConnectException {
@Cleanup InputStream is = new ByteArrayInputStream(pdfByte);
File pdf = createFile(is, PDF_EXTENSION);
log.debug("PDF is: #0 #1", pdf.getName(), pdf.isFile());
return convert(pdf);
}
private byte[] convert(File pdf) throws IOException {
if (pdf == null) {
throw new IOException("The document to be converted is null");
}
File convertedPdfA = convert(pdf, PDF_EXTENSION);
@Cleanup final InputStream inputStream = new BufferedInputStream(new FileInputStream(convertedPdfA));
byte[] pdfa = IOUtils.toByteArray(inputStream);
return pdfa;
}
/**
* Creates a temp file and writes the content of InputStream to it. doesn't close input
*
* @return File
*/
private java.io.File createFile(InputStream in, String extension) throws IOException {
java.io.File f = File.createTempFile("tmpFile", extension);
@Cleanup BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(f));
IOUtils.copy(in, out);
return f;
}
/**
* This DocumentFormat must be used when converting from document (not pdf) to pdf/a
* For some reason "PDF/A-1" is called "SelectPdfVersion" internally; maybe they plan to add other PdfVersions later.
*/
private DocumentFormat toFormatPDFA() {
DocumentFormat format = new DocumentFormat("PDF/A", PDF, "application/pdf");
Map<String, Object> properties = new HashMap<String, Object>();
properties.put("FilterName", "writer_pdf_Export");
Map<String, Object> filterData = new HashMap<String, Object>();
filterData.put("SelectPdfVersion", this.PDFX1A2001);
properties.put("FilterData", filterData);
format.setStoreProperties(DocumentFamily.TEXT, properties);
return format;
}
/**
* This DocumentFormat must be used when converting from pdf to pdf/a
* For some reason "PDF/A-1" is called "SelectPdfVersion" internally; maybe they plan to add other PdfVersions later.
*/
private DocumentFormat toFormatPDFA_DRAW() {
DocumentFormat format = new DocumentFormat("PDF/A", PDF, "application/pdf");
Map<String, Object> properties = new HashMap<String, Object>();
properties.put("FilterName", "draw_pdf_Export");
Map<String, Object> filterData = new HashMap<String, Object>();
filterData.put("SelectPdfVersion", this.PDFX1A2001);
properties.put("FilterData", filterData);
format.setStoreProperties(DocumentFamily.DRAWING, properties);
return format;
}
}
Remember to close the connection when your application is quit/shutdown
Labels:
jodconverter,
jodconverter 3,
PDF,
pdfa
Wednesday, May 12, 2010
Automate converting of documents to PDF & PDF/A using JODConverter 2
In this blog post I will be showing a great library for converting existing documents to PDF/A using an OpenSource library called JODConverter.
Note there is nothing that prevents you to convert to normal PDF.
JODConverter leverages OpenOffice.org, which provides arguably the best import/export filters for OpenDocument and Microsoft Office formats available today. Thus, it requires an installation of OpenOffice and it supports all documents which OpenOffice supports.
JODConverter automates all conversions supported by OpenOffice.org, including
Starting OpenOffice as a service
JODConverter needs to connect to a running OpenOffice.org instance in order to perform the document conversions. This is different from starting the OpenOffice.org program as you would normally do. OpenOffice.org can be configured to run as a service and listen for commands on a TCP port. One way of doing this is to run the following command in Linux: (You only need to change location of the soffice)
Note that you cannot open OpenOffice.org if you have this service running as headless mode.
If you are running your system on Windows, you can read here for information on how to create a service on Windows.
See the Uno/FAQ on the OpenOffice.org Wiki for more on this topic.
Command-Line Tool (cli like)
You can run JODConverter as cli (command line interface) like program.
To use it as a command line tool, you need to download the 2.2.2 distribution, unpack it, and run it using Java.
To convert a single file specify input and output files as parameters
Usage in your Java applications
Using JODConverter in your own Java application is very easy. The following example shows the skeleton code required to perform a one off conversion from a Word document to PDF:
To get convert the same document to PDF/A instead, you need to create a custom DocumentFormat that is of type PDF/A and then send that into the convert method like this:
Note that this is a very simple example. I do not recommend opening and closing connection for each conversion. You open once the application is started (or the first time you want to convert), and then close the connection when the application shuts down.
Note there is nothing that prevents you to convert to normal PDF.
JODConverter leverages OpenOffice.org, which provides arguably the best import/export filters for OpenDocument and Microsoft Office formats available today. Thus, it requires an installation of OpenOffice and it supports all documents which OpenOffice supports.
JODConverter automates all conversions supported by OpenOffice.org, including
- Microsoft Office to OpenDocument, and viceversa
- Word to OpenDocument Text (odt); OpenDocument Text (odt) to Word
- Excel to OpenDocument Spreadsheet (ods); OpenDocument Spreadsheet (ods) to Excel
- PowerPoint to OpenDocument Presentation (odp); OpenDocument Presentation (odp) to PowerPoint
- Any format to PDF
- OpenDocument (Text, Spreadsheet, Presentation) to PDF
- Word to PDF; Excel to PDF; PowerPoint to PDF
- RTF to PDF; WordPerfect to PDF; ...
- And more
- OpenDocument Presentation (odp) to Flash; PowerPoint to Flash
- RTF to OpenDocument; WordPerfect to OpenDocument
- Any format to HTML (with limitations)
- Support for OpenOffice.org 1.0 and old StarOffice formats
- ...
- As a Java library, embedded in your own Java application
- As a command line tool, possibly invoked from your own scripts
- As a simple web application: upload your input document, select the desired format and download the converted version
- As a web service, invoked from your own application written in your favourite language (.NET, PHP, Python, Ruby, ...)
Starting OpenOffice as a service
JODConverter needs to connect to a running OpenOffice.org instance in order to perform the document conversions. This is different from starting the OpenOffice.org program as you would normally do. OpenOffice.org can be configured to run as a service and listen for commands on a TCP port. One way of doing this is to run the following command in Linux: (You only need to change location of the soffice)
/usr/bin/soffice "-accept=socket,host=localhost,port=8100;urp;StarOffice.ServiceManager" -norestore -nofirststartwizard -nologo -headless &
I suggest putting this script in /etc/init.d/ so it will run automatically.Note that you cannot open OpenOffice.org if you have this service running as headless mode.
If you are running your system on Windows, you can read here for information on how to create a service on Windows.
See the Uno/FAQ on the OpenOffice.org Wiki for more on this topic.
Command-Line Tool (cli like)
You can run JODConverter as cli (command line interface) like program.
To use it as a command line tool, you need to download the 2.2.2 distribution, unpack it, and run it using Java.
To convert a single file specify input and output files as parameters
java -jar lib/jodconverter-cli-2.2.0.jar document.doc document.pdf
To convert multiple files to a given format specify the format using the -f (or --output-format) option and then pass the input files as parametersjava -jar lib/jodconverter-cli-2.2.0.jar -f pdf *.odt
Usage in your Java applications
Using JODConverter in your own Java application is very easy. The following example shows the skeleton code required to perform a one off conversion from a Word document to PDF:
File inputFile = new File("document.doc");
File outputFile = new File("document.pdf");
// connect to an OpenOffice.org instance running on port 8100
OpenOfficeConnection connection = new SocketOpenOfficeConnection(8100);
connection.connect();
// convert
DocumentConverter converter = new OpenOfficeDocumentConverter(connection);
converter.convert(inputFile, outputFile);
// close the connection
connection.disconnect();
To get convert the same document to PDF/A instead, you need to create a custom DocumentFormat that is of type PDF/A and then send that into the convert method like this:
/**
* Returns DocumentFormat of PDF/A
*/
private DocumentFormat toDocumentFormatPDFA() {
//These are the different PDF version's you can get. 1 is the default PDF/A
final int PDFXNONE = 0;
final int PDFX1A2001 = 1;
final int PDFX32002 = 2;
final int PDFA1A = 3;
final int PDFA1B = 4;
// create a PDF DocumentFormat (as normally configured in document-formats.xml)
DocumentFormat customPdfFormat = new DocumentFormat(PORTABEL_FORMAT, PDF_APP, "pdf");
//now set our custom options
customPdfFormat.setExportFilter(DocumentFamily.TEXT, "writer_pdf_Export");
/*
* For some reason "PDF/A-1" is called "SelectPdfVersion" internally; maybe they plan to add other
* PdfVersions later.
*/
final Map<String, Integer> pdfOptions = new HashMap<String, Integer>();
pdfOptions.put("SelectPdfVersion", PDFX1A2001);
customPdfFormat.setExportOption(DocumentFamily.TEXT, "FilterData", pdfOptions);
return customPdfFormat;
}
And then you call the convert method with toDocumentFormatPDFA() as parameter.converter.convert(inputFile, outputFile, toDocumentFormatPDFA());
Note that this is a very simple example. I do not recommend opening and closing connection for each conversion. You open once the application is started (or the first time you want to convert), and then close the connection when the application shuts down.
Labels:
jodconverter,
PDF,
pdfa
Monday, April 5, 2010
Advanced Seam series part 3 of 3: Asynchronous mail sending
Advanced Seam series part 3 of 3: Asynchronous mail sending
In this last series I will be showing how easy you can set up your seam environment to handle asynchronous mail sending. You can even raise other events asynchronously.
Seam makes it very easy to perform work asynchronously from a web request.
Seam layers a simple asynchronous method and event facility over your choice of dispatchers:
Asynchronous configuration
The default dispatcher, based upon a
If you have a task that is not critical that it must be performed, ie a clean up task, or something trivial, then it is no reason not to use the default dispatcher.
However, if you want the guarantee that the task is called you might use the Timer Service.
If you're working in an environment that supports EJB 3.0, you can add the following line to
Finally, your third is to use an Open Source alternative called Quartz (recently acquired by Terracotta). To use Quartz, you will need to bundle the Quartz library JAR (found in the
It is up to the reader to choose whichever asynchronous strategy they see fit.
Sending emails Asynchronously
It is really very easy sending a plain email with Seam. All you have to do is use Seam mail, annotate the method and interface (if you are using EJB's) with @Asynchronous and its done. However, the tricky part is using EL expressions and variables stored in the different Contexts, so that you can produce dynamic emails.
There are two ways you can call a task asynchronously. You can either send an asynchronous event, or as I previously explained, annotate a method with @Asynchronous, and call it normally, ie:
The key point to note is the
In this last series I will be showing how easy you can set up your seam environment to handle asynchronous mail sending. You can even raise other events asynchronously.
Seam makes it very easy to perform work asynchronously from a web request.
Seam layers a simple asynchronous method and event facility over your choice of dispatchers:
java.util.concurrent.ScheduledThreadPoolExecutor
(by default)- The EJB timer service (for EJB 3.0 environments)
- Quartz
The default dispatcher, based upon a
ScheduledThreadPoolExecutor
performs efficiently but does not guarantee that the task will ever actually be executed. This is because it does not have a persistence state, meaning it only stores the events in memory. If your application server goes down in between the calls, they will not run.If you have a task that is not critical that it must be performed, ie a clean up task, or something trivial, then it is no reason not to use the default dispatcher.
However, if you want the guarantee that the task is called you might use the Timer Service.
If you're working in an environment that supports EJB 3.0, you can add the following line to
components.xml
:<async:timer-service-dispatcher/>
Then your asynchronous tasks will be processed by the container's EJB timer service. The Timer Service implementation is persistence based, thus you will get some guarantee that the tasks will eventually run.Finally, your third is to use an Open Source alternative called Quartz (recently acquired by Terracotta). To use Quartz, you will need to bundle the Quartz library JAR (found in the
lib
directory) in your EAR and declare it as a Java module in application.xml
.The Quartz dispatcher may be configured by adding a Quartz property file to the classpath. It must be named seam.quartz.properties
.In addition, you need to add the following line to components.xml
to install the Quartz dispatcher.<async:quartz-dispatcher/>
Note that Quartz uses RAMJobStore as default, thus it is not persistence based on default. You will need to configure it to use persistence base. It is up to the reader to choose whichever asynchronous strategy they see fit.
Sending emails Asynchronously
It is really very easy sending a plain email with Seam. All you have to do is use Seam mail, annotate the method and interface (if you are using EJB's) with @Asynchronous and its done. However, the tricky part is using EL expressions and variables stored in the different Contexts, so that you can produce dynamic emails.
There are two ways you can call a task asynchronously. You can either send an asynchronous event, or as I previously explained, annotate a method with @Asynchronous, and call it normally, ie:
Events.instance().raiseAsynchronousEvent("sendOneTimepassword", user, theOnetimepass);
//Or the normal way
@In MailService mailService;
mailService.sendSupport(sender,supportEmail,supportMessage);
Lets say you have the following Stateless EJB that is responsible for sending emails:One of the methodsimport javax.ejb.Local; import javax.ejb.Stateless; import org.jboss.seam.annotations.AutoCreate; import org.jboss.seam.annotations.In; import org.jboss.seam.annotations.Name; import org.jboss.seam.annotations.Observer; import org.jboss.seam.annotations.async.Asynchronous; import org.jboss.seam.contexts.Contexts; import org.jboss.seam.faces.Renderer; /** * This service is responsible for sending emails asynchronously * @author Shervin Asgari * */ @Stateless @Local(MailService.class) @Name("mailService") @AutoCreate public class MailServiceImpl implements MailService { @In(create = true) private Renderer renderer; @Asynchronous public void sendSupport(User sender, String supportEmail, String supportMessage) { Contexts.getEventContext().set("sender", sender); Contexts.get
Event
Context().set("supportEmail", supportEmail); Contexts.get
Event
Context().set("supportMessage", supportMessage); renderer.render("/generic/email-template/support-email-template.xhtml"); } @Observer("sendOneTimepassword") //@Asynchronous, we dont need to annotate with @Asynchronous if we are raising the event asynchronously public void sendOneTimepassword(User emailUser, String oneTimePassword) { Contexts.get
Event
Context().set("emailUser", emailUser); Contexts.get
Event
Context().set("oneTimePassword", oneTimePassword); renderer.render("/generic/email-template/onetimepassword-email-template.xhtml"); } }
sendSupport()
is responsible for sending support messages as email, whilst the other method sendOneTimepassword
is used to send generated one time passwords to a user so that they can authenticate to the system. Have you look here for information on how you can set up your system to do exactly that.The key point to note is the
Contexts.get
Event
Context().set("emailUser", emailUser);
. If I would have normally injected the user in the EmailService, it would not work. Since the call is made asynchronously we need to set the variables in the asynchronous context. Thus we can now get hold of them in the email template:<m:message
xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:m="http://jboss.com/products/seam/mail"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html">
<m:from name="Someone">no-reply@someplace.com</m:from>
<m:to name="#{emailUser.name}" address="#{emailUser.fromEmail}" />
<m:subject>#{messages['mail.onetime.heading']}</m:subject>
<m:body>
Your one time password is: <i>#{oneTimePassword}</i>
<p>#{messages['mail.onetime.noreply']}</p>
</m:body>
</m:message>
Thats really all there is to it! Now your emails will be sent asynchronously.
Labels:
Seam 2,
Seam Series
Friday, March 26, 2010
Advanced Seam series part 2 of 3: Seam Filters
Seam Filters.
In this blog post I will be showing two filters. One that hacks in UTF-8 as encoding, and the other, a download filter for downloading files efficiently.
Seam filters can potentially be called outside of the Seam context, thus they are not truly a Seam component, but they act as one. However, they are able to reuse the functionality for component scanning, installation and configuration for filters.
There are two ways to define filters.
1. Through web.xml
And the Filter it self:
This filter is a fix for a bug in Seam encoding filter. More information here.
Remember you must implement Filter.
In this next example I will show an efficient and pretty safe download filter which you can use to serve files to the user. First I want to present the way you shouldn't do it.
Say you have a File entity class.
But to save you some time, here is the method:
A much better way is to use a filter.
All you need to do is create a link that has the downloadFileId as parameter and the filter will activate, like this http://somedomain.com/myApp/somePage.seam?downloadFileId=1-BAC272728189887A672
In this blog post I will be showing two filters. One that hacks in UTF-8 as encoding, and the other, a download filter for downloading files efficiently.
Seam filters can potentially be called outside of the Seam context, thus they are not truly a Seam component, but they act as one. However, they are able to reuse the functionality for component scanning, installation and configuration for filters.
There are two ways to define filters.
1. Through web.xml
<filter>
<filter-name>UTF8 Filter</filter-name>
<filter-class>com.something.filter.UTF8Filter</filter-class>
</filter>
<filter-mapping>
<filter-name>UTF8 Filter</filter-name>
<url-pattern>*.seam</url-pattern>
</filter-mapping>
And the Filter it self:
This filter is a fix for a bug in Seam encoding filter. More information here.
Remember you must implement Filter.
/**
* This Filter is a fix for bug in seam encoding filter, see here for more details: https://jira.jboss.org/jira/browse/JBSEAM-3006
* It might be removed if the version will be upgraded to at least 2.2.1.CR1
*
*/
public class UTF8Filter implements Filter {
public void destroy() {}
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
// set encoding to UTF-8
req.setCharacterEncoding("UTF-8");
chain.doFilter(req, res);
return;
}
public void init(FilterConfig arg0) throws ServletException {}
}
However, the servlet specification doesn't provide a well defined order if you specify your filters in a web.xml
, however Seam provides a way to do that by using the @Filter annotation.In this next example I will show an efficient and pretty safe download filter which you can use to serve files to the user. First I want to present the way you shouldn't do it.
Say you have a File entity class.
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Lob;
import javax.persistence.ManyToOne;
import javax.persistence.PrePersist;
import javax.persistence.PreUpdate;
import javax.persistence.Transient;
import org.hibernate.validator.NotNull;
@Entity
public class File {
@Id
@GeneratedValue
private Long id;
@NotNull
@Column(nullable = false, length = 256)
private String name;
@Column
private String hash;
// BLOB = L + 2 bytes (max size is 2^16 - 1 or 65,535 bytes, 65KB)
// MEDIUMBLOB = L + 3 bytes (max size is 2^24 - 1 or 16,777,215 bytes, 16MB)
// LONGBLOB = L + 4 bytes (max size is 2^32 - 1 or 4,294,967,295 bytes, 4GB)
@Basic(fetch = FetchType.LAZY)
// @Basic is used in conjunction with @Lob
@Lob
// Set to MAX 100MB LONGBLOB in MySQL
@Column(length = 104857600, nullable = false)
private byte[] data;
@PrePersist
@PreUpdate
public void setHash() {
try {
hash = PasswordSupport.generateFileHash(data);
} catch (GeneralSecurityException e) {
e.printStackTrace();
hash = null;
}
}
@Transient
public InputStream getInputStream() {
return new ByteArrayInputStream(data);
}
//getters and setters
}
For the PasswordSupport class (which basically hashes the input), have a look at my previous post. We always want to generate a new hash if a new file appears, thus the triggers @PreUpdate and @PrePersistBut to save you some time, here is the method:
/**
* Hash file
* @throws GeneralSecurityException
*/
public static final String generateFileHash(byte[] data) throws GeneralSecurityException {
byte[] salt = new byte[1024];
String theHash;
try {
AnnotatedBeanProperty<UserPassword> userPasswordProperty = new AnnotatedBeanProperty<UserPassword>(ProcessUser.class, UserPassword.class);
// Will get the hash value from annotation UserPassword in User.class
PasswordHash.instance().setHashAlgorithm(userPasswordProperty.getAnnotation().hash().toUpperCase());
theHash = PasswordHash.instance().createPasswordKey(String.valueOf(Arrays.hashCode(data)).toCharArray(), salt, userPasswordProperty.getAnnotation().iterations());
return theHash;
} finally {
salt = null;
theHash = null;
}
}
Typically, you would do the following to serve the user a file:import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.FileNameMap;
import java.net.URLConnection;
import javax.annotation.PostConstruct;
import javax.ejb.Local;
import javax.ejb.Stateless;
import javax.faces.context.FacesContext;
import javax.persistence.EntityManager;
import javax.persistence.EntityNotFoundException;
import javax.servlet.http.HttpServletResponse;
import org.jboss.seam.annotations.AutoCreate;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Name;
@Name("fileService")
@AutoCreate
@Stateless
@Local(FileService.class)
public class FileServiceImpl implements FileService {
@In
private EntityManager entityManager;
private FileNameMap fileNameMap;
@PostConstruct
public void contruct() {
fileNameMap = URLConnection.getFileNameMap();
}
@Deprecated
public void download(File file) throws IOException {
byte[] data = file.getData();
HttpServletResponse response = (HttpServletResponse) FacesContext.getCurrentInstance().getExternalContext().getResponse();
//Take mime type from filename, I don't know if the null check is necessary, the API doesn't say.
String mime = fileNameMap.getContentTypeFor(file.getName());
if (mime == null || "".equals(mime.trim())) {
mime = "application/octet-stream";
}
response.setContentType(mime);
response.addHeader("Content-Disposition", "attachment;filename=" + file.getName());
response.setContentLength(data.length);
OutputStream writer = response.getOutputStream();
writer.write(data);
writer.flush();
writer.close();
//Skip the rest of JSF phases
FacesContext.getCurrentInstance().responseComplete();
}
//rest of ejb not shown
}
This method of doing it is bad because it will load the file in to memory and serve it to the user. Not to mention all the Seam interceptors along the way.A much better way is to use a filter.
All you need to do is create a link that has the downloadFileId as parameter and the filter will activate, like this http://somedomain.com/myApp/somePage.seam?downloadFileId=1-BAC272728189887A672
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
import javax.faces.context.FacesContext;
import javax.persistence.EntityManager;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.mydomain.model.File;
import org.jboss.seam.Component;
import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.annotations.Startup;
import org.jboss.seam.annotations.intercept.BypassInterceptors;
import org.jboss.seam.annotations.web.Filter;
import org.jboss.seam.contexts.Lifecycle;
import org.jboss.seam.web.AbstractFilter;
/**
* Implemented from suggestion on
* {@link http://seamframework.org/Community/LargeFileDownload}
* @author Shervin Asgari
*
*/
@Name("downloadFilter")
@Filter(around = { "org.jboss.seam.web.ajax4jsfFilter" })
@Scope(ScopeType.APPLICATION)
@Startup
@BypassInterceptors
public class DownloadFilter extends AbstractFilter {
public void doFilter(ServletRequest request, ServletResponse resp, FilterChain arg2) throws IOException, ServletException {
if (HttpServletRequest.class.isAssignableFrom(request.getClass())) {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse response = (HttpServletResponse) resp;
String downloadFileId = req.getParameter("downloadFileId");
//The param is seperated with the id of the file and the hash eg http://localhost:8080/myapp/somePage.xhtml/?downloadFileId=1-8B94B45466C738FE90EF9CA8D47281EAE821F4E0
//The two ifs could be one, but for debugging and breakpoints, its easier this way
if (downloadFileId != null && downloadFileId.matches("^\\d+-.*$")) {
boolean started = false;
try {
String[] ids = downloadFileId.split("-",0);
if(ids.length >= 1) {
String fileId = ids[0];
String hash = ids.length >= 2 ? ids[1] : null;
started = true;
Lifecycle.beginCall();
File file = null;
EntityManager entityManager = (EntityManager) Component.getInstance("entityManager");
if(hash != null) {
file = (File) entityManager.createQuery("SELECT f FROM " + File.class.getName() + " f " +
"WHERE f.id=:id and f.hash=:hash").setMaxResults(1).setParameter("id", Long.valueOf(fileId)).setParameter("hash", hash).getSingleResult();
} else {
//This is here for backward compatibility
file = (File) entityManager.createQuery("SELECT f FROM " + File.class.getName() + " f " +
"WHERE f.id=:id and f IS NULL").setMaxResults(1).setParameter("id", Long.valueOf(fileId)).getSingleResult();
}
if(file != null) {
FacesContext facesContext = FacesContext.getCurrentInstance();
InputStream stream = file.getInputStream();
response.setContentType("application/octet-stream");
response.setContentLength(stream.available());
response.setCharacterEncoding("UTF-8");
response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(file.getName(), "UTF-8"));
OutputStream out = response.getOutputStream();
try {
byte[] buf = new byte[response.getBufferSize()];
int len;
while ((len = stream.read(buf)) > 0) {
out.write(buf, 0, len);
}
} finally {
out.close();
}
if(facesContext != null)
facesContext.responseComplete();
}
}
} catch (Exception e) {
e.printStackTrace();
resp.getOutputStream().write(String.valueOf("Error downloading file" + e.getMessage()).getBytes());
}
finally {
if(started)
Lifecycle.endCall();
else
arg2.doFilter(req, response);
}
} else {
arg2.doFilter(req, response);
}
}
}
}
This way you will not load the file into memory, and avoid all the interceptors of Seam, thus it will perform much faster and better. Note that we have included a backward compatibility in case the file entity does not have a hash yet.
Labels:
filters,
Seam 2,
Seam Series
Saturday, February 13, 2010
Advanced Seam series part 1 of 3: Seam Interceptors
Seam Interceptors
EJB 3.0 introduced a standard interceptor model for session bean components. Interceptors are a great way of performing extra functionality. I will not go into detail of what Interceptors are. If you want to know more, google is your friend.
Prerequisites you should know:
First example
Imagine this simple model. A user is connected to one organization, and only the user connected to that Organization is allowed to view information about that organization.
Let's say you print out the users organization, and you have a page with detailed information about that organization like this:
In the request, something like this will appear:
And this is how your simple OrganizationAdmin.java component looks like:
Now, imagine changing the organizationId in the request to show information about an organization you should initially not be allowed to see.
This is obviously very bad. We have restricted the component to role user, but another user can easily change the request to another id, and get hold of another organization.
To fix this, you can add this extra security check:
Voila! Now if an authorized user tries to "hack" the system, you will throw a SecurityException. This is fine and works just fine. However, it is cumbersome to do and you have to remember to do this everywhere where these exploits exists.
Wouldn't it be nice to just do this:
I for one think this is much easier to do. You can even reuse the @AuthorizeOrganization annotation to apply the same security other places. You only need to write the logic once.
Our first Interceptor
We can do this by using @Interceptor or @Interceptors. The latter is pre Java EE 5 compatible. I will cover both, but will show the latter in this example.
We start by creating our two annotations.
@AuthorizeOrganization
Note the @Interceptors(MyInterceptor.class). It is here we register this annotation to the Interceptor MyInterceptor.
AnnotationHandler.java
Note that this is just an interface that all our custom annotations will inherit from
BasicHandler.java
An abstract class all handler will extend from. Read javadoc and comments in code to get real understanding of what's going on. Much of this code is really not necessary for this simple example. But this is a good portion of the one we are using, and it is quite flexible.
@AuthorizeOrganizationHandler.java
Last but not least, our interceptor.
MyInterceptor.java
Thats it! Now you can easily add new annotation and handlers and just add login in BasicHandler. If you look closely you will see that we automatically give all privileges to admin, but only check user's organization if they are role user. You can modify and do whatever fits your need.
Second Example
Finally I want to also mention the other Seam interceptor.
This is taken directly from the Seam forum (Link here). Which will print something like this:
The first column contains the accumulated time spent in this method. The second column contains the number of times the method has been called, and the final column shows which method was called.
This Interceptor will measure how long each method will take to execute in milliseconds.
TimingInterceptor.java
TimedInvocation.java
Lastly, we will create a simple filter that will print out the result. The filter is only installed in debug mode so that it doesn't run on production. Be aware that the interceptor will still run if you don't forget to uncomment the interceptor.
TimingFilter.java
@MeasureCalls
Just put @MeasureCalls on your component and all methods on that component will be invoked on.
EJB 3.0 introduced a standard interceptor model for session bean components. Interceptors are a great way of performing extra functionality. I will not go into detail of what Interceptors are. If you want to know more, google is your friend.
Prerequisites you should know:
- Seam has a few built in Interceptors, these include but are not limited to:
- BijectionInterceptor
- MethodContextInterceptor
- ConversationInterceptor
- SynchronizationInterceptor
- ConversationalInterceptor
- RemoveInterceptor
- SeamInterceptor
- SecurityInterceptor
- TransactionInterceptor
- EventInterceptor
- HibernateSessionProxyInterceptor
- @BypassInterceptors will skip Interceptors
First example
Imagine this simple model. A user is connected to one organization, and only the user connected to that Organization is allowed to view information about that organization.
Let's say you print out the users organization, and you have a page with detailed information about that organization like this:
<s:link view="myOrganization.xhtml" value="#{organizationAction.choose}">
<f:param name="id" value="#{organization.id}"/>
</s:link>
In the request, something like this will appear:
http://mydomain.com/myOrganization.xhtml?actionMethod=myOrganization.xhtml%3organizationAction%choose()&organizationId=3
And this is how your simple OrganizationAdmin.java component looks like:
@Name("organizationAdmin")
@Scope(ScopeType.CONVERSATION)
@Restrict("#{s:hasRole('user')}&;quot;)
public class OrganizationAction {
@RequestParameter
Long id;
@In
EntityManager entityManager;
Organization myOrganization;
@Begin
public void choose() {
if(id != null) {
this.myOrganization = (Organization) entityManager.find(Organization.class, id);
}
}
public Organization getMyOrganization() {
return this.myOrganization;
}
}
Now, imagine changing the organizationId in the request to show information about an organization you should initially not be allowed to see.
This is obviously very bad. We have restricted the component to role user, but another user can easily change the request to another id, and get hold of another organization.
To fix this, you can add this extra security check:
@Name("organizationAdmin")
@Scope(ScopeType.CONVERSATION)
@Restrict("#{s:hasRole('user')}")
public class OrganizationAction {
@RequestParameter
Long id;
@In
EntityManager entityManager;
Organization myOrganization;
@In(create = true)
User currentUser; //The current user logged in
@Begin
public void choose() {
if(id != null) {
this.myOrganization = (Organization) entityManager.find(Organization.class, id);
if(!currentUser.getOrganization().getId().equals(myOrganization.getId()))
throw new SecurityException("You are not allowed to view this page");
}
}
public Organization getMyOrganization() {
return this.myOrganization;
}
}
Voila! Now if an authorized user tries to "hack" the system, you will throw a SecurityException. This is fine and works just fine. However, it is cumbersome to do and you have to remember to do this everywhere where these exploits exists.
Wouldn't it be nice to just do this:
@Name("organizationAdmin")
@Scope(ScopeType.CONVERSATION)
@Restrict("#{s:hasRole('user')}")
@MySecurityAuthorized
public class OrganizationAction {
@RequestParameter
Long id;
@In
EntityManager entityManager;
Organization myOrganization;
@Begin
public void choose() {
if(id != null) {
this.myOrganization = (Organization) entityManager.find(Organization.class, id);
}
}
@AuthorizeOrganization
public Organization getMyOrganization() {
return this.myOrganization;
}
}
I for one think this is much easier to do. You can even reuse the @AuthorizeOrganization annotation to apply the same security other places. You only need to write the logic once.
Our first Interceptor
We can do this by using @Interceptor or @Interceptors. The latter is pre Java EE 5 compatible. I will cover both, but will show the latter in this example.
We start by creating our two annotations.
@AuthorizeOrganization
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AuthorizeOrganization {}
@MySecurityAuthorized/**
* This annotation can be added to any Seam component to hook in interception of our custom annotations.
* Important: Unless a custom specific @AuthorizeX annotation (like @AuthorizeOrganization) is also present, this annotation won't do anything
*
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@org.jboss.seam.annotations.intercept.Interceptors(MyInterceptor.class)
public @interface MySecurityAuthorized {}
Note the @Interceptors(MyInterceptor.class). It is here we register this annotation to the Interceptor MyInterceptor.
AnnotationHandler.java
Note that this is just an interface that all our custom annotations will inherit from
public interface AnnotationHandler {
public void preProceed();
public void postProceed(Object retObj);
public boolean isCurrentlyUsed();
}
BasicHandler.java
An abstract class all handler will extend from. Read javadoc and comments in code to get real understanding of what's going on. Much of this code is really not necessary for this simple example. But this is a good portion of the one we are using, and it is quite flexible.
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.interceptor.InvocationContext;
import org.jboss.seam.Component;
import org.jboss.seam.core.Events;
import org.jboss.seam.log.Logging;
/**
* Class that can be extended when creating new custom managed annotations.
* Add extending classes to MyInterceptor.
*
* @param <T> The Annotation that should be looked for
*/
public abstract class BasicHandler<T extends Annotation, U> {
private User user;
private Boolean isAdmin;
private boolean isMethodAnnotated;
private List<U> allInstanceParams = new ArrayList<U>();
private List<U> annotatedInstanceParams = new ArrayList<U>();
private List<Long> annotatedIdParams = new ArrayList<Long>();
private Class<U> authorizedClass;
/**
* This constructor parses the method signature for annotations,
* validated annotated types and
* if needed verifies the current User
* @param ic, the current InvocationContext
* @param annotationClazz, the class of the annotation to be scanned for
* @param authorizedClass, the class of the object to authorize
*/
@SuppressWarnings("unchecked")
public BasicHandler(InvocationContext ic, Class<T> annotationClazz, Class<U> authorizedClass) {
this.authorizedClass = authorizedClass;
//method annotation
Annotation annotation = ic.getMethod().getAnnotation(annotationClazz);
if (annotation != null) {
isMethodAnnotated = true;
}
//parameter annotations - why why why is there no Parameter class with methods for annotations, values, types?
Annotation[][] aaa = ic.getMethod().getParameterAnnotations();
for (int i = 0; i < aaa.length; i++) {
Object obj = ic.getParameters()[i];
if (isMethodAnnotated) {
allInstanceParams.add((U) obj);
}
Annotation[] aa = aaa[i];
for (int j = 0; j < aa.length; j++) {
Annotation a = aa[j];
if (a.annotationType().equals(annotationClazz) && ic.getParameters()[i] != null) {
//So we've found an annotated not null param
if (authorizedClass.isInstance(obj)) {
//instance of class to be authorized
annotatedInstanceParams.add((U) obj);
} else if (obj instanceof Long) {
//id of instance of class to be authorized
annotatedIdParams.add((Long) obj);
} else {
//Annotation put on erronous type
illegalObjectAnnotated((U) obj, annotationClazz);
}
//params.add(ic.getParameters()[i]);
}
}
}
//Verify user
if (isCurrentlyUsed()) {
getUser();
}
}
//public methods
public boolean isCurrentlyUsed() {
return (isMethodAnnotated || !annotatedInstanceParams.isEmpty() || !annotatedIdParams.isEmpty() ? true : false);
}
//protected methods
@SuppressWarnings("unchecked")
protected List<U> getAllReturnInstances(Object retObj) {
List<U> l = new ArrayList<U>();
if (isMethodAnnotated()) {
if (authorizedClass.isInstance(retObj)) {
//Simple object
l.add((U) retObj);
} else if (retObj instanceof Collection<?>) {
//Collections
for (Object obj : (Collection<?>) retObj) {
if (authorizedClass.isInstance(obj)) {
l.add((U) obj);
}
}
}
}
return l;
}
/**
* Authorizing Organization
* Role "admin" may access everything
* Other (like "user") must be within the organization to have privilege
* @param organization
*/
protected boolean authorize(Organization organization) {
if (organization != null) {
return authorizeUser(organization.getId().toString());
} else {
//null object need no authorization
return true;
}
}
/**
* Getting current logged in user
* @return Getting current logged in user
*/
protected User getUser() {
if (user == null) {
user = (User) Component.getInstance("currentUser", true);
if (user.getId() == null || user.getId() == 0) {
//annotation is present but we don't have an user
logAndThrow("User is null, must exsist to enable authentication");
}
}
return user;
}
/**
* Is user admin?
* @return
*/
protected boolean isAdmin() {
if (isAdmin == null) {
return isRoleInRoles(user.getRoles(), "admin");
}
return isAdmin;
}
/**
* Checks to see if the role is in the Collection<Role>
* If one of the roles appears in the Collection it returns true
*
* @param allroles - All the roles
* @param roles - varargs containing the roles to check for
*
* @return true if any of the role is found
*/
private boolean isRoleInRoles(Collection<Role> allroles, String ...roles) {
if (allroles == null || roles == null || roles.length == 0)
return false;
for (Role r : allroles)
for(String role : roles)
if (r.getName().equals(role))
return true;
return false;
}
protected boolean logAndThrow(String s) {
Logging.getLogProvider(this.getClass()).warn(s);
throw new SecurityException(s);
}
//private methods
private boolean authorizeUser(String orgId) {
User user = getUser();
if (isAdmin()) {
//Admin can do whatever
return true;
} else if (user.getOrganization().getId().equals(orgId)) {
//Correct organization, can also check role here if you want
return true;
} else {
//wrong organization, fail authorization
return logAndThrow("User '" + user.getUsername() + "' is not authorized to view this page");
}
}
private boolean isMethodAnnotated() {
return isMethodAnnotated;
}
private void illegalObjectAnnotated(U obj, Class<? extends Annotation> annotationClass) {
logAndThrow("Program error, annotation " + annotationClass.getSimpleName() + " cannot handle object of type " + obj.getClass().getSimpleName());
}
//Getters
public List<U> getAllInstanceParams() {
return allInstanceParams;
}
public List<U> getAnnotatedInstanceParams() {
return annotatedInstanceParams;
}
public List<Long> getAnnotatedIdParams() {
return annotatedIdParams;
}
}
@AuthorizeOrganizationHandler.java
/**
* This AnnotationHandler handles the @AuthorizeOrganization annotation.
* It authorizes a User for an Organization
*
*/
public class AuthorizeOrganizationHandler extends BasicHandler<AuthorizeOrganization, Organization> implements AnnotationHandler {
/**
* This constructor looks for @AuthorizeOrganization markers on method
* If any exists, User is validated directly.
* @param ic
*/
public AuthorizeOrganizationHandler(InvocationContext ic) {
super(ic, AuthorizeOrganization.class, Organization.class);
}
//Public methods
public void preProceed() {
//Method level - input parameters
for (Organization obj : getAllInstanceParams()) {
authorize(obj);
}
}
public void postProceed(Object retObj) {
//return value(s)
for (Organization obj : getAllReturnInstances(retObj)) {
authorize(obj);
}
}
}
Last but not least, our interceptor.
MyInterceptor.java
import javax.interceptor.AroundInvoke;
import javax.interceptor.InvocationContext;
/**
* This interceptor handles method/param level annotations
* (Seam only provides Class (ElementType.TYPE) level annotation support)
* Thus this interceptor must be added to those beans that wish to use these annotations
* If we implement new annotations, their corresponding AnnotationHandler must be registered below
*
*/
public class MyInterceptor {
@AroundInvoke
public Object aroundInvoke(InvocationContext ic) throws Exception {
//gather custom annotation handlers
List<AnnotationHandler> ahs = new ArrayList<AnnotationHandler>();
addHandlerIfNeeded(new AuthorizeOrganizationHandler(ic), ahs);
//add new annotation handlers here - TODO: Use more clever generics instead
//handle pre proceed
for (AnnotationHandler ah : ahs) {
ah.preProceed();
}
//proceed
Object obj = ic.proceed();
//handle post proceed
Collections.reverse(ahs); //Reversing list in order to get same behavior as interceptors/filters
for (AnnotationHandler ah : ahs) {
ah.postProceed(obj);
}
return obj;
}
private void addHandlerIfNeeded(AnnotationHandler handler, List<AnnotationHandler> ahs) {
if (handler.isCurrentlyUsed()) {
ahs.add(handler);
}
}
}
Thats it! Now you can easily add new annotation and handlers and just add login in BasicHandler. If you look closely you will see that we automatically give all privileges to admin, but only check user's organization if they are role user. You can modify and do whatever fits your need.
Second Example
Finally I want to also mention the other Seam interceptor.
This is taken directly from the Seam forum (Link here). Which will print something like this:
284.94 ms 1 FooBean.getRandomDroplets() 284.56 ms 1 GahBean.getRandomDroplets() 201.60 ms 2 SohBean.searchRatedDoodlesWithinHead() 185.94 ms 1 FroBean.doSearchPopular() 157.63 ms 1 FroBean.doSearchRecent() 42.34 ms 1 FooBean.fetchMostRecentYodel() 41.94 ms 1 GahBean.getMostRecentYodel() 15.89 ms 1 FooBean.getNoOfYodels() 15.00 ms 1 GahBean.getNoOfYodels() 9.14 ms 1 SohBean.mainYodels() 1.11 ms 2 SohBean.trackHoorayEvent() 0.32 ms 1 FroBean.reset() 0.22 ms 43 NohBean.thumbPicture() 0.03 ms 18 FooBean.getMostRecentYodels() 0.01 ms 1 NohBean.profilePicture() 0.01 ms 1 FroBean.setToDefault() 0.01 ms 1 FroBean.getRecentMarker()
The first column contains the accumulated time spent in this method. The second column contains the number of times the method has been called, and the final column shows which method was called.
This Interceptor will measure how long each method will take to execute in milliseconds.
TimingInterceptor.java
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.jboss.seam.annotations.intercept.AroundInvoke;
import org.jboss.seam.annotations.intercept.Interceptor;
import org.jboss.seam.core.BijectionInterceptor;
import org.jboss.seam.core.ConversationInterceptor;
import org.jboss.seam.core.ConversationalInterceptor;
import org.jboss.seam.core.EventInterceptor;
import org.jboss.seam.core.MethodContextInterceptor;
import org.jboss.seam.core.SynchronizationInterceptor;
import org.jboss.seam.ejb.RemoveInterceptor;
import org.jboss.seam.ejb.SeamInterceptor;
import org.jboss.seam.intercept.InvocationContext;
import org.jboss.seam.persistence.HibernateSessionProxyInterceptor;
import org.jboss.seam.security.SecurityInterceptor;
import org.jboss.seam.transaction.TransactionInterceptor;
/**
* This interceptor will calculate how long it takes to execute each method
*
* Copied and modified from @link http://www.seamframework.org/Community/SeamPerformanceProblemRewardingWorkaround
*
*/
@Interceptor(
around = {
BijectionInterceptor.class,
MethodContextInterceptor.class,
ConversationInterceptor.class,
SynchronizationInterceptor.class,
ConversationalInterceptor.class,
RemoveInterceptor.class,
SeamInterceptor.class,
SecurityInterceptor.class,
TransactionInterceptor.class,
EventInterceptor.class,
HibernateSessionProxyInterceptor.class
})
public class TimingInterceptor {
public final static CallChain callChain = new CallChain();
@AroundInvoke
public Object timeCall(InvocationContext invocation) throws Exception {
long t0 = System.nanoTime();
try {
return invocation.proceed();
} finally {
long dt = System.nanoTime() - t0;
callChain.addInvocation(invocation, dt);
}
}
// -----------------------------------------------------------------------------
/**
* A call chain is the set of invocations on methods (annotated
* with MeasureCalls) that a request issued on its way through
* the application stack.
*/
public static class CallChain extends ThreadLocal<Map<Method, TimedInvocation>> {
@Override
protected Map<Method, TimedInvocation> initialValue() {
return new HashMap<Method, TimedInvocation>();
}
public void addInvocation(InvocationContext invocation, long dt) {
Map<Method, TimedInvocation> invocations = get();
Method method = invocation.getMethod();
if (!invocations.containsKey(method)) {
invocations.put(method, new TimedInvocation(invocation.getMethod(), dt));
} else {
TimedInvocation timedInvocation = invocations.get(method);
timedInvocation.anotherCall(dt);
}
}
public int totalNumberOfInvocations() {
Map<Method, TimedInvocation> invocations = get();
Collection<TimedInvocation> timedInvocationCollection = invocations.values();
int totCalls = 0;
for (TimedInvocation invocation : timedInvocationCollection)
totCalls += invocation.getCalls();
return totCalls;
}
}
}
TimedInvocation.java
import java.lang.reflect.Method;
import org.apache.commons.lang.StringUtils;
/**
* TimedInvocation is an invocation (i.e. a method call) which is being
* counted and timed.
*/
public class TimedInvocation implements Comparable<TimedInvocation> {
private long dt;
private int calls = 1;
private Method method;
public TimedInvocation(Method method, long dt) {
this.method = method;
this.dt = dt;
}
public long getDt() {
return dt;
}
public Method getMethod() {
return method;
}
/**
* The first column contains the accumulated time spent in this method.
* The second column contains the number of times the method has been called.
* The third column contains the method which was called
*/
public String toString() {
String className = method.getDeclaringClass().getName();
String shortendName = className.substring(method.getDeclaringClass().getPackage().getName().length() + 1);
String duration = StringUtils.leftPad(Double.valueOf(dt / 1e6) + " ms", 11);
String nCallStr = StringUtils.leftPad(String.valueOf(calls), 4);
return duration + nCallStr + " " + shortendName + "." + method.getName() + "()";
}
public void anotherCall(long dt) {
this.dt += dt;
calls++;
}
public int compareTo(TimedInvocation o) {
return -Long.valueOf(dt).compareTo(o.dt);
}
public int getCalls() {
return calls;
}
}
Lastly, we will create a simple filter that will print out the result. The filter is only installed in debug mode so that it doesn't run on production. Be aware that the interceptor will still run if you don't forget to uncomment the interceptor.
TimingFilter.java
/**
* This filter is used in conjunction with the TimingInterceptor. *
* It is defaulted to only work in debug mode (debug = true)
* To use this filter you need to set value to true
*
*/
@Startup
@Scope(ScopeType.APPLICATION)
@Name("timingFilter")
@BypassInterceptors
@Filter
@Install(debug = true)
public class TimingFilter extends AbstractFilter {
private static final LogProvider log = Logging.getLogProvider(TimingFilter.class);
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
if (!(req instanceof HttpServletRequest)) {
chain.doFilter(req, res);
return;
}
TimingInterceptor.callChain.remove();
chain.doFilter(req, res);
Map<Method, TimedInvocation> invocations = TimingInterceptor.callChain.get();
for(TimedInvocation timedInvocation : invocations.values()) {
log.debug(timedInvocation.toString());
}
}
}
Finally create an annotation where you register the interceptor.
@MeasureCalls
/**
* This is an annotation that is used with the Interceptor TimingInterceptor,
* that will measure time it takes to execute each method in a seam component.
*
* The filter that uses this interceptor only works in debug mode and you will need to set value to true
* on the TimingFilter. Otherwize you will not see anything when running this in production or test.
* However, the interceptor will still run, so be "careful" where you put this annotation.
*
* Usage:
* @AutoCreate
* @Name("theBeanILikeToInvestigate")
* @MeasureCalls
* public class TheBeanILikeToInvestigare
*
*
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Interceptors(TimingInterceptor.class)
public @interface MeasureCalls {}
Just put @MeasureCalls on your component and all methods on that component will be invoked on.
Labels:
interceptor,
Seam,
Seam 2,
Seam Series
Subscribe to:
Posts (Atom)
Labels
- Seam (14)
- Java (11)
- Hibernate (5)
- JPA (4)
- Seam 2 (4)
- Seam 2.1.1 (4)
- Seam Series (4)
- Devoxx (3)
- EJB3 (3)
- JavaOne (3)
- MySQL (3)
- PDF (3)
- jodconverter (3)
- jodconverter 3 (3)
- axis2 (2)
- hash password (2)
- pdfa (2)
- sigar (2)
- swing (2)
- testng (2)
- webservice (2)
- AngularJS (1)
- Cache (1)
- Comment (1)
- Dropwizard (1)
- Encryption (1)
- Enum (1)
- Freemarker (1)
- Guava (1)
- IE8 (1)
- IdentityManager (1)
- InnoDB (1)
- Iran (1)
- JOOReport (1)
- JSF (1)
- Jasypt (1)
- JavaPolis (1)
- Memory (1)
- MyISAM (1)
- ODT (1)
- OSS (1)
- OpenOffice (1)
- REST (1)
- Seam book (1)
- Tehran (1)
- UTF-8 (1)
- Ubuntu hardy (1)
- Windows vista (1)
- ant (1)
- chess (1)
- damenes tale (1)
- filters (1)
- firefox (1)
- html cache (1)
- interceptor (1)
- java 5 (1)
- java 6 (1)
- java 7 (1)
- javazone (1)
- javazone 2015 (1)
- jaxb (1)
- jboss (1)
- jboss 4 (1)
- jboss-ws (1)
- jdbc (1)
- jquery (1)
- jrebel (1)
- julebord (1)
- junit (1)
- musehånd (1)
- ojdbc (1)
- oracle (1)
- persistence.xml (1)
- prettify (1)
- ptql (1)
- richfaces (1)
- rollermouse pro (1)
- rpi (1)
- s:cache (1)
- seam-gen (1)
- second level cache (1)
- treecache (1)
- wicket (1)