1 /* 2 * $Id: MuleEventContext.java 7963 2007-08-21 08:53:15Z dirk.olmes $ 3 * -------------------------------------------------------------------------------------- 4 * Copyright (c) MuleSource, Inc. All rights reserved. http://www.mulesource.com 5 * 6 * The software in this package is published under the terms of the CPAL v1.0 7 * license, a copy of which has been included with this distribution in the 8 * LICENSE.txt file. 9 */ 10 11 package org.mule.impl; 12 13 import org.mule.MuleManager; 14 import org.mule.config.MuleProperties; 15 import org.mule.config.i18n.CoreMessages; 16 import org.mule.impl.endpoint.MuleEndpoint; 17 import org.mule.transaction.TransactionCoordination; 18 import org.mule.umo.FutureMessageResult; 19 import org.mule.umo.TransactionException; 20 import org.mule.umo.UMODescriptor; 21 import org.mule.umo.UMOEvent; 22 import org.mule.umo.UMOEventContext; 23 import org.mule.umo.UMOException; 24 import org.mule.umo.UMOMessage; 25 import org.mule.umo.UMOSession; 26 import org.mule.umo.UMOTransaction; 27 import org.mule.umo.endpoint.UMOEndpoint; 28 import org.mule.umo.endpoint.UMOEndpointURI; 29 import org.mule.umo.endpoint.UMOImmutableEndpoint; 30 import org.mule.umo.transformer.TransformerException; 31 32 import java.io.OutputStream; 33 34 import edu.emory.mathcs.backport.java.util.concurrent.Callable; 35 import org.apache.commons.logging.Log; 36 import org.apache.commons.logging.LogFactory; 37 38 /** 39 * <code>MuleEventContext</code> is the context object for the current request. 40 * Using the context, developers can send/dispatch/receive events programmatically as 41 * well as manage transactions. 42 */ 43 public class MuleEventContext implements UMOEventContext 44 { 45 /** 46 * logger used by this class 47 */ 48 protected static final Log logger = LogFactory.getLog(MuleEventContext.class); 49 50 private final UMOEvent event; 51 private final UMOSession session; 52 53 MuleEventContext(UMOEvent event) 54 { 55 this.event = event; 56 this.session = event.getSession(); 57 } 58 59 /** 60 * Returns the message payload for this event 61 * 62 * @return the message payload for this event 63 */ 64 public UMOMessage getMessage() 65 { 66 return event.getMessage(); 67 } 68 69 /** 70 * Reterns the conents of the message as a byte array. 71 * 72 * @return the conents of the message as a byte array 73 * @throws org.mule.umo.UMOException if the message cannot be converted into an 74 * array of bytes 75 */ 76 public byte[] getMessageAsBytes() throws UMOException 77 { 78 return event.getMessageAsBytes(); 79 } 80 81 /** 82 * Returns the message transformed into it's recognised or expected format. The 83 * transformer used is the one configured on the endpoint through which this 84 * event was received. 85 * 86 * @return the message transformed into it's recognised or expected format. 87 * @throws org.mule.umo.transformer.TransformerException if a failure occurs in 88 * the transformer 89 * @see org.mule.umo.transformer.UMOTransformer 90 */ 91 public Object getTransformedMessage() throws TransformerException 92 { 93 return event.getTransformedMessage(); 94 } 95 96 /** 97 * Returns the message transformed into its recognised or expected format. The 98 * transformer used is the one configured on the endpoint through which this 99 * event was received. 100 * 101 * @param expectedType The class type required for the return object. This param 102 * just provides a convienient way to manage type casting of 103 * transformed objects 104 * @return the message transformed into it's recognised or expected format. 105 * @throws org.mule.umo.transformer.TransformerException if a failure occurs or 106 * if the return type is not the same as the expected type in the 107 * transformer 108 * @see org.mule.umo.transformer.UMOTransformer 109 */ 110 public Object getTransformedMessage(Class expectedType) throws TransformerException 111 { 112 Object message = getTransformedMessage(); 113 if (expectedType != null && expectedType.isAssignableFrom(message.getClass())) 114 { 115 return message; 116 } 117 else 118 { 119 throw new TransformerException( 120 CoreMessages.transformOnObjectNotOfSpecifiedType(this.getComponentDescriptor().getName(), 121 expectedType), this.event.getEndpoint().getTransformer()); 122 } 123 } 124 125 /** 126 * Returns the message transformed into it's recognised or expected format and 127 * then into an array of bytes. The transformer used is the one configured on the 128 * endpoint through which this event was received. 129 * 130 * @return the message transformed into it's recognised or expected format as an 131 * array of bytes. 132 * @throws org.mule.umo.transformer.TransformerException if a failure occurs in 133 * the transformer 134 * @see org.mule.umo.transformer.UMOTransformer 135 */ 136 public byte[] getTransformedMessageAsBytes() throws TransformerException 137 { 138 return event.getTransformedMessageAsBytes(); 139 } 140 141 /** 142 * Returns the message transformed into it's recognised or expected format and 143 * then into a String. The transformer used is the one configured on the endpoint 144 * through which this event was received. 145 * 146 * @return the message transformed into it's recognised or expected format as a 147 * Strings. 148 * @throws org.mule.umo.transformer.TransformerException if a failure occurs in 149 * the transformer 150 * @see org.mule.umo.transformer.UMOTransformer 151 */ 152 public String getTransformedMessageAsString(String encoding) throws TransformerException 153 { 154 return event.getTransformedMessageAsString(encoding); 155 } 156 157 /** 158 * Returns the message contents as a string 159 * 160 * @return the message contents as a string 161 * @throws org.mule.umo.UMOException if the message cannot be converted into a 162 * string 163 */ 164 public String getMessageAsString(String encoding) throws UMOException 165 { 166 return event.getMessageAsString(encoding); 167 } 168 169 /** 170 * Returns the message transformed into it's recognised or expected format and 171 * then into a String. The transformer used is the one configured on the endpoint 172 * through which this event was received. This method will use the default 173 * encoding on the event 174 * 175 * @return the message transformed into it's recognised or expected format as a 176 * Strings. 177 * @throws org.mule.umo.transformer.TransformerException if a failure occurs in 178 * the transformer 179 * @see org.mule.umo.transformer.UMOTransformer 180 */ 181 public String getTransformedMessageAsString() throws TransformerException 182 { 183 return event.getTransformedMessageAsString(); 184 } 185 186 /** 187 * Returns the message contents as a string This method will use the default 188 * encoding on the event 189 * 190 * @return the message contents as a string 191 * @throws org.mule.umo.UMOException if the message cannot be converted into a 192 * string 193 */ 194 public String getMessageAsString() throws UMOException 195 { 196 return event.getMessageAsString(); 197 } 198 199 /** 200 * Returns the current transaction (if any) for the session 201 * 202 * @return the current transaction for the session or null if there is no 203 * transaction in progress 204 */ 205 public UMOTransaction getCurrentTransaction() 206 { 207 return TransactionCoordination.getInstance().getTransaction(); 208 } 209 210 public void markTransactionForRollback() throws TransactionException 211 { 212 if (getCurrentTransaction() != null) 213 { 214 getCurrentTransaction().setRollbackOnly(); 215 } 216 } 217 218 /** 219 * This will send an event via the configured outbound router on the component 220 * 221 * @param message the message to send 222 * @return the result of the send if any 223 * @throws org.mule.umo.UMOException if there is no outbound endpoint configured 224 * on the component or the events fails during dispatch 225 */ 226 public UMOMessage sendEvent(Object message) throws UMOException 227 { 228 return sendEvent(new MuleMessage(message, event.getMessage())); 229 } 230 231 /** 232 * Depending on the session state this methods either Passes an event 233 * synchronously to the next available Mule UMO in the pool or via the endpoint 234 * configured for the event 235 * 236 * @param message the event message payload to send 237 * @param endpoint The endpoint to disptch the event through. 238 * @return the return Message from the call or null if there was no result 239 * @throws org.mule.umo.UMOException if the event fails to be processed by the 240 * component or the transport for the endpoint 241 */ 242 public UMOMessage sendEvent(UMOMessage message, UMOEndpoint endpoint) throws UMOException 243 { 244 // If synchronous receive has not been explicitly set, default it to true 245 setRemoteSync(message, endpoint); 246 return session.sendEvent(message, endpoint); 247 } 248 249 /** 250 * Depending on the session state this methods either Passes an event 251 * synchronously to the next available Mule UMO in the pool or via the endpoint 252 * configured for the event 253 * 254 * @param message the message payload to send 255 * @return the return Message from the call or null if there was no result 256 * @throws org.mule.umo.UMOException if the event fails to be processed by the 257 * component or the transport for the endpoint 258 */ 259 public UMOMessage sendEvent(UMOMessage message) throws UMOException 260 { 261 // If synchronous receive has not been explicitly set, default it to 262 // true 263 setRemoteSync(message, event.getEndpoint()); 264 return session.sendEvent(message); 265 } 266 267 /** 268 * Depending on the session state this methods either Passes an event 269 * synchronously to the next available Mule UMO in the pool or via the 270 * endpointUri configured for the event 271 * 272 * @param message the event message payload to send 273 * @param endpointUri The endpointUri to disptch the event through 274 * @return the return Message from the call or null if there was no result 275 * @throws org.mule.umo.UMOException if the event fails to be processed by the 276 * component or the transport for the endpointUri 277 */ 278 public UMOMessage sendEvent(UMOMessage message, UMOEndpointURI endpointUri) throws UMOException 279 { 280 UMOEndpoint endpoint = MuleEndpoint.getOrCreateEndpointForUri(endpointUri, 281 UMOEndpoint.ENDPOINT_TYPE_SENDER); 282 283 // If synchronous receive has not been explicitly set, default it to 284 // true 285 setRemoteSync(message, endpoint); 286 return session.sendEvent(message, endpoint); 287 } 288 289 /** 290 * sends an event request via the configured outbound router for this component. 291 * This method return immediately, but the result of the event invocation 292 * available from the returned a Future result that can be accessed later by the 293 * the returned FutureMessageResult. the Future messageResult can be queried at 294 * any time to check that the invocation has completed. A timeout is associated 295 * with the invocation, which is the maximum time in milli-seconds that the 296 * invocation should take to complete 297 * 298 * @param message the object that is the payload of the event 299 * @param timeout how long to block in milliseconds waiting for a result 300 * @return the result message if any of the invocation 301 * @throws org.mule.umo.UMOException if the dispatch fails or the components or 302 * transfromers cannot be found 303 * @see org.mule.umo.FutureMessageResult 304 */ 305 public FutureMessageResult sendEventAsync(final Object message, final int timeout) throws UMOException 306 { 307 Callable callable = new Callable() 308 { 309 public Object call() throws Exception 310 { 311 UMOMessage umoMessage = new MuleMessage(message, event.getMessage()); 312 umoMessage.setBooleanProperty(MuleProperties.MULE_REMOTE_SYNC_PROPERTY, true); 313 umoMessage.setIntProperty(MuleProperties.MULE_EVENT_TIMEOUT_PROPERTY, timeout); 314 return sendEvent(umoMessage); 315 } 316 }; 317 318 FutureMessageResult result = new FutureMessageResult(callable); 319 // TODO MULE-732: use injected ExecutorService 320 result.execute(); 321 return result; 322 } 323 324 /** 325 * sends an event request via the configured outbound router for this component. 326 * This method return immediately, but the result of the event invocation 327 * available from the returned a Future result that can be accessed later by the 328 * the returned FutureMessageResult. the Future messageResult can be queried at 329 * any time to check that the invocation has completed. A timeout is associated 330 * with the invocation, which is the maximum time in milli-seconds that the 331 * invocation should take to complete 332 * 333 * @param message the UMOMessage of the event 334 * @param timeout how long to block in milliseconds waiting for a result 335 * @return the result message if any of the invocation 336 * @throws org.mule.umo.UMOException if the dispatch fails or the components or 337 * transfromers cannot be found 338 * @see org.mule.umo.FutureMessageResult 339 */ 340 public FutureMessageResult sendEventAsync(final UMOMessage message, final int timeout) 341 throws UMOException 342 { 343 Callable callable = new Callable() 344 { 345 public Object call() throws Exception 346 { 347 message.setBooleanProperty(MuleProperties.MULE_REMOTE_SYNC_PROPERTY, true); 348 message.setIntProperty(MuleProperties.MULE_EVENT_TIMEOUT_PROPERTY, timeout); 349 return sendEvent(message); 350 } 351 }; 352 353 FutureMessageResult result = new FutureMessageResult(callable); 354 // TODO MULE-732: use injected ExecutorService 355 result.execute(); 356 return result; 357 } 358 359 /** 360 * sends an event request via the configured outbound router for this component. 361 * This method return immediately, but the result of the event invocation 362 * available from the returned a Future result that can be accessed later by the 363 * the returned FutureMessageResult. the Future messageResult can be queried at 364 * any time to check that the invocation has completed. A timeout is associated 365 * with the invocation, which is the maximum time in milli-seconds that the 366 * invocation should take to complete 367 * 368 * @param message the UMOMessage of the event 369 * @param endpointUri the endpointUri to dispatch to 370 * @param timeout how long to block in milliseconds waiting for a result 371 * @return the result message if any of the invocation 372 * @throws org.mule.umo.UMOException if the dispatch fails or the components or 373 * transfromers cannot be found 374 * @see org.mule.umo.FutureMessageResult 375 */ 376 public FutureMessageResult sendEventAsync(final UMOMessage message, 377 final UMOEndpointURI endpointUri, 378 final int timeout) throws UMOException 379 { 380 Callable callable = new Callable() 381 { 382 public Object call() throws Exception 383 { 384 message.setBooleanProperty(MuleProperties.MULE_REMOTE_SYNC_PROPERTY, true); 385 message.setIntProperty(MuleProperties.MULE_EVENT_TIMEOUT_PROPERTY, timeout); 386 return sendEvent(message, endpointUri); 387 } 388 }; 389 390 FutureMessageResult result = new FutureMessageResult(callable); 391 // TODO MULE-732: use injected ExecutorService 392 result.execute(); 393 return result; 394 } 395 396 /** 397 * sends an event request via the configured outbound router for this component. 398 * This method return immediately, but the result of the event invocation 399 * available from the returned a Future result that can be accessed later by the 400 * the returned FutureMessageResult. the Future messageResult can be queried at 401 * any time to check that the invocation has completed. A timeout is associated 402 * with the invocation, which is the maximum time in milli-seconds that the 403 * invocation should take to complete 404 * 405 * @param message the UMOMessage of the event 406 * @param endpointName The endpoint name to disptch the event through. This will 407 * be looked up first on the component configuration and then on the 408 * mule manager configuration 409 * @param timeout how long to block in milliseconds waiting for a result 410 * @return the result message if any of the invocation 411 * @throws org.mule.umo.UMOException if the dispatch fails or the components or 412 * transfromers cannot be found 413 * @see org.mule.umo.FutureMessageResult 414 */ 415 public FutureMessageResult sendEventAsync(final UMOMessage message, 416 final String endpointName, 417 final int timeout) throws UMOException 418 { 419 Callable callable = new Callable() 420 { 421 public Object call() throws Exception 422 { 423 message.setBooleanProperty(MuleProperties.MULE_REMOTE_SYNC_PROPERTY, true); 424 message.setIntProperty(MuleProperties.MULE_EVENT_TIMEOUT_PROPERTY, timeout); 425 return sendEvent(message, endpointName); 426 } 427 }; 428 429 FutureMessageResult result = new FutureMessageResult(callable); 430 // TODO MULE-732: use injected ExecutorService 431 result.execute(); 432 return result; 433 } 434 435 /** 436 * Depending on the session state this methods either Passes an event 437 * synchronously to the next available Mule UMO in the pool or via the endpoint 438 * configured for the event 439 * 440 * @param message the event message payload to send 441 * @param endpointName The endpoint name to disptch the event through. This will 442 * be looked up first on the component configuration and then on the 443 * mule manager configuration 444 * @return the return Message from the call or null if there was no result 445 * @throws org.mule.umo.UMOException if the event fails to be processed by the 446 * component or the transport for the endpoint 447 */ 448 public UMOMessage sendEvent(UMOMessage message, String endpointName) throws UMOException 449 { 450 UMOEndpoint endpoint = MuleManager.getInstance().lookupEndpoint(endpointName); 451 setRemoteSync(message, endpoint); 452 return session.sendEvent(message, endpoint); 453 } 454 455 /** 456 * This will dispatch an event asynchronously via the configured outbound 457 * endpoint on the component for this session 458 * 459 * @param message payload to dispatch 460 * @throws org.mule.umo.UMOException if there is no outbound endpoint configured 461 * on the component or the events fails during dispatch 462 */ 463 public void dispatchEvent(Object message) throws UMOException 464 { 465 session.dispatchEvent(new MuleMessage(message, event.getMessage())); 466 } 467 468 /** 469 * This will dispatch an event asynchronously via the configured outbound 470 * endpoint on the component for this session 471 * 472 * @param message the message to send 473 * @throws org.mule.umo.UMOException if there is no outbound endpoint configured 474 * on the component or the events fails during dispatch 475 */ 476 public void dispatchEvent(UMOMessage message) throws UMOException 477 { 478 session.dispatchEvent(message); 479 } 480 481 /** 482 * Depending on the session state this methods either Passes an event 483 * asynchronously to the next available Mule UMO in the pool or via the 484 * endpointUri configured for the event 485 * 486 * @param message the event message payload to send 487 * @param endpointUri the endpointUri to dispatc the event to first on the 488 * component configuration and then on the mule manager configuration 489 * @throws org.mule.umo.UMOException if the event fails to be processed by the 490 * component or the transport for the endpointUri 491 */ 492 public void dispatchEvent(UMOMessage message, UMOEndpointURI endpointUri) throws UMOException 493 { 494 UMOEndpoint endpoint = MuleEndpoint.getOrCreateEndpointForUri(endpointUri, 495 UMOEndpoint.ENDPOINT_TYPE_SENDER); 496 session.dispatchEvent(message, endpoint); 497 } 498 499 /** 500 * Depending on the session state this methods either Passes an event 501 * asynchronously to the next available Mule UMO in the pool or via the endpoint 502 * configured for the event 503 * 504 * @param message the event message payload to send 505 * @param endpointName The endpoint name to disptch the event through. This will 506 * be looked up first on the component configuration and then on the 507 * mule manager configuration 508 * @throws org.mule.umo.UMOException if the event fails to be processed by the 509 * component or the transport for the endpoint 510 */ 511 public void dispatchEvent(UMOMessage message, String endpointName) throws UMOException 512 { 513 session.dispatchEvent(message, endpointName); 514 } 515 516 /** 517 * Depending on the session state this methods either Passes an event 518 * asynchronously to the next available Mule UMO in the pool or via the endpoint 519 * configured for the event 520 * 521 * @param message the event message payload to send 522 * @param endpoint The endpoint name to disptch the event through. 523 * @throws org.mule.umo.UMOException if the event fails to be processed by the 524 * component or the transport for the endpoint 525 */ 526 public void dispatchEvent(UMOMessage message, UMOEndpoint endpoint) throws UMOException 527 { 528 session.dispatchEvent(message, endpoint); 529 } 530 531 /** 532 * Requests a synchronous receive of an event on the component 533 * 534 * @param endpoint the endpoint identifing the endpointUri on ewhich the event 535 * will be received 536 * @param timeout time in milliseconds before the request timesout 537 * @return The requested event or null if the request times out 538 * @throws org.mule.umo.UMOException if the request operation fails 539 */ 540 public UMOMessage receiveEvent(UMOEndpoint endpoint, long timeout) throws UMOException 541 { 542 return session.receiveEvent(endpoint, timeout); 543 } 544 545 /** 546 * Requests a synchronous receive of an event on the component 547 * 548 * @param endpointName the endpoint identifing the endpointUri on ewhich the 549 * event will be received 550 * @param timeout time in milliseconds before the request timesout 551 * @return The requested event or null if the request times out 552 * @throws org.mule.umo.UMOException if the request operation fails 553 */ 554 public UMOMessage receiveEvent(String endpointName, long timeout) throws UMOException 555 { 556 return session.receiveEvent(endpointName, timeout); 557 } 558 559 /** 560 * Requests a synchronous receive of an event on the component 561 * 562 * @param endpointUri the endpointUri on which the event will be received 563 * @param timeout time in milliseconds before the request timesout 564 * @return The requested event or null if the request times out 565 * @throws org.mule.umo.UMOException if the request operation fails 566 */ 567 public UMOMessage receiveEvent(UMOEndpointURI endpointUri, long timeout) throws UMOException 568 { 569 UMOEndpoint endpoint = MuleEndpoint.getOrCreateEndpointForUri(endpointUri, 570 UMOEndpoint.ENDPOINT_TYPE_SENDER); 571 return session.receiveEvent(endpoint, timeout); 572 } 573 574 /** 575 * @return the component descriptor of the component that received this event 576 */ 577 public UMODescriptor getComponentDescriptor() 578 { 579 if (event.getComponent() != null) 580 { 581 return event.getComponent().getDescriptor(); 582 } 583 else 584 { 585 return null; 586 } 587 } 588 589 /** 590 * Determines whether the default processing for this event will be executed. By 591 * default, the Mule server will route events according to a components 592 * configuration. The user can override this behaviour by obtaining a reference 593 * to the Event context, either by implementing 594 * <code>org.mule.umo.lifecycle.Callable</code> or calling 595 * <code>RequestContext.getEventContext</code> to obtain the UMOEventContext for 596 * the current thread. The user can programmatically control how events are 597 * dispatched. 598 * 599 * @return Returns true is the user has set stopFurtherProcessing. 600 * @see org.mule.umo.manager.UMOManager 601 * @see org.mule.umo.UMOEventContext 602 * @see org.mule.umo.lifecycle.Callable 603 */ 604 public boolean isStopFurtherProcessing() 605 { 606 return RequestContext.getEvent().isStopFurtherProcessing(); 607 } 608 609 /** 610 * Determines whether the default processing for this event will be executed. By 611 * default, the Mule server will route events according to a components 612 * configuration. The user can override this behaviour by obtaining a reference 613 * to the Event context, either by implementing 614 * <code>org.mule.umo.lifecycle.Callable</code> or calling 615 * <code>UMOManager.getEventContext</code> to obtain the UMOEventContext for 616 * the current thread. The user can programmatically control how events are 617 * dispached. 618 * 619 * @param stopFurtherProcessing the value to set. 620 */ 621 public void setStopFurtherProcessing(boolean stopFurtherProcessing) 622 { 623 RequestContext.getEvent().setStopFurtherProcessing(stopFurtherProcessing); 624 } 625 626 /** 627 * An outputstream the can optionally be used write response data to an incoming 628 * message. 629 * 630 * @return an output strem if one has been made available by the message receiver 631 * that received the message 632 */ 633 public OutputStream getOutputStream() 634 { 635 return event.getOutputStream(); 636 } 637 638 /** 639 * Determines whether the was sent synchrounously or not 640 * 641 * @return true if the event is synchronous 642 */ 643 public boolean isSynchronous() 644 { 645 return event.isSynchronous(); 646 } 647 648 public UMOEndpointURI getEndpointURI() 649 { 650 return event.getEndpoint().getEndpointURI(); 651 } 652 653 /** 654 * Returns the transaction for the current event or null if there is no 655 * transaction in progresss 656 * 657 * @return the transaction for the current event or null if there is no 658 * transaction in progresss 659 */ 660 public UMOTransaction getTransaction() 661 { 662 return TransactionCoordination.getInstance().getTransaction(); 663 } 664 665 /** 666 * Get the timeout value associated with the event 667 * 668 * @return the timeout for the event 669 */ 670 public int getTimeout() 671 { 672 return event.getTimeout(); 673 } 674 675 private void setRemoteSync(UMOMessage message, UMOImmutableEndpoint endpoint) 676 { 677 if (endpoint.isRemoteSync()) 678 { 679 if (getTransaction() == null) 680 { 681 message.setBooleanProperty(MuleProperties.MULE_REMOTE_SYNC_PROPERTY, true); 682 } 683 else 684 { 685 throw new IllegalStateException( 686 CoreMessages.cannotUseTxAndRemoteSync().getMessage()); 687 } 688 } 689 } 690 691 /** 692 * Determines whether the event flow is being streamed 693 * 694 * @return true if the request should be streamed 695 */ 696 public boolean isStreaming() 697 { 698 return event.getEndpoint().isStreaming(); 699 } 700 701 /** 702 * Gets the encoding for the current message. For potocols that send encoding 703 * Information with the message, this method should be overriden to expose the 704 * transport encoding, otherwise the default encoding in the Mule configuration 705 * will be used 706 * 707 * @return the encoding for this message. This method must never return null 708 */ 709 public String getEncoding() 710 { 711 return event.getEncoding(); 712 } 713 714 public UMOSession getSession() 715 { 716 return event.getSession(); 717 } 718 719 public String toString() 720 { 721 return event.toString(); 722 } 723 724 }