All Downloads are FREE. Search and download functionalities are using the official Maven repository.

lrgs.archive.MsgPeriodArchive Maven / Gradle / Ivy

Go to download

A collection of software for aggregatting and processing environmental data such as from NOAA GOES satellites.

The newest version!
/*
*  $Id$
*
*  This is open-source software written by ILEX Engineering, Inc., under
*  contract to the federal government. You are free to copy and use this
*  source code for your own purposes, except that no part of this source
*  code may be claimed to be proprietary.
*
*  Except for specific contractual terms between ILEX and the federal 
*  government, this source code is provided completely without warranty.
*  For more information contact: [email protected]
*
*/
package lrgs.archive;

import java.io.File;
import java.io.IOException;
import java.util.Vector;
import java.util.Date;
import java.util.TimeZone;
import java.text.SimpleDateFormat;

import ilex.util.Logger;
import lrgs.common.DcpMsg;
import lrgs.common.DcpMsgFlag;
import lrgs.common.DcpMsgIndex;
import lrgs.common.ArchiveUnavailableException;
import lrgs.common.SearchTimeoutException;
import lrgs.common.LrgsErrorCode;

/**
An archive for a specific period.
*/
public class MsgPeriodArchive
{
	private String module = "PerArch";

	/**
	 * time_t of start of period covered by this file.
	 * Each file is uniquely identified by a startTime.
	 */
	int startTime;

	/** String Date YYYY/MM/DD corresponding to startTime */
	String myname;

	/** Static date formatter used for date string. */
	static SimpleDateFormat startFmt = 
		new SimpleDateFormat("yyyy/MM/dd");

	/** Static date formatter used for debug messages. */
	static SimpleDateFormat debugFmt = 
		new SimpleDateFormat("yyyy/MM/dd-HH:mm:ss");
	
	static
	{
		startFmt.setTimeZone(TimeZone.getTimeZone("UTC"));
		debugFmt.setTimeZone(TimeZone.getTimeZone("UTC"));
	}

	/**
	 * Time duration of this file (1 day).
	 */
	public static final int periodDuration = 3600*24;
	
	/**
	 * Temporary storage when reading blocks of indexes.
	 */
//	private DcpMsgIndex[] indexBuf;

	private static final int INDEX_BUF_SIZE = 100;

	/** Object used to read/write the indexes */
	IndexFile indexFile;
	
	private MsgIndexMinute[] minuteIndex;

	public static final int MIN_PER_DAY = 60*24;

	/** Cache of DcpMsgIndex objects. */

	class Cache extends Vector
	{
		private int startIndexNum;
		public static final int CACHE_MAX_SIZE = 25000; // About 2 hours worth
		public static final int CACHE_INCR = 5000;

		public Cache(int startIndexNum)
		{
			super();
			this.startIndexNum = startIndexNum;
		}

		public int getStartIndexNum() { return startIndexNum; }
		
		/** Remove entries up to but not including the specified index. */
		private void rmBefore(int idx) 
		{
			removeRange(0,idx);
			startIndexNum += idx;
		}
		
		public synchronized boolean add(DcpMsgIndex idx)
		{
			if (size() >= CACHE_MAX_SIZE)
				rmBefore(CACHE_MAX_SIZE-CACHE_INCR);
			return super.add(idx);
		}

		public synchronized void clear() { super.clear(); }

		public synchronized DcpMsgIndex getByIndexNum(int idxNum)
		{
			if (idxNum >= startIndexNum
			 && idxNum < startIndexNum + size())
				return super.get(idxNum - startIndexNum);
			else
				return null;
		}
	};
	private Cache cache;

	
	/** Object used to read/write the messages */
	private MsgFile msgFile;

	/** true if this archive is the current (writable) archive. */
	private boolean isCurrent;

	private String rootPath;
	public static final String MSG_EXT =    ".msg";
	public static final String MINUTE_EXT = ".min";
	public static final String INDEX_EXT =  ".idx";
	public static final String IHASH_EXT = ".ihash";
	
