import java.util.logging.Logger; import java.security.Principal; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.ServletOutputStream; import java.io.OutputStreamWriter; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; /* for streaming the cutout-file */ import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.util.Arrays; import java.util.List; import java.util.LinkedList; import java.util.Map; import java.util.HashMap; import java.util.Properties; // for Logging/Accounting import org.json.simple.JSONObject; import java.text.SimpleDateFormat; import java.util.Date; import java.util.TimeZone; import java.nio.file.StandardOpenOption; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import vo.parameter.*; public class ServletCutout extends javax.servlet.http.HttpServlet { protected static final Logger LOGGER = Logger.getLogger(ServletCutout.class.getName()); protected static final Settings settings = Settings.getInstance(); final String RESPONSE_ENCODING = "utf-8"; final String DEFAULT_RESPONSEFORMAT = settings.defaults.responseFormat; final String DEFAULT_SKY_SYSTEM = settings.defaults.skySystem; final String DEFAULT_SPEC_SYSTEM = settings.defaults.specSystem; final String DEFAULT_TIME_SYSTEM = "MJD_UTC"; // FIXME take from confif file boolean showDuration = settings.defaults.showDuration; long startTime_msec; protected Cutout cutout = new CutoutImpl(settings); private Subsurvey[] subsurveys = null; public void init() throws ServletException { super.init(); LOGGER.info("AMQP : " + settings.amqpConn.toString()); LOGGER.info("FITS : " + settings.fitsPaths.toString()); String surveysAbsPathname = settings.fitsPaths.surveysMetadataAbsPathname(); if( (surveysAbsPathname != null) && (surveysAbsPathname.length() > 1) ) subsurveys = Subsurvey.loadSubsurveys(surveysAbsPathname); } protected void doSodaDescriptor(PrintWriter writer, String requestUrl) { String theDescriptor = "<VOTABLE xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"http://www.ivoa.net/xml/VOTable/v1.3\" version=\"1.3\">" + "<RESOURCE type=\"meta\" utype=\"adhoc:service\" name=\"this\">" + "<PARAM name=\"standardID\" datatype=\"char\" arraysize=\"*\" value=\"ivo://ivoa.net/std/SODA#sync-1.0\"/>" + "<PARAM name=\"accessURL\" datatype=\"char\" arraysize=\"*\" value=\"" + requestUrl + "\"/>" + "<GROUP name=\"inputParams\">" + "<PARAM name=\"ID\" ucd=\"meta.id;meta.dataset\" datatype=\"char\" arraysize=\"*\" value=\"\"/>" + "<PARAM name=\"POS\" ucd=\"pos.outline;obs\" datatype=\"char\" arraysize=\"*\" value=\"\"/>" + "<PARAM name=\"CIRCLE\" ucd=\"phys.angArea;obs\" unit=\"deg\" datatype=\"double\" arraysize=\"3\" xtype=\"circle\" value=\"\"/>" + "<PARAM name=\"POLYGON\" unit=\"deg\" ucd=\"pos.outline;obs\" datatype=\"double\" arraysize=\"*\" xtype=\"polygon\" value=\"\"/>" + "<PARAM name=\"BAND\" ucd=\"stat.interval\" unit=\"m\" datatype=\"double\" arraysize=\"2\" xtype=\"interval\" value=\"\"/>" + "<PARAM name=\"TIME\" ucd=\"time.interval;obs.exposure\" unit=\"d\" datatype=\"double\" arraysize=\"2\" xtype=\"interval\" value=\"\"/>" + "<PARAM name=\"POL\" ucd=\"meta.code;phys.polarization\" datatype=\"char\" arraysize=\"*\" value=\"\"/>" + "<PARAM name=\"PIXELS\" ucd=\"instr.pixel;meta.dataset\" datatype=\"char\" arraysize=\"*\" value=\"\"/>" + "<PARAM name=\"RESPONSEFORMAT\" ucd=\"meta.code.mime\" datatype=\"char\" arraysize=\"*\" value=\"application/fits\"/>" + "<PARAM name=\"POSSYS\" ucd=\"pos.frame\" datatype=\"char\" arraysize=\"*\" value=\"\">" + "<DESCRIPTION>Coordinate system for POS values</DESCRIPTION>" + "<VALUES>" + "<OPTION>ICRS</OPTION>" + "<OPTION>GALACTIC</OPTION>" + "</VALUES>" + "</PARAM>" + "<PARAM name=\"BANDSYS\" ucd=\"spect;pos.frame\" datatype=\"char\" arraysize=\"*\" value=\"\">" + "<DESCRIPTION>Coordinate system for BAND vlaues.</DESCRIPTION>" + "<VALUES>" + "<OPTION>WAVE_Barycentric</OPTION>" + "<OPTION>VELO_LSRK</OPTION>" + "</VALUES>" + "</PARAM>" + "</GROUP>" + "</RESOURCE>" + "</VOTABLE>"; writer.println(theDescriptor); } protected void doCutoutStream(String id, Pos pos, Band band, Time time, Pol pol, String pixels, OutputStream respOutputStream) throws IOException, InterruptedException { LOGGER.info("trace" + pos); Resolver rsl = new ResolverFromId(); rsl.resolve(id); cutout.doStream(rsl.relPathname(), rsl.hdunum(), pos, band, time, pol, pixels, respOutputStream); } protected DataLink doCutoutFile(String id, Pos pos, Band band, Time time, Pol pol, String pixels, boolean countNullValues, String respFormat) throws IOException, InterruptedException { LOGGER.info("trace"); String relPathname; int hdunum; String dbUri = settings.dbConn.uri(); if(settings.dbConn.isDbUriEmpty()) { Resolver rsl = new ResolverFromId(); rsl.resolve(id); relPathname = rsl.relPathname(); hdunum = rsl.hdunum(); } else { ResolverByObsCore rsl = new ResolverByObsCore(settings.dbConn, subsurveys); rsl.resolve(id); relPathname = rsl.relPathname(); hdunum = rsl.hdunum(); String subsurveyId = rsl.obsCollection(); FitsCard[] extraCards = null; if(subsurveyId != null) { extraCards = Subsurvey.subsurveysFindCards(subsurveys, subsurveyId); } else { LOGGER.info("Resolver with Obscore returns subsurveyId null: no extraCards loaded."); } // FIXME use of extraCards not implemented } final String DEFAULT_TIME_SYSTEM = "MJD_UTC"; // FIXME take from confif file //if(pos != null) pos.setSystem(DEFAULT_SKY_SYSTEM); //if(band != null) band.setSystem(DEFAULT_SPEC_SYSTEM); //if(time != null) time.setSystem(DEFAULT_TIME_SYSTEM); CutResult cutResult = cutout.doFile(relPathname, hdunum, pos, band, time, pol, pixels, false, null); DataLink dlk = new DataLink(settings, cutResult, id, pos, band, time, pol, countNullValues); return dlk; } protected void doMultiValuedParamNotSupported(String message, PrintWriter printWriter) { printWriter.println("MultiValuedParamNotSupported : " + message); } protected void doUsageError(String message, PrintWriter printWriter) { printWriter.println("UsageError : " + message); } protected void doError(String message, PrintWriter printWriter) { printWriter.println("Error : " + message); } /* HTTP/J2EE -> SODA */ /* DALI allows GET and POST for sync services */ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException, UnsupportedEncodingException { startTime_msec = System.currentTimeMillis(); final boolean NO_QUERY_STRING = (request.getQueryString() == null); if(NO_QUERY_STRING) { writeSodaDescriptor(request, response); LOGGER.info("normal exit with SODA service descriptor"); return; } else { convertHttpToSoda(request, response); LOGGER.info("normal exit"); } } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException, UnsupportedEncodingException { startTime_msec = System.currentTimeMillis(); final boolean NO_QUERY_STRING = (request.getQueryString() == null); if(NO_QUERY_STRING) { writeSodaDescriptor(request, response); LOGGER.info("normal exit with SODA service descriptor"); return; } else { convertHttpToSoda(request, response); LOGGER.info("normal exit"); } } protected void writeSodaDescriptor(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException, UnsupportedEncodingException { PrintWriter writer = new PrintWriter(new OutputStreamWriter(response.getOutputStream(), RESPONSE_ENCODING)); response.setContentType("text/xml"); doSodaDescriptor(writer, request.getRequestURL().toString()); writer.close(); } protected void convertHttpToSoda(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException, UnsupportedEncodingException { ServletOutputStream respOutputStream = response.getOutputStream(); try { Map<String, String[]> params = request.getParameterMap(); String id = SingleStringParam.parseSingleStringParam(params, "ID"); Pos pos = Pos.parsePos(params, DEFAULT_SKY_SYSTEM); Band band = Band.parseBand(params, DEFAULT_SPEC_SYSTEM); Time time = Time.parseTime(params, DEFAULT_TIME_SYSTEM); Pol pol = Pol.parsePol(params); String pixels = SingleStringParam.parseSingleStringParam(params, "PIXELS"); String respFormat = sodaReq_getResponseFormat(request, DEFAULT_RESPONSEFORMAT); LOGGER.info("responseFormat: " + respFormat); if(respFormat.startsWith("application/fits")) { response.setContentType(respFormat); doCutoutStream(id, pos, band, time, pol, pixels, respOutputStream); } else if(respFormat.startsWith("application/x-vlkb+xml")) { boolean countNullValues = vlkbReq_getNullValues(request); response.setContentType(respFormat); DataLink respDataLink = doCutoutFile(id, pos, band, time, pol, pixels, countNullValues, respFormat); /* FIXME errors from engine not checked - cut-file might not have been created */ LOGGER.info("DataLink - id:" + respDataLink.id + " url: " + respDataLink.accessUrl ); final String respEncoding = "utf-8"; PrintWriter writer = new PrintWriter(new OutputStreamWriter(respOutputStream, RESPONSE_ENCODING)); XmlSerializer.serializeToLegacyCutResults(writer, respEncoding, respDataLink, showDuration, startTime_msec); writer.close(); /* NOTE must close to force flush to complete the xml */ } else { throw new IllegalArgumentException("Unsupported RESPONSEFORMAT value : " + respFormat); } } catch(MultiValuedParamNotSupported ex) { LOGGER.info("MultiValuedParamNotSupported: " + ex.getMessage()); response.setStatus(HttpServletResponse.SC_BAD_REQUEST); response.setContentType("text/plain"); PrintWriter writer = new PrintWriter(new OutputStreamWriter(respOutputStream, RESPONSE_ENCODING)); doMultiValuedParamNotSupported(ex.getMessage(), writer); writer.close(); } catch(IllegalArgumentException ex) { LOGGER.info("IllegalArgumentException: " + ex.getMessage()); response.setStatus(HttpServletResponse.SC_BAD_REQUEST); response.setContentType("text/plain"); PrintWriter writer = new PrintWriter(new OutputStreamWriter(respOutputStream, RESPONSE_ENCODING)); doUsageError(ex.getMessage(), writer); writer.close(); } catch(Exception ex) { LOGGER.info("Exception: " + ex.getMessage()); ex.printStackTrace(); response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); response.setContentType("text/plain"); PrintWriter writer = new PrintWriter(new OutputStreamWriter(respOutputStream, RESPONSE_ENCODING)); doError(ex.toString(), writer); writer.close(); } finally { respOutputStream.close(); } } /* SODA */ /* return null if value not present or the value if present exactly once * else throw MultiplicityNotSupoorted SODA_error */ private String soda_getSingleValue(HttpServletRequest req, String name) { String[] valArr = req.getParameterValues(name); if(valArr == null) return null; else if(valArr.length == 0) return null; else if(valArr.length == 1) return valArr[0]; else throw new IllegalArgumentException( "MultiValuedParamNotSupported: " + name + " was found " + valArr.length + " times"); } private String sodaReq_getResponseFormat(HttpServletRequest req, String defaultResponseFormat) { String respFormat = soda_getSingleValue(req, "RESPONSEFORMAT"); return ((respFormat == null) ? defaultResponseFormat : respFormat); } private boolean vlkbReq_getNullValues(HttpServletRequest request) { return (null != soda_getSingleValue(request, "nullvals")); } } /* from SODA (upon error): Error codes are specified in DALI. Error documents should be text using the text/plain content-type and the text must begin with one of the following strings: Error CodeDescription --------------------------------------- Error: General error (not covered below) AuthenticationError: Not authenticated AuthorizationError: Not authorized to access the resource ServiceUnavailable: Transient error (could succeed with retry) UsageError: Permanent error (retry pointless) MultiValuedParamNotSupported: request included multiple values for a parameter but the service only supports a single value */ /* from DALI (upon successful request): The service should set HTTP headers (Fielding and Gettys et al., 1999) that are useful to the correct values where possible. Recommended headers to set when possible: Content-Type Content-Encoding Content-Length -- not in SPDA-stream impossible to know Last-Modified -- not in SODA-stream impossible to know */