package org.autoplot.hapiserver; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.nio.charset.Charset; import java.util.Arrays; import java.util.Calendar; import java.util.Enumeration; import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.TimeZone; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.das2.qds.DataSetUtil; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; /** * helpful functions. * @author jbf */ public class Util { private static final Logger LOGGER= Logger.getLogger("hapi"); /** * return the duration in a easily-human-consumable form. * @param dt the duration in milliseconds. * @return a duration like "2.6 hours" */ public static String getDurationForHumans( long dt ) { if ( dt<2*1000 ) { return dt+" milliseconds"; } else if ( dt<2*60000 ) { return String.format( Locale.US, "%.1f",dt/1000.)+" seconds"; } else if ( dt<2*3600000 ) { return String.format( Locale.US, "%.1f",dt/60000.)+" minutes"; } else if ( dt<2*86400000 ) { return String.format( Locale.US, "%.1f",dt/3600000.)+" hours"; } else { return String.format( Locale.US, "%.1f",dt/86400000.)+" days"; } } /** * if HAPI_HOME has not been set, then set it. * @param context */ public static void maybeInitialize( ServletContext context ) { if ( HAPI_HOME==null ) { String s= context.getInitParameter(HAPI_SERVER_HOME_PROPERTY); setHapiHome( new File( s ) ); } } private static volatile File HAPI_HOME=null; /** * return the root of the HAPI server. * @return the root of the HAPI server. */ public static File getHapiHome() { if ( Util.HAPI_HOME==null ) { throw new IllegalArgumentException("Util.HAPI_HOME is not set, load info page first."); } else { return Util.HAPI_HOME; } } public static void setHapiHome( File f ) { File HAPI_HOME_ = Util.HAPI_HOME; if ( HAPI_HOME_==null ) { synchronized ( Util.class ) { HAPI_HOME_ = Util.HAPI_HOME; if ( HAPI_HOME_==null ) { Util.HAPI_HOME= f; } } } else { // it has been set already. } } /** * This should point to the name of the directory containing HAPI configuration. * This directory should contain catalog.json, capabilities.json and a * subdirectory "info" which contains files with the name <ID>.json, * each containing the info response. Note these should also contain a * tag "x_uri" which is the Autoplot URI that serves this data. */ public static final String HAPI_SERVER_HOME_PROPERTY = "HAPI_SERVER_HOME"; /** * return the HAPI protocol version. * @return the HAPI protocol version. */ public static final String hapiVersion() { return "3.0"; } /** * return the server implementation version. * @return the server implementation version. */ public static final String serverVersion() { return "20220209.0738"; } /** * return true if the client is trusted and additional information about * the server for debugging can be included in the response. * @param request * @return */ public static final boolean isTrustedClient( HttpServletRequest request ) { String remoteAddr= request.getRemoteAddr(); if ( remoteAddr.equals("127.0.0.1" ) || remoteAddr.equals("0:0:0:0:0:0:0:1") ) { Enumeration hh= request.getHeaders("X-Forwarded-For"); if ( hh.hasMoreElements() ) { remoteAddr = hh.nextElement(); } } return remoteAddr.equals("127.0.0.1" ) || remoteAddr.equals("0:0:0:0:0:0:0:1"); } private static final Pattern PATTERN_KEY= Pattern.compile("\\d{8}+"); static boolean isKey(String key) { return PATTERN_KEY.matcher(key).matches(); } /** * convert IDs and NAMEs into safe names which will work on all platforms. * If the name is modified, it will start with an underscore (_). *
  • poolTemperature -> poolTemperature *
  • Iowa City Conditions -> _Iowa+City+Conditions * @param s * @return */ public static final String fileSystemSafeName( String s ) { Pattern p= Pattern.compile("[a-zA-Z0-9\\-\\+\\*\\._]+"); Matcher m= p.matcher(s); if ( m.matches() ) { return s; } else { String s1= s.replaceAll("\\+","2B"); s1= s1.replaceAll(" ","\\+"); if ( p.matcher(s1).matches() ) { return "_" + s1; } else { byte[] bb= s.getBytes( Charset.forName("UTF-8") ); StringBuilder sb= new StringBuilder("_"); for ( byte b: bb ) { sb.append( String.format("%02X", b) ); } return sb.toString(); } } } /** * this key can create place for the data * @param id * @param key * @return */ static boolean keyCanCreate(String id,String key) { if ( !isKey(key) ) { throw new IllegalArgumentException("is not a key: "+key); } File keyFile= new File( getHapiHome(), "keys" ); if ( !keyFile.exists() ) return false; keyFile= new File( keyFile, fileSystemSafeName(id) + ".json" ); if ( !keyFile.exists() ) return false; try { JSONObject jo= HapiServerSupport.readJSON(keyFile); if ( jo.has(key) ) { jo= jo.getJSONObject(key); return jo.getBoolean("create"); } else { return false; } } catch ( IOException | JSONException ex ) { throw new RuntimeException(ex); } } /** * this key can modify or add records to the data * @param id * @param key * @return */ static boolean keyCanModify(String id,String key) { if ( !isKey(key) ) { throw new IllegalArgumentException("is not a key: "+key); } File keyFile= new File( getHapiHome(), "keys" ); if ( !keyFile.exists() ) return false; keyFile= new File( keyFile, fileSystemSafeName(id) + ".json" ); if ( !keyFile.exists() ) return false; try { JSONObject jo= HapiServerSupport.readJSON(keyFile); if ( jo.has(key) ) { jo= jo.getJSONObject(key); return jo.getBoolean("modify") || jo.getBoolean("create"); } else { return false; } } catch ( IOException | JSONException ex ) { throw new RuntimeException(ex); } } /** * this key can delete records from the data * @param id * @param key * @return */ static boolean keyCanDelete(String id,String key) { if ( !isKey(key) ) { throw new IllegalArgumentException("is not a key: "+key); } File keyFile= new File( getHapiHome(), "keys" ); if ( !keyFile.exists() ) return false; keyFile= new File( keyFile, fileSystemSafeName(id) + ".json" ); if ( !keyFile.exists() ) return false; try { JSONObject jo= HapiServerSupport.readJSON(keyFile); if ( jo.has(key) ) { jo= jo.getJSONObject(key); return jo.getBoolean("delete") || jo.getBoolean("create"); } else { return false; } } catch ( IOException | JSONException ex ) { throw new RuntimeException(ex); } } /** * transfers the data from one channel to another. src and dest are * closed after the operation is complete. * @param src * @param dest * @throws java.io.IOException */ public static void transfer( InputStream src, OutputStream dest ) throws IOException { final byte[] buffer = new byte[ 16 * 1024 ]; int i= src.read(buffer); while ( i != -1) { dest.write(buffer,0,i); i= src.read(buffer); } dest.close(); src.close(); } /** * return true if this is valid JSON, false otherwise, and log the exception at SEVERE. * @param json * @return */ public static boolean validateJSON( String json ) { try { new JSONObject( json ); return true; } catch (JSONException ex) { Logger.getLogger(Util.class.getName()).log(Level.SEVERE, null, ex); return false; } } /** * send a "bad id" response to the client. * @param id the id. * @param response the response object * @param out the print writer for the response object. */ public static void raiseBadId(String id, HttpServletResponse response, final PrintWriter out) { try { JSONObject jo= new JSONObject(); jo.put("HAPI",Util.hapiVersion()); jo.put("createdAt",String.format("%tFT% map= new HashMap(); // map from name to index in dataset. Map iMap= new HashMap(); // map from name to position in csv. JSONArray jsonParameters= info.getJSONArray("parameters"); int index=0; int[] lens= getNumberOfElements(info); for ( int i=0; i=0; k-- ) { newParameters.put( k+1, newParameters.get(k) ); } newParameters.put(0,jsonParameters.get(0)); } // unpack the resort where the lengths are greater than 1. int[] indexMap1= new int[ DataSetUtil.sum(lengths) ]; int c= 0; if ( indexMap1.length>indexMap.length ) { for ( int k=0; k -1) { if (j0 < nf) { throw new IllegalArgumentException("expected " + nf + " fields"); } else if (j0 != nf) { LOGGER.log(Level.WARNING, "expected {0} fields, got {1}", new Object[]{nf, j0}); } } return Arrays.copyOfRange(result, 0, j0); } } /** * properly trim the byte array containing a UTF-8 String to a limit * @param bytes the bytes * @param k the number of bytes * @return */ public static byte[] trimUTF8( byte[] bytes, int k ) { if ( bytes.length==k ) return bytes; int b= bytes[k]; if ( b>127 ) { // uh-oh, we are mid-UTF8-extended character. while ( k>0 && b>127 ) { k=k-1; b= bytes[k]; } } bytes= Arrays.copyOf( bytes, k ); return bytes; } public static void main( String[] args ) { String line= "a,b,\"c,d\""; String[] ss; ss= csvSplit( line, -1 ); for ( String s: ss ) { System.err.println( s ); } } }