1 /*
   2  * Copyright (c) 2000, 2014, 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 ((service.getAttribute(PrinterIsAcceptingJobs.class)) ==
 319                          PrinterIsAcceptingJobs.NOT_ACCEPTING_JOBS) {
 320             throw new PrintException("Printer is not accepting job.");
 321         }
 322 
 323         this.doc = doc;
 324         /* check if the parameters are valid before doing much processing */
 325         DocFlavor flavor = doc.getDocFlavor();
 326 
 327         Object data;
 328 
 329         try {
 330             data = doc.getPrintData();
 331         } catch (IOException e) {
 332             notifyEvent(PrintJobEvent.JOB_FAILED);
 333             throw new PrintException("can't get print data: " + e.toString());
 334         }
 335 
 336         if (data == null) {
 337             throw new PrintException("Null print data.");
 338         }
 339 
 340         if (flavor == null || (!service.isDocFlavorSupported(flavor))) {
 341             notifyEvent(PrintJobEvent.JOB_FAILED);
 342             throw new PrintJobFlavorException("invalid flavor", flavor);
 343         }
 344 
 345         initializeAttributeSets(doc, attributes);
 346 
 347         getAttributeValues(flavor);
 348 
 349         // set up mOptions
 350         if ((service instanceof IPPPrintService) &&
 351             CUPSPrinter.isCupsRunning()) {
 352 
 353              IPPPrintService.debug_println(debugPrefix+
 354                         "instanceof IPPPrintService");
 355 
 356              if (mediaName != null) {
 357                  CustomMediaSizeName customMedia =
 358                      ((IPPPrintService)service).findCustomMedia(mediaName);
 359                  if (customMedia != null) {
 360                      mOptions = " media="+ customMedia.getChoiceName();
 361                  }
 362              }
 363 
 364              if (customTray != null &&
 365                  customTray instanceof CustomMediaTray) {
 366                  String choice = customTray.getChoiceName();
 367                  if (choice != null) {
 368                      mOptions += " media="+choice;
 369                  }
 370              }
 371 
 372              if (nUp != null) {
 373                  mOptions += " number-up="+nUp.getValue();
 374              }
 375 
 376              if (orient != OrientationRequested.PORTRAIT &&
 377                  (flavor != null) &&
 378                  !flavor.equals(DocFlavor.SERVICE_FORMATTED.PAGEABLE)) {
 379                  mOptions += " orientation-requested="+orient.getValue();
 380              }
 381 
 382              if (sides != null) {
 383                  mOptions += " sides="+sides;
 384              }
 385 
 386         }
 387 
 388         IPPPrintService.debug_println(debugPrefix+"mOptions "+mOptions);
 389         String repClassName = flavor.getRepresentationClassName();
 390         String val = flavor.getParameter("charset");
 391         String encoding = "us-ascii";
 392         if (val != null && !val.equals("")) {
 393             encoding = val;
 394         }
 395 
 396         if (flavor.equals(DocFlavor.INPUT_STREAM.GIF) ||
 397             flavor.equals(DocFlavor.INPUT_STREAM.JPEG) ||
 398             flavor.equals(DocFlavor.INPUT_STREAM.PNG) ||
 399             flavor.equals(DocFlavor.BYTE_ARRAY.GIF) ||
 400             flavor.equals(DocFlavor.BYTE_ARRAY.JPEG) ||
 401             flavor.equals(DocFlavor.BYTE_ARRAY.PNG)) {
 402             try {
 403                 instream = doc.getStreamForBytes();
 404                 if (instream == null) {
 405                     notifyEvent(PrintJobEvent.JOB_FAILED);
 406                     throw new PrintException("No stream for data");
 407                 }
 408                 if (!(service instanceof IPPPrintService &&
 409                     ((IPPPrintService)service).isIPPSupportedImages(
 410                                                 flavor.getMimeType()))) {
 411                     printableJob(new ImagePrinter(instream));
 412                     if (service instanceof IPPPrintService) {
 413                         ((IPPPrintService)service).wakeNotifier();
 414                     } else {
 415                         ((UnixPrintService)service).wakeNotifier();
 416                     }
 417                     return;
 418                 }
 419             } catch (ClassCastException cce) {
 420                 notifyEvent(PrintJobEvent.JOB_FAILED);
 421                 throw new PrintException(cce);
 422             } catch (IOException ioe) {
 423                 notifyEvent(PrintJobEvent.JOB_FAILED);
 424                 throw new PrintException(ioe);
 425             }
 426         } else if (flavor.equals(DocFlavor.URL.GIF) ||
 427                    flavor.equals(DocFlavor.URL.JPEG) ||
 428                    flavor.equals(DocFlavor.URL.PNG)) {
 429             try {
 430                 URL url = (URL)data;
 431                 if ((service instanceof IPPPrintService) &&
 432                     ((IPPPrintService)service).isIPPSupportedImages(
 433                                                flavor.getMimeType())) {
 434                     instream = url.openStream();
 435                 } else {
 436                     printableJob(new ImagePrinter(url));
 437                     if (service instanceof IPPPrintService) {
 438                         ((IPPPrintService)service).wakeNotifier();
 439                     } else {
 440                         ((UnixPrintService)service).wakeNotifier();
 441                     }
 442                     return;
 443                 }
 444             } catch (ClassCastException cce) {
 445                 notifyEvent(PrintJobEvent.JOB_FAILED);
 446                 throw new PrintException(cce);
 447             } catch (IOException e) {
 448                 notifyEvent(PrintJobEvent.JOB_FAILED);
 449                 throw new PrintException(e.toString());
 450             }
 451         } else if (flavor.equals(DocFlavor.CHAR_ARRAY.TEXT_PLAIN) ||
 452                    flavor.equals(DocFlavor.READER.TEXT_PLAIN) ||
 453                    flavor.equals(DocFlavor.STRING.TEXT_PLAIN)) {
 454             try {
 455                 reader = doc.getReaderForText();
 456                 if (reader == null) {
 457                    notifyEvent(PrintJobEvent.JOB_FAILED);
 458                    throw new PrintException("No reader for data");
 459                 }
 460             } catch (IOException ioe) {
 461                 notifyEvent(PrintJobEvent.JOB_FAILED);
 462                 throw new PrintException(ioe.toString());
 463             }
 464         } else if (repClassName.equals("[B") ||
 465                    repClassName.equals("java.io.InputStream")) {
 466             try {
 467                 instream = doc.getStreamForBytes();
 468                 if (instream == null) {
 469                     notifyEvent(PrintJobEvent.JOB_FAILED);
 470                     throw new PrintException("No stream for data");
 471                 }
 472             } catch (IOException ioe) {
 473                 notifyEvent(PrintJobEvent.JOB_FAILED);
 474                 throw new PrintException(ioe.toString());
 475             }
 476         } else if  (repClassName.equals("java.net.URL")) {
 477             /*
 478              * This extracts the data from the URL and passes it the content
 479              * directly to the print service as a file.
 480              * This is appropriate for the current implementation where lp or
 481              * lpr is always used to spool the data. We expect to revise the
 482              * implementation to provide more complete IPP support (ie not just
 483              * CUPS) and at that time the job will be spooled via IPP
 484              * and the URL
 485              * itself should be sent to the IPP print service not the content.
 486              */
 487             URL url = (URL)data;
 488             try {
 489                 instream = url.openStream();
 490             } catch (IOException e) {
 491                 notifyEvent(PrintJobEvent.JOB_FAILED);
 492                 throw new PrintException(e.toString());
 493             }
 494         } else if (repClassName.equals("java.awt.print.Pageable")) {
 495             try {
 496                 pageableJob((Pageable)doc.getPrintData());
 497                 if (service instanceof IPPPrintService) {
 498                     ((IPPPrintService)service).wakeNotifier();
 499                 } else {
 500                     ((UnixPrintService)service).wakeNotifier();
 501                 }
 502                 return;
 503             } catch (ClassCastException cce) {
 504                 notifyEvent(PrintJobEvent.JOB_FAILED);
 505                 throw new PrintException(cce);
 506             } catch (IOException ioe) {
 507                 notifyEvent(PrintJobEvent.JOB_FAILED);
 508                 throw new PrintException(ioe);
 509             }
 510         } else if (repClassName.equals("java.awt.print.Printable")) {
 511             try {
 512                 printableJob((Printable)doc.getPrintData());
 513                 if (service instanceof IPPPrintService) {
 514                     ((IPPPrintService)service).wakeNotifier();
 515                 } else {
 516                     ((UnixPrintService)service).wakeNotifier();
 517                 }
 518                 return;
 519             } catch (ClassCastException cce) {
 520                 notifyEvent(PrintJobEvent.JOB_FAILED);
 521                 throw new PrintException(cce);
 522             } catch (IOException ioe) {
 523                 notifyEvent(PrintJobEvent.JOB_FAILED);
 524                 throw new PrintException(ioe);
 525             }
 526         } else {
 527             notifyEvent(PrintJobEvent.JOB_FAILED);
 528             throw new PrintException("unrecognized class: "+repClassName);
 529         }
 530 
 531         // now spool the print data.
 532         PrinterOpener po = new PrinterOpener();
 533         java.security.AccessController.doPrivileged(po);
 534         if (po.pex != null) {
 535             throw po.pex;
 536         }
 537         OutputStream output = po.result;
 538 
 539         /* There are three cases:
 540          * 1) Text data from a Reader, just pass through.
 541          * 2) Text data from an input stream which we must read using the
 542          *    correct encoding
 543          * 3) Raw byte data from an InputStream we don't interpret as text,
 544          *    just pass through: eg postscript.
 545          */
 546 
 547         BufferedWriter bw = null;
 548         if ((instream == null && reader != null)) {
 549             BufferedReader br = new BufferedReader(reader);
 550             OutputStreamWriter osw = new OutputStreamWriter(output);
 551             bw = new BufferedWriter(osw);
 552             char []buffer = new char[1024];
 553             int cread;
 554 
 555             try {
 556                 while ((cread = br.read(buffer, 0, buffer.length)) >=0) {
 557                     bw.write(buffer, 0, cread);
 558                 }
 559                 br.close();
 560                 bw.flush();
 561                 bw.close();
 562             } catch (IOException e) {
 563                 notifyEvent(PrintJobEvent.JOB_FAILED);
 564                 throw new PrintException (e);
 565             }
 566         } else if (instream != null &&
 567                    flavor.getMediaType().equalsIgnoreCase("text")) {
 568             try {
 569 
 570                 InputStreamReader isr = new InputStreamReader(instream,
 571                                                               encoding);
 572                 BufferedReader br = new BufferedReader(isr);
 573                 OutputStreamWriter osw = new OutputStreamWriter(output);
 574                 bw = new BufferedWriter(osw);
 575                 char []buffer = new char[1024];
 576                 int cread;
 577 
 578                 while ((cread = br.read(buffer, 0, buffer.length)) >=0) {
 579                     bw.write(buffer, 0, cread);
 580                 }
 581                 bw.flush();
 582             } catch (IOException e) {
 583                 notifyEvent(PrintJobEvent.JOB_FAILED);
 584                 throw new PrintException (e);
 585             } finally {
 586                 try {
 587                     if (bw != null) {
 588                         bw.close();
 589                     }
 590                 } catch (IOException e) {
 591                 }
 592             }
 593         } else if (instream != null) {
 594             BufferedInputStream bin = new BufferedInputStream(instream);
 595             BufferedOutputStream bout = new BufferedOutputStream(output);
 596             byte[] buffer = new byte[1024];
 597             int bread = 0;
 598 
 599             try {
 600                 while ((bread = bin.read(buffer)) >= 0) {
 601                     bout.write(buffer, 0, bread);
 602                 }
 603                 bin.close();
 604                 bout.flush();
 605                 bout.close();
 606             } catch (IOException e) {
 607                 notifyEvent(PrintJobEvent.JOB_FAILED);
 608                 throw new PrintException (e);
 609             }
 610         }
 611         notifyEvent(PrintJobEvent.DATA_TRANSFER_COMPLETE);
 612 
 613         if (mDestType == UnixPrintJob.DESTPRINTER) {
 614             PrinterSpooler spooler = new PrinterSpooler();
 615             java.security.AccessController.doPrivileged(spooler);
 616             if (spooler.pex != null) {
 617                 throw spooler.pex;
 618             }
 619         }
 620         notifyEvent(PrintJobEvent.NO_MORE_EVENTS);
 621         if (service instanceof IPPPrintService) {
 622             ((IPPPrintService)service).wakeNotifier();
 623         } else {
 624             ((UnixPrintService)service).wakeNotifier();
 625         }
 626     }
 627 
 628     public void printableJob(Printable printable) throws PrintException {
 629         try {
 630             synchronized(this) {
 631                 if (job != null) { // shouldn't happen
 632                     throw new PrintException("already printing");
 633                 } else {
 634                     job = new PSPrinterJob();
 635                 }
 636             }
 637             job.setPrintService(getPrintService());
 638             job.setCopies(copies);
 639             job.setJobName(jobName);
 640             PageFormat pf = new PageFormat();
 641             if (mediaSize != null) {
 642                 Paper p = new Paper();
 643                 p.setSize(mediaSize.getX(MediaSize.INCH)*72.0,
 644                           mediaSize.getY(MediaSize.INCH)*72.0);
 645                 p.setImageableArea(72.0, 72.0, p.getWidth()-144.0,
 646                                    p.getHeight()-144.0);
 647                 pf.setPaper(p);
 648             }
 649             if (orient == OrientationRequested.REVERSE_LANDSCAPE) {
 650                 pf.setOrientation(PageFormat.REVERSE_LANDSCAPE);
 651             } else if (orient == OrientationRequested.LANDSCAPE) {
 652                 pf.setOrientation(PageFormat.LANDSCAPE);
 653             }
 654             job.setPrintable(printable, pf);
 655             job.print(reqAttrSet);
 656             notifyEvent(PrintJobEvent.DATA_TRANSFER_COMPLETE);
 657             return;
 658         } catch (PrinterException pe) {
 659             notifyEvent(PrintJobEvent.JOB_FAILED);
 660             throw new PrintException(pe);
 661         } finally {
 662             printReturned = true;
 663             notifyEvent(PrintJobEvent.NO_MORE_EVENTS);
 664         }
 665     }
 666 
 667     public void pageableJob(Pageable pageable) throws PrintException {
 668         try {
 669             synchronized(this) {
 670                 if (job != null) { // shouldn't happen
 671                     throw new PrintException("already printing");
 672                 } else {
 673                     job = new PSPrinterJob();
 674                 }
 675             }
 676             job.setPrintService(getPrintService());
 677             job.setCopies(copies);
 678             job.setJobName(jobName);
 679             job.setPageable(pageable);
 680             job.print(reqAttrSet);
 681             notifyEvent(PrintJobEvent.DATA_TRANSFER_COMPLETE);
 682             return;
 683         } catch (PrinterException pe) {
 684             notifyEvent(PrintJobEvent.JOB_FAILED);
 685             throw new PrintException(pe);
 686         } finally {
 687             printReturned = true;
 688             notifyEvent(PrintJobEvent.NO_MORE_EVENTS);
 689         }
 690     }
 691     /* There's some inefficiency here as the job set is created even though
 692      * it may never be requested.
 693      */
 694     private synchronized void
 695         initializeAttributeSets(Doc doc, PrintRequestAttributeSet reqSet) {
 696 
 697         reqAttrSet = new HashPrintRequestAttributeSet();
 698         jobAttrSet = new HashPrintJobAttributeSet();
 699 
 700         Attribute[] attrs;
 701         if (reqSet != null) {
 702             reqAttrSet.addAll(reqSet);
 703             attrs = reqSet.toArray();
 704             for (int i=0; i<attrs.length; i++) {
 705                 if (attrs[i] instanceof PrintJobAttribute) {
 706                     jobAttrSet.add(attrs[i]);
 707                 }
 708             }
 709         }
 710 
 711         DocAttributeSet docSet = doc.getAttributes();
 712         if (docSet != null) {
 713             attrs = docSet.toArray();
 714             for (int i=0; i<attrs.length; i++) {
 715                 if (attrs[i] instanceof PrintRequestAttribute) {
 716                     reqAttrSet.add(attrs[i]);
 717                 }
 718                 if (attrs[i] instanceof PrintJobAttribute) {
 719                     jobAttrSet.add(attrs[i]);
 720                 }
 721             }
 722         }
 723 
 724         /* add the user name to the job */
 725         String userName = "";
 726         try {
 727           userName = System.getProperty("user.name");
 728         } catch (SecurityException se) {
 729         }
 730 
 731         if (userName == null || userName.equals("")) {
 732             RequestingUserName ruName =
 733                 (RequestingUserName)reqSet.get(RequestingUserName.class);
 734             if (ruName != null) {
 735                 jobAttrSet.add(
 736                     new JobOriginatingUserName(ruName.getValue(),
 737                                                ruName.getLocale()));
 738             } else {
 739                 jobAttrSet.add(new JobOriginatingUserName("", null));
 740             }
 741         } else {
 742             jobAttrSet.add(new JobOriginatingUserName(userName, null));
 743         }
 744 
 745         /* if no job name supplied use doc name (if supplied), if none and
 746          * its a URL use that, else finally anything .. */
 747         if (jobAttrSet.get(JobName.class) == null) {
 748             JobName jobName;
 749             if (docSet != null && docSet.get(DocumentName.class) != null) {
 750                 DocumentName docName =
 751                     (DocumentName)docSet.get(DocumentName.class);
 752                 jobName = new JobName(docName.getValue(), docName.getLocale());
 753                 jobAttrSet.add(jobName);
 754             } else {
 755                 String str = "JPS Job:" + doc;
 756                 try {
 757                     Object printData = doc.getPrintData();
 758                     if (printData instanceof URL) {
 759                         str = ((URL)(doc.getPrintData())).toString();
 760                     }
 761                 } catch (IOException e) {
 762                 }
 763                 jobName = new JobName(str, null);
 764                 jobAttrSet.add(jobName);
 765             }
 766         }
 767 
 768         jobAttrSet = AttributeSetUtilities.unmodifiableView(jobAttrSet);
 769     }
 770 
 771     private void getAttributeValues(DocFlavor flavor) throws PrintException {
 772         Attribute attr;
 773         Class category;
 774 
 775         if (reqAttrSet.get(Fidelity.class) == Fidelity.FIDELITY_TRUE) {
 776             fidelity = true;
 777         } else {
 778             fidelity = false;
 779         }
 780 
 781         Attribute []attrs = reqAttrSet.toArray();
 782         for (int i=0; i<attrs.length; i++) {
 783             attr = attrs[i];
 784             category = attr.getCategory();
 785             if (fidelity == true) {
 786                 if (!service.isAttributeCategorySupported(category)) {
 787                     notifyEvent(PrintJobEvent.JOB_FAILED);
 788                     throw new PrintJobAttributeException(
 789                         "unsupported category: " + category, category, null);
 790                 } else if
 791                     (!service.isAttributeValueSupported(attr, flavor, null)) {
 792                     notifyEvent(PrintJobEvent.JOB_FAILED);
 793                     throw new PrintJobAttributeException(
 794                         "unsupported attribute: " + attr, null, attr);
 795                 }
 796             }
 797             if (category == Destination.class) {
 798                 URI uri = ((Destination)attr).getURI();
 799                 if (!"file".equals(uri.getScheme())) {
 800                     notifyEvent(PrintJobEvent.JOB_FAILED);
 801                     throw new PrintException("Not a file: URI");
 802                 } else {
 803                     try {
 804                         mDestType = DESTFILE;
 805                         mDestination = (new File(uri)).getPath();
 806                     } catch (Exception e) {
 807                         throw new PrintException(e);
 808                     }
 809                     // check write access
 810                     SecurityManager security = System.getSecurityManager();
 811                     if (security != null) {
 812                       try {
 813                         security.checkWrite(mDestination);
 814                       } catch (SecurityException se) {
 815                         notifyEvent(PrintJobEvent.JOB_FAILED);
 816                         throw new PrintException(se);
 817                       }
 818                     }
 819                 }
 820             } else if (category == JobSheets.class) {
 821                 if ((JobSheets)attr == JobSheets.NONE) {
 822                    mNoJobSheet = true;
 823                 }
 824             } else if (category == JobName.class) {
 825                 jobName = ((JobName)attr).getValue();
 826             } else if (category == Copies.class) {
 827                 copies = ((Copies)attr).getValue();
 828             } else if (category == Media.class) {
 829                 if (attr instanceof MediaSizeName) {
 830                     mediaName = (MediaSizeName)attr;
 831                     IPPPrintService.debug_println(debugPrefix+
 832                                                   "mediaName "+mediaName);
 833                 if (!service.isAttributeValueSupported(attr, null, null)) {
 834                     mediaSize = MediaSize.getMediaSizeForName(mediaName);
 835                 }
 836               } else if (attr instanceof CustomMediaTray) {
 837                   customTray = (CustomMediaTray)attr;
 838               }
 839             } else if (category == OrientationRequested.class) {
 840                 orient = (OrientationRequested)attr;
 841             } else if (category == NumberUp.class) {
 842                 nUp = (NumberUp)attr;
 843             } else if (category == Sides.class) {
 844                 sides = (Sides)attr;
 845             }
 846         }
 847     }
 848 
 849     private String[] printExecCmd(String printer, String options,
 850                                  boolean noJobSheet,
 851                                  String banner, int copies, String spoolFile) {
 852         int PRINTER = 0x1;
 853         int OPTIONS = 0x2;
 854         int BANNER  = 0x4;
 855         int COPIES  = 0x8;
 856         int NOSHEET  = 0x10;
 857         int pFlags = 0;
 858         String execCmd[];
 859         int ncomps = 2; // minimum number of print args
 860         int n = 0;
 861 
 862         // conveniently "lp" is the default destination for both lp and lpr.
 863         if (printer != null && !printer.equals("") && !printer.equals("lp")) {
 864             pFlags |= PRINTER;
 865             ncomps+=1;
 866         }
 867         if (options != null && !options.equals("")) {
 868             pFlags |= OPTIONS;
 869             ncomps+=1;
 870         }
 871         if (banner != null && !banner.equals("")) {
 872             pFlags |= BANNER;
 873             ncomps+=1;
 874         }
 875         if (copies > 1) {
 876             pFlags |= COPIES;
 877             ncomps+=1;
 878         }
 879         if (noJobSheet) {
 880             pFlags |= NOSHEET;
 881             ncomps+=1;
 882         }
 883         if (UnixPrintServiceLookup.osname.equals("SunOS")) {
 884             ncomps+=1; // lp uses 1 more arg than lpr (make a copy)
 885             execCmd = new String[ncomps];
 886             execCmd[n++] = "/usr/bin/lp";
 887             execCmd[n++] = "-c";           // make a copy of the spool file
 888             if ((pFlags & PRINTER) != 0) {
 889                 execCmd[n++] = "-d" + printer;
 890             }
 891             if ((pFlags & BANNER) != 0) {
 892                 String quoteChar = "\"";
 893                 execCmd[n++] = "-t "  + quoteChar+banner+quoteChar;
 894             }
 895             if ((pFlags & COPIES) != 0) {
 896                 execCmd[n++] = "-n " + copies;
 897             }
 898             if ((pFlags & NOSHEET) != 0) {
 899                 execCmd[n++] = "-o nobanner";
 900             }
 901             if ((pFlags & OPTIONS) != 0) {
 902                 execCmd[n++] = "-o " + options;
 903             }
 904         } else {
 905             execCmd = new String[ncomps];
 906             execCmd[n++] = "/usr/bin/lpr";
 907             if ((pFlags & PRINTER) != 0) {
 908                 execCmd[n++] = "-P" + printer;
 909             }
 910             if ((pFlags & BANNER) != 0) {
 911                 execCmd[n++] = "-J "  + banner;
 912             }
 913             if ((pFlags & COPIES) != 0) {
 914                 execCmd[n++] = "-#" + copies;
 915             }
 916             if ((pFlags & NOSHEET) != 0) {
 917                 execCmd[n++] = "-h";
 918             }
 919             if ((pFlags & OPTIONS) != 0) {
 920                 execCmd[n++] = "-o" + options;
 921             }
 922         }
 923         execCmd[n++] = spoolFile;
 924         if (IPPPrintService.debugPrint) {
 925             System.out.println("UnixPrintJob>> execCmd");
 926             for (int i=0; i<execCmd.length; i++) {
 927                 System.out.print(" "+execCmd[i]);
 928             }
 929             System.out.println();
 930         }
 931         return execCmd;
 932     }
 933 
 934     private static int DESTPRINTER = 1;
 935     private static int DESTFILE = 2;
 936     private int mDestType = DESTPRINTER;
 937 
 938     private File spoolFile;
 939     private String mDestination, mOptions="";
 940     private boolean mNoJobSheet = false;
 941 
 942     // Inner class to run "privileged" to open the printer output stream.
 943 
 944     private class PrinterOpener implements java.security.PrivilegedAction {
 945         PrintException pex;
 946         OutputStream result;
 947 
 948         public Object run() {
 949             try {
 950                 if (mDestType == UnixPrintJob.DESTFILE) {
 951                     spoolFile = new File(mDestination);
 952                 } else {
 953                     /* Write to a temporary file which will be spooled to
 954                      * the printer then deleted. In the case that the file
 955                      * is not removed for some reason, request that it is
 956                      * removed when the VM exits.
 957                      */
 958                     spoolFile = Files.createTempFile("javaprint", "").toFile();
 959                     spoolFile.deleteOnExit();
 960                 }
 961                 result = new FileOutputStream(spoolFile);
 962                 return result;
 963             } catch (IOException ex) {
 964                 // If there is an IOError we subvert it to a PrinterException.
 965                 notifyEvent(PrintJobEvent.JOB_FAILED);
 966                 pex = new PrintException(ex);
 967             }
 968             return null;
 969         }
 970     }
 971 
 972     // Inner class to run "privileged" to invoke the system print command
 973 
 974     @SuppressWarnings("finally") // Exception always thrown in finally block
 975     private class PrinterSpooler implements java.security.PrivilegedAction {
 976         PrintException pex;
 977 
 978         private void handleProcessFailure(final Process failedProcess,
 979                 final String[] execCmd, final int result) throws IOException {
 980             try (StringWriter sw = new StringWriter();
 981                     PrintWriter pw = new PrintWriter(sw)) {
 982                 pw.append("error=").append(Integer.toString(result));
 983                 pw.append(" running:");
 984                 for (String arg: execCmd) {
 985                     pw.append(" '").append(arg).append("'");
 986                 }
 987                 try (InputStream is = failedProcess.getErrorStream();
 988                         InputStreamReader isr = new InputStreamReader(is);
 989                         BufferedReader br = new BufferedReader(isr)) {
 990                     while (br.ready()) {
 991                         pw.println();
 992                         pw.append("\t\t").append(br.readLine());
 993                     }
 994                 } finally {
 995                     pw.flush();
 996                     throw new IOException(sw.toString());
 997                 }
 998             }
 999         }
1000 
1001         public Object run() {
1002             if (spoolFile == null || !spoolFile.exists()) {
1003                pex = new PrintException("No spool file");
1004                notifyEvent(PrintJobEvent.JOB_FAILED);
1005                return null;
1006             }
1007             try {
1008                 /**
1009                  * Spool to the printer.
1010                  */
1011                 String fileName = spoolFile.getAbsolutePath();
1012                 String execCmd[] = printExecCmd(mDestination, mOptions,
1013                                mNoJobSheet, jobName, copies, fileName);
1014 
1015                 Process process = Runtime.getRuntime().exec(execCmd);
1016                 process.waitFor();
1017                 final int result = process.exitValue();
1018                 if (0 != result) {
1019                     handleProcessFailure(process, execCmd, result);
1020                 }
1021                 notifyEvent(PrintJobEvent.DATA_TRANSFER_COMPLETE);
1022             } catch (IOException ex) {
1023                 notifyEvent(PrintJobEvent.JOB_FAILED);
1024                 // REMIND : 2d printing throws PrinterException
1025                 pex = new PrintException(ex);
1026             } catch (InterruptedException ie) {
1027                 notifyEvent(PrintJobEvent.JOB_FAILED);
1028                 pex = new PrintException(ie);
1029             } finally {
1030                 spoolFile.delete();
1031                 notifyEvent(PrintJobEvent.NO_MORE_EVENTS);
1032             }
1033             return null;
1034         }
1035     }
1036 
1037     public void cancel() throws PrintException {
1038         synchronized (this) {
1039             if (!printing) {
1040                 throw new PrintException("Job is not yet submitted.");
1041             } else if (job != null && !printReturned) {
1042                 job.cancel();
1043                 notifyEvent(PrintJobEvent.JOB_CANCELED);
1044                 return;
1045             } else {
1046                 throw new PrintException("Job could not be cancelled.");
1047             }
1048         }
1049     }
1050 }