Tuesday, July 26, 2011

ADF 11g : Printing Directly From Your Application

Last week I was asked this question : "Can we print directly from within our ADF Application, without invoking the printer dialog ?" I wasn't sure but after some investigation the answer was clear. Yes you can ! Here is how...

I decided to create a print start up form where I can select printers and print the document. Most of the functionality needed is provided by the Java Print Service API.

Selecting available printers

I start with a way to show all printers available to the session. For that I simply use the PrinterServiceLookup.
 PrintService[] printers =
PrintServiceLookup.lookupPrintServices(null, null);

The result I can now use to create an Array of SelectItems in order to make the list available in the application.
1:   public SelectItem[] getAllPrinters() {
2: if (allPrinters == null) {
3: PrintService[] printers =
4: PrintServiceLookup.lookupPrintServices(null, null);
5: allPrinters = new SelectItem[printers.length];
6: for (int i = 0; i < printers.length; i++) {
7: SelectItem printer =
8: new SelectItem(printers[i].getName(), printers[i].getName());
9: allPrinters[i] = printer;
10: }
11: }
12: return allPrinters;
13: }

On the pagefragment I use a selectOneChoice component getting the values from the list created above. The code for the pagefragment is like this:
1:    <af:selectOneChoice label="Available Printers" partialTriggers="cb1"  
2: value="#{pageFlowScope.applicationPrinterBean.selectedPrinter}"
3: id="soc1" autoSubmit="true">
4: <f:selectItems value="#{pageFlowScope.applicationPrinterBean.allPrinters}"
5: id="si1"/>
6: </af:selectOneChoice>

When I run the application I see a list of all printers defined om my local machine and I can select any printer I like ...


What is the default printer ?

Usually there is a default printer defined. That is the one I want to use by default in this application. For that to work I need to get hold of the default printer defined. This can be found by invoking lookupDefaultPrintService().
1:   public String getDefaultPrinter() {
2: PrintService defaultPrinter =
3: PrintServiceLookup.lookupDefaultPrintService();
4: return defaultPrinter.getName();
5: }

If no printer is selected yet in the ADF Application, I use the default printer. I need to make an adjustment to the getter of the selected printer to return the default printer.
1:   public String getSelectedPrinter() {
2: if (selectedPrinter == null) {
3: return getDefaultPrinter();
4: } else {
5: return selectedPrinter;
6: }
7: }

Final requirement for the print startup form is to be able to reset the selected printer to the default if another printer was selected previously. For that I use a CommandButton that invokes an actionListener where I simply set the currently selected printer to be the default printer.
1:   public void resetToDefault(ActionEvent actionEvent) {
2: // Add event code here...
3: setSelectedPrinter(getDefaultPrinter());
4: }

By using this button, the default printer will be selected again.


Now what about the actual printing ?

Printing can be done by using the DocPrintJob, FileInputStream and Doc objects.
First is to create a FileInputStream based on the file that I want to print.
Next step is to create a Doc object based on the FileInputStream. Finally create a PrintJob based on the Doc object. I also show the user a message when something goes wrong. All this functionality is implemented in an actionListener behind a command button.
1:   public void invokePrintJob(ActionEvent actionEvent) {
2: // Add event code here...
3: PrintService[] printers =
4: PrintServiceLookup.lookupPrintServices(null, null);
5: Boolean printerFound = false;
6: for (int i = 0; i < printers.length && !printerFound; i++) {
7: if (printers[i].getName().equalsIgnoreCase(getSelectedPrinter())) {
8: printerFound=true;
9: PrintService printer = printers[i];
10: FileInputStream input;
11: try {
12: input =
13: new FileInputStream(theFile);
14: Doc doc = new SimpleDoc(input, DocFlavor.INPUT_STREAM.PNG, null);
15: PrintRequestAttributeSet attrs = new HashPrintRequestAttributeSet();
16: DocPrintJob job = printer.createPrintJob();
17: job.print(doc, attrs);
18: } catch (PrintException e) {
19: System.out.println(e.getMessage());
20: addFacesErrorMessage(e.getMessage());
21: } catch (FileNotFoundException e) {
22: System.out.println(e.getMessage());
23: addFacesErrorMessage(e.getMessage());
24: }
25: }
26: }
27: }

