When I typed in my question, similar questions came up, like this one [ How can I communicate Servlet from Swing ], but I looked at it, and didn't find the answer I'm looking for, so here is my question.
I'm developing a web app for internal call center, it uses a Java application [ CCT : call center technology ] to call asterisk server to make calls, this CCT has lots of servlets to generate html web pages so caller agents can access web pages and make calls. In order to better develop and debug, I added a Swing GUI panel to it, so when it runs on my development PC, it will open a window with lots of buttons, each represents a caller agent with their setting info, so I can click and look at the details. It runs fine and I've copies the code to a test server, when it runs there I can remote into it and see the Swing panel with JButtons representing each caller agent.
But the problem is, when this Java app runs on production server, it will be run as a Windows service [ so I was told by my boss ], and there will be no display, so the Swing panel won't be seen, therefore I thought about Java web Start, with it I can have a web page with a "WebStartLaunchButton" so when I click that button it will let me download a Jar file which will start a Swing App. But then I realize the Jar file will be downloaded onto my local PC, and run from the PC, it will have no knowledge of the servlet setting/info, it is independent, it has nothing to do with the call center web app sevlet, and won't be able to show caller agents info. Am I right, is there a way to have a Swing app run on the server and see [ have access to ] it from my PC ?
Here are more details, from the servlet there is a class like this :
public class DialerService extends Service implements Runnable
{
private boolean running = false;
private Thread thread;
private CallManager cm = null;
private AgentDialListManager adlm = null; // <-- My Swing program
private DataAccessManager dam = null;
...
public void run()
{
adlm = new AgentDialListManager(); // <-- My Swing program
...
}
...
private CallData getNextDial(SessionManager sm, SessionData session)
{
CallData nextDial = null;
AgentData agent = session.getAgentData();
if (agent != null)
{
SystemSettingsData settings = dam.getSystemSettings();
if (settings.isNextDialBufferOn())
{
nextDial = adlm.getNextNumber(dam,session); // <-- My Swing program : Get a number from self-managed agent dial list
}
...
}
}
...
}
Here is my main Swing program [ other related classes not shown ] :
package com.amerisave.cct.call;
import com.amerisave.cct.data.DataAccessManager;
import com.amerisave.cct.session.AgentData;
import com.amerisave.cct.session.SessionData;
import com.orderlysoftware.orderlycalls.OrderlyCalls;
...
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JTextArea;
import javax.swing.SwingConstants;
...
public class AgentDialListManager extends JPanel implements Runnable
{
public static final long serialVersionUID = 26362862L;
static Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
static int W = 1136, H = 800, updateInterval = 1000;
HashMap<Integer,AgentDialListRunner> agentDialListMap = null;
HashMap<Integer,JFrame> Agent_Panel_Frame_Map = null;
AgentData agent;
Insets anInset = new Insets(0,0,0,0);
AgentDialListRunner adlr;
JPanel agentPanel = new JPanel(),Log_Panel=new JPanel(new FlowLayout(1,0,0));
...
static JTabbedPane Tabbed_Pane;
static JTextArea Log_TextArea = new JTextArea(Get_System_Properties());
String hostAddress,hostName,dialerServerInfo="";
List dialerServerList = null;
Thread agentDialListManagerThread;
AgentDialListManager()
{
if (!agentDialListManager_Exists_B)
{
agentDialListMap = new HashMap();
Agent_Panel_Frame_Map = new HashMap();
shutdown = false;
if (showGUI_B) Init();
start();
agentDialListManager_Exists_B = true;
}
}
void Init()
{
setLayout(new FlowLayout(1,0,1));
Tabbed_Pane=new JTabbedPane();
Tabbed_Pane.setDoubleBuffered(true);
Tabbed_Pane.setPreferredSize(new Dimension(W - 20,H - 42));
Tabbed_Pane.setAutoscrolls(true);
Tabbed_Pane.setFont(new Font("Times New Roman",0,16));
Tabbed_Pane.setForeground(new Color(0,0,230));
add(Tabbed_Pane);
agentPanel.setBorder(new TitledBorder(new EtchedBorder(EtchedBorder.RAISED,Color.white,new Color(134,134,134))," Agents",TitledBorder.CENTER,TitledBorder.TOP,new Font("Times New Roman",0,15),new Color(0,60,198)));
agentPanel.setPreferredSize(new Dimension(W - 20,H - 42));
Tabbed_Pane.addTab("Agents",null,agentPanel,"");
JPanel bottomPanel = new JPanel(new FlowLayout(1,0,0));
JPanel skipRatioPanel = new JPanel(new FlowLayout(1,0,0));
skipRatioPanel.setBorder(new EtchedBorder(EtchedBorder.RAISED,new Color(0,188,250),new Color(134,134,134)));
skipRatioPanel.setPreferredSize(new Dimension(330,30));
bottomPanel.add(skipRatioPanel);
...
Tabbed_Pane.addTab("Log",null,Log_Panel,"");
setPreferredSize(new Dimension(W,H));
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
createAndShowGUI();
}
});
}
CallData getNextNumber(DataAccessManager dam,SessionData session)
{
CallData nextDial = null;
if (agentDialListMap != null)
{
agent = session.getAgentData();
int nEmployeeId = agent.nEmployeeId;
if (!agentDialListMap.containsKey(nEmployeeId))
{
adlr = new AgentDialListRunner(nEmployeeId,dam);
agentDialListMap.put(nEmployeeId,adlr);
agentCount = agentDialListMap.size();
agentPanel.setBorder(new TitledBorder(new EtchedBorder(EtchedBorder.RAISED,Color.white,new Color(134,134,134)),agentCount + " Agent" + (agentCount > 1 ? "s" : ""),TitledBorder.CENTER,TitledBorder.TOP,new Font("Times New Roman",0,15),new Color(0,60,198)));
addAgent(adlr.id,adlr,agent);
}
adlr = agentDialListMap.get(nEmployeeId);
nextDial = adlr.getNextNumber();
}
return nextDial;
}
void createAndShowGUI()
{
JFrame frame = new JFrame("AgentDialListManager [ Java = " + System.getProperty("java.version") + " | jetty.home = "+System.getProperty("jetty.home")+" ] dialerServerInfo = " + dialerServerInfo+" [ hostAddress = " + hostAddress+" hostName = " + hostName+" ]");
frame.add(this);
frame.addWindowListener(new WindowAdapter()
{
public void windowActivated(WindowEvent e) { }
public void windowClosed(WindowEvent e) { }
public void windowClosing(WindowEvent e) { if (runFromMain_B) System.exit(0); }
public void windowDeactivated(WindowEvent e) { }
public void windowDeiconified(WindowEvent e) { repaint(); }
public void windowGainedFocus(WindowEvent e) { repaint(); }
public void windowIconified(WindowEvent e) { }
public void windowLostFocus(WindowEvent e) { }
public void windowOpening(WindowEvent e) { repaint(); }
public void windowOpened(WindowEvent e) { }
public void windowResized(WindowEvent e) { repaint(); }
public void windowStateChanged(WindowEvent e) { repaint(); }
});
frame.pack();
if (runFromMain_B) frame.setLocationRelativeTo(null);
else frame.setBounds(displayCount==3?-1728:0,displayCount==3?0:26,this.getWidth(),this.getHeight() + 43);
frame.setVisible(true);
}
public void run()
{
try
{
while (!shutdown)
{
Thread.sleep(sleepTime);
AgentDialListRunner.averageTimeToMoreNumbers = 0;
for (Map.Entry<Integer,AgentDialListRunner> mapElement : agentDialListMap.entrySet())
{
int nEmployeeId = (int)mapElement.getKey();
AgentDialListRunner adlr = (AgentDialListRunner)mapElement.getValue();
AgentDialListRunner.averageTimeToMoreNumbers += adlr.averageTimeInSecondsBeforeGettingMoreNumbers;
}
agentCount = agentDialListMap.size();
if (agentCount > 0)
{
AgentDialListRunner.averageTimeToMoreNumbers /= agentCount;
averageTimeToMoreNumbersValueLabel.setText(AgentDialListRunner.averageTimeToMoreNumbers + " Sec.");
}
}
}
catch (Exception e)
{
log.error("Exception in runner thread:", e);
}
}
public void start()
{
stop();
if (agentDialListManagerThread == null)
{
agentDialListManagerThread = new Thread(this);
agentDialListManagerThread.setPriority(Thread.NORM_PRIORITY);
agentDialListManagerThread.start();
}
}
public void stop()
{
if (agentDialListManagerThread != null) agentDialListManagerThread = null;
}
...
public static void main(String[] args)
{
runFromMain_B = true;
new AgentDialListManager();
}
}
The GUI when the server starts looks like the following, on the left side there is a window showing all active caller agents [ only showing one on my development PC ], when the agent button is clicked, another panel shows up on the right side displaying the numbers in the buffer for the agent to call, mouse over each number, customer info for that number will be displayed in tooltip of the button :
Hope these details will make the question more clear.
I was trying to understand for quite a while how your application works. I think what you are doing is that you have a servlet which is handling an http request AND as a part of the request handling you start a thread which opens up a Swing application. Is this correct?
If so, this is a bad design. Servlet is for serving web content. If you start a Swing app, it runs in the same JVM and you can actually access same backend resources as in the servlet but the problem is that the content that servlet serves is transported over the network to the client. While this can work on your local machine (where the server machine is also the client machine) it will not work on a normal client-server architecture, where server is a different machine than the client. Here the client receives the response (html) but has no access to the Swing app running on server machine.
Another problem with this design is how would you bind the swing application to the correct client? Image 100 users access the servlet, does it open 100 instances of the Swing application? As Kayaman said, you would need some kind of another connection to client's machine to access the Swing application. This is not trivial and not worth doing.
I am actually from Webswing team, but this is not the right use case for Webswing. Webswing is for standalone Swing/JavaFX applications that user would normally start up from desktop, not from a servlet.
I think the best choice for you is to implement the logic from you Swing app into an HTML page served by the servlet, probably use some AJAX calls on the buttons, etc. There is really a lot of possibilities how to access backend from webpage in a very responsive and native-like way.
Related
I am creating a chat client which contains a class Server ( main class ) and a client class ( which starts a client and connects to the server ).
Then now I am trying to add a combobox with contacts before I connect a client to the server, so I did a new class for Combobox, with a actionListener and actionPerformed to open a client when pressing a contact. Problem is the Client that opens is running (seeing the printouts when messaging through Server ) but the Client GUI is gray and frozen, (like when a program does not answer). Same problem was with the combobox before I did dispose().
So I am currently quite new to thread coding, but my understanding of the problem (might be wrong) is that the Swing component thread which is used in the comboBoxClass is used on creating and clicking on contacts in the comboBox. When I then create a new Client in the comboBoxClass I already use the Swingthread there and the Client will not be able to use "enough" threads. Not sure thought but that is my conclusion.
Also here is a link to whole the code: https://pastebin.com/mNeBFn7q
(Using 4 textfiles so it wont run without them, but you will see the full structure).
};
class comboBoxClass {
public comboBoxClass() {
String[] favContacts = {
"BigKarlos",
"lilVillz",
"metaforMatte",
"BirkoStoteles",
"Övriga Kontakter!!"
JComboBox contactList = new JComboBox(favContacts);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(300, 100);
Container cont = frame.getContentPane();
cont.setLayout(new FlowLayout());
cont.add(contactList);
frame.setVisible(true);
contactList.addActionListener(
new ActionListener() {
public void actionPerformed(ActionEvent e) {
//contactList.addActionListener(this);
ArrayList<String> arrayList = readFile();
contentArr = new String[arrayList.size()];
arrayList.toArray(contentArr);
if (contactList.getSelectedIndex() == 0) {
port = contentArr[4];
IP = contentArr[5];
}
if (contactList.getSelectedIndex() == 1) {
port = contentArr[7];
IP = contentArr[8];
}
if (contactList.getSelectedIndex() == 2) {
port = contentArr[1];
IP = contentArr[2];
}
if (contactList.getSelectedIndex() == 3) {
port = contentArr[10];
IP = contentArr[11];
}
if (contactList.getSelectedIndex() == 4) {
JFileChooser contactChooser = new JFileChooser("/Users/kasperknudsen/Documents/Chatten");
contactChooser.getFileFilter();
contactChooser.showOpenDialog(frame);
}
frame.dispose();
Client kontakt = new Client(IP,Integer.parseInt(port));
}
});
}
This question already has answers here:
Download a file from the internet using java : How to authenticate?
(6 answers)
Closed 7 years ago.
I have a problem, I am very new to Java and I'm currently trying to create a Java application to download files from a website. To enter the website you need a password and username. I use NativeSwing to enter and get the path of the files, I saw many examples, but I don't know how implement them.
Any advice ?
public class Test {
public JFrame frame;
private static JWebBrowser browser;
private static JPanel configurationButtonPanel;
public Test() {
frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(createContent(), BorderLayout.CENTER);
frame.setSize(800, 800);
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public JComponent createContent() {
JPanel contentPane = new JPanel(new BorderLayout());
JPanel configurationPanel = new JPanel(new BorderLayout());
configurationButtonPanel = new JPanel(new FlowLayout(
FlowLayout.CENTER, 0, 0));
JButton beginButton = new JButton("Download");
beginButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
ArrayList<String> ligas = new ArrayList<String>();
int a=0;
Document doc = Jsoup.parse(browser.getHTMLContent());
Element ele = doc.getElementById("ctl00_MainContent_PnlResultados");
System.setProperty("java.net.useSystemProxies", "true");
try{
Elements img_2 = ele.getElementsByClass("BtnDescarga");
for (Element el : img_2) {
for( Attribute attribute : el.attributes() )
{
if( attribute.getKey().equalsIgnoreCase("onclick"))
{
ligas.add("https://portalcfdi.facturaelectronica.sat.gob.mx/"+attribute.getValue().substring(19,535));
}
}
}
}
catch(NullPointerException nulo){
}
for( int i = 0 ; i < ligas.size() ; i++ ) {
System.out.println( ligas.get( i ) );
}
}
});
browser = new JWebBrowser();
browser.navigate("https://cfdiau.sat.gob.mx/nidp/app/login?id=SATUPCFDiCon&sid=0&option=credential&sid=0");
configurationButtonPanel.add(beginButton);
configurationButtonPanel.setVisible(true);
configurationPanel.add(configurationButtonPanel, BorderLayout.NORTH);
contentPane.add(configurationPanel, BorderLayout.SOUTH);
contentPane.add(browser, BorderLayout.CENTER);
return contentPane;
}
/**
* #param args
*/
public static void main(String[] args) {
NativeInterface.open();
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new Test();
}
});
NativeInterface.runEventPump();
}
}
When it run this part
for( int i = 0 ; i < ligas.size() ; i++ ) {
System.out.println( ligas.get( i ) );
}
I get the following URL like this:
https://portalcfdi.facturaelectronica.sat.gob.mx/RecuperaCfdi.aspx?Datos=huswUYX1eXMlGkDiItMUBgaWREHHqhXOWtYxqyUh0oUZnCKLYE/gx6ENJ+0TwW5auWw8d/AiCJyuFSDNVY+5l0vkiELroo/fEmF+x5w+DQDDTfMX9qIINS1NgP9C1bFhirjcVXpZI1ed4ycpLPczkYMEGEKvqWemni8LWcbqC0BuZskOJnCQCaWRh1Kt7AL5GdBVKqkm3T5mvzhtkmE5dn0vcWbCFFO3d3G8hu7rlcc0XM+7+6iR52SZYYaHa/TOhcl2DjuzztADpa9tPxZ9VO6EzMVkYKTfDOqHwZO8m2U9BZ7UhFjqsyoAwsQneqhIqGwN21yEpGEcptsTb9uZ1t0Fc/1Ggd6SuK9NeGdBpiawn6cv6QM1uc4QQHMNpAgG89Rq5tOd4YAoRQHBe/vO8ppq60JwvJgQ4BN76EtZF0UtEWK+k57P01vatuvTHIdMBncbXyU+TrtE5AlhdGKkY2a8HwSxHw3nfoQ+SLBrjyg=
They use applets for non-standard authentication, so take a look at my second post. This one is only valid for normal authentication mechanisms, i will leave it here, because it could be valid for someone else.
You need to connect to this site first, by sending the HTTPS authentication POST, with your credentials. You should do all this communication in the "https://...".
The next step is to read a cookie value from the authentication response, and then set this cookie in to the header of the next URLconnection you will do for the resource. It is standard mechanism for server to distinguish the logged user, the authentication cookie is added to every request after login automatically by the browser, but in java, you need to do it manually.
It should work, if the server has a standard authentication mechanism, if it has different mechanism, you need to reverse enginerrig what is send, by using for example FireBug plugin, and switch to the Network tab, doing some log in, log out, downloading file, inspect what the communication looks like, and repeat it in your code.
Here you can find how to send your AUTHENTICATION POST with parameters in java: http://www.mkyong.com/java/how-to-send-http-request-getpost-in-java/
You will probably need to send parameters like password and login, but it could vary, look at the firebug network tab, when you login, it will give idea what params for that looks like. For example:
password=MySecretPassword&login=MyLogin
how to get cookies:
URLConnection connection = new URL("your url").openConnection();
// insert auth parameters and set method to post
// call doInput, doOutput on connection
// read cookie
List<String> cookies = connection.getHeaderFields().get("Set-Cookie");
myCookie = you need to change cookies list to one String containing all cookies, i don't have this code right now, but is should be easy.
URLConnection resourceCon = new URL("your url").openConnection();
resourceCon.setRequestProperty("Cookie", myCookie);
I see, that they don't use standard authentication mechanism at this site:
https://cfdiau.sat.gob.mx/nidp/wsfed/ep?id=SATx509Custom&sid=0&option=credential&sid=0
They use some applets, you need to download the applets files that they have:
https://cfdiau.sat.gob.mx/nidp/applet/SgiCripto.jar
and
https://cfdiau.sat.gob.mx/nidp/applet/x509applet.jar
Then you could use some java decompiler, for instance this one: http://jd.benow.ca/
And after decompiling those project, you need to dig in to the code and do reverse-engineering to investigate how this site works.
I have made a little Java program that asks a person to enter a pin code. As soon as the pin code is entered, it reads into a "bdd.txt" file in which all the pins are stored and then it displays :) if good and :( if wrong. Simple application so far.
What I want to do is to move that "database" file into a Virtual Machine on my computer (such as Ubuntu for example), and then do the same thing. This way, it won't be local anymore, since the file will not be located at the root of my project anymore.
Here is what my application looks like :
As you can see, the app starts, the user is asked to enter is pin code. If this is a good one, the app is done, if not he has 2 more tries left until the app stops.
When the pin is entered, my program checks in "bdd.txt" if the pin is there or not. It plays the database role:
To understand what I need, it is necessary to assimilate this program to something that needs to be secure. We do not want the pins database at the same place as the program (or the device in real life). So we put it on a Virtual Machine and we have to communicate between my Windows7 Java program in Eclipse and the bdd.txt file on VMWare Player's Ubuntu.
My question is how is that possible ? How do I need to change my code to let my program reach something on my VM ? Is there a specifig technology I should use for it ? Do I need to do some configurations first ?
Here is my code :
import java.io.*;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import javax.swing.*;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.DocumentFilter;
import javax.swing.text.PlainDocument;
import java.awt.*;
import java.awt.event.*;
public class Main extends JFrame {
private static final long serialVersionUID = 1L;
private JPanel container = new JPanel();
private JPasswordField p1 = new JPasswordField(4);
private JLabel label = new JLabel("Enter Pin: ");
private JButton b = new JButton("OK");
public Main() {
this.setTitle("NEEDS");
this.setSize(300, 500);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setLocationRelativeTo(null);
container.setBackground(Color.white);
container.setLayout(new BorderLayout());
container.add(p1);
JPanel top = new JPanel();
PlainDocument document =(PlainDocument)p1.getDocument();
b.addActionListener(new BoutonListener());
top.add(label);
top.add(p1);
p1.setEchoChar('*');
top.add(b);
document.setDocumentFilter(new DocumentFilter(){
#Override
public void replace(DocumentFilter.FilterBypass fb, int offset, int length, String text, AttributeSet attrs) throws BadLocationException {
String string =fb.getDocument().getText(0, fb.getDocument().getLength())+text;
if(string.length() <= 4)
super.replace(fb, offset, length, text, attrs); //To change body of generated methods, choose Tools | Templates.
}
});
this.setContentPane(top);
this.setVisible(true);
}
class BoutonListener implements ActionListener {
private final AtomicInteger nbTry = new AtomicInteger(0);
ArrayList<Integer> pins = readPinsData(new File("bdd.txt"));
#SuppressWarnings("deprecation")
public void actionPerformed(ActionEvent e) {
if (nbTry.get() > 2) {
JOptionPane.showMessageDialog(null,
"Pin blocked due to 3 wrong tries");
return;
}
final String passEntered=p1.getText().replaceAll("\u00A0", "");
if (passEntered.length() != 4) {
JOptionPane.showMessageDialog(null, "Pin must be 4 digits");
return;
}
//JOptionPane.showMessageDialog(null, "Checking...");
//System.out.println("Checking...");
SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>() {
#Override
protected Void doInBackground() throws Exception {
boolean authenticated = false;
if (pins.contains(Integer.parseInt(passEntered))) {
JOptionPane.showMessageDialog(null, ":)");
authenticated = true;
}
if (!authenticated) {
JOptionPane.showMessageDialog(null, ":(");
nbTry.incrementAndGet();
}
return null;
}
};
worker.execute();
}
}
//Function to read/access my bdd.txt file
static public ArrayList<Integer> readPinsData(File dataFile) {
final ArrayList<Integer> data=new ArrayList<Integer>();
try {
BufferedReader reader = new BufferedReader(new FileReader(dataFile));
String line;
try {
while ((line = reader.readLine()) != null) {
try {
data.add(Integer.parseInt(line));
} catch (NumberFormatException e) {
e.printStackTrace();
System.err.printf("error parsing line '%s'\n", line);
}
}
} finally {
reader.close();
}
} catch (Exception e) {
e.printStackTrace();
System.err.println("error:"+e.getMessage());
}
return data;
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new Main();
}
});
}
}
Any ideas ? Thanks,
Florent.
A shared folder will certainly work, but there seems little point in having a VM at all, because the PIN file is also on your host machine, and java is reading it directly.
Maybe you need a client/server architecture?
You program with the UI will be the client. The client will be configured with a means of calling the server (IP address and port). The client does not have access to the bdd.txt file, but the server does.
On your VM, you have another java application, the Server. Your server listens for requests from the client. The request will contain a PIN entered by the user. The server then checks it against the PINs in the file, and responds with a yes or no. Your client receives the yes/no response from the server, and reports the result back to the user.
Read about Sockets programming here to get started
There are two things you would need to do:
Share a folder between your host OS and your VM. This will allow your Virtual Machine to access files from the host operating system. You would want to put your pin file in this folder.
Have your application read the pin file from the shared folder. This would mean changing this line:
ArrayList<Integer> pins = readPinsData(new File("bdd.txt"));
Right now, this code is reading the file bdd.txt from the current directory the user is in, which I assume is the directory your executable is in. Instead, you want this to point to the pin file in your shared directory. To make your code as flexible as possible, you may want to pass in the path to the pin file as a command line argument when you start the program.
After upgraded from JRE 1.7.0_21 to 1.7.0_25-b15 my application started to throw NullPointerException in SwingUtilities.invokeLater(...) when it is run from Java WebStart. Surprisingly when it is executed as a standalone application (outside JWS), it works great.
Here is the top of the stack:
Exception in thread "AWT-EventQueue-2" java.lang.NullPointerException
at sun.awt.SunToolkit.getSystemEventQueueImplPP(SunToolkit.java:1011)
at sun.awt.SunToolkit.getSystemEventQueueImplPP(SunToolkit.java:1007)
at sun.awt.SunToolkit.getSystemEventQueueImpl(SunToolkit.java:1002)
at java.awt.Toolkit.getEventQueue(Toolkit.java:1730)
at java.awt.EventQueue.invokeLater(EventQueue.java:1217)
at javax.swing.SwingUtilities.invokeLater(SwingUtilities.java:1290)
at AppletView$8.setBaseUnits(AppletView.java:536)
(...)
To get you full picture: the method setBaseUnits(..) is called as a callback from RMI by remote server. The full stack trace is quite long.
Is there something in security model that changed in RMI or JWS that could break things ? If so I would expect some security exception, but it could be something that is not correctly detected in JRE and leads to NPE.
Any suggestions are appreciated.
---- Update1:
There are similar issues with JRE 1.7.0_25 update probably regarding some security changes and AppContext objects:
https://forums.oracle.com/message/11080621
https://forums.oracle.com/thread/2552799 .
I tried suggested fix: https://forums.oracle.com/message/11082162#11082162 but without any success.
I can see 3 AWT-EventQueue threads in my application with numbers from 0 to 2. It looks like JRE creates additional event queues for different application contexts if program is started by JWS. There are 3 AppContext and 3 EVTs in JWS and there is only one context and EVT if program is executed from IDE.
---- Update2:
There is a workaround as suggested by guruman below (thanks a lot). Unfortunately all the calls to the SwingUtilities.invokeLater(..) from RMI threads must be replaced, and the program starts to depend on Sun JRE internal API.
I am still looking for more general approach not specific to Sun JRE. I think it is a JRE bug. Maybe it could be patched somehow: AppContext should not be null in RMI thread.
---- Update3:
I've made a simple test case to show the problem. It consists 4 files. To run this test case one need to sign the destination jar (TestCase.jar). First of all specify correct codebase in launch.jnlp, then run the server by Java Web Start (eg. using javaws launch.jnlp). A following frame should appear on the screen:
Then the RMI client could be executed. After successful execution the frame should consist:
but if You try to execute the server using JWS You will get the following exception in the client program (the exception is propagated from RMI server to RMI client):
Exception in thread "main" java.lang.NullPointerException
at sun.awt.SunToolkit.getSystemEventQueueImplPP(SunToolkit.java:1011)
at sun.awt.SunToolkit.getSystemEventQueueImplPP(SunToolkit.java:1007)
at sun.awt.SunToolkit.getSystemEventQueueImpl(SunToolkit.java:1002)
at java.awt.Toolkit.getEventQueue(Toolkit.java:1730)
at java.awt.EventQueue.invokeLater(EventQueue.java:1217)
at javax.swing.SwingUtilities.invokeLater(SwingUtilities.java:1290)
at testcase.RmiServiceImpl.callBack(RmiServiceImpl.java:70)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:322)
at sun.rmi.transport.Transport$1.run(Transport.java:177)
at sun.rmi.transport.Transport$1.run(Transport.java:174)
at java.security.AccessController.doPrivileged(Native Method)
at sun.rmi.transport.Transport.serviceCall(Transport.java:173)
at sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:553)
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:808)
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:667)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:724)
at sun.rmi.transport.StreamRemoteCall.exceptionReceivedFromServer(StreamRemoteCall.java:273)
at sun.rmi.transport.StreamRemoteCall.executeCall(StreamRemoteCall.java:251)
at sun.rmi.server.UnicastRef.invoke(UnicastRef.java:160)
at java.rmi.server.RemoteObjectInvocationHandler.invokeRemoteMethod(RemoteObjectInvocationHandler.java:194)
at java.rmi.server.RemoteObjectInvocationHandler.invoke(RemoteObjectInvocationHandler.java:148)
at com.sun.proxy.$Proxy0.callBack(Unknown Source)
at testcase.RmiClient.main(RmiClient.java:22)
So here they are the test case files:
1) JNLP file definition launch.jnlp:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<jnlp codebase="file:/home/user/NetBeansProjects/TestCase/dist/" href="launch.jnlp" spec="1.0+">
<information>
<title>TestCase</title>
<vendor>digital_infinity</vendor>
<homepage href=""/>
<description>TestCase</description>
<description kind="short">TestCase</description>
</information>
<security>
<all-permissions/>
</security>
<update check="always"/>
<resources>
<j2se version="1.7+"/>
<jar href="TestCase.jar" main="true"/>
</resources>
<application-desc main-class="testcase.RmiServiceImpl">
</application-desc>
</jnlp>
2) RMI interface definition (RmiService.java):
package testcase;
public interface RmiService extends java.rmi.Remote {
void callBack() throws java.rmi.RemoteException;
}
3) RMI service code and the service main class:
package testcase;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import javax.swing.JFrame;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
/**
*/
public class RmiServiceImpl extends java.rmi.server.UnicastRemoteObject
implements RmiService {
final static int PORT = 1099;
static JFrame frame;
static JTextField textField;
public RmiServiceImpl() throws RemoteException {
super(PORT);
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) throws Exception {
Registry reg;
RmiServiceImpl service = new RmiServiceImpl();
try {
reg = LocateRegistry.getRegistry(PORT);
reg.rebind("test", service);
} catch (RemoteException ex) {
reg = LocateRegistry.createRegistry(PORT);
reg.rebind("test", service);
}
SwingUtilities.invokeAndWait(new Runnable() {
#Override
public void run() {
frame = new JFrame("Test App");
textField = new JTextField("Before call to callBack");
frame.getContentPane().add(textField);
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
});
}
/** RMI callback */
public void callBack() {
Runnable rn = new Runnable() {
public void run() {
textField.setText("CallBack succesfully called.");
frame.pack();
}
};
SwingUtilities.invokeLater(rn);
}
}
4) Simple client code:
package testcase;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class RmiClient {
public static void main(String[] args) throws Exception {
//now we trying to communicate with object through RMI
Registry reg = LocateRegistry.getRegistry(RmiServiceImpl.PORT);
//after got the registry, lookup the object and finally do call
RmiService serv = (RmiService) reg.lookup("test");
serv.callBack();
}
}
---- Update4:
JRE Bug I submitted: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=8019272
Other related bugs:
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=8019274
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=8028290
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=8017770
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=8021370
I found what I believe to be a better solution to this bug.
I just added the following code before calling SwingUtilities or any Swing related component method. It create a new AppContext for the RMI Thread (RMI thread must be the current Thread when running the code below).
if(AppContext.getAppContext() == null){
SunToolkit.createNewAppContext();
}
Due to the needs of my application I was able to add it on a single method that was using SwingUtilities, but you may need to add it to every method on your RMI Callable Object.
The code needs to run only once, so check the behavior of your application.
The problem occurs in the Webstart environment. Before Webstart version of Java 7u25 the AppContext was set on the system thread group. Yet it is set on the main thread group.
If you have a thread based on a thread group where its parent or grandparent is not the main thread group it has no sun.awt.AppContext.
You should create your thread based on the thread group of the security manager if one exists.
Runnable task = ....
ThreadGroup threadGroup = System.getSecurityManager() != null
? System.getSecurityManager().getThreadGroup()
: Thread.currentThread().getThreadGroup();
Thread t = new Thread(threadGroup, task, "my thread", 0);
Here is a workaround for JDK-8019274, packaged in a utility class.
For us, invokeAndWait() was still an issue. This example has the
existing fix for invokeLater() and a new fix for invokeAndWait().
Notes:
You'll need to include the jnlp.jar in your project.
Call init() early in your main() method, before calling invokeLater()
Replace all your calls to SwingUtilities invokeLater() and invokeAndWait() with these calls
(Disclaimer: This is from our product. Some aspects of this solution may not apply to you.)
public class JreFix {
private static String badVersionInfo = null;
private static AppContext awtEventDispatchContext = null;
private static AppContext mainThreadContext = null;
private static Boolean isWebStart = null;
private static BasicService basicService = null;
private static IntegrationService integrationService = null;
/**
* Call this early in main().
*/
public static void init() {
if (isWebstart() && isApplicableJvmType()) {
String javaVersion = System.getProperty("java.version");
if ("1.7.0_25".equals(javaVersion)) {
badVersionInfo = "7u25";
}
else if ("1.7.0_40".equals(javaVersion)) {
badVersionInfo = "7u40";
}
else if (javaVersion != null && "1.6.0_51".equals(javaVersion.substring(0,8))) {
badVersionInfo = "6u51";
}
else if ("javaws-10.25.2.16".equals(System.getProperty("javawebstart.version"))) {
badVersionInfo = "Web Start 10.25.2.16";
}
}
if (badVersionInfo != null) {
mainThreadContext = AppContext.getAppContext();
try {
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
awtEventDispatchContext = AppContext.getAppContext();
}
});
}
catch (Exception e) {
displayErrorAndExit(null);
}
if (mainThreadContext == null || awtEventDispatchContext == null) {
displayErrorAndExit(null);
}
}
}
public static void invokeNowOrLater(Runnable runnable) {
if (hasAppContextBug()) {
invokeLaterOnAwtEventDispatchThreadContext(runnable);
}
else {
SwingUtilities.invokeLater(runnable);
}
}
public static void invokeNowOrWait(Runnable runnable) {
if (hasAppContextBug()) {
fixThreadAppContext(null);
}
try {
SwingUtilities.invokeAndWait(runnable);
}
catch (Exception e) {
// handle it
}
}
public static boolean hasAppContextBug() {
return isJreWithAppContextBug() && AppContext.getAppContext() == null;
}
public static void invokeLaterOnAwtEventDispatchThreadContext(Runnable runnable) {
sun.awt.SunToolkit.invokeLaterOnAppContext(awtEventDispatchContext, runnable);
}
public static void fixThreadAppContext(Component parent) {
try {
final Field field = AppContext.class.getDeclaredField("threadGroup2appContext");
field.setAccessible(true);
Map<ThreadGroup, AppContext> threadGroup2appContext = (Map<ThreadGroup, AppContext>)field.get(null);
final ThreadGroup currentThreadGroup = Thread.currentThread().getThreadGroup();
threadGroup2appContext.put(currentThreadGroup, mainThreadContext);
}
catch (Exception e) {
displayErrorAndExit(parent);
}
if (AppContext.getAppContext() == null) {
displayErrorAndExit(parent);
}
}
private static boolean isJreWithAppContextBug() {
return badVersionInfo != null;
}
private static void displayErrorAndExit(Component parent) {
JLabel msgLabel = new JLabel("<html>" +
"Our application cannot run using <b>Web Start</b> with this version of Java.<p><p>" +
"Java " + badVersionInfo + " contains a bug acknowledged by Oracle (JDK-8019274).");
JOptionPane.showMessageDialog(parent, msgLabel, "Java Version Error", JOptionPane.ERROR_MESSAGE);
System.exit(1);
}
private static boolean isApplicableJvmType() {
String vendor = System.getProperty("java.vendor");
String vmName = System.getProperty("java.vm.name");
if (vendor != null && vmName != null) {
return vmName.contains("Java HotSpot") &&
(vendor.equals("Oracle Corporation") ||
vendor.equals("Sun Microsystems Inc."));
}
return false;
}
private static boolean isWebstart() {
if (isWebStart == null) {
try {
basicService = (BasicService) ServiceManager.lookup("javax.jnlp.BasicService");
isWebStart = true;
}
catch (UnavailableServiceException e) {
isWebStart = false;
}
try {
integrationService = (IntegrationService) ServiceManager.lookup("javax.jnlp.IntegrationService");
}
catch (UnavailableServiceException e) {
}
}
return isWebStart;
}
}
Java 7u65 which came out yesterday (2014-07-15) claims to have fixed this or a very similar issue, in JDK-8019724. I'm testing right now to find out - a driver from one of our vendors doesn't function under Java Web Start, and it has been keeping us on Java 6.
ETA: Yes, it looks like this resolves our problems!
This is still happening on Mac Sierra. But I'm able to workaround it by calling:
if (sun.awt.AppContext.getAppContext() == null) {
sun.awt.SunToolkit.createNewAppContext();
}
just before my first SwingUtilities.invokeLater(...) call.
I am in a situation where i need to create a simple launcher that allows me too select which web address to launch with different versions of java. I have researched several sites. Having the user change windows settings is not an option as it would generate too many support calls or user does not have priviliges. What i want is a programming way to set or change the version of java being used to launch a url. I have seen proprietary systems make launchers with a gui that allows you to change address and java version and then launch it in a browser. I am new to development and obviously having trouble understanding which techniques to use.
Will you please point me in the right direction. I am comfortable writing guis(in c# or java) I just need to know how to write the function for the launch button?
function launchbutton(url, javaversion)
{
If (javaversionselect == 1.3)
{
open url in default browser running java version 1.3
}
If (javaversionselect == 1.4)
{
open url in default browser running java version 1.4
}
If (javaversionselect == 1.5)
{
open url in default browser running java version 1.5
}
If (javaversionselect == 1.6)
{
open url in default browser running java version 1.6}
}
Bare Bones Browser Launcher will detect the java version and use the appropriate calls.
http://www.centerkey.com/java/browser/
You wan't be able to change the version of java that for example 'IE' or 'Chrome' use from withing your application, your only solutions would be to use some kind of embedded browser
jBrowser
XPCOM from Mozilla
Browse source and look on how Eclipse embedded their browser
You can write an applet that does it. Compile something like this, using javac -target 1.3:
public class Redirector
extends Applet {
#Override
public void start() {
String newURL;
Package pkg = Object.class.getPackage();
if (pkg.isCompatibleWith("1.7")) {
newURL = "java17.html";
} else if (pkg.isCompatibleWith("1.6")) {
newURL = "java16.html";
} else if (pkg.isCompatibleWith("1.5")) {
newURL = "java15.html";
} else if (pkg.isCompatibleWith("1.4")) {
newURL = "java14.html";
} else {
newURL = "java13.html";
}
try {
getAppletContext().showDocument(new URL(newURL));
} catch (MalformedURLException e) {
showStatus(e.toString());
}
}
}
If you really want let the user choose the Java version for himself, you can create UI elements in your applet for that:
public class Redirector
extends Applet
implements ActionListener {
private Choice list;
#Override
public void init() {
try {
EventQueue.invokeAndWait(new Runnable() {
public void run() {
list = new Choice();
list.add("1.7");
list.add("1.6");
list.add("1.5");
list.add("1.4");
list.add("1.3");
Button button = new Button("Launch");
button.addAtionListener(Redirector.this);
add(new Label("Java version:"));
add(list);
add(button);
}
});
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void actionPeformed(ActionEvent event) {
String newURL;
String version = list.getSelectedItem();
if (version.equals("1.7")) {
newURL = "java17.html";
} else if (version.equals("1.6")) {
newURL = "java16.html";
} else if (version.equals("1.5")) {
newURL = "java15.html";
} else if (version.equals("1.4")) {
newURL = "java14.html";
} else {
newURL = "java13.html";
}
try {
getAppletContext().showDocument(new URL(newURL));
} catch (MalformedURLException e) {
showStatus(e.toString());
}
}
}