/*
* 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.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestMapping;
import thredds.server.AbstractController;
import thredds.util.ContentType;
import thredds.util.TdsPathUtils;
import ucar.nc2.ft.FeatureDatasetFactoryManager;
import ucar.nc2.util.EscapeStrings;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.output.XMLOutputter;
import org.jdom2.output.Format;
import org.springframework.web.servlet.mvc.LastModified;
import thredds.server.config.TdsContext;
import thredds.servlet.DataRootHandler;
import thredds.servlet.ServletUtil;
import thredds.servlet.DatasetHandler;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.io.*;
import java.util.StringTokenizer;
import ucar.ma2.InvalidRangeException;
import ucar.nc2.stream.NcStreamWriter;
import ucar.nc2.constants.FeatureType;
import ucar.nc2.NetcdfFile;
import ucar.nc2.ParsedSectionSpec;
/**
* Spring controller for CdmRemote service.
*
* @author caron
* @since May 28, 2009
*/
@Controller
@RequestMapping("/cdmremote")
public class CdmRemoteController extends AbstractController implements LastModified {
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(CdmRemoteController.class);
private static final boolean debug = false, showReq=false;
@Autowired
TdsContext tdsContext;
private boolean allow = true;
public void setTdsContext(TdsContext tdsContext) {
this.tdsContext = tdsContext;
}
public void setAllow(boolean allow) {
this.allow = allow;
}
@Override
protected String getControllerPath() { return "/cdmremote/"; }
@Override
protected String[] getEndings() { return new String[0]; }
@Override
public long getLastModified(HttpServletRequest req) {
String path = TdsPathUtils.extractPath(req, "wcs/");
File file = DataRootHandler.getInstance().getCrawlableDatasetAsFile(path);
if ((file != null) && file.exists())
return file.lastModified();
return -1;
}
@RequestMapping("**")
public void handleRequest(HttpServletRequest req, HttpServletResponse res,
@Valid CdmRemoteQueryBean qb,
BindingResult validationResult) throws IOException { //}, NcssException, ParseException, InvalidRangeException {
if (!allow) {
res.sendError(HttpServletResponse.SC_FORBIDDEN, "Service not supported");
return;
}
String datasetPath = getDatasetPath(req);
String absPath = getAbsolutePath(req);
if (showReq)
System.out.printf("CdmRemoteController req=%s%n", absPath+"?"+req.getQueryString());
if (debug) {
System.out.printf(" path=%s%n query=%s%n", datasetPath, req.getQueryString());
}
// query validation - first pass
if (!qb.validate()) {
res.sendError(HttpServletResponse.SC_BAD_REQUEST, qb.getErrorMessage());
if (debug) System.out.printf(" query error= %s %n", qb.getErrorMessage());
return;
}
if (debug) System.out.printf(" %s%n", qb);
NetcdfFile ncfile = null;
try {
ncfile = DatasetHandler.getNetcdfFile(req, res, datasetPath);
if (ncfile == null) return;
/*
res.setStatus(HttpServletResponse.SC_NOT_FOUND);
log.debug("DatasetHandler.FAIL path={}", datasetPath);
return;
} */
long size = -1;
switch (qb.getRequestType()) {
case capabilities: {
res.setContentType(ContentType.xml.getContentHeader());
PrintWriter pw = res.getWriter();
FeatureType ftFromMetadata = FeatureDatasetFactoryManager.findFeatureType(ncfile);
sendCapabilities(pw, ftFromMetadata, absPath); // LOOK BAD - must figure out what is the featureType and save it
res.flushBuffer();
return;
}
case form: // LOOK could do a ncss style form
case cdl: {
res.setContentType(ContentType.text.getContentHeader());
PrintWriter pw = res.getWriter();
ncfile.setLocation(datasetPath); // hide where the file is stored
String cdl = ncfile.toString();
size = (long) thredds.servlet.ServletUtil.setResponseContentLength(res, cdl);
pw.write(cdl);
break;
}
case ncml: {
res.setContentType(ContentType.xml.getContentHeader());
PrintWriter pw = res.getWriter();
ncfile.writeNcML(pw, absPath);
break;
}
case header: {
res.setContentType(ContentType.binary.getContentHeader());
res.setHeader("Content-Description", "ncstream");
OutputStream out = new BufferedOutputStream(res.getOutputStream(), 10 * 1000);
//WritableByteChannel wbc = Channels.newChannel(out);
NcStreamWriter ncWriter = new NcStreamWriter(ncfile, ServletUtil.getRequestBase(req));
size = ncWriter.sendHeader(out);
out.flush();
break;
}
default: {
res.setContentType(ContentType.binary.getContentHeader());
res.setHeader("Content-Description", "ncstream");
size = 0;
//WritableByteChannel wbc = Channels.newChannel(out);
NcStreamWriter ncWriter = new NcStreamWriter(ncfile, ServletUtil.getRequestBase(req));
String query;
if(qb.getVar() != null)
query = qb.getVar();
else
query = req.getQueryString();
if ((query == null) || (query.length() == 0)) {
res.sendError(HttpServletResponse.SC_BAD_REQUEST, "must have query string");
return;
}
OutputStream out = new BufferedOutputStream(res.getOutputStream(), 10 * 1000);
query = EscapeStrings.unescapeURLQuery(query);
StringTokenizer stoke = new StringTokenizer(query, ";"); // need UTF/%decode
while (stoke.hasMoreTokens()) {
ParsedSectionSpec cer = ParsedSectionSpec.parseVariableSection(ncfile, stoke.nextToken());
size += ncWriter.sendData(cer.v, cer.section, out, qb.getCompression());
}
out.flush();
}
} // end switch on req type
res.flushBuffer();
if (showReq)
System.out.printf("CdmRemoteController ok, size=%s%n", size);
} catch (FileNotFoundException e) {
log.debug("FAIL", e);
res.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
} catch (IllegalArgumentException | InvalidRangeException e) { // ParsedSectionSpec failed
res.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
} catch (Throwable e) {
log.error(e.getMessage(), e);
res.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
} finally {
if (null != ncfile)
try {
ncfile.close();
} catch (IOException ioe) {
log.error("Failed to close = " + datasetPath);
}
}
}
private void sendCapabilities(PrintWriter pw, FeatureType ft, String absPath) throws IOException {
Element rootElem = new Element("cdmRemoteCapabilities");
Document doc = new Document(rootElem);
rootElem.setAttribute("location", absPath);
Element elem = new Element("featureDataset");
if (ft != null) // LOOK lame
elem.setAttribute("type", ft.toString());
elem.setAttribute("url", absPath);
rootElem.addContent(elem);
XMLOutputter fmt = new XMLOutputter(Format.getPrettyFormat());
fmt.output(doc, pw);
}
/* private ModelAndView sendCapabilities(HttpServletResponse res, NetcdfFile ncfile, String absPath, PointQueryBean query) throws IOException {
NetcdfDataset ds = NetcdfDataset.wrap(ncfile, NetcdfDataset.getEnhanceAll());
Formatter errlog = new Formatter();
try {
FeatureDataset featureDataset = FeatureDatasetFactoryManager.wrap(null, ds, null, errlog);
if (featureDataset != null) {
FeatureType ft = featureDataset.getFeatureType();
if (ft != null)
ftype = featureType.toString();
}
} catch (Throwable t) {
}
this.fdp = fdp;
List<FeatureCollection> list = fdp.getPointFeatureCollectionList();
this.sobs = (StationTimeSeriesFeatureCollection) list.get(0);
String infoString;
Document doc = xmlWriter.getCapabilitiesDocument();
XMLOutputter fmt = new XMLOutputter(Format.getPrettyFormat());
infoString = fmt.outputString(doc);
res.setContentLength(infoString.length());
res.setContentType(getContentType(query));
OutputStream out = res.getOutputStream();
out.write(infoString.getBytes());
out.flush();
return null;
} */
}