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