/* * 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. */ /** * User: rkambic * Date: Oct 13, 2010 * Time: 11:19:50 AM */ package thredds.server.radarServer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.AbstractController; import thredds.server.config.TdsContext; import thredds.util.TdsPathUtils; import ucar.nc2.units.DateRange; import ucar.nc2.units.DateType; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.*; /* * Processes Queries for the RadarServer Spring Framework */ @Controller public class QueryRadarServerController extends AbstractController { private org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(getClass()); @Autowired private TdsContext tdsContext; private boolean htmlView; private boolean releaseDataset = false; public boolean isHtmlView() { return htmlView; } public void setHtmlView(boolean htmlView) { this.htmlView = htmlView; } public boolean isReleaseDataset() { return releaseDataset; } public void setReleaseDataset(boolean releaseDataset) { this.releaseDataset = releaseDataset; } /** * The view to forward to in case a bad query. */ //private static final String CREATE_VIEW = "forward:badquery.htm"; /** * The model key used to retrieve the message from the model. */ private static final String MODEL_KEY = "message"; /** * The unique key for retrieving the text associated with this message. */ private static final String MSG_CODE = "message.bad.query"; /* * why calculate over and over again 1970-01-01T00:00:00 */ private static DateType epicDateType; static { try { epicDateType = new DateType(RadarServerUtil.epic, null, null); } catch (java.text.ParseException e) { } } private final SimpleDateFormat dateFormat; public QueryRadarServerController() { dateFormat = new SimpleDateFormat("yyyyMMdd", Locale.US); dateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); } protected String getControllerPath() { return "/radarServer/"; } /** * Query RadarServer controller for Spring Framework * * @param request HttpServletRequest * @param response HttpServletResponse * @return ModelAndView * @throws Exception */ //@RequestMapping(value = {"/radarServer/**/*?*"}, method = RequestMethod.GET) // @RequestMapping(value = "/radarServer/**/*", method = {RequestMethod.GET, RequestMethod.HEAD}) protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception { DatasetRepository.init(tdsContext); if (request.getQueryString() == null) { response.sendError(HttpServletResponse.SC_BAD_REQUEST, "No query string"); return null; } // Query results in model Map<String, Object> model = new HashMap<>(); radarQuery(request, response, model); if (model.size() == 0) { return null; //ModelAndView mav = new ModelAndView(CREATE_VIEW); //mav.addObject(MODEL_KEY, MSG_CODE); //return mav; } else { return new ModelAndView("queryXml", model); } /* try { // Gather diagnostics for logging request. // catch rogue invalid request here if ( request.getQueryString() == null ) { log.debug( "Invalid dataset url reference "+ request.getPathInfo() ); throw new RadarServerException( "Invalid dataset url reference "+ request.getPathInfo() ); } // Query results in model Map<String,Object> model = new HashMap<String,Object>(); radarQuery( request, response, model ); if (model == null || model.size() == 0 ) { ModelAndView mav = new ModelAndView(CREATE_VIEW); mav.addObject(MODEL_KEY, MSG_CODE); return mav; } else { return new ModelAndView( "queryXml", model ); } } catch ( RadarServerException e ) { throw e; // pass it onto Spring exceptionResolver } catch ( Throwable e ) { log.error( "handleRequestInternal(): Problem handling request.", e ); throw new RadarServerException( "handleRequestInternal(): Problem handling request.", e ); } */ } // get/check/process query public void radarQuery(HttpServletRequest req, HttpServletResponse res, Map<String, Object> model) throws ServletException, IOException, RadarServerException { DatasetRepository.RadarType radarType = DatasetRepository.RadarType.nexrad; // need to extract data according to the (dataset) given String pathInfo = TdsPathUtils.extractPath(req, getControllerPath()); if (pathInfo == null) pathInfo = ""; if (pathInfo.startsWith("/")) pathInfo = pathInfo.substring(1); String rt = pathInfo.substring(0, pathInfo.indexOf('/', 1)); try { radarType = DatasetRepository.RadarType.valueOf(rt); } catch (Exception e) { res.sendError(HttpServletResponse.SC_BAD_REQUEST, "bad radarType=" + rt); return; } Boolean level2 = pathInfo.contains("level2"); // parse the input QueryParams qp = new QueryParams(); if (!qp.parseQuery(req, res, new String[]{QueryParams.XML, QueryParams.HTML, QueryParams.RAW, QueryParams.NETCDF})) { //log.error( "parseQuery Failed "+ qp.errs.toString() + req.getQueryString() ); //throw new RadarServerException( qp.errs.toString() );//+ req.getQueryString() ); return; //TODO: uncomment above 2 lines when QueryParams exception is fixed } // check Query Params if (!checkQueryParms(radarType, qp, level2)) { res.sendError(HttpServletResponse.SC_BAD_REQUEST, "checkQueryParms Failed=" + qp.errs.toString()); return; //log.error( "checkQueryParms Failed "+ qp.errs.toString() + req.getQueryString() ); //throw new RadarServerException( qp.errs.toString() );//+ req.getQueryString() ); } // check type of output wanted XML html qp.acceptType = qp.acceptType.replaceFirst(".*/", ""); // creates first part of catalog if (!createHeader(radarType, qp, pathInfo, model)) { res.sendError(HttpServletResponse.SC_BAD_REQUEST, "Write Header Failed=" + qp.errs.toString()); return; // log.error( "Write Header Failed "+ qp.errs.toString() + req.getQueryString() ); // throw new RadarServerException( qp.errs.toString() ); // req.getQueryString() ); } // gets products according to stations, time, and variables boolean dataFound = false; List<DatasetEntry> entries = new ArrayList<>(); if (qp.vars == null) { dataFound = processQuery(pathInfo, qp, null, entries); if (releaseDataset) DatasetRepository.removeRadarDatasetCollection(pathInfo, null); } else { int count = 0; for (String var : qp.vars) { dataFound = processQuery(pathInfo, qp, var, entries); if (dataFound) count++; if (releaseDataset) DatasetRepository.removeRadarDatasetCollection(pathInfo, var); } if (count > 0) dataFound = true; } // save entries model.put("datasets", entries); if (dataFound) { model.put("documentation", Integer.toString(entries.size()) + " datasets found for query"); } else if (qp.errs.length() > 0) { model.put("documentation", qp.errs.toString()); } else { model.put("documentation", "No data available for station(s) and time range"); } } // end radarNexradQuery // check that parms have valid stations, vars and times private Boolean checkQueryParms(DatasetRepository.RadarType radarType, QueryParams qp, Boolean level2) throws IOException { if (qp.hasBB) { if (radarType.equals(DatasetRepository.RadarType.nexrad)) qp.stns = RadarServerUtil.getStationNames(qp.getBB(), DatasetRepository.nexradList); else qp.stns = RadarServerUtil.getStationNames(qp.getBB(), DatasetRepository.terminalList); if (qp.stns.size() == 0) { qp.errs.append("Bounding Box contains no stations "); return false; } if (!level2) qp.stns = RadarServerUtil.convert4to3stations(qp.stns); } if (qp.hasStns) { if (RadarServerUtil.isStationListEmpty(qp.stns, radarType)) { qp.errs.append("No valid stations specified, need 1 "); return false; } else if (level2) { for (String stn : qp.stns) { if (stn.length() == 3) { qp.errs.append("Need 4 character station names "); return false; } } } else if (!level2) qp.stns = RadarServerUtil.convert4to3stations(qp.stns); } if (qp.hasLatlonPoint) { qp.stns = new ArrayList<String>(); if (radarType.equals(DatasetRepository.RadarType.nexrad)) qp.stns.add(RadarServerUtil.findClosestStation(qp.lat, qp.lon, DatasetRepository.nexradList)); else qp.stns.add(RadarServerUtil.findClosestStation(qp.lat, qp.lon, DatasetRepository.terminalList)); if (!level2) qp.stns = RadarServerUtil.convert4to3stations(qp.stns); } else if (qp.fatal) { qp.errs.append("No valid stations specified 2 "); return false; } if (qp.stns == null || qp.stns.size() == 0) { qp.errs.append("No valid stations specified, need 1 "); return false; } boolean useAllStations = (qp.stns.get(0).toUpperCase().equals("ALL")); if (useAllStations) { if (radarType.equals(DatasetRepository.RadarType.nexrad)) qp.stns = RadarServerUtil.getStationNames(DatasetRepository.nexradList); //need station names else qp.stns = RadarServerUtil.getStationNames(DatasetRepository.terminalList); //need station names if (!level2) qp.stns = RadarServerUtil.convert4to3stations(qp.stns); } if (qp.hasTimePoint) { if (qp.time.isPresent()) { try { qp.time_end = new DateType("present", null, null); qp.time_start = epicDateType; } catch (java.text.ParseException e) { qp.errs.append("Illegal param= 'time' must be valid ISO Duration"); return false; } } else { qp.time_end = qp.time; qp.time_start = qp.time; } } else if (qp.hasDateRange) { DateRange dr = qp.getCalendarDateRange().toDateRange(); qp.time_start = dr.getStart(); qp.time_end = dr.getEnd(); } else { // get all times qp.time_latest = 1; //qp.hasDateRange = true; try { qp.time = new DateType("present", null, null); qp.time_end = new DateType("present", null, null); qp.time_start = epicDateType; } catch (java.text.ParseException e) { qp.errs.append("Illegal param= 'time' must be valid ISO Duration "); return false; } } if (level2) { qp.vars = null; // level2 can't select vars } else if (qp.vars == null) { //level 3 with no vars qp.errs.append("No vars selected "); return false; } else if (qp.vars.get(0).contains("/")) { // remove desc from vars ArrayList<String> tmp = new ArrayList<String>(); for (String var : qp.vars) { tmp.add(var.replaceFirst("/.*", "")); } qp.vars = tmp; } return true; } // create catalog Header private Boolean createHeader(DatasetRepository.RadarType radarType, QueryParams qp, String pathInfo, Map<String, Object> model) throws IOException { Boolean level2 = pathInfo.contains("level2"); int level = (level2) ? 2 : 3; StringBuffer str = new StringBuffer(); str.append("Radar Level").append(level).append(" datasets in near real time"); model.put("name", str.toString()); str.setLength(0); str.append("/thredds/dodsC/").append(pathInfo).append("/"); model.put("base", str.toString()); str.setLength(0); str.append("RadarLevel").append(level).append(" datasets for available stations and times"); model.put("dname", str.toString()); str.setLength(0); str.append("accept=").append(qp.acceptType).append("&"); if (!level2 && qp.vars != null) { // add vars str.append("var="); for (int i = 0; i < qp.vars.size(); i++) { str.append(qp.vars.get(i)); if (i < qp.vars.size() - 1) { str.append(","); } } str.append("&"); } // use all stations if (qp.stns.get(0).toUpperCase().equals("ALL")) { str.append("stn=ALL&"); } else if (qp.hasStns) { for (String station : qp.stns) { str.append("stn=").append(station).append("&"); } } else if (qp.hasBB) { str.append("south=").append(qp.south).append("&north=").append(qp.north).append("&"); str.append("west=").append(qp.west).append("&east=").append(qp.east).append("&"); } // no time given if (qp.time_latest == 1) { //str.deleteCharAt( str.length() -1); str.append("time=present"); } else if (qp.hasDateRange) { if (qp.time_start.getDate() == null || qp.time_start.isBlank() || qp.time_end.getDate() == null || qp.time_end.isBlank()) { str.append("time_start=").append(qp.time_start.toString()); str.append("&time_end=").append(qp.time_end.toString()); qp.errs.append("need ISO time format "); return false; } else { str.append("time_start=").append(qp.time_start.toDateTimeStringISO()); str.append("&time_end=").append(qp.time_end.toDateTimeStringISO()); } } else if (qp.time.isPresent()) { str.append("time=present"); } else if (qp.hasTimePoint) { if (qp.time.getDate() == null || qp.time.isBlank()) { str.append("time=").append(qp.time.toString()); qp.errs.append("need ISO time format "); return false; } else { str.append("time=").append(qp.time.toDateTimeStringISO()); } } model.put("ID", str.toString()); if (level2) { model.put("type", "NEXRAD2"); } else if (radarType.equals(DatasetRepository.RadarType.nexrad)) { model.put("type", "NIDS"); } else { model.put("type", "TDWR"); } // at this point must have stations if (RadarServerUtil.isStationListEmpty(qp.stns, radarType)) { qp.errs.append("No station(s) meet query criteria "); return false; } return true; } /* Final Output format, save information in DatasetEntry de <dataset name="Level2_KFTG_20100121_0000.ar2v" ID="735519521" urlPath="KFTG/20100121/Level2_KFTG_20100121_0000.ar2v"> <date type="start of ob">2010-01-21T00:00:00</date> </dataset> */ private Boolean processQuery(String dataset, QueryParams qp, String var, List<DatasetEntry> entries) throws RadarServerException { Boolean getAllTimes = true; String yyyymmddStart = null; String yyyymmddEnd = null; String dateStart = null; String dateEnd = null; try { if (!qp.time_start.equals(epicDateType)) { getAllTimes = false; yyyymmddStart = qp.time_start.toDateString(); yyyymmddStart = yyyymmddStart.replace("-", ""); yyyymmddEnd = qp.time_end.toDateString(); yyyymmddEnd = yyyymmddEnd.replace("-", ""); dateStart = yyyymmddStart + "_" + RadarServerUtil.hhmm(qp.time_start.toDateTimeString()); dateEnd = yyyymmddEnd + "_" + RadarServerUtil.hhmm(qp.time_end.toDateTimeString()); } DatasetRepository.RadarDatasetCollectionReturn rdcReturn = DatasetRepository.getRadarDatasetCollection(dataset, var); if (rdcReturn.err != null) { qp.errs.append(rdcReturn.err); return false; } RadarDatasetCollection rdc = rdcReturn.rdc; StringBuffer time = new StringBuffer(); StringBuffer product = new StringBuffer(); StringBuffer url = new StringBuffer(); boolean isLevel2 = dataset.contains("level2"); String type = (isLevel2 ? "Level2" : "Level3"); String suffix = (isLevel2 ? ".ar2v" : ".nids"); Calendar cal = Calendar.getInstance(java.util.TimeZone.getTimeZone("GMT")); Date now = cal.getTime(); String currentDay = dateFormat.format(now); for (String stn : qp.stns) { RadarStationCollection rsc = rdc.queryStation(stn, currentDay); if (rsc == null) continue; for (String day : rsc.getDays()) { // check for valid day if (!getAllTimes && !RadarServerUtil.isValidDay(day, yyyymmddStart, yyyymmddEnd)) continue; ArrayList<String> tal; if (rdc.isCaseStudy()) { // tal = rsc.getHourMinute("all"); for (String prod : tal) { // check times if (!getAllTimes && !RadarServerUtil.isValidDate(prod, dateStart, dateEnd)) continue; // save this entry DatasetEntry de = new DatasetEntry(); int idx = prod.indexOf('/'); if (idx > 0) { de.setName(prod.substring(idx + 1)); } else { de.setName(prod); } de.setID(Integer.toString(prod.hashCode())); url.setLength(0); url.append(stn).append("/"); if (var != null) { url.append(var).append("/"); } url.append(prod); de.setUrlPath(url.toString()); de.setDate(RadarServerUtil.getObTimeISO(prod)); entries.add(de); } continue; } else { tal = rsc.getHourMinute(day); } if (tal == null) continue; for (String hm : tal) { time.setLength(0); time.append(day).append("_").append(hm); if (!getAllTimes && !RadarServerUtil.isValidDate(time.toString(), dateStart, dateEnd)) continue; // save this entry DatasetEntry de = new DatasetEntry(); product.setLength(0); product.append(type).append("_").append(rsc.getStnName()).append("_"); if (!isLevel2) product.append(var).append("_"); product.append(day).append("_").append(hm).append(suffix); de.setName(product.toString()); de.setID(Integer.toString(product.toString().hashCode())); url.setLength(0); if (!isLevel2) { url.append(var).append("/"); } url.append(rsc.getStnName()).append("/").append(day).append("/").append(product.toString()); de.setUrlPath(url.toString()); de.setDate(RadarServerUtil.getObTimeISO(product.toString())); entries.add(de); if (qp.hasTimePoint) break; } if (qp.hasTimePoint) break; } } return true; } catch (Throwable e) { log.error("Error on dataset =" + dataset + " var =" + var, e); throw new RadarServerException("Error on dataset =" + dataset + " or var =" + var, e); } } /* * Used to store the information about a dataset */ public static class DatasetEntry { private String name; private String ID; private String urlPath; private String date; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getID() { return ID; } public void setID(String ID) { this.ID = ID; } public String getUrlPath() { return urlPath; } public void setUrlPath(String urlPath) { this.urlPath = urlPath; } public String getDate() { return date; } public void setDate(String date) { this.date = date; } } }