8153732: Windows remote printer changes do not reflect in lookupPrintServices()
Reviewed-by: prr, psadhukhan
--- a/src/java.desktop/windows/classes/sun/print/PrintServiceLookupProvider.java Mon Jun 25 12:50:25 2018 +0530
+++ b/src/java.desktop/windows/classes/sun/print/PrintServiceLookupProvider.java Mon Jun 25 14:32:46 2018 +0530
@@ -53,8 +53,42 @@
private PrintService defaultPrintService;
private String[] printers; /* excludes the default printer */
private PrintService[] printServices; /* includes the default printer */
+ private static boolean pollServices = true;
+ private static final int DEFAULT_MINREFRESH = 240; // 4 minutes
+ private static int minRefreshTime = DEFAULT_MINREFRESH;
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"));
+
+ if (pollStr != null) {
+ if (pollStr.equalsIgnoreCase("false")) {
+ pollServices = false;
+ }
+ }
+
+ /* 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"));
+
+ if (refreshTimeStr != null) {
+ try {
+ minRefreshTime = (Integer.valueOf(refreshTimeStr)).intValue();
+ } catch (NumberFormatException e) {
+ }
+ if (minRefreshTime < DEFAULT_MINREFRESH) {
+ minRefreshTime = DEFAULT_MINREFRESH;
+ }
+ }
+
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction<Void>() {
public Void run() {
@@ -96,11 +130,19 @@
if (osName != null && osName.startsWith("Windows 98")) {
return;
}
- // start the printer listener thread
+ // start the local printer listener thread
Thread thr = new Thread(null, new PrinterChangeListener(),
"PrinterListener", 0, false);
thr.setDaemon(true);
thr.start();
+
+ if (pollServices) {
+ // start the remote printer listener thread
+ Thread remThr = new Thread(null, new RemotePrinterChangeListener(),
+ "RemotePrinterListener", 0, false);
+ remThr.setDaemon(true);
+ remThr.start();
+ }
} /* else condition ought to never happen! */
}
@@ -316,7 +358,6 @@
}
return defaultPrintService;
}
-
class PrinterChangeListener implements Runnable {
long chgObj;
PrinterChangeListener() {
@@ -343,9 +384,74 @@
}
}
+ /* Windows provides *PrinterChangeNotification* functions that provides
+ information about printer status changes of the local printers but not
+ network printers.
+ Alternatively, Windows provides a way thro' 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 thro' 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 choosen 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 Runnable {
+ private String[] prevRemotePrinters;
+
+ RemotePrinterChangeListener() {
+ prevRemotePrinters = getRemotePrintersNames();
+ }
+
+ boolean doCompare(String[] str1, String[] str2) {
+ if (str1.length != str2.length) {
+ return true;
+ } else {
+ for (int i = 0;i < str1.length;i++) {
+ for (int j = 0;j < str2.length;j++) {
+ if (!str1[i].equals(str2[j])) {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public void run() {
+ while (true) {
+ String[] currentRemotePrinters = getRemotePrintersNames();
+ if (doCompare(prevRemotePrinters, currentRemotePrinters)) {
+
+ // updated the printers data
+ // printers list now contains both local and network printer data
+ refreshServices();
+
+ // store the current data for next comparison
+ prevRemotePrinters = currentRemotePrinters;
+ }
+
+ try {
+ Thread.sleep(minRefreshTime * 1000);
+ } catch (InterruptedException e) {
+ break;
+ }
+ }
+ }
+ }
+
private native String getDefaultPrinterName();
private native String[] getAllPrinterNames();
private native long notifyFirstPrinterChange(String printer);
private native void notifyClosePrinterChange(long chgObj);
private native int notifyPrinterChange(long chgObj);
+ private native String[] getRemotePrintersNames();
}
--- a/src/java.desktop/windows/native/libawt/windows/WPrinterJob.cpp Mon Jun 25 12:50:25 2018 +0530
+++ b/src/java.desktop/windows/native/libawt/windows/WPrinterJob.cpp Mon Jun 25 14:32:46 2018 +0530
@@ -232,6 +232,76 @@
}
}
+JNIEXPORT jobjectArray JNICALL
+Java_sun_print_PrintServiceLookupProvider_getRemotePrintersNames(JNIEnv *env,
+ jobject peer)
+{
+ TRY;
+
+ int remotePrintersCount = 0;
+ DWORD cbNeeded = 0;
+ DWORD cReturned = 0;
+ LPBYTE pPrinterEnum = NULL;
+ LPBYTE pNetworkPrinterLoc = NULL;
+
+ jstring utf_str;
+ jclass clazz = env->FindClass("java/lang/String");
+ if (clazz == NULL) {
+ return NULL;
+ }
+ jobjectArray nameArray;
+
+ try {
+ ::EnumPrinters(PRINTER_ENUM_LOCAL | PRINTER_ENUM_CONNECTIONS,
+ NULL, 4, NULL, 0, &cbNeeded, &cReturned);
+ pPrinterEnum = new BYTE[cbNeeded];
+ pNetworkPrinterLoc = new BYTE[cbNeeded/sizeof(PRINTER_INFO_4)];
+ ::EnumPrinters(PRINTER_ENUM_LOCAL | PRINTER_ENUM_CONNECTIONS,
+ NULL, 4, pPrinterEnum, cbNeeded, &cbNeeded,
+ &cReturned);
+
+ if (cReturned > 0) {
+ for (DWORD i = 0; i < cReturned; i++) {
+ PRINTER_INFO_4 *info4 = (PRINTER_INFO_4 *) (pPrinterEnum + i * sizeof(PRINTER_INFO_4));
+
+ // Store the network printers indexes
+ if (info4->Attributes & PRINTER_ATTRIBUTE_NETWORK) {
+ pNetworkPrinterLoc[remotePrintersCount++] = i;
+ }
+ }
+
+ // Allocate space only for the network type printers
+ nameArray = env->NewObjectArray(remotePrintersCount, clazz, NULL);
+ if (nameArray == NULL) {
+ throw std::bad_alloc();
+ }
+ } else {
+ nameArray = NULL;
+ }
+
+ // Loop thro' network printers list only
+ for (int i = 0; i < remotePrintersCount; i++) {
+ PRINTER_INFO_4 *info4 = (PRINTER_INFO_4 *)
+ (pPrinterEnum + pNetworkPrinterLoc[i] * sizeof(PRINTER_INFO_4));
+ utf_str = JNU_NewStringPlatform(env, info4->pPrinterName);
+ if (utf_str == NULL) {
+ throw std::bad_alloc();
+ }
+ env->SetObjectArrayElement(nameArray, i, utf_str);
+ env->DeleteLocalRef(utf_str);
+ }
+ } catch (std::bad_alloc&) {
+ delete [] pPrinterEnum;
+ delete [] pNetworkPrinterLoc;
+ throw;
+ }
+
+ delete [] pPrinterEnum;
+ delete [] pNetworkPrinterLoc;
+ return nameArray;
+
+ CATCH_BAD_ALLOC_RET(NULL);
+}
JNIEXPORT jfloatArray JNICALL
Java_sun_print_Win32PrintService_getMediaPrintableArea(JNIEnv *env,
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/awt/print/RemotePrinterStatusRefresh/RemotePrinterStatusRefresh.java Mon Jun 25 14:32:46 2018 +0530
@@ -0,0 +1,264 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * @test
+ * @bug 8153732
+ * @requires (os.family == "Windows")
+ * @summary Windows remote printer changes do not reflect in lookupPrintServices()
+ * @ignore Requires a new network printer installation\removal
+ * @run main/manual RemotePrinterStatusRefresh
+ */
+
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.event.ActionEvent;
+import java.awt.print.PageFormat;
+import java.awt.print.Paper;
+import java.awt.print.PrinterException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import javax.swing.BorderFactory;
+import javax.swing.Box;
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTextArea;
+import javax.swing.SwingUtilities;
+import java.awt.print.PrinterJob;
+import javax.print.PrintService;
+
+public class RemotePrinterStatusRefresh
+{
+ private static TestUI test = null;
+ public static void main(String args[]) throws Exception {
+ final CountDownLatch latch = new CountDownLatch(1);
+
+ // Test UI creation
+ test = new TestUI(latch);
+
+ SwingUtilities.invokeAndWait(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ test.createUI();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ });
+
+ // RemotePrinterStatusRefresh creation
+ RemotePrinterStatusRefresh RemotePrinterStatusRefresh = new RemotePrinterStatusRefresh();
+ SwingUtilities.invokeAndWait(() -> {
+ collectPrintersList(test.resultsTextArea, true);
+ });
+
+ // 8 min = 480000 msec
+ if(waitForFlag(480000)) {
+ SwingUtilities.invokeAndWait(() -> {
+ collectPrintersList(test.resultsTextArea, false);
+ });
+ } else {
+ dispose();
+ throw new RuntimeException("No new network printer got added/removed!! Test timed out!!");
+ }
+
+ boolean status = latch.await(1, TimeUnit.MINUTES);
+ if (!status) {
+ dispose();
+ throw new RuntimeException("Test timed out.");
+ }
+
+ if (test.testResult == false) {
+ dispose();
+ throw new RuntimeException("Test Failed.");
+ }
+
+ dispose();
+ }
+
+ public static void dispose() throws Exception {
+ SwingUtilities.invokeAndWait(() -> {
+ test.disposeUI();
+ });
+ }
+
+ public static boolean waitForFlag (long maxTimeoutInMsec) throws Exception {
+ while(!test.isAdded && maxTimeoutInMsec > 0) {
+ maxTimeoutInMsec -= 100;
+ Thread.sleep(100);
+ }
+
+ if(maxTimeoutInMsec <= 0) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ private static void collectPrintersList(JTextArea textArea, boolean before) {
+ if(before) {
+ System.out.println("List of printers(before): ");
+ textArea.setText("List of printers(before): \n");
+ for (PrintService printServiceBefore : PrinterJob.lookupPrintServices()) {
+ System.out.println(printServiceBefore);
+ textArea.append(printServiceBefore.toString());
+ textArea.append("\n");
+ }
+ } else {
+ textArea.append("\n");
+ System.out.println("List of printers(after): ");
+ textArea.append("List of printers(after): \n");
+ for (PrintService printServiceAfter : PrinterJob.lookupPrintServices()) {
+ System.out.println(printServiceAfter);
+ textArea.append(printServiceAfter.toString());
+ textArea.append("\n");
+ }
+ }
+ }
+}
+
+class TestUI {
+ private static JFrame mainFrame;
+ private static JPanel mainControlPanel;
+
+ private static JTextArea instructionTextArea;
+
+ private static JPanel resultButtonPanel;
+ private static JButton passButton;
+ private static JButton failButton;
+ private static JButton addedButton;
+
+ private static JPanel testPanel;
+ private static JButton testButton;
+ private static JLabel buttonPressCountLabel;
+
+ private static GridBagLayout layout;
+ private final CountDownLatch latch;
+ public boolean testResult = false;
+ public volatile Boolean isAdded = false;
+ public static JTextArea resultsTextArea;
+
+ public TestUI(CountDownLatch latch) throws Exception {
+ this.latch = latch;
+ }
+
+ public final void createUI() {
+ mainFrame = new JFrame("RemotePrinterStatusRefresh");
+ layout = new GridBagLayout();
+ mainControlPanel = new JPanel(layout);
+ resultButtonPanel = new JPanel(layout);
+ testPanel = new JPanel(layout);
+ GridBagConstraints gbc = new GridBagConstraints();
+
+ // Create Test instructions
+ String instructions
+ = "This test displays the current list of printers(before) attached to \n"
+ + "this computer in the results panel.\n\n"
+ + "Please follow the below steps for this manual test\n"
+ + "--------------------------------------------------------------------\n"
+ + "Step 1: Add/Remove a new network printer and Wait for 4 minutes after adding/removing\n"
+ + "Step 2: Then click on 'Printer Added/Removed' button\n"
+ + "Step 2: Once the new network printer is added/removed, see if it is \n"
+ + " the same as displayed/not displayed in the results panel.\n"
+ + "Step 3: If displayed/not displayed, then click 'Pass' else click on 'Fail' button";
+
+ instructionTextArea = new JTextArea();
+ instructionTextArea.setText(instructions);
+ instructionTextArea.setEditable(false);
+ instructionTextArea.setBorder(BorderFactory.
+ createTitledBorder("Test Instructions"));
+
+ gbc.gridx = 0;
+ gbc.gridy = 0;
+ gbc.fill = GridBagConstraints.HORIZONTAL;
+ mainControlPanel.add(instructionTextArea, gbc);
+
+ gbc.gridx = 0;
+ gbc.gridy = 1;
+ testPanel.add(Box.createVerticalStrut(50));
+ mainControlPanel.add(testPanel);
+
+ addedButton = new JButton("Printer Added/Removed");
+ addedButton.setActionCommand("Added");
+ addedButton.addActionListener((ActionEvent e) -> {
+ System.out.println("Added Button pressed!");
+ isAdded = true;
+ });
+
+ // Create resultButtonPanel with Pass, Fail buttons
+ passButton = new JButton("Pass");
+ passButton.setActionCommand("Pass");
+ passButton.addActionListener((ActionEvent e) -> {
+ System.out.println("Pass Button pressed!");
+ testResult = true;
+ latch.countDown();
+ disposeUI();
+ });
+
+ failButton = new JButton("Fail");
+ failButton.setActionCommand("Fail");
+ failButton.addActionListener((ActionEvent e) -> {
+ System.out.println("Fail Button pressed!");
+ testResult = false;
+ latch.countDown();
+ disposeUI();
+ });
+
+ gbc.gridx = 0;
+ gbc.gridy = 0;
+ resultButtonPanel.add(addedButton, gbc);
+
+ gbc.gridx = 1;
+ gbc.gridy = 0;
+ resultButtonPanel.add(passButton, gbc);
+
+ gbc.gridx = 2;
+ gbc.gridy = 0;
+ resultButtonPanel.add(failButton, gbc);
+
+ resultsTextArea = new JTextArea();
+ resultsTextArea.setEditable(false);
+ resultsTextArea.setBorder(BorderFactory.
+ createTitledBorder("Results"));
+
+ gbc.gridx = 0;
+ gbc.gridy = 1;
+ gbc.fill = GridBagConstraints.HORIZONTAL;
+ mainControlPanel.add(resultsTextArea, gbc);
+
+ gbc.gridx = 0;
+ gbc.gridy = 2;
+ mainControlPanel.add(resultButtonPanel, gbc);
+
+ mainFrame.add(mainControlPanel);
+ mainFrame.pack();
+ mainFrame.setVisible(true);
+ }
+
+ public void disposeUI() {
+ mainFrame.dispose();
+ }
+}