/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.sun.jini.mahalo.log; import com.sun.jini.logging.Levels; import com.sun.jini.mahalo.log.MultiLogManager.LogRemovalManager; import com.sun.jini.mahalo.TxnManager; import java.util.ArrayList; import java.util.logging.Level; import java.util.logging.Logger; import java.io.*; /** * An implementation of a re-usable <code>Log</code>. * * @author Sun Microsystems, Inc. * * @see com.sun.jini.mahalo.log.Log */ public class SimpleLogFile implements Log { /** Unique ID associated with this log */ private /*final*/ long cookie; /** Output stream for writing log objects */ private /*final*/ ObjectOutputStream out; /** * File output stream associated with <code>out</code>. * Used to get a handle to underlying file descriptor object. */ private /*final*/ FileOutputStream outfile; /** (Relative) File name of the log file */ private /*final*/ String name; /** * Reference to <code>LogRemovalManager</code>, which is called * to remove this log from the managed set of logs. */ private /*final*/ LogRemovalManager logMgr; /** * Flag that indicates validity of this log. Set to false * by call to <code>invalidate()</code>. */ private boolean valid = true; /** * Flag to indicate that the log file has been created via * the read-only constructor. This flag is set to false via * the non-read-only constructor or a call to <code>recover()</code> */ private boolean readonly = false; /** Logger for persistence related messages */ private static final Logger persistenceLogger = Logger.getLogger(TxnManager.MAHALO + ".persistence"); /** Logger for operations related messages */ private static final Logger operationsLogger = Logger.getLogger(TxnManager.MAHALO + ".operations"); /** Logger for initialization related messages */ private static final Logger initLogger = Logger.getLogger(TxnManager.MAHALO + ".init"); /** * This class extends <tt>ObjectInputStream</tt> and overrides the * <code>readStreamHeader</code> method to a no-op operation. This class * is intended to work in conjunction with * <code>HeaderlessObjectOutputStream</code>. */ private static class HeaderlessObjectInputStream extends ObjectInputStream { /** * Simple constructor that passes its argument to the superclass * * @exception IOException if an I/O error occurs */ public HeaderlessObjectInputStream(InputStream in) throws IOException { super(in); } /** * Overrides <tt>ObjectInputStream</tt>'s method with no-op * functionality. * @see HeaderlessObjectOutputStream#writeStreamHeader * @exception IOException if an I/O error occurs */ protected void readStreamHeader() throws IOException { // Do nothing } } /** * This class extends <tt>ObjectOutputStream</tt> and overrides the * <code>writeStreamHeader</code> method to a no-op operation. This * class is intended to be used in conjunction with * <code>HeaderlessObjectInputStream</code>. */ private static class HeaderlessObjectOutputStream extends ObjectOutputStream { /** * Simple constructor that passes its argument to the superclass * * @exception IOException if an I/O error occurs */ public HeaderlessObjectOutputStream(OutputStream out) throws IOException { super(out); } /** * Overrides <tt>ObjectOutputStream</tt>'s method with no-op * functionality. This prevents header information from being * sent to the stream, which makes appending to existing log files * easier. Otherwise, appending header info to an existing log file * would cause a corresponding <code>ObjectInputStream</code> to * throw a <code>StreamCorruptedException</code> when it encountered * the header information instead of the class/object type code * information it was expecting. * * @exception IOException if an I/O error occurs */ protected void writeStreamHeader() throws IOException { // Do nothing } } /** * Creates a read-only <code>SimpleLogFile</code> * * To be used for read-only access to a named <code>Log</code>. This is * desired when recovering information from a <code>Log</code>. * * @param name names the file in which information is stored. * * @param logMgr <code>LogRemovalManager</code> managing this log. * This object is called back to remove this log * from the manager's managed set of log files. * * @see com.sun.jini.mahalo.log.Log * @see com.sun.jini.mahalo.log.LogManager * @see com.sun.jini.mahalo.log.MultiLogManager * @see com.sun.jini.mahalo.log.MultiLogManager.LogRemovalManager */ public SimpleLogFile(String name, LogRemovalManager logMgr) { init(name, 0, logMgr); readonly = true; } /** * Creates a <code>SimpleLogFile</code>. * * @param name names the file in which information is stored. * * @param cookie identifier representing information being stored. * * @param logMgr <code>LogRemovalManager</code> managing this log. * This object is called back to remove this log * from the manager's responsibility. * * @see com.sun.jini.mahalo.log.Log * @see com.sun.jini.mahalo.log.LogManager * @see com.sun.jini.mahalo.log.MultiLogManager * @see com.sun.jini.mahalo.log.MultiLogManager.LogRemovalManager */ public SimpleLogFile(String name, long cookie, LogRemovalManager logMgr) { init(name, cookie, logMgr); } /* * Utility method that contains code common to all constructors */ private void init(String name, long cookie, LogRemovalManager logMgr) { if (operationsLogger.isLoggable(Level.FINER)) { operationsLogger.entering(SimpleLogFile.class.getName(), "init", new Object[] {name, new Long(cookie), logMgr}); } if (name == null) throw new IllegalArgumentException("SimpleLogFile: null name"); if (logMgr == null) throw new IllegalArgumentException( "SimpleLogFile: null log manager"); this.name = name; this.cookie = cookie; this.logMgr = logMgr; if (operationsLogger.isLoggable(Level.FINER)) { operationsLogger.exiting(SimpleLogFile.class.getName(), "init"); } } /** * Returns the identifier associated with information in * this <code>Log</code>. * * @see com.sun.jini.mahalo.log.Log */ public long cookie() { return cookie; } /** * Add a <code>LogRecord</code> to the <code>Log</code>. * * @param rec the record to be logged. * * @see com.sun.jini.mahalo.log.LogRecord */ public synchronized void write(LogRecord rec) throws LogException { if (operationsLogger.isLoggable(Level.FINER)) { operationsLogger.entering(SimpleLogFile.class.getName(), "write", rec); } try { if (!valid) throw new InvalidatedLogException("Cannot write to to " + "invalidated log"); if (readonly) throw new LogException("Unable to write to read only log"); if (out == null) { boolean append = true; File log = new File(name); outfile = new FileOutputStream(name, append); out = new HeaderlessObjectOutputStream( new BufferedOutputStream(outfile)); if (log.length() == 0) { out.writeLong(cookie); } out.reset(); } out.writeObject(rec); out.flush(); outfile.getFD().sync(); if (persistenceLogger.isLoggable(Level.FINEST)) { persistenceLogger.log(Level.FINEST, "Wrote: {0}", rec); } } catch (InvalidClassException ice) { if (persistenceLogger.isLoggable(Level.WARNING)) { persistenceLogger.log(Level.WARNING, "Problem persisting LogRecord", ice); } // TODO - assertion error? ... should not happen } catch (NotSerializableException nse) { if (persistenceLogger.isLoggable(Level.WARNING)) { persistenceLogger.log(Level.WARNING, "Problem persisting LogRecord", nse); } // TODO - assertion error? ... should not happen } catch (IOException ioe) { if (persistenceLogger.isLoggable(Level.WARNING)) { persistenceLogger.log(Level.WARNING, "Problem persisting LogRecord", ioe); } // TODO - throw LogException? } catch (SecurityException se) { if (persistenceLogger.isLoggable(Level.WARNING)) { persistenceLogger.log(Level.WARNING, "Problem persisting LogRecord", se); } // TODO - assertion error? ... should not happen } if (operationsLogger.isLoggable(Level.FINER)) { operationsLogger.exiting(SimpleLogFile.class.getName(), "write", rec); } } /** * Invalidate the log. */ public synchronized void invalidate() throws LogException { // No short circuit check because we allow repeat calls if (operationsLogger.isLoggable(Level.FINER)) { operationsLogger.entering(MultiLogManager.class.getName(), "invalidate"); } if (persistenceLogger.isLoggable(Level.FINEST)) { persistenceLogger.log(Level.FINEST, "Invalidating log for cookie: {0}", new Long(cookie)); } if (valid) { // Set validity flag to false valid = false; // Ask log manager to remove us from its managed set logMgr.release(cookie); } try { if (out != null) { if (persistenceLogger.isLoggable(Level.FINEST)) { persistenceLogger.log(Level.FINEST, "Closing log file for: {0}", new Long(cookie)); } out.close(); // calls outfile.close() } } catch (IOException ioe) { // just log it if (persistenceLogger.isLoggable(Levels.HANDLED)) { persistenceLogger.log(Levels.HANDLED, "Problem closing log file", ioe); } } try { File fl = new File(name); if (persistenceLogger.isLoggable(Level.FINEST)) { persistenceLogger.log(Level.FINEST, "Deleting log file for: {0}", new Long(cookie)); } if(!fl.delete()) { if (persistenceLogger.isLoggable(Levels.HANDLED)) { persistenceLogger.log(Levels.HANDLED, "Could not delete log file"); } } } catch (SecurityException se) { // notify caller if (persistenceLogger.isLoggable(Level.FINEST)) { persistenceLogger.log(Level.FINEST, "SecurityException on log deletion", se); } throw new LogException("SimpleLogFile: invalidate: " + "cannot delete log file."); } if (operationsLogger.isLoggable(Level.FINER)) { operationsLogger.exiting(MultiLogManager.class.getName(), "invalidate"); } } /** * Recover information from the log. * * @param client who to inform with information from the log. * * @see com.sun.jini.mahalo.log.LogRecovery */ public synchronized void recover(LogRecovery client) throws LogException { if (operationsLogger.isLoggable(Level.FINER)) { operationsLogger.entering(MultiLogManager.class.getName(), "recover", client); } if (!valid) throw new InvalidatedLogException("Cannot recover from " + "invalidated log"); if (client == null) throw new IllegalArgumentException("Cannot have a <null> " + "client argument."); ObjectInputStream in = null; ArrayList recList = new ArrayList(); try { if (persistenceLogger.isLoggable(Level.FINEST)) { persistenceLogger.log(Level.FINEST, "Recovering from: {0}", name); } in = new HeaderlessObjectInputStream( new BufferedInputStream( new FileInputStream(name))); this.cookie = in.readLong(); LogRecord rec = null; boolean done = false; boolean update = true; try { while (!done) { // try to catch cast exceptions here rec = (LogRecord)in.readObject(); if (rec != null) { recList.add(rec); } else { //TBD - ignore? update = false; done = true; // bad log ... skip it if (persistenceLogger.isLoggable(Levels.HANDLED)) { persistenceLogger.log(Levels.HANDLED, "Log for cookie {0} contained a null " + "record object", new Long(cookie)); } } } } catch (ClassNotFoundException cnfe) { update = false; if (persistenceLogger.isLoggable(Level.WARNING)) { persistenceLogger.log(Level.WARNING, "Problem recovering log file", cnfe); } // TODO - assertion error? ... should not happen } catch (ClassCastException cce) { update = false; if (persistenceLogger.isLoggable(Level.WARNING)) { persistenceLogger.log(Level.WARNING, "Problem recovering log file", cce); } // TODO - assertion error? ... should not happen } catch (EOFException eofe) { // OK. Assume we've hit the end of the log file } catch (IOException ioe) { update = false; if (persistenceLogger.isLoggable(Level.WARNING)) { persistenceLogger.log(Level.WARNING, "Problem recovering log file", ioe); } } if (update) { for (int i=0; i<recList.size(); i++) { client.recover(cookie, (LogRecord)recList.get(i)); } } else { if (persistenceLogger.isLoggable(Level.WARNING)) { persistenceLogger.log(Level.WARNING, "Skipping log recovery for", name); } } } catch (IOException ioe) { // bogus log file -- skip it if (persistenceLogger.isLoggable(Level.WARNING)) { persistenceLogger.log(Level.WARNING, "Problem recovering log file", ioe); } } finally { try { if (in != null) in.close(); // calls fin.close() } catch (IOException ioe) { if (persistenceLogger.isLoggable(Levels.HANDLED)) { persistenceLogger.log(Levels.HANDLED, "Problem closing recovered log file", ioe); } } readonly = false; } if (operationsLogger.isLoggable(Level.FINER)) { operationsLogger.exiting(MultiLogManager.class.getName(), "recover"); } } // TBD - add a toString() method for debugging purposes }