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.io.IOException; 031import java.text.SimpleDateFormat; 032import java.util.ArrayList; 033import java.util.Date; 034import java.util.Iterator; 035 036import ptolemy.actor.AbstractInitializableAttribute; 037import ptolemy.actor.Actor; 038import ptolemy.actor.CompositeActor; 039import ptolemy.actor.ExecutionListener; 040import ptolemy.actor.Manager; 041import ptolemy.data.expr.FileParameter; 042import ptolemy.data.expr.StringParameter; 043import ptolemy.data.type.BaseType; 044import ptolemy.kernel.CompositeEntity; 045import ptolemy.kernel.util.Attribute; 046import ptolemy.kernel.util.ExceptionHandler; 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; 052 053/////////////////////////////////////////////////////////////////// 054//// CatchExceptionAttribute 055 056/** 057 This attribute catches exceptions and attempts to handle them with the 058 specified policy. If the exception cannot be handled, the attribute 059 indicates this to the Manager. Status messages may be logged to a file. 060 061 @author Edward A. Lee, Elizabeth Latronico 062 @version $Id$ 063 @since Ptolemy II 10.0 064 @Pt.ProposedRating Red (beth) 065 @Pt.AcceptedRating Red (beth) 066 */ 067public class CatchExceptionAttribute extends AbstractInitializableAttribute 068 implements ExceptionHandler, ExecutionListener { 069 070 /** Create a new actor in the specified container with the specified 071 * name. The name must be unique within the container or an exception 072 * is thrown. The container argument must not be null, or a 073 * NullPointerException will be thrown. 074 * 075 * @param container The container. 076 * @param name The name of this actor within the container. 077 * @exception IllegalActionException If this actor cannot be contained 078 * by the proposed container (see the setContainer() method). 079 * @exception NameDuplicationException If the name coincides with 080 * an entity already in the container. 081 */ 082 public CatchExceptionAttribute(CompositeEntity container, String name) 083 throws NameDuplicationException, IllegalActionException { 084 super(container, name); 085 086 policy = new StringParameter(this, "policy"); 087 policy.setExpression(THROW); 088 089 policy.addChoice(CONTINUE); 090 policy.addChoice(THROW); 091 policy.addChoice(RESTART); 092 policy.addChoice(STOP); 093 094 logFileName = new FileParameter(this, "logFile"); 095 logFileName.setExpression(""); 096 logFileName.setTypeEquals(BaseType.STRING); 097 _writer = null; 098 099 exceptionMessage = new StringParameter(this, "exceptionMessage"); 100 exceptionMessage.setExpression("No exceptions encountered"); 101 exceptionMessage.setVisibility(Settable.NOT_EDITABLE); 102 103 statusMessage = new StringParameter(this, "statusMessage"); 104 statusMessage.setExpression("No exceptions encountered"); 105 statusMessage.setVisibility(Settable.NOT_EDITABLE); 106 107 _initialized = false; 108 _resetMessages = true; 109 _restartDesired = false; 110 _subscribers = new ArrayList(); 111 112 } 113 114 /////////////////////////////////////////////////////////////////// 115 //// parameters //// 116 117 /** The exception message from the caught exception. */ 118 public StringParameter exceptionMessage; 119 120 /** The file, if any, to log messages to. */ 121 public FileParameter logFileName; 122 123 /** The error handling policy to apply if an exception occurs. 124 * 125 * One of: Continue, Throw, Restart, Quit 126 */ 127 public StringParameter policy; 128 129 /** The latest action, if any, taken by the CatchExceptionAttribute. 130 * For example, a notification that the model has restarted. 131 * It offers a way to provide feedback to the user. 132 */ 133 public StringParameter statusMessage; 134 135 /////////////////////////////////////////////////////////////////// 136 //// public methods //// 137 138 /** React to a change in an attribute. This method is called by 139 * a contained attribute when its value changes. In this base class, 140 * the method does nothing. In derived classes, this method may 141 * throw an exception, indicating that the new attribute value 142 * is invalid. It is up to the caller to restore the attribute 143 * to a valid value if an exception is thrown. 144 * @param attribute The attribute that changed. 145 * @exception IllegalActionException If the change is not acceptable 146 * to this container (not thrown in this base class). 147 */ 148 @Override 149 public void attributeChanged(Attribute attribute) 150 throws IllegalActionException { 151 152 // Open the log file attributeChanged so that exceptions occurring in 153 // preinitialize() or initialize() may logged 154 155 if (attribute == logFileName) { 156 if (logFileName != null) { 157 // Copied (with some edits) from FileWriter 158 String filenameValue = logFileName.getExpression(); 159 160 if (filenameValue == null || filenameValue.equals("\"\"")) { 161 // See $PTII/ptolemy/domains/sdf/kernel/test/auto/zeroRate_delay5.xml, which sets 162 // the filename to a string that has two doublequotes. "" 163 // _setWriter(null) will close any existing writer 164 _setWriter(null); 165 } else if (!filenameValue.equals(_previousFilename)) { 166 // New filename. Close the previous. 167 _previousFilename = filenameValue; 168 _setWriter(null); 169 if (!filenameValue.trim().equals("")) { 170 java.io.Writer writer = logFileName.openForWriting(); 171 _setWriter(writer); 172 } 173 } 174 } 175 } else { 176 super.attributeChanged(attribute); 177 } 178 } 179 180 /** Clone the attribute into the specified workspace. 181 * @param workspace The workspace for the new object. 182 * @return A new actor. 183 * @exception CloneNotSupportedException If a derived class contains 184 * an attribute that cannot be cloned. 185 */ 186 @Override 187 public Object clone(Workspace workspace) throws CloneNotSupportedException { 188 CatchExceptionAttribute newObject = (CatchExceptionAttribute) super.clone( 189 workspace); 190 191 newObject._subscribers = new ArrayList(); 192 return newObject; 193 } 194 195 /** Do nothing upon execution error. Exceptions are passed to this 196 * attribute through handleException(). This method is required by 197 * the ExecutionListener interface. 198 * @param manager Ignored. 199 * @param throwable Ignored. 200 */ 201 @Override 202 public void executionError(Manager manager, Throwable throwable) { 203 204 } 205 206 /** Restart here if restart is desired. This method is called upon 207 * successful completion. 208 * @param manager The manager that starts the run. 209 */ 210 @Override 211 public void executionFinished(Manager manager) { 212 if (_restartDesired) { 213 Date date = new Date(System.currentTimeMillis()); 214 SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss"); 215 216 try { 217 // Start a new execution in a new thread 218 try { 219 manager.startRun(); 220 } catch (IllegalActionException e) { 221 _writeMessage("Cannot restart model. " 222 + "Manager.startRun() failed."); 223 } 224 225 _writeMessage("Model restarted at " + dateFormat.format(date)); 226 } catch (IOException e) { 227 statusMessage.setExpression("Error: Cannot write to file."); 228 } 229 // Do NOT reset messages in the event of a restart 230 // This way, user can see that model was restarted 231 _resetMessages = false; 232 _restartDesired = false; 233 } 234 } 235 236 // TODO: Figure out what makes sense for continue (if anything) 237 238 /** Handle an exception according to the specified policy: 239 * 240 * continue: Not implemented yet 241 * Consume the exception and return control to the director. 242 * Could be valuable for domains like DE or modal models when new 243 * events will arrive. Probably not appropriate for domains like SDF 244 * where the director follows a predefined schedule based on data flow 245 * (since the actor throwing the exception no longer provides output to 246 * the next actor). 247 * 248 * throw: Do not catch the exception. 249 * 250 * restart: Stop and restart the model. Does not apply to exceptions 251 * generated during initialize(). 252 * 253 * stop: Stop the model. 254 * 255 * @param context The object in which the error occurred. 256 * @param exception The exception to be handled. 257 * @return true if the exception is handled; false if this attribute 258 * did not handle it 259 * @exception IllegalActionException If thrown by the parent 260 */ 261 @Override 262 public boolean handleException(NamedObj context, Throwable exception) 263 throws IllegalActionException { 264 265 // Try / catch for IOException from file writer 266 267 try { 268 // Save the exception message. Only informational at the moment. 269 exceptionMessage.setExpression(exception.getMessage()); 270 if (_writer != null) { 271 _writer.write("Exception: " + exception.getMessage() + "\n"); 272 _writer.flush(); 273 } 274 275 Date date = new Date(System.currentTimeMillis()); 276 SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss"); 277 278 // Handle the exception according to the specified policy 279 // TODO: Apply different policies depending on the type of exception. 280 // How would the policy be specified then? 281 282 String policyValue = policy.stringValue(); 283 284 // Set initialized to false here, unless policy is to restart, in 285 // which case set it after the current value is checked 286 if (!policyValue.equals(RESTART)) { 287 // Set _initialized here instead of in wrapup(), since 288 // wrapup() is called prior to handleException() 289 _initialized = false; 290 } 291 292 if (policyValue.equals(CONTINUE)) { 293 _writeMessage( 294 "Execution continued at " + dateFormat.format(date)); 295 296 // FIXME: Is continue possible? Looks like wrapup() is called 297 // automatically before handleException() 298 } else if (policyValue.equals(THROW)) { 299 _writeMessage("Exception thrown at " + dateFormat.format(date)); 300 301 // Return false if an exception is thrown, since this attribute 302 // did not resolve the exception. 303 return false; 304 305 } else if (policyValue.equals(RESTART)) { 306 // Restarts the model in a new thread 307 308 // Check if the model made it through initialize(). If not, return 309 // false (thereby leaving exception unhandled) 310 if (!_initialized) { 311 312 // Return false if an exception is thrown, since this attribute 313 // did not resolve the exception. 314 _writeMessage("Cannot restart: Error before or during " 315 + "intialize()"); 316 return false; 317 } 318 319 // Set _initialized here, instead of in wrapup(), since 320 // wrapup() is called prior to handleException() 321 _initialized = false; 322 323 // Find an actor in the model; use the actor to get the manager. 324 Manager manager = null; 325 326 NamedObj toplevel = toplevel(); 327 if (toplevel != null) { 328 Iterator iterator = toplevel.containedObjectsIterator(); 329 while (iterator.hasNext()) { 330 Object obj = iterator.next(); 331 if (obj instanceof Actor) { 332 manager = ((Actor) obj).getManager(); 333 } 334 } 335 } 336 337 if (manager != null) { 338 // End execution 339 manager.finish(); 340 341 // Wait until the manager notifies listeners of successful 342 // completion before restarting. Manager will call 343 // _executionFinished(). Set a flag here indicating to restart 344 _restartDesired = true; 345 346 } else { 347 _writeMessage( 348 "Cannot restart model since there is no model " 349 + "Manager. Perhaps the model has no actors?"); 350 return false; 351 } 352 353 } else if (policyValue.equals(STOP)) { 354 _writeMessage("Model stopped at " + dateFormat.format(date)); 355 356 // Call validate() to notify listeners of these changes 357 exceptionMessage.validate(); 358 statusMessage.validate(); 359 360 // wrapup() is automatically called prior to handleException(), 361 // so don't need to call it again 362 } else { 363 _writeMessage("Illegal policy encountered at: " 364 + dateFormat.format(date)); 365 366 // Throw an exception here instead of just returning false, since 367 // this is a problem with CatchExceptionAttribute 368 throw new IllegalActionException(this, 369 "Illegal exception handling policy."); 370 } 371 372 // Call validate() to notify listeners of these changes 373 exceptionMessage.validate(); 374 statusMessage.validate(); 375 376 _resetMessages = false; 377 378 // Notify all ExceptionSubscribers 379 for (ExceptionSubscriber subscriber : _subscribers) { 380 subscriber.exceptionOccurred(policyValue, exception); 381 } 382 383 return true; 384 } catch (IOException ioe) { 385 statusMessage.setExpression("Error: Cannot write to file."); 386 return false; 387 } 388 } 389 390 /** Find all of the ExceptionSubscribers in the model. 391 * 392 * @exception IllegalActionException If thrown by parent 393 */ 394 @Override 395 public void initialize() throws IllegalActionException { 396 397 // Use allAtomicEntityList here to include entities inside composite 398 // actors. Could switch to containedObjectsIterator in the future if we 399 // want to allow attributes to be ExceptionSubscribers. (Will need to 400 // implement a deep search. containedObjectsIterator does not look 401 // inside composite entities). 402 Iterator iterator = ((CompositeActor) toplevel()).allAtomicEntityList() 403 .iterator(); 404 _subscribers.clear(); 405 NamedObj obj; 406 407 while (iterator.hasNext()) { 408 obj = (NamedObj) iterator.next(); 409 if (obj instanceof ExceptionSubscriber) { 410 _subscribers.add((ExceptionSubscriber) obj); 411 } 412 } 413 } 414 415 /** React to a change of state in the Manager. 416 * 417 * @param manager The model manager 418 */ 419 @Override 420 public void managerStateChanged(Manager manager) { 421 if (manager.getState().equals(Manager.EXITING)) { 422 // Close file writer, if any 423 if (_writer != null) { 424 try { 425 _writer.close(); 426 } catch (IOException e) { 427 // Can't really do anything about an exception here? 428 } 429 } 430 } else if (manager.getState().equals(Manager.INITIALIZING)) { 431 // Enable restart once all objects have been initialized 432 //_initialized is set back to false at the end of _handleException() 433 if (_resetMessages) { 434 exceptionMessage.setExpression("No exceptions encountered"); 435 statusMessage.setExpression("No exceptions encountered"); 436 437 // Call validate() to notify listeners of these changes 438 try { 439 exceptionMessage.validate(); 440 statusMessage.validate(); 441 } catch (IllegalActionException e) { 442 try { 443 _writeMessage("Error initializing status message."); 444 } catch (IOException ioe) { 445 statusMessage.setExpression("Error writing to file"); 446 } 447 } 448 } 449 450 _resetMessages = true; 451 _initialized = true; 452 } 453 } 454 455 /** Register this attribute with the manager. Done here instead of in the 456 * constructor since the director is found in order to get the manager. 457 * The constructor for this attribute might be called before the 458 * constructor for the director. 459 * 460 * @exception IllegalActionException If the parent class throws it 461 */ 462 @Override 463 public void preinitialize() throws IllegalActionException { 464 super.preinitialize(); 465 466 // Find an actor in the model; use the actor to get the manager. 467 Manager manager = null; 468 469 NamedObj toplevel = toplevel(); 470 if (toplevel != null) { 471 Iterator iterator = toplevel.containedObjectsIterator(); 472 while (iterator.hasNext()) { 473 Object obj = iterator.next(); 474 if (obj instanceof Actor) { 475 manager = ((Actor) obj).getManager(); 476 } 477 } 478 } 479 480 if (manager != null) { 481 manager.addExecutionListener(this); 482 } else { 483 throw new IllegalActionException(this, "Manager cannot be found. " 484 + "Perhaps the model has no actors?"); 485 } 486 } 487 488 /////////////////////////////////////////////////////////////////// 489 //// protected methods //// 490 491 /** Write the given message to the statusMessage parameter and to the log 492 * file, if open. 493 * @param message The message to write 494 * @exception IOException If there is a problem writing to the file 495 */ 496 protected void _writeMessage(String message) throws IOException { 497 statusMessage.setExpression(message); 498 if (_writer != null) { 499 _writer.write(message + " \n"); 500 _writer.flush(); 501 } 502 } 503 504 /////////////////////////////////////////////////////////////////// 505 //// private methods //// 506 507 /** Set the writer. If there was a previous writer, close it. 508 * To set standard output, call this method with argument null. 509 * @param writer The writer to write to. 510 * @exception IllegalActionException If an IO error occurs. 511 */ 512 private void _setWriter(java.io.Writer writer) 513 throws IllegalActionException { 514 // Copied (with some edits) from FileWriter. 515 try { 516 if (_writer != null && _writer != _stdOut) { 517 _writer.close(); 518 519 // Since we have closed the writer, we also need to clear 520 // _previousFilename, so that a new writer will be opened for 521 // this filename if the model is executed again 522 _previousFilename = null; 523 } 524 } catch (IOException ex) { 525 throw new IllegalActionException(this, ex, 526 "setWriter(" + writer + ") failed"); 527 } 528 529 if (writer != null) { 530 _writer = writer; 531 } else { 532 _writer = _stdOut; 533 } 534 } 535 536 /////////////////////////////////////////////////////////////////// 537 //// public variables //// 538 539 /** String value for the "continue" policy. */ 540 public static final String CONTINUE = "continue"; 541 542 /** String value for the "restart" policy. */ 543 public static final String RESTART = "restart"; 544 545 /** String value for the "throw" policy. */ 546 public static final String THROW = "throw"; 547 548 /** String value for the "stop" policy. */ 549 public static final String STOP = "stop"; 550 551 /////////////////////////////////////////////////////////////////// 552 //// private variables //// 553 554 /** True if the model has been initialized but not yet wrapped up, 555 * false otherwise. Some policies (e.g. restart) are desirable only 556 * for run-time exceptions. 557 */ 558 private boolean _initialized; 559 560 /** The previously used filename, or null if none has been previously used. 561 */ 562 private String _previousFilename = null; 563 564 /** True if the model has been started externally (e.g. by a user); 565 * false if the model has been restarted by this attribute. 566 */ 567 private boolean _resetMessages; 568 569 /** True if this attribute should invoke Manager.startRun() upon successful 570 * completion (i.e. when executionFinished() is invoked). 571 */ 572 private boolean _restartDesired; 573 574 /** A list of all ExceptionSubscribers, to be notified when an exception is 575 * caught by this class. 576 */ 577 private ArrayList<ExceptionSubscriber> _subscribers; 578 579 /** Standard out as a writer. */ 580 private static java.io.Writer _stdOut = null; 581 582 /** The writer to write to. */ 583 private java.io.Writer _writer = null; 584}