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