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 printer listener thread
 100             Thread thr = new Thread(null, new PrinterChangeListener(),
 101                                     "PrinterListener", 0, false);
 102             thr.setDaemon(true);
 103             thr.start();
 104         } /* else condition ought to never happen! */
 105     }
 106 
 107     /* Want the PrintService which is default print service to have
 108      * equality of reference with the equivalent in list of print services
 109      * This isn't required by the API and there's a risk doing this will
 110      * lead people to assume its guaranteed.
 111      */
 112     public synchronized PrintService[] getPrintServices() {
 113         SecurityManager security = System.getSecurityManager();
 114         if (security != null) {
 115             security.checkPrintJobAccess();
 116         }
 117         if (printServices == null) {
 118             refreshServices();
 119         }
 120         return printServices;
 121     }
 122 
 123     private synchronized void refreshServices() {
 124         printers = getAllPrinterNames();
 125         if (printers == null) {
 126             // In Windows it is safe to assume no default if printers == null so we
 127             // don't get the default.
 128             printServices = new PrintService[0];
 129             return;
 130         }
 131 
 132         PrintService[] newServices = new PrintService[printers.length];
 133         PrintService defService = getDefaultPrintService();
 134         for (int p = 0; p < printers.length; p++) {
 135             if (defService != null &&
 136                 printers[p].equals(defService.getName())) {
 137                 newServices[p] = defService;
 138             } else {
 139                 if (printServices == null) {
 140                     newServices[p] = new Win32PrintService(printers[p]);
 141                 } else {
 142                     int j;
 143                     for (j = 0; j < printServices.length; j++) {
 144                         if ((printServices[j]!= null) &&
 145                             (printers[p].equals(printServices[j].getName()))) {
 146                             newServices[p] = printServices[j];
 147                             printServices[j] = null;
 148                             break;
 149                         }
 150                     }
 151                     if (j == printServices.length) {
 152                         newServices[p] = new Win32PrintService(printers[p]);
 153                     }
 154                 }
 155             }
 156         }
 157 
 158         // Look for deleted services and invalidate these
 159         if (printServices != null) {
 160             for (int j=0; j < printServices.length; j++) {
 161                 if ((printServices[j] instanceof Win32PrintService) &&
 162                     (!printServices[j].equals(defaultPrintService))) {
 163                     ((Win32PrintService)printServices[j]).invalidateService();
 164                 }
 165             }
 166         }
 167         printServices = newServices;
 168     }
 169 
 170 
 171     public synchronized PrintService getPrintServiceByName(String name) {
 172 
 173         if (name == null || name.equals("")) {
 174             return null;
 175         } else {
 176             /* getPrintServices() is now very fast. */
 177             PrintService[] printServices = getPrintServices();
 178             for (int i=0; i<printServices.length; i++) {
 179                 if (printServices[i].getName().equals(name)) {
 180                     return printServices[i];
 181                 }
 182             }
 183             return null;
 184         }
 185     }
 186 
 187     @SuppressWarnings("unchecked") // Cast to Class<PrintServiceAttribute>
 188     boolean matchingService(PrintService service,
 189                             PrintServiceAttributeSet serviceSet) {
 190         if (serviceSet != null) {
 191             Attribute [] attrs =  serviceSet.toArray();
 192             Attribute serviceAttr;
 193             for (int i=0; i<attrs.length; i++) {
 194                 serviceAttr
 195                     = service.getAttribute((Class<PrintServiceAttribute>)attrs[i].getCategory());
 196                 if (serviceAttr == null || !serviceAttr.equals(attrs[i])) {
 197                     return false;
 198                 }
 199             }
 200         }
 201         return true;
 202     }
 203 
 204     public PrintService[] getPrintServices(DocFlavor flavor,
 205                                            AttributeSet attributes) {
 206 
 207         SecurityManager security = System.getSecurityManager();
 208         if (security != null) {
 209           security.checkPrintJobAccess();
 210         }
 211         PrintRequestAttributeSet requestSet = null;
 212         PrintServiceAttributeSet serviceSet = null;
 213 
 214         if (attributes != null && !attributes.isEmpty()) {
 215 
 216             requestSet = new HashPrintRequestAttributeSet();
 217             serviceSet = new HashPrintServiceAttributeSet();
 218 
 219             Attribute[] attrs = attributes.toArray();
 220             for (int i=0; i<attrs.length; i++) {
 221                 if (attrs[i] instanceof PrintRequestAttribute) {
 222                     requestSet.add(attrs[i]);
 223                 } else if (attrs[i] instanceof PrintServiceAttribute) {
 224                     serviceSet.add(attrs[i]);
 225                 }
 226             }
 227         }
 228 
 229         /*
 230          * Special case: If client is asking for a particular printer
 231          * (by name) then we can save time by getting just that service
 232          * to check against the rest of the specified attributes.
 233          */
 234         PrintService[] services = null;
 235         if (serviceSet != null && serviceSet.get(PrinterName.class) != null) {
 236             PrinterName name = (PrinterName)serviceSet.get(PrinterName.class);
 237             PrintService service = getPrintServiceByName(name.getValue());
 238             if (service == null || !matchingService(service, serviceSet)) {
 239                 services = new PrintService[0];
 240             } else {
 241                 services = new PrintService[1];
 242                 services[0] = service;
 243             }
 244         } else {
 245             services = getPrintServices();
 246         }
 247 
 248         if (services.length == 0) {
 249             return services;
 250         } else {
 251             ArrayList<PrintService> matchingServices = new ArrayList<>();
 252             for (int i=0; i<services.length; i++) {
 253                 try {
 254                     if (services[i].
 255                         getUnsupportedAttributes(flavor, requestSet) == null) {
 256                         matchingServices.add(services[i]);
 257                     }
 258                 } catch (IllegalArgumentException e) {
 259                 }
 260             }
 261             services = new PrintService[matchingServices.size()];
 262             return matchingServices.toArray(services);
 263         }
 264     }
 265 
 266     /*
 267      * return empty array as don't support multi docs
 268      */
 269     public MultiDocPrintService[]
 270         getMultiDocPrintServices(DocFlavor[] flavors,
 271                                  AttributeSet attributes) {
 272         SecurityManager security = System.getSecurityManager();
 273         if (security != null) {
 274           security.checkPrintJobAccess();
 275         }
 276         return new MultiDocPrintService[0];
 277     }
 278 
 279 
 280     public synchronized PrintService getDefaultPrintService() {
 281         SecurityManager security = System.getSecurityManager();
 282         if (security != null) {
 283           security.checkPrintJobAccess();
 284         }
 285 
 286 
 287         // Windows does not have notification for a change in default
 288         // so we always get the latest.
 289         defaultPrinter = getDefaultPrinterName();
 290         if (defaultPrinter == null) {
 291             return null;
 292         }
 293 
 294         if ((defaultPrintService != null) &&
 295             defaultPrintService.getName().equals(defaultPrinter)) {
 296 
 297             return defaultPrintService;
 298         }
 299 
 300          // Not the same as default so proceed to get new PrintService.
 301 
 302         // clear defaultPrintService
 303         defaultPrintService = null;
 304 
 305         if (printServices != null) {
 306             for (int j=0; j<printServices.length; j++) {
 307                 if (defaultPrinter.equals(printServices[j].getName())) {
 308                     defaultPrintService = printServices[j];
 309                     break;
 310                 }
 311             }
 312         }
 313 
 314         if (defaultPrintService == null) {
 315             defaultPrintService = new Win32PrintService(defaultPrinter);
 316         }
 317         return defaultPrintService;
 318     }
 319 
 320     /* Windows provides *PrinterChangeNotification* functions that provides 
 321        information about printer status changes of the local printers but not
 322        network printers.
 323        Alternatively, Windows provides a way thro' which one can get the
 324        network printer status changes by using WMI, RegistryKeyChange combination,
 325        which is a slightly complex mechanism. 
 326        The Windows WMI offers an async and sync method to read thro' registry 
 327        via the WQL query. The async method is considered dangerous as it leaves 
 328        open a channel until we close it. But the async method has the advantage of 
 329        being notified of a change in registry by calling callback without polling for it.
 330        The sync method uses the polling mechanism to notify.
 331        RegistryValueChange cannot be used in combination with WMI to get registry
 332        value change notification because of an error that may be generated because the
 333        scope of the query would be too big to handle(at times).
 334        Hence an alternative mechanism is choosen via the EnumPrinters by polling for the
 335        count of printer status changes(add\remove) and based on it update the printers
 336        list.
 337     */
 338     class PrinterChangeListener implements Runnable {
 339         long prevRemotePrintersCount = 0;
 340 
 341         PrinterChangeListener() {
 342             prevRemotePrintersCount = GetRemotePrintersCount();
 343         }
 344 
 345         @Override
 346         public void run() {
 347             while(true) {
 348                 long currentRemotePrintersCount = GetRemotePrintersCount();
 349                 if(prevRemotePrintersCount != currentRemotePrintersCount) {
 350 
 351                     // updated the printers data
 352                     // printers list now contains both local and network printer data
 353                     refreshServices();
 354 
 355                     // store the current data for next comparison
 356                     prevRemotePrintersCount = currentRemotePrintersCount;
 357                 }
 358             }
 359         }
 360     }
 361 
 362     private native String getDefaultPrinterName();
 363     private native String[] getAllPrinterNames();
 364     private native long GetRemotePrintersCount();
 365 }