PHP and Java: Using Java Print Service with Zend Server Community Edition

June 24, 2009

Uncategorized

PHP and Java:

Using Java Print
Service with Zend Server Community Edition

If one were to survey the extensions available in PHP one
might come to the conclusion that we actually lived in a paperless society.
However, we all know that isn’t true. So why does PHP not have good printer
support? One option that explains why this might be the case is simply that
printing documents is contrary to what PHP does well; handle short requests as
quickly as possible. Printing is not conducive to this.

But what if you still need to have printing support in your
PHP application? With Zend
Server
, Zend’s integrated PHP application stack, the answer might be not
what you were expecting. Use the PHP Java Bridge, available in both the href="http://www.zend.com/community/zend-server-ce">community and href="http://www.zend.com/en/products/server/">commercial editions of Zend
Server.

First, you might be thinking “Java?!” What’s this guy
smoking? Well, Padron when I am able to, but Java is quite a legitimate
option. The reason why I am highlighting Java here is because it fits into the
same realm as PHP; environment independence and this is a supportable solution.
Many other printing options in PHP require operating system dependent methods,
they are not supported or they are not built for PHP 5. As a reader of this
article, it is likely that you need one, or all.

Printing in Java is done through the Java Print Service
(JPS) API. It has been available in Java since 1997 as part of AWT (Abstract
Windows Toolkit), prior to the advent of the JPS API. Using the AWT mechanism,
a document could be printed via AWT or, later on, the 2D Graphics interface.
But that was about it. The JPS API added more functionality than the original
printing API. One example of this is printer discovery. Prior to JPS, the
default printer was the only option.

In this article we will take a look at how you can use the
Zend Server Community Edition Java Bridge to connect in with the Java Print
Service.

Step One: Finding your printers

Prior to printing any documents you will need to find a
printer to print to. In Java, this is done by calling the static method style='font-size:10.0pt;font-family:"Courier New";mso-bidi-font-family:"Courier New";
color:black'>PrintServiceLookup.lookupPrintServices. However,
this class is a static class which means when we try to access it in PHP, the
Java Bridge will try to create a new instance of it. This will cause an
exception to be thrown.

>Attempting to browse installed printers

$jps >= >new >java( >‘javax.print.PrintServiceLookup’ >);

$printers = $jps->lookupPrintServices(); >

Fatal error:
Uncaught exception 'JavaException' with message 'Java Exception java.lang.InstantiationException: 

java.lang.InstantiationException
at
sun.reflect.InstantiationExceptionConstructorAccessorImpl.newInstance(Unknown
Source) at 

java.lang.reflect.Constructor.newInstance(Unknown Source) ' in test.php:3 Stack trace: #0 test.php(3): *No Class!*->jbridge('javax.print.Pri...') #1 {main} thrown in test.php on line 3

The solution to this problem is to write a very simple
implementation of PrintServiceLookup. You can, of course, write a lot of
custom functionality in your implementation, but for this example, we just want
to make it simple.

>Figure 1: com.zend.Printer

package > com.zend;

import >
javax.print.DocFlavor;

import >
javax.print.MultiDocPrintService;

import >
javax.print.PrintService;

import >
javax.print.PrintServiceLookup;

import >
javax.print.attribute.AttributeSet;

public > >class > Printer >extends >
PrintServiceLookup {

      >public >
PrintService getDefaultPrintService() {

            >return > lookupDefaultPrintService();

      }

      >public > MultiDocPrintService[]
getMultiDocPrintServices(DocFlavor[]

flavor,
AttributeSet attr) {

                 

            >return > lookupMultiDocPrintServices(flavor,
attr);

      }

      >public >
PrintService[] getPrintServices() {

            >return >
PrintServiceLookup.lookupPrintServices( >null >, >null >);

      }

      >public >
PrintService[] getPrintServices(DocFlavor flavor,

AttributeSet
attr) {

            >return >
PrintServiceLookup.lookupPrintServices(flavor,attr);

      }

}

I wrote this code in Zend Studio for Eclipse using a new
Java Project called Printing. As such, the class files are compiled
automatically. So all I need to do now is point my Java Bridge JVM at the
appropriate directory. In the ZendServer/etc directory there is a file called java_bridge_server.ini.
This file contains all of the settings for the Java Bridge. By default, it
contains the following information:

>Figure 2: java_bridge_server.ini

