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