/*
* Copyright 1998-2014 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.cdmremote;
import org.springframework.stereotype.Controller;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.validation.BindException;
import org.jdom2.Document;
import org.jdom2.transform.XSLTransformer;
import org.jdom2.output.XMLOutputter;
import org.jdom2.output.Format;
import thredds.catalog.InvDatasetFeatureCollection;
import thredds.server.config.TdsContext;
import thredds.servlet.DatasetHandler;
import thredds.servlet.ServletUtil;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.List;
import java.util.Arrays;
import java.util.Formatter;
import ucar.nc2.constants.CDM;
import ucar.nc2.ft.*;
import ucar.nc2.ft.point.remote.PointStreamProto;
import ucar.nc2.ft.point.remote.PointStream;
import ucar.nc2.ft.point.writer.FeatureDatasetPointXML;
import ucar.nc2.stream.NcStream;
import ucar.nc2.stream.NcStreamWriter;
import ucar.nc2.stream.NcStreamProto;
import ucar.nc2.NetcdfFile;
import ucar.nc2.constants.FeatureType;
import ucar.nc2.dataset.NetcdfDataset;
import ucar.nc2.util.DiskCache2;
import ucar.unidata.geoloc.Station;
import ucar.unidata.util.StringUtil2;
/**
* Controller for CdmrFeature service.
* At the moment, only handles station time series
*
* @Deprecated deprecated in favor of CdmrfController
*
* @author caron
* @since May 28, 2009
*
*/
@Deprecated
@Controller
public class CdmrFeatureController { // implements LastModified {
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(CdmrFeatureController.class);
private static boolean debug = false, showTime = false, showReq = false;
private static DiskCache2 diskCache;
private static boolean allow = true;
public static void setDiskCache(DiskCache2 _diskCache) {
diskCache = _diskCache;
}
//public static void setAllow(boolean _allow) {
public void setAllow(boolean _allow) {
allow = _allow;
}
////////////////////////////////////////////////////////////////////
private TdsContext tdsContext;
public CdmrFeatureController() {
// setCommandClass(CdmRemoteQueryBean.class);
// setCommandName("PointQueryBean");
}
public void setTdsContext(TdsContext tdsContext) {
this.tdsContext = tdsContext;
}
protected ModelAndView handle(HttpServletRequest req, HttpServletResponse res, Object command, BindException errors) throws Exception {
if (!allow) {
res.sendError(HttpServletResponse.SC_FORBIDDEN, "Service not supported");
return null;
}
// absolute path of the dataset endpoint
String absPath = ServletUtil.getRequestServer(req) + req.getContextPath() + req.getServletPath() + req.getPathInfo();
if (showReq)
System.out.printf("CdmFeatureController req=%s%n", absPath + "?" + req.getQueryString());
if (debug) {
System.out.printf(" path=%s%n query=%s%n", req.getPathInfo(), req.getQueryString());
}
CdmRemoteQueryBean query = null;
try {
// query validation - first pass
query = (CdmRemoteQueryBean) command;
if (!query.validate()) {
res.sendError(HttpServletResponse.SC_BAD_REQUEST, query.getErrorMessage());
if (debug) System.out.printf(" query error= %s %n", query.getErrorMessage());
return null;
}
} catch (Throwable t) {
res.sendError(HttpServletResponse.SC_BAD_REQUEST, t.getMessage());
}
if (debug) System.out.printf(" %s%n", query);
FeatureDatasetPoint fdp = null;
// this looks for a featureCollection
InvDatasetFeatureCollection fc = DatasetHandler.getFeatureCollection(req, res);
if (fc != null) {
fdp = (FeatureDatasetPoint) fc.getFeatureDataset();
} else {
// tom kunicki 12/18/10
// allows a single NetcdfFile to be turned into a FeatureDataset
NetcdfFile ncfile = DatasetHandler.getNetcdfFile(req, res);
if (ncfile != null) {
FeatureDataset fd = FeatureDatasetFactoryManager.wrap(
FeatureType.ANY, // will check FeatureType below if needed...
NetcdfDataset.wrap(ncfile, null),
null,
new Formatter(System.err)); // better way to do this?
if (fd instanceof FeatureDatasetPoint) {
fdp = (FeatureDatasetPoint) fd;
}
}
}
if (fdp == null) {
res.sendError(HttpServletResponse.SC_NOT_FOUND, "not a point or station dataset");
return null;
}
List<FeatureCollection> list = fdp.getPointFeatureCollectionList();
if (list.size() == 0) {
log.error(fdp.getLocation()+" does not have any PointFeatureCollections");
res.sendError(HttpServletResponse.SC_NOT_FOUND, fdp.getLocation()+" does not have any PointFeatureCollections");
return null;
}
// check on feature type, using suffix convention LOOK
String path = req.getPathInfo();
FeatureType ft = null;
if (path.endsWith("/station")) {
ft = FeatureType.STATION;
path = path.substring(0, path.lastIndexOf('/'));
} else if (path.endsWith("/point")) {
ft = FeatureType.POINT;
path = path.substring(0, path.lastIndexOf('/'));
}
if (ft != null && ft != fdp.getFeatureType()) {
res.sendError(HttpServletResponse.SC_NOT_FOUND, "feature type mismatch: expetected " + ft + " found" + fdp.getFeatureType());
}
try {
CdmRemoteQueryBean.RequestType reqType = query.getRequestType();
CdmRemoteQueryBean.ResponseType resType = query.getResponseType();
switch (reqType) {
case capabilities:
case form:
return processXml(req, res, fdp, absPath, query);
case header:
return processHeader(absPath, res, fdp, query);
case dataForm:
case data:
return processData(req, res, fdp, path, query);
case stations:
if (resType == CdmRemoteQueryBean.ResponseType.xml)
return processXml(req, res, fdp, absPath, query);
else
return processStations(res, fdp, query);
}
} catch (FileNotFoundException e) {
res.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
return null;
} catch (Throwable t) {
log.error("CdmRemoteController exception:", t);
res.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, t.getMessage());
return null;
} finally {
if (showReq) System.out.printf(" done%n");
if (null != fdp)
try {
fdp.close();
} catch (IOException ioe) {
log.error("Failed to close = " + path);
}
}
return null;
}
private String getContentType(CdmRemoteQueryBean query) {
CdmRemoteQueryBean.RequestType reqType = query.getRequestType();
if (reqType == CdmRemoteQueryBean.RequestType.form)
return "text/html; charset=iso-8859-1";
CdmRemoteQueryBean.ResponseType resType = query.getResponseType();
switch (resType) {
case csv:
return "text/plain";
case netcdf:
return "application/x-netcdf";
case ncstream:
return "application/octet-stream";
case xml:
return "application/xml";
}
return "text/plain";
}
private String getContentDescription(CdmRemoteQueryBean query) {
CdmRemoteQueryBean.ResponseType resType = query.getResponseType();
switch (resType) {
case ncstream:
return "ncstream";
default:
return null;
}
}
private ModelAndView processData(HttpServletRequest req, HttpServletResponse res, FeatureDatasetPoint fdp, String path, CdmRemoteQueryBean qb) throws IOException {
switch (fdp.getFeatureType()) {
case POINT:
return processPointData(req, res, fdp, path, qb);
case STATION:
return processStationData(req, res, fdp, path, qb);
}
return null;
}
private ModelAndView processPointData(HttpServletRequest req, HttpServletResponse res, FeatureDatasetPoint fdp, String path, CdmRemoteQueryBean qb) throws IOException {
/*long start = 0;
if (showTime) {
start = System.currentTimeMillis();
ucar.unidata.io.RandomAccessFile.setDebugAccess(true); // LOOK !!
}
List<FeatureCollection> coll = fdp.getPointFeatureCollectionList();
PointFeatureCollection pfc = (PointFeatureCollection) coll.get(0);
PointWriter pointWriter = new PointWriter(fdp, pfc, qb, diskCache);
// query validation - second pass
if (!pointWriter.validate(res)) {
return null;
}
// set content type, description
res.setContentType(getContentType(qb));
if (null != getContentDescription(qb))
res.setHeader("Content-Description", getContentDescription(qb));
// special handling for netcdf files
CdmRemoteQueryBean.ResponseType resType = qb.getResponseType();
if (resType == CdmRemoteQueryBean.ResponseType.netcdf) {
if (path.startsWith("/")) path = path.substring(1);
path = StringUtil2.replace(path, "/", "-");
res.setHeader("Content-Disposition", "attachment; filename=" + path + ".nc");
File file = pointWriter.writeNetcdf();
ServletUtil.returnFile(req, res, file, getContentType(qb));
if (!file.delete()) {
log.warn("file delete failed =" + file.getPath());
}
if (showTime) {
long took = System.currentTimeMillis() - start;
System.out.println("\ntotal response took = " + took + " msecs");
}
} else {
// otherwise stream it out
PointWriter.Writer w = pointWriter.write(res);
if (showTime) {
long took = System.currentTimeMillis() - start;
System.out.printf("%ntotal response took %d msecs nobs = %d%n seeks= %d nbytes read= %d%n", took, w.count,
ucar.unidata.io.RandomAccessFile.getDebugNseeks(), ucar.unidata.io.RandomAccessFile.getDebugNbytes());
ucar.unidata.io.RandomAccessFile.setDebugAccess(false); // LOOK !!
}
}*/
return null;
}
private ModelAndView processStationData(HttpServletRequest req, HttpServletResponse res, FeatureDatasetPoint fdp, String path, CdmRemoteQueryBean qb) throws IOException {
/* long start = 0;
if (showTime) {
start = System.currentTimeMillis();
ucar.unidata.io.RandomAccessFile.setDebugAccess(true); // LOOK !!
}
List<FeatureCollection> coll = fdp.getPointFeatureCollectionList();
StationTimeSeriesFeatureCollection sfc = (StationTimeSeriesFeatureCollection) coll.get(0);
StationWriter stationWriter = new StationWriter(fdp, sfc, qb, diskCache);
if (!stationWriter.validate(res)) {
return null; // error was sent
}
// set content type, description
res.setContentType(getContentType(qb));
if (null != getContentDescription(qb))
res.setHeader("Content-Description", getContentDescription(qb));
// special handling for netcdf files
CdmRemoteQueryBean.ResponseType resType = qb.getResponseType();
if (resType == CdmRemoteQueryBean.ResponseType.netcdf) {
if (path.startsWith("/")) path = path.substring(1);
path = StringUtil2.replace(path, "/", "-");
res.setHeader("Content-Disposition", "attachment; filename=" + path + ".nc");
File file = stationWriter.writeNetcdf();
ServletUtil.returnFile(req, res, file, getContentType(qb));
if (!file.delete()) {
log.warn("file delete failed =" + file.getPath());
}
if (showTime) {
long took = System.currentTimeMillis() - start;
System.out.println("\ntotal response took = " + took + " msecs");
}
return null;
}
// otherwise stream it out
StationWriter.Writer w = stationWriter.write(res);
if (showTime) {
long took = System.currentTimeMillis() - start;
System.out.printf("%ntotal response took %d msecs nobs = %d%n seeks= %d nbytes read= %d%n", took, w.count,
ucar.unidata.io.RandomAccessFile.getDebugNseeks(), ucar.unidata.io.RandomAccessFile.getDebugNbytes());
ucar.unidata.io.RandomAccessFile.setDebugAccess(false); // LOOK !!
}*/
return null;
}
private ModelAndView processStations(HttpServletResponse res, FeatureDatasetPoint fdp, CdmRemoteQueryBean query) throws IOException {
OutputStream out = new BufferedOutputStream(res.getOutputStream(), 10 * 1000);
res.setContentType(getContentType(query));
if (null != getContentDescription(query))
res.setHeader("Content-Description", getContentDescription(query));
try {
List<FeatureCollection> coll = fdp.getPointFeatureCollectionList();
StationTimeSeriesFeatureCollection sfc = (StationTimeSeriesFeatureCollection) coll.get(0);
List<Station> stations;
if (query.getLatLonRect() != null)
stations = sfc.getStations(query.getLatLonRect());
else if (query.getStnNames() != null)
stations = sfc.getStations(Arrays.asList(query.getStnNames()));
else
stations = sfc.getStations();
PointStreamProto.StationList stationsp = PointStream.encodeStations(stations);
byte[] b = stationsp.toByteArray();
PointStream.writeMagic(out, PointStream.MessageType.StationList);
NcStream.writeVInt(out, b.length);
out.write(b);
} catch (Throwable t) {
NcStreamProto.Error err = NcStream.encodeErrorMessage(t.getMessage());
byte[] b = err.toByteArray();
PointStream.writeMagic(out, PointStream.MessageType.Error);
NcStream.writeVInt(out, b.length);
out.write(b);
throw new IOException(t);
}
out.flush();
res.flushBuffer();
return null;
}
private ModelAndView processHeader(String absPath, HttpServletResponse res, FeatureDatasetPoint fdp, CdmRemoteQueryBean query) throws IOException {
OutputStream out = new BufferedOutputStream(res.getOutputStream(), 10 * 1000);
res.setContentType(getContentType(query));
if (null != getContentDescription(query))
res.setHeader("Content-Description", getContentDescription(query));
NetcdfFile ncfile = fdp.getNetcdfFile(); // LOOK will fail
NcStreamWriter ncWriter = new NcStreamWriter(ncfile, absPath);
//WritableByteChannel wbc = Channels.newChannel(out);
ncWriter.sendHeader(out);
NcStream.writeVInt(out, 0);
out.flush();
res.flushBuffer();
return null;
}
private ModelAndView processXml(HttpServletRequest req, HttpServletResponse res, FeatureDatasetPoint fdp, String absPath, CdmRemoteQueryBean query) throws IOException {
//String path = ServletUtil.getRequestServer(req) + req.getContextPath() + req.getServletPath() + datasetPath;
FeatureDatasetPointXML xmlWriter = new FeatureDatasetPointXML(fdp, absPath);
CdmRemoteQueryBean.RequestType reqType = query.getRequestType();
String infoString;
try {
if (reqType == CdmRemoteQueryBean.RequestType.capabilities) {
Document doc = xmlWriter.getCapabilitiesDocument();
XMLOutputter fmt = new XMLOutputter(Format.getPrettyFormat());
infoString = fmt.outputString(doc);
} else if (reqType == CdmRemoteQueryBean.RequestType.stations) {
Document doc = xmlWriter.makeStationCollectionDocument(query.getLatLonRect(), query.getStnNames());
XMLOutputter fmt = new XMLOutputter(Format.getPrettyFormat());
infoString = fmt.outputString(doc);
} else if (reqType == CdmRemoteQueryBean.RequestType.form) {
String xslt = fdp.getFeatureType() == FeatureType.STATION ? "ncssSobs.xsl" : "fmrcPoint.xsl";
InputStream is = getXSLT(xslt);
Document doc = xmlWriter.getCapabilitiesDocument();
XSLTransformer transformer = new XSLTransformer(is);
Document html = transformer.transform(doc);
XMLOutputter fmt = new XMLOutputter(Format.getPrettyFormat());
infoString = fmt.outputString(html);
} else {
return null;
}
} catch (Exception e) {
log.error("SobsServlet internal error on "+fdp.getLocation(), e);
res.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "SobsServlet internal error");
return null;
}
res.setContentType(getContentType(query));
res.setContentLength(infoString.getBytes(CDM.utf8Charset).length);
OutputStream out = res.getOutputStream();
out.write(infoString.getBytes(CDM.utf8Charset));
out.flush();
return null;
}
private InputStream getXSLT(String xslName) {
return getClass().getResourceAsStream("/resources/xsl/" + xslName);
}
}