[JAVA_BRIDGE_SERVER]

CLASSPATH="C:\Program
Files\Zend\ZendServer\bin\javamw.jar;."

What we need to do is add the new class file to the class
path. So append the base directory of the Printing project’s bin directory to
the class path.

>Figure 3: Modified java_bridge_server.ini

[JAVA_BRIDGE_SERVER]

CLASSPATH="C:\Program
Files\Zend\ZendServer\bin\javamw.jar;. ;c:\workspace\Printing\bin"

After restarting the Java Bridge we can now test to see if
we can get a list of the installed printers in PHP. The code to test this is
relatively simple.

>Figure 4: Test code for printer list

$jps >= >new >java( >‘com.zend.Printer’ >);

foreach >( >$jps >->getPrintServices()
as >$printer >) {

      >echo >$printer >->getName(). >"\n" >;

}

This code then prints a list of all of the printers
installed on your system

>Figure 5: Test code output

>WebEx Document Loader

>PrimoPDF

>PDFCreator

>Microsoft XPS Document
Writer

>Microsoft Office
Document Image Writer

If you are wondering about the overhead of doing this call,
it is 4-7ms once the initial call to the Java Bridge has been made.

Step Two: Printing a simple document

Printing a document can be either complex or simple. It can
be simple if the printer you are working with supports the type of document
that is going to be submitted. With the JPS this is known as a document flavor
and is represented by the class DocFlavor. But prior to
submitting the document, we need to find out if the printer supports it. That
is done by calling the getSupportedDocFlavors()
method on the $printer object.

>Figure 6: Getting a list of supported document
flavors

$jps >= >new >java( >‘com.zend.Printer’ >);