The code for the page fragment is simple.
1:    <af:toolbar>    
2: <af:commandToolbarButton text="Reset to Default"
3: actionListener="#{pageFlowScope.applicationPrinterBean.resetToDefault}"
4: partialSubmit="true" id="cb1"/>
5: <af:commandToolbarButton text="Print" id="ctb1"
6: actionListener="#{pageFlowScope.applicationPrinterBean.invokePrintJob}"/>
7: </af:toolbar>

And the page now has a print button.

When the print button is pushed, the document is sent directly to the printer.

It is also possible to set job properties such as number of copies, the orientation of the print, and many other properties.
1:  attrs.add(new Copies(2));
2: attrs.add(OrientationRequested.LANDSCAPE);

I can check the result of this in the print properties of the printjob.

Now one last thing to do is to give the printjob a decent name. As you can see, by default, all jobs have the same name, and that has no relation whatsoever with the document name, neither can it be related to a user or client.

I want to use a jobname that is more descriptive. I can do that by adding the JobName to the PrintRequestAttributeSet.
1:  PrintRequestAttributeSet attrs = new HashPrintRequestAttributeSet();
2: attrs.add(new Copies(2));
3: attrs.add(OrientationRequested.LANDSCAPE);
4: JobName name = new JobName(file ,null);
5: attrs.add(name);

The result is obvious.


Conclusion
http://www.blogger.com/img/blank.gif
Printing can be done directly from within the ADF Application. You can use almost any of the properties that can be used in the printer dialog of your browser or operating system. This example can be extended and I will probably create an ADF taskflow library to deploy this functionality.

Resources : javax.print javadoc

You can request a copy of the workspace by emailing me at lucbors at gmail dot com. OR you can download the workspace here.

14 comments:

Wilfred van der Deijl said...

Interesting post. However you seem to suggest this is printing from the client. Isn't this printing from the server and therefor showing printers available on the server, not the client. Can still be very helpful though

luc bors said...

Thanks Wilfred. I adjusted the post. Client is gone where it might raise the wrong suggestion.

adelaide printer said...

Amazing post..ADF 11G printing is really amazing..Its mind blowing..Thanks for telling about 3D printing.
adelaide printer

luc bors said...

On behalve of Rakesh

Hi Sir,

I am a beginner in ADF and have been trying your excellent post on ADF: Print Directly from your Application.

Can you please send me the workspace please ?

Rakesh

From Mauritius Island.

luc bors said...

requested by Wilhelm Colln
Hello Luc,

I have a requirement to print about 1000 documents (letters)
to a local pinter with information produced from a template (html)
with custom tagsassociated to information from de Oracle DB.

I think part of your example will help us to handle the selection of the printer.
Could you share your workspace ?

Any hint about working around this requirement would be appreciated.

I have made some tests and to avoid printing the header/footer of the browser
used the pdf format, but it is a slow process.

Thanks in advance,

Wilhelm

luc bors said...

Requested by Eduardo D. Vidal

Hello, I was reading your blog entry http://lucbors.blogspot.com/2011/07/adf-11g-printing-directly-from-your.html
and I would like to request the workspace you used for the sample, right know I'm working on something related with direct printing and your post really helps me.

Receive my regards.

luc bors said...

Requested by Eduardo D. Vidal

Hello, I was reading your blog entry http://lucbors.blogspot.com/2011/07/adf-11g-printing-directly-from-your.html
and I would like to request the workspace you used for the sample, right know I'm working on something related with direct printing and your post really helps me.

Receive my regards.

Unknown said...

Excellent solution, thank you very much,could you share to me work space
julykt@gmai.com

Regards
KT

Iván said...

I'm having troubles when the application is deployed in an i-frame in other adf application.
It returns a null value.
Any idea how can I solve this problem?

Alexandr Sokolovski said...

Your approach is good till the client and application server run on the same host. Otherwise you should use applets. look at this post http://adfpractice-sokol.blogspot.com/2012/06/obtaining-hostname-and-printers-via.html

Naeem Qasim said...

Hi, This is a very good article. In this article, you are printing files present on the hard drive. I have a requirement to print the data shown on the web page using an ADF read-only table. How can I print this data.

Luc Bors said...

Hi Naeem, you can use showprintablepageBehavior tag for that.

Anonymous said...

Hi I have tried this code this code is working fine for hp lazer printers but it is not sending any print to thermal printers and snagit printers please give me any suggestion what modifications are required

Muhammad Arshad Nadeem said...

Hi.... good post.. Helped me a lot.. but is there any possibility to pass a query and query result printed. suggest me a link..