1 | /** |
2 | * Copyright 2005-2011 Steve McDuff d-duff@users.sourceforge.net |
3 | * |
4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
5 | * you may not use this file except in compliance with the License. |
6 | * You may obtain a copy of the License at |
7 | * |
8 | * http://www.apache.org/licenses/LICENSE-2.0 |
9 | * |
10 | * Unless required by applicable law or agreed to in writing, software |
11 | * distributed under the License is distributed on an "AS IS" BASIS, |
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 | * See the License for the specific language governing permissions and |
14 | * limitations under the License. |
15 | */ |
16 | package org.deduced.viewer.web.shared; |
17 | |
18 | import java.io.Serializable; |
19 | import java.util.ArrayList; |
20 | import java.util.HashMap; |
21 | import java.util.Iterator; |
22 | import java.util.List; |
23 | import java.util.Map; |
24 | import java.util.Map.Entry; |
25 | import java.util.Set; |
26 | |
27 | import com.google.gwt.user.client.Timer; |
28 | import com.google.gwt.user.client.rpc.AsyncCallback; |
29 | import com.google.gwt.user.client.ui.Label; |
30 | import com.google.gwt.user.client.ui.RootPanel; |
31 | |
32 | /** |
33 | * The Model Registry is the central class on the client side. It registers all |
34 | * the models, dispatches change events and allows the models to reach back to |
35 | * the server. |
36 | * |
37 | * @author Steve McDuff |
38 | * |
39 | */ |
40 | public class ModelRegistry |
41 | { |
42 | /** |
43 | * amount of time to wait between server pooling request in milliseconds. |
44 | */ |
45 | public static final int SERVER_POLLING_LOOP_DELAY_IN_MILLISECONDS = 1000; |
46 | |
47 | /** |
48 | * maximum ignore event age in milliseconds |
49 | */ |
50 | private static final int MAXIMUM_IGNORE_EVENT_AGE_IN_MILLISECONDS = |
51 | 5 * 60 * 1000; |
52 | |
53 | /** |
54 | * map of all the registered models based on their unique ID key. |
55 | */ |
56 | private Map<String, Model> registry = new HashMap<String, Model>(); |
57 | |
58 | /** |
59 | * the User Interface Service used to call the server |
60 | */ |
61 | private DynamicUserInterfaceServiceAsync userInterfaceService; |
62 | |
63 | /** |
64 | * default void server call back |
65 | */ |
66 | private final ErrorReportCallback callBack = new ErrorReportCallback(); |
67 | |
68 | /** |
69 | * map of all the change events to ignore |
70 | */ |
71 | private Map<ChangeEvent, IgnoredEventInformation> ignoreChangeEventMap = |
72 | new HashMap<ChangeEvent, IgnoredEventInformation>(); |
73 | |
74 | /** |
75 | * map of all the update property that haven't been sent yet |
76 | */ |
77 | private Map<ChangeEvent, PendingChangeEvent> pendingChangeEventMap = |
78 | new HashMap<ChangeEvent, PendingChangeEvent>(); |
79 | |
80 | /** |
81 | * error label |
82 | */ |
83 | private Label errorLabel; |
84 | |
85 | /** |
86 | * Timer used to poll the server for updates. |
87 | */ |
88 | private Timer refreshTimer; |
89 | |
90 | /** |
91 | * root widget control |
92 | */ |
93 | private RootWidgetControl rootWidgetControl; |
94 | |
95 | /** |
96 | * is the get latest event method currently waiting for results from the |
97 | * server. |
98 | */ |
99 | private boolean getLatestEventsIsRunning = false; |
100 | |
101 | /** |
102 | * Should the getLatestEvent method be called when it finishes it's current |
103 | * execution. |
104 | */ |
105 | private boolean getLatestEventsRunAgain = false; |
106 | |
107 | /** |
108 | * ModelRegistry constructor |
109 | */ |
110 | public ModelRegistry() |
111 | { |
112 | createRefreshTimer(); |
113 | } |
114 | |
115 | /** |
116 | * get RootWidgetControl |
117 | * |
118 | * @return root widget control |
119 | */ |
120 | public RootWidgetControl getRootWidgetControl() |
121 | { |
122 | return rootWidgetControl; |
123 | } |
124 | |
125 | /** |
126 | * set RootWidgetControl |
127 | * |
128 | * @param setRootWidgetControl the new root widget control |
129 | */ |
130 | public void setRootWidgetControl( |
131 | RootWidgetControl setRootWidgetControl) |
132 | { |
133 | rootWidgetControl = setRootWidgetControl; |
134 | } |
135 | |
136 | /** |
137 | * start Server Polling Timer |
138 | */ |
139 | public void startServerPollingTimer() |
140 | { |
141 | Timer currentRefreshTimer = getRefreshTimer(); |
142 | if (currentRefreshTimer != null) |
143 | { |
144 | currentRefreshTimer |
145 | .schedule(SERVER_POLLING_LOOP_DELAY_IN_MILLISECONDS); |
146 | } |
147 | } |
148 | |
149 | /** |
150 | * get Initial User Interface |
151 | */ |
152 | public void getInitialUserInterface() |
153 | { |
154 | getUserInterfaceService().getInitialUserInterface( |
155 | new AsyncCallback<ViewModel>() |
156 | { |
157 | @Override |
158 | public void onSuccess( |
159 | ViewModel result) |
160 | { |
161 | initializeUserInterface(result); |
162 | } |
163 | |
164 | @Override |
165 | public void onFailure( |
166 | Throwable caught) |
167 | { |
168 | showErrorException("getInitialUserInterface failed", caught); |
169 | } |
170 | |
171 | }); |
172 | } |
173 | |
174 | /** |
175 | * initialize the User Interface |
176 | * |
177 | * @param rootModel the root model to use |
178 | */ |
179 | public void initializeUserInterface( |
180 | ViewModel rootModel) |
181 | { |
182 | if (rootModel != null) |
183 | { |
184 | rootModel.registerModel(this); |
185 | |
186 | rootModel.setRootWidgetControl(rootWidgetControl); |
187 | rootModel.synchronizeWithUI(); |
188 | |
189 | // Setup timer to gather UI changes |
190 | startServerPollingTimer(); |
191 | } |
192 | else |
193 | { |
194 | showErrorLabel("getInitialUserInterface returned null."); |
195 | } |
196 | } |
197 | |
198 | /** |
199 | * signal Triggered |
200 | * |
201 | * @param objectID object ID |
202 | * @param signalName signal name |
203 | * @param callback call back interface |
204 | */ |
205 | public void signalTriggered( |
206 | String objectID, String signalName, AsyncCallback<Void> callback) |
207 | { |
208 | AsyncCallback<Void> callBackToUse = |
209 | createUIRefreshLinkedCallback(callback); |
210 | |
211 | getUserInterfaceService().signalTriggered(objectID, signalName, |
212 | callBackToUse); |
213 | } |
214 | |
215 | /** |
216 | * create Refresh Timer |
217 | */ |
218 | public void createRefreshTimer() |
219 | { |
220 | refreshTimer = new Timer() |
221 | { |
222 | /** |
223 | * (non-JSDoc) |
224 | * |
225 | * @see com.google.gwt.user.client.Timer#run() |
226 | */ |
227 | @Override |
228 | public void run() |
229 | { |
230 | getLatestEvents(); |
231 | } |
232 | }; |
233 | } |
234 | |
235 | /** |
236 | * call the server to get the latest update events and handle them. |
237 | */ |
238 | public void getLatestEvents() |
239 | { |
240 | if (getLatestEventsIsRunning) |
241 | { |
242 | getLatestEventsRunAgain = true; |
243 | } |
244 | |
245 | getLatestEventsIsRunning = true; |
246 | cancelPollingTimer(); |
247 | getUserInterfaceService().getChangeEventList( |
248 | new AsyncCallback<ArrayList<ChangeEvent>>() |
249 | { |
250 | |
251 | @Override |
252 | public void onFailure( |
253 | Throwable caught) |
254 | { |
255 | getLatestEventsFinished(); |
256 | showErrorException("getLatestEvents failed", caught); |
257 | } |
258 | |
259 | @Override |
260 | public void onSuccess( |
261 | ArrayList<ChangeEvent> result) |
262 | { |
263 | getLatestEventsFinished(); |
264 | handleChangeEventList(result); |
265 | } |
266 | }); |
267 | } |
268 | |
269 | /** |
270 | * get Latest Events Finished |
271 | */ |
272 | public void getLatestEventsFinished() |
273 | { |
274 | getLatestEventsIsRunning = false; |
275 | |
276 | if (getLatestEventsRunAgain) |
277 | { |
278 | getLatestEventsRunAgain = false; |
279 | getLatestEvents(); |
280 | } |
281 | else |
282 | { |
283 | startServerPollingTimer(); |
284 | } |
285 | } |
286 | |
287 | /** |
288 | * cancel Polling Timer |
289 | */ |
290 | public void cancelPollingTimer() |
291 | { |
292 | Timer currentRrefreshTimer = getRefreshTimer(); |
293 | if (currentRrefreshTimer != null) |
294 | { |
295 | currentRrefreshTimer.cancel(); |
296 | } |
297 | } |
298 | |
299 | /** |
300 | * handle a list of Change Event from the serverList |
301 | * |
302 | * @param changeEventList the list of change event to process |
303 | */ |
304 | public void handleChangeEventList( |
305 | List<ChangeEvent> changeEventList) |
306 | { |
307 | if (changeEventList != null) |
308 | { |
309 | for (ChangeEvent changeEvent : changeEventList) |
310 | { |
311 | handleChangeEventSilent(changeEvent); |
312 | } |
313 | } |
314 | } |
315 | |
316 | /** |
317 | * get Refresh Timer |
318 | * |
319 | * @return the refresh timer |
320 | */ |
321 | public Timer getRefreshTimer() |
322 | { |
323 | return refreshTimer; |
324 | } |
325 | |
326 | /** |
327 | * get the User Interface Service used to call the server |
328 | * |
329 | * @return the User Interface Service used to call the server |
330 | */ |
331 | public DynamicUserInterfaceServiceAsync getUserInterfaceService() |
332 | { |
333 | return userInterfaceService; |
334 | } |
335 | |
336 | /** |
337 | * set the User Interface Service to use |
338 | * |
339 | * @param setUserInterfaceService the asynchronous user interface service to |
340 | * call. |
341 | */ |
342 | public void setUserInterfaceService( |
343 | DynamicUserInterfaceServiceAsync setUserInterfaceService) |
344 | { |
345 | userInterfaceService = setUserInterfaceService; |
346 | } |
347 | |
348 | /** |
349 | * register Model. Once registered, a model can be found based on it's |
350 | * unique ID and it can receive change events. |
351 | * |
352 | * @param model the model to register |
353 | */ |
354 | public void registerModel( |
355 | Model model) |
356 | { |
357 | registry.put(model.getId(), model); |
358 | } |
359 | |
360 | /** |
361 | * unregister a Model |
362 | * |
363 | * @param model the model to unregister |
364 | */ |
365 | public void unRegisterModel( |
366 | Model model) |
367 | { |
368 | registry.remove(model.getId()); |
369 | } |
370 | |
371 | /** |
372 | * get Model based on it's id |
373 | * |
374 | * @param id the model ID |
375 | * @return the matching model. Null if the ID can't be found. |
376 | */ |
377 | public Model getModel( |
378 | String id) |
379 | { |
380 | return registry.get(id); |
381 | } |
382 | |
383 | /** |
384 | * handle Change Event by dispatching it to the model that matches the |
385 | * change event ID and hiding any caught exception. |
386 | * |
387 | * @param changeEvent the change event to handle |
388 | */ |
389 | public void handleChangeEventSilent( |
390 | ChangeEvent changeEvent) |
391 | { |
392 | try |
393 | { |
394 | handleChangeEvent(changeEvent); |
395 | } |
396 | catch (Exception e) |
397 | { |
398 | showErrorException(e); |
399 | } |
400 | } |
401 | |
402 | /** |
403 | * handle Change Event by dispatching it to the model that matches the |
404 | * change event ID |
405 | * |
406 | * @param changeEvent the change event to dispatch |
407 | */ |
408 | public void handleChangeEvent( |
409 | ChangeEvent changeEvent) |
410 | { |
411 | if (changeEvent != null) |
412 | { |
413 | if (!isEventIgnored(changeEvent)) |
414 | { |
415 | Model model = getModel(changeEvent.getId()); |
416 | |
417 | if (model != null) |
418 | { |
419 | model.propertyChanged(changeEvent); |
420 | } |
421 | } |
422 | } |
423 | } |
424 | |
425 | /** |
426 | * show Error Exception on the UI. Called by models who want to report |
427 | * problems directly to the user interface. This is meant mostly for |
428 | * internal type of errors. |
429 | * |
430 | * @param description error message to display on top of the exception. |
431 | * @param caught the exception to display |
432 | */ |
433 | public void showErrorException( |
434 | String description, Throwable caught) |
435 | { |
436 | String errorText = description + " : " + caught.getMessage(); |
437 | showErrorLabel(errorText); |
438 | } |
439 | |
440 | /** |
441 | * show Error Exception on the UI. Called by models who want to report |
442 | * problems directly to the user interface. This is meant mostly for |
443 | * internal type of errors. |
444 | * |
445 | * @param caught the exception to display |
446 | */ |
447 | public void showErrorException( |
448 | Throwable caught) |
449 | { |
450 | String errorText = "Exception Caught : " + caught.getMessage(); |
451 | showErrorLabel(errorText); |
452 | } |
453 | |
454 | /** |
455 | * show an Error Label on the UI. Called by models who want to report |
456 | * problems directly to the user interface. This is meant mostly for |
457 | * internal type of errors. |
458 | * |
459 | * @param errorText the error text to display. |
460 | */ |
461 | public void showErrorLabel( |
462 | String errorText) |
463 | { |
464 | Label currentErrorLabel = getErrorLabel(); |
465 | currentErrorLabel.setText(errorText); |
466 | } |
467 | |
468 | /** |
469 | * get Error Label |
470 | * |
471 | * @return the error label |
472 | */ |
473 | public Label getErrorLabel() |
474 | { |
475 | if (errorLabel == null) |
476 | { |
477 | errorLabel = new Label(); |
478 | RootPanel rootPanel = RootPanel.get("dynamicView"); |
479 | Utilities.addWidgetToPanel(errorLabel, rootPanel); |
480 | } |
481 | return errorLabel; |
482 | } |
483 | |
484 | /** |
485 | * get the Default Void server Call back |
486 | * |
487 | * @return the Default Void server Call back |
488 | */ |
489 | public AsyncCallback<Void> getDefaultVoidCallback() |
490 | { |
491 | return callBack; |
492 | } |
493 | |
494 | /** |
495 | * Server update Error Report Call back |
496 | * |
497 | * @author Steve McDuff |
498 | * |
499 | */ |
500 | public class ErrorReportCallback implements AsyncCallback<Void> |
501 | { |
502 | |
503 | /** |
504 | * (non-JSDoc) |
505 | * |
506 | * @see com.google.gwt.user.client.rpc.AsyncCallback#onFailure(java.lang.Throwable) |
507 | */ |
508 | @Override |
509 | public void onFailure( |
510 | Throwable caught) |
511 | { |
512 | showErrorException(caught); |
513 | } |
514 | |
515 | /** |
516 | * (non-JSDoc) |
517 | * |
518 | * @see com.google.gwt.user.client.rpc.AsyncCallback#onSuccess(java.lang.Object) |
519 | */ |
520 | @Override |
521 | public void onSuccess( |
522 | Void result) |
523 | { |
524 | |
525 | } |
526 | } |
527 | |
528 | /** |
529 | * Linked asynchronous call backs will have a chance to do work before or |
530 | * after their link is invoked. |
531 | * |
532 | * @author Steve McDuff |
533 | * @param <T> type value returned in the call back |
534 | * |
535 | */ |
536 | public class LinkedAsyncCallback<T> implements AsyncCallback<T> |
537 | { |
538 | |
539 | /** |
540 | * optional linked call back |
541 | */ |
542 | private AsyncCallback<T> linkedCallback; |
543 | |
544 | /** |
545 | * get Linked Call back |
546 | * |
547 | * @return linked call back |
548 | */ |
549 | public AsyncCallback<T> getLinkedCallback() |
550 | { |
551 | return linkedCallback; |
552 | } |
553 | |
554 | /** |
555 | * set Linked Call back |
556 | * |
557 | * @param setLinkedCallback the new linked call back |
558 | */ |
559 | public void setLinkedCallback( |
560 | AsyncCallback<T> setLinkedCallback) |
561 | { |
562 | linkedCallback = setLinkedCallback; |
563 | } |
564 | |
565 | /** |
566 | * (non-JSDoc) |
567 | * |
568 | * @see com.google.gwt.user.client.rpc.AsyncCallback#onFailure(java.lang.Throwable) |
569 | */ |
570 | @Override |
571 | public void onFailure( |
572 | Throwable caught) |
573 | { |
574 | if (linkedCallback != null) |
575 | { |
576 | linkedCallback.onFailure(caught); |
577 | } |
578 | } |
579 | |
580 | /** |
581 | * (non-JSDoc) |
582 | * |
583 | * @see com.google.gwt.user.client.rpc.AsyncCallback#onSuccess(java.lang.Object) |
584 | */ |
585 | @Override |
586 | public void onSuccess( |
587 | T result) |
588 | { |
589 | if (linkedCallback != null) |
590 | { |
591 | linkedCallback.onSuccess(result); |
592 | } |
593 | } |
594 | } |
595 | |
596 | /** |
597 | * Server update Error Report Call back |
598 | * |
599 | * @author Steve McDuff |
600 | * @param <T> type value returned in the call back |
601 | * |
602 | */ |
603 | public class RefreshUiOnSuccessLinkedAsyncCallback<T> extends |
604 | LinkedAsyncCallback<T> |
605 | { |
606 | /** |
607 | * (non-JSDoc) |
608 | * |
609 | * @see com.google.gwt.user.client.rpc.AsyncCallback#onSuccess(java.lang.Object) |
610 | */ |
611 | @Override |
612 | public void onSuccess( |
613 | T result) |
614 | { |
615 | super.onSuccess(result); |
616 | |
617 | // fetch the latest events right after a successful change to get |
618 | // the UI to reflect the latest changes without waiting for the |
619 | // polling loop. |
620 | getLatestEvents(); |
621 | } |
622 | } |
623 | |
624 | /** |
625 | * call back used to trap property updates that might be pending |
626 | * |
627 | * @author Steve McDuff |
628 | * |
629 | */ |
630 | public class UpdatePropertyCallback extends |
631 | RefreshUiOnSuccessLinkedAsyncCallback<Void> |
632 | { |
633 | |
634 | /** |
635 | * which property was changed on which object |
636 | */ |
637 | private ChangeEvent changeEventLocation; |
638 | |
639 | /** |
640 | * actual updated value change event |
641 | */ |
642 | private ChangeEvent changeEvent; |
643 | |
644 | /** |
645 | * |
646 | * UpdatePropertyCallback constructor |
647 | */ |
648 | public UpdatePropertyCallback() |
649 | { |
650 | |
651 | } |
652 | |
653 | /** |
654 | * get Change Event |
655 | * |
656 | * @return the change event |
657 | */ |
658 | public ChangeEvent getChangeEvent() |
659 | { |
660 | return changeEvent; |
661 | } |
662 | |
663 | /** |
664 | * set Change Event Location |
665 | * |
666 | * @param setChangeEventLocation new change event |
667 | */ |
668 | public void setChangeEventLocation( |
669 | ChangeEvent setChangeEventLocation) |
670 | { |
671 | changeEventLocation = setChangeEventLocation; |
672 | } |
673 | |
674 | /** |
675 | * get Change Event Location |
676 | * |
677 | * @return change event location |
678 | */ |
679 | public ChangeEvent getChangeEventLocation() |
680 | { |
681 | return changeEventLocation; |
682 | } |
683 | |
684 | /** |
685 | * |
686 | * set Change Event |
687 | * |
688 | * @param setChangeEvent new change event |
689 | */ |
690 | public void setChangeEvent( |
691 | ChangeEvent setChangeEvent) |
692 | { |
693 | changeEvent = setChangeEvent; |
694 | } |
695 | |
696 | /** |
697 | * (non-JSDoc) |
698 | * |
699 | * @see com.google.gwt.user.client.rpc.AsyncCallback#onFailure(java.lang.Throwable) |
700 | */ |
701 | @Override |
702 | public void onFailure( |
703 | Throwable caught) |
704 | { |
705 | updateCompleted(); |
706 | super.onFailure(caught); |
707 | } |
708 | |
709 | /** |
710 | * (non-JSDoc) |
711 | * |
712 | * @see com.google.gwt.user.client.rpc.AsyncCallback#onSuccess(java.lang.Object) |
713 | */ |
714 | @Override |
715 | public void onSuccess( |
716 | Void result) |
717 | { |
718 | updateCompleted(); |
719 | super.onSuccess(result); |
720 | } |
721 | |
722 | /** |
723 | * update Completed |
724 | */ |
725 | public void updateCompleted() |
726 | { |
727 | propertyUpdateCompleted(this); |
728 | } |
729 | } |
730 | |
731 | /** |
732 | * |
733 | * propertyUpdateCompleted |
734 | * |
735 | * @param callback change event call back |
736 | */ |
737 | protected void propertyUpdateCompleted( |
738 | UpdatePropertyCallback callback) |
739 | { |
740 | ChangeEvent changeEventLocation = callback.getChangeEventLocation(); |
741 | PendingChangeEvent pendingEvent = |
742 | pendingChangeEventMap.remove(changeEventLocation); |
743 | if (pendingEvent != null && !pendingEvent.isSent()) |
744 | { |
745 | // discovered a pending change, send it to the server assuming the |
746 | // new value is different. |
747 | ChangeEvent newEvent = pendingEvent.getEvent(); |
748 | |
749 | ChangeEvent oldChangeEvent = callback.getChangeEvent(); |
750 | if (!Utilities.equals(oldChangeEvent.getSerializableValue(), |
751 | newEvent.getSerializableValue())) |
752 | { |
753 | updateProperty(newEvent.getId(), newEvent.getName(), |
754 | newEvent.getSerializableValue(), pendingEvent.getCallback()); |
755 | } |
756 | } |
757 | } |
758 | |
759 | /** |
760 | * update Property and ignore the incoming change event. Also stack similar |
761 | * changes together and ignore older ones. |
762 | * |
763 | * @param objectID object ID that changed |
764 | * @param propertyName property name to change |
765 | * @param newValue new value |
766 | * @param callback call back interface |
767 | */ |
768 | public void updateProperty( |
769 | String objectID, String propertyName, Serializable newValue, |
770 | AsyncCallback<Void> callback) |
771 | { |
772 | ChangeEvent pendingEventLocation = new ChangeEvent(); |
773 | |
774 | pendingEventLocation.setId(objectID); |
775 | pendingEventLocation.setName(propertyName); |
776 | pendingEventLocation.setValue(null); |
777 | pendingEventLocation.setType("update"); |
778 | pendingEventLocation.setSortingInstance(false); |
779 | |
780 | ChangeEvent ignoreEvent = new ChangeEvent(); |
781 | |
782 | ignoreEvent.setId(objectID); |
783 | ignoreEvent.setName(propertyName); |
784 | ignoreEvent.setValue(Serializer.serialize(newValue)); |
785 | ignoreEvent.setType("update"); |
786 | ignoreEvent.setSortingInstance(false); |
787 | |
788 | PendingChangeEvent pending = new PendingChangeEvent(); |
789 | pending.setCallback(callback); |
790 | pending.setEvent(ignoreEvent); |
791 | |
792 | PendingChangeEvent removedPendingEvent = |
793 | pendingChangeEventMap.remove(pendingEventLocation); |
794 | if (removedPendingEvent != null) |
795 | { |
796 | AsyncCallback<Void> cancelledCallback = |
797 | removedPendingEvent.getCallback(); |
798 | if (cancelledCallback != null) |
799 | { |
800 | cancelledCallback.onSuccess(null); |
801 | } |
802 | |
803 | // there is already an update of this property going on, buffer this |
804 | // one. |
805 | pendingChangeEventMap.put(pendingEventLocation, pending); |
806 | } |
807 | else |
808 | { |
809 | addEventToIgnoreList(ignoreEvent); |
810 | |
811 | UpdatePropertyCallback updateCallBack = |
812 | new UpdatePropertyCallback(); |
813 | updateCallBack.setChangeEvent(ignoreEvent); |
814 | updateCallBack.setChangeEventLocation(pendingEventLocation); |
815 | updateCallBack.setLinkedCallback(callback); |
816 | |
817 | pending.setSent(true); |
818 | pendingChangeEventMap.put(pendingEventLocation, pending); |
819 | |
820 | getUserInterfaceService().updateProperty(objectID, propertyName, |
821 | Serializer.serialize(newValue), updateCallBack); |
822 | } |
823 | } |
824 | |
825 | /** |
826 | * add Event To Ignore List |
827 | * |
828 | * @param ignoreEvent event to ignore |
829 | */ |
830 | public void addEventToIgnoreList( |
831 | ChangeEvent ignoreEvent) |
832 | { |
833 | IgnoredEventInformation iei = ignoreChangeEventMap.get(ignoreEvent); |
834 | if (iei == null) |
835 | { |
836 | iei = new IgnoredEventInformation(); |
837 | ignoreChangeEventMap.put(ignoreEvent, iei); |
838 | } |
839 | iei.incrementIgnoredCount(); |
840 | } |
841 | |
842 | /** |
843 | * clean Old Events : parse the list of ignored change events to see if some |
844 | * of them are too old. |
845 | */ |
846 | public void cleanOldEvents() |
847 | { |
848 | long currentTime = Utilities.getTimeStampInMilliseconds(); |
849 | |
850 | long minimumTimeValue = |
851 | currentTime - MAXIMUM_IGNORE_EVENT_AGE_IN_MILLISECONDS; |
852 | |
853 | cleanEventsOlderThanMinimumTimeValue(currentTime, minimumTimeValue); |
854 | } |
855 | |
856 | /** |
857 | * |
858 | * clean Events Older Than Minimum Time Value |
859 | * |
860 | * @param currentTime current time |
861 | * @param minimumTimeValue minimum time value |
862 | */ |
863 | public void cleanEventsOlderThanMinimumTimeValue( |
864 | long currentTime, long minimumTimeValue) |
865 | { |
866 | Set<Entry<ChangeEvent, IgnoredEventInformation>> entrySet = |
867 | ignoreChangeEventMap.entrySet(); |
868 | |
869 | for (Iterator<Entry<ChangeEvent, IgnoredEventInformation>> iterator = |
870 | entrySet.iterator(); iterator.hasNext();) |
871 | { |
872 | Entry<ChangeEvent, IgnoredEventInformation> entry = iterator.next(); |
873 | |
874 | IgnoredEventInformation value = entry.getValue(); |
875 | |
876 | // the time stamp of the event to ignore is too old, delete it. |
877 | long eventTime = value.getIgnoredTimeStamp(); |
878 | if (eventTime < minimumTimeValue) |
879 | { |
880 | ChangeEvent expiredEvent = entry.getKey(); |
881 | |
882 | showExpiredChangeEventWarning(currentTime, eventTime, |
883 | expiredEvent); |
884 | |
885 | iterator.remove(); |
886 | } |
887 | } |
888 | } |
889 | |
890 | /** |
891 | * show Expired Change Event Warning |
892 | * |
893 | * @param currentTime the current time |
894 | * @param eventTime the event time |
895 | * @param expiredEvent the expired event |
896 | */ |
897 | public void showExpiredChangeEventWarning( |
898 | long currentTime, long eventTime, ChangeEvent expiredEvent) |
899 | { |
900 | String errorText = |
901 | "Found expired change event : Change Event Timestamp is : " |
902 | + eventTime + " ms, current time is : " + currentTime |
903 | + " ms. Exceeds maximum delay of : " |
904 | + MAXIMUM_IGNORE_EVENT_AGE_IN_MILLISECONDS + " ms. " |
905 | + expiredEvent; |
906 | |
907 | showErrorLabel(errorText); |
908 | } |
909 | |
910 | /** |
911 | * is a change Event Ignored since it was first created by the client |
912 | * |
913 | * @param changeEvent the change event to potentially ignore |
914 | * @return true if we can ignore the change event |
915 | */ |
916 | public boolean isEventIgnored( |
917 | ChangeEvent changeEvent) |
918 | { |
919 | boolean ignored = false; |
920 | IgnoredEventInformation ignoredEventInformation = |
921 | ignoreChangeEventMap.get(changeEvent); |
922 | if (ignoredEventInformation != null) |
923 | { |
924 | // the event must be ignored. |
925 | |
926 | // decrement the number of times the event must be ignored |
927 | boolean removeInfo = |
928 | ignoredEventInformation.decrementIgnoredCount(); |
929 | if (removeInfo) |
930 | { |
931 | // the ignore count reached zero, we can forget about this |
932 | // event. |
933 | ignoreChangeEventMap.remove(changeEvent); |
934 | } |
935 | ignored = true; |
936 | } |
937 | return ignored; |
938 | } |
939 | |
940 | /** |
941 | * get Ignore Change Event Map |
942 | * |
943 | * @return the ignored change event map |
944 | */ |
945 | public Map<ChangeEvent, IgnoredEventInformation> getIgnoreChangeEventMap() |
946 | { |
947 | return ignoreChangeEventMap; |
948 | } |
949 | |
950 | /** |
951 | * monitor Synchronize With UI event. By default, this method does nothing. |
952 | * Overriding it allows users to monitor synchronization events. |
953 | * |
954 | * @param model the model being synchronized |
955 | */ |
956 | public void monitorSynchronizeWithUI( |
957 | @SuppressWarnings("unused") UserInterfaceModel model) |
958 | { |
959 | |
960 | } |
961 | |
962 | /** |
963 | * update a Reference List and ignore the updates that it might generate |
964 | * |
965 | * @param selectionListID the selection list |
966 | * @param selectedObjectIDList the list of selected object IDs |
967 | * @param oldSelectedObjectIDList the previous selection |
968 | * @param callback function call back |
969 | */ |
970 | public void updateReferenceList( |
971 | String selectionListID, ArrayList<String> selectedObjectIDList, |
972 | ArrayList<String> oldSelectedObjectIDList, AsyncCallback<Void> callback) |
973 | { |
974 | addIgnoreAddEvents(selectionListID, selectedObjectIDList, |
975 | oldSelectedObjectIDList); |
976 | |
977 | addIgnoreRemoveEvents(selectionListID, selectedObjectIDList, |
978 | oldSelectedObjectIDList); |
979 | |
980 | AsyncCallback<Void> callBackToUse = |
981 | createUIRefreshLinkedCallback(callback); |
982 | |
983 | getUserInterfaceService().updateReferenceList(selectionListID, |
984 | selectedObjectIDList, callBackToUse); |
985 | } |
986 | |
987 | /** |
988 | * create UI Refresh Linked Call back |
989 | * |
990 | * @param <T> the type of value returned in the call |
991 | * @param originalCallback the original call back |
992 | * @return a call back that will invoke the original call back and trigger a |
993 | * UI refresh. |
994 | */ |
995 | public <T> AsyncCallback<T> createUIRefreshLinkedCallback( |
996 | AsyncCallback<T> originalCallback) |
997 | { |
998 | RefreshUiOnSuccessLinkedAsyncCallback<T> callbackToUse = |
999 | new RefreshUiOnSuccessLinkedAsyncCallback<T>(); |
1000 | callbackToUse.setLinkedCallback(originalCallback); |
1001 | return callbackToUse; |
1002 | } |
1003 | |
1004 | /** |
1005 | * add Ignore Remove Events |
1006 | * |
1007 | * @param selectionListID the selection list ID |
1008 | * @param selectedObjectIDList the new list of selected IDs |
1009 | * @param oldSelectedObjectIDList the old list of selected IDs |
1010 | */ |
1011 | public void addIgnoreRemoveEvents( |
1012 | String selectionListID, List<String> selectedObjectIDList, |
1013 | List<String> oldSelectedObjectIDList) |
1014 | { |
1015 | List<String> removeStrings = new ArrayList<String>(); |
1016 | |
1017 | if (oldSelectedObjectIDList != null) |
1018 | { |
1019 | removeStrings.addAll(oldSelectedObjectIDList); |
1020 | } |
1021 | if (selectedObjectIDList != null) |
1022 | { |
1023 | removeStrings.removeAll(selectedObjectIDList); |
1024 | } |
1025 | |
1026 | for (String removeId : removeStrings) |
1027 | { |
1028 | ChangeEvent ignoreEvent = new ChangeEvent(); |
1029 | ignoreEvent.setId(selectionListID); |
1030 | ignoreEvent.setName("selected user interface element"); |
1031 | ignoreEvent.setType("remove"); |
1032 | ignoreEvent.setValue(Serializer.serializeString(removeId)); |
1033 | addEventToIgnoreList(ignoreEvent); |
1034 | } |
1035 | } |
1036 | |
1037 | /** |
1038 | * add Ignore Add Events |
1039 | * |
1040 | * @param selectionListID the selection list ID |
1041 | * @param selectedObjectIDList the new selection list |
1042 | * @param oldSelectedObjectIDList the old selection list |
1043 | */ |
1044 | public void addIgnoreAddEvents( |
1045 | String selectionListID, List<String> selectedObjectIDList, |
1046 | List<String> oldSelectedObjectIDList) |
1047 | { |
1048 | List<String> addStrings = new ArrayList<String>(); |
1049 | |
1050 | if (selectedObjectIDList != null) |
1051 | { |
1052 | addStrings.addAll(selectedObjectIDList); |
1053 | } |
1054 | if (oldSelectedObjectIDList != null) |
1055 | { |
1056 | addStrings.removeAll(oldSelectedObjectIDList); |
1057 | } |
1058 | |
1059 | for (String addId : addStrings) |
1060 | { |
1061 | ChangeEvent ignoreEvent = new ChangeEvent(); |
1062 | ignoreEvent.setId(selectionListID); |
1063 | ignoreEvent.setName("selected user interface element"); |
1064 | ignoreEvent.setType("add"); |
1065 | ignoreEvent.setValue(Serializer.serializeString(addId)); |
1066 | addEventToIgnoreList(ignoreEvent); |
1067 | } |
1068 | } |
1069 | |
1070 | } |