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