	/**
	 * Constructor.
	 * Load MsgIndexMinute array from disk.
	 * Initialize indexBuf to an array of objects.
	 * The 'rootPath' argument determines the location and base name for the
	 * three files making up this period. The message file will have extension
	 * '.msg', the index file '.idx', and the minute file '.min'.
	 *
	 * @param rootPath the root path name for files for this period. 
	 * @param startTime unique start time for this period (unix time_t)
	 * @param isCurrent if true, open files read/write. Else read-only.
	 */
	public MsgPeriodArchive(String rootPath, int startTime, boolean isCurrent)
		throws IOException
	{
		module = module + "(" + startFmt.format(new Date(startTime * 1000L)) + ")";
		Logger.instance().info(module 
			+ " New archive '" + rootPath + "' start time=" + startTime
			+ ", isCurrent=" + isCurrent);

		this.rootPath = rootPath;
		this.startTime = startTime;
		this.myname = "DayArchive(" + 
			startFmt.format(new Date(startTime * 1000L)) + ")";
		this.isCurrent = isCurrent;

		// Allocate a buffer of indexes for searching.
//		indexBuf = new DcpMsgIndex[INDEX_BUF_SIZE];
//		for(int i=0; i= MIN_PER_DAY)
			return;

		synchronized(minuteIndex)
		{
			if (minuteIndex[minuteNum].isEmpty()
			 || indexNum < minuteIndex[minuteNum].startIndexNum)
				minuteIndex[minuteNum].startIndexNum = indexNum;
			if (dapsTime < minuteIndex[minuteNum].oldestDapsTime)
				minuteIndex[minuteNum].oldestDapsTime = dapsTime;
		}
	}
	
	/**
	 * Called on the current archive when a new period is starting and 
	 * this period is finished (i.e. end of day).
	 * Flushes the minute archive to disk.
	 * Re-opens index and message files read-only.
	 */
	public synchronized void finish( )
	{
		Logger.instance().info("Archive " + myname + " is finished ... "
			+ " re-opening read-only.");
		try { MinuteFile.save(rootPath + MINUTE_EXT, minuteIndex); }
		catch(IOException ex)
		{
			Logger.instance().warning(module 
				+ " Cannot save minute index: " + ex);
		}
		indexFile.close();
		indexFile = null;
		msgFile.close();
		msgFile = null;

		isCurrent = false;
		try { indexFile = new IndexFile(rootPath + INDEX_EXT, false); }
		catch(IOException ioex)
		{
			Logger.instance().warning(module + 
				" Cannot open index file '" + rootPath + INDEX_EXT 
				+ "': " + ioex);
		}
		try { msgFile = new MsgFile(new File(rootPath + MSG_EXT), false); }
		catch(IOException ioex)
		{
			Logger.instance().warning(module + 
				" Cannot open message file '" + rootPath + MSG_EXT 
				+ "': " + ioex);
		}
	}

	/**
	 * Called prior to shutdown to close all files and release resources.
	 */
	public void close()
	{
//		indexBuf = null;
		indexFile.close();
		minuteIndex = null;
		cache = null;
		msgFile.close();
	}

	/**
	 * This method is called periodically to checkpoint critical info to disk
	 * and other periodic maintenance, like flushing old entries from the 
	 * cache.
	 */
	public synchronized void checkpoint()
	{
		Logger.instance().debug3("MsgPeriodArchive.checkpoint()");
		if (isCurrent)
		{
			try { MinuteFile.save(rootPath + MINUTE_EXT, minuteIndex); }
			catch(IOException ex)
			{
				Logger.instance().warning(module + 
					" Cannot checkpoint minute index: " + ex);
			}
		}
		if (cache != null)
		{
			// Keep this cache until an hour after the day is over.
			if (!isCurrent
			 && cache != null
			 && System.currentTimeMillis()/1000L > 
				startTime + periodDuration + 3600L)
			{
				// More than 1 hr past end of period: no more caching.
				cache.clear();
				cache = null;
			}
		}
	}

