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