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