8263311: Watch registry changes for remote printers update instead of polling

Reviewed-by: psadhukhan, serb
This commit is contained in:
Alexey Ivanov 2021-03-18 13:26:22 +00:00
parent 3f31a6baa9
commit a85dc557b3
3 changed files with 49 additions and 176 deletions

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2000, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -26,8 +26,6 @@
package sun.print; package sun.print;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import javax.print.DocFlavor; import javax.print.DocFlavor;
import javax.print.MultiDocPrintService; import javax.print.MultiDocPrintService;
@ -50,31 +48,7 @@ public class PrintServiceLookupProvider extends PrintServiceLookup {
private String[] printers; /* excludes the default printer */ private String[] printers; /* excludes the default printer */
private PrintService[] printServices; /* includes the default printer */ private PrintService[] printServices; /* includes the default printer */
private static final int DEFAULT_REFRESH_TIME = 240; // 4 minutes
private static final int MINIMUM_REFRESH_TIME = 120; // 2 minutes
private static final boolean pollServices;
private static final int refreshTime;
static { static {
/* The system property "sun.java2d.print.polling"
* can be used to force the printing code to poll or not poll
* for PrintServices.
*/
String pollStr = java.security.AccessController.doPrivileged(
new sun.security.action.GetPropertyAction("sun.java2d.print.polling"));
pollServices = !("false".equalsIgnoreCase(pollStr));
/* The system property "sun.java2d.print.minRefreshTime"
* can be used to specify minimum refresh time (in seconds)
* for polling PrintServices. The default is 240.
*/
String refreshTimeStr = java.security.AccessController.doPrivileged(
new sun.security.action.GetPropertyAction(
"sun.java2d.print.minRefreshTime"));
refreshTime = (refreshTimeStr != null)
? getRefreshTime(refreshTimeStr)
: DEFAULT_REFRESH_TIME;
java.security.AccessController.doPrivileged( java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction<Void>() { new java.security.PrivilegedAction<Void>() {
public Void run() { public Void run() {
@ -84,17 +58,6 @@ public class PrintServiceLookupProvider extends PrintServiceLookup {
}); });
} }
private static int getRefreshTime(final String refreshTimeStr) {
try {
int minRefreshTime = Integer.parseInt(refreshTimeStr);
return (minRefreshTime < MINIMUM_REFRESH_TIME)
? MINIMUM_REFRESH_TIME
: minRefreshTime;
} catch (NumberFormatException e) {
return DEFAULT_REFRESH_TIME;
}
}
/* The singleton win32 print lookup service. /* The singleton win32 print lookup service.
* Code that is aware of this field and wants to use it must first * Code that is aware of this field and wants to use it must first
* see if its null, and if so instantiate it by calling a method such as * see if its null, and if so instantiate it by calling a method such as
@ -126,13 +89,11 @@ public class PrintServiceLookupProvider extends PrintServiceLookup {
thr.setDaemon(true); thr.setDaemon(true);
thr.start(); thr.start();
if (pollServices) {
// start the remote printer listener thread // start the remote printer listener thread
Thread remThr = new Thread(null, new RemotePrinterChangeListener(), Thread remThr = new Thread(null, new RemotePrinterChangeListener(),
"RemotePrinterListener", 0, false); "RemotePrinterListener", 0, false);
remThr.setDaemon(true); remThr.setDaemon(true);
remThr.start(); remThr.start();
}
} /* else condition ought to never happen! */ } /* else condition ought to never happen! */
} }
@ -356,70 +317,15 @@ public class PrintServiceLookupProvider extends PrintServiceLookup {
} }
} }
/* Windows provides *PrinterChangeNotification* functions that provides private final class RemotePrinterChangeListener implements Runnable {
information about printer status changes of the local printers but not
network printers.
Alternatively, Windows provides a way through which one can get the
network printer status changes by using WMI, RegistryKeyChange combination,
which is a slightly complex mechanism.
The Windows WMI offers an async and sync method to read through registry
via the WQL query. The async method is considered dangerous as it leaves
open a channel until we close it. But the async method has the advantage of
being notified of a change in registry by calling callback without polling for it.
The sync method uses the polling mechanism to notify.
RegistryValueChange cannot be used in combination with WMI to get registry
value change notification because of an error that may be generated because the
scope of the query would be too big to handle(at times).
Hence an alternative mechanism is chosen via the EnumPrinters by polling for the
count of printer status changes(add\remove) and based on it update the printers
list.
*/
class RemotePrinterChangeListener implements Comparator<String>, Runnable {
RemotePrinterChangeListener() {
}
@Override
public int compare(String o1, String o2) {
return ((o1 == null)
? ((o2 == null) ? 0 : 1)
: ((o2 == null) ? -1 : o1.compareTo(o2)));
}
@Override @Override
public void run() { public void run() {
// Init the list of remote printers notifyRemotePrinterChange(); // busy loop in the native code
String[] prevRemotePrinters = getRemotePrintersNames();
if (prevRemotePrinters != null) {
Arrays.sort(prevRemotePrinters, this);
}
while (true) {
try {
Thread.sleep(refreshTime * 1000);
} catch (InterruptedException e) {
break;
}
String[] currentRemotePrinters = getRemotePrintersNames();
if (currentRemotePrinters != null) {
Arrays.sort(currentRemotePrinters, this);
}
if (!Arrays.equals(prevRemotePrinters, currentRemotePrinters)) {
// The list of remote printers got updated,
// so update the cached list printers which
// includes both local and network printers
refreshServices();
// store the current data for next comparison
prevRemotePrinters = currentRemotePrinters;
}
}
} }
} }
private native String getDefaultPrinterName(); private native String getDefaultPrinterName();
private native String[] getAllPrinterNames(); private native String[] getAllPrinterNames();
private native void notifyLocalPrinterChange(); private native void notifyLocalPrinterChange();
private native String[] getRemotePrintersNames(); private native void notifyRemotePrinterChange();
} }

