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.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 
UnixPrintJob(PrintService service)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 
getPrintService()136     public PrintService getPrintService() {
137         return service;
138     }
139 
getAttributes()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 
addPrintJobListener(PrintJobListener listener)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 
removePrintJobListener(PrintJobListener listener)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      */
closeDataStreams()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 
notifyEvent(int reason)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 
addPrintJobAttributeListener( PrintJobAttributeListener listener, PrintJobAttributeSet attributes)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 
removePrintJobAttributeListener( PrintJobAttributeListener listener)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 
print(Doc doc, PrintRequestAttributeSet attributes)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.equals("")) {
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 
printableJob(Printable printable)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 
pageableJob(Pageable pageable)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
initializeAttributeSets(Doc doc, PrintRequestAttributeSet reqSet)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.equals("")) {
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 
getAttributeValues(DocFlavor flavor)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 
printExecCmd(String printer, String options, boolean noJobSheet, String jobTitle, int copies, String spoolFile)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.equals("") && !printer.equals("lp")) {
869             pFlags |= PRINTER;
870             ncomps+=1;
871         }
872         if (options != null && !options.equals("")) {
873             pFlags |= OPTIONS;
874             ncomps+=1;
875         }
876         if (jobTitle != null && !jobTitle.equals("")) {
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         if (PrintServiceLookupProvider.osname.equals("SunOS")) {
892             ncomps+=1; // lp uses 1 more arg than lpr (make a copy)
893             execCmd = new String[ncomps];
894             execCmd[n++] = "/usr/bin/lp";
895             execCmd[n++] = "-c";           // make a copy of the spool file
896             if ((pFlags & PRINTER) != 0) {
897                 execCmd[n++] = "-d" + printer;
898             }
899             if ((pFlags & JOBTITLE) != 0) {
900                 String quoteChar = "\"";
901                 execCmd[n++] = "-t "  + quoteChar+jobTitle+quoteChar;
902             }
903             if ((pFlags & COPIES) != 0) {
904                 execCmd[n++] = "-n " + copies;
905             }
906             if ((pFlags & NOSHEET) != 0) {
907                 execCmd[n++] = "-o nobanner";
908             } else if (getPrintService().
909                         isAttributeCategorySupported(JobSheets.class)) {
910                 execCmd[n++] = "-o job-sheets=standard";
911             }
912             if ((pFlags & OPTIONS) != 0) {
913                 execCmd[n++] = "-o " + options;
914             }
915         } else {
916             execCmd = new String[ncomps];
917             execCmd[n++] = "/usr/bin/lpr";
918             if ((pFlags & PRINTER) != 0) {
919                 execCmd[n++] = "-P" + printer;
920             }
921             if ((pFlags & JOBTITLE) != 0) {
922                 execCmd[n++] = "-J "  + jobTitle;
923             }
924             if ((pFlags & COPIES) != 0) {
925                 execCmd[n++] = "-#" + copies;
926             }
927             if ((pFlags & NOSHEET) != 0) {
928                 execCmd[n++] = "-h";
929             } else if (getPrintService().
930                         isAttributeCategorySupported(JobSheets.class)) {
931                 execCmd[n++] = "-o job-sheets=standard";
932             }
933             if ((pFlags & OPTIONS) != 0) {
934                 execCmd[n++] = "-o" + options;
935             }
936         }
937         execCmd[n++] = spoolFile;
938         if (IPPPrintService.debugPrint) {
939             System.out.println("UnixPrintJob>> execCmd");
940             for (int i=0; i<execCmd.length; i++) {
941                 System.out.print(" "+execCmd[i]);
942             }
943             System.out.println();
944         }
945         return execCmd;
946     }
947 
948     private static int DESTPRINTER = 1;
949     private static int DESTFILE = 2;
950     private int mDestType = DESTPRINTER;
951 
952     private File spoolFile;
953     private String mDestination, mOptions="";
954     private boolean mNoJobSheet = false;
955 
956     // Inner class to run "privileged" to open the printer output stream.
957 
958     private class PrinterOpener implements java.security.PrivilegedAction<OutputStream> {
959         PrintException pex;
960         OutputStream result;
961 
run()962         public OutputStream run() {
963             try {
964                 if (mDestType == UnixPrintJob.DESTFILE) {
965                     spoolFile = new File(mDestination);
966                 } else {
967                     /* Write to a temporary file which will be spooled to
968                      * the printer then deleted. In the case that the file
969                      * is not removed for some reason, request that it is
970                      * removed when the VM exits.
971                      */
972                     spoolFile = Files.createTempFile("javaprint", "").toFile();
973                     spoolFile.deleteOnExit();
974                 }
975                 result = new FileOutputStream(spoolFile);
976                 return result;
977             } catch (IOException ex) {
978                 // If there is an IOError we subvert it to a PrinterException.
979                 notifyEvent(PrintJobEvent.JOB_FAILED);
980                 pex = new PrintException(ex);
981             }
982             return null;
983         }
984     }
985 
986     // Inner class to run "privileged" to invoke the system print command
987 
988     private class PrinterSpooler implements java.security.PrivilegedAction<Object> {
989         PrintException pex;
990 
handleProcessFailure(final Process failedProcess, final String[] execCmd, final int result)991         private void handleProcessFailure(final Process failedProcess,
992                 final String[] execCmd, final int result) throws IOException {
993             try (StringWriter sw = new StringWriter();
994                     PrintWriter pw = new PrintWriter(sw)) {
995                 pw.append("error=").append(Integer.toString(result));
996                 pw.append(" running:");
997                 for (String arg: execCmd) {
998                     pw.append(" '").append(arg).append("'");
999                 }
1000                 try (InputStream is = failedProcess.getErrorStream();
1001                         InputStreamReader isr = new InputStreamReader(is);
1002                         BufferedReader br = new BufferedReader(isr)) {
1003                     while (br.ready()) {
1004                         pw.println();
1005                         pw.append("\t\t").append(br.readLine());
1006                     }
1007                 } finally {
1008                     pw.flush();
1009                 }
1010                 throw new IOException(sw.toString());
1011             }
1012         }
1013 
run()1014         public Object run() {
1015             if (spoolFile == null || !spoolFile.exists()) {
1016                pex = new PrintException("No spool file");
1017                notifyEvent(PrintJobEvent.JOB_FAILED);
1018                return null;
1019             }
1020             try {
1021                 /**
1022                  * Spool to the printer.
1023                  */
1024                 String fileName = spoolFile.getAbsolutePath();
1025                 String execCmd[] = printExecCmd(mDestination, mOptions,
1026                                mNoJobSheet, jobName, copies, fileName);
1027 
1028                 Process process = Runtime.getRuntime().exec(execCmd);
1029                 process.waitFor();
1030                 final int result = process.exitValue();
1031                 if (0 != result) {
1032                     handleProcessFailure(process, execCmd, result);
1033                 }
1034                 notifyEvent(PrintJobEvent.DATA_TRANSFER_COMPLETE);
1035             } catch (IOException ex) {
1036                 notifyEvent(PrintJobEvent.JOB_FAILED);
1037                 // REMIND : 2d printing throws PrinterException
1038                 pex = new PrintException(ex);
1039             } catch (InterruptedException ie) {
1040                 notifyEvent(PrintJobEvent.JOB_FAILED);
1041                 pex = new PrintException(ie);
1042             } finally {
1043                 spoolFile.delete();
1044                 notifyEvent(PrintJobEvent.NO_MORE_EVENTS);
1045             }
1046             return null;
1047         }
1048     }
1049 
cancel()1050     public void cancel() throws PrintException {
1051         synchronized (this) {
1052             if (!printing) {
1053                 throw new PrintException("Job is not yet submitted.");
1054             } else if (job != null && !printReturned) {
1055                 job.cancel();
1056                 notifyEvent(PrintJobEvent.JOB_CANCELED);
1057                 return;
1058             } else {
1059                 throw new PrintException("Job could not be cancelled.");
1060             }
1061         }
1062     }
1063 }
1064