I'm developing a realtime notification system through WebSockets by using Spring 4.
The source code is as follows:
WebSocketConfig:
#Configuration
#EnableScheduling
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/lrt").withSockJS();
}
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/queue/", "/topic/");
registry.setApplicationDestinationPrefixes("/app");
}
}
LRTStatusListener:
#Service
public class LRTStatusListener implements ApplicationListener<BrokerAvailabilityEvent>{
private static final Logger LOG = LoggerFactory.getLogger(LRTStatusListener.class);
private final static long LRT_ID = 1234567890;
private final static String LRT_OWNER = "Walter White";
private final LRTStatusGenerator lrtStatusGenerator = new LRTStatusGenerator(LRT_ID, LRT_OWNER);
private final MessageSendingOperations<String> messagingTemplate;
private AtomicBoolean brokerAvailable = new AtomicBoolean();
#Autowired
public LRTStatusListener(MessageSendingOperations<String> messagingTemplate) {
this.messagingTemplate = messagingTemplate;
}
#Override
public void onApplicationEvent(BrokerAvailabilityEvent event) {
this.brokerAvailable.set(event.isBrokerAvailable());
}
#Scheduled(fixedDelay=2000)
public void sendLRTStatus() {
LRTStatus lrtStatus = this.lrtStatusGenerator.generateLRTStatus();
if (LOG.isTraceEnabled())
LOG.trace("Sending LRT status");
if (this.brokerAvailable.get())
this.messagingTemplate
.convertAndSend("/topic/status" + lrtStatus.getLRTId(), lrtStatus);
}
// Random status generator
private static class LRTStatusGenerator {
private LRTStatus lrtStatus;
public LRTStatusGenerator(long lrtId, String owner) {
lrtStatus = new LRTStatus(lrtId, owner, getCurrentTimestamp(), generateLRTStatusMessage());
}
public LRTStatus generateLRTStatus() {
lrtStatus.setMessage(generateLRTStatusMessage());
return lrtStatus;
}
private String getCurrentTimestamp() {
Date date = new Date();
Timestamp timestamp = new Timestamp(date.getTime());
return timestamp.toString();
}
private String generateLRTStatusMessage() {
String statusMessage;
switch ((int) Math.random() * 2) {
case 1:
statusMessage =
"HANK: What? You want me to beg? You're the smartest guy I ever met. " +
"And you're too stupid to see... he made up his mind ten minutes ago.";
break;
case 2:
statusMessage =
"WALTER: That's right. Now say my name. " +
"- DECLAN: ...You're Heisenberg. - WALTER: You're goddamn right.";
break;
default:
statusMessage =
"WALTER: I am not in danger, Skyler. I am the danger! " +
"A guy opens his door and gets shot and you think that of me? " +
"No. I am the one who knocks!";
break;
}
return statusMessage;
}
}
}
CheckLRTStatusController
#Controller
public class CheckLRTStatusController {
#MessageExceptionHandler
#SendToUser("/topic/errors")
public String handleException(Throwable exception) {
return exception.getMessage();
}
}
The application simulates the status of a long running transaction (LRT), by changing its info every 2000ms.
Now, I'm testing the WebSocket by defining a client via SockJS:
<script src="http://cdn.sockjs.org/sockjs-0.3.min.js"></script>
<script>
var sock = new SockJS('/lrt');
sock.onopen = function() {
console.log('open');
};
sock.onmessage = function(e) {
console.log('message', e.data);
};
sock.onclose = function() {
console.log('close');
};
</script>
The connection works fine, but I'm unable to see the data stream.
How can I properly configure my application in order to produce and then route on my client's console the messages sent by the WebSocket Server?
Note that I'm also using a build-in Message Broker with the aim to manage the message queue.
Is this the only JavaScript code you currently have?:
<script src="http://cdn.sockjs.org/sockjs-0.3.min.js"></script>
<script>
var sock = new SockJS('/lrt');
sock.onopen = function() {
console.log('open');
};
sock.onmessage = function(e) {
console.log('message', e.data);
};
sock.onclose = function() {
console.log('close');
};
</script>
That only sets up the connection with fallback on SockJS but you are not subscribing to the message broker. You need to do that too.
In your current setup you have:
registry.enableSimpleBroker("/queue/", "/topic/");
You need to create a JavaScript STOMP client (over SockJS) that subscribes for those, something like:
stompClient.subscribe("/topic/status*", function(message) {
...
});
stompClient.subscribe("/queue/whatever", function(message) {
...
});
Have a look at the spring-websocket-portfolio application for a complete working example.
Related
I'm trying to follow a guide to WebSockets at https://www.devglan.com/spring-boot/spring-boot-angular-websocket
I'd like it to respond to ws://localhost:8448/wsb/softlayer-cost-file, but I'm sure I misunderstood something. I'd like to get it to receive a binary file and issue periodic updates as the file is being processed.
Questions are:
How come Spring does not respond to my requests despite all the multiple URLs I try (see below).
Does my RxJS call run once and then conclude, or does it keep running until some closure has happened? Sorry to ask what might be obvious to others.
On my Spring Boot Server start, I see no errors. After about 5-7 minutes of running, I saw the following log message:
INFO o.s.w.s.c.WebSocketMessageBrokerStats - WebSocketSession[0 current WS(0)-HttpStream(0)-HttpPoll(0), 0 total, 0 closed abnormally (0 connect failure, 0 send limit, 0 transport error)], stompSubProtocol[processed CONNECT(0)-CONNECTED(0)-DISCONNECT(0)], stompBrokerRelay[null], inboundChannel[pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 0], outboundChannel[pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 0], sockJsScheduler[pool size = 6, active threads = 1, queued tasks = 0, completed tasks = 5]
I've pointed my browser at these URLs and can't get the Spring Boot server to show any reaction:
ws://localhost:8448/app/message
ws://localhost:8448/greeting/app/message
ws://localhost:8448/topic
ws://localhost:8448/queue
(I got the initial request formed in Firefox, then clicked edit/resend to try again).
WebSocketConfig.java
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Autowired
CostFileUploadWebSocketHandler costFileUploadWebSocketHandler;
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new SocketTextHandler(), "/wst");
registry.addHandler(costFileUploadWebSocketHandler, "/wsb/softlayer-cost-file");
}
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic/", "/queue/");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/greeting").setAllowedOrigins("*");
// .withSockJS();
}
}
CostFileUploadWebSocketHandler.java
#Component
public class CostFileUploadWebSocketHandler extends BinaryWebSocketHandler {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private SoftLayerJobService softLayerJobService;
private SoftLayerService softLayerService;
private AuthenticationFacade authenticationFacade;
#Autowired
CostFileUploadWebSocketHandler(SoftLayerJobService softLayerJobService, SoftLayerService softLayerService,
AuthenticationFacade authenticationFacade) {
this.softLayerJobService = softLayerJobService;
this.softLayerService = softLayerService;
this.authenticationFacade = authenticationFacade;
}
Map<WebSocketSession, FileUploadInFlight> sessionToFileMap = new WeakHashMap<>();
#Override
public boolean supportsPartialMessages() {
return true;
}
class WebSocketProgressReporter implements ProgressReporter {
private WebSocketSession session;
public WebSocketProgressReporter(WebSocketSession session) {
this.session = session;
}
#Override
public void reportCurrentProgress(BatchStatus currentBatchStatus, long currentPercentage) {
try {
session.sendMessage(new TextMessage("BatchStatus "+currentBatchStatus));
session.sendMessage(new TextMessage("Percentage Complete "+currentPercentage));
} catch(IOException e) {
throw new RuntimeException(e);
}
}
}
#Override
protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) throws Exception {
ByteBuffer payload = message.getPayload();
FileUploadInFlight inflightUpload = sessionToFileMap.get(session);
if (inflightUpload == null) {
throw new IllegalStateException("This is not expected");
}
inflightUpload.append(payload);
if (message.isLast()) {
File fileNameSaved = save(inflightUpload.name, "websocket", inflightUpload.bos.toByteArray());
BatchStatus currentBatchStatus = BatchStatus.UNKNOWN;
long percentageComplete;
ProgressReporter progressReporter = new WebSocketProgressReporter(session);
SoftLayerCostFileJobExecutionThread softLayerCostFileJobExecutionThread =
new SoftLayerCostFileJobExecutionThread(softLayerService, softLayerJobService, fileNameSaved,progressReporter);
logger.info("In main thread about to begin separate thread");
ForkJoinPool.commonPool().submit(softLayerCostFileJobExecutionThread);
while(!softLayerCostFileJobExecutionThread.jobDone());
// softLayerCostFileJobExecutionThread.run();
// Wait for above to complete somehow
// StepExecution foundStepExecution = jobExplorer.getJobExecution(
// jobExecutionThread.getJobExecutionResult().getJobExecution().getId()
// ).getStepExecutions().stream().filter(stepExecution->stepExecution.getStepName().equals("softlayerUploadFile")).findFirst().orElseGet(null);
// if (!"COMPLETED".equals(jobExecutionResult.getExitStatus())) {
// throw new UploadFileException(file.getOriginalFilename() + " exit status: " + jobExecutionResult.getExitStatus());
// }
logger.info("In main thread after separate thread submitted");
session.sendMessage(new TextMessage("UPLOAD "+inflightUpload.name));
session.close();
sessionToFileMap.remove(session);
logger.info("Uploaded "+inflightUpload.name);
}
String response = "Upload Chunk: size "+ payload.array().length;
logger.debug(response);
}
private File save(String fileName, String prefix, byte[] data) throws IOException {
Path basePath = Paths.get(".", "uploads", prefix, UUID.randomUUID().toString());
logger.info("Saving incoming cost file "+fileName+" to "+basePath);
Files.createDirectories(basePath);
FileChannel channel = new FileOutputStream(Paths.get(basePath.toString(), fileName).toFile(), false).getChannel();
channel.write(ByteBuffer.wrap(data));
channel.close();
return new File(basePath.getFileName().toString());
}
#Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
sessionToFileMap.put(session, new FileUploadInFlight(session));
}
static class FileUploadInFlight {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
String name;
String uniqueUploadId;
ByteArrayOutputStream bos = new ByteArrayOutputStream();
/**
* Fragile constructor - beware not prod ready
* #param session
*/
FileUploadInFlight(WebSocketSession session) {
String query = session.getUri().getQuery();
String uploadSessionIdBase64 = query.split("=")[1];
String uploadSessionId = new String(Base64Utils.decodeUrlSafe(uploadSessionIdBase64.getBytes()));
List<String> sessionIdentifiers = Splitter.on("\\").splitToList(uploadSessionId);
String uniqueUploadId = session.getRemoteAddress().toString()+sessionIdentifiers.get(0);
String fileName = sessionIdentifiers.get(1);
this.name = fileName;
this.uniqueUploadId = uniqueUploadId;
logger.info("Preparing upload for "+this.name+" uploadSessionId "+uploadSessionId);
}
public void append(ByteBuffer byteBuffer) throws IOException{
bos.write(byteBuffer.array());
}
}
}
Below is a snippet of Angular code where I make the call to the websocket. The service is intended to receive a file, then provide regular updates of percentage complete until the service is completed. Does this call need to be in a loop, or does the socket run until it's closed?
Angular Snippet of call to WebSocket:
this.softlayerService.uploadBlueReportFile(this.blueReportFile)
.subscribe(data => {
this.showLoaderBlueReport = false;
this.successBlueReport = true;
this.blueReportFileName = "No file selected";
this.responseBlueReport = 'File '.concat(data.fileName).concat(' ').concat('is ').concat(data.exitStatus);
this.blueReportSelected = false;
this.getCurrentUserFiles();
},
(error)=>{
if(error.status === 504){
this.showLoaderBlueReport = false;
this.stillProcessing = true;
}else{
this.showLoaderBlueReport = false;
this.displayUploadBlueReportsError(error, 'File upload failed');
}
});
}
I need to call an API every 30 seconds and need to refresh the grid with updated data. I using Server push, but I can't find the optimal solution in my case. Below is my UI code
#Route(value = UserNavigation.ROUTE)
#PreserveOnRefresh
#org.springframework.stereotype.Component
public class UserNavigation extends AppLayout {
CCUIConfig config;
#Autowired
public UserNavigation( CCUIConfig config) {
this.config = config;
addToDrawer(createAccordianMenu());
}
private Component createAccordianMenu() {
VerticalLayout scrollableLayout = new VerticalLayout();
Div kioskMonitor_div = new Div(kiosksMonitorLabel);
kioskMonitor_div.addClickListener(event -> {
try {
setContent(new Monitor(config).getDashboard());
} catch (Exception e) {
e.printStackTrace();
}
});
scrollableLayout.add(kioskMonitor_div);
return scrollableLayout;
}
}
#Push
#Route
public class Monitor extends VerticalLayout{
CCUIConfig config;
Grid<Model> grid = new Grid<>();
private FeederThread thread;
public Monitor(CCUIConfig config) {
this.config = config;
}
#Override
protected void onAttach(AttachEvent attachEvent) {
// Start the data feed thread
super.onAttach(attachEvent);
thread = new FeederThread(attachEvent.getUI(),grid);
thread.start();
}
#Override
protected void onDetach(DetachEvent detachEvent) {
thread.interrupt();
thread = null;
}
public Component getDashboard() throws IOException{
String updateddate =null;
VerticalLayout dashboardview = new VerticalLayout();
Grid.Column<Model> idColumn = grid.addColumn(Model::getid)
.setHeader(ID);
Grid.Column<Model> nameColumn = grid.addColumn(Model::getName)
.setHeader(Name);
Grid.Column<Model> memoryColumn = grid.addColumn(Model::getRefreshTime)
.setHeader(Refresh time"));
dashboardview.add(grid);
return dashboardview;
}
}
class FeederThread extends Thread {
private final com.vaadin.flow.component.UI ui;
private final Grid<Model> grid;
private final CCUIConfig config;
private int count = 30;
public FeederThread(com.vaadin.flow.component.UI ui,Grid<Model> grid) {
this.config = new CCUIConfig();
this.ui = ui;
this.grid= grid;
}
#Override
public void run() {
while (count>0){
try {
Thread.sleep(1000);
ui.access(()-> {
System.out.println("Thread Entry ");
try {
StringBuilder jsongrid = HttpClientGetRequestClient.executeUrlGet(config.getRefreshAPI());
ObjectMapper mapper = new ObjectMapper();
List<Model> userlist = new ArrayList<Model>();
String date="";
if(jsongrid!=null)
{
ModelWrapper eh = mapper.readValue(jsongrid.toString(), ModelWrapper.class);
userlist = eh.getMetricsData();
}
if(userlist != null)
{
grid.setItems(userlist);
grid.getDataProvider().refreshAll();
}
}catch(JSONException |JsonProcessingException e) {
e.printStackTrace();
}
});
count--;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
UserNavigation is the page that loads after user login, it contains a sidebar with multiple options(or functionalities). One of the functionality is the Monitor screen.
Monitor is the page with a grid where we need to refresh it in 30 seconds.
FeederThread is the thread class that calls an API and update the data in the grid asynchronously.
As from the above code, what happens is like
onAttach(AttachEvent attachEvent)
is not getting executed so the grid is not getting displayed on the page, we use vaadin 14, any help will be appreciated.
This is about a spring boot + angularjs web application which uses websocket + stompjs to send push notifications.
I upgraded from Spring boot 1.2.0 to 2.1.3 recently. Before this upgrade websocket (push notifications) was working fine for couple of years.
I just upgraded spring boot and websocket related code remains exactly same, but it is not working now.
Not working means:
Below line executed at server side without any error/exception.
simpMessagingTemplate.convertAndSend("/topic/notify", payload);
Chrome debugger receives only "h" (heartbeat) but not the actual message.
I have no clue because,
server side code executed successfully till the last line.
websocket session established, I can get heartbeat messages, but no error at client side as well.
Code (but this same code works well with Spring boot 1.2.0:
1. Config:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Value("${server.sessionTimeout}")
long sessionTimeoutInSecs;
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/notify").withSockJS();
}
#Bean
public ServletServerContainerFactoryBean createWebSocketContainer() {
ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
// in milliseconds
container.setMaxSessionIdleTimeout(sessionTimeoutInSecs * 1000);
return container;
}
}
2. Message sending code:
simpMessagingTemplate.convertAndSend("/topic/notify", payload);
3. Client code:
(function() {
myApp.factory('autoUpdateTasksService', function($resource, $q, $log) {
var initSockets, notify, reconnect, socket, _callback;
_callback = null;
socket = {
client: null,
stomp: null
};
initSockets = function() {
socket.client = new SockJS('/notify');
socket.stomp = Stomp.over(socket.client);
socket.stomp.connect({}, function() {});
socket.client.onopen = function() {
var subscription1;
subscription1 = socket.stomp.subscribe("/topic/notify", notify);
//$log.log('socket connected');
};
};
reconnect = function() {
setTimeout(initSockets, 1000);
};
notify = function(message) {
try{
var taskNotifyObject;
if (message.body) {
taskNotifyObject = angular.fromJson(message.body);
//$log.log(taskNotifyObject);
var notificationArray=[];
notificationArray.push(taskNotifyObject);
_callback(notificationArray);
} else {
//$log.log("empty message");
}
} catch(e){
// alert(e.message);
}
};
return {
init: function(callback) {
_callback = callback;
initSockets();
}
};
});
}).call(this);
Is anything changed in spring framework between versions?
How I can debug/find where the message is lost?
Rootcause: AFTER the upgrade, the code in my question was failed to create connection between server and client ( failed to created websocketSession).
Changing code as below solves the problem, but I am NOT sure why this solution is working,
if anyone explains why this solution is working, It would be a great help.
1. Config:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Value("${server.servlet.session.timeout}")
long sessionTimeoutInSecs;
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/queue");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/notify").addInterceptors(new HttpSessionHandshakeInterceptor());
}
#Bean
public ServletServerContainerFactoryBean createWebSocketContainer() {
ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
// in milliseconds
container.setMaxSessionIdleTimeout(sessionTimeoutInSecs * 1000);
return container;
}
/**
* DefaultSimpUserRegistry is the replacement of MySessionRegistry ( Custom UserSessionRegistry ) after upgrade to Spring 5.
* Below required with Spring 4.
* import org.springframework.messaging.simp.user.UserSessionRegistry;
#Repository
public class MySessionRegistry implements UserSessionRegistry, ApplicationListener<AbstractSubProtocolEvent> {
*
*/
#Bean
public DefaultSimpUserRegistry defaultSimpUserRegistry() {
DefaultSimpUserRegistry userRegistry = new DefaultSimpUserRegistry();
return userRegistry;
}
}
2. Message sending code:
import org.springframework.web.socket.messaging.DefaultSimpUserRegistry;
#Autowired
DefaultSimpUserRegistry defaultSimpUserRegistry;
.....
SimpUser simpUser = defaultSimpUserRegistry.getUser(payload.getUserName());
if(simpUser != null && simpUser.hasSessions()) {
template.convertAndSendToUser(payload.getUserName(), "/queue/notify", payload);
}
3. Client code:
(function() {
myApp.factory('autoUpdateTasksService', function($resource, $q, $log) {
var initSockets, notify, reconnect, socket, _callback;
_callback = null;
socket = {
client: null,
stomp: null
};
getContextPath = function() {
return window.location.pathname.substring(0, window.location.pathname.indexOf("/",2));
};
initSockets = function() {
//socket.addr = "wss://" + window.location.host + "/notify";
socket.addr = ((window.location.protocol && (window.location.protocol.indexOf("https") >= 0)) ? "wss://" : "ws://") + window.location.host + getContextPath() + "/notify";
socket.client = Stomp.client(socket.addr); //new SockJS('/notify');
socket.client.connect({}, function () {
$log.log("Connected to websocket through " + socket.addr);
socket.client.subscribe("/user/queue/notify", notify);
}, function (err) {
$log.log("Error when connection to websocket " + socket.addr + ".\n" + err);
});
};
How I can debug/find where the message is lost?
To validate client-server connectivity ( or creation of websocketSession), I added below listener.
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.web.socket.messaging.SessionConnectedEvent;
import org.springframework.web.socket.messaging.SessionSubscribeEvent;
#Component
public class WebSocketListener implements ApplicationListener <ApplicationEvent> {
//WebSocket session created
if (appEvent instanceof SessionConnectedEvent){
StompHeaderAccessor sha = StompHeaderAccessor.wrap(((SessionConnectedEvent) appEvent).getMessage());
logger.info("SessionConnectedEvent: STOMP WebSocket session created for the user: {}", sha.getUser().getName());
}
//subscribed to websocketSession
if (appEvent instanceof SessionSubscribeEvent){
StompHeaderAccessor sha = StompHeaderAccessor.wrap(((SessionSubscribeEvent) appEvent).getMessage());
logger.info("SessionSubscribeEvent: User {} subscribed to WebSocket session, destination: {}", sha.getUser().getName(), sha.getDestination());
}
//
// if (appEvent instanceof BrokerAvailabilityEvent){
// logger.info("BrokerAvailabilityEvent: {}", appEvent.toString());
// }
}
}
We have a GWT application which crashes in Firefox versions 21 and above, including in the latest version 23.0.1. In earlier versions of Firefox and IE 9, it works fine. This is in deployed mode and not because of the GWT plugin. The situation it crashes is when there are huge number of RPC calls, may be around 300 to 400.
As the application in which it happens is fairly complex, I tried to simulate this issue with a simple prototype. I observed that my prototype crashes when the number of RPC calls reach 100000. But this scenario is very unlikely in my application where RPC calls are around 300-400 as observed using Firebug.
I am trying to find out what else I am missing in my prototype so that it also crashes with 300-400 RPC calls.
GWT version - 2.4
GXT version - 2.2.5
package com.ganesh.check.firefox.client;
public class FirefoxCrash implements EntryPoint {
private static final String SERVER_ERROR = "An error occurred while "
+ "attempting to contact the server. Please check your network "
+ "connection and try again.";
private final GreetingServiceAsync greetingService = GWT
.create(GreetingService.class);
public native static void consoleLog(String text)/*-{
$wnd.console.log(text);
}-*/;
public void onModuleLoad() {
final Button sendButton = new Button("Send");
final TextBox nameField = new TextBox();
nameField.setText("GWT User");
final Label errorLabel = new Label();
final Label countLabel = new Label();
// We can add style names to widgets
sendButton.addStyleName("sendButton");
// Add the nameField and sendButton to the RootPanel
// Use RootPanel.get() to get the entire body element
RootPanel.get("nameFieldContainer").add(nameField);
RootPanel.get("sendButtonContainer").add(sendButton);
RootPanel.get("errorLabelContainer").add(errorLabel);
RootPanel.get("count").add(countLabel);
// Focus the cursor on the name field when the app loads
nameField.setFocus(true);
nameField.selectAll();
// Create the popup dialog box
final DialogBox dialogBox = new DialogBox();
dialogBox.setText("Remote Procedure Call");
dialogBox.setAnimationEnabled(true);
final Button closeButton = new Button("Close");
// We can set the id of a widget by accessing its Element
closeButton.getElement().setId("closeButton");
final Label textToServerLabel = new Label();
final HTML serverResponseLabel = new HTML();
VerticalPanel dialogVPanel = new VerticalPanel();
dialogVPanel.addStyleName("dialogVPanel");
dialogVPanel.add(new HTML("<b>Sending name to the server:</b>"));
dialogVPanel.add(textToServerLabel);
dialogVPanel.add(new HTML("<br><b>Server replies:</b>"));
dialogVPanel.add(serverResponseLabel);
dialogVPanel.setHorizontalAlignment(VerticalPanel.ALIGN_RIGHT);
dialogVPanel.add(closeButton);
dialogBox.setWidget(dialogVPanel);
// Add a handler to close the DialogBox
closeButton.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
dialogBox.hide();
sendButton.setEnabled(true);
sendButton.setFocus(true);
}
});
class MyHandler implements ClickHandler, KeyUpHandler {
private int resultCount = 0;
/**
* Fired when the user clicks on the sendButton.
*/
public void onClick(ClickEvent event) {
sendNameToServer();
}
public void onKeyUp(KeyUpEvent event) {
if (event.getNativeKeyCode() == KeyCodes.KEY_ENTER) {
sendNameToServer();
}
}
private void sendNameToServer() {
// First, we validate the input.
errorLabel.setText("");
String textToServer = nameField.getText();
// Then, we send the input to the server.
textToServerLabel.setText(textToServer);
serverResponseLabel.setText("");
final int loopCount = Integer.parseInt(textToServer);
resultCount=0;
for (int i = 0; i < loopCount; i++) {
greetingService.getResult(textToServer,
new AsyncCallback<ResultBean>() {
public void onFailure(Throwable caught) {
consoleLog(caught.getMessage());
}
public void onSuccess(ResultBean result) {
//countLabel.setText(++resultCount + "");
resultCount++;
if(resultCount==loopCount){
countLabel.setText(resultCount + "");
}
consoleLog("Result returned for "+resultCount);
}
});
}
}
}
// Add a handler to send the name to the server
MyHandler handler = new MyHandler();
sendButton.addClickHandler(handler);
nameField.addKeyUpHandler(handler);
}
}
public class GreetingServiceImpl extends RemoteServiceServlet implements
GreetingService {
public ResultBean getResult(String name) {
ResultBean result = new ResultBean();
Random random = new Random();
int suffix = random.nextInt();
result.setName("Name "+suffix);
result.setAddress("Address "+suffix);
result.setZipCode(suffix);
result.setDoorNumber("Door "+suffix);
return result;
}
public class ResultBean implements Serializable {
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public int getZipCode() {
return zipCode;
}
public void setZipCode(int zipCode) {
this.zipCode = zipCode;
}
public String getDoorNumber() {
return doorNumber;
}
public void setDoorNumber(String doorNumber) {
this.doorNumber = doorNumber;
}
private String name;
private String address;
private int zipCode;
private String doorNumber;
}
Hi I'm trying to retrieve a linkedhashset from the Google datastore but nothing seems to happen. I want to display the results in a Grid using GWT on a page. I have put system.out.println() in all the classes to see where I go wrong but it only shows one and I don't recieve any errors. I use 6 classes 2 in the server package(ContactDAOJdo/ContactServiceImpl) and 4 in the client package(ContactService/ContactServiceAsync/ContactListDelegate/ContactListGui). I hope someone can explain why this isn't worken and point me in the right direction.
public class ContactDAOJdo implements ContactDAO {
#SuppressWarnings("unchecked")
#Override
public LinkedHashSet<Contact> listContacts() {
PersistenceManager pm = PmfSingleton.get().getPersistenceManager();
String query = "select from " + Contact.class.getName();
System.out.print("ContactDAOJdo: ");
return (LinkedHashSet<Contact>) pm.newQuery(query).execute();
}
}
public class ContactServiceImpl extends RemoteServiceServlet implements ContactService{
private static final long serialVersionUID = 1L;
private ContactDAO contactDAO = new ContactDAOJdo() {
#Override
public LinkedHashSet<Contact> listContacts() {
LinkedHashSet<Contact> contacts = contactDAO.listContacts();
System.out.println("service imp "+contacts);
return contacts;
}
}
#RemoteServiceRelativePath("contact")
public interface ContactService extends RemoteService {
LinkedHashSet<Contact> listContacts();
}
public interface ContactServiceAsync {
void listContacts(AsyncCallback<LinkedHashSet <Contact>> callback);
}
public class ListContactDelegate {
private ContactServiceAsync contactService = GWT.create(ContactService.class);
ListContactGUI gui;
void listContacts(){
contactService.listContacts(new AsyncCallback<LinkedHashSet<Contact>> () {
public void onFailure(Throwable caught) {
gui.service_eventListContactenFailed(caught);
System.out.println("delegate "+caught);
}
public void onSuccess(LinkedHashSet<Contact> result) {
gui.service_eventListRetrievedFromService(result);
System.out.println("delegate "+result);
}
});
}
}
public class ListContactGUI {
protected Grid contactlijst;
protected ListContactDelegate listContactService;
private Label status;
public void init() {
status = new Label();
contactlijst = new Grid();
contactlijst.setVisible(false);
status.setText("Contact list is being retrieved");
placeWidgets();
}
public void service_eventListRetrievedFromService(LinkedHashSet<Contact> result){
System.out.println("1 service eventListRetreivedFromService "+result);
status.setText("Retrieved contactlist list");
contactlijst.setVisible(true);
this.contactlijst.clear();
this.contactlijst.resizeRows(1 + result.size());
int row = 1;
this.contactlijst.setWidget(0, 0, new Label ("Voornaam"));
this.contactlijst.setWidget(0, 1, new Label ("Achternaam"));
for(Contact contact: result) {
this.contactlijst.setWidget(row, 0, new Label (contact.getVoornaam()));
this.contactlijst.setWidget(row, 1, new Label (contact.getVoornaam()));
row++;
System.out.println("voornaam: "+contact.getVoornaam());
}
System.out.println("2 service eventListRetreivedFromService "+result);
}
public void placeWidgets() {
System.out.println("placewidget inside listcontactgui" + contactlijst);
RootPanel.get("status").add(status);
RootPanel.get("contactlijst").add(contactlijst);
}
public void service_eventListContactenFailed(Throwable caught) {
status.setText("Unable to retrieve contact list from database.");
}
}
It could be the query returns a lazy list. Which means not all values are in the list at the moment the list is send to the client. I used a trick to just call size() on the list (not sure how I got to that solution, but seems to work):
public LinkedHashSet<Contact> listContacts() {
final PersistenceManager pm = PmfSingleton.get().getPersistenceManager();
try {
final LinkedHashSet<Contact> contacts =
(LinkedHashSet<Contact>) pm.newQuery(Contact.class).execute();
contacts.size(); // this triggers to get all values.
return contacts;
} finally {
pm.close();
}
}
But I'm not sure if this is the best practice...