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 PrinterChangeListener();
 132             thr.setDaemon(true);
 133             thr.start();
 134 
 135             if (pollServices) {
 136                 // start the remote printer listener thread
 137                 Thread remThr = new RemotePrinterChangeListener();
 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.equals("")) {
 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     boolean matchingService(PrintService service,
 225                             PrintServiceAttributeSet serviceSet) {
 226         if (serviceSet != null) {
 227             Attribute [] attrs =  serviceSet.toArray();
 228             Attribute serviceAttr;
 229             for (int i=0; i<attrs.length; i++) {
 230                 serviceAttr
 231                     = service.getAttribute((Class<PrintServiceAttribute>)attrs[i].getCategory());
 232                 if (serviceAttr == null || !serviceAttr.equals(attrs[i])) {
 233                     return false;
 234                 }
 235             }
 236         }
 237         return true;
 238     }
 239 
 240     public PrintService[] getPrintServices(DocFlavor flavor,
 241                                            AttributeSet attributes) {
 242 
 243         SecurityManager security = System.getSecurityManager();
 244         if (security != null) {
 245           security.checkPrintJobAccess();
 246         }
 247         PrintRequestAttributeSet requestSet = null;
 248         PrintServiceAttributeSet serviceSet = null;
 249 
 250         if (attributes != null && !attributes.isEmpty()) {
 251 
 252             requestSet = new HashPrintRequestAttributeSet();
 253             serviceSet = new HashPrintServiceAttributeSet();
 254 
 255             Attribute[] attrs = attributes.toArray();
 256             for (int i=0; i<attrs.length; i++) {
 257                 if (attrs[i] instanceof PrintRequestAttribute) {
 258                     requestSet.add(attrs[i]);
 259                 } else if (attrs[i] instanceof PrintServiceAttribute) {
 260                     serviceSet.add(attrs[i]);
 261                 }
 262             }
 263         }
 264 
 265         /*
 266          * Special case: If client is asking for a particular printer
 267          * (by name) then we can save time by getting just that service
 268          * to check against the rest of the specified attributes.
 269          */
 270         PrintService[] services = null;
 271         if (serviceSet != null && serviceSet.get(PrinterName.class) != null) {
 272             PrinterName name = (PrinterName)serviceSet.get(PrinterName.class);
 273             PrintService service = getPrintServiceByName(name.getValue());
 274             if (service == null || !matchingService(service, serviceSet)) {
 275                 services = new PrintService[0];
 276             } else {
 277                 services = new PrintService[1];
 278                 services[0] = service;
 279             }
 280         } else {
 281             services = getPrintServices();
 282         }
 283 
 284         if (services.length == 0) {
 285             return services;
 286         } else {
 287             ArrayList matchingServices = new ArrayList();
 288             for (int i=0; i<services.length; i++) {
 289                 try {
 290                     if (services[i].
 291                         getUnsupportedAttributes(flavor, requestSet) == null) {
 292                         matchingServices.add(services[i]);
 293                     }
 294                 } catch (IllegalArgumentException e) {
 295                 }
 296             }
 297             services = new PrintService[matchingServices.size()];
 298             return (PrintService[])matchingServices.toArray(services);
 299         }
 300     }
 301 
 302     /*
 303      * return empty array as don't support multi docs
 304      */
 305     public MultiDocPrintService[]
 306         getMultiDocPrintServices(DocFlavor[] flavors,
 307                                  AttributeSet attributes) {
 308         SecurityManager security = System.getSecurityManager();
 309         if (security != null) {
 310           security.checkPrintJobAccess();
 311         }
 312         return new MultiDocPrintService[0];
 313     }
 314 
 315 
 316     public synchronized PrintService getDefaultPrintService() {
 317         SecurityManager security = System.getSecurityManager();
 318         if (security != null) {
 319           security.checkPrintJobAccess();
 320         }
 321 
 322 
 323         // Windows does not have notification for a change in default
 324         // so we always get the latest.
 325         defaultPrinter = getDefaultPrinterName();
 326         if (defaultPrinter == null) {
 327             return null;
 328         }
 329 
 330         if ((defaultPrintService != null) &&
 331             defaultPrintService.getName().equals(defaultPrinter)) {
 332 
 333             return defaultPrintService;
 334         }
 335 
 336          // Not the same as default so proceed to get new PrintService.
 337 
 338         // clear defaultPrintService
 339         defaultPrintService = null;
 340 
 341         if (printServices != null) {
 342             for (int j=0; j<printServices.length; j++) {
 343                 if (defaultPrinter.equals(printServices[j].getName())) {
 344                     defaultPrintService = printServices[j];
 345                     break;
 346                 }
 347             }
 348         }
 349 
 350         if (defaultPrintService == null) {
 351             defaultPrintService = new Win32PrintService(defaultPrinter);
 352         }
 353         return defaultPrintService;
 354     }
 355 
 356     class PrinterChangeListener extends Thread {
 357         long chgObj;
 358         PrinterChangeListener() {
 359             chgObj = notifyFirstPrinterChange(null);
 360         }
 361 
 362         public void run() {
 363             if (chgObj != -1) {
 364                 while (true) {
 365                     // wait for configuration to change
 366                     if (notifyPrinterChange(chgObj) != 0) {
 367                         try {
 368                             refreshServices();
 369                         } catch (SecurityException se) {
 370                             break;
 371                         }
 372                     } else {
 373                         notifyClosePrinterChange(chgObj);
 374                         break;
 375                     }
 376                 }
 377             }
 378         }
 379     }
 380 
 381     /* Windows provides *PrinterChangeNotification* functions that provides
 382        information about printer status changes of the local printers but not
 383        network printers.
 384        Alternatively, Windows provides a way through which one can get the
 385        network printer status changes by using WMI, RegistryKeyChange combination,
 386        which is a slightly complex mechanism.
 387        The Windows WMI offers an async and sync method to read through registry
 388        via the WQL query. The async method is considered dangerous as it leaves
 389        open a channel until we close it. But the async method has the advantage of
 390        being notified of a change in registry by calling callback without polling for it.
 391        The sync method uses the polling mechanism to notify.
 392        RegistryValueChange cannot be used in combination with WMI to get registry
 393        value change notification because of an error that may be generated because the
 394        scope of the query would be too big to handle(at times).
 395        Hence an alternative mechanism is chosen via the EnumPrinters by polling for the
 396        count of printer status changes(add\remove) and based on it update the printers
 397        list.
 398     */
 399     class RemotePrinterChangeListener extends Thread implements Comparator<String>{
 400         private String[] prevRemotePrinters;
 401 
 402         RemotePrinterChangeListener() {
 403             prevRemotePrinters = getRemotePrintersNames();
 404         }
 405 
 406         @Override
 407         public int compare(String o1, String o2) {
 408             return ((o1 == null)
 409                     ? ((o2 == null) ? 0 : 1)
 410                     : ((o2 == null) ? -1 : o1.compareTo(o2)));
 411         }
 412 
 413         @Override
 414         public void run() {
 415             // Init the list of remote printers
 416             String[] prevRemotePrinters = getRemotePrintersNames();
 417             if (prevRemotePrinters != null) {
 418                 Arrays.sort(prevRemotePrinters, this);
 419             }
 420 
 421             while (true) {
 422                 try {
 423                     Thread.sleep(refreshTime * 1000);
 424                 } catch (InterruptedException e) {
 425                     break;
 426                 }
 427 
 428                 String[] currentRemotePrinters = getRemotePrintersNames();
 429                 if (currentRemotePrinters != null) {
 430                     Arrays.sort(currentRemotePrinters, this);
 431                 }
 432                 if (!Arrays.equals(prevRemotePrinters, currentRemotePrinters)) {
 433                     // The list of remote printers got updated,
 434                     // so update the cached list printers which
 435                     // includes both local and network printers
 436                     refreshServices();
 437 
 438                     // store the current data for next comparison
 439                     prevRemotePrinters = currentRemotePrinters;
 440                 }
 441             }
 442         }
 443     }
 444 
 445     private native String getDefaultPrinterName();
 446     private native String[] getAllPrinterNames();
 447     private native long notifyFirstPrinterChange(String printer);
 448     private native void notifyClosePrinterChange(long chgObj);
 449     private native int notifyPrinterChange(long chgObj);
 450     private native String[] getRemotePrintersNames();
 451 }