diff --git a/build.xml b/build.xml new file mode 100644 index 0000000..b3d4bf5 --- /dev/null +++ b/build.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/annotations-java8.jar b/lib/annotations-java8.jar new file mode 100644 index 0000000..857c151 Binary files /dev/null and b/lib/annotations-java8.jar differ diff --git a/lib/guava-21.0.jar b/lib/guava-21.0.jar new file mode 100644 index 0000000..0618195 Binary files /dev/null and b/lib/guava-21.0.jar differ diff --git a/lib/json.jar b/lib/json.jar new file mode 100644 index 0000000..0e6bd4d Binary files /dev/null and b/lib/json.jar differ diff --git a/lib/org.everit.json.schema-1.5.0.jar b/lib/org.everit.json.schema-1.5.0.jar new file mode 100644 index 0000000..1ca2ea6 Binary files /dev/null and b/lib/org.everit.json.schema-1.5.0.jar differ diff --git a/resources/data/sources_v1.0.json b/resources/data/sources_v1.0.json new file mode 100644 index 0000000..04e245f --- /dev/null +++ b/resources/data/sources_v1.0.json @@ -0,0 +1,52 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "DataSources", + "description": "Helioviewer DataSources API response", + "type": "object", + "patternProperties": { + "^.*$": { + "oneOf": [ + { "$ref": "#/definitions/tree_node" }, + { "$ref": "#/definitions/tree_leaf" } + ] + } + }, + "definitions": { + "root_part": { + "patternProperties": { + "^.*$": { + "oneOf": [ + { "$ref": "#/definitions/tree_node" }, + { "$ref": "#/definitions/tree_leaf" } + ] + } + } + }, + "tree_node": { + "properties": { + "name": { "type": "string" }, + "description": { "type": "string" }, + "children": { "$ref": "#/definitions/root_part" }, + "default": { "type" : "boolean" } + }, + "required": [ "name", "description", "children" ] + }, + "tree_leaf": { + "properties": { + "name": { "type": "string" }, + "description": { "type": "string" }, + "sourceId": { "type": "integer" }, + "start": { + "type": "string", + "format": "sql-date-time" + }, + "end": { + "type": "string", + "format": "sql-date-time" + }, + "default": { "type" : "boolean" } + }, + "required": [ "name", "description", "sourceId", "start", "end" ] + } + } +} diff --git a/src/org/helioviewer/jhv/DataSourcesChecker.java b/src/org/helioviewer/jhv/DataSourcesChecker.java new file mode 100644 index 0000000..c0ce58c --- /dev/null +++ b/src/org/helioviewer/jhv/DataSourcesChecker.java @@ -0,0 +1,11 @@ +package org.helioviewer.jhv; + +import org.helioviewer.jhv.io.DataSources; + +public class DataSourcesChecker { + + public static void main(String[] args) { + DataSources.loadSources(); + } + +} diff --git a/src/org/helioviewer/jhv/JHVGlobals.java b/src/org/helioviewer/jhv/JHVGlobals.java new file mode 100644 index 0000000..b2bb570 --- /dev/null +++ b/src/org/helioviewer/jhv/JHVGlobals.java @@ -0,0 +1,25 @@ +package org.helioviewer.jhv; + +import java.util.concurrent.ExecutorService; + +import org.helioviewer.jhv.threads.JHVExecutor; + +public class JHVGlobals { + + public static String userAgent = "DataSources-schema"; + + private static final ExecutorService executorService = JHVExecutor.getJHVWorkersExecutorService("MAIN", 10); + + public static ExecutorService getExecutorService() { + return executorService; + } + + public static int getStdReadTimeout() { + return 180000; + } + + public static int getStdConnectTimeout() { + return 60000; + } + +} diff --git a/src/org/helioviewer/jhv/base/DownloadStream.java b/src/org/helioviewer/jhv/base/DownloadStream.java new file mode 100644 index 0000000..d2bf70b --- /dev/null +++ b/src/org/helioviewer/jhv/base/DownloadStream.java @@ -0,0 +1,206 @@ +package org.helioviewer.jhv.base; + +import java.awt.EventQueue; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStreamWriter; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import java.nio.charset.StandardCharsets; +import java.util.regex.Matcher; +import java.util.zip.GZIPInputStream; +import java.util.zip.InflaterInputStream; + +import org.helioviewer.jhv.JHVGlobals; +import org.helioviewer.jhv.base.logging.Log; + +public class DownloadStream { + + // Input stream to read the data from + private InputStream in = null; + + // Output to send as a post request + private String output = null; + + // Suggested name to save (if wanted) + private String outputName = null; + + private String contentDisposition = null; + private int contentLength = -1; + private boolean response400 = false; + + // Read timeout in ms + private final int readTimeout; + + // Connect timeout in ms + private final int connectTimeout; + + // URL to connect + private final URL url; + private final boolean ignore400; + + private DownloadStream(URL _url, int _connectTimeout, int _readTimeout, boolean _ignore400) { + url = _url; + readTimeout = _readTimeout; + connectTimeout = _connectTimeout; + ignore400 = _ignore400; + } + + private DownloadStream(URL _url, boolean _ignore400) { + this(_url, JHVGlobals.getStdConnectTimeout(), JHVGlobals.getStdReadTimeout(), _ignore400); + } + + public DownloadStream(String _url, boolean _ignore400) throws MalformedURLException { + this(new URL(_url), _ignore400); + } + + public DownloadStream(URL _url) { + this(_url, JHVGlobals.getStdConnectTimeout(), JHVGlobals.getStdReadTimeout(), false); + } + + public DownloadStream(String _url) throws MalformedURLException { + this(new URL(_url)); + } + + public boolean isResponse400() { + return response400; + } + + private static InputStream getEncodedStream(String encoding, InputStream httpStream) throws IOException { + if (encoding != null) { + switch (encoding.toLowerCase()) { + case "gzip": + return new GZIPInputStream(httpStream); + case "deflate": + return new InflaterInputStream(httpStream); + } + } + return httpStream; + } + + /** + * Opens the connection with compression if the server supports + * + * @throws IOException + * From accessing the network + */ + private void connect() throws IOException { + if (EventQueue.isDispatchThread()) + throw new IOException("Don't do that"); + + //Log.debug("Connect to " + url); + URLConnection connection = url.openConnection(); + // Set timeouts + connection.setConnectTimeout(connectTimeout); + connection.setReadTimeout(readTimeout); + + if (connection instanceof HttpURLConnection) { + HttpURLConnection httpC = (HttpURLConnection) connection; + // get compression if supported + httpC.setRequestProperty("Accept-Encoding", "gzip,deflate"); + httpC.setRequestProperty("User-Agent", JHVGlobals.userAgent); + + // Write post data if necessary + if (output != null) { + connection.setDoOutput(true); + try (OutputStreamWriter out = new OutputStreamWriter(connection.getOutputStream(), StandardCharsets.UTF_8)) { + out.write(output); + } + } + + try { + httpC.connect(); + } catch (IOException e) { + Log.warn("HTTP connection failed: " + url + " " + e); + } + + // Check the connection code + int code = httpC.getResponseCode(); + if (code > 400) { + throw new IOException("Error opening HTTP connection to " + url + " Response code: " + code); + } + + if (!ignore400 && code == 400) { + throw new IOException("Error opening HTTP connection to " + url + " Response code: " + code); + } + + InputStream strm; + if (code == 400) { + response400 = true; + strm = httpC.getErrorStream(); + if (strm == null) + strm = httpC.getInputStream(); + } else { + strm = httpC.getInputStream(); + } + + contentDisposition = httpC.getHeaderField("Content-Disposition"); + in = getEncodedStream(httpC.getContentEncoding(), strm); + } else { + // Not an http connection + // Write post data if necessary + if (output != null) { + connection.setDoOutput(true); + try (OutputStreamWriter out = new OutputStreamWriter(connection.getOutputStream(), StandardCharsets.UTF_8)) { + out.write(output); + } + } + // Okay just normal + in = connection.getInputStream(); + } + contentLength = connection.getContentLength(); + } + + /** + * Gives the outstream to read the response, after calling connect. If it is + * not already connected it will automatically connect + * + * @return output stream of the connection + * @throws IOException + * Error from creating the connction + */ + public InputStream getInput() throws IOException { + if (in == null) + connect(); + return in; + } + + /** + * After requesting the data the associated file name to save from + * Content-Disposition or the url name + * + * @return suggested download name + */ + public String getOutputName() { + if (outputName == null) { + if (contentDisposition != null) { + Matcher m = Regex.ContentDispositionFilename.matcher(contentDisposition); + if (m.find()) { + outputName = m.group(1); + } + } + if (outputName == null) { + outputName = url.getFile().replace('/', '-'); + } + } + return outputName; + } + + public int getContentLength() { + return contentLength; + } + + /** + * Set the output to send to the server (in HTTP as POST) + * + * @param _output + * Send output to the server, null if nothing (GET in HTTP) + */ + public void setOutput(String _output) { + output = _output; + } + +} diff --git a/src/org/helioviewer/jhv/base/FileUtils.java b/src/org/helioviewer/jhv/base/FileUtils.java new file mode 100644 index 0000000..6ab64db --- /dev/null +++ b/src/org/helioviewer/jhv/base/FileUtils.java @@ -0,0 +1,130 @@ +package org.helioviewer.jhv.base; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.Scanner; + +// A class which provides functions for accessing and working with files +public class FileUtils { + + private static final int BUFSIZ = 65536; + + /** + * Return the current working directory + * + * @return the current working directory + */ + public static File getWorkingDirectory() { + return new File(System.getProperty("user.dir")); + } + + /** + * Method copies a file from src to dst. + * + * @param src + * Source file + * @param dst + * Destination file + * @throws IOException + */ + public static void copy(File src, File dst) throws IOException { + try (InputStream in = new FileInputStream(src); OutputStream out = new FileOutputStream(dst)) { + // Transfer bytes from in to out + byte[] buf = new byte[BUFSIZ]; + int len; + while ((len = in.read(buf)) > 0) { + out.write(buf, 0, len); + } + } + } + + /** + * Method saving a stream to dst. + * + * @param in + * Input stream, will be closed if finished + * @param dst + * Destination file + * @throws IOException + */ + public static void save(InputStream in, File dst) throws IOException { + // Transfer bytes from in to out + try (OutputStream out = new FileOutputStream(dst)) { + byte[] buf = new byte[BUFSIZ]; + int len; + while ((len = in.read(buf)) > 0) { + out.write(buf, 0, len); + } + } + in.close(); + } + + /** + * Returns an input stream to a resource. This function can be used even if + * the whole program and resources are within a JAR file. The path must + * begin with a slash and contain all subfolders, e.g.: /images/sample_image.png + * The class loader used is the same which was used to load FileUtils. + * + * @param resourcePath + * The path to the resource + * @return An InputStream to the resource + */ + public static InputStream getResourceInputStream(String resourcePath) { + return FileUtils.class.getResourceAsStream(resourcePath); + } + + public static String convertStreamToString(InputStream is) { + try (Scanner s = new Scanner(is, StandardCharsets.UTF_8.name())) { + s.useDelimiter("\\A"); + return s.hasNext() ? s.next() : ""; + } + } + + /** + * Returns an URL to a resource. This function can be used even if the whole + * program and resources are within a JAR file. The path must begin with a + * slash and contain all subfolders, e.g.: /images/sample_image.png + * The class loader used is the same which was used to load FileUtils. + * + * @param resourcePath + * The path to the resource + * @return An URL to the resource + */ + public static URL getResourceUrl(String resourcePath) { + return FileUtils.class.getResource(resourcePath); + } + + public static boolean deleteDir(File dir) { + String[] children; + if (dir.isDirectory() && (children = dir.list()) != null) { + for (String child : children) { + boolean success = deleteDir(new File(dir, child)); + if (!success) { + return false; + } + } + } + return dir.delete(); + } + + public static String URL2String(URL url) { + StringBuilder sb = new StringBuilder(); + try (Scanner scanner = new Scanner(new BufferedReader(new InputStreamReader(url.openStream(), StandardCharsets.UTF_8)))) { + while (scanner.hasNext()) { + sb.append(scanner.nextLine()).append('\n'); + } + } catch (Exception e) { + e.printStackTrace(); + } + return sb.toString(); + } + +} diff --git a/src/org/helioviewer/jhv/base/JSONUtils.java b/src/org/helioviewer/jhv/base/JSONUtils.java new file mode 100644 index 0000000..80b8230 --- /dev/null +++ b/src/org/helioviewer/jhv/base/JSONUtils.java @@ -0,0 +1,38 @@ +package org.helioviewer.jhv.base; + +import java.io.ByteArrayOutputStream; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.nio.charset.StandardCharsets; +import java.util.zip.GZIPOutputStream; + +import org.helioviewer.jhv.base.logging.Log; +import org.json.JSONObject; +import org.json.JSONTokener; + +public class JSONUtils { + + private static final int BUFSIZ = 65536; + + public static JSONObject getJSONStream(InputStream in) { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8), BUFSIZ)) { + return new JSONObject(new JSONTokener(reader)); + } catch (Exception e) { + Log.error("Invalid JSON response " + e); + return new JSONObject(); + } + } + + public static byte[] compressJSON(JSONObject json) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + OutputStreamWriter out = new OutputStreamWriter(new GZIPOutputStream(baos, BUFSIZ), StandardCharsets.UTF_8); + json.write(out); + out.close(); + + return baos.toByteArray(); + } + +} diff --git a/src/org/helioviewer/jhv/base/Regex.java b/src/org/helioviewer/jhv/base/Regex.java new file mode 100644 index 0000000..6ed9cd7 --- /dev/null +++ b/src/org/helioviewer/jhv/base/Regex.java @@ -0,0 +1,76 @@ +package org.helioviewer.jhv.base; + +import java.util.regex.Pattern; + +public class Regex { + + // https://github.com/android/platform_frameworks_base/blob/master/core/java/android/util/Patterns.java + + /** + * Good characters for Internationalized Resource Identifiers (IRI). + * This comprises most common used Unicode characters allowed in IRI + * as detailed in RFC 3987. + * Specifically, those two byte Unicode characters are not included. + */ + private static final String GOOD_IRI_CHAR = + "a-zA-Z0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF"; + + private static final Pattern IP_ADDRESS + = Pattern.compile( + "((25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.(25[0-5]|2[0-4]" + + "[0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1]" + + "[0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}" + + "|[1-9][0-9]|[0-9]))"); + + /** + * RFC 1035 Section 2.3.4 limits the labels to a maximum 63 octets. + */ + private static final String IRI + = "[" + GOOD_IRI_CHAR + "]([" + GOOD_IRI_CHAR + "\\-]{0,61}[" + GOOD_IRI_CHAR + "]){0,1}"; + + private static final String GOOD_GTLD_CHAR = + "a-zA-Z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF"; + private static final String GTLD = "[" + GOOD_GTLD_CHAR + "]{2,63}"; + private static final String HOST_NAME = "(" + IRI + "\\.)+" + GTLD; + + private static final Pattern DOMAIN_NAME + = Pattern.compile("(" + HOST_NAME + "|" + IP_ADDRESS + ")"); + + /** + * Regular expression pattern to match most part of RFC 3987 + * Internationalized URLs, aka IRIs. Commonly used Unicode characters are + * added. + */ + public static final Pattern WEB_URL = Pattern.compile( + "((?:(http|https|Http|Https|rtsp|Rtsp):\\/\\/(?:(?:[a-zA-Z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)" + + "\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,64}(?:\\:(?:[a-zA-Z0-9\\$\\-\\_" + + "\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,25})?\\@)?)?" + + "(?:" + DOMAIN_NAME + ")" + + "(?:\\:\\d{1,5})?)" // plus option port number + + "(\\/(?:(?:[" + GOOD_IRI_CHAR + "\\;\\/\\?\\:\\@\\&\\=\\#\\~" // plus option query params + + "\\-\\.\\+\\!\\*\\'\\(\\)\\,\\_])|(?:\\%[a-fA-F0-9]{2}))*)?" + + "(?:\\b|$)"); // and finally, a word boundary or end of + // input. This is to stop foo.sure from + // matching as foo.su + + public static final Pattern EMAIL_ADDRESS + = Pattern.compile( + "[a-zA-Z0-9\\+\\.\\_\\%\\-\\+]{1,256}" + + "\\@" + + "[a-zA-Z0-9][a-zA-Z0-9\\-]{0,64}" + + "(" + + "\\." + + "[a-zA-Z0-9][a-zA-Z0-9\\-]{0,25}" + + ")+" + ); + + + public static final Pattern HREF = Pattern.compile("href=\"(.*?)\""); + + public static final Pattern FloatingPoint = Pattern.compile("[\\x00-\\x20]*[+-]?(NaN|Infinity|((((\\p{Digit}+)(\\.)?((\\p{Digit}+)?)([eE][+-]?(\\p{Digit}+))?)|(\\.((\\p{Digit}+))([eE][+-]?(\\p{Digit}+))?)|(((0[xX](\\p{XDigit}+)(\\.)?)|(0[xX](\\p{XDigit}+)?(\\.)(\\p{XDigit}+)))[pP][+-]?(\\p{Digit}+)))[fFdD]?))[\\x00-\\x20]*"); + public static final Pattern Integer = Pattern.compile("\\d+"); + + // Pattern to extract the filename from HTTP Content-Disposition header + public static final Pattern ContentDispositionFilename = Pattern.compile("filename=\\\"(.*?)\\\""); + +} diff --git a/src/org/helioviewer/jhv/base/logging/Log.java b/src/org/helioviewer/jhv/base/logging/Log.java new file mode 100644 index 0000000..93e2efa --- /dev/null +++ b/src/org/helioviewer/jhv/base/logging/Log.java @@ -0,0 +1,21 @@ +package org.helioviewer.jhv.base.logging; + +public class Log { + + public static void warn(Object obj) { + System.err.println(obj.toString()); + } + + public static void error(Object obj) { + System.err.println(obj.toString()); + } + + public static void error(Object obj, Throwable error) { + System.err.println(obj.toString() + error.toString()); + } + + public static void debug(Object obj) { + System.out.println(obj.toString()); + } + +} diff --git a/src/org/helioviewer/jhv/base/time/JHVDate.java b/src/org/helioviewer/jhv/base/time/JHVDate.java new file mode 100644 index 0000000..6fc1eca --- /dev/null +++ b/src/org/helioviewer/jhv/base/time/JHVDate.java @@ -0,0 +1,44 @@ +package org.helioviewer.jhv.base.time; + +import org.jetbrains.annotations.NotNull; + +public class JHVDate implements Comparable { + + private final String string; + public final long milli; + + public JHVDate(String date) { + this(TimeUtils.parse(date)); + } + + public JHVDate(long _milli) { + if (_milli < 0) + throw new IllegalArgumentException("Argument cannot be negative"); + milli = _milli; + string = TimeUtils.format(milli); + } + + @Override + public int compareTo(@NotNull JHVDate dt) { + return milli < dt.milli ? -1 : (milli > dt.milli ? +1 : 0); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof JHVDate)) + return false; + JHVDate d = (JHVDate) o; + return milli == d.milli; + } + + @Override + public int hashCode() { + return (int) (milli ^ (milli >>> 32)); + } + + @Override + public String toString() { + return string; + } + +} diff --git a/src/org/helioviewer/jhv/base/time/TimeUtils.java b/src/org/helioviewer/jhv/base/time/TimeUtils.java new file mode 100644 index 0000000..5be6193 --- /dev/null +++ b/src/org/helioviewer/jhv/base/time/TimeUtils.java @@ -0,0 +1,91 @@ +package org.helioviewer.jhv.base.time; + +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.Optional; + +import org.everit.json.schema.FormatValidator; + +public class TimeUtils { + + private static final ZoneOffset ZERO = ZoneOffset.ofTotalSeconds(0); + private static final DateTimeFormatter sqlFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + private static final DateTimeFormatter fileFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss"); + + public static final long DAY_IN_MILLIS = 86400000; + public static final long MINUTE_IN_MILLIS = 60000; + + public static final JHVDate EPOCH = new JHVDate("2000-01-01T00:00:00"); + public static final JHVDate MINIMAL_DATE = new JHVDate("1970-01-01T00:00:00"); + public static final JHVDate MAXIMAL_DATE = new JHVDate("2050-01-01T00:00:00"); + + public static String format(long milli) { + return DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(Instant.ofEpochMilli(milli).atOffset(ZERO)); + } + + public static String formatZ(long milli) { + return DateTimeFormatter.ISO_INSTANT.format(Instant.ofEpochMilli(milli)); + } + + public static String formatSQL(long milli) { + return sqlFormatter.format(Instant.ofEpochMilli(milli).atOffset(ZERO)); + } + + public static String formatDate(long milli) { + return DateTimeFormatter.ISO_LOCAL_DATE.format(Instant.ofEpochMilli(milli).atOffset(ZERO)); + } + + public static String formatTime(long milli) { + return DateTimeFormatter.ISO_LOCAL_TIME.format(Instant.ofEpochMilli(milli).atOffset(ZERO)); + } + + public static String formatFilename(long milli) { + return fileFormatter.format(Instant.ofEpochMilli(milli).atOffset(ZERO)); + } + + public static long parse(String date) { + return LocalDateTime.parse(date, DateTimeFormatter.ISO_LOCAL_DATE_TIME).toInstant(ZERO).toEpochMilli(); + } + + public static long parseSQL(String date) { + return LocalDateTime.parse(date, sqlFormatter).toInstant(ZERO).toEpochMilli(); + } + + public static long parseDate(String date) { + return LocalDate.parse(date, DateTimeFormatter.ISO_LOCAL_DATE).toEpochDay() * DAY_IN_MILLIS; + } + + public static long parseTime(String date) { + return LocalTime.parse(date, DateTimeFormatter.ISO_LOCAL_TIME).toSecondOfDay() * 1000L; + } + + public static class SQLDateTimeFormatValidator implements FormatValidator { + + @Override + public Optional validate(final String subject) { + try { + long time = parseSQL(subject); + if (time < MINIMAL_DATE.milli || time > MAXIMAL_DATE.milli) + throw new Exception(); + + return Optional.empty(); + } catch (DateTimeParseException e) { + return Optional.of(String.format("[%s] is not a valid sql-date-time.", subject)); + } catch (Exception e) { + return Optional.of(String.format("[%s] is outside date range of [%s,%s].", subject, MINIMAL_DATE, MAXIMAL_DATE)); + } + } + + @Override + public String formatName() { + return "sql-date-time"; + } + + } + +} diff --git a/src/org/helioviewer/jhv/io/DataSources.java b/src/org/helioviewer/jhv/io/DataSources.java new file mode 100644 index 0000000..4ecbaf0 --- /dev/null +++ b/src/org/helioviewer/jhv/io/DataSources.java @@ -0,0 +1,95 @@ +package org.helioviewer.jhv.io; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.helioviewer.jhv.JHVGlobals; + +@SuppressWarnings("serial") +public class DataSources { + + static final Set SupportedObservatories = Collections.unmodifiableSet(new HashSet<>(Arrays.asList( + "SOHO", "SDO", "STEREO_A", "STEREO_B", "PROBA2", "ROB-USET", "ROB-Humain", "NSO-GONG", "NSO-SOLIS", "Kanzelhoehe", "NRH", "Yohkoh", "Hinode", "TRACE" + ))); + + private static final HashMap> serverSettings = new HashMap>() { + { + put("ROB", new HashMap() { + { + put("API.getDataSources", "http://swhv.oma.be/hv/api/?action=getDataSources&verbose=true&enable=[STEREO_A,STEREO_B,PROBA2]"); + put("API.getJP2Image", "http://swhv.oma.be/hv/api/index.php?action=getJP2Image&"); + put("API.getJPX", "http://swhv.oma.be/hv/api/index.php?action=getJPX&"); + put("label", "Royal Observatory of Belgium"); + put("schema", "/data/sources_v1.0.json"); + put("availability.images", "http://swhv.oma.be/availability/images/availability/availability.html"); + } + }); + put("IAS", new HashMap() { + { + put("API.getDataSources", "http://helioviewer.ias.u-psud.fr/helioviewer/api/?action=getDataSources&verbose=true&enable=[TRACE,Hinode,Yohkoh,STEREO_A,STEREO_B,PROBA2]"); + put("API.getJP2Image", "http://helioviewer.ias.u-psud.fr/helioviewer/api/index.php?action=getJP2Image&"); + put("API.getJPX", "http://helioviewer.ias.u-psud.fr/helioviewer/api/index.php?action=getJPX&"); + put("label", "Institut d'Astrophysique Spatiale"); + put("schema", "/data/sources_v1.0.json"); + } + }); + put("GSFC", new HashMap() { + { + put("API.getDataSources", "https://api.helioviewer.org/v2/getDataSources/?verbose=true&enable=[TRACE,Hinode,Yohkoh,STEREO_A,STEREO_B,PROBA2]"); + put("API.getJP2Image", "https://api.helioviewer.org/v2/getJP2Image/?"); + put("API.getJPX", "https://api.helioviewer.org/v2/getJPX/?"); + put("label", "Goddard Space Flight Center"); + put("schema", "/data/sources_v1.0.json"); + } + }); + /* + put("GSFC SCI Test", new HashMap() { + { + put("API.getDataSources", "http://helioviewer.sci.gsfc.nasa.gov/api.php?action=getDataSources&verbose=true&enable=[TRACE,Hinode,Yohkoh,STEREO_A,STEREO_B,PROBA2]"); + put("API.getJP2Image", "http://helioviewer.sci.gsfc.nasa.gov/api.php?action=getJP2Image&"); + put("API.getJPX", "http://helioviewer.sci.gsfc.nasa.gov/api.php?action=getJPX&"); + put("label", "Goddard Space Flight Center SCI Test"); + put("schema", "/data/sources_v1.0.json"); + } + }); + put("GSFC NDC Test", new HashMap() { + { + put("API.getDataSources", "http://gs671-heliovw7.ndc.nasa.gov/api.php?action=getDataSources&verbose=true&enable=[TRACE,Hinode,Yohkoh,STEREO_A,STEREO_B,PROBA2]"); + put("API.getJP2Image", "http://gs671-heliovw7.ndc.nasa.gov/api.php?action=getJP2Image&"); + put("API.getJPX", "http://gs671-heliovw7.ndc.nasa.gov/api.php?action=getJPX&"); + put("label", "Goddard Space Flight Center NDC Test"); + put("schema", "/data/sources_v1.0.json"); + } + }); + put("LOCALHOST", new HashMap() { + { + put("API.getDataSources", "http://localhost:8080/helioviewer/api/?action=getDataSources&verbose=true&enable=[STEREO_A,STEREO_B,PROBA2]"); + put("API.getJP2Image", "http://localhost:8080/helioviewer/api/index.php?action=getJP2Image&"); + put("API.getJPX", "http://localhost:8080/helioviewer/api/index.php?action=getJPX&"); + put("schema", "/data/sources_v1.0.json"); + put("label", "Localhost"); + } + }); + */ + } + }; + + public static Set getServers() { + return serverSettings.keySet(); + } + + public static String getServerSetting(String server, String setting) { + Map settings = serverSettings.get(server); + return settings == null ? null : settings.get(setting); + } + + public static void loadSources() { + for (String serverName : serverSettings.keySet()) + JHVGlobals.getExecutorService().execute(new DataSourcesTask(serverName)); + } + +} diff --git a/src/org/helioviewer/jhv/io/DataSourcesTask.java b/src/org/helioviewer/jhv/io/DataSourcesTask.java new file mode 100644 index 0000000..ffca720 --- /dev/null +++ b/src/org/helioviewer/jhv/io/DataSourcesTask.java @@ -0,0 +1,74 @@ +package org.helioviewer.jhv.io; + +import java.io.IOException; +import java.io.InputStream; + +import org.everit.json.schema.Schema; +import org.everit.json.schema.loader.SchemaLoader; +import org.helioviewer.jhv.base.DownloadStream; +import org.helioviewer.jhv.base.FileUtils; +import org.helioviewer.jhv.base.JSONUtils; +import org.helioviewer.jhv.base.logging.Log; +import org.helioviewer.jhv.base.time.TimeUtils; +import org.helioviewer.jhv.threads.JHVWorker; +import org.json.JSONObject; +import org.json.JSONTokener; + +public class DataSourcesTask extends JHVWorker { + + private final String url; + private final String schemaName; + + public DataSourcesTask(String server) { + url = DataSources.getServerSetting(server, "API.getDataSources"); + schemaName = DataSources.getServerSetting(server, "schema"); + setThreadName("MAIN--DataSources"); + } + + @Override + protected Void backgroundWork() { + while (true) { + Schema schema = null; + try (InputStream is = FileUtils.getResourceInputStream(schemaName)) { + JSONObject rawSchema = new JSONObject(new JSONTokener(is)); + SchemaLoader schemaLoader = SchemaLoader.builder().schemaJson(rawSchema).addFormatValidator(new TimeUtils.SQLDateTimeFormatValidator()).build(); + schema = schemaLoader.load().build(); + } catch (Exception e) { + Log.error("Could not load the JSON schema: ", e); + } + + try { + JSONObject json = JSONUtils.getJSONStream(new DownloadStream(url).getInput()); +/* + if (url.contains("helioviewer.org")) { + json.getJSONObject("PROBA2").getJSONObject("children").getJSONObject("SWAP").getJSONObject("children").remove("174"); + JSONObject o = new JSONObject( "{\"sourceId\":32,\"layeringOrder\":1,\"name\":\"174\u205fÅ\",\"nickname\":\"SWAP 174\",\"start\":\"2010-01-04 17:00:50\",\"description\":\"174 Ångström extreme ultraviolet\",\"end\":\"2017-03-21 10:23:31\",\"label\":\"Measurement\"} "); + json.getJSONObject("PROBA2").getJSONObject("children").getJSONObject("SWAP").getJSONObject("children").put("174", o); + } +*/ + if (schema != null) + schema.validate(json); + return null; + } catch (IOException e) { + try { + // Log.error(e); + Thread.sleep(5000); + } catch (InterruptedException e1) { + Log.error(e1); + break; + } + } + } + return null; + } + + @Override + protected void done() { + try { + get(); // recover background exceptions + } catch (Exception e) { + e.printStackTrace(); + } + } + +} diff --git a/src/org/helioviewer/jhv/threads/JHVExecutor.java b/src/org/helioviewer/jhv/threads/JHVExecutor.java new file mode 100644 index 0000000..9f3c14a --- /dev/null +++ b/src/org/helioviewer/jhv/threads/JHVExecutor.java @@ -0,0 +1,98 @@ +package org.helioviewer.jhv.threads; + +import java.lang.ref.WeakReference; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +public class JHVExecutor { + +/* +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + +import sun.awt.AppContext; + + public static synchronized void setSwingWorkersExecutorService(int MAX_WORKER_THREADS) { + + final AppContext appContext = AppContext.getAppContext(); + ExecutorService executorService = (ExecutorService) appContext.get(SwingWorker.class); + if (executorService == null) { + executorService = new ThreadPoolExecutor(MAX_WORKER_THREADS / 2, MAX_WORKER_THREADS, + 10L, TimeUnit.MINUTES, new LinkedBlockingQueue(), + new JHVThread.NamedThreadFactory("JHVWorker-Swing-")); + shutdownOnDisposal(appContext, executorService); + + appContext.put(SwingWorker.class, executorService); + } + } + + private static void shutdownOnDisposal(AppContext appContext, final ExecutorService es) { + // Don't use ShutdownHook here as it's not enough. We should track + // AppContext disposal instead of JVM shutdown, see 6799345 for details + appContext.addPropertyChangeListener(AppContext.DISPOSED_PROPERTY_NAME, + new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent pce) { + boolean disposed = (Boolean) pce.getNewValue(); + if (disposed) { + final WeakReference executorServiceRef = new WeakReference(es); + final ExecutorService executorService = executorServiceRef.get(); + if (executorService != null) { + AccessController.doPrivileged( + new PrivilegedAction() { + @Override + public Void run() { + executorService.shutdown(); + return null; + } + } + ); + } + } + } + } + ); + } +*/ + + public static ExecutorService getJHVWorkersExecutorService(String name, int MAX_WORKER_THREADS) { + ExecutorService executorService = new ThreadPoolExecutor(MAX_WORKER_THREADS / 2, MAX_WORKER_THREADS, + 10L, TimeUnit.MINUTES, new LinkedBlockingQueue<>(), new JHVThread.NamedThreadFactory("JHVWorker-" + name)) { + @Override + protected void afterExecute(Runnable r, Throwable t) { + super.afterExecute(r, t); + JHVThread.afterExecute(r, t); + } + }; + shutdownOnDisposal(executorService); + return executorService; + } + + private static void shutdownOnDisposal(ExecutorService es) { + Runnable shutdownHook = + new Runnable() { + final WeakReference executorServiceRef = new WeakReference<>(es); + public void run() { + ExecutorService executorService = executorServiceRef.get(); + if (executorService != null) { + AccessController.doPrivileged( + (PrivilegedAction) () -> { + executorService.shutdown(); + return null; + }); + } + } + }; + + AccessController.doPrivileged( + (PrivilegedAction) () -> { + Runtime.getRuntime().addShutdownHook(new Thread(shutdownHook)); + return null; + }); + } + +} diff --git a/src/org/helioviewer/jhv/threads/JHVThread.java b/src/org/helioviewer/jhv/threads/JHVThread.java new file mode 100644 index 0000000..9e94f38 --- /dev/null +++ b/src/org/helioviewer/jhv/threads/JHVThread.java @@ -0,0 +1,49 @@ +package org.helioviewer.jhv.threads; + +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.ThreadFactory; + +import org.jetbrains.annotations.NotNull; + +public class JHVThread { + + public static void afterExecute(Runnable r, Throwable t) { + if (t == null && r instanceof Future) { + try { + Future future = (Future) r; + if (future.isDone()) { + future.get(); + } + } catch (CancellationException e) { + t = e; + } catch (ExecutionException e) { + t = e.getCause(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); // ??? ignore/reset + } + } + if (t != null) { + t.printStackTrace(); + } + } + + // this creates daemon threads + public static class NamedThreadFactory implements ThreadFactory { + + final String name; + + public NamedThreadFactory(String _name) { + name = _name; + } + + @Override + public Thread newThread(@NotNull Runnable r) { + Thread thread = new Thread(r, name); + thread.setDaemon(true); + return thread; + } + } + +} diff --git a/src/org/helioviewer/jhv/threads/JHVWorker.java b/src/org/helioviewer/jhv/threads/JHVWorker.java new file mode 100644 index 0000000..00d4cd3 --- /dev/null +++ b/src/org/helioviewer/jhv/threads/JHVWorker.java @@ -0,0 +1,33 @@ +package org.helioviewer.jhv.threads; + +import javax.swing.SwingWorker; + +public abstract class JHVWorker extends SwingWorker { + + private String name; + + public String getThreadName() { + return name; + } + + public void setThreadName(String _name) { + name = _name; + } + + @Override + protected T doInBackground() { + String currentName = Thread.currentThread().getName(); + if (name != null) + Thread.currentThread().setName("JHVWorker-" + name); + + T ret = backgroundWork(); + + if (name != null) + Thread.currentThread().setName(currentName); + + return ret; + } + + protected abstract T backgroundWork(); + +}