foreach >( >$jps >->getPrintServices()
as >$printer >) {

      >echo >$printer >->getName(). >"
supports\n ";

      >$flv >= >array >();

      >foreach >( >$printer >->getSupportedDocFlavors()
as >$docFlavor >) {

            >$flv >[] =
(string)$docFlavor;

      }

      >echo >implode( >"\n
", $flv)."\n\n";

}

As an example, this script outputs

PDFCreator supports
    image/gif; class="[B"
    image/gif; class="java.io.InputStream"
    image/gif; class="java.net.URL"
    image/jpeg; class="[B"
    image/jpeg; class="java.io.InputStream"
    image/jpeg; class="java.net.URL"
    image/png; class="[B"
    image/png; class="java.io.InputStream"
    image/png; class="java.net.URL"
    application/x-java-jvm-local-objectref; class="java.awt.print.Pageable"
    application/x-java-jvm-local-objectref; class="java.awt.print.Printable"
    application/octet-stream; class="[B"

On my system, this ended up printing 75+ options for
printing once all of the document flavor and printer combinations were done.
However, one thing you may notice is that there is no PDF, Word or other such
complex types. We will look at those in step 3.

Let’s start with the code first and then we’ll explain
what’s going on.

>Figure 7: Code to print a simple document

// First select the active
printer

$jps >= >new >java( >'com.zend.Printer' >);

$activePrinter >= null;

foreach >( >$jps >->getPrintServices()
as >$printer >) {

      >if >( >$printer >->getName()
== 'PDFCreator' >) {

            >$activePrinter
= >$printer >;

            >break >;

      }

     

}

// We are going to print the
Zend Server product image

$url >= >new >java(

      >'java.net.URL' >,

      >'http://static.zend.com/topics/zend-server-product-page-main.jpg'

);

// Create a flavor that uses
an input stream so we can stream

// the results of the URL
request

$flavor >= >new >java( >'javax.print.DocFlavor$INPUT_STREAM' >);

// Create the document

$doc >= >new >java(

      >'javax.print.SimpleDoc' >,

      >$url >->openStream(),

      >$flavor >->JPEG,

      Null

);

// Create the job

$job >= >$activePrinter >->createPrintJob();

// Print!!

$job >->print( >$doc >, null);

Everything up until where $flavor is created is
self-explanatory. Creating $flavor can be likened to configuring the printer system
on what type of content to expect. The $INPUT_STREAM is a predefined class
that represents the type of document we’re going to print. Note that we did
not create an object of DocFlavor and then reference INPUT_STREAM->JPEG
in the SimpleDoc
call. i.e.
$flavor->INPUT_STREAM->JPEG
. This
does not work. So when referencing the predefined class, we need to reference
the first level.

After we have created our DocFlavor reference it is time to
create the actual doc. This is done by creating an instance of style='font-family:"Courier New";mso-bidi-font-family:"Courier New"'> javax.print.SimpleDoc.
This class can handle some basic images. We create an instance of it, passing
in the URL object’s InputStream instance with the DocFlavor into the
constructor. The third argument is for printing attributes, such as paper size
or the number of copies. This was omitted by passing in style='font-family:"Courier New";mso-bidi-font-family:"Courier New"'>NULL.

Once our document has been created it is time to retrieve a
new job from the printer. That is done by calling createPrintJob() on
our $activePrinter
object. Once we have an object representing the print job all that’s left to
do is print the document. Refreshing this page causes it to submit the job to
my local print spooler and I get a confirmation to print the page (this can be
automated). The result is an obvious PDF-based image retrieved from the URL
provided.

>Figure 8: The printed image

> border=0 width=280 height=212 id="_x0000_i1026"
src="/images/articles/4776/image001.png">

Step Three: Printing a complex document

            For
printing a complex document we are going to look at printing a PDF document.
There are some options available for printing Word documents in Java, but I did
not have the time to fully research it, nor did I want to spend the money, so
opted for PDF since it’s nice and portable.

            The
first thing you will need to do is find a PDF renderer. If we look at the
output of Figure 6 you will notice that PDF is not listed as one of the
supported flavors. I chose “PDF Renderer” from href="https://pdf-renderer.dev.java.net/">https://pdf-renderer.dev.java.net/.

            So,
let’s start first by creating a form that allows us to upload a PDF document to
our PHP installation.

>Figure 9: Code to print the form

require_once >'Zend/Loader/Autoloader.php' >;

Zend_Loader_Autoloader::getInstance();

$jps >= >new >java( >'com.zend.Printer' >);

$printers >= >array >();

$selectedPrinter >= null;

foreach >( >$jps >->getPrintServices()
as >$printer >) {

      >$printers >[] = >$printer >->getName();

      >if >( >isset >( >$_POST >[ >'printer' >])

&& >$_POST >[ >'printer' >] == >$printer >->getName())
{

            >$selectedPrinter
= >$printer >;

      }

}

$form >= >new >Zend_Form();

$form >->setView( >new >Zend_View());

$form >->setMethod( >'POST' >);

$form >->addElement( >'file' >, >'upload' >, >array >( >'label' >=> >'Upload
PDF'));

$form >->addElement( >'select' >, >'printer' >,

      >array >( >'multioptions'
=> >$printers >, >'label' >=> >'Choose
Printer'));

$form >->addElement( >'submit' >, >'Print' >);

if >( >$form >->isValid( >$_POST >)) {

      >echo >$form >->upload->getFileName();

      >exit >;

}

echo >$form >;

            You
will note that I used Zend Framework to print the form. I did that because I
like other people to do as much work for me as possible. This code creates the
following output:

>Figure 10: Form output

> border=0 width=175 height=77 id="Picture 1"
src="/images/articles/4776/image002.png">

            When
I submit this form it will print out the temporary name of the file that was
uploaded. Now I need to pass the contents of that file into the Java Bridge. One
might be tempted to keep this in PHP, but depending on the size of your
document this could be a prohibitive amount of data which, as I found in my
testing, may have some undesirable results and the mechanism needed to actually
do the printing is somewhat cumbersome. So, instead of writing this code in
PHP we are going to write a minimal abstraction layer in the style='font-family:"Courier New";mso-bidi-font-family:"Courier New"'>Printer
class.

The base object for the PDF
Renderer is the PDFFile class. Its constructor takes an instance of style='font-family:"Courier New";mso-bidi-font-family:"Courier New"'> java.nio.ByteBuffer
as its parameter. ByteBuffer is a class that is used with Java’s New IO
(NIO) package as a container for primitive data types. If you’d like some more
information on the NIO classes you can go to http://java.sun.com/javase/6/docs/technotes/guides/io/index.html.

            In
order to print the PDF document, we will need to create a bridge between the
PDF Renderer and the JPS. PDF Renderer does not directly support doing this,
but the example application that comes with the library has the code written
out. You will find it in Appendix A. That code will take the style='font-family:"Courier New";mso-bidi-font-family:"Courier New"'>PDFFile
object and render it using the 2DGraphics java library so the JPS
library can send the binary data to the printer.

            Once
the code from Appendix A has been compiled and included in the Java Bridge
class path we can now write our PHP code to send the PDF file to the printer.
First, we will print out the block as a whole and then go through it
line-by-line to explain what we’re doing at each step in the process since
there are a few moving parts here.

>Code to print a PDF to a JPS printer

if >( >$form >->isValid( >$_POST >)
&& $form->upload->receive()) {

>$mm >= >new >Java( >'java.nio.channels.FileChannel$MapMode' >);

>$file >= >new >java( >'java.io.File' >, >$form >->upload->getFileName());

>$fis >= >new >java( >'java.io.FileInputStream' >, >$file >);

>$fc >= >$fis >->getChannel();

>$bb >= >$fc >->map( >$mm >->READ_ONLY,
0 >, >$fc >->size());

>$pdfFile >= >new >java( >'com.sun.pdfview.PDFFile' >, >$bb >);   

>$renderer
= >new >java( >'com.zend.PDFPrintRenderer' >, >$pdfFile >);

>$job >= >$selectedPrinter >->createPrintJob();

>$flavor >= >new >java( >'javax.print.DocFlavor$SERVICE_FORMATTED' >);

>$doc >= >new >java(

            >'javax.print.SimpleDoc' >,

            >$renderer >,

            >$flavor >->PRINTABLE,

            null

      );

     

>$job >->print( >$doc >, null);

>exit >;

}

So let’s go through this line-by-line now.

Create a reference to the MapMode class so we can access the
fields in the class later on.

$mm
= new style='font-size:10.0pt;font-family:"Courier New";mso-bidi-font-family:"Courier New";
color:black'>java('java.nio.channels.FileChannel$MapMode' style='font-size:10.0pt;font-family:"Courier New";mso-bidi-font-family:"Courier New";
color:black'>);

Create a new File object that refers back to the
uploaded file name.

$file
= new style='font-size:10.0pt;font-family:"Courier New";mso-bidi-font-family:"Courier New";
color:black'>java('java.io.File' style='font-size:10.0pt;font-family:"Courier New";mso-bidi-font-family:"Courier New";
color:black'>, $form style='font-size:10.0pt;font-family:"Courier New";mso-bidi-font-family:"Courier New";
color:black'>->upload->getFileName());

Create an input stream that we can read from. We will not
read directly from this but rather use it as a gateway to retrieving the NIO channel
which is used to retrieve and buffer the data that will be used to create the style='font-family:"Courier New";mso-bidi-font-family:"Courier New"'>PDFFile
object.

$fis
= new style='font-size:10.0pt;font-family:"Courier New";mso-bidi-font-family:"Courier New";
color:black'>java('java.io.FileInputStream' style='font-size:10.0pt;font-family:"Courier New";mso-bidi-font-family:"Courier New";
color:black'>, $file style='font-size:10.0pt;font-family:"Courier New";mso-bidi-font-family:"Courier New";
color:black'>);

Retrieve the NIO channel

$fc
= $fis style='font-size:10.0pt;font-family:"Courier New";mso-bidi-font-family:"Courier New";
color:black'>->getChannel();

The map() method will map the file directly into memory.
This method returns a ByteBuffer instance that represents that mapping.

$bb
= $fc style='font-size:10.0pt;font-family:"Courier New";mso-bidi-font-family:"Courier New";
color:black'>->map($mm style='font-size:10.0pt;font-family:"Courier New";mso-bidi-font-family:"Courier New";
color:black'>->READ_ONLY, 0 style='font-size:10.0pt;font-family:"Courier New";mso-bidi-font-family:"Courier New";
color:black'>, $fc style='font-size:10.0pt;font-family:"Courier New";mso-bidi-font-family:"Courier New";
color:black'>->size());

After we have created the mapping we can now create our style='font-family:"Courier New";mso-bidi-font-family:"Courier New"'>PDFFile
object. The PDFFile
constructor requires its parameter to be an instance of ByteBuffer, which is
why we had to go the NIO route instead of standard Java IO.

$pdfFile
= new style='font-size:10.0pt;font-family:"Courier New";mso-bidi-font-family:"Courier New";
color:black'>java('com.sun.pdfview.PDFFile' style='font-size:10.0pt;font-family:"Courier New";mso-bidi-font-family:"Courier New";
color:black'>, $bb style='font-size:10.0pt;font-family:"Courier New";mso-bidi-font-family:"Courier New";
color:black'>);

Then create the renderer. This is the class that is
available in Appendix A which provides the interface between the PDF Renderer
library and the JPS library. It takes an instance of PDFFile as its
constructor parameter.

$renderer
= new style='font-size:10.0pt;font-family:"Courier New";mso-bidi-font-family:"Courier New";
color:black'>java('com.zend.PDFPrintRenderer' style='font-size:10.0pt;font-family:"Courier New";mso-bidi-font-family:"Courier New";
color:black'>, $pdfFile style='font-size:10.0pt;font-family:"Courier New";mso-bidi-font-family:"Courier New";
color:black'>);

Create a new printer job to work with.

$job
= $selectedPrinter style='font-size:10.0pt;font-family:"Courier New";mso-bidi-font-family:"Courier New";
color:black'>->createPrintJob();

Like the $mm variable, we need to access one of the fields in the style='font-family:"Courier New";mso-bidi-font-family:"Courier New"'>DocFlavor
class. However, earlier we referenced the INPUT_STREAM field whereas here we
are referencing SERVICE_FORMATTED. This is the flavor that is used to
send a printable Java object to the JPS. Since our PDFPrinterRenderer
implements the Printable interface, we are referencing the style='font-family:"Courier New";mso-bidi-font-family:"Courier New"'>application/x-java-jvm-local-objectref;
class="java.awt.print.Printable" flavor.

$flavor
= new style='font-size:10.0pt;font-family:"Courier New";mso-bidi-font-family:"Courier New";
color:black'>java('javax.print.DocFlavor$SERVICE_FORMATTED' style='font-size:10.0pt;font-family:"Courier New";mso-bidi-font-family:"Courier New";
color:black'>);

Create a new SimpleDoc object, passing in the
renderer object and specifying the PRINTABLE flavor.

$doc
= new java(

      'javax.print.SimpleDoc',

      $renderer,

      $flavor->PRINTABLE,

      null

);

Print!!

$job style='font-size:10.0pt;font-family:"Courier New";mso-bidi-font-family:"Courier New";
color:black'>->print($doc style='font-size:10.0pt;font-family:"Courier New";mso-bidi-font-family:"Courier New";
color:black'>, null);

At this point your hard drive will start spinning, your CPU usage
will jump and after a bit you should have a newly printed document. Note that this
mechanism will, by default, print every page in the document, but there are
ways of controlling this in the JPS library. It is also important to note that
this can be a very CPU intensive operation. If you are printing anything
beyond a few simple pages it is recommended that you take a look at some
queuing options so printing can be done separate from the HTTP request. Zend
Platform’s Job Queue is a great place to start.

I hope this was an informative look at a feature that can be
useful to PHP-based applications.

style='font-size:11.0pt;mso-bidi-font-size:12.0pt'>Kevin Schroeder is a
Technical Consultant for Zend Technologies, and well versed in a wide variety
of technologies pertinent to both small and large scale application
deployments. He primarily works with customers in the United States, providing
PHP-based services for a variety of customers. Kevin is also a proven
instructor for many of Zend’s courses and is involved in training development
as well as speaking at conferences on a variety of subjects pertaining to PHP.
Together with Jeff Olen, he is co-author of the book “The IBM i Programmer’s
Guide to PHP”.

Appendix A

>PDFRenderer implementation of Printable
Interface

public > >class > PDFPrintRenderer
implements > Printable
{

>private > PDFFile >file >;

>public >
PDFPrintRenderer(PDFFile file) {

  >this >. >file > = file;

}

>public > >int >
print(Graphics g, PageFormat format, int > index)

     
throws PrinterException {

            >int > pagenum =
index + 1;

           

            >// don't bother if the
page number is out of range.

            >if > ((pagenum
>= 1) && (pagenum <= file >.getNumPages()))
{

                 

           
// fit the PDFPage into
the printing area

           
Graphics2D g2 = (Graphics2D) g;

           
PDFPage page =
file.getPage(pagenum);

           
double pwidth = format.getImageableWidth();

           
double pheight = format.getImageableHeight();

           

           
double aspect = page.getAspectRatio();

           

           
// handle
page orientation matching

           
double paperaspect = pwidth / pheight;

           
if (paperaspect < 1.0) {

           
switch (format.getOrientation()) {

           
case PageFormat.REVERSE_LANDSCAPE >:

           
case PageFormat.LANDSCAPE:

           
format.setOrientation(PageFormat.
>PORTRAIT >);

           
break;

           
case PageFormat.PORTRAIT:

           
format.setOrientation(PageFormat.
>LANDSCAPE >);

           
break;

           
}

           
pwidth = format.getImageableWidth();

           
pheight = format.getImageableHeight();

           
paperaspect = pwidth / pheight;

           
}

           

           
Rectangle imgbounds;

           
int width;

           
int height;

           
if (aspect > paperaspect) {

           
// paper is too tall / pdfpage is too wide

           
height = (
int) (pwidth / aspect);

           
width = (
int) pwidth;

           
}
else {

           
// paper is too wide / pdfpage is too tall

           
width = (
int) (pheight * aspect);

           
height = (
int) pheight;

           
}

           
imgbounds =
new Rectangle((int) format.getImageableX(),

           
(
int) format.getImageableY(),

           
width, height);

           

           
// render
the page

           
PDFRenderer pgs =

           
new PDFRenderer(page, g2, imgbounds, >null >, >null >);

           
try {

           
page.waitForFinish();

           
pgs.run();

           
}
catch (InterruptedException ie) {

           
}

           
return PAGE_EXISTS >;

            }
else > {

           
return NO_SUCH_PAGE >;

            }

      }

     

}

,

6 Responses to “PHP and Java: Using Java Print Service with Zend Server Community Edition”

  1. lovezend2010 Says:

    hi i am working on a PHP application,and i want integrate a .jar file,so i am using JavaBridge in Zend Server CE,and it worked well with some examples,but the problem is that when i want display a message dialog ,it doesn’t work and there is no errors in the PHP_error.
    ****************HelloWorld.java**********
    public class HelloWorld {
    public static void main(String args[]) throws Exception {
    JOptionPane.showMessageDialog(null, "hello world");
    System.exit(0);
    }
    public void hello(String args[]) throws Exception {
    JOptionPane.showMessageDialog(null, "hello " + args[0]);
    }
    }

    i created the .jar and it’s worked well.

    *******************
    <?php
    java_require(‘http://localhost:10088/HelloWorld.jar’);
    $world = new java("HelloWorld");
    echo $world->hello(array("from PHP"));
    ?>
    so ths is the code php which ust display a message dialog!!!!

    thnx in advance

  2. kschroeder Says:

    There are a couple of ways of doing it but the most stable and predictable way that I’ve seen is simply by setting the class path in the ini file in the Zend Server config.

  3. jfasaldarriaga Says:

    @tuslo I have the same error.

    I post my problem on zend forums, but nobody seems like will answer it *sighs* (http://forums.zend.com/viewtopic.php?f=44&t=4481), I try to set the class path via java.lang.System but doesn’t work either.

    $system = new Java( "java.lang.System" );
    $system->setProperty( "java.class.path", "" );

    $classPath = implode( PATH_SEPARATOR, array(
    "C:\Program Files\Zend\ZendServer\bin\javamw.jar", // ZS java bridge jar.
    ".",
    realpath( APPLICATION_PATH . "/../library/Ceiba/Java/Test.jar" ),
    ) );

    $system->setProperty( "java.class.path", $classPath );

  4. tuslo Says:

    Hello,

    Thank you for useful material.
    I’m not a big expert of Java, so i have a question:
    I get from vendor 4 jars (1.jar, 2.jar…). I suppose one of them consists of class I should use in my php-script. I follow the recomendations above and add CLASSPATH to the
    java_bridge_server.ini and get
    [JAVA_BRIDGE_SERVER]
    CLASSPATH="C:\Program Files\Zend\ZendServer\bin\javamw.jar;.
    ;D:\m\1.jar;.;D:\m\2.jar;.;D:\m\3.jar;.;D:\m\4.jar"
    Restart JavaBridge
    But still have that exception: *No Class!*->jbridge(‘…. when using "new Java".

    Is it problem with .jar placement? Or wrong syntax at CLASSPATH?

    tnks

  5. kschroeder Says:

    Aaron,
    I don’t know of any, but that doesn’t mean they don’t exist on your system. I Googled "java print service cups" and got a fair number of hits. That might be a good place to start if you run into issues.

  6. Aaronmccall Says:

    Are there any caveats/idiosyncrasies to watch for when/if implementing this on a Linux system?