//
// 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 <A HREF="http://www.ssec.wisc.edu/mcidas/doc//prog_man.html">
* McIDAS Programmer's Manual</A>
*
* <pre>
*
* 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:
*
* <code>-Dadde.auto-upcase=true</code>
*
* 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
*
* </pre>
*
* @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.
* <p>
* An example URL might look like:
* <p>
* 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:
*
* <pre>
*
* 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
*
* </pre>
*/
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:
*
* <pre>
* 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
*
* </pre>
*/
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.
*
* <pre>
* 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
*
* </pre>
*/
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.
*
* <pre>
* 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
*
* </pre>
*/
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.
*
* <pre>
* 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
*
* </pre>
*/
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.
*
* <pre>
* 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
*
* </pre>
*/
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.
*
* <pre>
* 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
*
* </pre>
*/
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
*
* <pre>
* 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
*
* </pre>
*/
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<replaceString.length; i++) {
int pos = x.indexOf(replaceString[i]);
ok &= (pos < 0);
}
if (!ok) { // gotta do it
for (int i=0; i<replaceString.length; i++) {
int pos = -1;
while ((pos = x.indexOf(replaceString[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;
}
}