	/**
	  Called when this period has fallen off the archive because it's more
	  then the max number of days old. Deletes the files from the disk.
	*/
	public void delete()
	{
		Logger.instance().info(module 
			+ " Deleting Day Archive '" + rootPath + "'");
		close();
		File f = new File(rootPath + MSG_EXT);
		if (f.exists())
			f.delete();
		f = new File(rootPath + MINUTE_EXT);
		if (f.exists())
			f.delete();
		f = new File(rootPath + INDEX_EXT);
		if (f.exists())
			f.delete();
		f = new File(rootPath + IHASH_EXT);
		if (f.exists())
			f.delete();
	}

	/**
	 * Initialize an Index search. Use the since time to calculate the earliest 
	 * minute that can contain a message. Get it's startIndexNum from
	 * the MsgIndexMinute array. 
	 */
	public void startIndexSearch( SearchHandle handle )
	{
//boolean testcli = handle.filter.getClientName().contains("OISIN2");
//boolean testcli = handle.filter.getClientName().contains("no match");
//if (testcli)
//Logger.instance().info("starting index search for " + handle.filter.getClientName());
		Date d = handle.filter.getSinceTime();
		if (d == null)
		{
			Logger.instance().debug1(module + " " + myname 
				+ " startIndexSearch with null since time.");
			handle.nextIndexNum = 0;
		}
		else
		{
			int tt = (int)(d.getTime() / 1000L);
			handle.minIdx = (tt - startTime) / 60;
			if (handle.minIdx < 0)
				handle.minIdx = 0;
			else if (handle.minIdx >= MIN_PER_DAY)
				handle.minIdx = MIN_PER_DAY-1;

			// There may not be any data in the specified minute.
			// Scroll forward until we find some.
			handle.nextIndexNum = minuteIndex[handle.minIdx].startIndexNum;
			while(handle.nextIndexNum == -1 && handle.minIdx < MIN_PER_DAY-1)
			{
				handle.nextIndexNum = 
					minuteIndex[++handle.minIdx].startIndexNum;
			}
			if (handle.nextIndexNum == -1 && handle.minIdx == MIN_PER_DAY-1)
			{
				// Fell through -- no indexes this day. Set to # indexes which
				// will cause searchIndex to cycle to the next period.
				Logger.instance().debug1(module 
					+ "No indexes since specified time. "
					+ "Will drop through to next day.");
				handle.nextIndexNum = getNumIndexes();
			}

			Logger.instance().debug1(module + " " +
				myname + " startIndexSearch sincetime=" + debugFmt.format(d)
				+ ", minIdx=" + handle.minIdx + ", start idx="
				+ handle.nextIndexNum + ", num indexes in file="
				+ getNumIndexes());

		}
		handle.flushBuffer();
		handle.methodVar = null; // Not used for this type of search
	}
	
