1 /*
   2  * Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.print;
  27 
  28 import java.security.AccessController;
  29 import java.util.ArrayList;
  30 import java.util.Arrays;
  31 import javax.print.DocFlavor;
  32 import javax.print.MultiDocPrintService;
  33 import javax.print.PrintService;
  34 import javax.print.PrintServiceLookup;
  35 import javax.print.attribute.Attribute;
  36 import javax.print.attribute.AttributeSet;
  37 import javax.print.attribute.HashPrintRequestAttributeSet;
  38 import javax.print.attribute.HashPrintServiceAttributeSet;
  39 import javax.print.attribute.PrintRequestAttribute;
  40 import javax.print.attribute.PrintRequestAttributeSet;
  41 import javax.print.attribute.PrintServiceAttribute;
  42 import javax.print.attribute.PrintServiceAttributeSet;
  43 import javax.print.attribute.standard.PrinterName;
  44 
  45 public class PrintServiceLookupProvider extends PrintServiceLookup {
  46 
  47     private String defaultPrinter;
  48     private PrintService defaultPrintService;
  49     private String[] printers; /* excludes the default printer */
  50     private PrintService[] printServices; /* includes the default printer */
  51 
  52     private static final int DEFAULT_REFRESH_TIME = 240;  // 4 minutes
  53     private static final int MINIMUM_REFRESH_TIME = 120;  // 2 minutes
  54     private static final boolean pollServices;
  55     private static final int refreshTime;
  56 
  57     static {
  58         /* The system property "sun.java2d.print.polling"
  59          * can be used to force the printing code to poll or not poll
  60          * for PrintServices.
  61          */
  62         String pollStr = java.security.AccessController.doPrivileged(
  63             new sun.security.action.GetPropertyAction("sun.java2d.print.polling"));
  64         pollServices = !("false".equalsIgnoreCase(pollStr));
  65 
  66         /* The system property "sun.java2d.print.minRefreshTime"
  67          * can be used to specify minimum refresh time (in seconds)
  68          * for polling PrintServices.  The default is 240.
  69          */
  70         String refreshTimeStr = java.security.AccessController.doPrivileged(
  71             new sun.security.action.GetPropertyAction(
  72                 "sun.java2d.print.minRefreshTime"));
  73         refreshTime = (refreshTimeStr != null)
  74                       ? getRefreshTime(refreshTimeStr)
  75                       : DEFAULT_REFRESH_TIME;
  76 
  77         java.security.AccessController.doPrivileged(
  78             new java.security.PrivilegedAction<Void>() {
  79                 public Void run() {
  80                     System.loadLibrary("awt");
  81                     return null;
  82                 }
  83             });
  84     }
  85 
  86     private static int getRefreshTime(final String refreshTimeStr) {
  87         try {
  88             int minRefreshTime = Integer.parseInt(refreshTimeStr);
  89             return (minRefreshTime < MINIMUM_REFRESH_TIME)
  90                    ? MINIMUM_REFRESH_TIME
  91                    : minRefreshTime;
  92         } catch (NumberFormatException e) {
  93             return DEFAULT_REFRESH_TIME;
  94         }
  95     }
  96 
  97     /* The singleton win32 print lookup service.
  98      * Code that is aware of this field and wants to use it must first
  99      * see if its null, and if so instantiate it by calling a method such as
 100      * javax.print.PrintServiceLookup.defaultPrintService() so that the
 101      * same instance is stored there.
 102      */
 103     private static PrintServiceLookupProvider win32PrintLUS;
 104 
 105     /* Think carefully before calling this. Preferably don't call it. */
 106     public static PrintServiceLookupProvider getWin32PrintLUS() {
 107         if (win32PrintLUS == null) {
 108             /* This call is internally synchronized.
 109              * When it returns an instance of this class will have
 110              * been instantiated - else there's a JDK internal error.
 111              */
 112             PrintServiceLookup.lookupDefaultPrintService();
 113         }
 114         return win32PrintLUS;
 115     }
 116 
 117     public PrintServiceLookupProvider() {
 118 
 119         if (win32PrintLUS == null) {
 120             win32PrintLUS = this;
 121 
 122             String osName = AccessController.doPrivileged(
 123                 new sun.security.action.GetPropertyAction("os.name"));
 124             // There's no capability for Win98 to refresh printers.
 125             // See "OpenPrinter" for more info.
 126             if (osName != null && osName.startsWith("Windows 98")) {
 127                 return;
 128             }
 129             // start the local printer listener thread
 130             Thread thr = new Thread(null, new PrinterChangeListener(),
 131                                     "PrinterListener", 0, false);
 132             thr.setDaemon(true);
 133             thr.start();
 134 
 135             if (pollServices) {
 136                 // start the remote printer listener thread
 137                 Thread remThr = new Thread(null, new RemotePrinterChangeListener(),
 138                                            "RemotePrinterListener", 0, false);
 139                 remThr.setDaemon(true);
 140                 remThr.start();
 141             }
 142         } /* else condition ought to never happen! */
 143     }
 144 
 145     /* Want the PrintService which is default print service to have
 146      * equality of reference with the equivalent in list of print services
 147      * This isn't required by the API and there's a risk doing this will
 148      * lead people to assume its guaranteed.
 149      */
 150     public synchronized PrintService[] getPrintServices() {
 151         SecurityManager security = System.getSecurityManager();
 152         if (security != null) {
 153             security.checkPrintJobAccess();
 154         }
 155         if (printServices == null) {
 156             refreshServices();
 157         }
 158         return printServices;
 159     }
 160 
 161     private synchronized void refreshServices() {
 162         printers = getAllPrinterNames();
 163         if (printers == null) {
 164             // In Windows it is safe to assume no default if printers == null so we
 165             // don't get the default.
 166             printServices = new PrintService[0];
 167             return;
 168         }
 169 
 170         PrintService[] newServices = new PrintService[printers.length];
 171         PrintService defService = getDefaultPrintService();
 172         for (int p = 0; p < printers.length; p++) {
 173             if (defService != null &&
 174                 printers[p].equals(defService.getName())) {
 175                 newServices[p] = defService;
 176             } else {
 177                 if (printServices == null) {
 178                     newServices[p] = new Win32PrintService(printers[p]);
 179                 } else {
 180                     int j;
 181                     for (j = 0; j < printServices.length; j++) {
 182                         if ((printServices[j]!= null) &&
 183                             (printers[p].equals(printServices[j].getName()))) {
 184                             newServices[p] = printServices[j];
 185                             printServices[j] = null;
 186                             break;
 187                         }
 188                     }
 189                     if (j == printServices.length) {
 190                         newServices[p] = new Win32PrintService(printers[p]);
 191                     }
 192                 }
 193             }
 194         }
 195 
 196         // Look for deleted services and invalidate these
 197         if (printServices != null) {
 198             for (int j=0; j < printServices.length; j++) {
 199                 if ((printServices[j] instanceof Win32PrintService) &&
 200                     (!printServices[j].equals(defaultPrintService))) {
 201                     ((Win32PrintService)printServices[j]).invalidateService();
 202                 }
 203             }
 204         }
 205         printServices = newServices;
 206     }
 207 
 208 
 209     public synchronized PrintService getPrintServiceByName(String name) {
 210 
 211         if (name == null || name.isEmpty()) {
 212             return null;
 213         } else {
 214             /* getPrintServices() is now very fast. */
 215             PrintService[] printServices = getPrintServices();
 216             for (int i=0; i<printServices.length; i++) {
 217                 if (printServices[i].getName().equals(name)) {
 218                     return printServices[i];
 219                 }
 220             }
 221             return null;
 222         }
 223     }
 224 
 225     @SuppressWarnings("unchecked") // Cast to Class<PrintServiceAttribute>
 226     boolean matchingService(PrintService service,
 227                             PrintServiceAttributeSet serviceSet) {
 228         if (serviceSet != null) {
 229             Attribute [] attrs =  serviceSet.toArray();
 230             Attribute serviceAttr;
 231             for (int i=0; i<attrs.length; i++) {
 232                 serviceAttr
 233                     = service.getAttribute((Class<PrintServiceAttribute>)attrs[i].getCategory());
 234                 if (serviceAttr == null || !serviceAttr.equals(attrs[i])) {
 235                     return false;
 236                 }
 237             }
 238         }
 239         return true;
 240     }
 241 
 242     public PrintService[] getPrintServices(DocFlavor flavor,
 243                                            AttributeSet attributes) {
 244 
 245         SecurityManager security = System.getSecurityManager();
 246         if (security != null) {
 247           security.checkPrintJobAccess();
 248         }
 249         PrintRequestAttributeSet requestSet = null;
 250         PrintServiceAttributeSet serviceSet = null;
 251 
 252         if (attributes != null && !attributes.isEmpty()) {
 253 
 254             requestSet = new HashPrintRequestAttributeSet();
 255             serviceSet = new HashPrintServiceAttributeSet();
 256 
 257             Attribute[] attrs = attributes.toArray();
 258             for (int i=0; i<attrs.length; i++) {
 259                 if (attrs[i] instanceof PrintRequestAttribute) {
 260                     requestSet.add(attrs[i]);
 261                 } else if (attrs[i] instanceof PrintServiceAttribute) {
 262                     serviceSet.add(attrs[i]);
 263                 }
 264             }
 265         }
 266 
 267         /*
 268          * Special case: If client is asking for a particular printer
 269          * (by name) then we can save time by getting just that service
 270          * to check against the rest of the specified attributes.
 271          */
 272         PrintService[] services = null;
 273         if (serviceSet != null && serviceSet.get(PrinterName.class) != null) {
 274             PrinterName name = (PrinterName)serviceSet.get(PrinterName.class);
 275             PrintService service = getPrintServiceByName(name.getValue());
 276             if (service == null || !matchingService(service, serviceSet)) {
 277                 services = new PrintService[0];
 278             } else {
 279                 services = new PrintService[1];
 280                 services[0] = service;
 281             }
 282         } else {
 283             services = getPrintServices();
 284         }
 285 
 286         if (services.length == 0) {
 287             return services;
 288         } else {
 289             ArrayList<PrintService> matchingServices = new ArrayList<>();
 290             for (int i=0; i<services.length; i++) {
 291                 try {
 292                     if (services[i].
 293                         getUnsupportedAttributes(flavor, requestSet) == null) {
 294                         matchingServices.add(services[i]);
 295                     }
 296                 } catch (IllegalArgumentException e) {
 297                 }
 298             }
 299             services = new PrintService[matchingServices.size()];
 300             return matchingServices.toArray(services);
 301         }
 302     }
 303 
 304     /*
 305      * return empty array as don't support multi docs
 306      */
 307     public MultiDocPrintService[]
 308         getMultiDocPrintServices(DocFlavor[] flavors,
 309                                  AttributeSet attributes) {
 310         SecurityManager security = System.getSecurityManager();
 311         if (security != null) {
 312           security.checkPrintJobAccess();
 313         }
 314         return new MultiDocPrintService[0];
 315     }
 316 
 317 
 318     public synchronized PrintService getDefaultPrintService() {
 319         SecurityManager security = System.getSecurityManager();
 320         if (security != null) {
 321           security.checkPrintJobAccess();
 322         }
 323 
 324 
 325         // Windows does not have notification for a change in default
 326         // so we always get the latest.
 327         defaultPrinter = getDefaultPrinterName();
 328         if (defaultPrinter == null) {
 329             return null;
 330         }
 331 
 332         if ((defaultPrintService != null) &&
 333             defaultPrintService.getName().equals(defaultPrinter)) {
 334 
 335             return defaultPrintService;
 336         }
 337 
 338          // Not the same as default so proceed to get new PrintService.
 339 
 340         // clear defaultPrintService
 341         defaultPrintService = null;
 342 
 343         if (printServices != null) {
 344             for (int j=0; j<printServices.length; j++) {
 345                 if (defaultPrinter.equals(printServices[j].getName())) {
 346                     defaultPrintService = printServices[j];
 347                     break;
 348                 }
 349             }
 350         }
 351 
 352         if (defaultPrintService == null) {
 353             defaultPrintService = new Win32PrintService(defaultPrinter);
 354         }
 355         return defaultPrintService;
 356     }
 357 
 358     class PrinterChangeListener implements Runnable {
 359         long chgObj;
 360         PrinterChangeListener() {
 361             chgObj = notifyFirstPrinterChange(null);
 362         }
 363 
 364         @Override
 365         public void run() {
 366             if (chgObj != -1) {
 367                 while (true) {
 368                     // wait for configuration to change
 369                     if (notifyPrinterChange(chgObj) != 0) {
 370                         try {
 371                             refreshServices();
 372                         } catch (SecurityException se) {
 373                             break;
 374                         }
 375                     } else {
 376                         notifyClosePrinterChange(chgObj);
 377                         break;
 378                     }
 379                 }
 380             }
 381         }
 382     }
 383 
 384     /* Windows provides *PrinterChangeNotification* functions that provides
 385        information about printer status changes of the local printers but not
 386        network printers.
 387        Alternatively, Windows provides a way through which one can get the
 388        network printer status changes by using WMI, RegistryKeyChange combination,
 389        which is a slightly complex mechanism.
 390        The Windows WMI offers an async and sync method to read through registry
 391        via the WQL query. The async method is considered dangerous as it leaves
 392        open a channel until we close it. But the async method has the advantage of
 393        being notified of a change in registry by calling callback without polling for it.
 394        The sync method uses the polling mechanism to notify.
 395        RegistryValueChange cannot be used in combination with WMI to get registry
 396        value change notification because of an error that may be generated because the
 397        scope of the query would be too big to handle(at times).
 398        Hence an alternative mechanism is chosen via the EnumPrinters by polling for the
 399        count of printer status changes(add\remove) and based on it update the printers
 400        list.
 401     */
 402     class RemotePrinterChangeListener implements Runnable {
 403         private String[] prevRemotePrinters;
 404 
 405         RemotePrinterChangeListener() {
 406         }
 407 
 408         @Override
 409         public void run() {
 410             // Init the list of remote printers
 411             prevRemotePrinters = getRemotePrintersNames();
 412 
 413             while (true) {
 414                 try {
 415                     Thread.sleep(refreshTime * 1000);
 416                 } catch (InterruptedException e) {
 417                     break;
 418                 }
 419 
 420                 String[] currentRemotePrinters = getRemotePrintersNames();
 421                 if (!Arrays.equals(prevRemotePrinters, currentRemotePrinters)) {
 422                     // The list of remote printers got updated,
 423                     // so update the cached list printers which
 424                     // includes both local and network printers
 425                     refreshServices();
 426 
 427                     // store the current data for next comparison
 428                     prevRemotePrinters = currentRemotePrinters;
 429                 }
 430             }
 431         }
 432     }
 433 
 434     private native String getDefaultPrinterName();
 435     private native String[] getAllPrinterNames();
 436     private native long notifyFirstPrinterChange(String printer);
 437     private native void notifyClosePrinterChange(long chgObj);
 438     private native int notifyPrinterChange(long chgObj);
 439     private native String[] getRemotePrintersNames();
 440 }