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 Thread(null, new PrinterChangeListener(), 130 "PrinterListener", 0, false); 131 thr.setDaemon(true); 132 thr.start(); 133 134 if (pollServices) { 135 // start the remote printer listener thread 136 Thread remThr = new Thread(null, new RemotePrinterChangeListener(), 137 "RemotePrinterListener", 0, false); 138 remThr.setDaemon(true); 139 remThr.start(); 140 } 141 } /* else condition ought to never happen! */ 142 } 143 144 /* Want the PrintService which is default print service to have 145 * equality of reference with the equivalent in list of print services 146 * This isn't required by the API and there's a risk doing this will 147 * lead people to assume its guaranteed. 148 */ 149 public synchronized PrintService[] getPrintServices() { 150 SecurityManager security = System.getSecurityManager(); 151 if (security != null) { 152 security.checkPrintJobAccess(); 153 } 154 if (printServices == null) { 155 refreshServices(); 156 } 157 return printServices; 158 } 159 160 private synchronized void refreshServices() { 161 printers = getAllPrinterNames(); 162 if (printers == null) { 163 // In Windows it is safe to assume no default if printers == null so we 164 // don't get the default. 165 printServices = new PrintService[0]; 166 return; 167 } 168 169 PrintService[] newServices = new PrintService[printers.length]; 170 PrintService defService = getDefaultPrintService(); 171 for (int p = 0; p < printers.length; p++) { 172 if (defService != null && 173 printers[p].equals(defService.getName())) { 174 newServices[p] = defService; 175 } else { 176 if (printServices == null) { 177 newServices[p] = new Win32PrintService(printers[p]); 178 } else { 179 int j; 180 for (j = 0; j < printServices.length; j++) { 181 if ((printServices[j]!= null) && 182 (printers[p].equals(printServices[j].getName()))) { 183 newServices[p] = printServices[j]; 184 printServices[j] = null; 185 break; 186 } 187 } 188 if (j == printServices.length) { 189 newServices[p] = new Win32PrintService(printers[p]); 190 } 191 } 192 } 193 } 194 195 // Look for deleted services and invalidate these 196 if (printServices != null) { 197 for (int j=0; j < printServices.length; j++) { 198 if ((printServices[j] instanceof Win32PrintService) && 199 (!printServices[j].equals(defaultPrintService))) { 200 ((Win32PrintService)printServices[j]).invalidateService(); 201 } 202 } 203 } 204 printServices = newServices; 205 } 206 207 208 public synchronized PrintService getPrintServiceByName(String name) { 209 210 if (name == null || name.isEmpty()) { 211 return null; 212 } else { 213 /* getPrintServices() is now very fast. */ 214 PrintService[] printServices = getPrintServices(); 215 for (int i=0; i<printServices.length; i++) { 216 if (printServices[i].getName().equals(name)) { 217 return printServices[i]; 218 } 219 } 220 return null; 221 } 222 } 223 224 @SuppressWarnings("unchecked") // Cast to Class<PrintServiceAttribute> 225 boolean matchingService(PrintService service, 226 PrintServiceAttributeSet serviceSet) { 227 if (serviceSet != null) { 228 Attribute [] attrs = serviceSet.toArray(); 229 Attribute serviceAttr; 230 for (int i=0; i<attrs.length; i++) { 231 serviceAttr 232 = service.getAttribute((Class<PrintServiceAttribute>)attrs[i].getCategory()); 233 if (serviceAttr == null || !serviceAttr.equals(attrs[i])) { 234 return false; 235 } 236 } 237 } 238 return true; 239 } 240 241 public PrintService[] getPrintServices(DocFlavor flavor, 242 AttributeSet attributes) { 243 244 SecurityManager security = System.getSecurityManager(); 245 if (security != null) { 246 security.checkPrintJobAccess(); 247 } 248 PrintRequestAttributeSet requestSet = null; 249 PrintServiceAttributeSet serviceSet = null; 250 251 if (attributes != null && !attributes.isEmpty()) { 252 253 requestSet = new HashPrintRequestAttributeSet(); 254 serviceSet = new HashPrintServiceAttributeSet(); 255 256 Attribute[] attrs = attributes.toArray(); 257 for (int i=0; i<attrs.length; i++) { 258 if (attrs[i] instanceof PrintRequestAttribute) { 259 requestSet.add(attrs[i]); 260 } else if (attrs[i] instanceof PrintServiceAttribute) { 261 serviceSet.add(attrs[i]); 262 } 263 } 264 } 265 266 /* 267 * Special case: If client is asking for a particular printer 268 * (by name) then we can save time by getting just that service 269 * to check against the rest of the specified attributes. 270 */ 271 PrintService[] services = null; 272 if (serviceSet != null && serviceSet.get(PrinterName.class) != null) { 273 PrinterName name = (PrinterName)serviceSet.get(PrinterName.class); 274 PrintService service = getPrintServiceByName(name.getValue()); 275 if (service == null || !matchingService(service, serviceSet)) { 276 services = new PrintService[0]; 277 } else { 278 services = new PrintService[1]; 279 services[0] = service; 280 } 281 } else { 282 services = getPrintServices(); 283 } 284 285 if (services.length == 0) { 286 return services; 287 } else { 288 ArrayList<PrintService> matchingServices = new ArrayList<>(); 289 for (int i=0; i<services.length; i++) { 290 try { 291 if (services[i]. 292 getUnsupportedAttributes(flavor, requestSet) == null) { 293 matchingServices.add(services[i]); 294 } 295 } catch (IllegalArgumentException e) { 296 } 297 } 298 services = new PrintService[matchingServices.size()]; 299 return matchingServices.toArray(services); 300 } 301 } 302 303 /* 304 * return empty array as don't support multi docs 305 */ 306 public MultiDocPrintService[] 307 getMultiDocPrintServices(DocFlavor[] flavors, 308 AttributeSet attributes) { 309 SecurityManager security = System.getSecurityManager(); 310 if (security != null) { 311 security.checkPrintJobAccess(); 312 } 313 return new MultiDocPrintService[0]; 314 } 315 316 317 public synchronized PrintService getDefaultPrintService() { 318 SecurityManager security = System.getSecurityManager(); 319 if (security != null) { 320 security.checkPrintJobAccess(); 321 } 322 323 324 // Windows does not have notification for a change in default 325 // so we always get the latest. 326 defaultPrinter = getDefaultPrinterName(); 327 if (defaultPrinter == null) { 328 return null; 329 } 330 331 if ((defaultPrintService != null) && 332 defaultPrintService.getName().equals(defaultPrinter)) { 333 334 return defaultPrintService; 335 } 336 337 // Not the same as default so proceed to get new PrintService. 338 339 // clear defaultPrintService 340 defaultPrintService = null; 341 342 if (printServices != null) { 343 for (int j=0; j<printServices.length; j++) { 344 if (defaultPrinter.equals(printServices[j].getName())) { 345 defaultPrintService = printServices[j]; 346 break; 347 } 348 } 349 } 350 351 if (defaultPrintService == null) { 352 defaultPrintService = new Win32PrintService(defaultPrinter); 353 } 354 return defaultPrintService; 355 } 356 357 class PrinterChangeListener implements Runnable { 358 long chgObj; 359 PrinterChangeListener() { 360 chgObj = notifyFirstPrinterChange(null); 361 } 362 363 @Override 364 public void run() { 365 if (chgObj != -1) { 366 while (true) { 367 // wait for configuration to change 368 if (notifyPrinterChange(chgObj) != 0) { 369 try { 370 refreshServices(); 371 } catch (SecurityException se) { 372 break; 373 } 374 } else { 375 notifyClosePrinterChange(chgObj); 376 break; 377 } 378 } 379 } 380 } 381 } 382 383 /* Windows provides *PrinterChangeNotification* functions that provides 384 information about printer status changes of the local printers but not 385 network printers. 386 Alternatively, Windows provides a way through which one can get the 387 network printer status changes by using WMI, RegistryKeyChange combination, 388 which is a slightly complex mechanism. 389 The Windows WMI offers an async and sync method to read through registry 390 via the WQL query. The async method is considered dangerous as it leaves 391 open a channel until we close it. But the async method has the advantage of 392 being notified of a change in registry by calling callback without polling for it. 393 The sync method uses the polling mechanism to notify. 394 RegistryValueChange cannot be used in combination with WMI to get registry 395 value change notification because of an error that may be generated because the 396 scope of the query would be too big to handle(at times). 397 Hence an alternative mechanism is chosen via the EnumPrinters by polling for the 398 count of printer status changes(add\remove) and based on it update the printers 399 list. 400 */ 401 class RemotePrinterChangeListener implements Runnable { 402 private String[] prevRemotePrinters; 403 404 RemotePrinterChangeListener() { 405 } 406 407 private boolean doCompare(String[] str1, String[] str2) { 408 if (str1 == null && str2 == null) { 409 return false; 410 } else if (str1 == null || str2 == null) { 411 return true; 412 } 413 414 if (str1.length != str2.length) { 415 return true; 416 } else { 417 for (int i = 0; i < str1.length; i++) { 418 for (int j = 0; j < str2.length; j++) { 419 // skip if both are nulls 420 if (str1[i] == null && str2[j] == null) { 421 continue; 422 } 423 424 // return true if there is a 'difference' but 425 // no need to access the individual string 426 if (str1[i] == null || str2[j] == null) { 427 return true; 428 } 429 430 // do comparison only if they are non-nulls 431 if (!str1[i].equals(str2[j])) { 432 return true; 433 } 434 } 435 } 436 } 437 438 return false; 439 } 440 441 @Override 442 public void run() { 443 // Init the list of remote printers 444 prevRemotePrinters = getRemotePrintersNames(); 445 446 while (true) { 447 try { 448 Thread.sleep(minRefreshTime * 1000); 449 } catch (InterruptedException e) { 450 break; 451 } 452 453 String[] currentRemotePrinters = getRemotePrintersNames(); 454 if (doCompare(prevRemotePrinters, currentRemotePrinters)) { 455 // The list of remote printers got updated, 456 // so update the cached list printers which 457 // includes both local and network printers 458 refreshServices(); 459 460 // store the current data for next comparison 461 prevRemotePrinters = currentRemotePrinters; 462 } 463 } 464 } 465 } 466 467 private native String getDefaultPrinterName(); 468 private native String[] getAllPrinterNames(); 469 private native long notifyFirstPrinterChange(String printer); 470 private native void notifyClosePrinterChange(long chgObj); 471 private native int notifyPrinterChange(long chgObj); 472 private native String[] getRemotePrintersNames(); 473 }