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