// ********************************************************************** // // <copyright> // // BBN Technologies // 10 Moulton Street // Cambridge, MA 02138 // (617) 873-8000 // // Copyright (C) BBNT Solutions LLC. All rights reserved. // // </copyright> // ********************************************************************** // // $Source: /cvs/distapps/openmap/src/corba/com/bbn/openmap/layer/rpf/corba/CRFPServer.java,v $ // $RCSfile: CRFPServer.java,v $ // $Revision: 1.6 $ // $Date: 2005/08/11 19:30:00 $ // $Author: dietrick $ // // ********************************************************************** package com.bbn.openmap.layer.rpf.corba; import java.awt.event.ActionListener; import java.util.Enumeration; import java.util.Hashtable; import java.util.StringTokenizer; import java.util.Vector; import javax.swing.Timer; import com.bbn.openmap.image.JPEGHelper; import com.bbn.openmap.layer.rpf.RpfCacheHandler; import com.bbn.openmap.layer.rpf.RpfColortable; import com.bbn.openmap.layer.rpf.RpfCoverageBox; import com.bbn.openmap.layer.rpf.RpfFrameCacheHandler; import com.bbn.openmap.layer.rpf.RpfIndexedImageData; import com.bbn.openmap.layer.rpf.RpfSubframe; import com.bbn.openmap.layer.rpf.RpfTocHandler; import com.bbn.openmap.layer.rpf.RpfViewAttributes; import com.bbn.openmap.layer.rpf.corba.CRpfFrameProvider.CRFPCADRGProjection; import com.bbn.openmap.layer.rpf.corba.CRpfFrameProvider.CRFPCoverageBox; import com.bbn.openmap.layer.rpf.corba.CRpfFrameProvider.CRFPViewAttributes; import com.bbn.openmap.layer.rpf.corba.CRpfFrameProvider.RawImage; import com.bbn.openmap.layer.rpf.corba.CRpfFrameProvider.ServerPOA; import com.bbn.openmap.layer.rpf.corba.CRpfFrameProvider.XYPoint; import com.bbn.openmap.proj.CADRG; import com.bbn.openmap.proj.coords.LatLonPoint; import com.bbn.openmap.util.Debug; import com.bbn.openmap.util.corba.CORBASupport; /** * The CRFPServer is a server implementation of the * CorbaRpfFrameProvider.idl. It realy implements most of the fuctions * of the RpfFrameProvider, but is not one. The CRFPClient is the * RpfFrameProvider. * * <P> * This server requires the com.sun.image.codec.jpeg package. */ public class CRFPServer extends ServerPOA implements ActionListener { protected static String iorfile = null; protected static String naming = null; /** A cache for every client. */ Hashtable caches; /** View Attributes for every client. */ Hashtable viewAttributeLists; /** The cache for the current client. */ protected RpfFrameCacheHandler currentCache; /** The view attrbutes for the current client. */ protected RpfViewAttributes currentViewAttributes; /** The paths to the RPF directories. */ protected String[] rpfpaths; /** The Rpf Table of Contents handlers for the data. */ protected RpfTocHandler[] tocs; /** Hashtable to keep track of how old certain caches are. */ Hashtable timestamps; /** * Timer for clearing out caches from sloppy clients. It's only * enabled when the -timewindow flag is used. */ Timer timer; /** 10, or the default number of active caches kept. */ public final static int DEFAULT_MAX_USERS = 10; /** The number of caches kept by the server. */ protected int maxUsers = DEFAULT_MAX_USERS; /** 5 minutes. The default timer cycle. */ public final static int DEFAULT_TIME_WINDOW = 1000 * 60 * 5; // 5 // minutes /** * The amount of time (milliseconds) reflecting how long an * inactive cache is kept */ protected long timeWindow = DEFAULT_TIME_WINDOW; /** * Default Constructor. */ public CRFPServer() { this("Default"); } /** * The constructor that you should use. * * @param name the identifying name for persistance. */ public CRFPServer(String name) { super(); caches = new Hashtable(); viewAttributeLists = new Hashtable(); timestamps = new Hashtable(); } /** * Get the current cache given a unique ID. If a cache is not * here, create it. * * @param uniqueID a unique identifier. */ protected RpfFrameCacheHandler getCurrentCache(String uniqueID) { RpfFrameCacheHandler cache = (RpfFrameCacheHandler) caches.get(uniqueID); if (cache == null && tocs != null) { Debug.message("crfp", "CRFPServer: Creating cache for new client"); cache = new RpfFrameCacheHandler(tocs); caches.put(uniqueID, cache); } timestamps.put(uniqueID, new Long(System.currentTimeMillis())); return cache; } /** * Get rid of any cache that is older than the time window. */ protected void cleanCache(long timeWindow) { // OK, we need to get rid of one. long currentTime = System.currentTimeMillis(); Enumeration keys = timestamps.keys(); while (keys.hasMoreElements()) { Object tester = keys.nextElement(); Long time = (Long) timestamps.get(tester); if ((currentTime - time.longValue()) >= timeWindow) { caches.remove(tester); timestamps.remove(tester); viewAttributeLists.remove(tester); if (Debug.debugging("crfp")) { Debug.output("Expired cache, removing, have " + caches.size() + " caches left."); } } } } /** * Create a spot in the cache for a new entry. If something is * removed from the cache, it is returned here. */ protected RpfCacheHandler sweepCaches() { if (caches.size() < maxUsers) { return null; } // OK, we need to get rid of one. long diff = Long.MAX_VALUE; Enumeration keys = timestamps.keys(); Object getRid = null; while (keys.hasMoreElements()) { Object tester = keys.nextElement(); Long time = (Long) timestamps.get(tester); if (time.longValue() < diff) { getRid = tester; diff = time.longValue(); } } boolean DEBUG = false; if (getRid != null) { if (Debug.debugging("crfp")) { DEBUG = true; } if (DEBUG) Debug.output("Removing cache for new user, was " + caches.size()); caches.remove(getRid); timestamps.remove(getRid); viewAttributeLists.remove(getRid); if (DEBUG) Debug.output(" now " + caches.size()); } if (caches.size() >= maxUsers) { return sweepCaches(); } else { return (RpfCacheHandler) getRid; } } /** * Get the current view attributes given a unique ID. If view * attributes are not here, create them. * * @param uniqueID a client-unique identifier. */ protected RpfViewAttributes getCurrentViewAttributes(String uniqueID) { RpfViewAttributes va = (RpfViewAttributes) viewAttributeLists.get(uniqueID); if (va == null) { Debug.message("crfp", "CRFPServer: Creating attributes for new client"); va = new RpfViewAttributes(); viewAttributeLists.put(uniqueID, va); } return va; } /** * Set the view attributtes for the current client. * * @param va the view attribute settings. * @param uniqueID a client-unique identifier. */ public void setViewAttributes(CRFPViewAttributes va, String uniqueID) { currentViewAttributes = getCurrentViewAttributes(uniqueID); currentViewAttributes.numberOfColors = (int) va.numberOfColors; currentViewAttributes.opaqueness = (int) va.opaqueness; currentViewAttributes.scaleImages = va.scaleImages; currentViewAttributes.imageScaleFactor = va.imageScaleFactor; currentViewAttributes.chartSeries = va.chartSeries; if (Debug.debugging("crfp")) { Debug.output("CRFPServer: Setting attributes for client:\n " + currentViewAttributes); } } /** * Get the Coverage Boxes that fit the geographical area given. * * @param ullat NW latitude. * @param ullon NW longitude * @param lrlat SE latitude * @param lrlon SE longitude * @param p a CADRG projection * @param uniqueID a client-unique identifier. */ public CRFPCoverageBox[] getCoverage(double ullat, double ullon, double lrlat, double lrlon, CRFPCADRGProjection p, String uniqueID) { Debug.message("crfp", "CRFPServer: Handling coverage request for client"); currentCache = getCurrentCache(uniqueID); currentViewAttributes = getCurrentViewAttributes(uniqueID); currentCache.setViewAttributes(currentViewAttributes); LatLonPoint llpoint = new LatLonPoint.Double(p.center.lat, p.center.lon); CADRG proj = new CADRG(llpoint, p.scale, p.width, p.height); Vector vector = currentCache.getCoverage(ullat, ullon, lrlat, lrlon, proj); return vectorToCRFPCoverageBoxes(vector); } /** * Method that provides all the coverage boxes that could provide * coverage over the given area. * * @param ullat NW latitude. * @param ullon NW longitude * @param lrlat SE latitude * @param lrlon SE longitude * @param p a CADRG projection * @param uniqueID a client-unique identifier. */ public CRFPCoverageBox[] getCatalogCoverage(double ullat, double ullon, double lrlat, double lrlon, CRFPCADRGProjection p, String chartSeriesCode, String uniqueID) { Debug.message("crfp", "CRFPServer: handling catalog request for client"); currentCache = getCurrentCache(uniqueID); currentViewAttributes = getCurrentViewAttributes(uniqueID); currentCache.setViewAttributes(currentViewAttributes); LatLonPoint llpoint = new LatLonPoint.Float(p.center.lat, p.center.lon); CADRG proj = new CADRG(llpoint, p.scale, p.width, p.height); Vector vector = currentCache.getCatalogCoverage(ullat, ullon, lrlat, lrlon, proj, chartSeriesCode); return vectorToCRFPCoverageBoxes(vector); } /** * Convert a Vector of RpfCoverageBox to a CRFPCoverageBox array. * * @param vector vector of RpfCoverageBox. * @return array of CRFPCoverageBox. */ protected CRFPCoverageBox[] vectorToCRFPCoverageBoxes(Vector vector) { int size = vector.size(); CRFPCoverageBox[] rets = new CRFPCoverageBox[size]; for (int i = 0; i < size; i++) { RpfCoverageBox box = (RpfCoverageBox) vector.elementAt(i); if (box != null) { rets[i] = new CRFPCoverageBox((float) box.nw_lat, (float) box.nw_lon, (float) box.se_lat, (float) box.se_lon, box.subframeLatInterval, box.subframeLonInterval, box.chartCode, (short) box.zone, new XYPoint((short) box.startIndexes.x, (short) box.startIndexes.y), new XYPoint((short) box.endIndexes.x, (short) box.endIndexes.y), (short) box.tocNumber, (short) box.entryNumber, box.scale, box.percentCoverage); } } return rets; } /** * Retrieve the subframe data from the frame cache, decompress it, * and convert it to a JPEG image. * * @param tocNumber the number of the RpfTocHandler for the * currentCache to use. * @param entryNumber the coverage box index that contains the * subframe. * @param x the horizontal location of the subframe. The * RpfCacheHandler figures this out. * @param y the vertical location of the subframe. The * RpfCacheHandler figures this out. * @param jpegQuality the compression parameter for the image. * @param uniqueID a client-unique identifier. * @return byte[] of jpeg image */ public byte[] getSubframeData(short tocNumber, short entryNumber, short x, short y, float jpegQuality, String uniqueID) { Debug.message("crfpdetail", "CRFPServer: handling subframe request for client"); try { currentCache = getCurrentCache(uniqueID); int[] pixels = currentCache.getSubframeData((int) tocNumber, (int) entryNumber, (int) x, (int) y); if (pixels != null) { byte[] compressed = null; try { compressed = JPEGHelper.encodeJPEG(RpfSubframe.PIXEL_EDGE_SIZE, RpfSubframe.PIXEL_EDGE_SIZE, pixels, jpegQuality); } catch (Exception e) { Debug.error("CRFPServer: JPEG Compression error: " + e); compressed = new byte[0]; } if (Debug.debugging("crfpdetail")) { Debug.output("CRFPServer: subframe is " + compressed.length + " bytes"); } return compressed; } } catch (OutOfMemoryError oome) { handleMemoryShortage(); } return new byte[0]; } public RawImage getRawSubframeData(short tocNumber, short entryNumber, short x, short y, String uniqueID) { Debug.message("crfpdetail", "CRFPServer: handling raw subframe request for client"); RawImage ri = new RawImage(); RpfIndexedImageData riid = null; try { currentCache = getCurrentCache(uniqueID); riid = currentCache.getRawSubframeData((int) tocNumber, (int) entryNumber, (int) x, (int) y); } catch (OutOfMemoryError oome) { handleMemoryShortage(); riid = null; } if (riid == null || riid.imageData == null) { Debug.message("crfpdetail", "CRFPServer: null image data"); ri.imagedata = new byte[0]; ri.colortable = new int[0]; } else { ri.imagedata = riid.imageData; RpfColortable colortable = currentCache.getColortable(); ri.colortable = new int[colortable.colors.length]; for (int i = 0; i < colortable.colors.length; i++) { ri.colortable[i] = colortable.colors[i].getRGB(); } Debug.message("crfpdetail", "CRFPServer: GOOD image data"); } return ri; } /** * Get the subframe attributes for the identified subframe. * Provided as a single string, with newline characters separating * features. * * @param tocNumber the number of the RpfTocHandler for the * currentCache to use. * @param entryNumber the coverage box index that contains the * subframe. * @param x the horizontal location of the subframe. The * RpfCacheHandler figures this out. * @param y the vertical location of the subframe. The * RpfCacheHandler figures this out. * @param uniqueID a client-unique identifier. * @return String with the subframe attributes. */ public String getSubframeAttributes(short tocNumber, short entryNumber, short x, short y, String uniqueID) { Debug.message("crfpdetail", "CRFPServer: handling subframe attribute request for client"); try { currentCache = getCurrentCache(uniqueID); return currentCache.getSubframeAttributes((int) tocNumber, (int) entryNumber, (int) x, (int) y); } catch (OutOfMemoryError oome) { handleMemoryShortage(); } return new String(); } /** * The signoff function lets the server know that a client is * checking out. * * @param uniqueID a client-unique identifier. */ public void signoff(String uniqueID) { Debug.message("crfp", "CRFPServer: Client" + uniqueID + " signing off!"); caches.remove(uniqueID); viewAttributeLists.remove(uniqueID); timestamps.remove(uniqueID); } protected void handleMemoryShortage() { Debug.error("CRFPServer out of memory! Dumping all caches!"); caches.clear(); viewAttributeLists.clear(); timestamps.clear(); } /** * Start the server. * * @param args command line arguments. */ public void start(String[] args) { CORBASupport cs = new CORBASupport(); if (args != null) { parseArgs(args); } cs.start(this, args, iorfile, naming); } /** * Set the maximum number of caches to given number, represented * in a string. If the string isn't a good number, * DEFAULT_MAX_USERS will be used. */ public void setMaxUsers(String number) { try { setMaxUsers(Integer.parseInt(number)); } catch (NumberFormatException nfe) { setMaxUsers(DEFAULT_MAX_USERS); } } /** * Set the maximum number of caches to given number. If the number * isn't a good, DEFAULT_MAX_USERS will be used. */ public void setMaxUsers(int number) { if (number >= 1) { maxUsers = number; } else { Debug.output("Max users of " + number + " not supported, set to " + DEFAULT_MAX_USERS); maxUsers = DEFAULT_MAX_USERS; } } /** * Get the maximum number of caches allowed in the server. One per * user. Get it? */ public int getMaxUsers() { return maxUsers; } /** * Set how long a user's cache will be kept around. */ public void setTimeWindow(String number) { try { setTimeWindow(Long.parseLong(number)); } catch (NumberFormatException nfe) { setTimeWindow(DEFAULT_TIME_WINDOW); } } /** * Set how long a user's cache will be kept around. */ public void setTimeWindow(long number) { if (timer == null) { timer = new javax.swing.Timer((int) number, (ActionListener) this); } if (number >= 1) { timeWindow = number; Debug.output("Timer enabled, set to " + (number / 1000) + " seconds"); } else if (number == 0) { // stop timer timer.stop(); return; } else { timeWindow = DEFAULT_TIME_WINDOW; Debug.output("Timer enabled, set to " + (DEFAULT_TIME_WINDOW / 1000) + " seconds"); } timer.start(); } /** * The the time window for how long users caches are kept around. */ public long getTimeWindow() { return timeWindow; } /** * Handle an ActionEvent from the Timer. * * @param ae action event from the timer. */ public void actionPerformed(java.awt.event.ActionEvent ae) { if (Debug.debugging("crfp")) { Debug.output("Ping! checking cache..."); } cleanCache(getTimeWindow()); } /** */ public void parseArgs(String[] args) { rpfpaths = null; try { for (int i = 0; i < args.length; i++) { if (args[i].equalsIgnoreCase("-ior")) { iorfile = args[++i]; } else if (args[i].equalsIgnoreCase("-name")) { naming = args[++i]; } else if (args[i].equalsIgnoreCase("-help")) { printHelp(); } else if (args[i].equalsIgnoreCase("-rpfpaths")) { rpfpaths = getPaths(args[++i]); } else if (args[i].equalsIgnoreCase("-maxusers")) { setMaxUsers(args[++i]); } else if (args[i].equalsIgnoreCase("-timewindow")) { setTimeWindow(args[++i]); } else if (args[i].equalsIgnoreCase("-verbose")) { Debug.put("crfp"); } else if (args[i].equalsIgnoreCase("-h")) { printHelp(); } } } catch (ArrayIndexOutOfBoundsException aioobe) { printHelp(); } // if you didn't specify an iorfile if (iorfile == null && naming == null) { Debug.error("CRFPServer: IOR file and name service name are null! Use `-ior' or '-name' flag!"); System.exit(-1); } if (rpfpaths == null) { Debug.error("CRFPServer: No RPF directory paths specified! Use `-rpfpaths' flag!"); System.exit(-1); } else { tocs = RpfFrameCacheHandler.createTocHandlers(rpfpaths); Debug.output("CRFPServer: CRFPServer! Running with paths => "); for (int j = 0; j < rpfpaths.length; j++) { Debug.output(" " + rpfpaths[j]); } } } private String[] getPaths(String str) { StringTokenizer tok = new StringTokenizer(str, ";"); int len = tok.countTokens(); String[] paths = new String[len]; for (int j = 0; j < len; j++) { paths[j] = tok.nextToken(); } return paths; } /** * <b>printHelp </b> should print a usage statement which reflects * the command line needs of your specialist. */ public void printHelp() { Debug.output("usage: java CRFPServer [-ior <file> || -name <NAME>] -rpfpaths \"<path to rpf dir>;<path to rpf dir>;<...>\" -maxusers <max number of users to cache> -timewindow <milliseconds for idle cache removal>"); System.exit(1); } public static void main(String[] args) { Debug.init(System.getProperties()); // Create the specialist server CRFPServer srv = new CRFPServer("CRFPServer"); srv.start(args); } }