/* * Copyright (c) 2000, 2015, 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.io.BufferedReader; import java.io.FileInputStream; import java.io.InputStream; import java.io.InputStreamReader; import java.io.IOException; import java.util.ArrayList; import java.util.Vector; import java.security.AccessController; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; import javax.print.DocFlavor; import javax.print.MultiDocPrintService; import javax.print.PrintService; import javax.print.PrintServiceLookup; import javax.print.attribute.Attribute; import javax.print.attribute.AttributeSet; import javax.print.attribute.HashPrintRequestAttributeSet; import javax.print.attribute.HashPrintServiceAttributeSet; import javax.print.attribute.PrintRequestAttribute; import javax.print.attribute.PrintRequestAttributeSet; import javax.print.attribute.PrintServiceAttribute; import javax.print.attribute.PrintServiceAttributeSet; import javax.print.attribute.standard.PrinterName; import javax.print.attribute.standard.PrinterURI; import java.io.File; import java.io.FileReader; import java.net.URL; import java.nio.file.Files; /* * Remind: This class uses solaris commands. We also need a linux * version */ public class PrintServiceLookupProvider extends PrintServiceLookup implements BackgroundServiceLookup, Runnable { /* Remind: the current implementation is static, as its assumed * its preferable to minimize creation of PrintService instances. * Later we should add logic to add/remove services on the fly which * will take a hit of needing to regather the list of services. */ private String defaultPrinter; private PrintService defaultPrintService; private PrintService[] printServices; /* includes the default printer */ private Vector lookupListeners = null; private static String debugPrefix = "PrintServiceLookupProvider>> "; private static boolean pollServices = true; private static final int DEFAULT_MINREFRESH = 120; // 2 minutes private static int minRefreshTime = DEFAULT_MINREFRESH; static String osname; // List of commands used to deal with the printer queues on AIX String[] lpNameComAix = { "/usr/bin/lsallq", "/usr/bin/lpstat -W -p|/usr/bin/expand|/usr/bin/cut -f1 -d' '", "/usr/bin/lpstat -W -d|/usr/bin/expand|/usr/bin/cut -f1 -d' '", "/usr/bin/lpstat -W -v" }; private static final int aix_lsallq = 0; private static final int aix_lpstat_p = 1; private static final int aix_lpstat_d = 2; private static final int aix_lpstat_v = 3; private static int aix_defaultPrinterEnumeration = aix_lsallq; static { /* The system property "sun.java2d.print.polling" * can be used to force the printing code to poll or not poll * for PrintServices. */ String pollStr = java.security.AccessController.doPrivileged( new sun.security.action.GetPropertyAction("sun.java2d.print.polling")); if (pollStr != null) { if (pollStr.equalsIgnoreCase("true")) { pollServices = true; } else if (pollStr.equalsIgnoreCase("false")) { pollServices = false; } } /* The system property "sun.java2d.print.minRefreshTime" * can be used to specify minimum refresh time (in seconds) * for polling PrintServices. The default is 120. */ String refreshTimeStr = java.security.AccessController.doPrivileged( new sun.security.action.GetPropertyAction( "sun.java2d.print.minRefreshTime")); if (refreshTimeStr != null) { try { minRefreshTime = (new Integer(refreshTimeStr)).intValue(); } catch (NumberFormatException e) { } if (minRefreshTime < DEFAULT_MINREFRESH) { minRefreshTime = DEFAULT_MINREFRESH; } } osname = java.security.AccessController.doPrivileged( new sun.security.action.GetPropertyAction("os.name")); /* The system property "sun.java2d.print.aix.lpstat" * can be used to force the usage of 'lpstat -p' to enumerate all * printer queues. By default we use 'lsallq', because 'lpstat -p' can * take lots of time if thousands of printers are attached to a server. */ if (isAIX()) { String aixPrinterEnumerator = java.security.AccessController.doPrivileged( new sun.security.action.GetPropertyAction("sun.java2d.print.aix.lpstat")); if (aixPrinterEnumerator != null) { if (aixPrinterEnumerator.equalsIgnoreCase("lpstat")) { aix_defaultPrinterEnumeration = aix_lpstat_p; } else if (aixPrinterEnumerator.equalsIgnoreCase("lsallq")) { aix_defaultPrinterEnumeration = aix_lsallq; } } } } static boolean isMac() { return osname.startsWith("Mac"); } static boolean isSysV() { return osname.equals("SunOS"); } static boolean isLinux() { return (osname.equals("Linux")); } static boolean isBSD() { return (osname.equals("Linux") || osname.contains("OS X")); } static boolean isAIX() { return osname.equals("AIX"); } static final int UNINITIALIZED = -1; static final int BSD_LPD = 0; static final int BSD_LPD_NG = 1; static int cmdIndex = UNINITIALIZED; String[] lpcFirstCom = { "/usr/sbin/lpc status | grep : | sed -ne '1,1 s/://p'", "/usr/sbin/lpc status | grep -E '^[ 0-9a-zA-Z_-]*@' | awk -F'@' '{print $1}'" }; String[] lpcAllCom = { "/usr/sbin/lpc status all | grep : | sed -e 's/://'", "/usr/sbin/lpc status all | grep -E '^[ 0-9a-zA-Z_-]*@' | awk -F'@' '{print $1}' | sort" }; String[] lpcNameCom = { "| grep : | sed -ne 's/://p'", "| grep -E '^[ 0-9a-zA-Z_-]*@' | awk -F'@' '{print $1}'" }; static int getBSDCommandIndex() { String command = "/usr/sbin/lpc status all"; String[] names = execCmd(command); if ((names == null) || (names.length == 0)) { return BSD_LPD_NG; } for (int i=0; i printerList, PrintService ps) { int index = printerList.indexOf(ps); // Check if PrintService with same name is already in the list. if (CUPSPrinter.isCupsRunning() && index != -1) { // Bug in Linux: Duplicate entry of a remote printer // and treats it as local printer but it is returning wrong // information when queried using IPP. Workaround is to remove it. // Even CUPS ignores these entries as shown in lpstat or using // their web configuration. PrinterURI uri = ps.getAttribute(PrinterURI.class); if (uri.getURI().getHost().equals("localhost")) { IPPPrintService.debug_println(debugPrefix+"duplicate PrintService, ignoring the new local printer: "+ps); return index; // Do not add this. } PrintService oldPS = printerList.get(index); uri = oldPS.getAttribute(PrinterURI.class); if (uri.getURI().getHost().equals("localhost")) { IPPPrintService.debug_println(debugPrefix+"duplicate PrintService, removing existing local printer: "+oldPS); printerList.remove(oldPS); } else { return index; } } printerList.add(ps); return (printerList.size() - 1); } // refreshes "printServices" public synchronized void refreshServices() { /* excludes the default printer */ String[] printers = null; // array of printer names String[] printerURIs = null; //array of printer URIs try { getDefaultPrintService(); } catch (Throwable t) { IPPPrintService.debug_println(debugPrefix+ "Exception getting default printer : " + t); } if (CUPSPrinter.isCupsRunning()) { try { printerURIs = CUPSPrinter.getAllPrinters(); IPPPrintService.debug_println("CUPS URIs = " + printerURIs); if (printerURIs != null) { for (int p = 0; p < printerURIs.length; p++) { IPPPrintService.debug_println("URI="+printerURIs[p]); } } } catch (Throwable t) { IPPPrintService.debug_println(debugPrefix+ "Exception getting all CUPS printers : " + t); } if ((printerURIs != null) && (printerURIs.length > 0)) { printers = new String[printerURIs.length]; for (int i=0; i printerList = new ArrayList<>(); int defaultIndex = -1; for (int p=0; p 0) { PrintService saveService = printServices[0]; printServices[0] = printServices[defaultIndex]; printServices[defaultIndex] = saveService; } } private boolean matchesAttributes(PrintService service, PrintServiceAttributeSet attributes) { Attribute [] attrs = attributes.toArray(); for (int i=0; i)attrs[i].getCategory()); if (serviceAttr == null || !serviceAttr.equals(attrs[i])) { return false; } } return true; } /* This checks for validity of the printer name before passing as * parameter to a shell command. */ private boolean checkPrinterName(String s) { char c; for (int i=0; i < s.length(); i++) { c = s.charAt(i); if (Character.isLetterOrDigit(c) || c == '-' || c == '_' || c == '.' || c == '/') { continue; } else { return false; } } return true; } /* * Gets the printer name compatible with the list of printers returned by * the system when we query default or all the available printers. */ private String getPrinterDestName(PrintService ps) { if (isMac()) { return ((IPPPrintService)ps).getDest(); } return ps.getName(); } /* On a network with many (hundreds) of network printers, it * can save several seconds if you know all you want is a particular * printer, to ask for that printer rather than retrieving all printers. */ private PrintService getServiceByName(PrinterName nameAttr) { String name = nameAttr.getValue(); if (name == null || name.equals("") || !checkPrinterName(name)) { return null; } /* check if all printers are already available */ if (printServices != null) { for (PrintService printService : printServices) { PrinterName printerName = printService.getAttribute(PrinterName.class); if (printerName.getValue().equals(name)) { return printService; } } } /* take CUPS into account first */ if (CUPSPrinter.isCupsRunning()) { try { return new IPPPrintService(name, new URL("http://"+ CUPSPrinter.getServer()+":"+ CUPSPrinter.getPort()+"/"+ name)); } catch (Exception e) { IPPPrintService.debug_println(debugPrefix+ " getServiceByName Exception "+ e); } } /* fallback if nothing not having a printer at this point */ PrintService printer = null; if (isMac() || isSysV()) { printer = getNamedPrinterNameSysV(name); } else if (isAIX()) { printer = getNamedPrinterNameAIX(name); } else { printer = getNamedPrinterNameBSD(name); } return printer; } private PrintService[] getPrintServices(PrintServiceAttributeSet serviceSet) { if (serviceSet == null || serviceSet.isEmpty()) { return getPrintServices(); } /* Typically expect that if a service attribute is specified that * its a printer name and there ought to be only one match. * Directly retrieve that service and confirm * that it meets the other requirements. * If printer name isn't mentioned then go a slow path checking * all printers if they meet the reqiremements. */ PrintService[] services; PrinterName name = (PrinterName)serviceSet.get(PrinterName.class); PrintService defService; if (name != null && (defService = getDefaultPrintService()) != null) { /* To avoid execing a unix command see if the client is asking * for the default printer by name, since we already have that * initialised. */ PrinterName defName = defService.getAttribute(PrinterName.class); if (defName != null && name.equals(defName)) { if (matchesAttributes(defService, serviceSet)) { services = new PrintService[1]; services[0] = defService; return services; } else { return new PrintService[0]; } } else { /* Its not the default service */ PrintService service = getServiceByName(name); if (service != null && matchesAttributes(service, serviceSet)) { services = new PrintService[1]; services[0] = service; return services; } else { return new PrintService[0]; } } } else { /* specified service attributes don't include a name.*/ Vector matchedServices = new Vector<>(); services = getPrintServices(); for (int i = 0; i< services.length; i++) { if (matchesAttributes(services[i], serviceSet)) { matchedServices.add(services[i]); } } services = new PrintService[matchedServices.size()]; for (int i = 0; i< services.length; i++) { services[i] = matchedServices.elementAt(i); } return services; } } /* * If service attributes are specified then there must be additional * filtering. */ public PrintService[] getPrintServices(DocFlavor flavor, AttributeSet attributes) { SecurityManager security = System.getSecurityManager(); if (security != null) { security.checkPrintJobAccess(); } PrintRequestAttributeSet requestSet = null; PrintServiceAttributeSet serviceSet = null; if (attributes != null && !attributes.isEmpty()) { requestSet = new HashPrintRequestAttributeSet(); serviceSet = new HashPrintServiceAttributeSet(); Attribute[] attrs = attributes.toArray(); for (int i=0; i matchingServices = new ArrayList<>(); for (int i=0; i= 2) { defaultPrinter = printerInfo[0]; psuri = printerInfo[1]; } } else { if (isMac() || isSysV()) { defaultPrinter = getDefaultPrinterNameSysV(); } else if (isAIX()) { defaultPrinter = getDefaultPrinterNameAIX(); } else { defaultPrinter = getDefaultPrinterNameBSD(); } } if (defaultPrinter == null) { return null; } defaultPrintService = null; if (printServices != null) { for (int j=0; j(); lookupListeners.add(listener); Thread lookupThread = new Thread(this); lookupThread.start(); } else { lookupListeners.add(listener); } } } /* This method isn't used in most cases because we rely on code in * javax.print.PrintServiceLookup. This is needed just for the cases * where those interfaces are by-passed. */ private PrintService[] copyOf(PrintService[] inArr) { if (inArr == null || inArr.length == 0) { return inArr; } else { PrintService []outArr = new PrintService[inArr.length]; System.arraycopy(inArr, 0, outArr, 0, inArr.length); return outArr; } } public void run() { PrintService[] services = getPrintServices(); synchronized (this) { BackgroundLookupListener listener; for (int i=0; i 0) { return null; } else { return new UnixPrintService(name); } } private String[] getAllPrinterNamesSysV() { String defaultPrinter = "lp"; String command = "/usr/bin/lpstat -v|/usr/bin/expand|/usr/bin/cut -f3 -d' ' |/usr/bin/cut -f1 -d':' | /usr/bin/sort"; String [] names = execCmd(command); ArrayList printerNames = new ArrayList<>(); for (int i=0; i < names.length; i++) { if (!names[i].equals("_default") && !names[i].equals(defaultPrinter) && !names[i].equals("")) { printerNames.add(names[i]); } } return printerNames.toArray(new String[printerNames.size()]); } private String getDefaultPrinterNameAIX() { String[] names = execCmd(lpNameComAix[aix_lpstat_d]); // Remove headers and bogus entries added by remote printers. names = UnixPrintService.filterPrinterNamesAIX(names); if (names == null || names.length != 1) { // No default printer found return null; } else { return names[0]; } } private PrintService getNamedPrinterNameAIX(String name) { // On AIX there should be no blank after '-v'. String[] result = execCmd(lpNameComAix[aix_lpstat_v] + name); // Remove headers and bogus entries added by remote printers. result = UnixPrintService.filterPrinterNamesAIX(result); if (result == null || result.length != 1) { return null; } else { return new UnixPrintService(name); } } private String[] getAllPrinterNamesAIX() { // Determine all printers of the system. String [] names = execCmd(lpNameComAix[aix_defaultPrinterEnumeration]); // Remove headers and bogus entries added by remote printers. names = UnixPrintService.filterPrinterNamesAIX(names); ArrayList printerNames = new ArrayList(); for ( int i=0; i < names.length; i++) { printerNames.add(names[i]); } return printerNames.toArray(new String[printerNames.size()]); } static String[] execCmd(final String command) { ArrayList results = null; try { final String[] cmd = new String[3]; if (isSysV() || isAIX()) { cmd[0] = "/usr/bin/sh"; cmd[1] = "-c"; cmd[2] = "env LC_ALL=C " + command; } else { cmd[0] = "/bin/sh"; cmd[1] = "-c"; cmd[2] = "LC_ALL=C " + command; } results = AccessController.doPrivileged( new PrivilegedExceptionAction>() { public ArrayList run() throws IOException { Process proc; BufferedReader bufferedReader = null; File f = Files.createTempFile("prn","xc").toFile(); cmd[2] = cmd[2]+">"+f.getAbsolutePath(); proc = Runtime.getRuntime().exec(cmd); try { boolean done = false; // in case of interrupt. while (!done) { try { proc.waitFor(); done = true; } catch (InterruptedException e) { } } if (proc.exitValue() == 0) { FileReader reader = new FileReader(f); bufferedReader = new BufferedReader(reader); String line; ArrayList results = new ArrayList<>(); while ((line = bufferedReader.readLine()) != null) { results.add(line); } return results; } } finally { f.delete(); // promptly close all streams. if (bufferedReader != null) { bufferedReader.close(); } proc.getInputStream().close(); proc.getErrorStream().close(); proc.getOutputStream().close(); } return null; } }); } catch (PrivilegedActionException e) { } if (results == null) { return new String[0]; } else { return results.toArray(new String[results.size()]); } } private class PrinterChangeListener implements Runnable { @Override public void run() { int refreshSecs; while (true) { try { refreshServices(); } catch (Exception se) { IPPPrintService.debug_println(debugPrefix+"Exception in refresh thread."); break; } if ((printServices != null) && (printServices.length > minRefreshTime)) { // compute new refresh time 1 printer = 1 sec refreshSecs = printServices.length; } else { refreshSecs = minRefreshTime; } try { Thread.sleep(refreshSecs * 1000); } catch (InterruptedException e) { break; } } } } }