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