View file

@ -191,12 +191,6 @@ Java_sun_print_PrintServiceLookupProvider_getAllPrinterNames(JNIEnv *env,
return getPrinterNames(env, PRINTER_ENUM_LOCAL | PRINTER_ENUM_CONNECTIONS); return getPrinterNames(env, PRINTER_ENUM_LOCAL | PRINTER_ENUM_CONNECTIONS);
} }
JNIEXPORT jobjectArray JNICALL
Java_sun_print_PrintServiceLookupProvider_getRemotePrintersNames(JNIEnv *env,
jobject peer)
{
return getPrinterNames(env, PRINTER_ENUM_CONNECTIONS);
}
JNIEXPORT void JNICALL JNIEXPORT void JNICALL
Java_sun_print_PrintServiceLookupProvider_notifyLocalPrinterChange(JNIEnv *env, Java_sun_print_PrintServiceLookupProvider_notifyLocalPrinterChange(JNIEnv *env,
@ -239,6 +233,37 @@ Java_sun_print_PrintServiceLookupProvider_notifyLocalPrinterChange(JNIEnv *env,
::ClosePrinter(hPrinter); ::ClosePrinter(hPrinter);
} }
JNIEXPORT void JNICALL
Java_sun_print_PrintServiceLookupProvider_notifyRemotePrinterChange(JNIEnv *env,
jobject peer)
{
jclass cls = env->GetObjectClass(peer);
CHECK_NULL(cls);
jmethodID refresh = env->GetMethodID(cls, "refreshServices", "()V");
CHECK_NULL(refresh);
HKEY hKey;
if (ERROR_SUCCESS != RegOpenKeyEx(HKEY_CURRENT_USER,
_T("Printers\\Connections"),
0, KEY_NOTIFY, &hKey)) {
return;
}
BOOL keepMonitoring;
do {
keepMonitoring =
ERROR_SUCCESS == RegNotifyChangeKeyValue(hKey, TRUE,
REG_NOTIFY_CHANGE_NAME,
NULL,
FALSE);
if (keepMonitoring) {
env->CallVoidMethod(peer, refresh);
}
} while (keepMonitoring && !env->ExceptionCheck());
RegCloseKey(hKey);
}
JNIEXPORT jfloatArray JNICALL JNIEXPORT jfloatArray JNICALL
Java_sun_print_Win32PrintService_getMediaPrintableArea(JNIEnv *env, Java_sun_print_Win32PrintService_getMediaPrintableArea(JNIEnv *env,

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2018, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -23,10 +23,10 @@
/* /*
* @test * @test
* @bug 8153732 8212202 8221263 8221412 8222108 * @bug 8153732 8212202 8221263 8221412 8222108 8263311
* @requires (os.family == "Windows") * @requires (os.family == "Windows")
* @summary Windows remote printer changes do not reflect in lookupPrintServices() * @summary Windows remote printer changes do not reflect in lookupPrintServices()
* @run main/manual/othervm -Dsun.java2d.print.minRefreshTime=120 RemotePrinterStatusRefresh * @run main/manual RemotePrinterStatusRefresh
*/ */
import java.awt.BorderLayout; import java.awt.BorderLayout;
@ -63,12 +63,7 @@ import static javax.swing.BorderFactory.createTitledBorder;
public class RemotePrinterStatusRefresh extends WindowAdapter { public class RemotePrinterStatusRefresh extends WindowAdapter {
private static final long DEFAULT_REFRESH_TIME = 240L; private static final long TIMEOUT = 15L * 60;
private static final long MINIMAL_REFRESH_TIME = 120L;
private static final long refreshTime = getRefreshTime();
private static final long TIMEOUT = refreshTime * 4 + 60;
private static final CountDownLatch latch = new CountDownLatch(1); private static final CountDownLatch latch = new CountDownLatch(1);
@ -86,7 +81,6 @@ public class RemotePrinterStatusRefresh extends WindowAdapter {
private final ServiceItemListModel beforeList; private final ServiceItemListModel beforeList;
private final ServiceItemListModel afterList; private final ServiceItemListModel afterList;
private JTextField nextRefresh;
private JTextField timeLeft; private JTextField timeLeft;
private final Timer timer; private final Timer timer;
@ -184,22 +178,18 @@ public class RemotePrinterStatusRefresh extends WindowAdapter {
+ "configured printers.\n" + "configured printers.\n"
+ "Step 1: Add or Remove a network printer using " + "Step 1: Add or Remove a network printer using "
+ "Windows Control Panel.\n" + "Windows Control Panel.\n"
+ "Step 2: Wait for 2\u20134 minutes after adding or removing.\n" + "Step 2: Click Refresh."
+ " \"Next printer refresh in\" gives you a "
+ "rough estimation on when update will happen.\n"
+ "Step 3: Click Refresh."
+ "\"After\" list is populated with updated list " + "\"After\" list is populated with updated list "
+ "of printers.\n" + "of printers.\n"
+ "Step 4: Compare the list of printers in \"Before\" and " + "Step 3: Compare the list of printers in \"Before\" and "
+ "\"After\" lists.\n" + "\"After\" lists.\n"
+ " Added printers are highlighted with " + " Added printers are highlighted with "
+ "green color, removed ones \u2014 with " + "green color, removed ones \u2014 with "
+ "red color.\n" + "red color.\n"
+ "Step 5: Click Pass if the list of printers is correctly " + "Step 4: Click Pass if the list of printers is correctly "
+ "updated.\n" + "updated.\n"
+ "Step 6: If the list is not updated, wait for another " + "Step 5: If the list is not updated, click Refresh again.\n"
+ "2\u20134 minutes, and then click Refresh again.\n" + "Step 6: If the list does not update, click Fail.\n"
+ "Step 7: If the list does not update, click Fail.\n"
+ "\n" + "\n"
+ "You have to click Refresh to enable Pass and Fail buttons. " + "You have to click Refresh to enable Pass and Fail buttons. "
+ "If no button is pressed,\n" + "If no button is pressed,\n"
@ -216,18 +206,6 @@ public class RemotePrinterStatusRefresh extends WindowAdapter {
} }
} }
private static long getRefreshTime() {
String refreshTime =
System.getProperty("sun.java2d.print.minRefreshTime",
Long.toString(DEFAULT_REFRESH_TIME));
try {
long value = Long.parseLong(refreshTime);
return value < MINIMAL_REFRESH_TIME ? MINIMAL_REFRESH_TIME : value;
} catch (NumberFormatException e) {
return DEFAULT_REFRESH_TIME;
}
}
private static void createUI() { private static void createUI() {
test = new RemotePrinterStatusRefresh(); test = new RemotePrinterStatusRefresh();
} }
@ -278,31 +256,6 @@ public class RemotePrinterStatusRefresh extends WindowAdapter {
javaVersion.setEditable(false); javaVersion.setEditable(false);
javaLabel.setLabelFor(javaVersion); javaLabel.setLabelFor(javaVersion);
JLabel refreshTimeLabel = new JLabel("Refresh interval:");
long minutes = refreshTime / 60;
long seconds = refreshTime % 60;
String interval = String.format("%1$d seconds%2$s",
refreshTime,
minutes > 0
? String.format(" (%1$d %2$s%3$s)",
minutes,
minutes > 1 ? "minutes" : "minute",
seconds > 0
? String.format(" %1$d %2$s",
seconds,
seconds > 1 ? "seconds" : "second")
: "")
: ""
);
JTextField refreshInterval = new JTextField(interval);
refreshInterval.setEditable(false);
refreshTimeLabel.setLabelFor(refreshInterval);
JLabel nextRefreshLabel = new JLabel("Next printer refresh in:");
nextRefresh = new JTextField();
nextRefresh.setEditable(false);
nextRefreshLabel.setLabelFor(nextRefresh);
JLabel timeoutLabel = new JLabel("Time left:"); JLabel timeoutLabel = new JLabel("Time left:");
timeLeft = new JTextField(); timeLeft = new JTextField();
timeLeft.setEditable(false); timeLeft.setEditable(false);
@ -317,14 +270,10 @@ public class RemotePrinterStatusRefresh extends WindowAdapter {
layout.createSequentialGroup() layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING) .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)
.addComponent(javaLabel) .addComponent(javaLabel)
.addComponent(refreshTimeLabel)
.addComponent(nextRefreshLabel)
.addComponent(timeoutLabel) .addComponent(timeoutLabel)
) )
.addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING, true) .addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING, true)
.addComponent(javaVersion) .addComponent(javaVersion)
.addComponent(refreshInterval)
.addComponent(nextRefresh)
.addComponent(timeLeft) .addComponent(timeLeft)
) )
); );
@ -334,12 +283,6 @@ public class RemotePrinterStatusRefresh extends WindowAdapter {
.addComponent(javaLabel) .addComponent(javaLabel)
.addComponent(javaVersion) .addComponent(javaVersion)
) )
.addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE)
.addComponent(refreshTimeLabel)
.addComponent(refreshInterval))
.addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE)
.addComponent(nextRefreshLabel)
.addComponent(nextRefresh))
.addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE) .addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE)
.addComponent(timeoutLabel) .addComponent(timeoutLabel)
.addComponent(timeLeft)) .addComponent(timeLeft))
@ -493,7 +436,6 @@ public class RemotePrinterStatusRefresh extends WindowAdapter {
disposeUI(); disposeUI();
} }
timeLeft.setText(formatTime(left)); timeLeft.setText(formatTime(left));
nextRefresh.setText(formatTime(refreshTime - (elapsed % refreshTime)));
} }
private static String formatTime(final long seconds) { private static String formatTime(final long seconds) {