| /* | |
| * Copyright (c) 2000, 2025, Oracle and/or its affiliates. All rights reserved. | |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| * | |
| * This code is free software; you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License version 2 only, as | |
| * published by the Free Software Foundation. Oracle designates this | |
| * particular file as subject to the "Classpath" exception as provided | |
| * by Oracle in the LICENSE file that accompanied this code. | |
| * | |
| * This code is distributed in the hope that it will be useful, but WITHOUT | |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| * version 2 for more details (a copy is included in the LICENSE file that | |
| * accompanied this code). | |
| * | |
| * You should have received a copy of the GNU General Public License version | |
| * 2 along with this work; if not, write to the Free Software Foundation, | |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| * | |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| * or visit www.oracle.com if you need additional information or have any | |
| * questions. | |
| */ | |
| package sun.print; | |
| import java.net.URI; | |
| import java.net.URL; | |
| import java.io.BufferedInputStream; | |
| import java.io.BufferedOutputStream; | |
| import java.io.BufferedReader; | |
| import java.io.BufferedWriter; | |
| import java.io.File; | |
| import java.io.FileOutputStream; | |
| import java.io.InputStream; | |
| import java.io.InputStreamReader; | |
| import java.io.OutputStream; | |
| import java.io.OutputStreamWriter; | |
| import java.io.IOException; | |
| import java.io.PrintWriter; | |
| import java.io.Reader; | |
| import java.io.StringWriter; | |
| import java.nio.file.Files; | |
| import java.util.ArrayList; | |
| import javax.print.CancelablePrintJob; | |
| import javax.print.Doc; | |
| import javax.print.DocFlavor; | |
| import javax.print.PrintService; | |
| import javax.print.PrintException; | |
| import javax.print.event.PrintJobEvent; | |
| import javax.print.event.PrintJobListener; | |
| import javax.print.event.PrintJobAttributeListener; | |
| import javax.print.attribute.Attribute; | |
| import javax.print.attribute.AttributeSetUtilities; | |
| import javax.print.attribute.DocAttributeSet; | |
| import javax.print.attribute.HashPrintJobAttributeSet; | |
| import javax.print.attribute.HashPrintRequestAttributeSet; | |
| import javax.print.attribute.PrintJobAttribute; | |
| import javax.print.attribute.PrintJobAttributeSet; | |
| import javax.print.attribute.PrintRequestAttribute; | |
| import javax.print.attribute.PrintRequestAttributeSet; | |
| import javax.print.attribute.standard.Copies; | |
| import javax.print.attribute.standard.Destination; | |
| import javax.print.attribute.standard.DocumentName; | |
| import javax.print.attribute.standard.Fidelity; | |
| import javax.print.attribute.standard.JobName; | |
| import javax.print.attribute.standard.JobOriginatingUserName; | |
| import javax.print.attribute.standard.JobSheets; | |
| import javax.print.attribute.standard.Media; | |
| import javax.print.attribute.standard.MediaSize; | |
| import javax.print.attribute.standard.MediaSizeName; | |
| import javax.print.attribute.standard.OrientationRequested; | |
| import javax.print.attribute.standard.RequestingUserName; | |
| import javax.print.attribute.standard.NumberUp; | |
| import javax.print.attribute.standard.Sides; | |
| import javax.print.attribute.standard.PrinterIsAcceptingJobs; | |
| import java.awt.print.PageFormat; | |
| import java.awt.print.PrinterJob; | |
| import java.awt.print.Pageable; | |
| import java.awt.print.Paper; | |
| import java.awt.print.Printable; | |
| import java.awt.print.PrinterException; | |
| public final class UnixPrintJob implements CancelablePrintJob { | |
| private static String debugPrefix = "UnixPrintJob>> "; | |
| private transient ArrayList<PrintJobListener> jobListeners; | |
| private transient ArrayList<PrintJobAttributeListener> attrListeners; | |
| private transient ArrayList<PrintJobAttributeSet> listenedAttributeSets; | |
| private PrintService service; | |
| private boolean fidelity; | |
| private boolean printing = false; | |
| private boolean printReturned = false; | |
| private PrintRequestAttributeSet reqAttrSet = null; | |
| private PrintJobAttributeSet jobAttrSet = null; | |
| private PrinterJob job; | |
| private Doc doc; | |
| /* these variables used globally to store reference to the print | |
| * data retrieved as a stream. On completion these are always closed | |
| * if non-null. | |
| */ | |
| private InputStream instream = null; | |
| private Reader reader = null; | |
| /* default values overridden by those extracted from the attributes */ | |
| private String jobName = "Java Printing"; | |
| private int copies = 1; | |
| private MediaSizeName mediaName = MediaSizeName.NA_LETTER; | |
| private MediaSize mediaSize = MediaSize.NA.LETTER; | |
| private CustomMediaTray customTray = null; | |
| private OrientationRequested orient = OrientationRequested.PORTRAIT; | |
| private NumberUp nUp = null; | |
| private Sides sides = null; | |
| UnixPrintJob(PrintService service) { | |
| this.service = service; | |
| mDestination = service.getName(); | |
| if (PrintServiceLookupProvider.isMac()) { | |
| mDestination = ((IPPPrintService)service).getDest(); | |
| } | |
| mDestType = UnixPrintJob.DESTPRINTER; | |
| JobSheets js = (JobSheets)(service. | |
| getDefaultAttributeValue(JobSheets.class)); | |
| if (JobSheets.NONE.equals(js)) { | |
| mNoJobSheet = true; | |
| } | |
| } | |
| @Override | |
| public PrintService getPrintService() { | |
| return service; | |
| } | |
| @Override | |
| public PrintJobAttributeSet getAttributes() { | |
| synchronized (this) { | |
| if (jobAttrSet == null) { | |
| /* just return an empty set until the job is submitted */ | |
| PrintJobAttributeSet jobSet = new HashPrintJobAttributeSet(); | |
| return AttributeSetUtilities.unmodifiableView(jobSet); | |
| } else { | |
| return jobAttrSet; | |
| } | |
| } | |
| } | |
| @Override | |
| public void addPrintJobListener(PrintJobListener listener) { | |
| synchronized (this) { | |
| if (listener == null) { | |
| return; | |
| } | |
| if (jobListeners == null) { | |
| jobListeners = new ArrayList<>(); | |
| } | |
| jobListeners.add(listener); | |
| } | |
| } | |
| @Override | |
| public void removePrintJobListener(PrintJobListener listener) { | |
| synchronized (this) { | |
| if (listener == null || jobListeners == null ) { | |
| return; | |
| } | |
| jobListeners.remove(listener); | |
| if (jobListeners.isEmpty()) { | |
| jobListeners = null; | |
| } | |
| } | |
| } | |
| /* Closes any stream already retrieved for the data. | |
| * We want to avoid unnecessarily asking the Doc to create a stream only | |
| * to get a reference in order to close it because the job failed. | |
| * If the representation class is itself a "stream", this | |
| * closes that stream too. | |
| */ | |
| private void closeDataStreams() { | |
| if (doc == null) { | |
| return; | |
| } | |
| Object data = null; | |
| try { | |
| data = doc.getPrintData(); | |
| } catch (IOException e) { | |
| return; | |
| } | |
| if (instream != null) { | |
| try { | |
| instream.close(); | |
| } catch (IOException e) { | |
| } finally { | |
| instream = null; | |
| } | |
| } | |
| else if (reader != null) { | |
| try { | |
| reader.close(); | |
| } catch (IOException e) { | |
| } finally { | |
| reader = null; | |
| } | |
| } | |
| else if (data instanceof InputStream) { | |
| try { | |
| ((InputStream)data).close(); | |
| } catch (IOException e) { | |
| } | |
| } | |
| else if (data instanceof Reader) { | |
| try { | |
| ((Reader)data).close(); | |
| } catch (IOException e) { | |
| } | |
| } | |
| } | |
| private void notifyEvent(int reason) { | |
| /* since this method should always get called, here's where | |
| * we will perform the clean up of any data stream supplied. | |
| */ | |
| switch (reason) { | |
| case PrintJobEvent.DATA_TRANSFER_COMPLETE: | |
| case PrintJobEvent.JOB_CANCELED : | |
| case PrintJobEvent.JOB_FAILED : | |
| case PrintJobEvent.NO_MORE_EVENTS : | |
| case PrintJobEvent.JOB_COMPLETE : | |
| closeDataStreams(); | |
| } | |
| synchronized (this) { | |
| if (jobListeners != null) { | |
| PrintJobListener listener; | |
| PrintJobEvent event = new PrintJobEvent(this, reason); | |
| for (int i = 0; i < jobListeners.size(); i++) { | |
| listener = jobListeners.get(i); | |
| switch (reason) { | |
| case PrintJobEvent.JOB_CANCELED : | |
| listener.printJobCanceled(event); | |
| break; | |
| case PrintJobEvent.JOB_FAILED : | |
| listener.printJobFailed(event); | |
| break; | |
| case PrintJobEvent.DATA_TRANSFER_COMPLETE : | |
| listener.printDataTransferCompleted(event); | |
| break; | |
| case PrintJobEvent.NO_MORE_EVENTS : | |
| listener.printJobNoMoreEvents(event); | |
| break; | |
| default: | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| @Override | |
| public void addPrintJobAttributeListener( | |
| PrintJobAttributeListener listener, | |
| PrintJobAttributeSet attributes) { | |
| synchronized (this) { | |
| if (listener == null) { | |
| return; | |
| } | |
| if (attrListeners == null) { | |
| attrListeners = new ArrayList<>(); | |
| listenedAttributeSets = new ArrayList<>(); | |
| } | |
| attrListeners.add(listener); | |
| if (attributes == null) { | |
| attributes = new HashPrintJobAttributeSet(); | |
| } | |
| listenedAttributeSets.add(attributes); | |
| } | |
| } | |
| @Override | |
| public void removePrintJobAttributeListener( | |
| PrintJobAttributeListener listener) { | |
| synchronized (this) { | |
| if (listener == null || attrListeners == null ) { | |
| return; | |
| } | |
| int index = attrListeners.indexOf(listener); | |
| if (index == -1) { | |
| return; | |
| } else { | |
| attrListeners.remove(index); | |
| listenedAttributeSets.remove(index); | |
| if (attrListeners.isEmpty()) { | |
| attrListeners = null; | |
| listenedAttributeSets = null; | |
| } | |
| } | |
| } | |
| } | |
| @Override | |
| public void print(Doc doc, PrintRequestAttributeSet attributes) | |
| throws PrintException { | |
| synchronized (this) { | |
| if (printing) { | |
| throw new PrintException("already printing"); | |
| } else { | |
| printing = true; | |
| } | |
| } | |
| if ((service.getAttribute(PrinterIsAcceptingJobs.class)) == | |
| PrinterIsAcceptingJobs.NOT_ACCEPTING_JOBS) { | |
| throw new PrintException("Printer is not accepting job."); | |
| } | |
| this.doc = doc; | |
| /* check if the parameters are valid before doing much processing */ | |
| DocFlavor flavor = doc.getDocFlavor(); | |
| Object data; | |
| try { | |
| data = doc.getPrintData(); | |
| } catch (IOException e) { | |
| notifyEvent(PrintJobEvent.JOB_FAILED); | |
| throw new PrintException("can't get print data: " + e.toString()); | |
| } | |
| if (data == null) { | |
| throw new PrintException("Null print data."); | |
| } | |
| if (flavor == null || (!service.isDocFlavorSupported(flavor))) { | |
| notifyEvent(PrintJobEvent.JOB_FAILED); | |
| throw new PrintJobFlavorException("invalid flavor", flavor); | |
| } | |
| initializeAttributeSets(doc, attributes); | |
| getAttributeValues(flavor); | |
| // set up mOptions | |
| if ((service instanceof IPPPrintService) && | |
| CUPSPrinter.isCupsRunning()) { | |
| IPPPrintService.debug_println(debugPrefix+ | |
| "instanceof IPPPrintService"); | |
| if (mediaName != null) { | |
| CustomMediaSizeName customMedia = | |
| ((IPPPrintService)service).findCustomMedia(mediaName); | |
| if (customMedia != null) { | |
| mOptions = " media="+ customMedia.getChoiceName(); | |
| } | |
| } | |
| if (customTray != null) { | |
| String choice = customTray.getChoiceName(); | |
| if (choice != null) { | |
| mOptions += " InputSlot="+choice; | |
| } | |
| } | |
| if (nUp != null) { | |
| mOptions += " number-up="+nUp.getValue(); | |
| } | |
| if (orient != OrientationRequested.PORTRAIT && | |
| (flavor != null) && | |
| !flavor.equals(DocFlavor.SERVICE_FORMATTED.PAGEABLE)) { | |
| mOptions += " orientation-requested="+orient.getValue(); | |
| } | |
| if (sides != null) { | |
| mOptions += " sides="+sides; | |
| } | |
| } | |
| IPPPrintService.debug_println(debugPrefix+"mOptions "+mOptions); | |
| String repClassName = flavor.getRepresentationClassName(); | |
| String val = flavor.getParameter("charset"); | |
| String encoding = "us-ascii"; | |
| if (val != null && !val.isEmpty()) { | |
| encoding = val; | |
| } | |
| if (flavor.equals(DocFlavor.INPUT_STREAM.GIF) || | |
| flavor.equals(DocFlavor.INPUT_STREAM.JPEG) || | |
| flavor.equals(DocFlavor.INPUT_STREAM.PNG) || | |
| flavor.equals(DocFlavor.BYTE_ARRAY.GIF) || | |
| flavor.equals(DocFlavor.BYTE_ARRAY.JPEG) || | |
| flavor.equals(DocFlavor.BYTE_ARRAY.PNG)) { | |
| try { | |
| instream = doc.getStreamForBytes(); | |
| if (instream == null) { | |
| notifyEvent(PrintJobEvent.JOB_FAILED); | |
| throw new PrintException("No stream for data"); | |
| } | |
| if (!(service instanceof IPPPrintService && | |
| ((IPPPrintService)service).isIPPSupportedImages( | |
| flavor.getMimeType()))) { | |
| printableJob(new ImagePrinter(instream)); | |
| if (service instanceof IPPPrintService) { | |
| ((IPPPrintService)service).wakeNotifier(); | |
| } else { | |
| ((UnixPrintService)service).wakeNotifier(); | |
| } | |
| return; | |
| } | |
| } catch (ClassCastException | IOException e) { | |
| notifyEvent(PrintJobEvent.JOB_FAILED); | |
| throw new PrintException(e); | |
| } | |
| } else if (flavor.equals(DocFlavor.URL.GIF) || | |
| flavor.equals(DocFlavor.URL.JPEG) || | |
| flavor.equals(DocFlavor.URL.PNG)) { | |
| try { | |
| URL url = (URL)data; | |
| if ((service instanceof IPPPrintService) && | |
| ((IPPPrintService)service).isIPPSupportedImages( | |
| flavor.getMimeType())) { | |
| instream = url.openStream(); | |
| } else { | |
| printableJob(new ImagePrinter(url)); | |
| if (service instanceof IPPPrintService) { | |
| ((IPPPrintService)service).wakeNotifier(); | |
| } else { | |
| ((UnixPrintService)service).wakeNotifier(); | |
| } | |
| return; | |
| } | |
| } catch (ClassCastException cce) { | |
| notifyEvent(PrintJobEvent.JOB_FAILED); | |
| throw new PrintException(cce); | |
| } catch (IOException e) { | |
| notifyEvent(PrintJobEvent.JOB_FAILED); | |
| throw new PrintException(e.toString()); | |
| } | |
| } else if (flavor.equals(DocFlavor.CHAR_ARRAY.TEXT_PLAIN) || | |
| flavor.equals(DocFlavor.READER.TEXT_PLAIN) || | |
| flavor.equals(DocFlavor.STRING.TEXT_PLAIN)) { | |
| try { | |
| reader = doc.getReaderForText(); | |
| if (reader == null) { | |
| notifyEvent(PrintJobEvent.JOB_FAILED); | |
| throw new PrintException("No reader for data"); | |
| } | |
| } catch (IOException ioe) { | |
| notifyEvent(PrintJobEvent.JOB_FAILED); | |
| throw new PrintException(ioe.toString()); | |
| } | |
| } else if (repClassName.equals("[B") || | |
| repClassName.equals("java.io.InputStream")) { | |
| try { | |
| instream = doc.getStreamForBytes(); | |
| if (instream == null) { | |
| notifyEvent(PrintJobEvent.JOB_FAILED); | |
| throw new PrintException("No stream for data"); | |
| } | |
| } catch (IOException ioe) { | |
| notifyEvent(PrintJobEvent.JOB_FAILED); | |
| throw new PrintException(ioe.toString()); | |
| } | |
| } else if (repClassName.equals("java.net.URL")) { | |
| /* | |
| * This extracts the data from the URL and passes it the content | |
| * directly to the print service as a file. | |
| * This is appropriate for the current implementation where lp or | |
| * lpr is always used to spool the data. We expect to revise the | |
| * implementation to provide more complete IPP support (ie not just | |
| * CUPS) and at that time the job will be spooled via IPP | |
| * and the URL | |
| * itself should be sent to the IPP print service not the content. | |
| */ | |
| URL url = (URL)data; | |
| try { | |
| instream = url.openStream(); | |
| } catch (IOException e) { | |
| notifyEvent(PrintJobEvent.JOB_FAILED); | |
| throw new PrintException(e.toString()); | |
| } | |
| } else if (repClassName.equals("java.awt.print.Pageable")) { | |
| try { | |
| pageableJob((Pageable)doc.getPrintData()); | |
| if (service instanceof IPPPrintService) { | |
| ((IPPPrintService)service).wakeNotifier(); | |
| } else { | |
| ((UnixPrintService)service).wakeNotifier(); | |
| } | |
| return; | |
| } catch (ClassCastException | IOException e) { | |
| notifyEvent(PrintJobEvent.JOB_FAILED); | |
| throw new PrintException(e); | |
| } | |
| } else if (repClassName.equals("java.awt.print.Printable")) { | |
| try { | |
| printableJob((Printable)doc.getPrintData()); | |
| if (service instanceof IPPPrintService) { | |
| ((IPPPrintService)service).wakeNotifier(); | |
| } else { | |
| ((UnixPrintService)service).wakeNotifier(); | |
| } | |
| return; | |
| } catch (ClassCastException | IOException e) { | |
| notifyEvent(PrintJobEvent.JOB_FAILED); | |
| throw new PrintException(e); | |
| } | |
| } else { | |
| notifyEvent(PrintJobEvent.JOB_FAILED); | |
| throw new PrintException("unrecognized class: "+repClassName); | |
| } | |
| // now spool the print data. | |
| PrinterOpener po = new PrinterOpener(); | |
| po.run(); | |
| if (po.pex != null) { | |
| throw po.pex; | |
| } | |
| OutputStream output = po.result; | |
| /* There are three cases: | |
| * 1) Text data from a Reader, just pass through. | |
| * 2) Text data from an input stream which we must read using the | |
| * correct encoding | |
| * 3) Raw byte data from an InputStream we don't interpret as text, | |
| * just pass through: eg postscript. | |
| */ | |
| BufferedWriter bw = null; | |
| if ((instream == null && reader != null)) { | |
| BufferedReader br = new BufferedReader(reader); | |
| OutputStreamWriter osw = new OutputStreamWriter(output); | |
| bw = new BufferedWriter(osw); | |
| char []buffer = new char[1024]; | |
| int cread; | |
| try { | |
| while ((cread = br.read(buffer, 0, buffer.length)) >=0) { | |
| bw.write(buffer, 0, cread); | |
| } | |
| br.close(); | |
| bw.flush(); | |
| bw.close(); | |
| } catch (IOException e) { | |
| notifyEvent(PrintJobEvent.JOB_FAILED); | |
| throw new PrintException (e); | |
| } | |
| } else if (instream != null && | |
| flavor.getMediaType().equalsIgnoreCase("text")) { | |
| try { | |
| InputStreamReader isr = new InputStreamReader(instream, | |
| encoding); | |
| BufferedReader br = new BufferedReader(isr); | |
| OutputStreamWriter osw = new OutputStreamWriter(output); | |
| bw = new BufferedWriter(osw); | |
| char []buffer = new char[1024]; | |
| int cread; | |
| while ((cread = br.read(buffer, 0, buffer.length)) >=0) { | |
| bw.write(buffer, 0, cread); | |
| } | |
| bw.flush(); | |
| } catch (IOException e) { | |
| notifyEvent(PrintJobEvent.JOB_FAILED); | |
| throw new PrintException (e); | |
| } finally { | |
| try { | |
| if (bw != null) { | |
| bw.close(); | |
| } | |
| } catch (IOException e) { | |
| } | |
| } | |
| } else if (instream != null) { | |
| try (BufferedInputStream bin = new BufferedInputStream(instream); | |
| BufferedOutputStream bout = new BufferedOutputStream(output)) { | |
| bin.transferTo(bout); | |
| } catch (IOException e) { | |
| notifyEvent(PrintJobEvent.JOB_FAILED); | |
| throw new PrintException(e); | |
| } | |
| } | |
| notifyEvent(PrintJobEvent.DATA_TRANSFER_COMPLETE); | |
| if (mDestType == UnixPrintJob.DESTPRINTER) { | |
| PrinterSpooler spooler = new PrinterSpooler(); | |
| spooler.run(); | |
| if (spooler.pex != null) { | |
| throw spooler.pex; | |
| } | |
| } | |
| notifyEvent(PrintJobEvent.NO_MORE_EVENTS); | |
| if (service instanceof IPPPrintService) { | |
| ((IPPPrintService)service).wakeNotifier(); | |
| } else { | |
| ((UnixPrintService)service).wakeNotifier(); | |
| } | |
| } | |
| public void printableJob(Printable printable) throws PrintException { | |
| try { | |
| synchronized(this) { | |
| if (job != null) { // shouldn't happen | |
| throw new PrintException("already printing"); | |
| } else { | |
| job = new PSPrinterJob(); | |
| } | |
| } | |
| job.setPrintService(getPrintService()); | |
| job.setCopies(copies); | |
| job.setJobName(jobName); | |
| PageFormat pf = new PageFormat(); | |
| if (mediaSize != null) { | |
| Paper p = new Paper(); | |
| p.setSize(mediaSize.getX(MediaSize.INCH)*72.0, | |
| mediaSize.getY(MediaSize.INCH)*72.0); | |
| p.setImageableArea(72.0, 72.0, p.getWidth()-144.0, | |
| p.getHeight()-144.0); | |
| pf.setPaper(p); | |
| } | |
| if (orient == OrientationRequested.REVERSE_LANDSCAPE) { | |
| pf.setOrientation(PageFormat.REVERSE_LANDSCAPE); | |
| } else if (orient == OrientationRequested.LANDSCAPE) { | |
| pf.setOrientation(PageFormat.LANDSCAPE); | |
| } | |
| job.setPrintable(printable, pf); | |
| job.print(reqAttrSet); | |
| notifyEvent(PrintJobEvent.DATA_TRANSFER_COMPLETE); | |
| return; | |
| } catch (PrinterException pe) { | |
| notifyEvent(PrintJobEvent.JOB_FAILED); | |
| throw new PrintException(pe); | |
| } finally { | |
| printReturned = true; | |
| notifyEvent(PrintJobEvent.NO_MORE_EVENTS); | |
| } | |
| } | |
| public void pageableJob(Pageable pageable) throws PrintException { | |
| try { | |
| synchronized(this) { | |
| if (job != null) { // shouldn't happen | |
| throw new PrintException("already printing"); | |
| } else { | |
| job = new PSPrinterJob(); | |
| } | |
| } | |
| job.setPrintService(getPrintService()); | |
| job.setCopies(copies); | |
| job.setJobName(jobName); | |
| job.setPageable(pageable); | |
| job.print(reqAttrSet); | |
| notifyEvent(PrintJobEvent.DATA_TRANSFER_COMPLETE); | |
| return; | |
| } catch (PrinterException pe) { | |
| notifyEvent(PrintJobEvent.JOB_FAILED); | |
| throw new PrintException(pe); | |
| } finally { | |
| printReturned = true; | |
| notifyEvent(PrintJobEvent.NO_MORE_EVENTS); | |
| } | |
| } | |
| /* There's some inefficiency here as the job set is created even though | |
| * it may never be requested. | |
| */ | |
| private synchronized void | |
| initializeAttributeSets(Doc doc, PrintRequestAttributeSet reqSet) { | |
| reqAttrSet = new HashPrintRequestAttributeSet(); | |
| jobAttrSet = new HashPrintJobAttributeSet(); | |
| Attribute[] attrs; | |
| if (reqSet != null) { | |
| reqAttrSet.addAll(reqSet); | |
| attrs = reqSet.toArray(); | |
| for (int i=0; i<attrs.length; i++) { | |
| if (attrs[i] instanceof PrintJobAttribute) { | |
| jobAttrSet.add(attrs[i]); | |
| } | |
| } | |
| } | |
| DocAttributeSet docSet = doc.getAttributes(); | |
| if (docSet != null) { | |
| attrs = docSet.toArray(); | |
| for (int i=0; i<attrs.length; i++) { | |
| if (attrs[i] instanceof PrintRequestAttribute) { | |
| reqAttrSet.add(attrs[i]); | |
| } | |
| if (attrs[i] instanceof PrintJobAttribute) { | |
| jobAttrSet.add(attrs[i]); | |
| } | |
| } | |
| } | |
| /* add the user name to the job */ | |
| String userName = System.getProperty("user.name"); | |
| if (userName == null || userName.isEmpty()) { | |
| RequestingUserName ruName = | |
| (RequestingUserName)reqSet.get(RequestingUserName.class); | |
| if (ruName != null) { | |
| jobAttrSet.add( | |
| new JobOriginatingUserName(ruName.getValue(), | |
| ruName.getLocale())); | |
| } else { | |
| jobAttrSet.add(new JobOriginatingUserName("", null)); | |
| } | |
| } else { | |
| jobAttrSet.add(new JobOriginatingUserName(userName, null)); | |
| } | |
| /* if no job name supplied use doc name (if supplied), if none and | |
| * its a URL use that, else finally anything .. */ | |
| if (jobAttrSet.get(JobName.class) == null) { | |
| JobName jobName; | |
| if (docSet != null && docSet.get(DocumentName.class) != null) { | |
| DocumentName docName = | |
| (DocumentName)docSet.get(DocumentName.class); | |
| jobName = new JobName(docName.getValue(), docName.getLocale()); | |
| jobAttrSet.add(jobName); | |
| } else { | |
| String str = "JPS Job:" + doc; | |
| try { | |
| Object printData = doc.getPrintData(); | |
| if (printData instanceof URL) { | |
| str = ((URL)(doc.getPrintData())).toString(); | |
| } | |
| } catch (IOException e) { | |
| } | |
| jobName = new JobName(str, null); | |
| jobAttrSet.add(jobName); | |
| } | |
| } | |
| jobAttrSet = AttributeSetUtilities.unmodifiableView(jobAttrSet); | |
| } | |
| private void getAttributeValues(DocFlavor flavor) throws PrintException { | |
| Attribute attr; | |
| Class<? extends Attribute> category; | |
| if (reqAttrSet.get(Fidelity.class) == Fidelity.FIDELITY_TRUE) { | |
| fidelity = true; | |
| } else { | |
| fidelity = false; | |
| } | |
| Attribute []attrs = reqAttrSet.toArray(); | |
| for (int i=0; i<attrs.length; i++) { | |
| attr = attrs[i]; | |
| category = attr.getCategory(); | |
| if (fidelity == true) { | |
| if (!service.isAttributeCategorySupported(category)) { | |
| notifyEvent(PrintJobEvent.JOB_FAILED); | |
| throw new PrintJobAttributeException( | |
| "unsupported category: " + category, category, null); | |
| } else if | |
| (!service.isAttributeValueSupported(attr, flavor, null)) { | |
| notifyEvent(PrintJobEvent.JOB_FAILED); | |
| throw new PrintJobAttributeException( | |
| "unsupported attribute: " + attr, null, attr); | |
| } | |
| } | |
| if (category == Destination.class) { | |
| URI uri = ((Destination)attr).getURI(); | |
| if (!"file".equals(uri.getScheme())) { | |
| notifyEvent(PrintJobEvent.JOB_FAILED); | |
| throw new PrintException("Not a file: URI"); | |
| } else { | |
| try { | |
| mDestType = DESTFILE; | |
| mDestination = (new File(uri)).getPath(); | |
| } catch (Exception e) { | |
| throw new PrintException(e); | |
| } | |
| } | |
| } else if (category == JobSheets.class) { | |
| if ((JobSheets)attr == JobSheets.NONE) { | |
| mNoJobSheet = true; | |
| } | |
| } else if (category == JobName.class) { | |
| jobName = ((JobName)attr).getValue(); | |
| } else if (category == Copies.class) { | |
| copies = ((Copies)attr).getValue(); | |
| } else if (category == Media.class) { | |
| if (attr instanceof MediaSizeName) { | |
| mediaName = (MediaSizeName)attr; | |
| IPPPrintService.debug_println(debugPrefix+ | |
| "mediaName "+mediaName); | |
| if (!service.isAttributeValueSupported(attr, null, null)) { | |
| mediaSize = MediaSize.getMediaSizeForName(mediaName); | |
| } | |
| } else if (attr instanceof CustomMediaTray) { | |
| customTray = (CustomMediaTray)attr; | |
| } | |
| } else if (category == OrientationRequested.class) { | |
| orient = (OrientationRequested)attr; | |
| } else if (category == NumberUp.class) { | |
| nUp = (NumberUp)attr; | |
| } else if (category == Sides.class) { | |
| sides = (Sides)attr; | |
| } | |
| } | |
| } | |
| private String[] printExecCmd(String printer, String options, | |
| boolean noJobSheet, | |
| String jobTitle, int copies, String spoolFile) { | |
| int PRINTER = 0x1; | |
| int OPTIONS = 0x2; | |
| int JOBTITLE = 0x4; | |
| int COPIES = 0x8; | |
| int NOSHEET = 0x10; | |
| int pFlags = 0; | |
| String[] execCmd; | |
| int ncomps = 2; // minimum number of print args | |
| int n = 0; | |
| // conveniently "lp" is the default destination for both lp and lpr. | |
| if (printer != null && !printer.isEmpty() && !printer.equals("lp")) { | |
| pFlags |= PRINTER; | |
| ncomps+=1; | |
| } | |
| if (options != null && !options.isEmpty()) { | |
| pFlags |= OPTIONS; | |
| ncomps+=options.trim().split(" ").length; | |
| } | |
| if (jobTitle != null && !jobTitle.isEmpty()) { | |
| pFlags |= JOBTITLE; | |
| ncomps+=1; | |
| } | |
| if (copies > 1) { | |
| pFlags |= COPIES; | |
| ncomps+=1; | |
| } | |
| if (noJobSheet) { | |
| pFlags |= NOSHEET; | |
| ncomps+=1; | |
| } else if (getPrintService(). | |
| isAttributeCategorySupported(JobSheets.class)) { | |
| ncomps+=1; | |
| } | |
| execCmd = new String[ncomps]; | |
| execCmd[n++] = "/usr/bin/lpr"; | |
| if ((pFlags & PRINTER) != 0) { | |
| execCmd[n++] = "-P" + printer; | |
| } | |
| if ((pFlags & JOBTITLE) != 0) { | |
| execCmd[n++] = "-J " + jobTitle; | |
| } | |
| if ((pFlags & COPIES) != 0) { | |
| execCmd[n++] = "-#" + copies; | |
| } | |
| if ((pFlags & NOSHEET) != 0) { | |
| execCmd[n++] = "-h"; | |
| } else if (getPrintService(). | |
| isAttributeCategorySupported(JobSheets.class)) { | |
| execCmd[n++] = "-o job-sheets=standard"; | |
| } | |
| if ((pFlags & OPTIONS) != 0) { | |
| for (String option : options.trim().split(" ")) { | |
| execCmd[n++] = "-o " + option; | |
| } | |
| } | |
| execCmd[n++] = spoolFile; | |
| if (IPPPrintService.debugPrint) { | |
| System.out.println("UnixPrintJob>> execCmd"); | |
| for (int i=0; i<execCmd.length; i++) { | |
| System.out.print(" "+execCmd[i]); | |
| } | |
| System.out.println(); | |
| } | |
| return execCmd; | |
| } | |
| private static int DESTPRINTER = 1; | |
| private static int DESTFILE = 2; | |
| private int mDestType = DESTPRINTER; | |
| private File spoolFile; | |
| private String mDestination, mOptions=""; | |
| private boolean mNoJobSheet = false; | |
| private final class PrinterOpener { | |
| PrintException pex; | |
| OutputStream result; | |
| public OutputStream run() { | |
| try { | |
| if (mDestType == UnixPrintJob.DESTFILE) { | |
| spoolFile = new File(mDestination); | |
| } else { | |
| /* Write to a temporary file which will be spooled to | |
| * the printer then deleted. In the case that the file | |
| * is not removed for some reason, request that it is | |
| * removed when the VM exits. | |
| */ | |
| spoolFile = Files.createTempFile("javaprint", "").toFile(); | |
| spoolFile.deleteOnExit(); | |
| } | |
| result = new FileOutputStream(spoolFile); | |
| return result; | |
| } catch (IOException ex) { | |
| // If there is an IOError we subvert it to a PrinterException. | |
| notifyEvent(PrintJobEvent.JOB_FAILED); | |
| pex = new PrintException(ex); | |
| } | |
| return null; | |
| } | |
| } | |
| private final class PrinterSpooler { | |
| PrintException pex; | |
| private void handleProcessFailure(final Process failedProcess, | |
| final String[] execCmd, final int result) throws IOException { | |
| try (StringWriter sw = new StringWriter(); | |
| PrintWriter pw = new PrintWriter(sw)) { | |
| pw.append("error=").append(Integer.toString(result)); | |
| pw.append(" running:"); | |
| for (String arg: execCmd) { | |
| pw.append(" '").append(arg).append("'"); | |
| } | |
| try (InputStream is = failedProcess.getErrorStream(); | |
| InputStreamReader isr = new InputStreamReader(is); | |
| BufferedReader br = new BufferedReader(isr)) { | |
| while (br.ready()) { | |
| pw.println(); | |
| pw.append("\t\t").append(br.readLine()); | |
| } | |
| } finally { | |
| pw.flush(); | |
| } | |
| throw new IOException(sw.toString()); | |
| } | |
| } | |
| public Object run() { | |
| if (spoolFile == null || !spoolFile.exists()) { | |
| pex = new PrintException("No spool file"); | |
| notifyEvent(PrintJobEvent.JOB_FAILED); | |
| return null; | |
| } | |
| try { | |
| /** | |
| * Spool to the printer. | |
| */ | |
| String fileName = spoolFile.getAbsolutePath(); | |
| String[] execCmd = printExecCmd(mDestination, mOptions, | |
| mNoJobSheet, jobName, copies, fileName); | |
| Process process = Runtime.getRuntime().exec(execCmd); | |
| process.waitFor(); | |
| final int result = process.exitValue(); | |
| if (0 != result) { | |
| handleProcessFailure(process, execCmd, result); | |
| } | |
| notifyEvent(PrintJobEvent.DATA_TRANSFER_COMPLETE); | |
| } catch (IOException ex) { | |
| notifyEvent(PrintJobEvent.JOB_FAILED); | |
| // REMIND : 2d printing throws PrinterException | |
| pex = new PrintException(ex); | |
| } catch (InterruptedException ie) { | |
| notifyEvent(PrintJobEvent.JOB_FAILED); | |
| pex = new PrintException(ie); | |
| } finally { | |
| spoolFile.delete(); | |
| notifyEvent(PrintJobEvent.NO_MORE_EVENTS); | |
| } | |
| return null; | |
| } | |
| } | |
| @Override | |
| public void cancel() throws PrintException { | |
| synchronized (this) { | |
| if (!printing) { | |
| throw new PrintException("Job is not yet submitted."); | |
| } else if (job != null && !printReturned) { | |
| job.cancel(); | |
| notifyEvent(PrintJobEvent.JOB_CANCELED); | |
| return; | |
| } else { | |
| throw new PrintException("Job could not be cancelled."); | |
| } | |
| } | |
| } | |
| } |