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
26package sun.print;
27
28import java.net.URI;
29import java.net.URL;
30import java.io.BufferedInputStream;
31import java.io.BufferedOutputStream;
32import java.io.BufferedReader;
33import java.io.BufferedWriter;
34import java.io.File;
35import java.io.FileOutputStream;
36import java.io.InputStream;
37import java.io.InputStreamReader;
38import java.io.OutputStream;
39import java.io.OutputStreamWriter;
40import java.io.IOException;
41import java.io.PrintWriter;
42import java.io.Reader;
43import java.io.StringWriter;
44import java.nio.file.Files;
45import java.util.Vector;
46
47import javax.print.CancelablePrintJob;
48import javax.print.Doc;
49import javax.print.DocFlavor;
50import javax.print.PrintService;
51import javax.print.PrintException;
52import javax.print.event.PrintJobEvent;
53import javax.print.event.PrintJobListener;
54import javax.print.event.PrintJobAttributeListener;
55
56import javax.print.attribute.Attribute;
57import javax.print.attribute.AttributeSetUtilities;
58import javax.print.attribute.DocAttributeSet;
59import javax.print.attribute.HashPrintJobAttributeSet;
60import javax.print.attribute.HashPrintRequestAttributeSet;
61import javax.print.attribute.PrintJobAttribute;
62import javax.print.attribute.PrintJobAttributeSet;
63import javax.print.attribute.PrintRequestAttribute;
64import javax.print.attribute.PrintRequestAttributeSet;
65import javax.print.attribute.standard.Copies;
66import javax.print.attribute.standard.Destination;
67import javax.print.attribute.standard.DocumentName;
68import javax.print.attribute.standard.Fidelity;
69import javax.print.attribute.standard.JobName;
70import javax.print.attribute.standard.JobOriginatingUserName;
71import javax.print.attribute.standard.JobSheets;
72import javax.print.attribute.standard.Media;
73import javax.print.attribute.standard.MediaSize;
74import javax.print.attribute.standard.MediaSizeName;
75import javax.print.attribute.standard.OrientationRequested;
76import javax.print.attribute.standard.RequestingUserName;
77import javax.print.attribute.standard.NumberUp;
78import javax.print.attribute.standard.Sides;
79import javax.print.attribute.standard.PrinterIsAcceptingJobs;
80
81import java.awt.print.PageFormat;
82import java.awt.print.PrinterJob;
83import java.awt.print.Pageable;
84import java.awt.print.Paper;
85import java.awt.print.Printable;
86import java.awt.print.PrinterException;
87
88
89
90public class UnixPrintJob implements CancelablePrintJob {
91    private static String debugPrefix = "UnixPrintJob>> ";
92
93    private transient Vector<PrintJobListener> jobListeners;
94    private transient Vector<PrintJobAttributeListener> attrListeners;
95    private transient Vector<PrintJobAttributeSet> listenedAttributeSets;
96
97    private PrintService service;
98    private boolean fidelity;
99    private boolean printing = false;
100    private boolean printReturned = false;
101    private PrintRequestAttributeSet reqAttrSet = null;
102    private PrintJobAttributeSet jobAttrSet = null;
103    private PrinterJob job;
104    private Doc doc;
105    /* these variables used globally to store reference to the print
106     * data retrieved as a stream. On completion these are always closed
107     * if non-null.
108     */
109    private InputStream instream = null;
110    private Reader reader = null;
111
112    /* default values overridden by those extracted from the attributes */
113    private String jobName = "Java Printing";
114    private int copies = 1;
115    private MediaSizeName mediaName = MediaSizeName.NA_LETTER;
116    private MediaSize     mediaSize = MediaSize.NA.LETTER;
117    private CustomMediaTray     customTray = null;
118    private OrientationRequested orient = OrientationRequested.PORTRAIT;
119    private NumberUp nUp = null;
120    private Sides sides = null;
121
122    UnixPrintJob(PrintService service) {
123        this.service = service;
124        mDestination = service.getName();
125        if (PrintServiceLookupProvider.isMac()) {
126            mDestination = ((IPPPrintService)service).getDest();
127        }
128        mDestType = UnixPrintJob.DESTPRINTER;
129        JobSheets js = (JobSheets)(service.
130                                      getDefaultAttributeValue(JobSheets.class));
131        if (js != null && js.equals(JobSheets.NONE)) {
132            mNoJobSheet = true;
133        }
134    }
135
136    public PrintService getPrintService() {
137        return service;
138    }
139
140    public PrintJobAttributeSet getAttributes() {
141        synchronized (this) {
142            if (jobAttrSet == null) {
143                /* just return an empty set until the job is submitted */
144                PrintJobAttributeSet jobSet = new HashPrintJobAttributeSet();
145                return AttributeSetUtilities.unmodifiableView(jobSet);
146            } else {
147              return jobAttrSet;
148            }
149        }
150    }
151
152    public void addPrintJobListener(PrintJobListener listener) {
153        synchronized (this) {
154            if (listener == null) {
155                return;
156            }
157            if (jobListeners == null) {
158                jobListeners = new Vector<>();
159            }
160            jobListeners.add(listener);
161        }
162    }
163
164    public void removePrintJobListener(PrintJobListener listener) {
165        synchronized (this) {
166            if (listener == null || jobListeners == null ) {
167                return;
168            }
169            jobListeners.remove(listener);
170            if (jobListeners.isEmpty()) {
171                jobListeners = null;
172            }
173        }
174    }
175
176
177    /* Closes any stream already retrieved for the data.
178     * We want to avoid unnecessarily asking the Doc to create a stream only
179     * to get a reference in order to close it because the job failed.
180     * If the representation class is itself a "stream", this
181     * closes that stream too.
182     */
183    private void closeDataStreams() {
184
185        if (doc == null) {
186            return;
187        }
188
189        Object data = null;
190
191        try {
192            data = doc.getPrintData();
193        } catch (IOException e) {
194            return;
195        }
196
197        if (instream != null) {
198            try {
199                instream.close();
200            } catch (IOException e) {
201            } finally {
202                instream = null;
203            }
204        }
205        else if (reader != null) {
206            try {
207                reader.close();
208            } catch (IOException e) {
209            } finally {
210                reader = null;
211            }
212        }
213        else if (data instanceof InputStream) {
214            try {
215                ((InputStream)data).close();
216            } catch (IOException e) {
217            }
218        }
219        else if (data instanceof Reader) {
220            try {
221                ((Reader)data).close();
222            } catch (IOException e) {
223            }
224        }
225    }
226
227    private void notifyEvent(int reason) {
228
229        /* since this method should always get called, here's where
230         * we will perform the clean up of any data stream supplied.
231         */
232        switch (reason) {
233            case PrintJobEvent.DATA_TRANSFER_COMPLETE:
234            case PrintJobEvent.JOB_CANCELED :
235            case PrintJobEvent.JOB_FAILED :
236            case PrintJobEvent.NO_MORE_EVENTS :
237            case PrintJobEvent.JOB_COMPLETE :
238                closeDataStreams();
239        }
240
241        synchronized (this) {
242            if (jobListeners != null) {
243                PrintJobListener listener;
244                PrintJobEvent event = new PrintJobEvent(this, reason);
245                for (int i = 0; i < jobListeners.size(); i++) {
246                    listener = jobListeners.elementAt(i);
247                    switch (reason) {
248
249                        case PrintJobEvent.JOB_CANCELED :
250                            listener.printJobCanceled(event);
251                            break;
252
253                        case PrintJobEvent.JOB_FAILED :
254                            listener.printJobFailed(event);
255                            break;
256
257                        case PrintJobEvent.DATA_TRANSFER_COMPLETE :
258                            listener.printDataTransferCompleted(event);
259                            break;
260
261                        case PrintJobEvent.NO_MORE_EVENTS :
262                            listener.printJobNoMoreEvents(event);
263                            break;
264
265                        default:
266                            break;
267                    }
268                }
269            }
270       }
271    }
272
273    public void addPrintJobAttributeListener(
274                                  PrintJobAttributeListener listener,
275                                  PrintJobAttributeSet attributes) {
276        synchronized (this) {
277            if (listener == null) {
278                return;
279            }
280            if (attrListeners == null) {
281                attrListeners = new Vector<>();
282                listenedAttributeSets = new Vector<>();
283            }
284            attrListeners.add(listener);
285            if (attributes == null) {
286                attributes = new HashPrintJobAttributeSet();
287            }
288            listenedAttributeSets.add(attributes);
289        }
290    }
291
292    public void removePrintJobAttributeListener(
293                                        PrintJobAttributeListener listener) {
294        synchronized (this) {
295            if (listener == null || attrListeners == null ) {
296                return;
297            }
298            int index = attrListeners.indexOf(listener);
299            if (index == -1) {
300                return;
301            } else {
302                attrListeners.remove(index);
303                listenedAttributeSets.remove(index);
304                if (attrListeners.isEmpty()) {
305                    attrListeners = null;
306                    listenedAttributeSets = null;
307                }
308            }
309        }
310    }
311
312    public void print(Doc doc, PrintRequestAttributeSet attributes)
313        throws PrintException {
314
315        synchronized (this) {
316            if (printing) {
317                throw new PrintException("already printing");
318            } else {
319                printing = true;
320            }
321        }
322
323        if ((service.getAttribute(PrinterIsAcceptingJobs.class)) ==
324                         PrinterIsAcceptingJobs.NOT_ACCEPTING_JOBS) {
325            throw new PrintException("Printer is not accepting job.");
326        }
327
328        this.doc = doc;
329        /* check if the parameters are valid before doing much processing */
330        DocFlavor flavor = doc.getDocFlavor();
331
332        Object data;
333
334        try {
335            data = doc.getPrintData();
336        } catch (IOException e) {
337            notifyEvent(PrintJobEvent.JOB_FAILED);
338            throw new PrintException("can't get print data: " + e.toString());
339        }
340
341        if (data == null) {
342            throw new PrintException("Null print data.");
343        }
344
345        if (flavor == null || (!service.isDocFlavorSupported(flavor))) {
346            notifyEvent(PrintJobEvent.JOB_FAILED);
347            throw new PrintJobFlavorException("invalid flavor", flavor);
348        }
349
350        initializeAttributeSets(doc, attributes);
351
352        getAttributeValues(flavor);
353
354        // set up mOptions
355        if ((service instanceof IPPPrintService) &&
356            CUPSPrinter.isCupsRunning()) {
357
358             IPPPrintService.debug_println(debugPrefix+
359                        "instanceof IPPPrintService");
360
361             if (mediaName != null) {
362                 CustomMediaSizeName customMedia =
363                     ((IPPPrintService)service).findCustomMedia(mediaName);
364                 if (customMedia != null) {
365                     mOptions = " media="+ customMedia.getChoiceName();
366                 }
367             }
368
369             if (customTray != null &&
370                 customTray instanceof CustomMediaTray) {
371                 String choice = customTray.getChoiceName();
372                 if (choice != null) {
373                     mOptions += " InputSlot="+choice;
374                 }
375             }
376
377             if (nUp != null) {
378                 mOptions += " number-up="+nUp.getValue();
379             }
380
381             if (orient != OrientationRequested.PORTRAIT &&
382                 (flavor != null) &&
383                 !flavor.equals(DocFlavor.SERVICE_FORMATTED.PAGEABLE)) {
384                 mOptions += " orientation-requested="+orient.getValue();
385             }
386
387             if (sides != null) {
388                 mOptions += " sides="+sides;
389             }
390
391        }
392
393        IPPPrintService.debug_println(debugPrefix+"mOptions "+mOptions);
394        String repClassName = flavor.getRepresentationClassName();
395        String val = flavor.getParameter("charset");
396        String encoding = "us-ascii";
397        if (val != null && !val.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
633    public void printableJob(Printable printable) throws PrintException {
634        try {
635            synchronized(this) {
636                if (job != null) { // shouldn't happen
637                    throw new PrintException("already printing");
638                } else {
639                    job = new PSPrinterJob();
640                }
641            }
642            job.setPrintService(getPrintService());
643            job.setCopies(copies);
644            job.setJobName(jobName);
645            PageFormat pf = new PageFormat();
646            if (mediaSize != null) {
647                Paper p = new Paper();
648                p.setSize(mediaSize.getX(MediaSize.INCH)*72.0,
649                          mediaSize.getY(MediaSize.INCH)*72.0);
650                p.setImageableArea(72.0, 72.0, p.getWidth()-144.0,
651                                   p.getHeight()-144.0);
652                pf.setPaper(p);
653            }
654            if (orient == OrientationRequested.REVERSE_LANDSCAPE) {
655                pf.setOrientation(PageFormat.REVERSE_LANDSCAPE);
656            } else if (orient == OrientationRequested.LANDSCAPE) {
657                pf.setOrientation(PageFormat.LANDSCAPE);
658            }
659            job.setPrintable(printable, pf);
660            job.print(reqAttrSet);
661            notifyEvent(PrintJobEvent.DATA_TRANSFER_COMPLETE);
662            return;
663        } catch (PrinterException pe) {
664            notifyEvent(PrintJobEvent.JOB_FAILED);
665            throw new PrintException(pe);
666        } finally {
667            printReturned = true;
668            notifyEvent(PrintJobEvent.NO_MORE_EVENTS);
669        }
670    }
671
672    public void pageableJob(Pageable pageable) throws PrintException {
673        try {
674            synchronized(this) {
675                if (job != null) { // shouldn't happen
676                    throw new PrintException("already printing");
677                } else {
678                    job = new PSPrinterJob();
679                }
680            }
681            job.setPrintService(getPrintService());
682            job.setCopies(copies);
683            job.setJobName(jobName);
684            job.setPageable(pageable);
685            job.print(reqAttrSet);
686            notifyEvent(PrintJobEvent.DATA_TRANSFER_COMPLETE);
687            return;
688        } catch (PrinterException pe) {
689            notifyEvent(PrintJobEvent.JOB_FAILED);
690            throw new PrintException(pe);
691        } finally {
692            printReturned = true;
693            notifyEvent(PrintJobEvent.NO_MORE_EVENTS);
694        }
695    }
696    /* There's some inefficiency here as the job set is created even though
697     * it may never be requested.
698     */
699    private synchronized void
700        initializeAttributeSets(Doc doc, PrintRequestAttributeSet reqSet) {
701
702        reqAttrSet = new HashPrintRequestAttributeSet();
703        jobAttrSet = new HashPrintJobAttributeSet();
704
705        Attribute[] attrs;
706        if (reqSet != null) {
707            reqAttrSet.addAll(reqSet);
708            attrs = reqSet.toArray();
709            for (int i=0; i<attrs.length; i++) {
710                if (attrs[i] instanceof PrintJobAttribute) {
711                    jobAttrSet.add(attrs[i]);
712                }
713            }
714        }
715
716        DocAttributeSet docSet = doc.getAttributes();
717        if (docSet != null) {
718            attrs = docSet.toArray();
719            for (int i=0; i<attrs.length; i++) {
720                if (attrs[i] instanceof PrintRequestAttribute) {
721                    reqAttrSet.add(attrs[i]);
722                }
723                if (attrs[i] instanceof PrintJobAttribute) {
724                    jobAttrSet.add(attrs[i]);
725                }
726            }
727        }
728
729        /* add the user name to the job */
730        String userName = "";
731        try {
732          userName = System.getProperty("user.name");
733        } catch (SecurityException se) {
734        }
735
736        if (userName == null || userName.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
776    private void getAttributeValues(DocFlavor flavor) throws PrintException {
777        Attribute attr;
778        Class<? extends Attribute> category;
779
780        if (reqAttrSet.get(Fidelity.class) == Fidelity.FIDELITY_TRUE) {
781            fidelity = true;
782        } else {
783            fidelity = false;
784        }
785
786        Attribute []attrs = reqAttrSet.toArray();
787        for (int i=0; i<attrs.length; i++) {
788            attr = attrs[i];
789            category = attr.getCategory();
790            if (fidelity == true) {
791                if (!service.isAttributeCategorySupported(category)) {
792                    notifyEvent(PrintJobEvent.JOB_FAILED);
793                    throw new PrintJobAttributeException(
794                        "unsupported category: " + category, category, null);
795                } else if
796                    (!service.isAttributeValueSupported(attr, flavor, null)) {
797                    notifyEvent(PrintJobEvent.JOB_FAILED);
798                    throw new PrintJobAttributeException(
799                        "unsupported attribute: " + attr, null, attr);
800                }
801            }
802            if (category == Destination.class) {
803                URI uri = ((Destination)attr).getURI();
804                if (!"file".equals(uri.getScheme())) {
805                    notifyEvent(PrintJobEvent.JOB_FAILED);
806                    throw new PrintException("Not a file: URI");
807                } else {
808                    try {
809                        mDestType = DESTFILE;
810                        mDestination = (new File(uri)).getPath();
811                    } catch (Exception e) {
812                        throw new PrintException(e);
813                    }
814                    // check write access
815                    SecurityManager security = System.getSecurityManager();
816                    if (security != null) {
817                      try {
818                        security.checkWrite(mDestination);
819                      } catch (SecurityException se) {
820                        notifyEvent(PrintJobEvent.JOB_FAILED);
821                        throw new PrintException(se);
822                      }
823                    }
824                }
825            } else if (category == JobSheets.class) {
826                if ((JobSheets)attr == JobSheets.NONE) {
827                   mNoJobSheet = true;
828                }
829            } else if (category == JobName.class) {
830                jobName = ((JobName)attr).getValue();
831            } else if (category == Copies.class) {
832                copies = ((Copies)attr).getValue();
833            } else if (category == Media.class) {
834                if (attr instanceof MediaSizeName) {
835                    mediaName = (MediaSizeName)attr;
836                    IPPPrintService.debug_println(debugPrefix+
837                                                  "mediaName "+mediaName);
838                if (!service.isAttributeValueSupported(attr, null, null)) {
839                    mediaSize = MediaSize.getMediaSizeForName(mediaName);
840                }
841              } else if (attr instanceof CustomMediaTray) {
842                  customTray = (CustomMediaTray)attr;
843              }
844            } else if (category == OrientationRequested.class) {
845                orient = (OrientationRequested)attr;
846            } else if (category == NumberUp.class) {
847                nUp = (NumberUp)attr;
848            } else if (category == Sides.class) {
849                sides = (Sides)attr;
850            }
851        }
852    }
853
854    private String[] printExecCmd(String printer, String options,
855                                 boolean noJobSheet,
856                                 String jobTitle, int copies, String spoolFile) {
857        int PRINTER = 0x1;
858        int OPTIONS = 0x2;
859        int JOBTITLE  = 0x4;
860        int COPIES  = 0x8;
861        int NOSHEET  = 0x10;
862        int pFlags = 0;
863        String execCmd[];
864        int ncomps = 2; // minimum number of print args
865        int n = 0;
866
867        // conveniently "lp" is the default destination for both lp and lpr.
868        if (printer != null && !printer.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
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
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
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
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