001/* Catch exceptions and handle them with the specified policy. 002 003 Copyright (c) 2014 The Regents of the University of California. 004 All rights reserved. 005 Permission is hereby granted, without written agreement and without 006 license or royalty fees, to use, copy, modify, and distribute this 007 software and its documentation for any purpose, provided that the above 008 copyright notice and the following two paragraphs appear in all copies 009 of this software. 010 011 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY 012 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES 013 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF 014 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF 015 SUCH DAMAGE. 016 017 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 018 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 019 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE 020 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF 021 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, 022 ENHANCEMENTS, OR MODIFICATIONS. 023 024 PT_COPYRIGHT_VERSION_2 025 COPYRIGHTENDKEY 026 027 */ 028package ptolemy.actor.lib; 029 030import java.text.SimpleDateFormat; 031import java.util.ArrayList; 032import java.util.Date; 033import java.util.Iterator; 034import java.util.LinkedHashSet; 035import java.util.Set; 036 037import ptolemy.actor.AbstractInitializableAttribute; 038import ptolemy.actor.Actor; 039import ptolemy.actor.CompositeActor; 040import ptolemy.actor.ExecutionListener; 041import ptolemy.actor.Initializable; 042import ptolemy.actor.Manager; 043import ptolemy.actor.lib.gui.ExceptionManagerGUIFactory; 044import ptolemy.data.expr.StringParameter; 045import ptolemy.kernel.util.ExceptionHandler; 046import ptolemy.kernel.util.HierarchyListener; 047import ptolemy.kernel.util.IllegalActionException; 048import ptolemy.kernel.util.NameDuplicationException; 049import ptolemy.kernel.util.NamedObj; 050import ptolemy.kernel.util.Settable; 051import ptolemy.kernel.util.Workspace; 052import ptolemy.moml.MoMLModelAttribute; 053 054/////////////////////////////////////////////////////////////////// 055//// ExceptionManager 056 057/** 058 The ExceptionManager catches exceptions and attempts to handle them with the 059 specified policy. It notifies ExceptionSubscribers after an exception has 060 occurred and again after the handling attempt. If the exception cannot be 061 handled, the model Manager is informed. 062 063 @author Elizabeth Latronico 064 @version $Id$ 065 @since Ptolemy II 10.0 066 @Pt.ProposedRating Red (beth) 067 @Pt.AcceptedRating Red (beth) 068 */ 069 070public class ExceptionManager extends MoMLModelAttribute implements 071 ExceptionHandler, ExecutionListener, Initializable, HierarchyListener { 072 073 /** Create a model attribute with the specified container and name. 074 * @param container The specified container. 075 * @param name The specified name. 076 * @exception IllegalActionException If the attribute is not of an 077 * acceptable class for the container, or if the name contains a period. 078 * @exception NameDuplicationException If the name coincides with an 079 * attribute already in the container. 080 */ 081 public ExceptionManager(NamedObj container, String name) 082 throws NameDuplicationException, IllegalActionException { 083 super(container, name); 084 085 // Provide a default model 086 _model = new ExceptionManagerModel(this, workspace()); 087 088 // TODO: Icon should list contained commands and status of each 089 _attachText("_iconDescription", "<svg>\n" 090 + "<rect x=\"-50\" y=\"-20\" width=\"100\" height=\"40\" " 091 + "style=\"fill:blue\"/>" + "<text x=\"-40\" y=\"-5\" " 092 + "style=\"font-size:14; font-family:SansSerif; fill:white;\">" 093 + "Exception \nManager</text></svg>"); 094 095 new ExceptionManagerGUIFactory(this, "_ExceptionManagerGUIFactory"); 096 097 policy = new StringParameter(this, "policy"); 098 policy.setExpression(CatchExceptionAttribute.THROW); 099 100 policy.addChoice(CatchExceptionAttribute.RESTART); 101 policy.addChoice(CatchExceptionAttribute.STOP); 102 policy.addChoice(CatchExceptionAttribute.THROW); 103 104 exceptionMessage = new StringParameter(this, "exceptionMessage"); 105 exceptionMessage.setExpression("No exceptions encountered"); 106 exceptionMessage.setVisibility(Settable.NOT_EDITABLE); 107 108 statusMessage = new StringParameter(this, "statusMessage"); 109 statusMessage.setExpression("No exceptions encountered"); 110 statusMessage.setVisibility(Settable.NOT_EDITABLE); 111 112 modelURL.setVisibility(Settable.NONE); 113 114 _resetMessages = true; 115 _restartDesired = false; 116 _subscribers = new ArrayList(); 117 } 118 119 /////////////////////////////////////////////////////////////////// 120 //// public variables //// 121 122 /** The exception message from the caught exception. */ 123 public StringParameter exceptionMessage; 124 125 /** The error handling policy to apply if an exception occurs. 126 * One of: restart, stop, throw. See {@link CatchExceptionAttribute} 127 */ 128 public StringParameter policy; 129 130 /** The latest action, if any, taken by the CatchExceptionAttribute. 131 * For example, a notification that the model has restarted. 132 * It offers a way to provide feedback to the user. 133 */ 134 public StringParameter statusMessage; 135 136 /////////////////////////////////////////////////////////////////// 137 //// public methods //// 138 139 /** Add the specified object to the set of objects whose 140 * preinitialize(), initialize(), and wrapup() 141 * methods should be invoked upon invocation of the corresponding 142 * methods of this object. 143 * @param initializable The object whose methods should be invoked. 144 * @see #removeInitializable(Initializable) 145 */ 146 @Override 147 public void addInitializable(Initializable initializable) { 148 if (_initializables == null) { 149 _initializables = new LinkedHashSet<Initializable>(); 150 } 151 _initializables.add(initializable); 152 } 153 154 /** Clone the object into the specified workspace. 155 * @param workspace The workspace for the new object. 156 * @return A new NamedObj. 157 * @exception CloneNotSupportedException If any of the attributes 158 * cannot be cloned. 159 */ 160 @Override 161 public Object clone(Workspace workspace) throws CloneNotSupportedException { 162 ExceptionManager newObject = (ExceptionManager) super.clone(workspace); 163 164 newObject._initializables = new LinkedHashSet<Initializable>(); 165 newObject._initialized = false; 166 newObject._resetMessages = false; 167 newObject._restartDesired = false; 168 newObject._subscribers = new ArrayList(); 169 170 return newObject; 171 } 172 173 /** Do nothing upon execution error. Exceptions are passed to this 174 * attribute through handleException(). This method is required by 175 * the ExecutionListener interface. 176 */ 177 @Override 178 public void executionError(Manager manager, Throwable throwable) { 179 180 } 181 182 /** Restart here if restart is desired. This method is called upon 183 * successful completion. 184 */ 185 @Override 186 public void executionFinished(Manager manager) { 187 if (_restartDesired) { 188 Date date = new Date(System.currentTimeMillis()); 189 SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss"); 190 191 // Start a new execution in a new thread 192 try { 193 manager.startRun(); 194 } catch (IllegalActionException e) { 195 statusMessage.setExpression("Cannot restart model. " 196 + "Manager.startRun() failed."); 197 } 198 199 statusMessage.setExpression( 200 "Model restarted at " + dateFormat.format(date)); 201 202 // Do NOT reset messages in the event of a restart 203 // This way, user can see that model was restarted 204 _resetMessages = false; 205 _restartDesired = false; 206 } 207 } 208 209 /** Notify this object that the containment hierarchy above it has 210 * changed. This method does nothing because instead we use 211 * {@link #preinitialize()} to handle re-establishing the connections. 212 * @exception IllegalActionException If the change is not 213 * acceptable. 214 * @see AbstractInitializableAttribute 215 */ 216 @Override 217 public void hierarchyChanged() throws IllegalActionException { 218 // Make sure we are registered as to be initialized 219 // with the container. 220 Initializable container = _getInitializableContainer(); 221 if (container != null) { 222 container.addInitializable(this); 223 } 224 } 225 226 /** Notify this object that the containment hierarchy above it will be 227 * changed. 228 * @exception IllegalActionException If unlinking to a published port fails. 229 * @see AbstractInitializableAttribute 230 */ 231 @Override 232 public void hierarchyWillChange() throws IllegalActionException { 233 // Unregister to be initialized with the initializable container. 234 // We will be re-registered when hierarchyChanged() is called. 235 Initializable container = _getInitializableContainer(); 236 if (container != null) { 237 container.removeInitializable(this); 238 } 239 } 240 241 /** Find all of the ExceptionSubscribers in the model and save in a list. 242 * 243 * @exception IllegalActionException If thrown by parent 244 */ 245 @Override 246 public void initialize() throws IllegalActionException { 247 248 // Use allAtomicEntityList here to include entities inside composite 249 // actors. Could switch to containedObjectsIterator in the future if we 250 // want to allow attributes to be ExceptionSubscribers. (Will need to 251 // implement a deep search. containedObjectsIterator does not look 252 // inside composite entities). 253 254 Iterator iterator = ((CompositeActor) toplevel()).allAtomicEntityList() 255 .iterator(); 256 _subscribers.clear(); 257 NamedObj obj; 258 259 while (iterator.hasNext()) { 260 obj = (NamedObj) iterator.next(); 261 if (obj instanceof ExceptionSubscriber) { 262 _subscribers.add((ExceptionSubscriber) obj); 263 } 264 } 265 266 // Also, check for entities inside the model contained by this attribute 267 iterator = _model.containedObjectsIterator(); 268 269 while (iterator.hasNext()) { 270 obj = (NamedObj) iterator.next(); 271 if (obj instanceof ExceptionSubscriber) { 272 _subscribers.add((ExceptionSubscriber) obj); 273 } 274 } 275 276 // TODO: Figure out why setting this through the constructor is not 277 // working 278 ((ExceptionManagerModel) _model).setModelContainer(this); 279 } 280 281 /** Handle an exception according to the specified policy: 282 * 283 * continue: Not implemented yet 284 * Consume the exception and return control to the director. 285 * Could be valuable for domains like DE or modal models when new 286 * events will arrive. Probably not appropriate for domains like SDF 287 * where the director follows a predefined schedule based on data flow 288 * (since the actor throwing the exception no longer provides output to 289 * the next actor). 290 * 291 * throw: Do not catch the exception. 292 * 293 * restart: Stop and restart the model. Does not apply to exceptions 294 * generated during initialize(). 295 * 296 * stop: Stop the model. 297 * 298 * @param context The object in which the error occurred. 299 * @param exception The exception to be handled. 300 * @return true if the exception is handled; false if this attribute 301 * did not handle it 302 * @exception IllegalActionException If thrown by the parent 303 */ 304 305 @Override 306 public boolean handleException(NamedObj context, Throwable exception) 307 throws IllegalActionException { 308 309 // Notify all subscribers, in the specified order, of the exception 310 // Note at this stage it is not guaranteed that the exception can be 311 // handled successfully 312 313 for (ExceptionSubscriber subscriber : _subscribers) { 314 subscriber.exceptionOccurred(policy.getValueAsString(), exception); 315 } 316 317 // Handle the exception according to the policy 318 319 // Save the exception message. Only informational at the moment. 320 exceptionMessage.setExpression(exception.getMessage()); 321 322 Date date = new Date(System.currentTimeMillis()); 323 SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss"); 324 325 // Handle the exception according to the specified policy 326 327 String policyValue = policy.stringValue(); 328 329 // Set initialized to false here, unless policy is to restart, in 330 // which case set it after the current value is checked 331 if (!policyValue.equals(CatchExceptionAttribute.RESTART)) { 332 // Set _initialized here instead of in wrapup(), since 333 // wrapup() is called prior to handleException() 334 _initialized = false; 335 } 336 337 if (policyValue.equals(CatchExceptionAttribute.RESTART)) { 338 // Restarts the model in a new thread 339 340 // Check if the model made it through initialize(). If not, return 341 // false (thereby leaving exception unhandled) 342 if (!_initialized) { 343 344 // Return false if an exception is thrown, since this attribute 345 // did not resolve the exception. 346 statusMessage.setExpression("Cannot restart: Error before " 347 + "or during intialize()"); 348 for (ExceptionSubscriber subscriber : _subscribers) { 349 subscriber.exceptionHandled(false, policyValue); 350 } 351 return false; 352 } 353 354 // Set _initialized here, instead of in wrapup(), since 355 // wrapup() is called prior to handleException() 356 _initialized = false; 357 358 // Find an actor in the model; use the actor to get the manager. 359 Manager manager = null; 360 361 NamedObj toplevel = toplevel(); 362 if (toplevel != null) { 363 Iterator iterator = toplevel.containedObjectsIterator(); 364 while (iterator.hasNext()) { 365 Object obj = iterator.next(); 366 if (obj instanceof Actor) { 367 manager = ((Actor) obj).getManager(); 368 } 369 } 370 } 371 372 if (manager != null) { 373 // End execution 374 manager.finish(); 375 376 // Wait until the manager notifies listeners of successful 377 // completion before restarting. Manager will call 378 // _executionFinished(). Set a flag here indicating to restart 379 _restartDesired = true; 380 381 } else { 382 statusMessage.setExpression("Cannot restart model since " 383 + "there is no model Manager. Perhaps the model has no " 384 + "actors?"); 385 for (ExceptionSubscriber subscriber : _subscribers) { 386 subscriber.exceptionHandled(false, policyValue); 387 } 388 return false; 389 } 390 391 } else if (policyValue.equals(CatchExceptionAttribute.STOP)) { 392 statusMessage.setExpression( 393 "Model stopped at " + dateFormat.format(date)); 394 395 // Call validate() to notify listeners of these changes 396 exceptionMessage.validate(); 397 statusMessage.validate(); 398 399 // wrapup() is automatically called prior to handleException(), 400 // so don't need to call it again 401 } else if (policyValue.equals(CatchExceptionAttribute.THROW)) { 402 statusMessage.setExpression( 403 "Exception thrown at " + dateFormat.format(date)); 404 405 // Return false if an exception is thrown, since this attribute 406 // did not resolve the exception. 407 for (ExceptionSubscriber subscriber : _subscribers) { 408 subscriber.exceptionHandled(false, policyValue); 409 } 410 return false; 411 412 } else { 413 statusMessage.setExpression("Illegal policy encountered at: " 414 + dateFormat.format(date)); 415 416 for (ExceptionSubscriber subscriber : _subscribers) { 417 subscriber.exceptionHandled(false, policyValue); 418 } 419 // Throw an exception here instead of just returning false, since 420 // this is a problem with CatchExceptionAttribute 421 throw new IllegalActionException(this, 422 "Illegal exception handling policy."); 423 } 424 425 // Call validate() to notify listeners of these changes 426 exceptionMessage.validate(); 427 statusMessage.validate(); 428 429 _resetMessages = false; 430 431 for (ExceptionSubscriber subscriber : _subscribers) { 432 subscriber.exceptionHandled(true, policyValue); 433 } 434 435 return true; 436 } 437 438 /** React to a change of state in the Manager. 439 * 440 * @param manager The model manager 441 */ 442 443 @Override 444 public void managerStateChanged(Manager manager) { 445 446 if (manager.getState().equals(Manager.INITIALIZING)) { 447 // Enable restart once all objects have been initialized 448 //_initialized is set back to false at the end of _handleException() 449 if (_resetMessages) { 450 exceptionMessage.setExpression("No exceptions encountered"); 451 statusMessage.setExpression("No exceptions encountered"); 452 453 // Call validate() to notify listeners of these changes 454 try { 455 exceptionMessage.validate(); 456 statusMessage.validate(); 457 } catch (IllegalActionException e) { 458 // TODO: What to do if parameters don't validate()? 459 } 460 } 461 462 _resetMessages = true; 463 _initialized = true; 464 } 465 } 466 467 /** Remove the specified object from the list of objects whose 468 * preinitialize(), initialize(), and wrapup() 469 * methods should be invoked upon invocation of the corresponding 470 * methods of this object. If the specified object is not 471 * on the list, do nothing. 472 * @param initializable The object whose methods should no longer be invoked. 473 * @see #addInitializable(Initializable) 474 */ 475 @Override 476 public void removeInitializable(Initializable initializable) { 477 if (_initializables != null) { 478 _initializables.remove(initializable); 479 if (_initializables.size() == 0) { 480 _initializables = null; 481 } 482 } 483 } 484 485 /** Register this attribute with the manager. Done here instead of in the 486 * constructor since the director is found in order to get the manager. 487 * The constructor for this attribute might be called before the 488 * constructor for the director. 489 * 490 * @exception IllegalActionException If the parent class throws it 491 */ 492 @Override 493 public void preinitialize() throws IllegalActionException { 494 495 // Find an actor in the model; use the actor to get the manager. 496 Manager manager = null; 497 498 NamedObj toplevel = toplevel(); 499 if (toplevel != null) { 500 Iterator iterator = toplevel.containedObjectsIterator(); 501 while (iterator.hasNext()) { 502 Object obj = iterator.next(); 503 if (obj instanceof Actor) { 504 manager = ((Actor) obj).getManager(); 505 } 506 } 507 } 508 509 if (manager != null) { 510 manager.addExecutionListener(this); 511 } else { 512 throw new IllegalActionException(this, "Manager cannot be found. " 513 + "Perhaps the model has no actors?"); 514 } 515 } 516 517 /** Override the base class to register as an 518 * {@link Initializable} 519 * so that preinitialize() is invoked, and as a 520 * {@link HierarchyListener}, so that we are notified of 521 * changes in the hierarchy above. 522 * @param container The proposed container. 523 * @exception IllegalActionException If the action would result in a 524 * recursive containment structure, or if 525 * this entity and container are not in the same workspace. 526 * @exception NameDuplicationException If the container already has 527 * an entity with the name of this entity. 528 * @see AbstractInitializableAttribute 529 */ 530 @Override 531 public void setContainer(NamedObj container) 532 throws IllegalActionException, NameDuplicationException { 533 Initializable previousInitializableContainer = _getInitializableContainer(); 534 NamedObj previousContainer = getContainer(); 535 super.setContainer(container); 536 Initializable newInitializableContainer = _getInitializableContainer(); 537 if (previousInitializableContainer != newInitializableContainer) { 538 if (previousInitializableContainer != null) { 539 previousInitializableContainer.removeInitializable(this); 540 } 541 if (newInitializableContainer != null) { 542 newInitializableContainer.addInitializable(this); 543 } 544 } 545 if (previousContainer != container) { 546 if (previousContainer != null) { 547 previousContainer.removeHierarchyListener(this); 548 } 549 if (container != null) { 550 container.addHierarchyListener(this); 551 } 552 } 553 } 554 555 /** Invoke wrapup() on registered initializables. 556 * @exception IllegalActionException If thrown by a subclass. 557 * @see AbstractInitializableAttribute 558 */ 559 @Override 560 public void wrapup() throws IllegalActionException { 561 // Invoke initializable methods. 562 if (_initializables != null) { 563 for (Initializable initializable : _initializables) { 564 initializable.wrapup(); 565 } 566 } 567 } 568 569 /////////////////////////////////////////////////////////////////// 570 //// protected methods //// 571 572 /** Return the first Initializable encountered above this 573 * in the hierarchy that will be initialized (i.e., it is either 574 * an atomic actor or an opaque composite actor). 575 * @return The first Initializable above this in the hierarchy, 576 * or null if there is none. 577 * @see AbstractInitializableAttribute 578 */ 579 protected Initializable _getInitializableContainer() { 580 NamedObj container = getContainer(); 581 while (container != null) { 582 if (container instanceof Initializable) { 583 if (container instanceof CompositeActor) { 584 if (((CompositeActor) container).isOpaque()) { 585 return (Initializable) container; 586 } 587 } else { 588 return (Initializable) container; 589 } 590 } 591 container = container.getContainer(); 592 } 593 return null; 594 } 595 596 // Commands 597 // - Message displayer (separate or integrate?) (probably easier to just integrate) 598 // - File logger 599 // - Emailer 600 601 /////////////////////////////////////////////////////////////////// 602 //// private variables //// 603 604 /** List of objects whose (pre)initialize() and wrapup() methods should be 605 * slaved to these. 606 * See {@link AbstractInitializableAttribute} 607 */ 608 private transient Set<Initializable> _initializables; 609 610 /** True if the model has been initialized but not yet wrapped up; 611 * false otherwise. Some policies (e.g. restart) are desirable only 612 * for run-time exceptions. 613 */ 614 private boolean _initialized; 615 616 /** True if the model has been started externally (e.g. by a user); 617 * false if the model has been restarted by this attribute. 618 */ 619 private boolean _resetMessages; 620 621 /** True if this attribute should invoke Manager.startRun() upon successful 622 * completion (i.e. when executionFinished() is invoked). 623 */ 624 private boolean _restartDesired; 625 626 /** A list of all ExceptionSusbcribers, to be notified when an exception is 627 * caught by this class. 628 */ 629 private ArrayList<ExceptionSubscriber> _subscribers; 630}