Having figured out how to use the wait() and notify() in Java application to fetch some data from the Internet, I had to migrate that code into my Android application. As it turns out the code that would've worked in Java app would never had worked within my Android app even with attempts to make it multi-threaded (with Runnable and then ASyncTask). The problem seems that the Android app will hang after a call on Object.wait() and will never continue further.
The following is the code of the Java & Android classes:
Java
import java.util.Map;
import com.firebase.client.DataSnapshot;
import com.firebase.client.Firebase;
import com.firebase.client.FirebaseError;
import com.firebase.client.ValueEventListener;
public class Getter {
private String username = "jdk17";
private String userFullname;
private Object userObj = new Object();
public static void main(String[] args) {
System.out.println("Main");
String text;
Getter main = new Getter();
text = main.getString();
System.out.println("Main - Text = " + text);
}
public String getString() {
Firebase firebaseRef = new Firebase(
"https://demoandroid.firebaseio.com/user/username/" + username);
firebaseRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onCancelled(FirebaseError arg0) {
}
#Override
public void onDataChange(DataSnapshot snap) {
System.out.println("***********onDataChange()***********");
Object obj = snap.getValue();
userFullname = (String) ((Map) obj).get("fullname");
System.out.println("********* The text = " + userFullname);
synchronized (userObj) {
userObj.notify();
}
}
});
try {
synchronized (userObj) {
System.out.println("Calling wait()");
userObj.wait();
}
} catch (InterruptedException e1) {
e1.printStackTrace();
}
System.out.println("getString() returning text = " + userFullname);
return userFullname;
}
}
Android
package com.example.paabooking;
import java.util.Map;
import android.util.Log;
import com.firebase.client.DataSnapshot;
import com.firebase.client.Firebase;
import com.firebase.client.FirebaseError;
import com.firebase.client.ValueEventListener;
public class FirebaseHelper {
private final String TAG = "FirebaseHelper";
private String username = "jdk17";
private String userFullname;
private Object userObj = new Object();
public FirebaseHelper() {}
public String getString() {
Log.d(TAG, "getString()");
Firebase firebaseRef = new Firebase("https://demoandroid.firebaseio.com/user/username/" + username);
firebaseRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onCancelled(FirebaseError arg0) {Log.d(TAG, "cancelled.");}
#Override
public void onDataChange(DataSnapshot snap) {
Log.d(TAG, "***********onDataChange()***********");
// TODO Auto-generated method stub
Object obj = snap.getValue();
userFullname = (String)((Map)obj).get("fullname");
Log.d(TAG, "********* The text = " + userFullname);
synchronized(userObj) {
userObj.notify();
}
}
});
try {
synchronized (userObj) {
Log.d(TAG, "Calling wait()");
userObj.wait();
}
} catch (InterruptedException e1) {
e1.printStackTrace();
}
Log.d(TAG,"getString() returning text = " + userFullname);
return userFullname;
}
}
Console printout:Java
Main
Calling wait()
***********onDataChange()***********
********* The text = Tom Barry
getString() returning text = Tom Barry
Main - Text = Tom Barry
Console printout: Android
getString()
Calling wait()
Java Firebase Library - https://www.firebase.com/docs/java-quickstart.html
I don't think that this is due to any differences (real or hypothetical) between wait/notify in Java and Android.
I think that the difference is explained by this quote from the Firebase page you linked to:
"By default, on Android, all callbacks are executed on the main thread. On other JVM targets, callbacks are executed on a new, separate thread. You can configure this behavior by providing your own EventTarget to the default Config used by the library.".
In the Android case, your main thread appears to be instantiating the Firebase object, adding the listener, and then calling wait(). But wait() is blocking the main thread ... so, of course the main thread is not in a position to accept the callback that would wake it up. Hence, everything freezes.
The 2nd sentence of the quote seems to suggest the way to solve the problem.
I've never come across Firebase before, let alone tried to use it. This is just based on my superficial reading of the documentation and your code.
Related
I am developing a client-server application, where I wanted to have a persistent connection between client-server, and I chose the CometD framework for the same.
I successfully created the CometD application.
Client -
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.cometd.bayeux.Channel;
import org.cometd.bayeux.Message;
import org.cometd.bayeux.client.ClientSessionChannel;
import org.cometd.client.BayeuxClient;
import org.cometd.client.transport.LongPollingTransport;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import com.synacor.idm.auth.LdapAuthenticator;
import com.synacor.idm.resources.LdapResource;
public class CometDClient {
private volatile BayeuxClient client;
private final AuthListner authListner = new AuthListner();
private LdapResource ldapResource;
public static void main(String[] args) throws Exception {
org.eclipse.jetty.util.log.Log.getProperties().setProperty("org.eclipse.jetty.LEVEL", "ERROR");
org.eclipse.jetty.util.log.Log.getProperties().setProperty("org.eclipse.jetty.util.log.announce", "false");
org.eclipse.jetty.util.log.Log.getRootLogger().setDebugEnabled(false);
CometDClient client = new CometDClient();
client.run();
}
public void run() {
String url = "http://localhost:1010/cometd";
HttpClient httpClient = new HttpClient();
try {
httpClient.start();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
client = new BayeuxClient(url, new LongPollingTransport(null, httpClient));
client.getChannel(Channel.META_HANDSHAKE).addListener(new InitializerListener());
client.getChannel(Channel.META_CONNECT).addListener(new ConnectionListener());
client.getChannel("/ldapAuth").addListener(new AuthListner());
client.handshake();
boolean success = client.waitFor(1000, BayeuxClient.State.CONNECTED);
if (!success) {
System.err.printf("Could not handshake with server at %s%n", url);
return;
}
}
private void initialize() {
client.batch(() -> {
ClientSessionChannel authChannel = client.getChannel("/ldapAuth");
authChannel.subscribe(authListner);
});
}
private class InitializerListener implements ClientSessionChannel.MessageListener {
#Override
public void onMessage(ClientSessionChannel channel, Message message) {
if (message.isSuccessful()) {
initialize();
}
}
}
private class ConnectionListener implements ClientSessionChannel.MessageListener {
private boolean wasConnected;
private boolean connected;
#Override
public void onMessage(ClientSessionChannel channel, Message message) {
if (client.isDisconnected()) {
connected = false;
connectionClosed();
return;
}
wasConnected = connected;
connected = message.isSuccessful();
if (!wasConnected && connected) {
connectionEstablished();
} else if (wasConnected && !connected) {
connectionBroken();
}
}
}
private void connectionEstablished() {
System.err.printf("system: Connection to Server Opened%n");
}
private void connectionClosed() {
System.err.printf("system: Connection to Server Closed%n");
}
private void connectionBroken() {
System.err.printf("system: Connection to Server Broken%n");
}
private class AuthListner implements ClientSessionChannel.MessageListener{
#Override
public void onMessage(ClientSessionChannel channel, Message message) {
Object data2 = message.getData();
System.err.println("Authentication String " + data2 );
if(data2 != null && data2.toString().indexOf("=")>0) {
String[] split = data2.toString().split(",");
String userString = split[0];
String passString = split[1];
String[] splitUser = userString.split("=");
String[] splitPass = passString.split("=");
LdapAuthenticator authenticator = new LdapAuthenticator(ldapResource);
if(authenticator.authenticateToLdap(splitUser[1], splitPass[1])) {
// client.getChannel("/ldapAuth").publish("200:success from client "+user);
// channel.publish("200:Success "+user);
Map<String, Object> data = new HashMap<>();
// Fill in the structure, for example:
data.put(splitUser[1], "Authenticated");
channel.publish(data, publishReply -> {
if (publishReply.isSuccessful()) {
System.out.print("message sent successfully on server");
}
});
}
}
}
}
}
Server - Service Class
import java.util.List;
import java.util.concurrent.BlockingQueue;
import org.cometd.bayeux.MarkedReference;
import org.cometd.bayeux.Promise;
import org.cometd.bayeux.server.BayeuxServer;
import org.cometd.bayeux.server.ConfigurableServerChannel;
import org.cometd.bayeux.server.ServerChannel;
import org.cometd.bayeux.server.ServerMessage;
import org.cometd.bayeux.server.ServerSession;
import org.cometd.server.AbstractService;
import org.cometd.server.ServerMessageImpl;
import com.synacor.idm.resources.AuthenticationResource;
import com.synacor.idm.resources.AuthenticationResource.AuthC;
public class AuthenticationService extends AbstractService implements AuthenticationResource.Listener {
String authParam;
BayeuxServer bayeux;
BlockingQueue<String> sharedResponseQueue;
public AuthenticationService(BayeuxServer bayeux) {
super(bayeux, "ldapagentauth");
addService("/ldapAuth", "ldapAuthentication");
this.bayeux = bayeux;
}
public void ldapAuthentication(ServerSession session, ServerMessage message) {
System.err.println("********* inside auth service ***********");
Object data = message.getData();
System.err.println("****** got data back from client " +data.toString());
sharedResponseQueue.add(data.toString());
}
#Override
public void onUpdates(List<AuthC> updates) {
System.err.println("********* inside auth service listner ***********");
MarkedReference<ServerChannel> createChannelIfAbsent = bayeux.createChannelIfAbsent("/ldapAuth", new ConfigurableServerChannel.Initializer() {
public void configureChannel(ConfigurableServerChannel channel)
{
channel.setPersistent(true);
channel.setLazy(true);
}
});
ServerChannel reference = createChannelIfAbsent.getReference();
for (AuthC authC : updates) {
authParam = authC.getAuthStr();
this.sharedResponseQueue= authC.getsharedResponseQueue();
ServerChannel channel = bayeux.getChannel("/ldapAuth");
ServerMessageImpl serverMessageImpl = new ServerMessageImpl();
serverMessageImpl.setData(authParam);
reference.setBroadcastToPublisher(false);
reference.publish(getServerSession(), authParam, Promise.noop());
}
}
}
Event trigger class-
public class AuthenticationResource implements Runnable{
private final JerseyClientBuilder clientBuilder;
private final BlockingQueue<String> sharedQueue;
private final BlockingQueue<String> sharedResponseQueue;
private boolean isAuthCall = false;
private String userAuth;
private final List<Listener> listeners = new CopyOnWriteArrayList<Listener>();
Thread runner;
public AuthenticationResource(JerseyClientBuilder clientBuilder,BlockingQueue<String> sharedQueue,BlockingQueue<String> sharedResponseQueue) {
super();
this.clientBuilder = clientBuilder;
this.sharedQueue = sharedQueue;
this.sharedResponseQueue= sharedResponseQueue;
this.runner = new Thread(this);
this.runner.start();
}
public List<Listener> getListeners()
{
return listeners;
}
#Override
public void run() {
List<AuthC> updates = new ArrayList<AuthC>();
// boolean is = true;
while(true){
if(sharedQueue.size()<=0) {
continue;
}
try {
userAuth = sharedQueue.take();
// Notify the listeners
for (Listener listener : listeners)
{
updates.add(new AuthC(userAuth,sharedResponseQueue));
listener.onUpdates(updates);
}
updates.add(new AuthC(userAuth,sharedResponseQueue));
System.out.println("****** Auth consume ******** " + userAuth);
if(userAuth != null) {
isAuthCall = true;
}
} catch (Exception err) {
err.printStackTrace();
break;
}
// if (sharedQueue.size()>0) {
// is = false;
// }
}
}
public static class AuthC
{
private final String authStr;
private final BlockingQueue<String> sharedResponseQueue;
public AuthC(String authStr,BlockingQueue<String> sharedResponseQueue)
{
this.authStr = authStr;
this.sharedResponseQueue=sharedResponseQueue;
}
public String getAuthStr()
{
return authStr;
}
public BlockingQueue<String> getsharedResponseQueue()
{
return sharedResponseQueue;
}
}
public interface Listener extends EventListener
{
void onUpdates(List<AuthC> updates);
}
}
I have successfully established a connection between client and server.
Problems -
1- When I am sending a message from the server to the Client, the same message is sent out multiple times. I only expecting one request-response mechanism.
In my case- server is sending user credentila I am expecting result, whether the user is authenticated or not.
you can see in image how it is flooding with same string at client side -
2- There was other problem looping up of message between client and server, that I can be able to resolve by adding, but still some time looping of message is happening.
serverChannel.setBroadcastToPublisher(false);
3- If I change the auth string on sever, at client side it appears to be old one.
For example -
1 request from server - auth string -> user=foo,pass=bar -> at
client side - user=foo,pass=bar
2 request from server - auth string user=myuser,pass=mypass ->
at client side - user=foo,pass=bar
this are the three problems, please guide me and help me to resolve this.
CometD offer a request/response style of messaging using remote calls, both on the client and on the server (you want to use annotated services on the server).
Channel /ldapAuth has 2 subscribers: the remote client (which subscribes with authChannel.subscribe(...)), and the server-side AuthenticationService (which subscribes with addService("/ldapAuth", "ldapAuthentication")).
Therefore, every time you publish to that channel from AuthenticationService.onUpdates(...), you publish to the remote client, and then back to AuthenticationService, and that is why calling setBroadcastToPublisher(false) helps.
For authentication messages, it's probably best that you stick with remote calls, because they have a natural request/response semantic, rather than a broadcasting semantic.
Please read about how applications should interact with CometD.
About other looping, there are no loops triggered by CometD.
You have loops in your application (in AuthenticationService.onUpdates(...)) and you take from a queue that may have the same information multiple times (in AuthenticationResource.run() -- which by the way it's a spin loop that will likely spin a CPU core to 100% utilization -- you should fix that).
The fact that you see stale data it's likely not a CometD issue, since CometD does not store messages anywhere so it cannot make up user-specific data.
I recommend that you clean up your code using remote calls and annotated services.
Also, clean up your own code from spin loops.
If you still have the problem after the suggestions above, look harder for application mistakes, it's unlikely that this is a CometD issue.
I'm creating a chat app and I want some way to keep track of the messages. I've already read the following threads on the topic:
Firebase firestore collection count
How to get a count of number of documents in a collection with Cloud Firestore
How to keep track of listeners in Firebase on Android?
And I manage to get a count, it works, but the counter starts over every time I close the app?
I have this method to find the number of Docs in a Firestore collection:
public void numberOfMessagesInConversation() {
CollectionReference messageRef = db.collection("users")
.document(userID)
.collection("conversations")
.document("conversation0") //Specified conversation
.collection("messages");
messageRef.addSnapshotListener(new EventListener<QuerySnapshot>() {
#Override
public void onEvent(QuerySnapshot documentSnapshots, FirebaseFirestoreException e) {
counter = documentSnapshots.size();
Log.d(TAG, "counter: " + counter);
}
});
}
Then I have another method where I call the method above, add 1 to the counter and return it as a String:
public String createAndReturnMessageSentNumberStringForDocName() {
numberOfMessagesInConversation();
counter++;
String messageNumberForDoc = String.valueOf(counter);
Log.d(TAG, "createAndReturnMessageSentNumberStringForDocName: " + messageNumberForDoc);
return messageNumberForDoc;
}
Lastly, I have a method to upload the message to Firestore, and I use the counted number to name the messages there, by creating names for the documents such as "message 1", "message 2", "message 3" etc. based on the counter.
public void addSentMessageToFirestoreDB(Map<String, Object> messageSent) {
String docNumber = createAndReturnMessageSentNumberStringForDocName();
Log.d(TAG, "docNumber: " + docNumber);
}
When I open the app, and I write the first messages, it correctly ads the messages in chronological order, and it keeps accurately track of the messages. However, when I close the app and re-open it, it starts to count from a lower number? I have defined the counter at the top like so:
int counter;
So I don't reset it to zero during the initiation either.
Here are my log outputs in chronological order:
I'm also confused why the "createAndReturnMessageSentNumberStringForDocName" method seems to be called first in the log? I thought the "numberOfMessagesInConversation" method would run first.
My new code after feedback from Alex Mamo:
public class ChatCloudSentMessageToFirestore {
private static final String TAG = "saveMessageSent";
int messageCounter;
// Initialize Firebase Firestore Database
private FirebaseFirestore db = FirebaseFirestore.getInstance();
// Initialize Firebase Authentification
private FirebaseAuth mAuth = FirebaseAuth.getInstance();
String userID = getCurrentFirebaseUserId();
public String getCurrentFirebaseUserId() {
FirebaseUser user = mAuth.getCurrentUser();
String userID = user.getUid();
return userID;
}
//Specified conversation
CollectionReference messageCollectionRef = db.collection("users")
.document(userID)
.collection("conversations")
.document("conversation0") //Specified conversation
.collection("messages");
private interface FirestoreCallback {
void onCallback(int messageCounter);
}
private void readData(FirestoreCallback firestoreCallback) {
messageCollectionRef.addSnapshotListener(new EventListener<QuerySnapshot>() {
#Override
public void onEvent(QuerySnapshot documentSnapshots, FirebaseFirestoreException e) {
messageCounter = documentSnapshots.size();
messageCounter++;
}
});
firestoreCallback.onCallback(messageCounter);
}
public int getMessageCount() {
readData(new FirestoreCallback() {
#Override
public void onCallback(int messageCounter) {
}
});
return messageCounter;
}
public void addSentMessageToFirestoreDB(final Map<String, Object> messageSent) {
WriteBatch batch = db.batch();
DocumentReference chrisSentMessageRef = db.collection("users")
.document("ypiXrobQxuZ0wplN5KO8gJR7Z4w1")
.collection("conversations")
.document("conversation0") //Specified conversation
.collection("messages")
.document("message" + Integer.toString(getMessageCount()) + " (sent)");
DocumentReference friendSentMessageRef = db.collection("users")
.document("LnUDNBVLW3PM7Dd7dbVJgwLzPe03")
.collection("conversations")
.document("conversation0")
.collection("messages")
.document("message" + Integer.toString(getMessageCount()) + " (sent)");
batch.set(chrisSentMessageRef, messageSent);
batch.set(friendSentMessageRef, messageSent);
batch.commit().addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
;
}
});
}
And I'm calling the addSentMessageToFirestoreDB method from a fragment like so:
//Writes the message to the database only
sendMessageButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
String textMessage = editTextChatInputBox.getText().toString();
if (textMessage.length() != 0) {
FirebaseUser user = mAuth.getCurrentUser();
String userID = user.getUid();
Map<String, Object> textMessageHashmap = new HashMap<>();
textMessageHashmap.put("From user with ID", userID);
textMessageHashmap.put("Message", textMessage);
textMessageHashmap.put("Message number in conversation", chatCloudSentMessageToFirestore.getMessageCount()
);
chatCloudSentMessageToFirestore.addSentMessageToFirestoreDB(textMessageHashmap);
}
editTextChatInputBox.setText("");
}
});
return rootView;
You cannot use something now, that hasn't been loaded yet. With other words, you cannot simply make the the counter global and use it outside the onEvent() method because it will always be null due the asynchronous behaviour of this method. This means that by the time you are trying to use that result outside that method, the data hasn't finished loading yet from the database and that's why is not accessible.
A quick solve for this problem would be to pass the counter variable as an argument ot the createAndReturnMessageSentNumberStringForDocName() method or to use it only inside the onEvent() method.
I thought the "numberOfMessagesInConversation" method would run first.
No it won't. The reson is because the this asynchronous behaviour.
If you want to use it outsise, I recommend you see the last part of my anwser from this post in which I have explained how it can be done using a custom callback. You can also take a look at this video for a better understanding.
I just want to post an update to my own question regarding how I proceeded and solved the original issue.
I asked Firebase directly and they advised me to use Distributed Counters / Shards.
Here's the documentation for Distributed Counters
This worked.
i have below code that gets executed when an admin is creating or deleting a user in the keycloak UI.
Through the help of the adminEvent: http://www.keycloak.org/docs/3.0/server_admin/topics/events/admin.html
Creating a user returns the user details via adminEvent.getRepresentation().
However when deleting a user returns me a null.
This is also the same when deleting a role, deleting a group or deleting a user_session.(ResourceTypes)
My question is how can i retrieve the deleted details?
import org.keycloak.events.admin.AdminEvent;
import org.keycloak.models.UserModel;
public void handleResourceOperation(AdminEvent adminEvent, UserModel user) {
MQMessage queueMessage = new MQMessage();
queueMessage.setIpAddress(adminEvent.getAuthDetails().getIpAddress());
queueMessage.setUsername(user.getUsername());
switch (adminEvent.getOperationType()) {
case CREATE:
LOGGER.info("OPERATION : CREATE USER");
LOGGER.info("USER Representation : " + adminEvent.getRepresentation());
String[] split = adminEvent.getRepresentation().split(",");
queueMessage.setTransactionDetail("Created user " + split[0].substring(12));
sendQueueMessage(adminEvent, queueMessage);
break;
case DELETE:
LOGGER.info("OPERATION : DELETE USER");
LOGGER.info("USER Representation : " + adminEvent.getRepresentation());
queueMessage.setTransactionDetail("User has been deleted.");
sendQueueMessage(adminEvent, queueMessage);
break;
}
I'm not sure you got the answer by now. Sharing the solution that may be helpful for others. User details can be captured in postInit method of EventListenerProviderFactory as below,
public class UserEventListenerProviderFactory implements EventListenerProviderFactory {
#Override
public EventListenerProvider create(KeycloakSession keycloakSession) {
return new UserEventListenerProvider(keycloakSession);
}
#Override
public void init(Config.Scope scope) {
}
#Override
public void postInit(KeycloakSessionFactory keycloakSessionFactory) {
keycloakSessionFactory.register(
(event) -> {
if (event instanceof UserModel.UserRemovedEvent) {
UserModel.UserRemovedEvent dEvent = (UserModel.UserRemovedEvent) event;
//TODO YOUR LOGIC WITH `dEvent.getUser()`
}
});
}
#Override
public void close() {
}
#Override
public String getId() {
return "sample_event_listener";
}
}
I'm trying to use Vaadin 8 FileDropTarget with a Tomcat server. The example code is little bit long, but maybe it will fit here:
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.OutputStream;
import com.vaadin.server.StreamVariable;
import com.vaadin.ui.Alignment;
import com.vaadin.ui.Label;
import com.vaadin.ui.Notification;
import com.vaadin.ui.ProgressBar;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.dnd.FileDropTarget;
import hu.dynaxpress.base.mvc.DynaAbstractController;
#SuppressWarnings({ "serial", "rawtypes" })
public class FileStreamController extends DynaAbstractController {
private VerticalLayout layout;
private ProgressBar progress;
#Override
protected void onInit() {
super.onInit();
System.err.println("FileStreamController.onInit");
}
#Override
protected void onEnter() {
System.err.println("FileStreamController.onEnter");
// https://vaadin.com/docs/v8/framework/components/components-upload.html
layout = new VerticalLayout();
final Label infoLabel = new Label("DROP A FILE ON THIS TEXT");
infoLabel.setWidth(240.0f, Unit.PIXELS);
final VerticalLayout dropPane = new VerticalLayout(infoLabel);
dropPane.setComponentAlignment(infoLabel, Alignment.MIDDLE_CENTER);
dropPane.addStyleName("drop-area");
dropPane.setSizeUndefined();
dropPane.setWidth("100%");
progress = new ProgressBar();
progress.setIndeterminate(false);
progress.setVisible(false);
progress.setWidth("100%");
dropPane.addComponent(progress);
layout.addComponent(dropPane);
new FileDropTarget<>(dropPane, fileDropEvent -> {
final long fileSizeLimit = 20 * 1024 * 1024 * 1024; // 20GB
fileDropEvent.getFiles().forEach(html5File -> {
// html5File.getFileSize() always returns zero, but why?
if (false && ( html5File.getFileSize() > fileSizeLimit) ) {
Notification.show(
"File rejected. Max size=" +fileSizeLimit+ ", actual="+html5File.getFileSize(),
Notification.Type.WARNING_MESSAGE);
} else {
Label lbl = new Label(html5File.getFileName() + " size=" + html5File.getFileSize() + " " + html5File.getType());
lbl.setWidth("100%");
layout.addComponent(lbl);
final StreamVariable streamVariable = new StreamVariable() {
#Override
public OutputStream getOutputStream() {
try {
return new FileOutputStream("F:\\"+html5File.getFileName());
} catch (FileNotFoundException e) {
e.printStackTrace();
throw new Error("F:\\"+html5File.getFileName());
}
}
#Override
public boolean listenProgress() { return false; }
#Override
public void onProgress(final StreamingProgressEvent event) {
progress.setValue(
event.getBytesReceived() / event.getContentLength()
);
}
#Override
public void streamingStarted(
final StreamingStartEvent event) {
progress.setVisible(true);
progress.setCaption(event.getFileName());
}
#Override
public void streamingFinished(
final StreamingEndEvent event) {
progress.setVisible(false);
}
#Override
public void streamingFailed(final StreamingErrorEvent event) {
progress.setVisible(false);
}
#Override
public boolean isInterrupted() { return false; }
};
html5File.setStreamVariable(streamVariable);
progress.setVisible(true);
}
});
});
setCompositionRoot(layout);
}
}
This code works, but it has terrible performance. In this MWE, the program writes all dropped files to the F:\ drive. It is a flash drive (I wanted to simulate streaming large files and see how much memory is consumed over time). But I found that this program writes out a 40MB file in 20 seconds. That is 2MB/sec speed. The server runs on localhost, the source file and the target F: drive are all on my local machine. If I simply copy the same file to the drive then it takes less than 2 seconds.
I have tried with another (very slow) flash drive too, and had similar results. When tomcat is streaming the file, then it is about 10-20 times slower.
Is there a way to make this faster? What am I doing wrong? I do not see any way to speed this up, because the streaming happens inside the component.
The second important (no related) question is that why can't I get the file size? See the comment in the code: "html5File.getFileSize() always returns zero, but why?" I have checked the POST headers from firefox debugger, and the browser sends the size of the file in an JSON/RPC call, before it sends the actual file data. So the server should know the file size before the first byte arrives.
I would like to add a GWT autosuggest textbox in JSP.
Could someone provide some insight into this?
Thanks
Typically GWT is considered a web application framework which is different to a widget framework. Personally I would consider GWT too heavy to just add an autosuggest to a simple web page and instead use something like jQuery autocomplete.
Having said that, there's nothing magical about running GWT code. Follow GWT standard module layout and just set up your JSP-page as a GWT host page where you alter the paths to be absolute to your compiled module.
Here an example of how I was able to get a suggest box to work. I make an RPC call to the database while the user is typing.
I agree that you could do something similar in jQuery but why would you when GWT has the widget available?
Hope this helps!
vendorSuggestBox = new SuggestBox(new SuggestionOracle()); //client package
public class SuggestionOracle extends SuggestOracle { //shared package
public boolean isDisplayStringHTML() {
return true;
}
#SuppressWarnings("unchecked")
public void requestSuggestions(Request request, Callback callback) {
ItemMovementRemoteServiceAsync service=GWT.create(ItemMovementRemoteService.class);
service.getVendors(request, new SuggestionCallback(request,callback));
}
#SuppressWarnings("unchecked")
class SuggestionCallback implements AsyncCallback {
private SuggestOracle.Request req;
private SuggestOracle.Callback callback;
public SuggestionCallback(SuggestOracle.Request _req, SuggestOracle.Callback _callback) {
req=_req;
callback=_callback;
}
public void onFailure(Throwable caught) {
callback.onSuggestionsReady(req, new SuggestOracle.Response());
}
public void onSuccess(Object result) {
callback.onSuggestionsReady(req, (SuggestOracle.Response) result);
}
}
public SuggestOracle.Response getVendors(Request req) { //server package
Connection db=null;
PreparedStatement ps=null;
ResultSet rs=null;
SuggestOracle.Response resp = new SuggestOracle.Response();
List<Suggestion> suggestions=new ArrayList<Suggestion>();
int count=0;
try {
db=Database.open("ACM0");
ps=db.prepareStatement(
" SELECT VE_CD,upper(VE_NAME) VE_NAME" +
" FROM AP.VE_WEB " +
" WHERE (VE_NAME NOT LIKE 'AC Moore%') " +
" AND (lower(VE_NAME) LIKE ? OR VE_CD LIKE ?)" +
" ORDER BY VE_NAME");
ps.setString(1, "%"+req.getQuery().toLowerCase()+"%");
ps.setString(2, "%"+req.getQuery().toLowerCase()+"%");
rs=ps.executeQuery();
while(rs.next() && count < 25) {
suggestions.add(new ASuggestion(rs.getString("VE_NAME").trim()+"-"+rs.getString("VE_CD").trim()));
count++;
}
resp.setSuggestions(suggestions);
} catch (Exception ex) {
ex.printStackTrace();
} finally {
Database.close(db);
}
return resp;
}
public class ASuggestion implements IsSerializable, Suggestion { //shared package model object
private String s;
public ASuggestion(){}
public ASuggestion(String s) {
this.s=s;
}
public String getDisplayString() {
return s;
}
public String getReplacementString() {
return s;
}