Donnerstag, 17. Dezember 2009

Printer is not accepting job

As a Java programmer you might have experienced this message already. And I bet, you were not happy with it.

You'll see it, e.g. if the printer has been turned off and the windows spooler treats the printer as offline.
Which is not that bad, as the spooler itself is still capable to receive print jobs and queue them until the printer is online again.

But not so Java. Java simply ignores the fact that the queue is still accepting jobs - even with JRE 1.6.0_17.

Probably someone mixed up the terms Printer and Queue. If - and only if - the queue is not accepting jobs, Java is allowed to throw an exception.
I am not sure if Windows allows to disable the Queue, the *nix spooler CUPS definitely does.
But actually, Java denies printing even if the Queue is online.

Long story short: If you can afford not having the PrinterIsAcceptingJobs status available in your program - what should be no problem, the following solution is for you.
The solution acutally is just for the Win32 printer service, but should be easily extendable for the *nix one - if you have problems there too.

You need:
  • A Java compiler
  • javassist
And this helper class:

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.NotFoundException;
import sun.print.Win32PrintService;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;

public class Win32PrintServiceFixer
{
 public static void main(String[] args) throws Exception
 {
  ClassPool pool = ClassPool.getDefault();
  instrument(Win32PrintService.class.getName(), pool);

  JarOutputStream jar = new JarOutputStream(new FileOutputStream("target/PrintServiceFixer.jar"));
  addClass(jar, Win32PrintService.class.getName(), "java");
  jar.close();
 }

 private static void addClass(JarOutputStream jar, String clazz, String target) throws IOException
 {
  byte[] buf = new byte[8192];
  int read;

  String name = clazz.replace('.', '/');

  JarEntry jarEntry = new JarEntry(name + ".class");
  jar.putNextEntry(jarEntry);

  InputStream is = new FileInputStream("target/" + target + "/" + name + ".class");
  do
  {
   read = is.read(buf);
   if (read > 0)
   {
    jar.write(buf, 0, read);
   }
  }
  while (read > -1);
  is.close();
 }

 private static void instrument(String clazz, ClassPool pool) throws NotFoundException, CannotCompileException, IOException
 {
  CtClass ctClass = pool.get(clazz);
  for (CtMethod cMethod : ctClass.getDeclaredMethods())
  {
   if (cMethod.getName().equals("getPrinterIsAcceptingJobs"))
   {
    System.err.println("replacing getPrinterIsAcceptingJobs");
    cMethod.setBody("return javax.print.attribute.standard.PrinterIsAcceptingJobs.ACCEPTING_JOBS;");
   }
  }
  ctClass.writeFile("target/java");
 }
}

This helper looks up the Win32PrintService class and instrument the bytecode in a what that the check for "PrinterIsAcceptingJobs" always returns: Yes, the printer is accepting jobs.
Finally, it bundles the resulting class in a jar file named PrintServiceFixer.jar - created in a directory named "target".

You should see

replacing getPrinterIsAcceptingJobs
on the console output. If this line is missing, your JDK/JRE version needs another trick.

Now you have to start your program with the "bootclasspath" option like this:

-Xbootclasspath/p:target/PrintServiceFixer.jar

Java will load our patched version of Win32PrintService instead of the original one - and - tata - your program no longer suffers from

javax.print.PrintException: Printer is not accepting job
exceptions.

Best will be you create a jar file per JRE/JDK version you use and carefully use the right version, else you might encounter more problems than you are going to fix.

If you would like to fix the PrintService for your platform, try to find the Class name of the platform dependent PrintService implementation and see if there is a method like "getPrinterIsAcceptingJobs". Change the helper class above accordingly and run it.

Another option to fix that problem is to use AspectJ with runtime weaving, but I often found myself to be too impatient with the resulting slower startup times of the application.
Anyway, you know what you should looking for now and it should be easy enough to create a pointcut.

Happy printing!

1 Kommentar: