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