001: /*
002: * This file is part of the Echo Web Application Framework (hereinafter "Echo").
003: * Copyright (C) 2002-2005 NextApp, Inc.
004: *
005: * Version: MPL 1.1/GPL 2.0/LGPL 2.1
006: *
007: * The contents of this file are subject to the Mozilla Public License Version
008: * 1.1 (the "License"); you may not use this file except in compliance with
009: * the License. You may obtain a copy of the License at
010: * http://www.mozilla.org/MPL/
011: *
012: * Software distributed under the License is distributed on an "AS IS" basis,
013: * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
014: * for the specific language governing rights and limitations under the
015: * License.
016: *
017: * Alternatively, the contents of this file may be used under the terms of
018: * either the GNU General Public License Version 2 or later (the "GPL"), or
019: * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
020: * in which case the provisions of the GPL or the LGPL are applicable instead
021: * of those above. If you wish to allow use of your version of this file only
022: * under the terms of either the GPL or the LGPL, and not to allow others to
023: * use your version of this file under the terms of the MPL, indicate your
024: * decision by deleting the provisions above and replace them with the notice
025: * and other provisions required by the GPL or the LGPL. If you do not delete
026: * the provisions above, a recipient may use your version of this file under
027: * the terms of any one of the MPL, the GPL or the LGPL.
028: */
029:
030: package nextapp.echo2.webcontainer;
031:
032: import java.util.HashSet;
033: import java.util.Set;
034:
035: import org.w3c.dom.Document;
036: import org.w3c.dom.Element;
037:
038: import nextapp.echo2.app.ApplicationInstance;
039: import nextapp.echo2.app.Command;
040: import nextapp.echo2.app.Component;
041: import nextapp.echo2.app.Window;
042: import nextapp.echo2.app.update.PropertyUpdate;
043: import nextapp.echo2.app.update.ServerComponentUpdate;
044: import nextapp.echo2.app.update.ServerUpdateManager;
045: import nextapp.echo2.app.update.UpdateManager;
046: import nextapp.echo2.webcontainer.syncpeer.WindowPeer;
047: import nextapp.echo2.webrender.Connection;
048: import nextapp.echo2.webrender.ServerMessage;
049: import nextapp.echo2.webrender.Service;
050: import nextapp.echo2.webrender.UserInstance;
051: import nextapp.echo2.webrender.WebRenderServlet;
052: import nextapp.echo2.webrender.servermessage.WindowUpdate;
053: import nextapp.echo2.webrender.service.JavaScriptService;
054: import nextapp.echo2.webrender.service.SynchronizeService;
055: import nextapp.echo2.webrender.util.DomUtil;
056:
057: /**
058: * A service which synchronizes the state of the client with that of the
059: * server. Requests made to this service are in the form of "ClientMessage"
060: * XML documents which describe the users actions since the last
061: * synchronization, e.g., input typed into text fields and the action taken
062: * (e.g., a button press) which caused the server interaction.
063: * The service then communicates these changes to the server-side application,
064: * and then generates an output "ServerMessage" containing instructions to
065: * update the client-side state of the application to the updated server-side
066: * state.
067: * <p>
068: * This class is derived from the base class <code>SynchronizeService</code>
069: * of the web renderer, which handles the lower-level work.
070: */
071: public class ContainerSynchronizeService extends SynchronizeService {
072:
073: /**
074: * Service to provide supporting JavaScript library.
075: */
076: public static final Service WEB_CONTAINER_SERVICE = JavaScriptService
077: .forResource("Echo.WebContainer",
078: "/nextapp/echo2/webcontainer/resource/js/WebContainer.js");
079:
080: static {
081: WebRenderServlet.getServiceRegistry()
082: .add(WEB_CONTAINER_SERVICE);
083: }
084:
085: /**
086: * A single shared instance of this stateless service.
087: */
088: public static final ContainerSynchronizeService INSTANCE = new ContainerSynchronizeService();
089:
090: /**
091: * Determines if any of the <code>Component</code> object in the provided
092: * set of "potential" ancestors is in fact an ancestor of
093: * <code>component</code>.
094: *
095: * @param potentialAncestors a set containing <code>Component</code>s
096: * @param component the <code>Component</code> to evaluate
097: * @return true if any component in <code>potentialAncestors</code> is an
098: * ancestor of <code>component</code>
099: */
100: private static boolean isAncestor(Set potentialAncestors,
101: Component component) {
102: component = component.getParent();
103: while (component != null) {
104: if (potentialAncestors.contains(component)) {
105: return true;
106: }
107: component = component.getParent();
108: }
109: return false;
110: }
111:
112: /**
113: * <code>ClientMessagePartProcessor</code> to process user-interface
114: * component input message parts.
115: */
116: private ClientMessagePartProcessor propertyUpdateProcessor = new ClientMessagePartProcessor() {
117:
118: /**
119: * @see nextapp.echo2.webrender.service.SynchronizeService.ClientMessagePartProcessor#getName()
120: */
121: public String getName() {
122: return "EchoPropertyUpdate";
123: }
124:
125: /**
126: * @see nextapp.echo2.webrender.service.SynchronizeService.ClientMessagePartProcessor#process(
127: * nextapp.echo2.webrender.UserInstance, org.w3c.dom.Element)
128: */
129: public void process(UserInstance userInstance,
130: Element messagePartElement) {
131: ContainerInstance ci = (ContainerInstance) userInstance;
132: Element[] propertyElements = DomUtil
133: .getChildElementsByTagName(messagePartElement,
134: "property");
135: for (int i = 0; i < propertyElements.length; ++i) {
136: String componentId = propertyElements[i]
137: .getAttribute("component-id");
138: Component component = ci
139: .getComponentByElementId(componentId);
140: if (component == null) {
141: // Component removed. This should not frequently occur, however in certain cases,
142: // e.g., dragging a window during an during before, during, after a server pushed update
143: // can result in the condition where input is received from a component which no longer
144: // is registered.
145: continue;
146: }
147: ComponentSynchronizePeer syncPeer = SynchronizePeerFactory
148: .getPeerForComponent(component.getClass());
149: if (!(syncPeer instanceof PropertyUpdateProcessor)) {
150: throw new IllegalStateException(
151: "Target peer is not an PropertyUpdateProcessor.");
152: }
153: ((PropertyUpdateProcessor) syncPeer)
154: .processPropertyUpdate(ci, component,
155: propertyElements[i]);
156: }
157: }
158: };
159:
160: /**
161: * <code>ClientMessagePartProcessor</code> to process user-interface
162: * component action message parts.
163: */
164: private ClientMessagePartProcessor actionProcessor = new ClientMessagePartProcessor() {
165:
166: /**
167: * @see nextapp.echo2.webrender.service.SynchronizeService.ClientMessagePartProcessor#getName()
168: */
169: public String getName() {
170: return "EchoAction";
171: }
172:
173: /**
174: * @see nextapp.echo2.webrender.service.SynchronizeService.ClientMessagePartProcessor#process(
175: * nextapp.echo2.webrender.UserInstance, org.w3c.dom.Element)
176: */
177: public void process(UserInstance userInstance,
178: Element messagePartElement) {
179: ContainerInstance ci = (ContainerInstance) userInstance;
180: Element actionElement = DomUtil.getChildElementByTagName(
181: messagePartElement, "action");
182: String componentId = actionElement
183: .getAttribute("component-id");
184: Component component = ci
185: .getComponentByElementId(componentId);
186: if (component == null) {
187: // Component removed. This should not frequently occur, however in certain cases,
188: // e.g., dragging a window during an during before, during, after a server pushed update
189: // can result in the condition where input is received from a component which no longer
190: // is registered.
191: return;
192: }
193: ComponentSynchronizePeer syncPeer = SynchronizePeerFactory
194: .getPeerForComponent(component.getClass());
195: if (!(syncPeer instanceof ActionProcessor)) {
196: throw new IllegalStateException(
197: "Target peer is not an ActionProcessor.");
198: }
199: ((ActionProcessor) syncPeer).processAction(ci, component,
200: actionElement);
201: }
202: };
203:
204: /**
205: * Creates a new <code>ContainerSynchronizeService</code>.
206: * Installs "ClientMessage" part processors.
207: */
208: private ContainerSynchronizeService() {
209: super ();
210: registerClientMessagePartProcessor(propertyUpdateProcessor);
211: registerClientMessagePartProcessor(actionProcessor);
212: }
213:
214: /**
215: * Performs disposal operations on components which have been removed from
216: * the hierarchy. Removes any <code>RenderState</code> objects being
217: * stored in the <code>ContainerInstance</code> for the disposed
218: * components. Invokes <code>ComponentSynchronizePeer.renderDispose()</code>
219: * such that the peers of the components can dispose of resources on the
220: * client.
221: *
222: * @param rc the relevant <code>RenderContext</code>
223: * @param componentUpdate the <code>ServerComponentUpdate</code> causing
224: * components to be disposed.
225: * @param disposedComponents the components to dispose
226: */
227: private void disposeComponents(RenderContext rc,
228: ServerComponentUpdate componentUpdate,
229: Component[] disposedComponents) {
230: ContainerInstance ci = rc.getContainerInstance();
231: for (int i = 0; i < disposedComponents.length; ++i) {
232: ComponentSynchronizePeer disposedSyncPeer = SynchronizePeerFactory
233: .getPeerForComponent(disposedComponents[i]
234: .getClass());
235: disposedSyncPeer.renderDispose(rc, componentUpdate,
236: disposedComponents[i]);
237: ci.removeRenderState(disposedComponents[i]);
238: }
239: }
240:
241: /**
242: * Invokes <code>renderDispose()</code> on
243: * <code>ComponentSynchronizePeer</code>s in a hierarchy of Components that is
244: * be re-rendered on the client. That is, this hierarchy of components exist on
245: * the client, are being removed, and will be re-rendered due to a container
246: * component NOT being capable of rendering a partial update.
247: * This method is invoked recursively.
248: *
249: * @param rc the relevant <code>RenderContext</code>
250: * @param update the update
251: * @param parent the <code>Component</code> whose descendants should be disposed
252: */
253: private void disposeReplacedDescendants(RenderContext rc,
254: ServerComponentUpdate update, Component parent) {
255: Component[] replacedComponents = parent.getVisibleComponents();
256: boolean isRoot = parent == update.getParent();
257: for (int i = 0; i < replacedComponents.length; ++i) {
258: // Verify that component was not added on this synchronization.
259: if (isRoot && update.hasAddedChild(replacedComponents[i])) {
260: // Component was added as a child on this synchronization:
261: // There is no reason to dispose of it as it does not yet exist on the client.
262: continue;
263: }
264:
265: // Recursively dispose child components.
266: disposeReplacedDescendants(rc, update,
267: replacedComponents[i]);
268:
269: // Dispose component.
270: ComponentSynchronizePeer syncPeer = SynchronizePeerFactory
271: .getPeerForComponent(replacedComponents[i]
272: .getClass());
273: syncPeer.renderDispose(rc, update, replacedComponents[i]);
274: }
275: }
276:
277: /**
278: * Determines if the specified <code>component</code> has been rendered to
279: * the client by determining if it is a descendant of any
280: * <code>LazyRenderContainer</code>s and if so querying them to determine
281: * the hierarchy's render state. This method is recursively invoked.
282: *
283: * @param ci the relevant <code>ContainerInstance</code>
284: * @param component the <code>Component</code> to analyze
285: * @return <code>true</code> if the <code>Component</code> has been
286: * rendered to the client
287: */
288: private boolean isRendered(ContainerInstance ci, Component component) {
289: Component parent = component.getParent();
290: if (parent == null) {
291: return true;
292: }
293: ComponentSynchronizePeer syncPeer = SynchronizePeerFactory
294: .getPeerForComponent(parent.getClass());
295: if (syncPeer instanceof LazyRenderContainer) {
296: boolean rendered = ((LazyRenderContainer) syncPeer)
297: .isRendered(ci, parent, component);
298: if (!rendered) {
299: return false;
300: }
301: }
302: return isRendered(ci, parent);
303: }
304:
305: /**
306: * Retrieves information about the current focused component on the client,
307: * if provided, and in such case notifies the
308: * <code>ApplicationInstance</code> of the focus.
309: *
310: * @param rc the relevant <code>RenderContext</code>
311: * @param clientMessageDocument the ClientMessage <code>Document</code> to
312: * retrieve focus information from
313: */
314: private void processClientFocusedComponent(RenderContext rc,
315: Document clientMessageDocument) {
316: if (clientMessageDocument.getDocumentElement().hasAttribute(
317: "focus")) {
318: String focusedComponentId = clientMessageDocument
319: .getDocumentElement().getAttribute("focus");
320: Component component = null;
321: if (focusedComponentId.length() > 2) {
322: // Valid component id.
323: component = rc.getContainerInstance()
324: .getComponentByElementId(focusedComponentId);
325: }
326: ApplicationInstance applicationInstance = rc
327: .getContainerInstance().getApplicationInstance();
328: applicationInstance
329: .getUpdateManager()
330: .getClientUpdateManager()
331: .setApplicationProperty(
332: ApplicationInstance.FOCUSED_COMPONENT_CHANGED_PROPERTY,
333: component);
334: }
335: }
336:
337: /**
338: * Handles an invalid transaction id scenario, reinitializing the entire
339: * state of the client.
340: *
341: * @param rc the relevant <code>RenderContex</code>
342: */
343: private void processInvalidTransaction(RenderContext rc) {
344: WindowUpdate.renderReload(rc.getServerMessage());
345: }
346:
347: /**
348: * Executes queued <code>Command</code>s.
349: *
350: * @param rc the relevant <code>RenderContext</code>
351: */
352: private void processQueuedCommands(RenderContext rc) {
353: ServerUpdateManager serverUpdateManager = rc
354: .getContainerInstance().getUpdateManager()
355: .getServerUpdateManager();
356: Command[] commands = serverUpdateManager.getCommands();
357: for (int i = 0; i < commands.length; i++) {
358: CommandSynchronizePeer peer = SynchronizePeerFactory
359: .getPeerForCommand(commands[i].getClass());
360: peer.render(rc, commands[i]);
361: }
362: }
363:
364: /**
365: * Processes updates from the application, generating an outgoing
366: * <code>ServerMessage</code>.
367: *
368: * @param rc the relevant <code>RenderContext</code>
369: */
370: private void processServerUpdates(RenderContext rc) {
371: ContainerInstance ci = rc.getContainerInstance();
372: UpdateManager updateManager = ci.getUpdateManager();
373: ServerUpdateManager serverUpdateManager = updateManager
374: .getServerUpdateManager();
375: ServerComponentUpdate[] componentUpdates = updateManager
376: .getServerUpdateManager().getComponentUpdates();
377:
378: if (serverUpdateManager.isFullRefreshRequired()) {
379: Window window = rc.getContainerInstance()
380: .getApplicationInstance().getDefaultWindow();
381: ServerComponentUpdate fullRefreshUpdate = componentUpdates[0];
382:
383: // Dispose of removed descendants.
384: Component[] removedDescendants = fullRefreshUpdate
385: .getRemovedDescendants();
386: disposeComponents(rc, fullRefreshUpdate, removedDescendants);
387:
388: // Perform full refresh.
389: RootSynchronizePeer rootSyncPeer = (RootSynchronizePeer) SynchronizePeerFactory
390: .getPeerForComponent(window.getClass());
391: rootSyncPeer.renderRefresh(rc, fullRefreshUpdate, window);
392:
393: setRootLayoutDirection(rc);
394: } else {
395: // Remove any updates whose updates are descendants of components which have not been rendered to the
396: // client yet due to lazy-loading containers.
397: for (int i = 0; i < componentUpdates.length; ++i) {
398: if (!isRendered(ci, componentUpdates[i].getParent())) {
399: componentUpdates[i] = null;
400: }
401: }
402:
403: // Set of Components whose HTML was entirely re-rendered, negating the need
404: // for updates of their children to be processed.
405: Set fullyReplacedHierarchies = new HashSet();
406:
407: for (int i = 0; i < componentUpdates.length; ++i) {
408: if (componentUpdates[i] == null) {
409: // Update removed, do nothing.
410: continue;
411: }
412:
413: // Dispose of removed children.
414: Component[] removedChildren = componentUpdates[i]
415: .getRemovedChildren();
416: disposeComponents(rc, componentUpdates[i],
417: removedChildren);
418:
419: // Dispose of removed descendants.
420: Component[] removedDescendants = componentUpdates[i]
421: .getRemovedDescendants();
422: disposeComponents(rc, componentUpdates[i],
423: removedDescendants);
424:
425: // Perform update.
426: Component parentComponent = componentUpdates[i]
427: .getParent();
428: if (!isAncestor(fullyReplacedHierarchies,
429: parentComponent)) {
430: // Only perform update if ancestor of updated component is NOT contained in
431: // the set of components whose descendants were fully replaced.
432: ComponentSynchronizePeer syncPeer = SynchronizePeerFactory
433: .getPeerForComponent(parentComponent
434: .getClass());
435: String targetId;
436: if (parentComponent.getParent() == null) {
437: targetId = null;
438: } else {
439: ComponentSynchronizePeer parentSyncPeer = SynchronizePeerFactory
440: .getPeerForComponent(parentComponent
441: .getParent().getClass());
442: targetId = parentSyncPeer
443: .getContainerId(parentComponent);
444: }
445: boolean fullReplacement = syncPeer.renderUpdate(rc,
446: componentUpdates[i], targetId);
447: if (fullReplacement) {
448: // Invoke renderDispose() on hierarchy of components destroyed by
449: // the complete replacement.
450: disposeReplacedDescendants(rc,
451: componentUpdates[i], parentComponent);
452: fullyReplacedHierarchies.add(parentComponent);
453: }
454: }
455: }
456: }
457: }
458:
459: /**
460: * @see nextapp.echo2.webrender.service.SynchronizeService#renderInit(nextapp.echo2.webrender.Connection,
461: * org.w3c.dom.Document)
462: */
463: protected ServerMessage renderInit(Connection conn,
464: Document clientMessageDocument) {
465: ServerMessage serverMessage = new ServerMessage();
466: RenderContext rc = new RenderContextImpl(conn, serverMessage);
467: ContainerInstance containerInstance = rc.getContainerInstance();
468: try {
469: serverMessage.addLibrary(WEB_CONTAINER_SERVICE.getId());
470:
471: processClientMessage(conn, clientMessageDocument);
472:
473: if (!containerInstance.isInitialized()) {
474: containerInstance.init(conn);
475: }
476:
477: ApplicationInstance applicationInstance = rc
478: .getContainerInstance().getApplicationInstance();
479: ApplicationInstance.setActive(applicationInstance);
480:
481: Window window = applicationInstance.getDefaultWindow();
482:
483: ServerComponentUpdate componentUpdate = new ServerComponentUpdate(
484: window);
485: ComponentSynchronizePeer syncPeer = SynchronizePeerFactory
486: .getPeerForComponent(window.getClass());
487: ((WindowPeer) syncPeer).renderRefresh(rc, componentUpdate,
488: window);
489:
490: //TODO. clean-up how these operations are invoked on init/update.
491: setAsynchronousMonitorInterval(rc);
492: setFocus(rc, true);
493: setModalContextRootId(rc);
494: setRootLayoutDirection(rc);
495:
496: processQueuedCommands(rc);
497:
498: applicationInstance.getUpdateManager().purge();
499:
500: return serverMessage;
501: } finally {
502: ApplicationInstance.setActive(null);
503: }
504: }
505:
506: /**
507: * @see nextapp.echo2.webrender.service.SynchronizeService#renderUpdate(nextapp.echo2.webrender.Connection,
508: * org.w3c.dom.Document)
509: */
510: protected ServerMessage renderUpdate(Connection conn,
511: Document clientMessageDocument) {
512: ServerMessage serverMessage = new ServerMessage();
513: RenderContext rc = new RenderContextImpl(conn, serverMessage);
514:
515: ContainerInstance ci = rc.getContainerInstance();
516: ApplicationInstance applicationInstance = ci
517: .getApplicationInstance();
518:
519: try {
520: if (!validateTransactionId(ci, clientMessageDocument)) {
521: processInvalidTransaction(rc);
522: return serverMessage;
523: }
524:
525: // Mark instance as active.
526: ApplicationInstance.setActive(applicationInstance);
527:
528: UpdateManager updateManager = applicationInstance
529: .getUpdateManager();
530:
531: processClientFocusedComponent(rc, clientMessageDocument);
532:
533: // Process updates from client.
534: processClientMessage(conn, clientMessageDocument);
535:
536: updateManager.processClientUpdates();
537:
538: // Process updates from server.
539: processServerUpdates(rc);
540:
541: setAsynchronousMonitorInterval(rc);
542: setFocus(rc, false);
543: setModalContextRootId(rc);
544:
545: processQueuedCommands(rc);
546:
547: updateManager.purge();
548:
549: return serverMessage;
550: } finally {
551: // Mark instance as inactive.
552: ApplicationInstance.setActive(null);
553: }
554: }
555:
556: /**
557: * Sets the interval between asynchronous monitor requests.
558: *
559: * @param rc the relevant <code>RenderContext</code>.
560: */
561: private void setAsynchronousMonitorInterval(RenderContext rc) {
562: boolean hasTaskQueues = rc.getContainerInstance()
563: .getApplicationInstance().hasTaskQueues();
564: if (hasTaskQueues) {
565: int interval = rc.getContainerInstance()
566: .getCallbackInterval();
567: rc.getServerMessage().setAsynchronousMonitorInterval(
568: interval);
569: } else {
570: rc.getServerMessage().setAsynchronousMonitorInterval(-1);
571: }
572: }
573:
574: /**
575: * Update the <code>ServerMessage</code> to set the focused component if
576: * required.
577: *
578: * @param rc the relevant <code>RenderContext</code>
579: * @param initial a flag indicating whether the initial synchronization is
580: * being performed, i.e., whether this method is being invoked from
581: * <code>renderInit()</code>
582: */
583: private void setFocus(RenderContext rc, boolean initial) {
584: ApplicationInstance applicationInstance = rc
585: .getContainerInstance().getApplicationInstance();
586: Component focusedComponent = null;
587: if (initial) {
588: focusedComponent = applicationInstance
589: .getFocusedComponent();
590: } else {
591: ServerUpdateManager serverUpdateManager = applicationInstance
592: .getUpdateManager().getServerUpdateManager();
593: PropertyUpdate focusUpdate = serverUpdateManager
594: .getApplicationPropertyUpdate(ApplicationInstance.FOCUSED_COMPONENT_CHANGED_PROPERTY);
595: if (focusUpdate != null) {
596: focusedComponent = (Component) focusUpdate
597: .getNewValue();
598: }
599: }
600:
601: if (focusedComponent != null) {
602: ComponentSynchronizePeer componentSyncPeer = SynchronizePeerFactory
603: .getPeerForComponent(focusedComponent.getClass());
604: if (componentSyncPeer instanceof FocusSupport) {
605: ((FocusSupport) componentSyncPeer).renderSetFocus(rc,
606: focusedComponent);
607: }
608: }
609: }
610:
611: /**
612: * Update the <code>ServerMessage</code> to describe the current root
613: * element of the modal context.
614: *
615: * @param rc the relevant <code>RenderContext</code>
616: */
617: private void setModalContextRootId(RenderContext rc) {
618: ApplicationInstance applicationInstance = rc
619: .getContainerInstance().getApplicationInstance();
620: Component modalContextRoot = applicationInstance
621: .getModalContextRoot();
622: if (modalContextRoot == null) {
623: rc.getServerMessage().setModalContextRootId(null);
624: } else {
625: rc.getServerMessage().setModalContextRootId(
626: ContainerInstance.getElementId(modalContextRoot));
627: }
628: }
629:
630: /**
631: * Update the <code>ServerMessage</code> to describe the current root
632: * layout direction
633: *
634: * @param rc the relevant <code>RenderContext</code>
635: */
636: private void setRootLayoutDirection(RenderContext rc) {
637: ApplicationInstance applicationInstance = rc
638: .getContainerInstance().getApplicationInstance();
639: rc.getServerMessage().setRootLayoutDirection(
640: applicationInstance.getLayoutDirection()
641: .isLeftToRight() ? ServerMessage.LEFT_TO_RIGHT
642: : ServerMessage.RIGHT_TO_LEFT);
643: }
644:
645: /**
646: * Determines if transaction id retrieved from client matches current transaction id.
647: *
648: * @param containerInstance the relevant <code>ContainerInstance</code>
649: * @param clientMessageDocument the incoming client message
650: * @return true if the transaction id is valid
651: */
652: private boolean validateTransactionId(
653: ContainerInstance containerInstance,
654: Document clientMessageDocument) {
655: try {
656: long clientTransactionId = Long
657: .parseLong(clientMessageDocument
658: .getDocumentElement().getAttribute(
659: "trans-id"));
660: return containerInstance.getCurrentTransactionId() == clientTransactionId;
661: } catch (NumberFormatException ex) {
662: // Client has not provided a transaction id at all, return true.
663: // This should not occur.
664: return true;
665: }
666: }
667: }
|