/******************************************************************************* * Copyright © 2006, 2013 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation * *******************************************************************************/ package org.eclipse.edt.javart.resources; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.lang.reflect.InvocationTargetException; import java.text.MessageFormat; import java.util.Date; import java.util.Enumeration; import java.util.HashMap; import java.util.Iterator; import java.util.Locale; import java.util.Properties; import java.util.ResourceBundle; import java.util.TreeSet; import org.eclipse.edt.javart.Constants; import org.eclipse.edt.javart.EglExit; import org.eclipse.edt.javart.Executable; import org.eclipse.edt.javart.ExitProgram; import org.eclipse.edt.javart.ExitRunUnit; import org.eclipse.edt.javart.FatalProblem; import org.eclipse.edt.javart.Program; import org.eclipse.edt.javart.RunUnit; import org.eclipse.edt.javart.Transfer; import org.eclipse.edt.javart.messages.Message; import org.eclipse.edt.javart.util.JavartUtil; import eglx.lang.AnyException; /** * This is a RunUnit. */ public abstract class RunUnitBase implements RunUnit, Serializable { private static final long serialVersionUID = Constants.SERIAL_VERSION_UID; /** * Version information. * TODO Keep the value of this field up to date! */ public static final String VERSION = "0.8.1"; /** * The StartupInfo that was used to create this RunUnit. */ private StartupInfo startupInfo; /** * The properties of this RunUnit. */ protected JavartProperties properties; /** * The object used to trace this RunUnit's actions. */ protected Trace trace; /** * The object for Locale-based information. */ protected LocalizedText localizedText; /** * Keeps track of resources. */ private transient ResourceManager resourceManager; /** * The currently running executable, or null if nothing is running. */ private Executable currentExecutable; /** * The libraries that have been loaded. */ protected HashMap<String, Executable> libraries; /** * If the RunUnit ends with an error it is stored in this field. */ private AnyException fatalError; /** * The returnCode of the RunUnit, determined by the returnCode field of the * last Program. */ private int returnCode; /** * Makes a new RunUnit. * * @param startInfo info about what kind of RunUnit is needed. */ public RunUnitBase( StartupInfo startInfo ) throws AnyException { startupInfo = startInfo; // The properties come from files. if ( startInfo.getProperties() != null ) { properties = startInfo.getProperties(); } else { properties = new JavartPropertiesFile( startupInfo.getPropertyFilePath() ); } // Initialize fields. trace = new Trace( properties.get( "egl.trace.type" ), properties.get( "egl.trace.device.option", "2" ), properties.get( "egl.trace.device.spec" ) ); localizedText = new LocalizedText( properties ); resourceManager = new ResourceManager(); libraries = new HashMap<String, Executable>(); // Trace the information about this RunUnit. if ( trace.traceIsOn() ) { trace.put( "*** " + new Date() + " ***" ); trace.put( "*** " + localizedText.getDateFormatter().format( new Date() ) + " ***" ); trace.put( " " ); trace.put( "RunUnit: " + startInfo.getRuName() ); trace.put( "Version: " + VERSION ); trace.put( "System: " + Platform.SYSTEM_TYPE ); trace.put( properties.getInfo() ); if ( trace.traceIsOn( Trace.PROPERTIES_TRACE ) ) { traceProperties(); } trace.put( localizedText.getInfo() ); trace.put( getTrace().getInfo() ); trace.put( "java.class.path: " + System.getProperty( "java.class.path" ) ); trace.put( "java.library.path: " + System.getProperty( "java.library.path" ) ); trace.put( " " ); } } /** * @return the StartupInfo. */ public StartupInfo getStartupInfo() { return startupInfo; } /** * @return the properties. */ public JavartProperties getProperties() { return properties; } /** * @return the trace object. */ public Trace getTrace() { return trace; } /** * @return the ResourceManager. */ public ResourceManager getResourceManager() { return resourceManager; } /** * Registers resource <CODE>rs</CODE>. * * @param rs the resource to register. */ public void registerResource( RecoverableResource rs ) { resourceManager.register( rs ); } /** * Unregisters resource <CODE>rs</CODE>. * * @param rs the resource to unregister. */ public void unregisterResource( RecoverableResource rs ) { resourceManager.unregister( rs ); } /** * Commits changes made by programs in the RunUnit. */ public void commit() throws AnyException { resourceManager.commit( this ); } /** * Rolls back changes made by programs in the RunUnit. */ public void rollback() throws AnyException { resourceManager.rollback( this ); } /** * Starts the RunUnit, running the Program by calling its main method. * Transfers are handled here. When the last Program is done, endRunUnit * will be called. * * @param program the initial Program of this RunUnit. */ public void start( Program program ) throws Exception { try { while ( true ) { try { currentExecutable = program; program.main(); endRunUnit( program ); return; } catch ( Transfer trans ) { program = setupTransfer( trans ); } } } catch ( EglExit exit ) { // This is not an error. endRunUnit( program ); } catch ( Exception ex ) { endRunUnit( program, ex ); } } /** * Call this to end a RunUnit normally. It performs cleanup tasks, commits * and releases all resources. If there's an error at any point, * endRunUnit(Program, Exception) is called. * * @param program the last Program in the RunUnit. */ public void endRunUnit( Executable program ) throws Exception { if ( trace.traceIsOn() ) { trace.put( "endRunUnit " + startupInfo.getRuName() + " (normal termination) with returnCode=" + returnCode ); } try { commit(); resourceManager.exit( this ); trace.close(); currentExecutable = null; } catch ( Exception ex ) { endRunUnit( program, ex ); } } /** * Call this to end a RunUnit because of an error. It performs cleanup tasks, * rolls back and releases all resources. * * @param program the last Program in the RunUnit. * @param ex the reason that the RunUnit is ending. */ public void endRunUnit( Executable program, Exception ex ) throws Exception { // The return code is always 693 if there was an error. returnCode = 693; if ( trace.traceIsOn() ) { trace.put( "endRunUnit " + startupInfo.getRuName() + " (error termination) with returnCode=" + returnCode ); } fatalError = JavartUtil.makeEglException( ex ); String message = fatalError.getMessage(); if ( message.length() == 0 ) { message = fatalError.toString(); } System.out.println( message ); if ( AnyException.STACK_TRACES ) { ex.printStackTrace( System.out ); } try { rollback(); } catch ( Exception ex2 ) { // Ignore it. } try { resourceManager.exit( this ); } catch ( Exception ex2 ) { // Ignore it. } trace.close(); currentExecutable = null; } /** * Returns the one and only instance of the given library for use in * this RunUnit. * * @param name the fully-qualified class of the library. * @return the library instance to use. * @throws AnyException */ public Executable loadLibrary( String name ) throws AnyException { Executable library = libraries.get( name ); if ( library == null ) { library = loadExecutable( name ); libraries.put( name, library ); } return library; } /** * Returns a new instance of the given Executable. * * @param name the fully-qualified class name. * @return the new Executable. * @throws AnyException if the load fails. */ @Override public Executable loadExecutable( String name ) throws AnyException { try { Class pgmClass = Class.forName( name, true, getClass().getClassLoader() ); return (Executable)pgmClass.newInstance(); } catch ( Throwable ex ) { if ( ex instanceof InvocationTargetException ) { // An exception was thrown by the constructor. The // exception is wrapped by the InvocationTargetException. ex = ((InvocationTargetException)ex).getTargetException(); } FatalProblem problem = new FatalProblem(); problem.initCause( ex ); throw problem.fillInMessage( Message.CREATE_OBJECT_FAILED, ex ); } } /** * Returns an error message formatted in the language of the default Locale. * Only use this when we can't figure out which language to use for error * messages. * * @param id the message ID. * @param inserts the inserts. * @return the formatted message. */ protected static String formatMessageInDefaultLocale( String id, Object[] inserts ) { ResourceBundle bundle = ResourceBundle.getBundle( "com.ibm.javart.messages.MessageBundle" ); String message = bundle.getString( id ); MessageFormat mf = new MessageFormat( message ); return mf.format( inserts ); } /** * Writes the properties to the trace object. If the property * "egl.jdbc.default.database.user.password" is set, we write a * question mark. */ private void traceProperties() { Properties props = properties.getProperties(); if ( props != null ) { // Use a TreeSet of keys so the output is sorted by key. Enumeration names = props.propertyNames(); TreeSet keySet = new TreeSet(); while ( names.hasMoreElements() ) { keySet.add( names.nextElement() ); } Iterator sortedKeys = keySet.iterator(); while ( sortedKeys.hasNext() ) { String key = (String)sortedKeys.next(); String val = props.getProperty( key ); if ( key.equals( "egl.jdbc.default.database.user.password" ) ) { val = "?"; } trace.put( " > " + key + '=' + val ); } } } /** * @return the returnCode of the last Program in the RunUnit. */ public int getReturnCode() { return returnCode; } /** * Sets the return code of the current Program in the RunUnit. * * @param rc the return code. */ public void setReturnCode( int rc ) { returnCode = rc; } /** * If the RunUnit has ended due to an error, it will be returned. Otherwise * null is returned. */ public AnyException getFatalError() { return fatalError; } /** * @return the object for Locale-based information. */ public LocalizedText getLocalizedText() { return localizedText; } /** * Switches to the given Locale. * * @param loc the Locale to use. */ public abstract void switchLocale( Locale loc ); /** * Exit the current program. * * @throws ExitProgram */ public void exitProgram() throws ExitProgram { if ( trace.traceIsOn() ) { trace.put( "Exit Program" ); } throw ExitProgram.getSingleton(); } /** * Exit the entire run unit. * * @throws ExitRunUnit */ public void exitRunUnit() throws ExitRunUnit { if ( trace.traceIsOn() ) { trace.put( "Exit RunUnit" ); } throw ExitRunUnit.getSingleton(); } /** * Called when a transfer is taking place. Some resources will be committed, * released, and/or closed. * * @param toTransaction true/false for toTransaction/toProgram. * @throws Exception if there's an error. */ public void transferCleanup( boolean toTransaction ) { // Close and release some resources. This includes all files, except // MQ files when transferring to a program. resourceManager.transferCleanup( this, toTransaction ); // There's more to do if we're transferring to a transaction. if ( toTransaction ) { unloadLibraries(); } } /** * Unloads all libraries. */ public void unloadLibraries() throws AnyException { libraries.clear(); } /** * Serializes an instance of this class. * * @param out The output stream. * @throws IOException */ private void writeObject( ObjectOutputStream out ) throws IOException { // Users can tell us what to do with the ResourceManager during serialization. // Save their choice in the properies object so we're able to properly deserialize. String noRollback = properties.get( "org.eclipse.edt.noRollbackOnSerialize" ); if ( noRollback == null ) { noRollback = Boolean.getBoolean( "org.eclipse.edt.noRollbackOnSerialize" ) ? "true" : "false"; properties.put( "org.eclipse.edt.noRollbackOnSerialize", noRollback ); } out.defaultWriteObject(); if ( noRollback.equals( "false" ) ) { try { resourceManager.rollback( this ); } catch ( AnyException je ) { throw new IOException( je.getMessage() ); } finally { try { resourceManager.exit( this ); } catch ( AnyException je ) { throw new IOException( je.getMessage() ); } } // We must serialize the resource manager last because it will // unregister anything that's not serializable. The field is transient // because we need to perform a rollback and exit before it's written. out.writeObject( resourceManager ); } } /** * Deserializes an instance of this class. * * @param in The input stream. * @throws IOException * @throws ClassNotFoundException */ private void readObject( ObjectInputStream in ) throws IOException, ClassNotFoundException { in.defaultReadObject(); // Users can tell us what to do with the ResourceManager during serialization. if ( "true".equals( properties.get( "org.eclipse.edt.noRollbackOnSerialize" ) ) ) { resourceManager = new ResourceManager(); } else { resourceManager = (ResourceManager)in.readObject(); } } /** * @return the active executable, or null if nothing is running. */ @Override public Executable getActiveExecutable() throws AnyException { return currentExecutable; } @Override public void setActiveExecutable( Executable executable ) { this.currentExecutable = executable; } /** * Makes changes necessary for a Transfer from one program to another. * * @param trans information about the transfer. * @return the new Program. */ private Program setupTransfer( Transfer trans ) throws Exception { // If this is a transfer to a transaction with a different program, // the new Program may use a different set of properties. Do the switch // before the new Program is created so its constructor will use the // fields of this RU which might change along with the properties. if ( newPropertiesNeeded( trans ) ) { String newName = JavartUtil.removePackageName( trans.name ); String oldName = currentExecutable._name(); if ( !newName.equals( oldName ) ) { String newPropertiesFilePath = trans.name.replace( '.', '/' ) + ".properties"; properties = new JavartPropertiesFile( newPropertiesFilePath ); // Replace the objects that depend on the properties. trace = new Trace( properties.get( "egl.trace.type" ), properties.get( "egl.trace.device.option", "2" ), properties.get( "egl.trace.device.spec" ) ); localizedText = new LocalizedText( properties ); } } // Create the new Program. The new Program will be set as the active executable later. Program newProgram = (Program)loadExecutable( trans.name ); // Initialize the new Program. if ( trans.input != null && newProgram._inputRecord() != null ) { newProgram._inputRecord().ezeCopy(trans.input); } return newProgram; } /** * Tells if a fresh set of properties should be loaded before a transfer. * * @param trans information about the transfer. * @return true if a fresh set of properties should be loaded before a transfer. */ protected abstract boolean newPropertiesNeeded( Transfer trans ); }