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