	/**
	 * Efficiently read a bunch of indexes from this archive, delegated 
	 * from MsgArchive. This method handles the forward index-file search
	 * algorithm.
	 * @param handle the search handle
	 * @param stopSearchMsec time to stop searching.
	 * @return one of the SEARCH_RESULT codes defined in MsgArchive.
	 * @see lrgs.archive.MsgArchive.search(SearchHandle handle)
	 */
	public synchronized int searchIndex(SearchHandle handle, long stopSearchMsec)
		throws ArchiveUnavailableException, SearchTimeoutException
	{
		int numIndexes = getNumIndexes();
		Date untilD = handle.filter.getUntilTime();
		int untilTt = Integer.MAX_VALUE;
		if (untilD != null)
			untilTt = (int)(untilD.getTime() / 1000L);
		int numAdded = 0;
int skipped = 0;
//boolean testcli = handle.filter.getClientName().contains("put ip or hostname here");
//boolean testcli = handle.filter.getClientName().contains("localhost");
//boolean testcli = handle.filter.getClientName().contains("OISIN2");

//if (testcli)
//Logger.instance().info(module+" MJM "+myname+".searchIndex untilTt=" 
//+ untilTt + "(" + (untilD==null?"null":debugFmt.format(untilD)) +")"
//+ ", numIndexes=" + numIndexes
//+ ", nextIndex=" + handle.nextIndexNum);
		
		DcpMsgIndex indexBuf[] = new DcpMsgIndex[INDEX_BUF_SIZE];
		for(int i=0;i 0; i++)
			{
				DcpMsgIndex dmi = indexBuf[i];

				// If this is a RealTime retrieval, and 'settlingTime'
				// is selected and this msg rcv-time is within 30 seconds
				// of now, then break;
				if (dmi != null
				 && untilTt == Integer.MAX_VALUE
				 && handle.filter.realTimeSettlingDelay()
				 && (dmi.getLocalRecvTime().getTime() > now-30000L))
				{
					
//if (testcli)
//Logger.instance().info(
//"Archive: Hit settling delay for msg from " + 
//dmi.getDcpAddress() + " with local rcv time=" + 
//dmi.getLocalRecvTime() + ", and xmit time=" + dmi.getXmitTime());
					settlingDelayHit = true;
					break retrieve_loop;
				}

				// We are going to process this message.
				handle.nextIndexNum++;
				indexBuf[i] = null;

//if (debug && dmi.getOffset() == 0L)
//Logger.instance().debug1("Reading 1st msg in file");
				if (dmi != null
				 && (dmi.getFlagbits() & DcpMsgFlag.MSG_DELETED) == 0
				 && handle.filter.passes(dmi)
				 && dmi.getOffset() >= 0L)
				{
					// Passes criteria -- read the message & add to handle buf.
					try
					{
						dmi.setDcpMsg(msgFile.readMsg(dmi.getOffset()));
						handle.addIndex(dmi);
						numAdded++;
					}
					catch(IOException ex) 
					{ // shouldn't happen unless someone deleted msg file.
						String msg = myname + 
							" Corrupt index or message file idxnum="
							+ (handle.nextIndexNum-1) + ": " + ex;
						Logger.instance().failure(module + " " + msg);
						continue;
						//throw new ArchiveUnavailableException(msg,
						//	LrgsErrorCode.DARCFILEIO);
					}
				}
			}
		}
		
		// Four possibilities for breaking out of loop:
		//   1. Handle's buffer is full (capacity == 0)
		//   2. Out of time & must return whatever results I have now.
		//   3. Real-Time Settling delay hit
		//   4. I hit the end of this archive.

		// Fell through: Either handle-full or End of this archive reached.
		if (handle.capacity() == 0)
		{
//if (testcli)
//Logger.instance().info(module + " MJM returning SEARCH_RESULT_MORE");
			return MsgArchive.SEARCH_RESULT_MORE;
		}
		
		// Out of time & must return results to client.
		if (System.currentTimeMillis() >= stopSearchMsec)
		{
//if (testcli)
//Logger.instance().info(module + 
//" MJM search stopped because search-time-limit exceeded.");
			return MsgArchive.SEARCH_RESULT_TIMELIMIT;
		}

		if (settlingDelayHit)
		{
//if (testcli)
//Logger.instance().info(module + " " + myname
//+ " MJM settlingDelayHit numIndexes=" + numIndexes
//+ ", nextIndex=" + handle.nextIndexNum
//+ ", skipped=" + skipped + " returning SEARCH_RESULT_PAUSE");

			// No until specified -- tell caller to pause before trying again.
			return MsgArchive.SEARCH_RESULT_PAUSE;
		}

		// Otherwise, I hit the end of this file.
		
		// If I'm not the current archive, jump to the next archive.
		if (!isCurrent)
		{
			handle.periodStartTime = startTime + periodDuration;
			handle.nextIndexNum = 0;
			handle.minIdx = 0;

//if (testcli)
//Logger.instance().info(module + " MJM " +
//myname + " End of non-current archive, switching to archive with start="
//+ debugFmt.format(new Date(handle.periodStartTime*1000L)));

			return MsgArchive.SEARCH_RESULT_MORE;
		}

//if (testcli)
//Logger.instance().info(module + " MJM " + myname 
//+ " End of current archive loop, handle filled with " + handle.idxBufFillLength 
//+ ", nextIdxBufNum=" + handle.nextIdxBufNum
//+ ", untilTt=" + untilTt + ", numAdded=" + numAdded);

