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