package org.autoplot.servlet; import org.autoplot.RenderType; import org.autoplot.ApplicationModel; import java.awt.Color; import java.awt.Font; import java.awt.Graphics2D; import java.awt.Image; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.util.logging.Logger; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URI; import java.net.URISyntaxException; import java.nio.file.Files; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.Enumeration; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.TimeZone; import java.util.logging.FileHandler; import java.util.logging.Handler; import java.util.logging.Level; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.io.output.TeeOutputStream; import org.das2.datum.DatumRange; import org.das2.datum.DatumRangeUtil; import org.das2.datum.TimeUtil; import org.das2.datum.Units; import org.das2.datum.UnitsUtil; import org.das2.graph.DasCanvas; import org.das2.graph.Painter; import org.das2.graph.Renderer; import org.das2.system.DasLogger; import org.das2.util.AboutUtil; import org.das2.util.FileUtil; import org.das2.util.TimerConsoleFormatter; import org.das2.util.awt.GraphicsOutput; import org.das2.util.monitor.NullProgressMonitor; import org.autoplot.dom.Application; import org.autoplot.dom.Axis; import org.autoplot.dom.DataSourceFilter; import org.autoplot.dom.Plot; import org.autoplot.dom.PlotElement; import org.autoplot.state.StatePersistence; import org.das2.qds.QDataSet; import org.autoplot.datasource.DataSetSelectorSupport; import org.autoplot.datasource.DataSetURI; import org.autoplot.datasource.DataSource; import org.autoplot.datasource.DataSourceFactory; import org.autoplot.datasource.FileSystemUtil; import org.autoplot.datasource.URISplit; import org.autoplot.datasource.capability.TimeSeriesBrowse; import org.autoplot.datasource.jython.JythonDataSourceFactory; import org.das2.qds.ops.Ops; import org.das2.util.TimingConsoleFormatter; /** * SimpleServlet produces PNG,PDF, and SVG products for * .vap files and Autoplot URIs. A simple set of controls is provided * to tweak layout when automatic settings are not satisfactory. * * If the URI is not whitelisted, then it will be logged. If the * URI or VAP is blacklisted, then it will throw an exception. * * Some known instances: * * @author jbf */ public class SimpleServlet extends HttpServlet { private static final Logger logger= Logger.getLogger("autoplot.servlet" ); static FileHandler handler; private static void addHandlers(long requestId) { try { FileHandler h = new FileHandler("/tmp/apservlet/log" + requestId + ".txt"); TimerConsoleFormatter form = new TimerConsoleFormatter(); form.setResetMessage("getImage"); h.setFormatter(form); h.setLevel(Level.ALL); DasLogger.addHandlerToAll(h); if (handler != null) handler.close(); handler = h; } catch (IOException | SecurityException ex) { logger.log(Level.SEVERE, ex.getMessage(), ex); } } /** * return true if the .vap file contains any references to local resources. * This checks .jyds references to see that they also do not contain local * references. * @param vap vap file * @return true if the file has local references. */ private static boolean vapHasLocalReferences( File vap ) { try { Application app= (Application) StatePersistence.restoreState(vap); DataSourceFilter[] dsfs= app.getDataSourceFilters(); for (DataSourceFilter dsf : dsfs) { URI uri = DataSetURI.toUri(dsf.getUri()); if (FileSystemUtil.isLocalResource(dsf.getUri())) { logger.log( Level.FINE, "vap contains local reference: {0}", uri ); return true; } else { try { DataSourceFactory dssf= DataSetURI.getDataSourceFactory( uri, new NullProgressMonitor() ); if ( dssf instanceof JythonDataSourceFactory ) { if ( JythonDataSourceFactory.jydsHasLocalReferences(uri) ) { return true; } } } catch (IllegalArgumentException | URISyntaxException ex) { Logger.getLogger(SimpleServlet.class.getName()).log(Level.SEVERE, null, ex); } } } Plot[] plots= app.getPlots(); for ( int i=0; i 0) { target.write(buf, 0, length); } } /** * Processes requests for both HTTP GET and POST methods. * @param request servlet request * @param response servlet response * @throws ServletException * @throws IOException */ protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { File cacheFile= null; File metaCacheFile= null; File logFile= null; // logging information from when file was created. FileInputStream fin= null; String qs= request.getQueryString(); String cacheControl= request.getHeader("Cache-Control"); synchronized ( this ) { if ( ServletInfo.isCaching() && qs!=null ) { String format = ServletUtil.getStringParameter(request, "format", "image/png"); if ( format.equals("image/png") ) { String hash= request.getQueryString(); File s= ServletInfo.getCacheDirectory(); hash= String.format( "%04d", Math.abs( hash.hashCode() % 10000 ) ); cacheFile= new File( s, hash + ".png" ); metaCacheFile= new File( s, hash + ".txt" ); logFile= new File( s, hash+".log" ); if ( cacheFile.exists() && !"no-cache".equals(cacheControl) ) { byte[] bb= Files.readAllBytes(metaCacheFile.toPath()); String qs0= new String( bb ); if ( qs0.equals(qs) ) { cacheFile.setLastModified( new Date().getTime() ); String host= java.net.InetAddress.getLocalHost().getCanonicalHostName(); response.setHeader( "X-Served-By", host ); response.setHeader( "X-Server-Version", ServletInfo.version ); response.setHeader( "X-Autoplot-cache", "yep" ); response.setHeader( "X-Autoplot-cache-filename", cacheFile.getName() ); fin= new FileInputStream( cacheFile ); } else { logger.finer( "cache slot occupied by another image, overwriting."); } } } } else { File s= ServletInfo.getCacheDirectory(); logFile= new File( s, "request.log" ); } } if ( fin!=null ) { try ( OutputStream outs= response.getOutputStream() ) { copyStream( fin, outs ); } fin.close(); return; } if ( logFile==null ) { throw new NullPointerException("logfile should be set"); } Handler h= new FileHandler(String.valueOf(logFile)); h.setFormatter( new TimingConsoleFormatter() ); logger.addHandler(h); logger.setLevel( Level.ALL ); h.setLevel( Level.ALL ); //logger.setLevel(Level.FINE); logger.finer(ServletInfo.version); logger.fine("======================="); long t0 = System.currentTimeMillis(); String suniq = request.getParameter("requestId"); long uniq; if (suniq == null) { uniq = (long) (Math.random() * 100); } else { uniq = Long.parseLong(suniq); addHandlers(uniq); } String debug = request.getParameter("debug"); if ( debug==null ) debug= "false"; //debug= "TRUE"; logit("-- new request " + uniq + " --", t0, uniq, debug); try { String suri = request.getParameter("uri"); if ( suri==null ) { suri = request.getParameter("url"); } if ( suri!=null && suri.startsWith("vap ") ) { // pluses are interpretted as spaces when they are parameters in URLs. These should have been escaped, but legacy operations require we handle this. suri= suri.replaceAll(" ","+"); } String id= request.getParameter("id"); // lookup local URIs in table id.txt String process = ServletUtil.getStringParameter(request, "process", ""); String vap = request.getParameter("vap"); int width = ServletUtil.getIntParameter(request, "width", -1); int height = ServletUtil.getIntParameter(request, "height", -1); String scanvasAspect = ServletUtil.getStringParameter(request, "canvas.aspect", ""); String format = ServletUtil.getStringParameter(request, "format", "image/png"); String font = ServletUtil.getStringParameter(request, "font", ""); String column = ServletUtil.getStringParameter(request, "column", ""); String row = ServletUtil.getStringParameter(request, "row", ""); String srenderType = ServletUtil.getStringParameter(request, "renderType", ""); String ssymbolSize = ServletUtil.getStringParameter(request, "symbolSize", ""); String stimeRange = ServletUtil.getStringParameter(request, "timeRange", ""); if ( stimeRange.length()==0 ) stimeRange= ServletUtil.getStringParameter(request, "timerange", ""); String scolor = ServletUtil.getStringParameter(request, "color", ""); String sfillColor = ServletUtil.getStringParameter(request, "fillColor", ""); String sforegroundColor = ServletUtil.getStringParameter(request, "foregroundColor", ""); String sbackgroundColor = ServletUtil.getStringParameter(request, "backgroundColor", ""); String title = ServletUtil.getStringParameter(request, "plot.title", ""); String xlabel = ServletUtil.getStringParameter(request, "plot.xaxis.label", ""); String xrange = ServletUtil.getStringParameter(request, "plot.xaxis.range", ""); String xlog = ServletUtil.getStringParameter(request, "plot.xaxis.log", ""); String xdrawTickLabels = ServletUtil.getStringParameter(request, "plot.xaxis.drawTickLabels", ""); String ylabel = ServletUtil.getStringParameter(request, "plot.yaxis.label", ""); String yrange = ServletUtil.getStringParameter(request, "plot.yaxis.range", ""); String ylog = ServletUtil.getStringParameter(request, "plot.yaxis.log", ""); String ydrawTickLabels = ServletUtil.getStringParameter(request, "plot.yaxis.drawTickLabels", ""); String zlabel = ServletUtil.getStringParameter(request, "plot.zaxis.label", ""); String zrange = ServletUtil.getStringParameter(request, "plot.zaxis.range", ""); String zlog = ServletUtil.getStringParameter(request, "plot.zaxis.log", ""); String zdrawTickLabels = ServletUtil.getStringParameter(request, "plot.zaxis.drawTickLabels", ""); String grid= ServletUtil.getStringParameter( request, "drawGrid", "" ); String stamp= ServletUtil.getStringParameter( request, "stamp", "false" ); // print a stamp for debugging. If not false, the value is printed in blue along with a timestamp. for (Enumeration en = request.getParameterNames(); en.hasMoreElements();) { String n = (String) en.nextElement(); logger.log( Level.FINE, "{0}: {1}", new Object[]{n, Arrays.asList(request.getParameterValues(n))}); } if (srenderType.equals("fill_to_zero")) { srenderType = "fillToZero"; } if ( suri!=null ) logger.log(Level.FINE, "suri={0}", suri); if ( vap!=null ) logger.log(Level.FINE, "vap={0}", vap); if ( id!=null ) logger.log(Level.FINE, "id={0}", id); // allow URI=vapfile if ( vap==null && suri!=null ) { if ( suri.contains(".vap") || suri.contains(".vap?") ) { vap= suri; suri= null; } } // To support load balancing, insert the actual host that resolved the request String host= java.net.InetAddress.getLocalHost().getCanonicalHostName(); response.setHeader( "X-Served-By", host ); response.setHeader("X-Server-Version", ServletInfo.version); if ( suri!=null ) { response.setHeader( "X-Autoplot-URI", suri ); } if ( id!=null ) { response.setHeader( "X-Autoplot-ID", id ); } // id lookups. The file id.txt is a flat file with hash comments, // with each record containing a regular expression with groups, // then a map with group ids. if ( id!=null ) { suri= null; Map ids= ServletUtil.getIdMap(); for ( Entry e : ids.entrySet() ) { Pattern p= Pattern.compile(e.getKey()); Matcher m= p.matcher(id); if ( m.matches() ) { suri= e.getValue(); for ( int i=1; i"+request.getRequestURI()+"?"+request.getQueryString()).getBytes()); } return; } else if (suri.equals("about:plugins")) { try (OutputStream out = response.getOutputStream()) { response.setContentType("text/html"); out.write(DataSetSelectorSupport.getPluginsText().getBytes()); } return; } else if (suri.equals("about:autoplot")) { try (OutputStream out = response.getOutputStream()) { response.setContentType("text/html"); String s = AboutUtil.getAboutHtml(); s = s.substring(0, s.length() - 7); s = s + "
"; s = s + "hapiServerCache="+ System.getProperty( "hapiServerCache" ) + "
"; s = s + "cdawebHttps=" + System.getProperty( "cdawebHttps" ) + "
"; s = s + "enableReferenceCache=" + System.getProperty( "enableReferenceCache" ) + "
"; s = s + "

servlet version="+ServletInfo.version+"
"; s = s + ""; out.write(s.getBytes()); } return; } else { response.setContentType(format); } logit("surl: "+suri,t0, uniq, debug ); logit("get parameters", t0, uniq, debug); System.setProperty("java.awt.headless", "true"); ApplicationModel appmodel = new ApplicationModel(); appmodel.addDasPeersToAppAndWait(); logit("create application model", t0, uniq, debug); Application dom = appmodel.getDocumentModel(); if ("true".equals(request.getParameter("autolayout"))) { dom.getOptions().setAutolayout(true); } else { dom.getOptions().setAutolayout(false); if (!row.equals("")) { dom.getController().getCanvas().getController().setRow(row); } else { //dom.getController().getCanvas().getController().setRow(row); } if (!column.equals("")) { dom.getController().getCanvas().getController().setColumn(column); } else { dom.getController().getCanvas().getController().setColumn("10em,100%-10em"); } dom.getCanvases(0).getRows(0).setTop("0%"); dom.getCanvases(0).getRows(0).setBottom("100%"); } if (!font.equals("")) appmodel.getCanvas().setBaseFont(Font.decode(font)); // do dimensions if ("".equals(scanvasAspect)) { if (width == -1) width = 700; if (height == -1) height = 400; } else { double aspect = Units.dimensionless.parse(scanvasAspect).doubleValue(Units.dimensionless); if (width == -1 && height != -1) width = (int) (height * aspect); if (height == -1 && width != -1) height = (int) (width / aspect); } if (vap == null) { dom.getController().getCanvas().setWidth(width); dom.getController().getCanvas().setHeight(height); DasCanvas c = dom.getController().getCanvas().getController().getDasCanvas(); c.prepareForOutput(width, height); // KLUDGE, resize all components for TimeSeriesBrowse } logit("set canvas parameters", t0, uniq, debug); if (vap != null) { boolean isLocalVap= FileSystemUtil.isLocalResource(vap); logger.log(Level.FINER, "vap isLocalVap={0}", isLocalVap); File openable = DataSetURI.getFile(vap,new NullProgressMonitor()); if ( !isLocalVap ) { if ( vapHasLocalReferences( openable ) && !ServletUtil.isWhitelisted( vap ) ) { throw new IllegalArgumentException("remote .vap file has local references"); } } URISplit split= URISplit.parse(vap); if (split.params != null) { LinkedHashMap params = URISplit.parseParams(split.params); if ( params.containsKey("timerange") && !params.containsKey("timeRange") ) { params.put("timeRange", params.remove("timerange") ); } if ( isLocalVap ) params.put("PWD",split.path); if ( stimeRange.trim().length()>0 ) { params.put( "timeRange", stimeRange ); } if ( !isLocalVap ) { LinkedHashMap params1 = new LinkedHashMap(); if ( params.get("timeRange")!=null ) { params1.put( "timeRange", params.get("timeRange") ); } params= params1; } appmodel.doOpenVap(openable, params); } else { LinkedHashMap params = new LinkedHashMap(); if ( isLocalVap ) params.put("PWD",split.path); if ( stimeRange.trim().length()>0 ) { params.put( "timeRange", stimeRange ); } appmodel.doOpenVap(openable, params); } logit("opened vap", t0, uniq, debug); width = appmodel.getDocumentModel().getCanvases(0).getWidth(); height = appmodel.getDocumentModel().getCanvases(0).getHeight(); DasCanvas c = dom.getController().getCanvas().getController().getDasCanvas(); c.prepareForOutput(width, height); // KLUDGE, resize all components for TimeSeriesBrowse } if (suri != null && !"".equals(suri)) { File data= new File( ServletUtil.getServletHome(), "data" ); if ( !data.exists() ) { if ( !data.mkdirs() ) { throw new IllegalArgumentException("Unable to make servlet data directory"); } } suri= URISplit.makeAbsolute( data.getAbsolutePath(), suri ); URISplit split = URISplit.parse(suri); if ( id==null ) { // id!=null indicates that the surl was generated within the server. if ( whiteListed ) { } else { if ( FileSystemUtil.isLocalResource(suri) ) { File p= new File(data.getAbsolutePath()); File f= new File(split.file.substring(7)); if ( FileUtil.isParent( p, f ) ) { logger.log(Level.FINE, "file within autoplot_data/server/data folder is allowed"); logger.log(Level.FINE, "{0}", suri); } else { // See http://autoplot.org/developer.servletSecurity for more info. logger.log(Level.FINE, "{0}", suri); throw new IllegalArgumentException("local resources cannot be served, except via local vap file. "); } } else { if ( split.file!=null && split.file.contains("jyds") || ( split.vapScheme!=null && split.vapScheme.equals("jyds") ) ) { File sd= ServletUtil.getServletHome(); File ff= new File( sd, "whitelist.txt" ); logger.log(Level.FINE, "non-local .jyds scripts are not allowed."); logger.log(Level.FINE, "{0}", suri); throw new IllegalArgumentException("non-local .jyds scripts are not allowed. Administrators may wish to whitelist this data, see "+ff+", which does not include a match for "+suri); //TODO: this server file reference should be removed. } } if ( split.vapScheme!=null && split.vapScheme.equals("vap+inline") && split.surl.contains("getDataSet") ) { // this list could go on forever... throw new IllegalArgumentException("vap+inline URI cannot contain getDataSet."); } } } DataSource dsource; try { dsource = DataSetURI.getDataSource(suri); DatumRange timeRange = null; if (!stimeRange.equals("")) { timeRange = DatumRangeUtil.parseTimeRangeValid(stimeRange); TimeSeriesBrowse tsb = dsource.getCapability(TimeSeriesBrowse.class); if (tsb != null) { tsb.setURI(suri); tsb.setTimeRange(timeRange); logit("timeSeriesBrowse got data source", t0, uniq, debug); suri= tsb.getURI(); } } response.setHeader( "X-Autoplot-TSB-URI", suri ); DataSourceFactory dsf= DataSetURI.getDataSourceFactory( DataSetURI.getURI(suri),new NullProgressMonitor()); List problems= new ArrayList<>(1); if ( dsf.reject(suri, problems, new NullProgressMonitor() )) { if ( problems.isEmpty() ) { // no explanation provided. throw new IllegalArgumentException("URI was rejected: "+suri ); } else if ( problems.size()==1 ) { throw new IllegalArgumentException("URI was rejected: "+problems.get(0) ); } else { throw new IllegalArgumentException("URI was rejected: "+problems.get(0) + " and "+(problems.size()-1) + "more" ); } } } catch (NullPointerException ex) { throw new RuntimeException("No such data source: ", ex); } catch (Exception ex) { throw ex; } logit("got data source", t0, uniq, debug); DatumRange timeRange = null; if (!stimeRange.equals("")) { timeRange = DatumRangeUtil.parseTimeRangeValid(stimeRange); TimeSeriesBrowse tsb = dsource.getCapability(TimeSeriesBrowse.class); if (tsb != null) { tsb.setTimeRange(timeRange); logit("timeSeriesBrowse got data source", t0, uniq, debug); } } if (!process.equals("")) { QDataSet r = dsource.getDataSet(new NullProgressMonitor()); logit("done with read", t0, uniq, debug); switch (process) { case "histogram": appmodel.setDataSet(Ops.histogram(r, 100)); break; case "magnitude(fft)": r = Ops.magnitude(Ops.fft(r)); appmodel.setDataSet(r); break; case "nop": appmodel.setDataSet(r); break; } logit("done with process", t0, uniq, debug); } else { appmodel.setDataSource(dsource); logit("done with setDataSource", t0, uniq, debug); } if (!stimeRange.equals("")) { appmodel.waitUntilIdle(); if (UnitsUtil.isTimeLocation(dom.getTimeRange().getUnits())) { dom.setTimeRange(timeRange); } logit("done with setTimeRange", t0, uniq, debug); } } // wait for autoranging, etc. dom.getController().waitUntilIdle(); // axis settings Plot p = dom.getController().getPlot(); if (!title.equals("")) p.setTitle(title); Axis axis = p.getXaxis(); if (!xlabel.equals("")) axis.setLabel(xlabel); if (!xrange.equals("")) { Units u = axis.getController().getDasAxis().getUnits(); DatumRange newRange = DatumRangeUtil.parseDatumRange(xrange, u); axis.setRange(newRange); } if (!xlog.equals("")) axis.setLog("true".equals(xlog)); if (!xdrawTickLabels.equals("")) axis.setDrawTickLabels("true".equals(xdrawTickLabels)); axis = p.getYaxis(); if (!ylabel.equals("")) axis.setLabel(ylabel); if (!yrange.equals("")) { Units u = axis.getController().getDasAxis().getUnits(); DatumRange newRange = DatumRangeUtil.parseDatumRange(yrange, u); axis.setRange(newRange); } if (!ylog.equals("")) axis.setLog("true".equals(ylog)); if (!ydrawTickLabels.equals("")) axis.setDrawTickLabels("true".equals(ydrawTickLabels)); axis = p.getZaxis(); if (!zlabel.equals("")) axis.setLabel(zlabel); if (!zrange.equals("")) { Units u = axis.getController().getDasAxis().getUnits(); DatumRange newRange = DatumRangeUtil.parseDatumRange(zrange, u); axis.setRange(newRange); } if (!zlog.equals("")) axis.setLog("true".equals(zlog)); if (!zdrawTickLabels.equals("")) axis.setDrawTickLabels("true".equals(zdrawTickLabels)); if (!srenderType.equals("")) { RenderType renderType = RenderType.valueOf(srenderType); dom.getController().getPlotElement().setRenderType(renderType); } if ( !ssymbolSize.equals("") ) { dom.getController().getPlotElement().getStyle().setSymbolSize( Double.parseDouble( ssymbolSize ) ); } if (!scolor.equals("")) { String[] scolors= scolor.split("[,;]"); // allow for comma-delimited list. if ( scolors.length==1 ) { dom.getController().getPlotElement().getStyle().setColor(Color.decode(scolor)); for ( PlotElement pe: dom.getPlotElements() ) { // bug where Bob saw red pe.getStyle().setColor(Color.decode(scolor)); } } else { dom.getController().getPlotElement().getStyle().setColor(Color.decode(scolors[0])); int i=0; for ( PlotElement pe: dom.getPlotElements() ) { if ( pe.isActive() ) { pe.getStyle().setColor(Color.decode(scolors[i])); if ( i /** * Handles the HTTP GET method. * @param request servlet request * @param response servlet response * @throws javax.servlet.ServletException * @throws java.io.IOException */ @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { processRequest(request, response); } /** * Handles the HTTP POST method. * @param request servlet request * @param response servlet response * @throws javax.servlet.ServletException * @throws java.io.IOException */ @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { processRequest(request, response); } /** * Returns a short description of the servlet. * @return a short description of the servlet. */ @Override public String getServletInfo() { return "Autoplot Simple Servlet"; }// private void logit(String string, long t0, long id, String debug) { boolean flushHandlers= true; if ( debug!=null && !debug.equals("false") ) { if ( logger.isLoggable(Level.FINE) ) { logger.log( Level.FINE, String.format( "##%d# %s: %d\n", id, string, (System.currentTimeMillis() - t0) ) ); if ( flushHandlers ) { for ( Handler h: logger.getHandlers() ) { h.flush(); } } } //System.err.println( String.format( "##%d# %s: %d\n", id, string, (System.currentTimeMillis() - t0) ) ); } else { if ( logger.isLoggable(Level.FINER) ) { logger.log( Level.FINER, String.format( "##%d# %s: %d\n", id, string, (System.currentTimeMillis() - t0) ) ); if ( flushHandlers ) { for ( Handler h: logger.getHandlers() ) { h.flush(); } } } } } }