A few times I have been asked about the integrating a Java applet to a Vaadin application. There are few things to bear in mind with that and I thought that is easier to create a package that you can use as a basis for your own integration.
So, here is a AppletIntegration add-on.
The package includes a base Java applet class that implements threaded communication with GWT component as well as base widget and server-side API to embed the applet.
AbstractApplet :
A base class for an applet. Implements a threaded JavaScript communication mechanism. The threading is needed if you want to integrate a privileged applet (i.e. access resources outside web sandbox). Just subclass this and implement the execute method.
VAppletIntegration :
Client-side Applet integration base for embedding and communicating with an applet. Implements GWT peer to for applet communication.
AppletIntegration :
Server-side applet integration class. This is intended to be subclassed to integrate a custom applet to your Vaadin application. Implements the server API for applet communication.
Remember, that to implement a privileged applet you need sign the applet jar.
after askin on IRC I got the link to the source code of the example which was really helpful.
Anyway I can’t make it work with my implementation. The init() method of my Applet is never called. Any idea what could be the cause of this ? The same is true for the doExecute(,) method.
Applet code:
package de.heagmobilo.dfi.manager.ui.streamer;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.params.HttpMethodParams;
import org.apache.commons.io.IOUtils;
import org.vaadin.applet.AbstractVaadinApplet;
import de.heagmobilo.dfi.manager.ui.data.WAVRecorder.RecorderException;
public class RecordSoundApplet extends AbstractVaadinApplet {
/**
*
*/
private static final long serialVersionUID = -3507633958558969641L;
public static final String CMD_SAVE = "post";
private static final int DEFAULT_HEIGHT = 1;
private static final int DEFAULT_WIDTH = 1;
private String postUrl;
private String postParamName;
private String sessionCookie;
private static final String POST_PARAM_SUFFIX = "_file";
public static final String CMD_RECORD = "record";
public static final String CMD_STOP = "stop";
private de.heagmobilo.dfi.manager.ui.data.WAVRecorder recorder = new de.heagmobilo.dfi.manager.ui.data.WAVRecorder();
@Override
public void init() {
super.init();
resize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
postUrl = super.getApplicationURL();
postParamName = getPaintableId();
postParamName += POST_PARAM_SUFFIX;
sessionCookie = getApplicationSessionCookie();
System.out.println("LOADED");
}
@Override
protected void doExecute(String command, Object[] params) {
System.out.println("CMD=" +command);
if (command.equals(CMD_SAVE)) {
postSoundToServer();
} else if (command.equals(CMD_RECORD)) {
recordSound();
} else if (command.equals(CMD_STOP)) {
stopRecordSound();
}
}
private synchronized void stopRecordSound() {
recorder.stop();
}
private synchronized void recordSound() {
try {
recorder.record();
} catch (RecorderException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private synchronized void postSoundToServer() {
if (postUrl == null) {
System.out.println("URL missing. Cancel post.");
return;
}
// Send in separate thread
Thread t = new Thread() {
@Override
public void run() {
try {
System.out.println("Post to server: " + postUrl);
System.out.println("Cookie: " + sessionCookie);
final ByteArrayOutputStream bo = new ByteArrayOutputStream();
IOUtils.copy(recorder.getRecordedStream(), bo);
PostMethod filePost = new PostMethod(postUrl);
filePost.getParams().setBooleanParameter(
HttpMethodParams.USE_EXPECT_CONTINUE, false);
filePost.setRequestHeader("Cookie", sessionCookie);
HttpClient client = new HttpClient();
client.getHttpConnectionManager().getParams()
.setConnectionTimeout(5000);
int status = client.executeMethod(filePost);
vaadinSync();
} catch (MalformedURLException e) {
System.out.println(e);
} catch (IOException e) {
System.out.println(e);
}
}
};
t.start();
}
}
Widget code:
package de.heagmobilo.dfi.manager.ui.streamer;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import org.vaadin.applet.client.ui.VAppletIntegration;
import com.vaadin.Application;
import com.vaadin.terminal.PaintException;
import com.vaadin.terminal.PaintTarget;
import com.vaadin.terminal.Resource;
import com.vaadin.terminal.StreamResource;
import com.vaadin.terminal.StreamResource.StreamSource;
import com.vaadin.terminal.gwt.server.WebApplicationContext;
import com.vaadin.ui.Upload;
@com.vaadin.ui.ClientWidget(org.vaadin.applet.client.ui.VAppletIntegration.class)
public class RecordSound extends Upload {
private static final long serialVersionUID = 7348838270779907095L;
private static final String APPLET_CLASS = "de.heagmobilo.dfi.manager.ui.streamer.RecordSoundApplet";
private static final String[] APPLET_ARCHIVES = new String[]
{
"screenshot.jar", "appletintegration-1.0.2.jar",
"commons-httpclient-3.1.jar", "commons-codec-1.3.jar",
"commons-logging-1.1.1.jar" };
public ByteArrayOutputStream data;
public boolean ready;
private boolean record = false;
private boolean stop = false;
private boolean save = false;
private List<Listener> listeners = new ArrayList<Listener>();
private String name;
/**
* Creates new screenshot component.
*
*/
public RecordSound() {
super(null, null);
setReceiver(new SoundRecordReceiver());
addListener(new UploadReadyListener());
}
/**
* Take a screenshot. Screenshots are taken interactively so that user
* selects an area from screen.
*
*/
public void record() {
record = true;
save = false;
stop = false;
requestRepaint();
}
public void stop() {
stop = true;
save = false;
record = false;
requestRepaint();
}
public void save(String name) {
save = true;
stop = false;
record = false;
this.name = name;
requestRepaint();
}
public void paintContent(PaintTarget target) throws PaintException {
System.out.println("paint!!!");
target.addUploadStreamVariable(this, "stream");
// Because we don't inherit the AppletIntegration component on the
// server-side, we have to add the attributes explicitly here:
String sid = getHttpSessionId();
if (sid != null) {
target.addAttribute(VAppletIntegration.ATTR_APPLET_SESSION, sid);
}
target.addAttribute(VAppletIntegration.ATTR_APPLET_CLASS, APPLET_CLASS);
target.addAttribute(VAppletIntegration.ATTR_APPLET_ARCHIVES,
APPLET_ARCHIVES);
if (record) {
System.out.println("RECORD!!!");
target.addAttribute(VAppletIntegration.ATTR_CMD, RecordSoundApplet.CMD_RECORD);
record = false;
} else if (save){
System.out.println("SAVE!!!");
target.addAttribute(VAppletIntegration.ATTR_CMD, RecordSoundApplet.CMD_SAVE);
save = false;
} else if (stop) {
System.out.println("STOP!!!");
target.addAttribute(VAppletIntegration.ATTR_CMD, RecordSoundApplet.CMD_STOP);
stop = false;
}
// super.paint(target);
}
/**
* Read the HTTP session id.
*
* This method cannot be called if this component has not been attached to
* the application.
*
* @return
*/
protected String getHttpSessionId() {
Application app = getApplication();
if (app != null) {
WebApplicationContext ctx = ((WebApplicationContext) app
.getContext());
if (ctx != null) {
return ctx.getHttpSession().getId();
}
}
return null;
}
private class SoundRecordReceiver implements Receiver {
private static final long serialVersionUID = 1828017160390445637L;
public OutputStream receiveUpload(String filename, String MIMEType) {
ready = false;
return data = new ByteArrayOutputStream();
}
}
private class UploadReadyListener implements FinishedListener {
private static final long serialVersionUID = -580474682420247918L;
@Override
public void uploadFinished(FinishedEvent event) {
ready = true;
save = false;
invokeListeners();
}
}
public String getSoundName() {
return name;
}
/**
* Listener for screenhot uploads. Invoked when a new screenshot has been
* trasfered to the server.
*
* @author Sami Ekblad
*
*/
public interface Listener {
void recordSoundReady(RecordSound screenshot);
}
/**
* Add new listener.
*
*/
public void addListener(Listener listener) {
listeners.add(listener);
}
private void invokeListeners() {
for (Listener l : listeners) {
l.recordSoundReady(this);
}
}
/**
* Remove a listener.
*
*/
public void removeListener(Listener listener) {
listeners.remove(listener);
}
/**
* Get current screenshot data as a Resource. Screenshots are PNG images.
*
* @see Resource
*/
public Resource getRecordSoundResource() {
StreamSource ss = new StreamSource() {
@Override
public InputStream getStream() {
return getRecordSoundInputStream();
}
};
StreamResource sr = new StreamResource(ss, name + ".wav", getApplication());
return sr;
}
/**
* Get current screenshot data as InputStream. Screenshots are PNG images.
*
* @see #getScreenshotResource()
*/
public InputStream getRecordSoundInputStream() {
return new ByteArrayInputStream(data != null ? data.toByteArray()
: new byte[] {});
}
}
Your code looks quite ok, but if the applet init does not get called, the reason is simply that the applet is not instantiated on the web page correctly. VAppletIntegration is responsible of this part.
I cannot say what is causing this, but the Java console is something that can help here. It shows what classes it tries to load and from where. My guess is that the applet class/jar is not in the correct place i.e. under the GWT public folder next to (generated) .gwt.xml file.
One thing I remembered one generic applet development tip:
You are sure you changed the code, but the changes are simply not reflected. You have cleared the class loader cache from the Java console, but still no effect. You should
restart your browser .
At least FF very persistently caches the jar files and they simply are not reloaded from the server until you restart the browser.
Good add-on! Can you provide simpler example, just calling applet frame with some parameters (vaadin textbox → applet textbox). I have trouble with Screenshot add-on. Thanks and sorry for my English.
Currently this is not officially supported as long as there is no client-side API for Vaadin components. It will most likely to to be in Vaadin 7 and something like this has been discussed at
Uservoice too.
However, here is a some workaround that you might be using today.
Pass the component reference to applet (component id) at the server-side paintContent
Subclass VAppletIntegration and pass the component id to applet)
Store the component id in your applet
Call changeVariable function with provided from applet
This will effectively send the variable to server which will change the TextField content back to browser.
I updated the add-on and fixed the result handling along the way. The main new feature is ability change Vaadin variables from the applet directly.
http://vaadin.com/addon/appletintegration
Test with the latest package and let us know. I would have no problems giving commit access too, but we have to ask the svn administrator that. Maybe it is easier for both of us to use email and patches?
For normal applet parameters there is setAppletParams method in the AppletIntegration class. It is a protected method, but to use it just subclass the AppletIntegration class.
I’m trying to run a simple applet from vaadin application as described in the example from addon page:
AppletIntegration applet = new AppletIntegration() {
private static final long serialVersionUID = 1L;
@Override
public void attach()
{
setAppletArchives(Arrays.asList(APPLET_ARCHIVES));
setCodebase("VAADIN/");
setAppletClass(APPLET_CLASS);
}
};
addComponent(applet);
where APPLET_CLASS = “com.alee.sca.authorization.AuthorizationApplet”; and APPLET_ARCHIVES = new String { “AuthorizationApplet.jar” };
When I run my application I recieve the following error:
SEVERE: Requested resource [VAADIN/com.alee.sca.authorization.AuthorizationApplet]
not found from filesystem or through class loader. Add widgetset and/or theme JAR to your classpath or add files to WebContent/VAADIN folder.
But I can reach AuthorizationApplet.jar from the browser so it exists on the server. I compiled widgetset and it is on classpath, AuthorizationApplet.jar is placed in folder web/VAADIN.
I also noticed that in screenshot addon not only applet jar is added to applet archives but also some libraries including applet integration addon jar. Is it necessary to add additional libraries to applet archives? And where should I put these jar files?
It seems that I solved the problem, applet is working now, but I have another question: how can I receive variables from applet back to vaadin application if I send them to server using vaadinUpdateVariable method? Should I implement a receiver like it is done in screenshot application or there is another way to do it?
Yes, you can receive the variables in the server. The receiver function you have to implement is
changeVariables . To do this you must inherit the AppletIntegration class and override it like:
@Override
public void changeVariables(Object source, Map variables) {
super.changeVariables(source, variables);
if (variables.containsKey("mydata")) {
String mydata = (String) variables.get("mydata");
}
}