		// Otherwise, end of current archive.
		//if (handle.isEmpty())
		if (numAdded == 0)
		{
			if (untilTt == Integer.MAX_VALUE)
			{
//if (testcli)
//Logger.instance().info(module + " " + myname
//+ " numIndexes=" + numIndexes
//+ ", nextIndex=" + handle.nextIndexNum
//+ ", skipped=" + skipped + " returning SEARCH_RESULT_PAUSE");

			  // No until specified -- tell caller to pause before trying again.
				return MsgArchive.SEARCH_RESULT_PAUSE;
			}
			else
			{
//if (testcli)
//Logger.instance().info(module + " " + myname + " returning SEARCH_RESULT_DONE");
				return MsgArchive.SEARCH_RESULT_DONE;
			}
		}
		else
		{
//if (testcli)
//Logger.instance().info(module + " " + myname + " FINAL returning SEARCH_RESULT_MORE");
			return MsgArchive.SEARCH_RESULT_MORE;
		}

/*
TODO:
1. Incorporate cache.
2. Incorporate Lrgs UNTIL time cutting off a search.
3. Above algorithm doesn't allow UNTIL time in future. Fix this.
4. Handle IO exceptions from index & msg file.
*/
	}

	/**
	 * Public synchronized version for call from outside the package. The actual
	 * method is not synchronized because it's called internally within other
	 * synchronized blocks.
	 */
	public synchronized DcpMsgIndex getIndexEntrySync(int idxNum)
	{
		return getIndexEntry(idxNum);
	}

	/**
	 * Returns the index from the number. If currently in the cache, just 
	 * return the reference. Else, read it from disk, construct, & return it.
	 * @return the index entry
	 */
	private DcpMsgIndex getIndexEntry( int idxNum)
	{
		if (idxNum < 0)
			return null;

		DcpMsgIndex dmi = (DcpMsgIndex)null;
		if (cache != null
		 && (dmi = cache.getByIndexNum(idxNum)) != null)
		{
			return dmi;
		}
		else 
		{
			DcpMsgIndex ret[] = new DcpMsgIndex[1];
			ret[0] = new DcpMsgIndex();
			try
			{
				int nr = indexFile.readIndexes(idxNum, 1, ret);
				if (nr == 0)
					return null;
				return ret[0];
			}
			catch(IOException ex)
			{
				Logger.instance().warning(module + " Archive " + myname +
					" error reading index " + idxNum + ": " + ex);
				return null;
			}
		}
	}

	/**
	 * Reads the message corresponding to the index, and places it inside
	 * the index data structure.
	 * @param dmi the index
	 * @throws ArchiveUnavailableException if error reading message file.
	 */
	public void readMessage(DcpMsgIndex dmi)
		throws ArchiveUnavailableException
	{
		if (dmi.getDcpMsg() != null)
			return;

		try { dmi.setDcpMsg(msgFile.readMsg(dmi.getOffset())); }
		catch(IOException ex)
		{
			throw new ArchiveUnavailableException(
				"Corrupt archive " + myname 
				+ " cannot read message for existing index at offset " 
				+ dmi.getOffset() + ": " + ex, LrgsErrorCode.DARCFILEIO);
		}
	}

	/**
	 * Internal method to get the end index number for the specified minute.
	 * This is normally the start of the next index minus one. But this method
	 * accounts for the last minute of the day, and the case where subsequent
	 * minutes don't have any data.
	 * @return end index for specified minute, or -1 if index file is empty.
	 */
	private int getMinuteEndIndexNum(int min)
	{
		int nextMin = min+1;
		for(; nextMin= MIN_PER_DAY)
			return indexFile.getNumEntries() - 1;
		else
			return minuteIndex[nextMin].startIndexNum - 1;
	}

	public String getRootPath() { return rootPath; }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy