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