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