/////////////////////////////////////////////////////////////////////////////
// Copyright (c) 1999, COAS, Oregon State University
// ALL RIGHTS RESERVED. U.S. Government Sponsorship acknowledged.
//
// Please read the full copyright notice in the file COPYRIGHT
// in this directory.
//
// Author: Nathan Potter (ndp@oce.orst.edu)
//
// College of Oceanic and Atmospheric Scieneces
// Oregon State University
// 104 Ocean. Admin. Bldg.
// Corvallis, OR 97331-5503
//
/////////////////////////////////////////////////////////////////////////////
/* $Id: DODSServlet.java,v 1.3 2004-02-06 15:23:50 donm Exp $
*
*/
package dods.servlet;
import java.io.*;
import java.text.*;
import java.util.*;
import java.util.zip.DeflaterOutputStream;
import javax.servlet.*;
import javax.servlet.http.*;
import dods.dap.*;
import dods.dap.Server.*;
import dods.dap.parser.ParseException;
import dods.util.*;
/**
* DODSServlet is the base servlet class for all DODS
* servers. (Well, all DODS servers running as java servlets)
* Default handlers for all of the acceptable DODS client
* requests are here.
* <p>
* Each of the request handlers appears as an adjunct method to
* the doGet() method of the base servlet class.
* <p>
* This is an abstract class because it is left to the individual
* server development efforts to write the getDDS() and
* getServerVersion() methods. The getDDS() method is intended to
* be where the server specific DODS server data types are
* used via their associated class factory.
* <p>
* This code relies on the <code>javax.servlet.ServletConfig</code>
* interface (in particular the <code>getInitParameter()</code> method)
* to retrieve the name of a .ini file containing information
* about where to find extensive configuration information used by
* the servlet. Alternate methods for establishing this functionality
* can be arranged by overloading the method <code>loadIniFile()</code>
* <p>
* The servlet should be started in the servlet engine with the following
* initParameters set:
* <p>
* <b>For the old jswdk servlet engine:</b>
* <pre>
* dts.code=dods.servers.test.dts
* dts.initparams=iniFilePath=/usr/dods/dts,iniFileName=dts.ini
* </pre>
* <b>For the tomcat servlet engine:</b>
* <pre>
* <servlet>
* <servlet-name>
* dts
* </servlet-name>
* <servlet-class>
* dods.servers.test.dts
* </servlet-class>
* <init-param>
* <param-name>iniFilePath</param-name>
* <param-value>/usr/dods/dts</param-value>
* </init-param>
* <init-param>
* <param-name>iniFileName</param-name>
* <param-value>dts.ini</param-value>
* </init-param>
* </servlet>
*
* </pre>
* Assuming, of course, that the .ini file is located in /usr/dods/dts
* and is named dts.ini. For example .ini files look in the subdirectory
* Java-DODS/.ini (where Java-DODS is the top of the distribution).
* <p>
* Also, the method <code>processDodsURL()</code> could be overloaded
* if some kind of special processing of the incoming request is needed
* to ascertain the DODS URL information.
*
* @see #getDDS(String)
* @see #loadIniFile()
* @see #processDodsURL(HttpServletRequest)
*
* @author Nathan David Potter
*/
public abstract class DODSServlet extends HttpServlet {
private boolean track = false;
/***************************************************************************
* Used for thread syncronization.
*
* @serial
*/
private Object syncLock = new Object();
/***************************************************************************
* Count "hits" on the server...
*
* @serial
*/
private int HitCounter = 0;
/***************************************************************************
* This function must be implemented locally for each DODS server. It should
* return a String containing the DODS Server Version...
*/
public abstract String getServerVersion();
/***************************************************************************
* This method must be implemented locally for each DODS server. The
* local implementation of this method is the key piece for connecting
* any localized data types that are derived from the dods.dap.Server types
* back into the running servlet.
* <p>
* This method should do the following:
* <ul>
* <li> Make a new ServerFactory (aka BaseTypeFactory) for the dataset requested.
* <li> Instantiate a ServerDDS using the ServerFactory and populate it (this
* could be accomplished by just opening a (cached?) DDS in a file and parsing it)
* <li> Return this freshly minted ServerDDS object (to the servlet code where it is used.)
* </ul>
*
* @param dataSet the name of the data set requested.
*
* @return The ServerDDS object all parsed and ready to roll.
*
* @see dods.dap.Server.ServerDDS
* @see dods.servers.sql.sqlServerFactory
* @see dods.servers.test.test_ServerFactory
*/
//protected abstract ServerDDS getDDS(String dataSet) throws DODSException, ParseException;
protected abstract GuardedDataset getDataset(requestState rs) throws DODSException, IOException, ParseException;
/***************************************************************************
* Intitializes the servlet. Init (at this time) basically sets up
* the object dods.util.Debug from the debuggery flags in the
* servlet InitParameters. The Debug object can be referenced (with
* impunity) from any of the dods code...
*
*/
public void init() throws ServletException {
super.init();
// debuggering
String debugOn = getInitParameter("DebugOn");
if (debugOn != null) {
System.out.println("** DebugOn **");
StringTokenizer toker = new StringTokenizer(debugOn);
while (toker.hasMoreTokens())
Debug.set(toker.nextToken(), true);
}
}
/***************************************************************************
* Turns a ParseException into a DODS error and sends it to the client.
*
* @param pe The <code>ParseException</code> that caused the problem.
* @param response The <code>HttpServletResponse</code> for the client.
*/
public void parseExceptionHandler(ParseException pe, HttpServletResponse response){
System.out.println( pe);
pe.printStackTrace();
try {
BufferedOutputStream eOut = new BufferedOutputStream(response.getOutputStream());
response.setHeader("Content-Description", "dods_error");
// This should probably be set to "plain" but this works, the
// C++ slients don't barf as they would if I sent "plain" AND
// the C++ don't expect compressed data if I do this...
response.setHeader("Content-Encoding", "");
// Strip any double quotes out of the parser error message.
// These get stuck in auto-magically by the javacc generated parser
// code and they break our error parser (bummer!)
String msg = pe.getMessage().replace('\"','\'');
DODSException de2 = new DODSException(DODSException.CANNOT_READ_FILE, msg);
de2.print(eOut);
}
catch(IOException ioe){
System.out.println("Cannot respond to client! IO Error: "+ioe.getMessage());
}
}
/***************************************************************************
* Sends a DODS error to the client.
*
* @param de The DODS exception that caused the problem.
* @param response The <code>HttpServletResponse</code> for the client.
*/
public void dodsExceptionHandler(DODSException de, HttpServletResponse response){
System.out.println( de);
de.printStackTrace();
try {
BufferedOutputStream eOut = new BufferedOutputStream(response.getOutputStream());
response.setHeader("Content-Description", "dods_error");
// This should probably be set to "plain" but this works, the
// C++ slients don't barf as they would if I sent "plain" AND
// the C++ don't expect compressed data if I do this...
response.setHeader("Content-Encoding", "");
de.print(eOut);
de.print(System.out);
}
catch(IOException ioe){
System.out.println("Cannot respond to client! IO Error: "+ioe.getMessage());
}
}
/***************************************************************************
* Sends an error to the client.
*
* @param de The exception that caused the problem.
* @param response The <code>HttpServletResponse</code> for the client.
*/
public void anyExceptionHandler(Throwable e, HttpServletResponse response, requestState rs){
try {
DataOutputStream dos = new DataOutputStream(response.getOutputStream());
response.setHeader("Content-Description", "dods_error");
// This should probably be set to "plain" but this works, the
// C++ slients don't barf as they would if I sent "plain" AND
// the C++ don't expect compressed data if I do this...
response.setHeader("Content-Encoding", "");
dos.writeUTF("DODServlet ERROR: "+e.getMessage());
System.out.println("DODServlet ERROR (anyExceptionHandler): "+e);
System.out.println(rs);
if (track) {
RequestDebug reqD = (RequestDebug) rs.getUserObject();
System.out.println(" request number: " + reqD.reqno+" thread: "+reqD.threadDesc);
}
e.printStackTrace();
}
catch(IOException ioe){
System.out.println("Cannot respond to client! IO Error: "+ioe.getMessage());
}
}
/***************************************************************************
*
* In this (default) implementation of the getDAS() method a locally cached
* DAS is retrieved and parsed. In this method the DAS for the passed dataset
* is loaded from the "das_cache_dir" indidcated in the "[Server]" section of the
* DODSiniFile. If the there is no file available a DODSException is
* thrown. It is certainly possible (and possibly very desirable) to override
* this method when overriding the getDDS() method. One reason for doing this
* is if the DODS server being implemented can generate the DAS information
* dynamically.
*
* When overriding this method be sure that it does the following:
* <ul>
* <li> Instantiates the DAS for the indicated (passed) dataset and
* populates it. This is accomplished in the default implementation
* by opening a (cached?) DAS stored in a file and parsing it. In
* a different implementation it could be created dynamically.
* <li> Returns this freshly minted DAS object. (to the servlet code where it is used.)
* </ul>
*
* @param dataSet the name of the data set requested.
*
*
* @return The DAS object for the data set specified in the parameter <code>dataSet</code>
*
* @see dods.dap.DAS
*/
protected DAS getDAS(requestState rs) throws DODSException, ParseException {
DataInputStream is = null;
DAS myDAS = new DAS();
boolean gotIt = false;
try {
is = openCachedDAS( rs);
myDAS.parse(is);
gotIt = true;
}
catch (FileNotFoundException fnfe) {
// This is no big deal. We just trap it and return an
// empty DAS object.
gotIt = false;
}
finally {
try {
if(is!=null) is.close();
}
catch (IOException ioe) {
throw new DODSException(DODSException.UNKNOWN_ERROR,ioe.getMessage());
}
}
if(gotIt)
if(Debug.isSet("showResponse"))
System.out.println("Successfully opened and parsed DAS cache: " + rs.getDataSet());
else
if(Debug.isSet("showResponse"))
System.out.println("No DAS present for dataset: " + rs.getDataSet());
return(myDAS);
}
/***************************************************************************/
/***************************************************************************
* Sends a DODS error (type UNKNOWN ERROR) to the client and displays a
* message on the server console.
*
*
* @param request The client's <code> HttpServletRequest</code> request object.
* @param response The server's <code> HttpServletResponse</code> response object.
* @param clientMsg Error message <code>String</code> to send to the client.
* @param serverMsg Error message <code>String</code> to display on the server console.
*/
public void sendDODSError(HttpServletRequest request,
HttpServletResponse response,
String clientMsg,
String serverMsg)
throws IOException, ServletException {
response.setContentType("text/plain");
response.setHeader("XDODS-Server", getServerVersion() );
response.setHeader("Content-Description", "dods_error");
// Commented because of a bug in the DODS C++ stuff...
//response.setHeader("Content-Encoding", "none");
ServletOutputStream Out = response.getOutputStream();
DODSException de = new DODSException(DODSException.UNKNOWN_ERROR, clientMsg);
de.print(Out);
response.setStatus(response.SC_OK);
System.out.println(serverMsg);
}
/***************************************************************************/
/***************************************************************************
* Opens a DDS cached on local disk. This can be used on DODS servers (such
* as the DODS SQL Server) that rely on locally cached DDS files as opposed
* to dynamically generated DDS's.
*
* <p>This method uses the <code>iniFile</code> object cached by <code>
* loadIniFile()</code> to determine where to look for the cached <code>
* DDS</code>.
*
* @param dataSet The name of the dataset whose DDS is being requested.
*
* @return An open <code>DataInputStream</code> from which the DDS can
* be read.
*
* @exception DODSException
*
* @see #loadIniFile()
*/
public DataInputStream openCachedDDS(requestState rs) throws DODSException {
String cacheDir = rs.getInitParameter("DDScache");
if(cacheDir == null)
cacheDir = rs.defaultDDScache;
try{
// go get a file stream that points to the requested DDSfile.
File fin = new File(cacheDir + rs.getDataSet());
FileInputStream fp_in = new FileInputStream(fin);
DataInputStream dds_source = new DataInputStream(fp_in);
return(dds_source);
}
catch (FileNotFoundException fnfe) {
throw new DODSException(DODSException.CANNOT_READ_FILE,fnfe.getMessage());
}
}
/***************************************************************************/
/***************************************************************************
* Opens a DAS cached on local disk. This can be used on DODS servers (such
* as the DODS SQL Server) that rely on locally cached DAS files as opposed
* to dynamically generated DAS's.
*
* <p>This method uses the <code>iniFile</code> object cached by <code>
* loadIniFile()</code> to determine where to look for the cached <code>
* DDS</code>.
*
* <p>If the DAS cannot be found an error is sent back to the client.
*
* @param dataSet The name of the data set whose DAS is being requested.
*
* @return An open <code>DataInputStream</code> from which the DAS can
* be read.
*
* @exception FileNotFoundException
*
* @see #loadIniFile()
*/
public DataInputStream openCachedDAS(requestState rs) throws FileNotFoundException {
String cacheDir = rs.getInitParameter("DAScache");
if(cacheDir == null)
cacheDir = rs.defaultDAScache;
// go get a file stream that points to the requested DASfile.
File fin = new File(cacheDir + rs.getDataSet());
FileInputStream fp_in = new FileInputStream(fin);
DataInputStream das_source = new DataInputStream(fp_in);
return(das_source);
}
/***************************************************************************/
/***************************************************************************
* Default handler for the client's DAS request. Operates on the assumption
* that the DAS information is cached on a disk local to the server. If you
* don't like that, then you better override it in your server :)
*
* <p>Once the DAS has been parsed it is sent to the requesting client.
*
* @param request The client's <code> HttpServletRequest</code> request object.
* @param response The server's <code> HttpServletResponse</code> response object.
* @param dataSet Name of the datset whose DAS object is requested.
* @param constraintExpression Constraint expression recieved from the client, probably
* not needed here, but what the heck, it's there if you need it.
*/
public void doGetDAS(HttpServletRequest request,
HttpServletResponse response,
requestState rs)
throws IOException, ServletException {
if(Debug.isSet("showResponse"))
System.out.println("Sending DAS for dataset: " + rs.getDataSet());
response.setContentType("text/plain");
response.setHeader("XDODS-Server", getServerVersion() );
response.setHeader("Content-Description", "dods_dds");
// Commented because of a bug in the DODS C++ stuff...
//response.setHeader("Content-Encoding", "plain");
OutputStream Out = new BufferedOutputStream(response.getOutputStream());
GuardedDataset ds = null;
try {
ds = getDataset(rs);
if(Debug.isSet("showResponse")) System.out.println("Got the GuardedDataset...");
DAS myDAS = ds.getDAS();
myDAS.print(Out);
response.setStatus(response.SC_OK);
}
catch (DODSException de){
dodsExceptionHandler(de,response);
}
catch (ParseException pe) {
parseExceptionHandler(pe,response);
}
finally { // release lock if needed
if (ds != null) ds.release();
}
}
/***************************************************************************/
/***************************************************************************
* Default handler for the client's DDS request. Requires the getDDS() method
* implemented by each server localization effort.
*
* <p>Once the DDS has been parsed and constrained it is sent to the
* requesting client.
*
* @param request The client's <code> HttpServletRequest</code> request object.
* @param response The server's <code> HttpServletResponse</code> response
* object.
* @param dataSet Name of the datset whose DDS object is requested.
* @param constraintExpression Constraint expression recieved from the client.
* This is used (if it's not just empty) to ship the client a view of the
* constrained DDS.
*/
public void doGetDDS(HttpServletRequest request,
HttpServletResponse response,
requestState rs)
throws IOException, ServletException {
if(Debug.isSet("showResponse"))
System.out.println("Sending DDS for dataset: " + rs.getDataSet());
response.setContentType("text/plain");
response.setHeader("XDODS-Server", getServerVersion() );
response.setHeader("Content-Description", "dods_dds");
// Commented because of a bug in the DODS C++ stuff...
//response.setHeader("Content-Encoding", "plain");
OutputStream Out = new BufferedOutputStream(response.getOutputStream());
GuardedDataset ds = null;
try {
ds = getDataset(rs);
// Utilize the getDDS() method to get a parsed and populated DDS
// for this server.
ServerDDS myDDS = ds.getDDS();
if(rs.getConstraintExpression().equals("")){ // No Constraint Expression?
// Send the whole DDS
myDDS.print(Out);
Out.flush();
}
else { // Otherwise, send the constrained DDS
// Instantiate the CEEvaluator and parse the constraint expression
CEEvaluator ce = new CEEvaluator(myDDS);
ce.parseConstraint(rs.getConstraintExpression());
// Send the constrained DDS back to the client
PrintWriter pw = new PrintWriter(new OutputStreamWriter(Out));
myDDS.printConstrained(pw);
pw.flush();
}
response.setStatus(response.SC_OK);
}
catch (DODSException de){
dodsExceptionHandler(de,response);
}
catch (IOException pe) {
anyExceptionHandler(pe,response, rs);
}
catch (ParseException pe) {
parseExceptionHandler(pe,response);
}
finally { // release lock if needed
if (ds != null) ds.release();
}
}
/***************************************************************************/
/***************************************************************************
* Default handler for the client's data request. Requires the getDDS()
* method implemented by each server localization effort.
*
* <p>Once the DDS has been parsed, the data is read (using the class in the
* localized server factory etc.), compared to the constraint expression,
* and then sent to the client.
*
* @param request The client's <code> HttpServletRequest</code> request
* object.
* @param response The server's <code> HttpServletResponse</code> response
* object.
* @param dataSet Name of the datset whose data is requested.
* @param constraintExpression Constraint expression recieved from the client.
* This is used (if it's not just empty) subset the data in the dataset.
*/
public void doGetDODS(HttpServletRequest request,
HttpServletResponse response,
requestState rs)
throws IOException, ServletException {
if (Debug.isSet("showResponse"))
System.out.println("Sending DODS Data For: " + rs.getDataSet());
response.setContentType("application/octet-stream");
response.setHeader("XDODS-Server", getServerVersion() );
response.setHeader("Content-Description", "dods_data");
ServletOutputStream sOut = response.getOutputStream();
OutputStream bOut, eOut;
if (rs.getAcceptsCompressed()){
response.setHeader("Content-Encoding", "deflate");
bOut = new DeflaterOutputStream(sOut);
}
else {
// Commented out because of a bug in the DODS C++ stuff...
//response.setHeader("Content-Encoding", "plain");
bOut = new BufferedOutputStream(sOut);
}
GuardedDataset ds = null;
try {
ds = getDataset(rs);
// Utilize the getDDS() method to get a parsed and populated DDS
// for this server.
ServerDDS myDDS = ds.getDDS();
// Instantiate the CEEvaluator and parse the constraint expression
CEEvaluator ce = new CEEvaluator(myDDS);
ce.parseConstraint( rs.getConstraintExpression());
// debug
// System.out.println("CE DDS = ");
// myDDS.printConstrained(System.out);
// Send the constrained DDS back to the client
PrintWriter pw = new PrintWriter(new OutputStreamWriter(bOut));
myDDS.printConstrained(pw);
// Send the Data delimiter back to the client
//pw.println("Data:"); // JCARON CHANGED
pw.flush();
bOut.write("\nData:\n".getBytes()); // JCARON CHANGED
bOut.flush();
// Send the binary data back to the client
DataOutputStream sink = new DataOutputStream(bOut);
ce.send(myDDS.getName(), sink, ds);
sink.flush();
// Finish up tsending the compressed stuff, but don't
// close the stream (who knows what the Servlet may expect!)
if (rs.getAcceptsCompressed())
((DeflaterOutputStream)bOut).finish();
response.setStatus(response.SC_OK);
}
catch (DODSException de){
dodsExceptionHandler(de, response);
}
catch (ParseException pe) {
parseExceptionHandler(pe, response);
}
finally { // release lock if needed
if (ds != null) ds.release();
}
}
/***************************************************************************/
/***************************************************************************
* Default handler for the client's directory request.
*
* Returns an html document to the client showing (a possibly pseudo)
* listing of the datasets available on the server in a directory listing
* format.
* <p>
* The bulk of this code resides in the class dods.servlet.dodsDIR and
* documentation may be found there.
*
* @param request The client's <code> HttpServletRequest</code> request
* object.
* @param response The server's <code> HttpServletResponse</code> response
* object.
*
* @see dodsDIR
*/
public void doGetDIR(HttpServletRequest request,
HttpServletResponse response,
requestState rs)
throws IOException, ServletException {
response.setHeader("XDODS-Server", getServerVersion());
response.setContentType("text/html");
response.setHeader("Content-Description", "dods_directory");
try {
dodsDIR di = new dodsDIR();
di.sendDIR(request, response, rs);
response.setStatus(response.SC_OK);
}
catch (DODSException de){
dodsExceptionHandler(de,response);
}
catch (ParseException pe) {
parseExceptionHandler(pe,response);
}
return;
}
/***************************************************************************/
/***************************************************************************
* Default handler for the client's version request.
*
* <p>Returns a plain text document with server version and DODS core
* version #'s
*
* @param request The client's <code> HttpServletRequest</code> request
* object.
* @param response The server's <code> HttpServletResponse</code> response
* object.
*/
public void doGetVER(HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException {
if(Debug.isSet("showResponse"))
System.out.println("Sending Version Tag.");
response.setContentType("text/plain");
response.setHeader("XDODS-Server", getServerVersion() );
response.setHeader("Content-Description", "dods_version");
// Commented because of a bug in the DODS C++ stuff...
//response.setHeader("Content-Encoding", "plain");
PrintWriter pw = new PrintWriter(new OutputStreamWriter(response.getOutputStream()));
pw.println("Server Version: "+getServerVersion());
pw.flush();
response.setStatus(response.SC_OK);
}
/***************************************************************************/
/***************************************************************************
* Default handler for the client's help request.
*
* <p> Returns an html page of help info for the server
*
* @param request The client's <code> HttpServletRequest</code> request
* object.
* @param response The server's <code> HttpServletResponse</code> response
* object.
*/
public void doGetHELP(HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException {
if(Debug.isSet("showResponse"))
System.out.println("Sending Help Page.");
response.setContentType("text/html");
response.setHeader("XDODS-Server", getServerVersion() );
response.setHeader("Content-Description", "dods_help");
// Commented because of a bug in the DODS C++ stuff...
//response.setHeader("Content-Encoding", "plain");
PrintWriter pw = new PrintWriter(new OutputStreamWriter(response.getOutputStream()));
printHelpPage(pw);
pw.flush();
response.setStatus(response.SC_OK);
}
/***************************************************************************/
/***************************************************************************
* Sends an html document to the client explaining that they have used a
* poorly formed URL and then the help page...
*
* @param request The client's <code> HttpServletRequest</code> request
* object.
* @param response The server's <code> HttpServletResponse</code> response
* object.
*/
public void badURL(HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException {
if(Debug.isSet("showResponse"))
System.out.println("Sending Bad URL Page.");
response.setContentType("text/html");
response.setHeader("XDODS-Server", getServerVersion() );
response.setHeader("Content-Description", "BadURL");
// Commented because of a bug in the DODS C++ stuff...
//response.setHeader("Content-Encoding", "plain");
PrintWriter pw = new PrintWriter(new OutputStreamWriter(response.getOutputStream()));
printBadURLPage(pw);
printHelpPage(pw);
pw.flush();
response.setStatus(response.SC_OK);
}
/***************************************************************************/
/***************************************************************************
* Default handler for DODS ascii data requests. Returns the request data as
* a comma delimited ascii file. Note that this means that the more complex
* DODS structures such as Grids get flattened...
* <p>
* The bulk of this code resides in the class dods.servlet.dodsASCII and
* documentation may be found there.
*
* @param request The client's <code> HttpServletRequest</code> request
* object.
* @param response The server's <code> HttpServletResponse</code> response
* object.
*
* @see dodsASCII
*/
public void doGetASC(HttpServletRequest request,
HttpServletResponse response,
requestState rs)
throws IOException, ServletException {
if (Debug.isSet("showResponse"))
System.out.println("Sending ASC Data For: " + rs.getDataSet());
response.setHeader("XDODS-Server", getServerVersion());
response.setContentType("text/plain");
response.setHeader("Content-Description", "dods_ascii");
try {
dodsASCII di = new dodsASCII();
di.sendASCII(request, response, rs.getDataSet());
response.setStatus(response.SC_OK);
}
catch (DODSException de){
dodsExceptionHandler(de,response);
}
catch (ParseException pe) {
parseExceptionHandler(pe,response);
}
return;
}
/***************************************************************************/
/***************************************************************************
* Default handler for DODS info requests. Returns an html document
* describing the contents of the servers datasets.
* <p>
* The bulk of this code resides in the class dods.servlet.dodsINFO and
* documentation may be found there.
*
* @param request The client's <code> HttpServletRequest</code> request
* object.
* @param response The server's <code> HttpServletResponse</code> response
* object.
*
* @see dodsINFO
*/
public void doGetINFO(HttpServletRequest request,
HttpServletResponse response,
requestState rs)
throws IOException, ServletException {
PrintStream pw = new PrintStream(response.getOutputStream());
response.setHeader("XDODS-Server", getServerVersion());
response.setContentType("text/html");
response.setHeader("Content-Description", "dods_description");
GuardedDataset ds = null;
try {
ds = getDataset(rs);
dodsINFO di = new dodsINFO();
di.sendINFO(pw, ds, rs);
response.setStatus(response.SC_OK);
}
catch (DODSException de){
dodsExceptionHandler(de,response);
}
// catch (IOException pe) {
// anyExceptionHandler(pe,response, rs);
// }
catch (ParseException pe) {
parseExceptionHandler(pe,response);
}
finally { // release lock if needed
if (ds != null) ds.release();
}
return;
}
/**************************************************************************/
/***************************************************************************
* Default handler for DODS .html requests. Returns the DODS Web Interface
* (aka The Interface From Hell) to the client.
* <p>
* The bulk of this code resides in the class dods.servlet.dodsHTML and
* documentation may be found there.
*
* @param request The client's <code> HttpServletRequest</code> request
* object.
* @param response The server's <code> HttpServletResponse</code> response
* object.
*
* @see dodsHTML
*/
public void doGetHTML(HttpServletRequest request,
HttpServletResponse response,
requestState rs)
throws IOException, ServletException {
response.setHeader("XDODS-Server", getServerVersion());
response.setContentType("text/html");
response.setHeader("Content-Description", "dods_form");
GuardedDataset ds = null;
try {
ds = getDataset(rs);
// Utilize the getDDS() method to get a parsed and populated DDS
// for this server.
ServerDDS myDDS = (ServerDDS) ds.getDDS();
DAS das = ds.getDAS();
dodsHTML di = new dodsHTML();
di.sendDataRequestForm(request, response, rs.getDataSet(), myDDS, das);
response.setStatus(response.SC_OK);
}
catch (DODSException de){
dodsExceptionHandler(de,response);
}
catch (IOException pe) {
anyExceptionHandler(pe,response, rs);
}
catch (ParseException pe) {
parseExceptionHandler(pe,response);
}
finally { // release lock if needed
if (ds != null) ds.release();
}
return;
}
/***************************************************************************/
/***************************************************************************
* Default handler for DODS catalog.xml requests.
*
* @param request The client's <code> HttpServletRequest</code> request
* object.
* @param response The server's <code> HttpServletResponse</code> response
* object.
*
* @see dodsHTML
*/
public void doGetCatalog(HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException {
response.setHeader("XDODS-Server", getServerVersion());
response.setContentType("text/xml");
response.setHeader("Content-Description", "dods_catalog");
PrintWriter pw = new PrintWriter(response.getOutputStream());
printCatalog( pw);
pw.flush();
response.setStatus(response.SC_OK);
return;
}
// to be overridden by servers that implement catalogs
protected void printCatalog(PrintWriter os) throws IOException {
os.println("Catalog not available for this server");
os.println("Server version = "+getServerVersion());
}
/***************************************************************************/
/***************************************************************************
* Default handler for DODS status requests; not publically available,
* used only for debugging
*
* @param request The client's <code> HttpServletRequest</code> request
* object.
* @param response The server's <code> HttpServletResponse</code> response
* object.
*
* @see dodsHTML
*/
public void doGetStatus(HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException {
response.setHeader("XDODS-Server", getServerVersion());
response.setContentType("text/html");
response.setHeader("Content-Description", "dods_status");
PrintWriter pw = new PrintWriter(response.getOutputStream());
pw.println("<title>Server Status</title>");
pw.println("<body><ul>");
printStatus( pw);
pw.println("</ul></body>");
pw.flush();
response.setStatus(response.SC_OK);
return;
}
// to be overridden by servers that implement status report
protected void printStatus(PrintWriter os) throws IOException {
os.println("<h2>Server version = " + getServerVersion() + "</h2>");
os.println("<h2>Number of Requests Received = " + HitCounter + "</h2>");
if (track) {
int n = prArr.size();
int pending = 0;
String preqs = "";
for (int i=0; i<n; i++) {
requestState rs = (requestState) prArr.get(i);
RequestDebug reqD = (RequestDebug) rs.getUserObject();
if ((rs != null) && !reqD.done){
preqs += "<pre>-----------------------\n";
preqs += "Request[" + reqD.reqno + "](" + reqD.threadDesc + ") is pending.\n";
preqs += rs.toString();
preqs += "</pre>";
pending++;
}
}
os.println("<h2>" + pending + " Pending Request(s)</h2>");
os.println(preqs);
}
}
/***************************************************************************/
/***************************************************************************
* This is a bit of instrumentation that I kept around to let me look at the
* state of the incoming <code>HttpServletRequest</code> from the client.
* This method calls the <code>get*</code> methods of the request and prints
* the results to standard out.
*
* @param request The <code>HttpServletRequest</code> object to probe.
*/
public void probeRequest(HttpServletRequest request){
Enumeration e;
int i;
System.out.println("####################### PROBE ##################################");
System.out.println("The HttpServletRequest object is actually a: "+request.getClass().getName());
System.out.println("");
System.out.println("HttpServletRequest Interface:");
System.out.println(" getAuthType: "+request.getAuthType());
System.out.println(" getMethod: "+request.getMethod());
System.out.println(" getPathInfo: "+request.getPathInfo());
System.out.println(" getPathTranslated: "+request.getPathTranslated());
System.out.println(" getQueryString: "+request.getQueryString());
System.out.println(" getRemoteUser: "+request.getRemoteUser());
System.out.println(" getRequestedSessionId: "+request.getRequestedSessionId());
System.out.println(" getRequestURI: "+request.getRequestURI());
System.out.println(" getServletPath: "+request.getServletPath());
System.out.println(" isRequestedSessionIdFromCookie: "+request.isRequestedSessionIdFromCookie());
System.out.println(" isRequestedSessionIdValid: "+request.isRequestedSessionIdValid());
System.out.println(" isRequestedSessionIdFromURL: "+request.isRequestedSessionIdFromURL());
System.out.println("");
i = 0;
e = request.getHeaderNames();
System.out.println(" Header Names:");
while(e.hasMoreElements()){
i++;
String s = (String) e.nextElement();
System.out.print(" Header["+i+"]: "+s);
System.out.println(": "+request.getHeader(s));
}
System.out.println("");
System.out.println("ServletRequest Interface:");
System.out.println(" getCharacterEncoding: "+request.getCharacterEncoding());
System.out.println(" getContentType: "+request.getContentType());
System.out.println(" getContentLength: "+request.getContentLength());
System.out.println(" getProtocol: "+request.getProtocol());
System.out.println(" getScheme: "+request.getScheme());
System.out.println(" getServerName: "+request.getServerName());
System.out.println(" getServerPort: "+request.getServerPort());
System.out.println(" getRemoteAddr: "+request.getRemoteAddr());
System.out.println(" getRemoteHost: "+request.getRemoteHost());
//System.out.println(" getRealPath: "+request.getRealPath());
System.out.println(".............................");
System.out.println("");
i = 0;
e = request.getAttributeNames();
System.out.println(" Attribute Names:");
while(e.hasMoreElements()){
i++;
String s = (String) e.nextElement();
System.out.print(" Attribute["+i+"]: "+s);
System.out.println(" Type: "+request.getAttribute(s));
}
System.out.println(".............................");
System.out.println("");
i = 0;
e = request.getParameterNames();
System.out.println(" Parameter Names:");
while(e.hasMoreElements()){
i++;
String s = (String) e.nextElement();
System.out.print(" Parameter["+i+"]: "+s);
System.out.println(" Value: "+request.getParameter(s));
}
System.out.println("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -");
System.out.println(" . . . . . . . . . Servlet Infomation API . . . . . . . . . . . . . .");
System.out.println("");
System.out.println("Servlet Context:");
System.out.println("");
ServletContext scntxt = getServletContext();
i = 0;
e = scntxt.getAttributeNames();
System.out.println(" Attribute Names:");
while(e.hasMoreElements()){
i++;
String s = (String) e.nextElement();
System.out.print(" Attribute["+i+"]: "+s);
System.out.println(" Type: "+scntxt.getAttribute(s));
}
System.out.println(" ServletContext.getMajorVersion(): " + scntxt.getMajorVersion());
// System.out.println("ServletContext.getMimeType(): " + sc.getMimeType());
System.out.println(" ServletContext.getMinorVersion(): " + scntxt.getMinorVersion());
// System.out.println("ServletContext.getRealPath(): " + sc.getRealPath());
System.out.println(".............................");
System.out.println("Servlet Config:");
System.out.println("");
ServletConfig scnfg = getServletConfig();
i = 0;
e = scnfg.getInitParameterNames();
System.out.println(" InitParameters:");
while (e.hasMoreElements()) {
String p = (String) e.nextElement();
System.out.print(" InitParameter["+i+"]: "+p);
System.out.println(" Value: " + scnfg.getInitParameter(p));
i++;
}
System.out.println(".............................");
System.out.println("HttpUtils:");
System.out.println("");
System.out.println("getRequestURL: "+HttpUtils.getRequestURL(request));
System.out.println("");
System.out.println("######################## END PROBE ###############################");
System.out.println("");
}
/***************************************************************************/
/****************************************************************************
* This method is used to convert special characters into their
* actual byte values.
* <p>
* For example, in a URL the space character
* is represented as "%20" this method will replace that with a
* space charater. (a single value of 0x20)
*
* @param ce The constraint expresion string as collected from the request
* object with <code>getQueryString()</code>
*
* @returns A string containing the prepared constraint expression. If there
* is a problem with the constraint expression a <code>null</code> is returned.
*/
private String prepCE(String ce){
int index;
//System.out.println("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
//System.out.println("Prepping: \""+ce+"\"");
if (ce == null) {
ce = "";
//System.out.println("null Constraint expression.");
} else if(!ce.equals("")) {
//System.out.println("Searching for: %");
index = ce.indexOf("%");
//System.out.println("index of %: "+index);
if(index == -1)
return(ce);
if(index>(ce.length()-3))
return(null);
while(index>=0){
//System.out.println("Found % at character " + index);
String specChar = ce.substring(index+1,index+3);
//System.out.println("specChar: \"" + specChar + "\"");
// Convert that bad boy!
char val = (char) Byte.parseByte(specChar,16);
//System.out.println(" val: '" + val + "'");
//System.out.println("String.valueOf(val): \"" + String.valueOf(val) + "\"");
ce = ce.substring(0,index) + String.valueOf(val) + ce.substring(index+3,ce.length());
//System.out.println("ce: \"" + ce + "\"");
index = ce.indexOf("%");
if(index>(ce.length()-3))
return(null);
}
}
// char ca[] = ce.toCharArray();
// for(int i=0; i<ca.length ;i++)
// System.out.print("'"+(byte)ca[i]+"' ");
// System.out.println("");
// System.out.println(ce);
// System.out.println("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ");
// System.out.println("Returning CE: \""+ce+"\"");
return (ce);
}
/***************************************************************************/
/****************************************************************************
* Evaluates a request object to determine if the client that sent the request
* accepts compressed return documents.
*
* @param request The <code>HttpServletRequest</code> sent by the client
* in question.
*
* @return True is the client accpets a compressed return document.
* False otherwise.
*/
protected boolean isTheClientCompressed(HttpServletRequest request){
boolean isTiny;
isTiny = false;
String Encoding = request.getHeader("Accept-Encoding");
if(Encoding != null)
isTiny = Encoding.equalsIgnoreCase("deflate");
else
isTiny = false;
return(isTiny);
}
/***************************************************************************/
/****************************************************************************
* Processes an incoming <code>HttpServletRequest</code> and from it sets the
* cached values for:
* <ul>
* <li> <b>dataSet</b> The data set name.(Accessible using
* <code> setDataSet() </code>
* and <code>getDataSet()</code>)</li>
* <li> <b>CE</b> The constraint expression.(Accessible using
* <code> setCE() </code>
* and <code>getCE()</code>)</li>
* <li> <b>requestSuffix</b> The request suffix, used by DODS to indicate
* the type of response desired by the client.
* (Accessible using
* <code> setRequestSuffix() </code>
* and <code>getRequestSuffix()</code>)</li>
* </ul>
* @param request The <code>HttpServletRequest</code> sent by the client
* in question.
*
* @returns True if the URL wasn't junk, false otherwise.
*
* @see #getDataSet()
* @see #setDataSet(String)
* @see #getCE()
* @see #setCE(String)
* @see #getRequestSuffix()
* @see #setRequestSuffix(String)
*/
protected requestState processDodsURL(HttpServletRequest request){
// Get the constraint expression from the request object and
// convert all those special characters denoted by a % sign
String CE = prepCE(request.getQueryString());
// If there was simply no constraint then prepCE() should have returned
// a CE equal "", the empty string. A null return indicates an error.
if (CE == null){
return null;
}
// Figure out the data set name.
String ds = request.getPathInfo();
String suffix = null;
if (ds != null) {
// Break the path up and find the last (terminal)
// end.
StringTokenizer st = new StringTokenizer(ds,"/");
String endOPath = "";
while(st.hasMoreTokens()){
endOPath = st.nextToken();
}
// Check the last element in the path for the
// character "."
int index = endOPath.lastIndexOf('.');
//System.out.println("last index of . in \""+ds+"\": "+index);
// If a dot is found take the stuff after it as the DODS suffix
if(index >= 0) {
// pluck the DODS suffix off of the end
suffix = endOPath.substring(index+1);
// Set the data set name to the entire path minus the
// suffix which we know exists in the last element
// of the path.
ds = ds.substring(1,ds.lastIndexOf('.'));
}
else { // strip the leading slash (/) from the dataset name and set the suffix to an empty string
suffix = "";
ds = ds.substring(1,ds.length());
}
}
return new requestState( ds,
suffix,
CE,
isTheClientCompressed(request),
getServletConfig(),
getServerName()
);
}
/***************************************************************************
*
* In this (default) implementation of the getServerName() method we just get
* the name of the servlet and pass it back. If something different is
* required, override this method when implementing the getDDS() and
* getServerVersion() methods.
* <p>
* This is typically used by the getINFO() method to figure out if there is
* information specific to this server residing in the info directory that
* needs to be returned to the client as part of the .info response.
*
* @return A string containing the name of the servlet class that is running.
*/
public String getServerName(){
// Ascertain the name of this server.
String servletName = this.getClass().getName();
return(servletName);
}
/**************************************************************************
* Handles incoming requests from clients. Parses the request and determines
* what kind of DODS response the cleint is requesting. If the request is
* understood, then the appropriate handler method is called, otherwise
* an error is returned to the client.
* <p>
* This method is the entry point for <code>DODSServlet</code>. It uses
* the methods <code>processDodsURL</code> to extract the DODS URL
* information from the incoming client request. This DODS URL information
* is cached and made accessible through get and set methods.
* <p>
* After <code>processDodsURL</code> is called <code>loadIniFile()</code>
* is called to load configuration information from a .ini file,
* <p>
* If the standard behaviour of the servlet (extracting the DODS URL
* information from the client request, or loading the .ini file) then
* you should overload <code>processDodsURL</code> and <code>loadIniFile()
* </code>. <b> We don't recommend overloading <code>doGet()</code> beacuse
* the logic contained there may change in our core and cause your server
* to behave unpredictably when future releases are installed.</b>
*
* @param request The client's <code> HttpServletRequest</code> request
* object.
* @param response The server's <code> HttpServletResponse</code> response
* object.
*
* @see #processDodsURL(HttpServletRequest)
* @see #getDataSet()
* @see #setDataSet(String)
* @see #getCE()
* @see #setCE(String)
* @see #getRequestSuffix()
* @see #setRequestSuffix(String)
* @see #loadIniFile()
*/
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException {
// response.setHeader("Last-Modified", (new Date()).toString() );
requestState rs = null;
RequestDebug reqD = null;
try {
if ( Debug.isSet("probeRequest")) probeRequest(request);
rs = processDodsURL(request);
synchronized(syncLock) {
long reqno = HitCounter++;
if (track) {
reqD = new RequestDebug(reqno, Thread.currentThread().toString());
rs.setUserObject( reqD);
if (prArr == null) prArr= new ArrayList( 10000);
prArr.add((int)reqno, rs);
}
if ( Debug.isSet("showRequest")) {
System.out.println("-------------------------------------------");
System.out.println("Server: "+getServerName()+" Request #"+ reqno);
System.out.println(rs.toString());
}
}
if(rs != null){
String dataSet = rs.getDataSet();
String requestSuffix = rs.getRequestSuffix();
if(dataSet == null) {
doGetDIR(request, response, rs);
}
else if (dataSet.equals("/")) {
doGetDIR(request, response, rs);
}
else if (dataSet.equals("")) {
doGetDIR(request, response, rs);
}
else if (dataSet.equalsIgnoreCase("/version") || dataSet.equalsIgnoreCase("/version/")){
doGetVER(request, response);
}
else if(dataSet.equalsIgnoreCase("/help") || dataSet.equalsIgnoreCase("/help/")) {
doGetHELP(request, response);
}
else if ( dataSet.equalsIgnoreCase("/"+requestSuffix)) {
doGetHELP(request, response);
}
else if ( requestSuffix.equalsIgnoreCase("dds")) {
doGetDDS(request, response, rs);
}
else if ( requestSuffix.equalsIgnoreCase("das")) {
doGetDAS(request, response, rs);
}
else if ( requestSuffix.equalsIgnoreCase("dods")) {
doGetDODS(request, response, rs);
}
else if ( requestSuffix.equalsIgnoreCase("asc") ||
requestSuffix.equalsIgnoreCase("ascii")) {
doGetASC(request, response, rs);
}
else if ( requestSuffix.equalsIgnoreCase("info")) {
doGetINFO(request, response, rs);
}
else if ( requestSuffix.equalsIgnoreCase("html") || requestSuffix.equalsIgnoreCase("htm")) {
doGetHTML(request, response, rs);
}
else if ( requestSuffix.equalsIgnoreCase("ver") || requestSuffix.equalsIgnoreCase("version")){
doGetVER(request, response);
}
else if ( requestSuffix.equalsIgnoreCase("help")) {
doGetHELP(request, response);
}
// JC added
else if ( requestSuffix.equalsIgnoreCase("xml") && dataSet.equalsIgnoreCase("catalog")){
doGetCatalog(request, response);
}
else if ( dataSet.equalsIgnoreCase("status")){
doGetStatus(request, response);
}
else if ( requestSuffix.equals("")) {
badURL(request, response);
}
else {
badURL(request, response);
}
}
else {
badURL(request, response);
}
if (reqD != null) reqD.done = true;
}
catch (Throwable e) {
anyExceptionHandler(e, response, rs);
}
}
//**************************************************************************
/***************************************************************************
* Prints the DODS Server help page to the passed PrintWriter
*
* @param pw PrintWriter stream to which to dump the help page.
*/
private void printHelpPage(PrintWriter pw) {
pw.println("<h3>DODS Server Help</h3>");
pw.println("To access most of the features of this DODS server, append");
pw.println("one of the following a five suffixes to a URL: .das, .dds, .dods., .info,");
pw.println(".ver or .help. Using these suffixes, you can ask this server for:<dl>");
pw.println("<dt> das <dd> attribute object");
pw.println("<dt> dds <dd> data type object");
pw.println("<dt> dods <dd> data object");
pw.println("<dt> info <dd> info object (attributes, types and other information)");
pw.println("<dt> html <dd> html form for this dataset");
pw.println("<dt> ver <dd> return the version number of the server");
pw.println("<dt> help <dd> help information (this text)</dl>");
pw.println("</dl>");
pw.println("For example, to request the DAS object from the FNOC1 dataset at URI/GSO (a");
pw.println("test dataset) you would appand `.das' to the URL:");
pw.println("http://dods.gso.uri.edu/cgi-bin/nph-nc/data/fnoc1.nc.das.");
pw.println("<p><b>Note</b>: Many DODS clients supply these extensions for you so you don't");
pw.println("need to append them (for example when using interfaces supplied by us or");
pw.println("software re-linked with a DODS client-library). Generally, you only need to");
pw.println("add these if you are typing a URL directly into a WWW browser.");
pw.println("<p><b>Note</b>: If you would like version information for this server but");
pw.println("don't know a specific data file or data set name, use `/version' for the");
pw.println("filename. For example: http://dods.gso.uri.edu/cgi-bin/nph-nc/version will");
pw.println("return the version number for the netCDF server used in the first example. ");
pw.println("<p><b>Suggestion</b>: If you're typing this URL into a WWW browser and");
pw.println("would like information about the dataset, use the `.info' extension.");
pw.println("<p>If you'd like to see a data values, use the `.html' extension and submit a");
pw.println("query using the customized form.");
}
//**************************************************************************
/***************************************************************************
* Prints the Bad URL Page page to the passed PrintWriter
*
* @param pw PrintWriter stream to which to dump the bad URL page.
*/
private void printBadURLPage(PrintWriter pw) {
pw.println("<h3>Error in URL</h3>");
pw.println("The URL extension did not match any that are known by this");
pw.println("server. Below is a list of the five extensions that are be recognized by");
pw.println("all DODS servers. If you think that the server is broken (that the URL you");
pw.println("submitted should have worked), then please contact the");
pw.println("DODS user support coordinator at: support@unidata.ucar.edu<p>");
}
//**************************************************************************
// debug
private ArrayList prArr = null;
private class RequestDebug {
long reqno;
String threadDesc;
boolean done = false;
RequestDebug( long reqno, String threadDesc) {
this.reqno = reqno;
this.threadDesc = threadDesc;
}
}
}