/*
* Copyright 1998-2009 University Corporation for Atmospheric Research/Unidata
*
* Portions of this software were developed by the Unidata Program at the
* University Corporation for Atmospheric Research.
*
* Access and use of this software shall impose the following obligations
* and understandings on the user. The user is granted the right, without
* any fee or cost, to use, copy, modify, alter, enhance and distribute
* this software, and any derivative works thereof, and its supporting
* documentation for any purpose whatsoever, provided that this entire
* notice appears in all copies of the software, derivative works and
* supporting documentation. Further, UCAR requests that the user credit
* UCAR/Unidata in any publications that result from the use of this
* software or in any product that includes this software. The names UCAR
* and/or Unidata, however, may not be used in any advertising or publicity
* to endorse or promote any products or commercial entity unless specific
* written permission is obtained from UCAR/Unidata. The user also
* understands that UCAR/Unidata is not obligated to provide the user with
* any support, consulting, training or assistance of any kind with regard
* to the use, operation and performance of this software nor to provide
* the user with any updates, revisions, new versions or "bug fixes."
*
* THIS SOFTWARE IS PROVIDED BY UCAR/UNIDATA "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL UCAR/UNIDATA BE LIABLE FOR ANY SPECIAL,
* INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
* FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
* NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
* WITH THE ACCESS, USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package thredds.server.radarServer;
import ucar.nc2.time.CalendarDateRange;
import ucar.unidata.geoloc.LatLonPointImpl;
import ucar.unidata.geoloc.LatLonRect;
import ucar.unidata.geoloc.LatLonPoint;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.net.URLDecoder;
import java.util.List;
import java.util.ArrayList;
import java.util.StringTokenizer;
import java.io.IOException;
import java.io.PrintWriter;
import thredds.servlet.ServletUtil;
import ucar.nc2.units.DateRange;
import ucar.nc2.units.TimeDuration;
import ucar.nc2.units.DateType;
/**
* Query parameter parsing for Netcdf Subset Service
*
* @author caron
* @deprecated use thredds.server.ncSubset.view.*
*/
public class QueryParams {
static private org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger( QueryParams.class );
static public final String RAW = "text/plain";
static public final String XML = "application/xml";
static public final String HTML = "text/html";
static public final String CSV = "text/csv";
static public final String NETCDF = "application/x-netcdf";
static public final String NETCDFS = "application/x-netcdfs";
static public final String CdmRemote = "application/x-cdmremote";
// the first in the list is the canonical name, the others are aliases
static String[][] validAccept = new String[][]{
{XML, "text/xml", "xml"},
{RAW, "raw", "ascii"},
{CSV, "csv"},
{HTML, "html"},
{NETCDF, "netcdf"},
{NETCDFS, "netcdfStream"},
{CdmRemote, "cdmRemote"},
};
public String queryString;
public List<String> accept;
public String acceptType; // choose one of the accept
public boolean wantAllVariables;
public List<String> vars;
// spatial subsetting
public boolean hasBB = false, hasStns = false, hasLatlonPoint = false; // only one is true
public double north, south, east, west;
public double lat, lon;
public List<String> stns; // for stationObs, empty list means all
public int horizStride, vertStride, timeStride; // 0 = none
public boolean hasVerticalCoord = false;
public double vertCoord;
// temporal subsetting
public boolean hasDateRange = false, hasTimePoint = false; // only one is true
public DateType time_start, time_end, time;
public TimeDuration time_duration;
public int time_latest;
// track errors
public StringBuilder errs = new StringBuilder();
public boolean fatal;
public String toString() {
StringBuilder sbuff = new StringBuilder();
sbuff.append("queryString= " + queryString + "\n\n");
sbuff.append("parsed=\n ");
if (hasBB)
sbuff.append("bb=" + getBB().toString2() + ";");
else if (hasLatlonPoint)
sbuff.append("lat/lon=" + getPoint() + ";");
else if (hasStns) {
boolean first = true;
sbuff.append("stns=");
for (String stnName : stns) {
if (!first) sbuff.append(",");
sbuff.append(stnName);
first = false;
}
sbuff.append(";");
} else {
sbuff.append("spatial=all;");
}
sbuff.append("\n ");
if (hasTimePoint)
sbuff.append("time=" + time + ";");
else if (hasDateRange) {
sbuff.append("timeRange=" + getCalendarDateRange() + ";");
} else {
sbuff.append("temporal=all;");
}
sbuff.append("\n ");
if (wantAllVariables)
sbuff.append("vars=all;");
else {
boolean first = true;
sbuff.append("vars=");
for (String varName : vars) {
if (!first) sbuff.append(",");
sbuff.append(varName);
first = false;
}
sbuff.append(";");
}
sbuff.append("\n ");
return sbuff.toString();
}
/**
* Parse request
*
* @param req HTTP request
* @param res HTTP response
* @param acceptOK array of acceptable accept types, in order. First one is default
* @return true if params are ok
* @throws java.io.IOException if I/O error
*/
public boolean parseQuery(HttpServletRequest req, HttpServletResponse res, String[] acceptOK) throws IOException {
queryString = req.getQueryString();
if (queryString == null) {
writeErr(req, res, "Must have a quey string", HttpServletResponse.SC_BAD_REQUEST);
return false;
}
queryString = URLDecoder.decode(queryString, "UTF-8"); // unescape
accept = parseList(req, "accept", QueryParams.validAccept, acceptOK[0]);
for (String ok : acceptOK) {
if (accept.contains(ok)) {
acceptType = ok;
}
}
if (acceptType == null) {
fatal = true;
errs.append("Accept parameter not supported ="+accept);
}
// list of variable names
String variables = ServletUtil.getParameterIgnoreCase(req, "variables");
wantAllVariables = (variables != null) && (variables.equals("all"));
if (!wantAllVariables) {
vars = parseList(req, "var");
if (vars.isEmpty()) {
vars = null;
wantAllVariables = true;
}
}
// spatial subsetting
String spatial = ServletUtil.getParameterIgnoreCase(req, "spatial");
boolean spatialNotSpecified = (spatial == null);
// bounding box
if (spatialNotSpecified || spatial.equalsIgnoreCase("bb")) {
north = parseLat(req, "north");
south = parseLat(req, "south");
east = parseDouble(req, "east");
west = parseDouble(req, "west");
hasBB = hasValidBB();
}
// stations
if (!hasBB && (spatialNotSpecified || spatial.equalsIgnoreCase("stns"))) {
stns = parseList(req, "stn");
hasStns = stns.size() > 0;
}
// lat/lon point
if (!hasBB && !hasStns && (spatialNotSpecified || spatial.equalsIgnoreCase("point"))) {
lat = parseLat(req, "latitude");
lon = parseLon(req, "longitude");
hasLatlonPoint = hasValidPoint();
}
// strides
horizStride = parseInt(req, "horizStride");
vertStride = parseInt(req, "vertStride");
timeStride = parseInt(req, "timeStride");
// time range
String temporal = ServletUtil.getParameterIgnoreCase(req, "temporal");
boolean timeNotSpecified = (temporal == null);
// time range
if (timeNotSpecified || temporal.equalsIgnoreCase("range")) {
time_start = parseDate(req, "time_start");
time_end = parseDate(req, "time_end");
time_duration = parseW3CDuration(req, "time_duration");
hasDateRange = hasValidDateRange();
}
// time point
if (timeNotSpecified || temporal.equalsIgnoreCase("point")) {
time = parseDate(req, "time");
hasTimePoint = (time != null);
}
// vertical coordinate
vertCoord = parseDouble(req, "vertCoord");
hasVerticalCoord = !Double.isNaN(vertCoord);
if (fatal) {
writeErr(req, res, errs.toString(), HttpServletResponse.SC_BAD_REQUEST);
return false;
}
return true;
}
public DateType parseDate(HttpServletRequest req, String key) {
String s = ServletUtil.getParameterIgnoreCase(req, key);
if (s != null) {
try {
return new DateType(s, null, null);
} catch (java.text.ParseException e) {
errs.append("Illegal param= '" + key + "=" + s + "' must be valid ISO Date\n");
fatal = true;
}
}
return null;
}
public TimeDuration parseW3CDuration(HttpServletRequest req, String key) {
String s = ServletUtil.getParameterIgnoreCase(req, key);
if (s != null) {
try {
return new TimeDuration(s);
} catch (java.text.ParseException e) {
errs.append("Illegal param= '" + key + "=" + s + "' must be valid ISO Duration\n");
fatal = true;
}
}
return null;
}
public double parseDouble(HttpServletRequest req, String key) {
String s = ServletUtil.getParameterIgnoreCase(req, key);
if ((s != null) && (s.trim().length() > 0)) {
try {
return Double.parseDouble(s);
} catch (NumberFormatException e) {
errs.append("Illegal param= '" + key + "=" + s + "' must be valid floating point number\n");
fatal = true;
}
}
return Double.NaN;
}
public int parseInt(HttpServletRequest req, String key) {
String s = ServletUtil.getParameterIgnoreCase(req, key);
if ((s != null) && (s.trim().length() > 0)) {
try {
return Integer.parseInt(s);
} catch (NumberFormatException e) {
errs.append("Illegal param= '" + key + "=" + s + "' must be valid integer number\n");
fatal = true;
}
}
return 0;
}
public double parseLat(HttpServletRequest req, String key) {
double lat = parseDouble(req, key);
if (!Double.isNaN(lat)) {
if ((lat > 90.0) || (lat < -90.0)) {
errs.append("Illegal param= '" + key + "=" + lat + "' must be between +/- 90.0\n");
lat = Double.NaN;
fatal = true;
}
}
return lat;
}
public double parseLon(HttpServletRequest req, String key) {
double lon = parseDouble(req, key);
if (!Double.isNaN(lon)) {
lon = LatLonPointImpl.lonNormal(lon);
}
return lon;
}
/**
* parse KVP for key=value or key=value,value,...
*
* @param req HTTP request
* @param key key to look for
* @return list of values, may be empty
*/
public List<String> parseList(HttpServletRequest req, String key) {
ArrayList<String> result = new ArrayList<String>();
// may have multiple key=value
String[] vals = ServletUtil.getParameterValuesIgnoreCase(req, key);
if (vals != null) {
for (String userVal : vals) {
if (userVal.contains(",")) { // comma separated values
StringTokenizer stoke = new StringTokenizer(userVal, ",");
while (stoke.hasMoreTokens()) {
String token = stoke.nextToken();
result.add(token);
}
} else { // single value
result.add(userVal);
}
}
}
return result;
}
/**
* Used for accept
* parse KVP for key=value or key=value,value,...
*
* @param req HTTP request
* @param key key to look for
* @param valids list of valid keywords
* @param defValue default value
* @return list of values, use default if not otherwise specified
*/
public List<String> parseList(HttpServletRequest req, String key, String[][] valids, String defValue) {
ArrayList<String> result = new ArrayList<String>();
// may have multiple key=value
String[] vals = ServletUtil.getParameterValuesIgnoreCase(req, key);
if (vals != null) {
for (String userVal : vals) {
if (userVal.contains(",")) { // comma separated values
StringTokenizer stoke = new StringTokenizer(userVal, ",");
while (stoke.hasMoreTokens()) {
String token = stoke.nextToken();
if (!findValid(token, valids, result))
errs.append("Illegal param '" + key + "=" + token + "'\n");
}
} else { // single value
if (!findValid(userVal, valids, result))
errs.append("Illegal param= '" + key + "=" + userVal + "'\n");
}
}
}
if ((result.size() == 0) && (defValue != null)) {
result.add(defValue);
}
return result;
}
// look for userVal in list of valids; add to result if found
// return true if found
private boolean findValid(String userVal, String[][] valids, ArrayList<String> result) {
for (String[] list : valids) {
String canon = list[0];
for (String valid : list) {
if (userVal.equalsIgnoreCase(valid)) {
result.add(canon);
return true;
}
}
}
return false;
}
/**
* Determine if a valid lat/lon bounding box was specified
*
* @return true if there is a valid BB, false if not. If an invalid BB, set fatal=true, with error message in errs.
*/
boolean hasValidBB() {
// no bb
if (Double.isNaN(north) && Double.isNaN(south) && Double.isNaN(east) && Double.isNaN(west))
return false;
// misformed bb
if (Double.isNaN(north) || Double.isNaN(south) || Double.isNaN(east) || Double.isNaN(west)) {
errs.append("Bounding Box must have all 4 parameters: north,south,east,west\n");
fatal = true;
return false;
}
if (north < south) {
errs.append("Bounding Box must have north > south\n");
fatal = true;
return false;
}
if (east < west) {
errs.append("Bounding Box must have east > west; if crossing 180 meridion, use east boundary > 180\n");
fatal = true;
return false;
}
return true;
}
public LatLonRect getBB() {
return new LatLonRect(new LatLonPointImpl(south, west), new LatLonPointImpl(north, east));
}
LatLonPoint getPoint() {
return new LatLonPointImpl(lat, lon);
}
/**
* Determine if a valid lat/lon point was specified
*
* @return true if there is a valid point, false if not. If an invalid point, set fatal=true, with error message in errs.
*/
boolean hasValidPoint() {
// no point
if (Double.isNaN(lat) && Double.isNaN(lon))
return false;
// misformed point
if (Double.isNaN(lat) || Double.isNaN(lon)) {
errs.append("Missing lat or lon parameter\n");
fatal = true;
return false;
}
return true;
}
/**
* Determine if a valid date range was specified
*
* @return true if there is a valid date range, false if not. If an invalid date range, append error message in errs.
*/
private boolean hasValidDateRange() {
// no range
if ((null == time_start) && (null == time_end) && (null == time_duration))
return false;
if ((null != time_start) && (null != time_end))
return true;
if ((null != time_start) && (null != time_duration))
return true;
if ((null != time_end) && (null != time_duration))
return true;
// misformed range
errs.append("Must have 2 of 3 parameters: time_start, time_end, time_duration\n");
return false;
}
public CalendarDateRange getCalendarDateRange() {
return hasDateRange ? CalendarDateRange.of(new DateRange(time_start, time_end, time_duration, null)) : null;
}
public void writeErr(HttpServletRequest req, HttpServletResponse res, String s, int code) throws IOException {
log.debug( "QueryParams bad request = {}", s);
res.setStatus(code);
if (s.length() > 0) {
PrintWriter pw = res.getWriter();
pw.print(s);
pw.print("Request= "+req.getRequestURI()+"?"+req.getQueryString());
pw.close();
}
}
}