//
// AddeURLConnection.java
//

/*
This source file is part of the edu.wisc.ssec.mcidas package and is
Copyright (C) 1998 - 2017 by Tom Whittaker, Tommy Jasmin, Tom Rink,
Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden
and others.
 
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Library General Public License for more details.

You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
MA 02111-1307, USA
*/

package edu.wisc.ssec.mcidas.adde;

import java.io.DataInputStream;
import java.io.BufferedInputStream;
import java.io.DataOutputStream;
import java.io.InputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLDecoder;
import java.util.StringTokenizer;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.GZIPInputStream;
import edu.wisc.ssec.mcidas.McIDASUtil;
import HTTPClient.UncompressInputStream;

/**
 * This class extends URLConnection, providing the guts of the
 * work to establish an ADDE network connection, put together
 * a request packet, and initiate data flow.  Connections for
 * image data, image directories, grid data, grid directory, 
 * point source data, text, weather text, observational text
 * and dataset information (McIDAS AGET, ADIR, GDIR, GGET, MDKS, 
 * TXTG, OBTG, WXTG and LWPR requests) are supported.
 *
 * @see 
 *      McIDAS Programmer's Manual
 *
 * 
 *
 * URLs must all have the following format:
 *
 *   adde://host/request?keyword_1=value_1&keyword_2=value_2
 *
 * where request can be one of the following:
 *
 *   datasetinfo - request for data set information (LWPR)
 *   griddirectory - request for grid directory information (GDIR)
 *   griddata - request for grid data (GGET)
 *   imagedata - request for data in AreaFile format (AGET)
 *   imagedirectory - request for image directory information (ADIR)
 *   pointdata - request for point data (MDKS)
 *   textdata - request to read a text file (TXTG) 
 *   wxtext   - request to read a weather text file (WTXG) 
 *   obtext   - request to read a observation text file (OBTG) 
 *
 * There can be any valid combination of the following supported keywords:
 *
 * -------for any request
 *
 *   group=<groupname>         ADDE group name
 *   user=<user_id>            ADDE user identification
 *   proj=<proj #>             a valid ADDE project number
 *   trace=<0/1>               setting to 1 tells server to write debug 
 *                               trace file 
 *   version=                  ADDE version number, currently 1 except for
 *                             griddata requests
 *   debug=                    set to true to watch the printlns stream by
 *   compress=                 set to "gzip" if you want to use the GZIP
 *                             compression or "compress" if you want to use 
 *                             transfers in Unix compress format (You need to 
 *                             have the VisAD package if you want to support 
 *                             this.)  default = none.
 *   port=                     Socket port to connect on.  Overridden by
 *                             a port specified in the host 
 *                             (e.g., adde.ucar.edu:500)
 *
 * -------for images:
 *
 *   descr=<descriptor>        ADDE descriptor name
 *   band=<band>               spectral band or channel number 
 *   mag=<lmag> <emag>         image magnification, postitive for blowup, 
 *                               negative for blowdown (default = 1, emag=lmag)
 *                               (imagedata only)
 *   latlon=<lat> <lon>        lat/lon point to center image on (imagedata only)
 *   linele=<lin> <ele> <type> line/element to center image on (imagedata only)
 *   place=<placement>         placement of lat/lon or linele points (center 
 *                               or upperleft (def=center)) (imagedata only)
 *   track<0>               tell the ADDE server not to track for this
 *
 *   pos=<position>            request an absolute or relative ADDE position 
 *                               number.  May use <start> <end>  Default
 *                               for <end> is 0 if start<0, or =start otherwise.
 *   size=<lines> <elements>   size of image to be returned (imagedata only)
 *   unit=<unit>               to specify calibration units other than the 
 *                               default 
 *   spac=<bytes>              number of bytes per data point, 1, 2, or 4 
 *                               (imagedata only)
 *   doc=<yes/no>              specify yes to include line documentation 
 *                               with image (def=no) 
 *   nav=<lalo>                include the lat-lon navigation info and not the O&A.
 *   aux=<yes/no>              specify yes to include auxilliary information 
 *                               with image 
 *   time=<time1> <time2>      specify the time range of images to select
 *                               (def=latest image if pos not specified)
 *   day=<day>                 specify the day of the images to select
 *                               (def=latest image if pos not specified)
 *   cal=<cal type>            request a specific calibration on the image 
 *                               (imagedata only)
 *   id=<stn id>               radar station id 
 *
 * ------ for grids:
 *
 *   descr=<descriptor>        ADDE descriptor name
 *   param=<param list>        parameter code list
 *   time=<model run time>     time in hhmmss format
 *   day=<model run day>       day in ccyyddd format
 *   lev=<level list>          list of requested levels (value or SFC, MSL 
 *                               or TRO)
 *   ftime=<forecast time>     valid time (hhmmss format) (use with fday)
 *   fday=<forecast day>       forecast day (ccyyddd)
 *   fhour=<forecast hours>    forecast hours (offset from model run time)
 *                                (hhmmss format)
 *   lat=<min lat> <max lat>   latitude bounding box (needs lon specified)
 *   lon=<min lon> <max lon>   longitude bounding box (needs lat specified)
 *   row=<min row> <max row>   row bounding box (needs col specified)
 *   col=<min col> <max col>   column bounding box (needs row specified)
 *   skip=<row> <col>          skip factors for rows and columns (def = 1 1)
 *   gpro=<pro>                grid projection (e.g. TANC)
 *   src=<s1> ... <s2>         list of grid sources (ETA, AVN, etc)
 *   drange=<btime> <etime> <inc>  range of primary days 
 *                                 that the grid represents (cannot use with 
 *                                 day=)
 *   frange=<btime> <etime> <inc>  range of forecast times 
 *                                 that the grid represents (cannot use with
 *                                 fhour=, fday= or ftime=)
 *   trange=<btime> <etime> <inc>  range of primary times 
 *                                 that the grid represents (cannot use with time=)
 *   num=<max>                 maximum number of grids (nn) to return (def=1)
 *
 * ------ for point data:
 *
 *   descr=<descriptor>        ADDE descriptor name
 *   pos=<position>            request an absolute or relative ADDE 
 *                               position number
 *   select=<select clause>    to specify which data is required
 *   param=<param list>        what parameters to return
 *   num=<max>                 maximum number of obs to return
 *   
 * ------ for text data:
 *
 *   descr=<descriptor>        ADDE descriptor name 
 *                             (may also be "descr=FILE=filename")
 *   file=<filename>           name of text file to read
 *
 * ------ for weather text data:
 *
 *   group=<group>         weather text group (default= RTWXTEXT)
 *   prod=<product>        predefind product name
 *   apro=<val1 .. valn>   AFOS/AWIPS product headers to match (don't 
 *                         use with wmo keyword
 *   astn=<val1 .. valn>   AFOS/AWIPS stations to match
 *   wmo= <val1 .. valn>   WMO product headers to match (don't 
 *                         use with apro keyword
 *   wstn=<val1 .. valn>   WMO stations to match
 *   day=<start end>       range of days to search
 *   dtime=<numhours>      maximum number of hours to search back (def=96)
 *   match=<match strings> list of character match strings to find from text
 *   num=<num>             number of matches to find (def=1)
 *
 * ------ for observational text data:
 *
 *   group=<group>         weather text group (default= RTWXTEXT)
 *   descr=<descriptor>    weather text subgroup (default=SFCHOURLY)
 *   id=<id1 id2 ... idn>  list of station ids
 *   co=<co1 co2 ... con>  list of countries
 *   reg=<reg1 reg2..regn> list of regions
 *   newest=<day hour>     most recent time to allow in request 
 *                         (def=current time)
 *   oldest=<day hour>     oldest observation time to allow in request
 *   type=<type>           numeric value for the type of ob
 *   nhours=<numhours>     maximum number of hours to search
 *   num=<num>             number of matches to find (def=1)
 *
 * -------------------------------------------------------------------------
 * The following keywords are required:
 *
 *   group 
 *   descr (except datasetinfo)
 *
 * These are case sensitive.  If you prefer to have them automatically upcased,
 * you can supply a system property on the command line for your application:
 *
 *     -Dadde.auto-upcase=true
 *
 * or add the "auto-upcase=true" keyword to the URL
 *
 * An example URL for images might look like:
 *
 *   adde://viper/imagedata?group=gvar&band=1&user=tjj&proj=6999&version=1
 *   
 * 
* * @author Tommy Jasmin, University of Wisconsin, SSEC * @author Don Murray, UCAR/Unidata * @author Tom Whittaker, SSEC/CIMSS * @author James Kelly, Australian Bureau of Meteorology */ public class AddeURLConnection extends URLConnection { private InputStream is = null; private DataInputStream dis = null; private DataOutputStream dos = null; private URL url; private static final Logger LOGGER = Logger.getLogger(AddeURLConnection.class.getName()); /** Set from the rawstream attribute. When true we don't read the first int bytes. This enables client code to move the byte stream to disk. */ private boolean rawStream = false; /** The default number of lines for an image request */ private final int DEFAULT_LINES = 480; /** The default number of elements for an image request */ private final int DEFAULT_ELEMS = 640; /** The default user id*/ public static final String DEFAULT_USER = "XXXX"; /** The default number of elements for an image request */ public static final int DEFAULT_PROJ = 0; /** The default user id*/ /** Size of an ADDE trailer */ private final static int TRAILER_SIZE = 92; /** Size of an ADDE request */ private final static int REQUEST_SIZE = 120; /** Size of an ADDE error message */ private final static int ERRMSG_SIZE = 72; /** Size of an ADDE error message offset */ private final static int ERRMSG_OFFS = 8; /** Flag for "compress" compression. Used to be synonymous with the port used for compress transfer */ private final static int COMPRESS = 503; /** Flag for "no compress" compression. Used to be synonymous with the port used for non-compressed transfer */ private final static int NO_COMPRESS = 500; /** Flag for GZip compression. Used to be synonymous with the port used for compressed transfers */ private final static int GZIP = 112; /** key for no compression */ private final int UNCOMPRESS_KEY = 1; /** key for compress compression */ private final int COMPRESS_KEY = 2; /** key for compress compression */ private final int GZIP_KEY = 3; /** default port transfers */ public final static int DEFAULT_PORT = 112; /** * ADDE Version 1 indicator */ public final static int VERSION_1 = 1; // ADDE server requests /** AGET request type */ public final static int AGET = 0; /** ADIR request type */ public final static int ADIR = 1; /** LWPR request type */ public final static int LWPR = 2; /** GDIR request type */ public final static int GDIR = 3; /** GGET request type */ public final static int GGET = 4; /** MDKS request type */ public final static int MDKS = 5; /** TXTG request type */ public final static int TXTG = 6; /** WTXG request type */ public final static int WTXG = 7; /** OBTG request type */ public final static int OBTG = 8; // ADDE data types (not used?) private final static int IMAGE = 100; private final static int GRID = 101; private final static int POINT = 102; private final static int TEXT = 103; private final static int WXTEXT = 104; private final static int OBTEXT = 105; private int numBytes = 0; private int dataType = IMAGE; private byte[] binaryData = null; // byte array to hold extra binary data /** request type - default to AGET */ private int reqType = AGET; /** debug flag - value can be overrided with debug=true */ private boolean debug = false; /** port to use for compression */ private int portToUse = 0; // set to zero for default /** compression type */ private int compressionType = GZIP; /** * * Constructor: just sets URL and calls superclass constructor. * Actual network connection is established in connect(). * * @param url url to use */ AddeURLConnection(URL url) throws IOException { super(url); this.url = normalizeURL(url); } /** * * Establishes an ADDE connection using the URL passed to the * constructor. Opens a socket on the ADDE port, and formulates * an ADDE image data request based on the file portion of URL. *

* An example URL might look like: *

* adde://viper.ssec.wisc.edu/image?group=gvar&band=1&mag=-8&version=1 * */ synchronized public void connect () throws IOException, AddeURLException { // First, see if we can use the URL passed in // verify the service is one we can handle // get rid of leading / // keep original to preserve case for user= clause String requestOriginal = url.getFile().substring(1); String request = requestOriginal.toLowerCase(); String path = url.getPath().substring(1).toLowerCase(); String query = url.getQuery(); debug = debug || request.indexOf("debug=true") >= 0; if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.finest("host from URL: " + url.getHost()); LOGGER.finest("file from URL: " + url.getFile()); } if (!path.startsWith("image") && (!path.startsWith("dataset")) && (!path.startsWith("dsinfo")) && (!path.startsWith("text")) && (!path.startsWith("wxtext")) && (!path.startsWith("obtext")) && (!path.startsWith("grid")) && (!path.startsWith("point")) ) { throw new AddeURLException("Request for unknown data"); } rawStream = (request.indexOf("rawstream=true")>=0); // service - for area files, it's either AGET (Area GET) or // ADIR (AREA directory) byte [] svc = null; if (path.startsWith("imagedir") || path.equals(AddeURL.REQ_ADIR)) { svc = "adir".getBytes(); reqType = ADIR; } else if (path.startsWith("dataset") || path.startsWith("dsinfo") || path.equals(AddeURL.REQ_LWPR)) { svc = "lwpr".getBytes(); reqType = LWPR; } else if (path.startsWith("text") || path.equals(AddeURL.REQ_TXTG)) { svc = "txtg".getBytes(); reqType = TXTG; } else if (path.startsWith("wxtext") || path.equals(AddeURL.REQ_WTXG)) { svc = "wtxg".getBytes(); reqType = WTXG; } else if (path.startsWith("obtext") || path.equals(AddeURL.REQ_OBTG)) { svc = "obtg".getBytes(); reqType = OBTG; } else if (path.startsWith("image") || path.equals(AddeURL.REQ_AGET)) { svc = "aget".getBytes(); reqType = AGET; } else if (path.startsWith("griddir") || path.equals(AddeURL.REQ_GDIR)) { svc = "gdir".getBytes(); reqType = GDIR; } else if (path.startsWith("grid") || path.equals(AddeURL.REQ_GGET)) { svc = "gget".getBytes(); reqType = GGET; } else if (path.startsWith("point") || path.equals(AddeURL.REQ_MDKS)) { svc = "mdks".getBytes(); reqType = MDKS; } else { throw new AddeURLException( "Invalid or unsupported ADDE service= "+svc.toString() ); } if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.finest("Service = " + new String(svc)); } // prep for real thing - get cmd from file part of URL int test = requestOriginal.indexOf("?"); //String uCmd = (test >=0) ? requestOriginal.substring(test+1) : requestOriginal; String uCmd = (query == null) ? requestOriginal : query; if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.finest("uCmd="+uCmd); } // build the command string StringBuffer sb = new StringBuffer(); switch (reqType) { case AGET: sb = decodeAGETString(uCmd); break; case ADIR: sb = decodeADIRString(uCmd); break; case LWPR: sb = decodeLWPRString(uCmd); break; case GDIR: sb = decodeGDIRString(uCmd); break; case GGET: sb = decodeGDIRString(uCmd); break; case MDKS: sb = decodeMDKSString(uCmd); break; case TXTG: sb = decodeTXTGString(uCmd); break; case WTXG: sb = decodeWTXGString(uCmd); break; case OBTG: sb = decodeOBTGString(uCmd); break; } // indefinitely use ADDE version 1 unless it's a GGET request sb.append(" VERSION="); sb.append((reqType == GGET || reqType == WTXG) ? "A" : "1"); // now convert to array of bytes for output since chars are two byte //String cmd = sb.toString().toUpperCase(); boolean a = Boolean.getBoolean("adde.auto-upcase"); boolean b = new Boolean( getValue(uCmd, "auto-upcase=", "false")).booleanValue(); String cmd = (a || b) ? sb.toString().toUpperCase() : sb.toString(); if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.finest(cmd); } byte [] ob = cmd.getBytes(); // get some other stuff // user initials - pass on what client supplied in user= keyword byte [] usr; String testStr = getValue(uCmd, "user=", DEFAULT_USER); if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.finest("user = " + testStr); } usr = testStr.getBytes(); // project number - we won't validate, but make sure it's there int proj = 0; testStr = getValue(uCmd, "proj=", ""+DEFAULT_PROJ); if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.finest("proj = " + testStr); } try { proj = Integer.parseInt(testStr); } catch (NumberFormatException e) { // TODO: Should we really throw an exception or just let it default to 0? throw new AddeURLException("Invalid project number: " + testStr, e); } // Figure out the port. if (url.getPort() == -1) { // not specified as part of URL testStr = getValue(uCmd, "port=", null); if (testStr != null) { try { portToUse = Integer.parseInt(testStr); } catch (NumberFormatException e) { // just use default LOGGER.severe( "Warning: Invalid port number \"" + testStr + "\" specified; using default port " + portToUse + " instead"); } } } else { // specified portToUse = url.getPort(); } // compression testStr = getValue(uCmd, "compress=", null); // if no compress keyword and if port was specified, use that as // the default for compression. if (testStr == null) { switch (portToUse) { case NO_COMPRESS: // port == 500 case UNCOMPRESS_KEY: // port == 1 testStr = "none"; break; case COMPRESS: // port == 503 case COMPRESS_KEY: // port == 2 testStr = "compress"; break; case GZIP: // port == 112 case GZIP_KEY: // port == 3 default: testStr = "gzip"; break; } } if (testStr.equalsIgnoreCase("gzip")) { compressionType = GZIP; } else if (testStr.equalsIgnoreCase("compress") || testStr.equalsIgnoreCase("true")) { // check to see if we can do uncompression try { Class c = Class.forName("HTTPClient.UncompressInputStream"); compressionType = COMPRESS; } catch (ClassNotFoundException cnfe) { LOGGER.severe( "Uncompression code not found, turning compression off"); } } else if (testStr.equalsIgnoreCase("none")) { compressionType = NO_COMPRESS; } if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.finest("compression = " + testStr); } // zero by default, for newer mcservs (1,2,3) if (portToUse < 4) { portToUse = DEFAULT_PORT; } // end of request decoding //------------------------------------------------------------------------- // now write this all to the port // first figure out which port to connect on. This can either be // one specified by the user. // // The priority is URL port, port=keyword, compression port (default) // get the IP address of the server byte [] ipa = new byte[4]; InetAddress ia = InetAddress.getByName(url.getHost()); ipa = ia.getAddress(); // if local ADDE host, force the compressionType to "off" if (ipa[0]==127 && ipa[1]==0 && ipa[2]==0 && ipa[3]==1) { compressionType = NO_COMPRESS; } if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.finest("connecting on port " + portToUse + " using " + (compressionType == GZIP ? "gzip" : compressionType == COMPRESS ? "compress" : "no") + " compression."); } Socket t; try { t = new Socket(url.getHost(), portToUse); // DRM 03-Mar-2001 } catch (UnknownHostException e) { throw new AddeURLException("Could not connect to host", e); } dos = new DataOutputStream ( t.getOutputStream() ); /* Now start pumping data to the server. The sequence is: - ADDE version (1 for now) - Server IP and Port number (latter used to determine compression) - Service Type (AGET, ADIR, etc) - Server IP and Port number (again) - Client address - User, project, password - Service Type (again) - Actual request */ // send version number - ADDE seems to be stuck at 1 dos.writeInt(VERSION_1); // send IP address of server // we know the server IP address is good cause we used it above dos.write(ipa, 0, ipa.length); // send ADDE port number which server uses to determine compression dos.writeInt(compressionType); // send the service type dos.write(svc, 0, svc.length); // now build and send request block, repeat some stuff // server IP address, port dos.write(ipa, 0, ipa.length); dos.writeInt(compressionType); // DRM 03-Mar-2001 // client IP address InetAddress lh = InetAddress.getLocalHost(); ipa = lh.getAddress(); dos.write(ipa, 0, ipa.length); // gotta send 4 user bytes, ADDE protocol expects it if (usr.length <= 4) { dos.write(usr, 0, usr.length); for (int i = 0; i < 4 - usr.length; i++) { dos.writeByte(' '); } } else { // if id entered was > 4 chars, complain throw new AddeURLException("Invalid user id: " + new String(usr)); } dos.writeInt(proj); // password chars - not used either byte [] pwd = new byte[12]; dos.write(pwd, 0, pwd.length); // service - resend svc array dos.write(svc, 0, svc.length); // Write out the data. There are 2 cases: // // 1) ob.length <= 120 // 2) ob.length > 120 // // In either case, there may or may not be additional binary data // int numBinaryBytes = 0; if (binaryData != null) numBinaryBytes = binaryData.length; if (ob.length > REQUEST_SIZE) { if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.finest("numBinaryBytes= " + numBinaryBytes); } dos.writeInt(ob.length + numBinaryBytes); // number of additional bytes dos.writeInt(ob.length); // number of bytes in request for (int i=0; i < REQUEST_SIZE - 4; i++) { // - 4 accounts for prev line dos.writeByte(0); } dos.write(ob,0,ob.length); } else { if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.finest("numBinaryBytes= " + numBinaryBytes); } dos.writeInt(numBinaryBytes); dos.write(ob, 0, ob.length); for (int i=0; i < REQUEST_SIZE - ob.length; i++) { dos.writeByte(' '); } } if (numBinaryBytes > 0) dos.write(binaryData, 0, numBinaryBytes); is = (compressionType == GZIP) ? new GZIPInputStream(t.getInputStream()) : (compressionType == COMPRESS) ? new UncompressInputStream(t.getInputStream()) : t.getInputStream(); dis = new DataInputStream(is); if (LOGGER.isLoggable(Level.FINEST) && (compressionType != portToUse) ) { LOGGER.finest("Compression is turned "+ ((compressionType == NO_COMPRESS)?"OFF":("ON using " + ((compressionType == GZIP)?"GZIP":"compress")))); } // get response from server, byte count coming if(!rawStream) { numBytes = dis.readInt(); if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.finest("server is sending: " + numBytes + " bytes"); } // if server returns zero, there was an error so read trailer and exit if (numBytes == 0) { byte [] trailer = new byte[TRAILER_SIZE]; dis.readFully(trailer, 0, trailer.length); String errMsg = new String(trailer, ERRMSG_OFFS, ERRMSG_SIZE); throw new AddeURLException(errMsg); } } // if we made it to here, we're getting data connected = true; } private static char AMPERSAND = '&'; /** * Search for a value in the string and return the value or the default * @param stringToSearch String to search for the value * @param key key to search for (includes '=' to disambiguate); * @param deflt default value * @return value in string */ private String getValue(String stringToSearch, String key, String deflt) { int startIdx = stringToSearch.toLowerCase().lastIndexOf(key); String retVal = deflt; if (startIdx >= 0) { int endIdx = stringToSearch.indexOf(AMPERSAND, startIdx); if (endIdx == -1) { // last on line endIdx = stringToSearch.length(); } retVal = stringToSearch.substring((startIdx + key.length()), endIdx); } return retVal; } /** * Get the request type * @return type of request (ADIR, AGET, etc) */ public int getRequestType() { return reqType; } /** * returns a reference to InputStream established in connect(). * calls connect() if client has not done so yet. * * @return InputStream reference */ synchronized public InputStream getInputStream () throws IOException { if (!connected) connect(); return is; } /** * * returns a reference to DataInputStream established in connect(). * calls connect() if client has not done so yet. * * @return DataInputStream reference */ synchronized public DataInputStream getDataInputStream () throws IOException { if (!connected) connect(); return dis; } /** * Return the number of bytes being sent by the server for the * first record. * * @return number of bytes send in the first record */ public int getInitialRecordSize() { return numBytes; } /** * Decode the ADDE request for image data. * * there can be any valid combination of the following supported keywords: * *

     *
     *   group=<groupname>         ADDE group name
     *   descr=<descriptor>        ADDE descriptor name
     *   band=<band>               spectral band or channel number 
     *   mag=<lmag> <emag>   image magnification, postitive for blowup, 
     *                                   negative for blowdown (default = 1, 
     *                                   emag=lmag) 
     *   latlon=<lat> <lon>  lat/lon point to center image on 
     *
     *   linele=<lin> <ele> <type> line/element to center image on 
     *                                   type = i(image, default), e(earth), a(area)
     *   place=<placement>         placement of lat/lon or linele points 
     *                                   c(center, default) or u(upperleft)  
     *   pos=<position>            request an absolute or relative ADDE position
     *                                   number
     *
     *   size=<lines> <elements>   size of image to be returned
     *   unit=<unit>               to specify calibration units other than the 
     *                                   default 
     *   spac=<bytes>              number of bytes per data point, 1, 2, or 4 
     *   doc=<yes/no>              specify yes to include line documentation 
     *                                   with image (def=no) 
     *   nav=<lalo>                include the lat-lon navigation info and not the O&A.
     *   aux=<yes/no>              specify yes to include auxilliary information
     *                                   with image 
     *   time=<time1> <time2>      specify the time range of images to select
     *                                   (def=latest image if pos not specified)
     *   day=<day>                 specify the day of the images to select
     *                                   (def=latest image if pos not specified)
     *   cal=<cal type>            request a specific calibration on the image 
     *   id=<stn id>               radar station id
     *   trace=<0/1>               setting to 1 tells server to write debug 
     *                                   trace file (imagedata, imagedirectory)
     *
     * the following keywords are required:
     *
     *   group
     *
     * an example URL might look like:
     *   adde://viper/imagedata?group=gvar&band=1
     *   
     * 
*/ private StringBuffer decodeAGETString(String uCmd) { StringBuffer buf = new StringBuffer(); boolean latFlag = false; boolean lonFlag = false; boolean linFlag = false; boolean eleFlag = false; String latString = null; String lonString = null; String linString = null; String eleString = null; String tempString = null; String testString = null; String lctestString = null; // Mandatory strings String groupString = null; String descrString = "ALL"; String posString = "0"; String numlinString = Integer.toString(DEFAULT_LINES); String numeleString = Integer.toString(DEFAULT_ELEMS); String magString = "X"; String traceString = "TRACE=0"; String spaceString = "SPAC=X"; String unitString = "UNIT=BRIT"; String auxString = "AUX=YES"; String navString = "NAV=X"; String calString = "CAL=X"; String docString = "DOC=NO"; String timeString = "TIME=X X I"; String lineleType = "A"; String placement = "C"; String trackString = "TRACKING=0"; StringTokenizer cmdTokens = new StringTokenizer(uCmd, "&"); while (cmdTokens.hasMoreTokens()) { testString = cmdTokens.nextToken(); lctestString = testString.toLowerCase(); // group, descr and pos are mandatory if (lctestString.startsWith("grou")) { groupString = testString.substring(testString.indexOf("=") + 1); } else if (lctestString.startsWith("des")) { descrString = testString.substring(testString.indexOf("=") + 1); } else if (lctestString.startsWith("pos")) { posString = testString.substring(testString.indexOf("=") + 1); } else if (lctestString.startsWith("lat")) // lat or latlon { latString = testString.substring(testString.indexOf("=") + 1).trim(); latFlag = true; if (latString.indexOf(" ") > 0) // is latlon, not just lat { StringTokenizer tok = new StringTokenizer(latString); if (tok.countTokens() < 2) break; for (int i = 0; i < 2; i++) { tempString = tok.nextToken(); if (i == 0) latString = tempString; else { lonString = negateLongitude(tempString); lonFlag = true; } } } } else if (lctestString.startsWith("lon")) { lonFlag = true; lonString = testString.substring(testString.indexOf("=") + 1); } else if (lctestString.startsWith("lin")) // line keyword or linele { tempString = testString.substring(testString.indexOf("=") + 1); if (tempString.indexOf(" ") > 0) // is linele, not just lin { StringTokenizer tok = new StringTokenizer(tempString); if (tok.countTokens() < 2) break; for (int i = 0; i < 2; i++) { tempString = tok.nextToken(); if (i == 0) { linString = tempString; linFlag = true; } else { eleString = tempString; eleFlag = true; } } if (tok.hasMoreTokens()) // specified file or image coords { tempString = tok.nextToken().toLowerCase(); if (tempString.startsWith("i")) lineleType = "i"; } } else // is just lines string { numlinString = tempString; } } else if (lctestString.startsWith("ele")) // elements keyword { numeleString = testString.substring(testString.indexOf("=") + 1); } else if (lctestString.startsWith("pla")) // placement keyword { if (testString.substring( testString.indexOf("=") + 1).toLowerCase().startsWith("u")) placement = "u"; } else if (lctestString.startsWith("mag")) { tempString = testString.substring(testString.indexOf("=") + 1); if (tempString.indexOf(" ") > 0) // is more than one mag { StringTokenizer tok = new StringTokenizer(tempString); if (tok.countTokens() < 2) break; for (int i = 0; i < 2; i++) { buf.append(" "); tempString = tok.nextToken(); if (i == 0) buf.append("LMAG=" + tempString); else buf.append("EMAG=" + tempString); } } else magString = tempString; } // now get the rest of the keywords (but filter out non-needed) else if (lctestString.startsWith("size")) // size keyword { tempString = testString.substring(testString.indexOf("=") + 1); if (tempString.trim().equalsIgnoreCase("all")) { numlinString = "99999"; numeleString = "99999"; } else if (tempString.indexOf(" ") > 0) //is linele, not just lin { StringTokenizer tok = new StringTokenizer(tempString); if (tok.countTokens() < 2) break; for (int i = 0; i < 2; i++) { tempString = tok.nextToken(); if (i == 0) numlinString = tempString; else numeleString = tempString; } } } else if (lctestString.startsWith("trace")) // trace keyword { traceString = testString; } else if (lctestString.startsWith("spa")) // spa keyword { spaceString = testString; } else if (lctestString.startsWith("nav")) // nav keyword { navString = testString; } else if (lctestString.startsWith("aux")) // aux keyword { auxString = testString; } else if (lctestString.startsWith("track")) // track keyword { trackString = testString; } else if (lctestString.startsWith("uni")) // unit keyword { unitString = testString; } else if (lctestString.startsWith("cal")) // cal keyword { calString = testString; } else if (lctestString.startsWith("doc")) // doc keyword { docString = testString; } else if (lctestString.startsWith("tim")) // time keyword { timeString = testString; } else if (lctestString.startsWith("ban")) // band keyword { buf.append(" "); buf.append(testString); } else if (lctestString.startsWith("day")) // day keyword { buf.append(" "); buf.append(testString); } else if (lctestString.startsWith("id")) // id keyword { buf.append(" "); buf.append(testString); } else if (lctestString.startsWith("lmag")) // lmag keyword { buf.append(" "); buf.append(testString); } else if (lctestString.startsWith("emag")) // emag keyword { buf.append(" "); buf.append(testString); } } buf.append(" "); buf.append(traceString); buf.append(" "); buf.append(spaceString); buf.append(" "); buf.append(unitString); buf.append(" "); buf.append(navString); buf.append(" "); buf.append(auxString); buf.append(" "); buf.append(trackString); buf.append(" "); buf.append(docString); buf.append(" "); buf.append(timeString); buf.append(" "); buf.append(calString); // now create command string StringBuffer posParams = new StringBuffer( groupString + " " + descrString + " " + posString.toUpperCase() + " "); // Set up location information String locString = "X X X "; if (latFlag && lonFlag) { locString = "e" + placement + " " + latString + " " + lonString + " "; } else if (linFlag && eleFlag) { locString = lineleType + placement +" " + linString + " " + eleString + " "; } posParams.append(locString.toUpperCase()); // add on the mag, lin and ele pos params posParams.append(magString + " " + numlinString + " " + numeleString + " "); return new StringBuffer(posParams + buf.toString().toUpperCase()); } /** * Decode the ADDE request for grid directory information. * * * there can be any valid combination of the following supported keywords: * *
     *   group=<groupname>       ADDE group name
     *   descr=<descriptor>      ADDE descriptor name
     *   param=<param list>      parameter code list
     *   time=<model run time>   time in hhmmss format
     *   day=<model run day>     day in ccyyddd format
     *   lev=<level list>        list of requested levels (value or SFC, MSL 
     *                             or TRO)
     *   ftime=<forecast time>   valid time (hhmmss format) (use with fday)
     *   fday=<forecast day>     forecast day (ccyyddd)
     *   fhour=<forecast hours>  forecast hours (offset from model run time)
     *                                (hhmmss format)
     *   lat=<min lat> <max lat> latitude bounding box (needs lon specified)
     *   lon=<min lon> <max lon> longitude bounding box (needs lat specified)
     *   row=<min row> <max row> row bounding box (needs col specified)
     *   col=<min col> <max col> column bounding box (needs row specified)
     *   skip=<row> <col>        skip factors for rows and columns (def = 1 1)
     *   num=<max>               maximum number of grids (nn) to return (def=1)
     *   trace=<0/1>             setting to 1 tells server to write debug 
     *                             trace file (imagedata, imagedirectory)
     *
     * the following keywords are required:
     *
     *   group
     *
     * an example URL might look like:
     *   adde://noaaport/griddirectory?group=ngm&num=10
     *   
     * 
*/ private StringBuffer decodeGDIRString(String uCmd) { StringBuffer buf = new StringBuffer(); String testString, tempString, lctestString; String groupString = null; String descrString = "ALL"; String sizeString = " 999999 "; String traceString = "TRACE=0"; String numString = "NUM=1"; String subsetString = null; String latString = null; String lonString = null; String rowString = null; String colString = null; String srcString = null; String skip = null; StringTokenizer cmdTokens = new StringTokenizer(uCmd, "&"); while (cmdTokens.hasMoreTokens()) { testString = cmdTokens.nextToken(); lctestString = testString.toLowerCase(); // group, descr and pos are mandatory if (lctestString.startsWith("grou")) { groupString = testString.substring(testString.indexOf("=") + 1); } else if (lctestString.startsWith("des")) { descrString = testString.substring(testString.indexOf("=") + 1); // now get the rest of the keywords (but filter out non-needed) } else if (lctestString.startsWith("num")) { numString = testString; } else if (lctestString.startsWith("tra")) { // trace keyword traceString = testString; } else if (lctestString.startsWith("pos")) { buf.append(" "); buf.append(testString); } else if (lctestString.startsWith("par")) { buf.append(" "); buf.append("parm="); buf.append(testString.substring(testString.indexOf("=") + 1)); } else if (lctestString.startsWith("fho")) { buf.append(" "); buf.append("vt="); String iHMS = testString.substring(testString.indexOf("=") + 1).trim(); buf.append(iHMS); if (iHMS.length() < 5) buf.append("0000"); } else if (lctestString.startsWith("day")) { buf.append(" "); buf.append(testString); } else if (lctestString.startsWith("time")) { buf.append(" "); buf.append(testString); } else if (lctestString.startsWith("lev")) { buf.append(" "); buf.append(testString); } else if (lctestString.startsWith("fday")) { buf.append(" "); buf.append(testString); } else if (lctestString.startsWith("ftime")) { buf.append(" "); buf.append(testString); } else if (lctestString.startsWith("vt")) { // deprecated buf.append(" "); buf.append(testString); } else if (lctestString.startsWith("lat")) { latString = ensureTwoValues( testString.substring(testString.indexOf("=") + 1)); } else if (lctestString.startsWith("lon")) { lonString = adjustLongitudes( ensureTwoValues( testString.substring(testString.indexOf("=") + 1))); } else if (lctestString.startsWith("row")) { rowString = ensureTwoValues( testString.substring(testString.indexOf("=") + 1)); } else if (lctestString.startsWith("col")) { colString = ensureTwoValues( testString.substring(testString.indexOf("=") + 1)); } else if (lctestString.startsWith("skip")) { skip = ensureTwoValues( testString.substring(testString.indexOf("=") + 1)); // added with great pains for James DRM 2001-07-05 ;-) } else if (lctestString.startsWith("src")) { buf.append(" "); buf.append(testString); } else if (lctestString.startsWith("gpro")) { buf.append(" "); buf.append(testString); } else if (lctestString.startsWith("trang")) { buf.append(" "); buf.append(testString); } else if (lctestString.startsWith("frang")) { buf.append(" "); buf.append(testString); } else if (lctestString.startsWith("drang")) { buf.append(" "); buf.append(testString); /* } else { System.out.println("Unknown token = "+testString); */ } } buf.append(" "); buf.append(numString); buf.append(" "); buf.append(traceString); buf.append(" "); //buf.append(" version=A "); // Create a subset string if (latString != null && lonString != null) { StringBuffer subBuf = new StringBuffer(); subBuf.append("subset="); subBuf.append(latString); subBuf.append(" "); subBuf.append(lonString); subBuf.append(" "); subBuf.append((skip == null) ? "1 1" : skip); subBuf.append(" LATLON"); subsetString = subBuf.toString(); if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.finest(subsetString); } } else if (rowString != null && colString != null) { StringBuffer subBuf = new StringBuffer(); subBuf.append("subset="); subBuf.append(rowString); subBuf.append(" "); subBuf.append(colString); subBuf.append(" "); subBuf.append((skip == null) ? "1 1" : skip); subBuf.append(" ROWCOL"); subsetString = subBuf.toString(); if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.finest(subsetString); } } else if (skip != null) { // only skip specified StringBuffer subBuf = new StringBuffer(); subBuf.append("subset=1 99999 1 99999"); subBuf.append(" "); subBuf.append(skip); subBuf.append(" ROWCOL"); subsetString = subBuf.toString(); if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.finest(subsetString); } } if (subsetString != null) buf.append(subsetString); // create command string String posParams = groupString + " " + descrString + " " + sizeString + " "; return new StringBuffer(posParams + buf.toString().toUpperCase()); } /** * Decode the ADDE request for image directory information. * *
     * there can be any valid combination of the following supported keywords:
     *
     *   group=<groupname>         ADDE group name
     *   descr=<descriptor>        ADDE descriptor name
     *   band=<band>               spectral band or channel number 
     *   pos=<position>            request an absolute or relative ADDE position
     *                               number
     *   doc=<yes/no>              specify yes to include line documentation 
     *                               with image (def=no) 
     *   nav=<lalo>                include the lat-lon navigation info and not the O&A.
     *   aux=<yes/no>              specify yes to include auxilliary information
     *                               with image 
     *   time=<time1> <time2>      specify the time range of images to select
     *                               (def=latest image if pos not specified)
     *   day=<day>                 specify the day of the images to select
     *                               (def=latest image if pos not specified)
     *   cal=<cal type>            request a specific calibration on the image 
     *   id=<stn id>               radar station id
     *   trace=<0/1>               setting to 1 tells server to write debug 
     *                               trace file (imagedata, imagedirectory)
     *
     * the following keywords are required:
     *
     *   group
     *
     * an example URL might look like:
     *   adde://viper/imagedirectory?group=gvar&descr=east1km&band=1
     *   
     * 
*/ private StringBuffer decodeADIRString(String uCmd) { StringBuffer buf = new StringBuffer(); String testString; String lctestString; String tempString; // Mandatory strings String groupString = null; String descrString = "ALL"; String posString = "0 0"; String traceString = "TRACE=0"; String bandString = "BAND=ALL X"; String auxString = "AUX=YES"; String trackString = "TRACKING=0"; StringTokenizer cmdTokens = new StringTokenizer(uCmd, "&"); while (cmdTokens.hasMoreTokens()) { testString = cmdTokens.nextToken(); lctestString = testString.toLowerCase(); // group, descr and pos are mandatory if (lctestString.startsWith("grou")) { groupString = testString.substring(testString.indexOf("=") + 1); } else if (lctestString.startsWith("des")) { descrString = testString.substring(testString.indexOf("=") + 1); } else if (lctestString.startsWith("pos")) { tempString = testString.substring(testString.indexOf("=") + 1).toLowerCase(); if (tempString.equals("")) { // null string posString = "0 0"; } else { // see if a single argument StringTokenizer stp = new StringTokenizer(tempString," "); if (stp.countTokens() == 1) { if (tempString.equals("all")) { // put in numeric posString = "1095519264"; } else if (tempString.equals("x")) { posString = "X X"; } else { int posval = Integer.parseInt(stp.nextToken().trim()); if (posval <= 0) { // if value < 0 insert 0 as ending posString = tempString + " 0"; } else { posString = tempString + " " + tempString; // else default } } } else { // more than one value...just copy it posString = tempString; } } } // now get the rest of the keywords (but filter out non-needed) else if (lctestString.startsWith("trace")) // trace keyword { traceString = testString; } else if (lctestString.startsWith("aux")) // aux keyword { auxString = testString; } else if (lctestString.startsWith("track")) // track keyword { trackString = testString; } else if (lctestString.startsWith("ban")) // band keyword { bandString = testString; } else if (lctestString.startsWith("tim")) // time keyword { buf.append(" "); buf.append(testString); } else if (lctestString.startsWith("day")) // time keyword { buf.append(" "); buf.append(testString); } else if (lctestString.startsWith("id")) // id keyword { buf.append(" "); buf.append(testString); } } buf.append(" "); buf.append(traceString); buf.append(" "); buf.append(bandString); buf.append(" "); buf.append(auxString); buf.append(" "); buf.append(trackString); // now create case sensitive command string String posParams = groupString + " " + descrString + " " + posString + " "; return new StringBuffer(posParams + buf.toString().toUpperCase()); } /** * Decode the ADDE request for a text file. * *
     * there can be any valid combination of the following supported keywords:
     *
     *   file=<filename>    the text file name on the server
     *   descr=<dataset>    the dataset name on the server
     *   group=<group>      the ADDE group name for this TEXT
     *
     * the following keywords are required:
     *
     *   file or descr
     *
     * an example URL might look like:
     *   adde://viper/text?group=textdata&file=myfile.txt
     *   
     * 
*/ public StringBuffer decodeTXTGString(String uCmd) { StringBuffer buf = new StringBuffer(); String testString; String lctestString; String groupString = null; String filenameString = null; String descrString = null; String traceString = "TRACE=0"; StringTokenizer cmdTokens = new StringTokenizer(uCmd, "&"); while (cmdTokens.hasMoreTokens()) { testString = cmdTokens.nextToken(); lctestString = testString.toLowerCase(); if (lctestString.startsWith("desc")) { descrString = testString.substring(testString.indexOf("=") + 1); } else if (lctestString.startsWith("file")) { filenameString = "FILE="+ testString.substring(testString.indexOf("=") + 1); } else if (lctestString.startsWith("grou")) { groupString = testString.substring(testString.indexOf("=") + 1); } else if (lctestString.startsWith("tra")) // trace keyword { traceString = testString; } } buf.append(groupString); buf.append(" "); buf.append(descrString); buf.append(" "); buf.append(filenameString); buf.append(" "); buf.append(traceString.toUpperCase()); return buf; } /** * Decode the ADDE request for a weather text. * *
     * there can be any valid combination of the following supported keywords:
     *
     *   group=<group>         weather text group (default= RTWXTEXT)
     *   prod=<product>        predefind product name
     *   apro=<val1 .. valn>   AFOS/AWIPS product headers to match (don't 
     *                         use with wmo keyword
     *   astn=<val1 .. valn>   AFOS/AWIPS stations to match
     *   wmo= <val1 .. valn>   WMO product headers to match (don't 
     *                         use with apro keyword
     *   wstn=<val1 .. valn>   WMO stations to match
     *   day=<start end>       range of days to search (def = current)
     *   dtime=<numhours>      maximum number of hours to search back (def=96)
     *   match=<match strings> list of character match strings to find from text
     *   num=<num>             number of matches to find (def=1)
     *
     * the following keywords are required:
     *
     *   day  (should default to current, but there's a bug)
     *   apro, astn or wstn
     *
     * an example URL might look like:
     *   adde://viper/text?group=textdata&file=myfile.txt
     *   
     * 
*/ public StringBuffer decodeWTXGString(String uCmd) { StringBuffer buf = new StringBuffer(); String testString; String lctestString; String tempString; String numString = "NUM=1"; String dTimeString = "DTIME=96.0000"; String traceString = "TRACE=0"; String dayString = "DAY="+McIDASUtil.mcSecsToDayTime(System.currentTimeMillis()/1000l)[0]; // Mandatory strings String groupString = "RTWXTEXT"; StringTokenizer cmdTokens = new StringTokenizer(uCmd, "&"); while (cmdTokens.hasMoreTokens()) { testString = cmdTokens.nextToken(); lctestString = testString.toLowerCase(); // group, descr and pos are mandatory if (lctestString.startsWith("grou")) { groupString = testString.substring(testString.indexOf("=") + 1); } else if (lctestString.startsWith("apro")) // apro keyword { buf.append(" "); buf.append(testString); } else if (lctestString.startsWith("astn")) // astn keyword { buf.append(" "); buf.append(testString); } else if (lctestString.startsWith("day")) // day keyword { dayString = testString; } else if (lctestString.startsWith("mat")) // match keyword { buf.append(" "); buf.append(testString); } else if (lctestString.startsWith("prod")) // prod keyword { buf.append(" "); buf.append(testString); } else if (lctestString.startsWith("sour")) // source keyword { buf.append(" "); buf.append(testString); } else if (lctestString.startsWith("wmo")) // wmo keyword { buf.append(" "); buf.append(testString); } else if (lctestString.startsWith("wstn")) // wstn keyword { buf.append(" "); buf.append(testString); } else if (lctestString.startsWith("tra")) // trace keyword { traceString = testString; } else if (lctestString.startsWith("num")) // num keyword { numString = testString; } else if (lctestString.startsWith("dtim")) // dtime keyword { dTimeString = testString; } } buf.append(" "); buf.append(dayString); buf.append(" "); buf.append(dTimeString); buf.append(" "); buf.append(numString); buf.append(" "); buf.append(traceString); // now create case sensitive command string String posParams = groupString + " "; return new StringBuffer(posParams + buf.toString().toUpperCase()); } /** * Decode the ADDE request for a weather observation text. * *
     * there can be any valid combination of the following supported keywords:
     *
     *
     *   group=<group>         weather text group (default= RTWXTEXT)
     *   descr=<descriptor>    weather text subgroup (default=SFCHOURLY)
     *   id=<id1 id2 ... idn>  list of station ids
     *   co=<co1 co2 ... con>  list of countries
     *   reg=<reg1 reg2..regn> list of regions
     *   newest=<day hour>     most recent time to allow in request 
     *                         (def=current time)
     *   oldest=<day hour>     oldest observation time to allow in request
     *   type=<type>           numeric value for the type of ob
     *   nhours=<numhours>     maximum number of hours to search
     *   num=<num>             number of matches to find (def=1)
     *
     * the following keywords are required:
     *
     *   group
     *   descr
     *   id, co, or reg
     *
     * an example URL might look like:
     *  adde://adde.ucar.edu/obtext?group=rtwxtext&descr=sfchourly&id=kden&num=2
     *   
     * 
*/ public StringBuffer decodeOBTGString(String uCmd) { StringBuffer buf = new StringBuffer(); String testString; String lctestString; String tempString; String numString = "NUM=1"; String traceString = "TRACE=0"; // Mandatory strings String groupString = "RTWXTEXT"; String descrString = "SFCHOURLY"; String idreqString = "IDREQ=LIST"; StringTokenizer cmdTokens = new StringTokenizer(uCmd, "&"); while (cmdTokens.hasMoreTokens()) { testString = cmdTokens.nextToken(); lctestString = testString.toLowerCase(); // group, descr and pos are mandatory if (lctestString.startsWith("grou")) { groupString = testString.substring(testString.indexOf("=") + 1); } else if (lctestString.startsWith("desc")) { descrString = testString.substring(testString.indexOf("=") + 1); } else if (lctestString.startsWith("id")) // id keyword { buf.append(" "); buf.append(testString); } else if (lctestString.startsWith("co")) // co keyword { buf.append(" "); buf.append(testString); } else if (lctestString.startsWith("reg")) // reg keyword { buf.append(" "); buf.append(testString); } else if (lctestString.startsWith("nhou")) // nhour keyword { buf.append(" "); buf.append(testString); } else if (lctestString.startsWith("new")) // newest keyword { buf.append(" "); buf.append(testString); } else if (lctestString.startsWith("old")) // oldest keyword { buf.append(" "); buf.append(testString); } else if (lctestString.startsWith("type")) // type keyword { buf.append(" "); buf.append(testString); } else if (lctestString.startsWith("tra")) // trace keyword { traceString = testString; } else if (lctestString.startsWith("num")) // num keyword { numString = testString; } } buf.append(" "); buf.append(numString); buf.append(" "); buf.append(traceString); // now create case sensitive command string String posParams = groupString + " " + descrString + " " + idreqString; return new StringBuffer(posParams + buf.toString().toUpperCase()); } /** * Decode the ADDE request for data set information. * *
     * there can be any valid combination of the following supported keywords:
     *
     *   group=<groupname>    ADDE group name
     *   type=<datatype>      ADDE data type.  Must be one of the following:
     *                             IMAGE, POINT, GRID, TEXT, NAV
     *                        the default is the IMAGE type.
     *
     * the following keywords are required:
     *
     *   group
     *
     * an example URL might look like:
     *   adde://viper/datasetinfo?group=gvar&type=image
     *   
     * 
*/ public StringBuffer decodeLWPRString(String uCmd) { StringBuffer buf = new StringBuffer(); String testString; String lctestString; String tempString; String groupString = null; String typeString = "ALA."; StringTokenizer cmdTokens = new StringTokenizer(uCmd, "&"); while (cmdTokens.hasMoreTokens()) { testString = cmdTokens.nextToken(); lctestString = testString.toLowerCase(); // group, descr and pos are mandatory if (lctestString.startsWith("grou")) { groupString = testString.substring(testString.indexOf("=") + 1); } if (lctestString.startsWith("type")) { tempString = testString.substring(testString.indexOf("=") + 1).toLowerCase(); if (tempString.startsWith("i")) typeString = "ALA."; if (tempString.startsWith("g")) typeString = "ALG."; else if (tempString.startsWith("p")) typeString = "ALM."; else if (tempString.startsWith("t")) typeString = "ALT."; else if (tempString.startsWith("n")) typeString = "ALN."; else if (tempString.startsWith("s")) typeString = "ALN."; } } buf.append(typeString); buf.append(groupString); return buf; } /** * Decode the ADDE request for point data. * * If the request contains specific parameters (eg param=t), * then the class variable binaryData is set to this param string * *
     *   group=<groupname>         ADDE group name
     *   descr=<descriptor>        ADDE descriptor name
     *   pos=<position>            request an absolute or relative ADDE 
     *                               position number
     *   select=<select clause>    to specify which data is required
     *   param=<param list>        what parameters to return
     *                             eg param=t[c]
     *                             note that the units [c] are ignored by server
     *                             it is the clients task to convert units
     *                             Note that if "param=" is used, 
     *                             binaryData is set to the
     *                             (processed) parameter list
     *   max=<max>                 maximum number of obs to return
     *   trace=<0/1>               setting to 1 tells server to write debug 
     *                               trace file (imagedata, imagedirectory)
     *   binaryData=<param list>   because an unlimited number of parameters may
     *                             be requested, these must be packaged up at the end
     *                             of the adde request, and this is known as the
     *                             "binary data" part of the request
     *
     * the following keywords are required:
     *
     *   group
     *
     * an example URL might look like:
     *   adde://rtds/point?group=neons&descr=metar
     *   
     * 
*/ private StringBuffer decodeMDKSString(String uCmd) { String testString = null; String lctestString = null; // Mandatory strings String groupString = null; String descrString = null; String maxString = "MAX=1"; String numString = ""; // Options strings String posString = "POS=0"; String traceString = "TRACE=0"; String selectString = ""; String parmString = ""; String justTheParametersString = ""; String justTheSelectString = ""; String sBinaryData = ""; boolean posInDescriptor = false; // in hard coded notation, the binaryData for "param=t" would look like: // binaryData = new byte[4]; // binaryData[0] = (byte) 'T'; // binaryData[1] = (byte) ' '; // binaryData[2] = (byte) ' '; // binaryData[3] = (byte) ' '; StringTokenizer cmdTokens = new StringTokenizer(uCmd, "&"); while (cmdTokens.hasMoreTokens()) { testString = cmdTokens.nextToken(); lctestString = testString.toLowerCase(); // group and descr if (lctestString.startsWith("grou")) { groupString = testString.substring(testString.indexOf("=") + 1); } else if (lctestString.startsWith("des")) { descrString = testString.substring(testString.indexOf("=") + 1); int pos = descrString.indexOf("."); if (pos >=0) { posString = "POS=" + descrString.substring(pos+1); descrString = descrString.substring(0,pos); posInDescriptor = true; } } else // in McIDAS Clients the parameter request string contains param= // but the adde server looks for parm= // this bit of code forces this change so that Java Clients behave // the same as McIDAS Clients if (lctestString.startsWith("par")) { justTheParametersString = testString.substring(testString.indexOf("=") + 1) ; parmString = "PARM=" + justTheParametersString; if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.finest("paramString = " + parmString); } sBinaryData = new String(decodePARAMString(justTheParametersString)); sBinaryData = sBinaryData.toUpperCase(); binaryData = sBinaryData.getBytes(); } else if (lctestString.startsWith("select")) { justTheSelectString = testString.substring(testString.indexOf("=") + 1) ; selectString = "SELECT=" + new String( decodeSELECTString(justTheSelectString)); if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.finest("Server selectString = " + selectString); } } else // similarly, McIDAS Clients use num= but the server wants max= if (lctestString.startsWith("num")) { maxString = "MAX=" + testString.substring(testString.indexOf("=") + 1) ; } else // allow for clever people who really know that the server uses // max = :-) if (lctestString.startsWith("max")) { maxString = testString; } // now get the rest of the keywords (but filter out non-needed) else if (lctestString.startsWith("tra")) // trace keyword { traceString = testString; } else if (lctestString.startsWith("pos") && !posInDescriptor) { posString = testString; } } // fudge the max string in case ALL is specified. Some servers // don't handle all if (maxString.trim().equalsIgnoreCase("max=all")) { maxString="MAX=99999"; } // now create case sensitive command string StringBuffer posParams = new StringBuffer(); posParams.append(groupString); posParams.append(" "); posParams.append(descrString); posParams.append(" "); posParams.append(parmString.toUpperCase()); posParams.append(" "); posParams.append(selectString.toUpperCase()); posParams.append(" "); posParams.append(posString.toUpperCase()); posParams.append(" "); posParams.append(traceString.toUpperCase()); posParams.append(" "); posParams.append(maxString.toUpperCase()); if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.finest("String passed to server = " + posParams); } return posParams; } /** * Helper function for decodeMDKSString to decode * the "param=" part of a point data request. * * @param justTheParametersString The parameter list which follows * "param=" eg: "id dir spd t[c] td[c]" * @return parameter list (padded to length 4 for server) * without any units (units are ignored by server) * eg: "id dir spd t td " */ private String decodePARAMString(String justTheParametersString) { String testString = null; String thisParam = null; String thisUnit = null; StringBuffer buf = new StringBuffer(); StringTokenizer paramTokens = new StringTokenizer(justTheParametersString, " "); while (paramTokens.hasMoreTokens()) { testString = (paramTokens.nextToken()).trim(); StringTokenizer thisParamToken = new StringTokenizer(testString, "[]"); thisParam = new String((thisParamToken.nextToken()).trim()); buf.append(thisParam); for (int i=thisParam.length(); i < 4; i++) { buf.append(" "); } if (thisParamToken.hasMoreTokens()) { // note that the units are ignored by the server // it is the client's responsibility to do unit conversion thisUnit = (thisParamToken.nextToken()).trim(); if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.finest("This Unit = " + thisUnit); } } } return (buf.toString()); } /** * Helper function for decodeMDKSString to decode * the "select=" part of a point data request. * * @param justTheSelectString The select list which follows "select=" eg: * 'id ymml; time 12 18; day 1999316; t[c] 20 30; td 270 276' * * @return The select list formatted for the server eg: * 'id ymml' 'time 12 to 18' 'day 1999316' 't 20 to 30 c' 'td 270 to 276' * * Reference * McIDAS 7.6 source code: m0psort.for */ private String decodeSELECTString(String justTheSelectString) { String testString = null; String entireSelectString = null; // String trimmedSelectString = null; String thisSelect = null; String thisUnit = null; StringBuffer buf = new StringBuffer(); StringTokenizer entireSelectToken = new StringTokenizer(justTheSelectString, "'"); entireSelectString = (entireSelectToken.nextToken()).trim(); // // Break SELECT string up into parts // StringTokenizer selectTokens = new StringTokenizer(entireSelectString, ";"); while (selectTokens.hasMoreTokens()) { thisSelect = (selectTokens.nextToken()).trim(); if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.finest(" this Select = " + thisSelect); } // // Break into individual clauses eg: // t[c] 20 30 // StringTokenizer thisSelectToken = new StringTokenizer(thisSelect, " "); int tokenCount = thisSelectToken.countTokens(); thisSelect = new String(thisSelectToken.nextToken()); if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.finest("this Select = " + thisSelect); } // // Check to see if any units are involved eg: // t[c] if (thisSelect.indexOf("[") > 0) { StringTokenizer thisUnitToken = new StringTokenizer(thisSelect, "[]"); if (thisUnitToken.hasMoreTokens()) { thisSelect = new String((thisUnitToken.nextToken()).trim()); buf.append("'" + thisSelect); if (thisUnitToken.hasMoreTokens()) { thisUnit = new String((thisUnitToken.nextToken()).trim()); } } } else { // no units involved eg: // t buf.append("'" + thisSelect); } // // Check for first numeric value eg if select='t[c] 20 30': // 20 // if (thisSelectToken.hasMoreTokens()) { thisSelect = thisSelectToken.nextToken(); if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.finest("this Select = " + thisSelect); } buf.append(" " + thisSelect); } // // Check for second numeric value eg if select='t[c] 20 30': // 30 // if (thisSelectToken.hasMoreTokens()) { thisSelect = thisSelectToken.nextToken(); // server requires TO for a range of values eg: // 20 to 30 buf.append(" TO " + thisSelect); if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.finest("this Select = " + thisSelect); } } // // add unit if specified // if (thisUnit != null) { buf.append(" " + thisUnit); thisUnit = null; } buf.append("' "); } return (buf.toString()); } /** * Ensures that a string is two values. If only one, then it * is returned as s + " " + s * @param s String to check */ private String ensureTwoValues(String s) { String retVal = null; if (s.trim().indexOf(" ") > 0) // has multiple values { StringTokenizer tok = new StringTokenizer(s); // return null if more than 2 if (tok.countTokens() == 2) retVal = s; } else { //retVal = new String(s + " " + s); retVal = s + " " + s; } return retVal; } /** * Adjust the longitude from East Postitive to west positive * @param input String to check */ private String adjustLongitudes(String input) { input = input.trim(); String lon1 = negateLongitude(input.substring(0, input.trim().indexOf(" ")).trim()); String lon2 = negateLongitude(input.substring(input.trim().indexOf(" ")).trim()); return (lon2 + " " + lon1); } /** * Negate a longitude. McIDAS convention is positive west. * @param eastLong eastLongitude to negate */ private String negateLongitude(String eastLong) { if (eastLong.indexOf("-") >= 0) // (comes in as -) return eastLong.substring(eastLong.indexOf("-") + 1); else return "-" + eastLong; } private static String[] replaceWith = {"&", "<", ">", "\'", "\"", "\r", "\n", " "}; private static String[] replaceString = {"&", "<", ">", "'", """, " ", " ", "%20" }; private URL normalizeURL(URL url) { String x; try { x = URLDecoder.decode(url.toString(), "UTF-8"); } catch (java.lang.Exception e) { throw new RuntimeException("URL decoding failed", e); } // common case no replacement boolean ok = true; for (int i=0; i=0) { if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.finest("found " + replaceString[i] + " at " + pos); } StringBuffer buf = new StringBuffer(x); buf.replace(pos, pos+(replaceString[i].length()), replaceWith[i]); x = buf.toString(); } } } if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.finest("normalized url = " + x); } try { return new URL(x); } catch (Exception e) {} return url; } }

Ads help maintain this website.