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 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 PrinterChangeListener(); 132 thr.setDaemon(true); 133 thr.start(); 134 135 if (pollServices) { 136 // start the remote printer listener thread 137 Thread remThr = new RemotePrinterChangeListener(); 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.equals("")) { 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 boolean matchingService(PrintService service, 225 PrintServiceAttributeSet serviceSet) { 226 if (serviceSet != null) { 227 Attribute [] attrs = serviceSet.toArray(); 228 Attribute serviceAttr; 229 for (int i=0; i<attrs.length; i++) { 230 serviceAttr 231 = service.getAttribute((Class<PrintServiceAttribute>)attrs[i].getCategory()); 232 if (serviceAttr == null || !serviceAttr.equals(attrs[i])) { 233 return false; 234 } 235 } 236 } 237 return true; 238 } 239 240 public PrintService[] getPrintServices(DocFlavor flavor, 241 AttributeSet attributes) { 242 243 SecurityManager security = System.getSecurityManager(); 244 if (security != null) { 245 security.checkPrintJobAccess(); 246 } 247 PrintRequestAttributeSet requestSet = null; 248 PrintServiceAttributeSet serviceSet = null; 249 250 if (attributes != null && !attributes.isEmpty()) { 251 252 requestSet = new HashPrintRequestAttributeSet(); 253 serviceSet = new HashPrintServiceAttributeSet(); 254 255 Attribute[] attrs = attributes.toArray(); 256 for (int i=0; i<attrs.length; i++) { 257 if (attrs[i] instanceof PrintRequestAttribute) { 258 requestSet.add(attrs[i]); 259 } else if (attrs[i] instanceof PrintServiceAttribute) { 260 serviceSet.add(attrs[i]); 261 } 262 } 263 } 264 265 /* 266 * Special case: If client is asking for a particular printer 267 * (by name) then we can save time by getting just that service 268 * to check against the rest of the specified attributes. 269 */ 270 PrintService[] services = null; 271 if (serviceSet != null && serviceSet.get(PrinterName.class) != null) { 272 PrinterName name = (PrinterName)serviceSet.get(PrinterName.class); 273 PrintService service = getPrintServiceByName(name.getValue()); 274 if (service == null || !matchingService(service, serviceSet)) { 275 services = new PrintService[0]; 276 } else { 277 services = new PrintService[1]; 278 services[0] = service; 279 } 280 } else { 281 services = getPrintServices(); 282 } 283 284 if (services.length == 0) { 285 return services; 286 } else { 287 ArrayList matchingServices = new ArrayList(); 288 for (int i=0; i<services.length; i++) { 289 try { 290 if (services[i]. 291 getUnsupportedAttributes(flavor, requestSet) == null) { 292 matchingServices.add(services[i]); 293 } 294 } catch (IllegalArgumentException e) { 295 } 296 } 297 services = new PrintService[matchingServices.size()]; 298 return (PrintService[])matchingServices.toArray(services); 299 } 300 } 301 302 /* 303 * return empty array as don't support multi docs 304 */ 305 public MultiDocPrintService[] 306 getMultiDocPrintServices(DocFlavor[] flavors, 307 AttributeSet attributes) { 308 SecurityManager security = System.getSecurityManager(); 309 if (security != null) { 310 security.checkPrintJobAccess(); 311 } 312 return new MultiDocPrintService[0]; 313 } 314 315 316 public synchronized PrintService getDefaultPrintService() { 317 SecurityManager security = System.getSecurityManager(); 318 if (security != null) { 319 security.checkPrintJobAccess(); 320 } 321 322 323 // Windows does not have notification for a change in default 324 // so we always get the latest. 325 defaultPrinter = getDefaultPrinterName(); 326 if (defaultPrinter == null) { 327 return null; 328 } 329 330 if ((defaultPrintService != null) && 331 defaultPrintService.getName().equals(defaultPrinter)) { 332 333 return defaultPrintService; 334 } 335 336 // Not the same as default so proceed to get new PrintService. 337 338 // clear defaultPrintService 339 defaultPrintService = null; 340 341 if (printServices != null) { 342 for (int j=0; j<printServices.length; j++) { 343 if (defaultPrinter.equals(printServices[j].getName())) { 344 defaultPrintService = printServices[j]; 345 break; 346 } 347 } 348 } 349 350 if (defaultPrintService == null) { 351 defaultPrintService = new Win32PrintService(defaultPrinter); 352 } 353 return defaultPrintService; 354 } 355 356 class PrinterChangeListener extends Thread { 357 long chgObj; 358 PrinterChangeListener() { 359 chgObj = notifyFirstPrinterChange(null); 360 } 361 362 public void run() { 363 if (chgObj != -1) { 364 while (true) { 365 // wait for configuration to change 366 if (notifyPrinterChange(chgObj) != 0) { 367 try { 368 refreshServices(); 369 } catch (SecurityException se) { 370 break; 371 } 372 } else { 373 notifyClosePrinterChange(chgObj); 374 break; 375 } 376 } 377 } 378 } 379 } 380 381 /* Windows provides *PrinterChangeNotification* functions that provides 382 information about printer status changes of the local printers but not 383 network printers. 384 Alternatively, Windows provides a way through which one can get the 385 network printer status changes by using WMI, RegistryKeyChange combination, 386 which is a slightly complex mechanism. 387 The Windows WMI offers an async and sync method to read through registry 388 via the WQL query. The async method is considered dangerous as it leaves 389 open a channel until we close it. But the async method has the advantage of 390 being notified of a change in registry by calling callback without polling for it. 391 The sync method uses the polling mechanism to notify. 392 RegistryValueChange cannot be used in combination with WMI to get registry 393 value change notification because of an error that may be generated because the 394 scope of the query would be too big to handle(at times). 395 Hence an alternative mechanism is chosen via the EnumPrinters by polling for the 396 count of printer status changes(add\remove) and based on it update the printers 397 list. 398 */ 399 class RemotePrinterChangeListener extends Thread implements Comparator<String>{ 400 private String[] prevRemotePrinters; 401 402 RemotePrinterChangeListener() { 403 prevRemotePrinters = getRemotePrintersNames(); 404 } 405 406 @Override 407 public int compare(String o1, String o2) { 408 return ((o1 == null) 409 ? ((o2 == null) ? 0 : 1) 410 : ((o2 == null) ? -1 : o1.compareTo(o2))); 411 } 412 413 @Override 414 public void run() { 415 // Init the list of remote printers 416 String[] prevRemotePrinters = getRemotePrintersNames(); 417 if (prevRemotePrinters != null) { 418 Arrays.sort(prevRemotePrinters, this); 419 } 420 421 while (true) { 422 try { 423 Thread.sleep(refreshTime * 1000); 424 } catch (InterruptedException e) { 425 break; 426 } 427 428 String[] currentRemotePrinters = getRemotePrintersNames(); 429 if (currentRemotePrinters != null) { 430 Arrays.sort(currentRemotePrinters, this); 431 } 432 if (!Arrays.equals(prevRemotePrinters, currentRemotePrinters)) { 433 // The list of remote printers got updated, 434 // so update the cached list printers which 435 // includes both local and network printers 436 refreshServices(); 437 438 // store the current data for next comparison 439 prevRemotePrinters = currentRemotePrinters; 440 } 441 } 442 } 443 } 444 445 private native String getDefaultPrinterName(); 446 private native String[] getAllPrinterNames(); 447 private native long notifyFirstPrinterChange(String printer); 448 private native void notifyClosePrinterChange(long chgObj); 449 private native int notifyPrinterChange(long chgObj); 450 private native String[] getRemotePrintersNames(); 451 }