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