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