1 /*
   2  * Copyright (c) 2000, 2012, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.print;
  27 
  28 import java.net.URI;
  29 import java.net.URL;
  30 import java.io.BufferedInputStream;
  31 import java.io.BufferedOutputStream;
  32 import java.io.BufferedReader;
  33 import java.io.BufferedWriter;
  34 import java.io.File;
  35 import java.io.FileOutputStream;
  36 import java.io.InputStream;
  37 import java.io.InputStreamReader;
  38 import java.io.OutputStream;
  39 import java.io.OutputStreamWriter;
  40 import java.io.IOException;
  41 import java.io.PrintWriter;
  42 import java.io.Reader;
  43 import java.io.StringWriter;
  44 import java.io.UnsupportedEncodingException;
  45 import java.nio.file.Files;
  46 import java.util.Vector;
  47 
  48 import javax.print.CancelablePrintJob;
  49 import javax.print.Doc;
  50 import javax.print.DocFlavor;
  51 import javax.print.DocPrintJob;
  52 import javax.print.PrintService;
  53 import javax.print.PrintException;
  54 import javax.print.event.PrintJobEvent;
  55 import javax.print.event.PrintJobListener;
  56 import javax.print.event.PrintJobAttributeListener;
  57 
  58 import javax.print.attribute.Attribute;
  59 import javax.print.attribute.AttributeSet;
  60 import javax.print.attribute.AttributeSetUtilities;
  61 import javax.print.attribute.DocAttributeSet;
  62 import javax.print.attribute.HashPrintJobAttributeSet;
  63 import javax.print.attribute.HashPrintRequestAttributeSet;
  64 import javax.print.attribute.PrintJobAttribute;
  65 import javax.print.attribute.PrintJobAttributeSet;
  66 import javax.print.attribute.PrintRequestAttribute;
  67 import javax.print.attribute.PrintRequestAttributeSet;
  68 import javax.print.attribute.PrintServiceAttributeSet;
  69 import javax.print.attribute.standard.Copies;
  70 import javax.print.attribute.standard.Destination;
  71 import javax.print.attribute.standard.DocumentName;
  72 import javax.print.attribute.standard.Fidelity;
  73 import javax.print.attribute.standard.JobName;
  74 import javax.print.attribute.standard.JobOriginatingUserName;
  75 import javax.print.attribute.standard.JobSheets;
  76 import javax.print.attribute.standard.Media;
  77 import javax.print.attribute.standard.MediaSize;
  78 import javax.print.attribute.standard.MediaSizeName;
  79 import javax.print.attribute.standard.OrientationRequested;
  80 import javax.print.attribute.standard.PrinterName;
  81 import javax.print.attribute.standard.RequestingUserName;
  82 import javax.print.attribute.standard.NumberUp;
  83 import javax.print.attribute.standard.Sides;
  84 import javax.print.attribute.standard.PrinterIsAcceptingJobs;
  85 
  86 import java.awt.print.*;
  87 
  88 
  89 
  90 public class UnixPrintJob implements CancelablePrintJob {
  91     private static String debugPrefix = "UnixPrintJob>> ";
  92 
  93     transient private Vector jobListeners;
  94     transient private Vector attrListeners;
  95     transient private Vector listenedAttributeSets;
  96 
  97     private PrintService service;
  98     private boolean fidelity;
  99     private boolean printing = false;
 100     private boolean printReturned = false;
 101     private PrintRequestAttributeSet reqAttrSet = null;
 102     private PrintJobAttributeSet jobAttrSet = null;
 103     private PrinterJob job;
 104     private Doc doc;
 105     /* these variables used globally to store reference to the print
 106      * data retrieved as a stream. On completion these are always closed
 107      * if non-null.
 108      */
 109     private InputStream instream = null;
 110     private Reader reader = null;
 111 
 112     /* default values overridden by those extracted from the attributes */
 113     private String jobName = "Java Printing";
 114     private int copies = 1;
 115     private MediaSizeName mediaName = MediaSizeName.NA_LETTER;
 116     private MediaSize     mediaSize = MediaSize.NA.LETTER;
 117     private CustomMediaTray     customTray = null;
 118     private OrientationRequested orient = OrientationRequested.PORTRAIT;
 119     private NumberUp nUp = null;
 120     private Sides sides = null;
 121 
 122     UnixPrintJob(PrintService service) {
 123         this.service = service;
 124         mDestination = service.getName();
 125         if (UnixPrintServiceLookup.isMac()) {
 126             mDestination = ((IPPPrintService)service).getDest();
 127         }
 128         mDestType = UnixPrintJob.DESTPRINTER;
 129     }
 130 
 131     public PrintService getPrintService() {
 132         return service;
 133     }
 134 
 135     public PrintJobAttributeSet getAttributes() {
 136         synchronized (this) {
 137             if (jobAttrSet == null) {
 138                 /* just return an empty set until the job is submitted */
 139                 PrintJobAttributeSet jobSet = new HashPrintJobAttributeSet();
 140                 return AttributeSetUtilities.unmodifiableView(jobSet);
 141             } else {
 142               return jobAttrSet;
 143             }
 144         }
 145     }
 146 
 147     public void addPrintJobListener(PrintJobListener listener) {
 148         synchronized (this) {
 149             if (listener == null) {
 150                 return;
 151             }
 152             if (jobListeners == null) {
 153                 jobListeners = new Vector();
 154             }
 155             jobListeners.add(listener);
 156         }
 157     }
 158 
 159     public void removePrintJobListener(PrintJobListener listener) {
 160         synchronized (this) {
 161             if (listener == null || jobListeners == null ) {
 162                 return;
 163             }
 164             jobListeners.remove(listener);
 165             if (jobListeners.isEmpty()) {
 166                 jobListeners = null;
 167             }
 168         }
 169     }
 170 
 171 
 172     /* Closes any stream already retrieved for the data.
 173      * We want to avoid unnecessarily asking the Doc to create a stream only
 174      * to get a reference in order to close it because the job failed.
 175      * If the representation class is itself a "stream", this
 176      * closes that stream too.
 177      */
 178     private void closeDataStreams() {
 179 
 180         if (doc == null) {
 181             return;
 182         }
 183 
 184         Object data = null;
 185 
 186         try {
 187             data = doc.getPrintData();
 188         } catch (IOException e) {
 189             return;
 190         }
 191 
 192         if (instream != null) {
 193             try {
 194                 instream.close();
 195             } catch (IOException e) {
 196             } finally {
 197                 instream = null;
 198             }
 199         }
 200         else if (reader != null) {
 201             try {
 202                 reader.close();
 203             } catch (IOException e) {
 204             } finally {
 205                 reader = null;
 206             }
 207         }
 208         else if (data instanceof InputStream) {
 209             try {
 210                 ((InputStream)data).close();
 211             } catch (IOException e) {
 212             }
 213         }
 214         else if (data instanceof Reader) {
 215             try {
 216                 ((Reader)data).close();
 217             } catch (IOException e) {
 218             }
 219         }
 220     }
 221 
 222     private void notifyEvent(int reason) {
 223 
 224         /* since this method should always get called, here's where
 225          * we will perform the clean up of any data stream supplied.
 226          */
 227         switch (reason) {
 228             case PrintJobEvent.DATA_TRANSFER_COMPLETE:
 229             case PrintJobEvent.JOB_CANCELED :
 230             case PrintJobEvent.JOB_FAILED :
 231             case PrintJobEvent.NO_MORE_EVENTS :
 232             case PrintJobEvent.JOB_COMPLETE :
 233                 closeDataStreams();
 234         }
 235 
 236         synchronized (this) {
 237             if (jobListeners != null) {
 238                 PrintJobListener listener;
 239                 PrintJobEvent event = new PrintJobEvent(this, reason);
 240                 for (int i = 0; i < jobListeners.size(); i++) {
 241                     listener = (PrintJobListener)(jobListeners.elementAt(i));
 242                     switch (reason) {
 243 
 244                         case PrintJobEvent.JOB_CANCELED :
 245                             listener.printJobCanceled(event);
 246                             break;
 247 
 248                         case PrintJobEvent.JOB_FAILED :
 249                             listener.printJobFailed(event);
 250                             break;
 251 
 252                         case PrintJobEvent.DATA_TRANSFER_COMPLETE :
 253                             listener.printDataTransferCompleted(event);
 254                             break;
 255 
 256                         case PrintJobEvent.NO_MORE_EVENTS :
 257                             listener.printJobNoMoreEvents(event);
 258                             break;
 259 
 260                         default:
 261                             break;
 262                     }
 263                 }
 264             }
 265        }
 266     }
 267 
 268     public void addPrintJobAttributeListener(
 269                                   PrintJobAttributeListener listener,
 270                                   PrintJobAttributeSet attributes) {
 271         synchronized (this) {
 272             if (listener == null) {
 273                 return;
 274             }
 275             if (attrListeners == null) {
 276                 attrListeners = new Vector();
 277                 listenedAttributeSets = new Vector();
 278             }
 279             attrListeners.add(listener);
 280             if (attributes == null) {
 281                 attributes = new HashPrintJobAttributeSet();
 282             }
 283             listenedAttributeSets.add(attributes);
 284         }
 285     }
 286 
 287     public void removePrintJobAttributeListener(
 288                                         PrintJobAttributeListener listener) {
 289         synchronized (this) {
 290             if (listener == null || attrListeners == null ) {
 291                 return;
 292             }
 293             int index = attrListeners.indexOf(listener);
 294             if (index == -1) {
 295                 return;
 296             } else {
 297                 attrListeners.remove(index);
 298                 listenedAttributeSets.remove(index);
 299                 if (attrListeners.isEmpty()) {
 300                     attrListeners = null;
 301                     listenedAttributeSets = null;
 302                 }
 303             }
 304         }
 305     }
 306 
 307     public void print(Doc doc, PrintRequestAttributeSet attributes)
 308         throws PrintException {
 309 
 310         synchronized (this) {
 311             if (printing) {
 312                 throw new PrintException("already printing");
 313             } else {
 314                 printing = true;
 315             }
 316         }
 317 
 318         if ((PrinterIsAcceptingJobs)(service.getAttribute(
 319                          PrinterIsAcceptingJobs.class)) ==
 320                          PrinterIsAcceptingJobs.NOT_ACCEPTING_JOBS) {
 321             throw new PrintException("Printer is not accepting job.");
 322         }
 323 
 324         this.doc = doc;
 325         /* check if the parameters are valid before doing much processing */
 326         DocFlavor flavor = doc.getDocFlavor();
 327 
 328         Object data;
 329 
 330         try {
 331             data = doc.getPrintData();
 332         } catch (IOException e) {
 333             notifyEvent(PrintJobEvent.JOB_FAILED);
 334             throw new PrintException("can't get print data: " + e.toString());
 335         }
 336 
 337         if (data == null) {
 338             throw new PrintException("Null print data.");
 339         }
 340 
 341         if (flavor == null || (!service.isDocFlavorSupported(flavor))) {
 342             notifyEvent(PrintJobEvent.JOB_FAILED);
 343             throw new PrintJobFlavorException("invalid flavor", flavor);
 344         }
 345 
 346         initializeAttributeSets(doc, attributes);
 347 
 348         getAttributeValues(flavor);
 349 
 350         // set up mOptions
 351         if ((service instanceof IPPPrintService) &&
 352             CUPSPrinter.isCupsRunning()) {
 353 
 354              IPPPrintService.debug_println(debugPrefix+
 355                         "instanceof IPPPrintService");
 356 
 357              if (mediaName != null) {
 358                  CustomMediaSizeName customMedia =
 359                      ((IPPPrintService)service).findCustomMedia(mediaName);
 360                  if (customMedia != null) {
 361                      mOptions = " media="+ customMedia.getChoiceName();
 362                  }
 363              }
 364 
 365              if (customTray != null &&
 366                  customTray instanceof CustomMediaTray) {
 367                  String choice = customTray.getChoiceName();
 368                  if (choice != null) {
 369                      mOptions += " media="+choice;
 370                  }
 371              }
 372 
 373              if (nUp != null) {
 374                  mOptions += " number-up="+nUp.getValue();
 375              }
 376 
 377              if (orient != OrientationRequested.PORTRAIT &&
 378                  (flavor != null) &&
 379                  !flavor.equals(DocFlavor.SERVICE_FORMATTED.PAGEABLE)) {
 380                  mOptions += " orientation-requested="+orient.getValue();
 381              }
 382 
 383              if (sides != null) {
 384                  mOptions += " sides="+sides;
 385              }
 386 
 387         }
 388 
 389         IPPPrintService.debug_println(debugPrefix+"mOptions "+mOptions);
 390         String repClassName = flavor.getRepresentationClassName();
 391         String val = flavor.getParameter("charset");
 392         String encoding = "us-ascii";
 393         if (val != null && !val.equals("")) {
 394             encoding = val;
 395         }
 396 
 397         if (flavor.equals(DocFlavor.INPUT_STREAM.GIF) ||
 398             flavor.equals(DocFlavor.INPUT_STREAM.JPEG) ||
 399             flavor.equals(DocFlavor.INPUT_STREAM.PNG) ||
 400             flavor.equals(DocFlavor.BYTE_ARRAY.GIF) ||
 401             flavor.equals(DocFlavor.BYTE_ARRAY.JPEG) ||
 402             flavor.equals(DocFlavor.BYTE_ARRAY.PNG)) {
 403             try {
 404                 instream = doc.getStreamForBytes();
 405                 if (instream == null) {
 406                     notifyEvent(PrintJobEvent.JOB_FAILED);
 407                     throw new PrintException("No stream for data");
 408                 }
 409                 if (!(service instanceof IPPPrintService &&
 410                     ((IPPPrintService)service).isIPPSupportedImages(
 411                                                 flavor.getMimeType()))) {
 412                     printableJob(new ImagePrinter(instream));
 413                     ((UnixPrintService)service).wakeNotifier();
 414                     return;
 415                 }
 416             } catch (ClassCastException cce) {
 417                 notifyEvent(PrintJobEvent.JOB_FAILED);
 418                 throw new PrintException(cce);
 419             } catch (IOException ioe) {
 420                 notifyEvent(PrintJobEvent.JOB_FAILED);
 421                 throw new PrintException(ioe);
 422             }
 423         } else if (flavor.equals(DocFlavor.URL.GIF) ||
 424                    flavor.equals(DocFlavor.URL.JPEG) ||
 425                    flavor.equals(DocFlavor.URL.PNG)) {
 426             try {
 427                 URL url = (URL)data;
 428                 if ((service instanceof IPPPrintService) &&
 429                     ((IPPPrintService)service).isIPPSupportedImages(
 430                                                flavor.getMimeType())) {
 431                     instream = url.openStream();
 432                 } else {
 433                     printableJob(new ImagePrinter(url));
 434                     ((UnixPrintService)service).wakeNotifier();
 435                     return;
 436                 }
 437             } catch (ClassCastException cce) {
 438                 notifyEvent(PrintJobEvent.JOB_FAILED);
 439                 throw new PrintException(cce);
 440             } catch (IOException e) {
 441                 notifyEvent(PrintJobEvent.JOB_FAILED);
 442                 throw new PrintException(e.toString());
 443             }
 444         } else if (flavor.equals(DocFlavor.CHAR_ARRAY.TEXT_PLAIN) ||
 445                    flavor.equals(DocFlavor.READER.TEXT_PLAIN) ||
 446                    flavor.equals(DocFlavor.STRING.TEXT_PLAIN)) {
 447             try {
 448                 reader = doc.getReaderForText();
 449                 if (reader == null) {
 450                    notifyEvent(PrintJobEvent.JOB_FAILED);
 451                    throw new PrintException("No reader for data");
 452                 }
 453             } catch (IOException ioe) {
 454                 notifyEvent(PrintJobEvent.JOB_FAILED);
 455                 throw new PrintException(ioe.toString());
 456             }
 457         } else if (repClassName.equals("[B") ||
 458                    repClassName.equals("java.io.InputStream")) {
 459             try {
 460                 instream = doc.getStreamForBytes();
 461                 if (instream == null) {
 462                     notifyEvent(PrintJobEvent.JOB_FAILED);
 463                     throw new PrintException("No stream for data");
 464                 }
 465             } catch (IOException ioe) {
 466                 notifyEvent(PrintJobEvent.JOB_FAILED);
 467                 throw new PrintException(ioe.toString());
 468             }
 469         } else if  (repClassName.equals("java.net.URL")) {
 470             /*
 471              * This extracts the data from the URL and passes it the content
 472              * directly to the print service as a file.
 473              * This is appropriate for the current implementation where lp or
 474              * lpr is always used to spool the data. We expect to revise the
 475              * implementation to provide more complete IPP support (ie not just
 476              * CUPS) and at that time the job will be spooled via IPP
 477              * and the URL
 478              * itself should be sent to the IPP print service not the content.
 479              */
 480             URL url = (URL)data;
 481             try {
 482                 instream = url.openStream();
 483             } catch (IOException e) {
 484                 notifyEvent(PrintJobEvent.JOB_FAILED);
 485                 throw new PrintException(e.toString());
 486             }
 487         } else if (repClassName.equals("java.awt.print.Pageable")) {
 488             try {
 489                 pageableJob((Pageable)doc.getPrintData());
 490                 if (service instanceof IPPPrintService) {
 491                     ((IPPPrintService)service).wakeNotifier();
 492                 } else {
 493                     ((UnixPrintService)service).wakeNotifier();
 494                 }
 495                 return;
 496             } catch (ClassCastException cce) {
 497                 notifyEvent(PrintJobEvent.JOB_FAILED);
 498                 throw new PrintException(cce);
 499             } catch (IOException ioe) {
 500                 notifyEvent(PrintJobEvent.JOB_FAILED);
 501                 throw new PrintException(ioe);
 502             }
 503         } else if (repClassName.equals("java.awt.print.Printable")) {
 504             try {
 505                 printableJob((Printable)doc.getPrintData());
 506                 if (service instanceof IPPPrintService) {
 507                     ((IPPPrintService)service).wakeNotifier();
 508                 } else {
 509                     ((UnixPrintService)service).wakeNotifier();
 510                 }
 511                 return;
 512             } catch (ClassCastException cce) {
 513                 notifyEvent(PrintJobEvent.JOB_FAILED);
 514                 throw new PrintException(cce);
 515             } catch (IOException ioe) {
 516                 notifyEvent(PrintJobEvent.JOB_FAILED);
 517                 throw new PrintException(ioe);
 518             }
 519         } else {
 520             notifyEvent(PrintJobEvent.JOB_FAILED);
 521             throw new PrintException("unrecognized class: "+repClassName);
 522         }
 523 
 524         // now spool the print data.
 525         PrinterOpener po = new PrinterOpener();
 526         java.security.AccessController.doPrivileged(po);
 527         if (po.pex != null) {
 528             throw po.pex;
 529         }
 530         OutputStream output = po.result;
 531 
 532         /* There are three cases:
 533          * 1) Text data from a Reader, just pass through.
 534          * 2) Text data from an input stream which we must read using the
 535          *    correct encoding
 536          * 3) Raw byte data from an InputStream we don't interpret as text,
 537          *    just pass through: eg postscript.
 538          */
 539 
 540         BufferedWriter bw = null;
 541         if ((instream == null && reader != null)) {
 542             BufferedReader br = new BufferedReader(reader);
 543             OutputStreamWriter osw = new OutputStreamWriter(output);
 544             bw = new BufferedWriter(osw);
 545             char []buffer = new char[1024];
 546             int cread;
 547 
 548             try {
 549                 while ((cread = br.read(buffer, 0, buffer.length)) >=0) {
 550                     bw.write(buffer, 0, cread);
 551                 }
 552                 br.close();
 553                 bw.flush();
 554                 bw.close();
 555             } catch (IOException e) {
 556                 notifyEvent(PrintJobEvent.JOB_FAILED);
 557                 throw new PrintException (e);
 558             }
 559         } else if (instream != null &&
 560                    flavor.getMediaType().equalsIgnoreCase("text")) {
 561             try {
 562 
 563                 InputStreamReader isr = new InputStreamReader(instream,
 564                                                               encoding);
 565                 BufferedReader br = new BufferedReader(isr);
 566                 OutputStreamWriter osw = new OutputStreamWriter(output);
 567                 bw = new BufferedWriter(osw);
 568                 char []buffer = new char[1024];
 569                 int cread;
 570 
 571                 while ((cread = br.read(buffer, 0, buffer.length)) >=0) {
 572                     bw.write(buffer, 0, cread);
 573                 }
 574                 bw.flush();
 575             } catch (IOException e) {
 576                 notifyEvent(PrintJobEvent.JOB_FAILED);
 577                 throw new PrintException (e);
 578             } finally {
 579                 try {
 580                     if (bw != null) {
 581                         bw.close();
 582                     }
 583                 } catch (IOException e) {
 584                 }
 585             }
 586         } else if (instream != null) {
 587             BufferedInputStream bin = new BufferedInputStream(instream);
 588             BufferedOutputStream bout = new BufferedOutputStream(output);
 589             byte[] buffer = new byte[1024];
 590             int bread = 0;
 591 
 592             try {
 593                 while ((bread = bin.read(buffer)) >= 0) {
 594                     bout.write(buffer, 0, bread);
 595                 }
 596                 bin.close();
 597                 bout.flush();
 598                 bout.close();
 599             } catch (IOException e) {
 600                 notifyEvent(PrintJobEvent.JOB_FAILED);
 601                 throw new PrintException (e);
 602             }
 603         }
 604         notifyEvent(PrintJobEvent.DATA_TRANSFER_COMPLETE);
 605 
 606         if (mDestType == UnixPrintJob.DESTPRINTER) {
 607             PrinterSpooler spooler = new PrinterSpooler();
 608             java.security.AccessController.doPrivileged(spooler);
 609             if (spooler.pex != null) {
 610                 throw spooler.pex;
 611             }
 612         }
 613         notifyEvent(PrintJobEvent.NO_MORE_EVENTS);
 614         if (service instanceof IPPPrintService) {
 615             ((IPPPrintService)service).wakeNotifier();
 616         } else {
 617             ((UnixPrintService)service).wakeNotifier();
 618         }
 619     }
 620 
 621     public void printableJob(Printable printable) throws PrintException {
 622         try {
 623             synchronized(this) {
 624                 if (job != null) { // shouldn't happen
 625                     throw new PrintException("already printing");
 626                 } else {
 627                     job = new PSPrinterJob();
 628                 }
 629             }
 630             job.setPrintService(getPrintService());
 631             job.setCopies(copies);
 632             job.setJobName(jobName);
 633             PageFormat pf = new PageFormat();
 634             if (mediaSize != null) {
 635                 Paper p = new Paper();
 636                 p.setSize(mediaSize.getX(MediaSize.INCH)*72.0,
 637                           mediaSize.getY(MediaSize.INCH)*72.0);
 638                 p.setImageableArea(72.0, 72.0, p.getWidth()-144.0,
 639                                    p.getHeight()-144.0);
 640                 pf.setPaper(p);
 641             }
 642             if (orient == OrientationRequested.REVERSE_LANDSCAPE) {
 643                 pf.setOrientation(PageFormat.REVERSE_LANDSCAPE);
 644             } else if (orient == OrientationRequested.LANDSCAPE) {
 645                 pf.setOrientation(PageFormat.LANDSCAPE);
 646             }
 647             job.setPrintable(printable, pf);
 648             job.print(reqAttrSet);
 649             notifyEvent(PrintJobEvent.DATA_TRANSFER_COMPLETE);
 650             return;
 651         } catch (PrinterException pe) {
 652             notifyEvent(PrintJobEvent.JOB_FAILED);
 653             throw new PrintException(pe);
 654         } finally {
 655             printReturned = true;
 656             notifyEvent(PrintJobEvent.NO_MORE_EVENTS);
 657         }
 658     }
 659 
 660     public void pageableJob(Pageable pageable) throws PrintException {
 661         try {
 662             synchronized(this) {
 663                 if (job != null) { // shouldn't happen
 664                     throw new PrintException("already printing");
 665                 } else {
 666                     job = new PSPrinterJob();
 667                 }
 668             }
 669             job.setPrintService(getPrintService());
 670             job.setCopies(copies);
 671             job.setJobName(jobName);
 672             job.setPageable(pageable);
 673             job.print(reqAttrSet);
 674             notifyEvent(PrintJobEvent.DATA_TRANSFER_COMPLETE);
 675             return;
 676         } catch (PrinterException pe) {
 677             notifyEvent(PrintJobEvent.JOB_FAILED);
 678             throw new PrintException(pe);
 679         } finally {
 680             printReturned = true;
 681             notifyEvent(PrintJobEvent.NO_MORE_EVENTS);
 682         }
 683     }
 684     /* There's some inefficiency here as the job set is created even though
 685      * it may never be requested.
 686      */
 687     private synchronized void
 688         initializeAttributeSets(Doc doc, PrintRequestAttributeSet reqSet) {
 689 
 690         reqAttrSet = new HashPrintRequestAttributeSet();
 691         jobAttrSet = new HashPrintJobAttributeSet();
 692 
 693         Attribute[] attrs;
 694         if (reqSet != null) {
 695             reqAttrSet.addAll(reqSet);
 696             attrs = reqSet.toArray();
 697             for (int i=0; i<attrs.length; i++) {
 698                 if (attrs[i] instanceof PrintJobAttribute) {
 699                     jobAttrSet.add(attrs[i]);
 700                 }
 701             }
 702         }
 703 
 704         DocAttributeSet docSet = doc.getAttributes();
 705         if (docSet != null) {
 706             attrs = docSet.toArray();
 707             for (int i=0; i<attrs.length; i++) {
 708                 if (attrs[i] instanceof PrintRequestAttribute) {
 709                     reqAttrSet.add(attrs[i]);
 710                 }
 711                 if (attrs[i] instanceof PrintJobAttribute) {
 712                     jobAttrSet.add(attrs[i]);
 713                 }
 714             }
 715         }
 716 
 717         /* add the user name to the job */
 718         String userName = "";
 719         try {
 720           userName = System.getProperty("user.name");
 721         } catch (SecurityException se) {
 722         }
 723 
 724         if (userName == null || userName.equals("")) {
 725             RequestingUserName ruName =
 726                 (RequestingUserName)reqSet.get(RequestingUserName.class);
 727             if (ruName != null) {
 728                 jobAttrSet.add(
 729                     new JobOriginatingUserName(ruName.getValue(),
 730                                                ruName.getLocale()));
 731             } else {
 732                 jobAttrSet.add(new JobOriginatingUserName("", null));
 733             }
 734         } else {
 735             jobAttrSet.add(new JobOriginatingUserName(userName, null));
 736         }
 737 
 738         /* if no job name supplied use doc name (if supplied), if none and
 739          * its a URL use that, else finally anything .. */
 740         if (jobAttrSet.get(JobName.class) == null) {
 741             JobName jobName;
 742             if (docSet != null && docSet.get(DocumentName.class) != null) {
 743                 DocumentName docName =
 744                     (DocumentName)docSet.get(DocumentName.class);
 745                 jobName = new JobName(docName.getValue(), docName.getLocale());
 746                 jobAttrSet.add(jobName);
 747             } else {
 748                 String str = "JPS Job:" + doc;
 749                 try {
 750                     Object printData = doc.getPrintData();
 751                     if (printData instanceof URL) {
 752                         str = ((URL)(doc.getPrintData())).toString();
 753                     }
 754                 } catch (IOException e) {
 755                 }
 756                 jobName = new JobName(str, null);
 757                 jobAttrSet.add(jobName);
 758             }
 759         }
 760 
 761         jobAttrSet = AttributeSetUtilities.unmodifiableView(jobAttrSet);
 762     }
 763 
 764     private void getAttributeValues(DocFlavor flavor) throws PrintException {
 765         Attribute attr;
 766         Class category;
 767 
 768         if (reqAttrSet.get(Fidelity.class) == Fidelity.FIDELITY_TRUE) {
 769             fidelity = true;
 770         } else {
 771             fidelity = false;
 772         }
 773 
 774         Attribute []attrs = reqAttrSet.toArray();
 775         for (int i=0; i<attrs.length; i++) {
 776             attr = attrs[i];
 777             category = attr.getCategory();
 778             if (fidelity == true) {
 779                 if (!service.isAttributeCategorySupported(category)) {
 780                     notifyEvent(PrintJobEvent.JOB_FAILED);
 781                     throw new PrintJobAttributeException(
 782                         "unsupported category: " + category, category, null);
 783                 } else if
 784                     (!service.isAttributeValueSupported(attr, flavor, null)) {
 785                     notifyEvent(PrintJobEvent.JOB_FAILED);
 786                     throw new PrintJobAttributeException(
 787                         "unsupported attribute: " + attr, null, attr);
 788                 }
 789             }
 790             if (category == Destination.class) {
 791                 URI uri = ((Destination)attr).getURI();
 792                 if (!"file".equals(uri.getScheme())) {
 793                     notifyEvent(PrintJobEvent.JOB_FAILED);
 794                     throw new PrintException("Not a file: URI");
 795                 } else {
 796                     try {
 797                         mDestType = DESTFILE;
 798                         mDestination = (new File(uri)).getPath();
 799                     } catch (Exception e) {
 800                         throw new PrintException(e);
 801                     }
 802                     // check write access
 803                     SecurityManager security = System.getSecurityManager();
 804                     if (security != null) {
 805                       try {
 806                         security.checkWrite(mDestination);
 807                       } catch (SecurityException se) {
 808                         notifyEvent(PrintJobEvent.JOB_FAILED);
 809                         throw new PrintException(se);
 810                       }
 811                     }
 812                 }
 813             } else if (category == JobSheets.class) {
 814                 if ((JobSheets)attr == JobSheets.NONE) {
 815                    mNoJobSheet = true;
 816                 }
 817             } else if (category == JobName.class) {
 818                 jobName = ((JobName)attr).getValue();
 819             } else if (category == Copies.class) {
 820                 copies = ((Copies)attr).getValue();
 821             } else if (category == Media.class) {
 822                 if (attr instanceof MediaSizeName) {
 823                     mediaName = (MediaSizeName)attr;
 824                     IPPPrintService.debug_println(debugPrefix+
 825                                                   "mediaName "+mediaName);
 826                 if (!service.isAttributeValueSupported(attr, null, null)) {
 827                     mediaSize = MediaSize.getMediaSizeForName(mediaName);
 828                 }
 829               } else if (attr instanceof CustomMediaTray) {
 830                   customTray = (CustomMediaTray)attr;
 831               }
 832             } else if (category == OrientationRequested.class) {
 833                 orient = (OrientationRequested)attr;
 834             } else if (category == NumberUp.class) {
 835                 nUp = (NumberUp)attr;
 836             } else if (category == Sides.class) {
 837                 sides = (Sides)attr;
 838             }
 839         }
 840     }
 841 
 842     private String[] printExecCmd(String printer, String options,
 843                                  boolean noJobSheet,
 844                                  String banner, int copies, String spoolFile) {
 845         int PRINTER = 0x1;
 846         int OPTIONS = 0x2;
 847         int BANNER  = 0x4;
 848         int COPIES  = 0x8;
 849         int NOSHEET  = 0x10;
 850         int pFlags = 0;
 851         String execCmd[];
 852         int ncomps = 2; // minimum number of print args
 853         int n = 0;
 854 
 855         // conveniently "lp" is the default destination for both lp and lpr.
 856         if (printer != null && !printer.equals("") && !printer.equals("lp")) {
 857             pFlags |= PRINTER;
 858             ncomps+=1;
 859         }
 860         if (options != null && !options.equals("")) {
 861             pFlags |= OPTIONS;
 862             ncomps+=1;
 863         }
 864         if (banner != null && !banner.equals("")) {
 865             pFlags |= BANNER;
 866             ncomps+=1;
 867         }
 868         if (copies > 1) {
 869             pFlags |= COPIES;
 870             ncomps+=1;
 871         }
 872         if (noJobSheet) {
 873             pFlags |= NOSHEET;
 874             ncomps+=1;
 875         }
 876         if (UnixPrintServiceLookup.osname.equals("SunOS")) {
 877             ncomps+=1; // lp uses 1 more arg than lpr (make a copy)
 878             execCmd = new String[ncomps];
 879             execCmd[n++] = "/usr/bin/lp";
 880             execCmd[n++] = "-c";           // make a copy of the spool file
 881             if ((pFlags & PRINTER) != 0) {
 882                 execCmd[n++] = "-d" + printer;
 883             }
 884             if ((pFlags & BANNER) != 0) {
 885                 String quoteChar = "\"";
 886                 execCmd[n++] = "-t "  + quoteChar+banner+quoteChar;
 887             }
 888             if ((pFlags & COPIES) != 0) {
 889                 execCmd[n++] = "-n " + copies;
 890             }
 891             if ((pFlags & NOSHEET) != 0) {
 892                 execCmd[n++] = "-o nobanner";
 893             }
 894             if ((pFlags & OPTIONS) != 0) {
 895                 execCmd[n++] = "-o " + options;
 896             }
 897         } else {
 898             execCmd = new String[ncomps];
 899             execCmd[n++] = "/usr/bin/lpr";
 900             if ((pFlags & PRINTER) != 0) {
 901                 execCmd[n++] = "-P" + printer;
 902             }
 903             if ((pFlags & BANNER) != 0) {
 904                 execCmd[n++] = "-J "  + banner;
 905             }
 906             if ((pFlags & COPIES) != 0) {
 907                 execCmd[n++] = "-#" + copies;
 908             }
 909             if ((pFlags & NOSHEET) != 0) {
 910                 execCmd[n++] = "-h";
 911             }
 912             if ((pFlags & OPTIONS) != 0) {
 913                 execCmd[n++] = "-o" + options;
 914             }
 915         }
 916         execCmd[n++] = spoolFile;
 917         if (IPPPrintService.debugPrint) {
 918             System.out.println("UnixPrintJob>> execCmd");
 919             for (int i=0; i<execCmd.length; i++) {
 920                 System.out.print(" "+execCmd[i]);
 921             }
 922             System.out.println();
 923         }
 924         return execCmd;
 925     }
 926 
 927     private static int DESTPRINTER = 1;
 928     private static int DESTFILE = 2;
 929     private int mDestType = DESTPRINTER;
 930 
 931     private File spoolFile;
 932     private String mDestination, mOptions="";
 933     private boolean mNoJobSheet = false;
 934 
 935     // Inner class to run "privileged" to open the printer output stream.
 936 
 937     private class PrinterOpener implements java.security.PrivilegedAction {
 938         PrintException pex;
 939         OutputStream result;
 940 
 941         public Object run() {
 942             try {
 943                 if (mDestType == UnixPrintJob.DESTFILE) {
 944                     spoolFile = new File(mDestination);
 945                 } else {
 946                     /* Write to a temporary file which will be spooled to
 947                      * the printer then deleted. In the case that the file
 948                      * is not removed for some reason, request that it is
 949                      * removed when the VM exits.
 950                      */
 951                     spoolFile = Files.createTempFile("javaprint", "").toFile();
 952                     spoolFile.deleteOnExit();
 953                 }
 954                 result = new FileOutputStream(spoolFile);
 955                 return result;
 956             } catch (IOException ex) {
 957                 // If there is an IOError we subvert it to a PrinterException.
 958                 notifyEvent(PrintJobEvent.JOB_FAILED);
 959                 pex = new PrintException(ex);
 960             }
 961             return null;
 962         }
 963     }
 964 
 965     // Inner class to run "privileged" to invoke the system print command
 966 
 967     private class PrinterSpooler implements java.security.PrivilegedAction {
 968         PrintException pex;
 969 
 970         private void handleProcessFailure(final Process failedProcess,
 971                 final String[] execCmd, final int result) throws IOException {
 972             try (StringWriter sw = new StringWriter();
 973                     PrintWriter pw = new PrintWriter(sw)) {
 974                 pw.append("error=").append(Integer.toString(result));
 975                 pw.append(" running:");
 976                 for (String arg: execCmd) {
 977                     pw.append(" '").append(arg).append("'");
 978                 }
 979                 try (InputStream is = failedProcess.getErrorStream();
 980                         InputStreamReader isr = new InputStreamReader(is);
 981                         BufferedReader br = new BufferedReader(isr)) {
 982                     while (br.ready()) {
 983                         pw.println();
 984                         pw.append("\t\t").append(br.readLine());
 985                     }
 986                 } finally {
 987                     pw.flush();
 988                     throw new IOException(sw.toString());
 989                 }
 990             }
 991         }
 992 
 993         public Object run() {
 994             if (spoolFile == null || !spoolFile.exists()) {
 995                pex = new PrintException("No spool file");
 996                notifyEvent(PrintJobEvent.JOB_FAILED);
 997                return null;
 998             }
 999             try {
1000                 /**
1001                  * Spool to the printer.
1002                  */
1003                 String fileName = spoolFile.getAbsolutePath();
1004                 String execCmd[] = printExecCmd(mDestination, mOptions,
1005                                mNoJobSheet, jobName, copies, fileName);
1006 
1007                 Process process = Runtime.getRuntime().exec(execCmd);
1008                 process.waitFor();
1009                 final int result = process.exitValue();
1010                 if (0 != result) {
1011                     handleProcessFailure(process, execCmd, result);
1012                 }
1013                 notifyEvent(PrintJobEvent.DATA_TRANSFER_COMPLETE);
1014             } catch (IOException ex) {
1015                 notifyEvent(PrintJobEvent.JOB_FAILED);
1016                 // REMIND : 2d printing throws PrinterException
1017                 pex = new PrintException(ex);
1018             } catch (InterruptedException ie) {
1019                 notifyEvent(PrintJobEvent.JOB_FAILED);
1020                 pex = new PrintException(ie);
1021             } finally {
1022                 spoolFile.delete();
1023                 notifyEvent(PrintJobEvent.NO_MORE_EVENTS);
1024             }
1025             return null;
1026         }
1027     }
1028 
1029     public void cancel() throws PrintException {
1030         synchronized (this) {
1031             if (!printing) {
1032                 throw new PrintException("Job is not yet submitted.");
1033             } else if (job != null && !printReturned) {
1034                 job.cancel();
1035                 notifyEvent(PrintJobEvent.JOB_CANCELED);
1036                 return;
1037             } else {
1038                 throw new PrintException("Job could not be cancelled.");
1039             }
1040         }
1041     }
1042 }