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