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