From 9cb3a4fa0b8fb979516f472e5d1124e68ce0e464 Mon Sep 17 00:00:00 2001
From: Brandon Clayton <bclayton@usgs.gov>
Date: Wed, 5 Mar 2025 10:04:09 -0700
Subject: [PATCH] move files from nshmp-utils-java

---
 .../internal/www/NshmpMicronautServlet.java   |  51 +++++
 .../nshmp/internal/www/ResponseBody.java      | 213 ++++++++++++++++++
 .../nshmp/internal/www/ResponseMetadata.java  |  18 ++
 .../nshmp/internal/www/WsUtils.java           | 100 ++++++++
 .../internal/www/meta/EnumParameter.java      |  22 ++
 .../nshmp/internal/www/meta/Status.java       |  18 ++
 .../internal/www/meta/StringParameter.java    |  20 ++
 7 files changed, 442 insertions(+)
 create mode 100644 src/main/java/gov/usgs/earthquake/nshmp/internal/www/NshmpMicronautServlet.java
 create mode 100644 src/main/java/gov/usgs/earthquake/nshmp/internal/www/ResponseBody.java
 create mode 100644 src/main/java/gov/usgs/earthquake/nshmp/internal/www/ResponseMetadata.java
 create mode 100644 src/main/java/gov/usgs/earthquake/nshmp/internal/www/WsUtils.java
 create mode 100644 src/main/java/gov/usgs/earthquake/nshmp/internal/www/meta/EnumParameter.java
 create mode 100644 src/main/java/gov/usgs/earthquake/nshmp/internal/www/meta/Status.java
 create mode 100644 src/main/java/gov/usgs/earthquake/nshmp/internal/www/meta/StringParameter.java

diff --git a/src/main/java/gov/usgs/earthquake/nshmp/internal/www/NshmpMicronautServlet.java b/src/main/java/gov/usgs/earthquake/nshmp/internal/www/NshmpMicronautServlet.java
new file mode 100644
index 00000000..81788783
--- /dev/null
+++ b/src/main/java/gov/usgs/earthquake/nshmp/internal/www/NshmpMicronautServlet.java
@@ -0,0 +1,51 @@
+package gov.usgs.earthquake.nshmp.internal.www;
+
+import org.reactivestreams.Publisher;
+
+import io.micronaut.core.type.MutableHeaders;
+import io.micronaut.http.HttpRequest;
+import io.micronaut.http.MutableHttpResponse;
+import io.micronaut.http.annotation.Filter;
+import io.micronaut.http.filter.HttpServerFilter;
+import io.micronaut.http.filter.ServerFilterChain;
+import io.reactivex.rxjava3.core.Flowable;
+import io.reactivex.rxjava3.schedulers.Schedulers;
+
+/**
+ * Custom NSHMP servlet implementation and URL helper class for Micronaut
+ * services.
+ *
+ * <p>This class sets custom response headers and provides a helper class to
+ * ensure serialized response URLs propagate the correct host and protocol from
+ * requests on USGS servers and caches that may have been forwarded.
+ *
+ * @author U.S. Geological Survey
+ */
+@Filter("/**")
+public class NshmpMicronautServlet implements HttpServerFilter {
+
+  /*
+   * Set CORS headers and content type.
+   *
+   * Because NSHMP services may be called by both the USGS website, other
+   * websites, and directly by 3rd party applications, responses generated by
+   * direct requests will not have the necessary header information that would
+   * be required by security protocols for web requests. This means that any
+   * initial direct request will pollute intermediate caches with a response
+   * that a browser will deem invalid.
+   */
+  @Override
+  public Publisher<MutableHttpResponse<?>> doFilter(
+      HttpRequest<?> request,
+      ServerFilterChain chain) {
+    return Flowable.just(chain).subscribeOn(Schedulers.io())
+        .switchMap(bool -> chain.proceed(request))
+        .doOnNext(res -> {
+          MutableHeaders headers = res.getHeaders();
+          headers.add("Access-Control-Allow-Origin", "*");
+          headers.add("Access-Control-Allow-Methods", "*");
+          headers.add("Access-Control-Allow-Headers", "*");
+        });
+  }
+
+}
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/internal/www/ResponseBody.java b/src/main/java/gov/usgs/earthquake/nshmp/internal/www/ResponseBody.java
new file mode 100644
index 00000000..650056c3
--- /dev/null
+++ b/src/main/java/gov/usgs/earthquake/nshmp/internal/www/ResponseBody.java
@@ -0,0 +1,213 @@
+package gov.usgs.earthquake.nshmp.internal.www;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.time.ZonedDateTime;
+
+import gov.usgs.earthquake.nshmp.internal.www.meta.Status;
+
+/**
+ * Generic wrapper around a web service response object that is typically
+ * serialized to JSON and sent back to requestor as an HttpResponse 'body'.
+ *
+ * <p>To create a response, use one of the three static builder methods:
+ * {@link Builder#error()}, {@link Builder#success()}, or
+ * {@link Builder#usage()}.
+ *
+ * @author U.S. Geological Survey
+ *
+ * @param <T> The request type
+ * @param <V> The response type
+ */
+public class ResponseBody<T, V> {
+
+  private final String name;
+  private final String date;
+  private final String status;
+  private final String url;
+  private final T request;
+  private final V response;
+  private final ResponseMetadata metadata;
+
+  private ResponseBody(Builder<T, V> builder) {
+    name = builder.name;
+    date = ZonedDateTime.now().format(WsUtils.DATE_FMT);
+    status = builder.status;
+    url = builder.url;
+    request = builder.request;
+    response = builder.response;
+    metadata = builder.metadata;
+  }
+
+  protected ResponseBody() {
+    date = null;
+    metadata = null;
+    name = null;
+    request = null;
+    response = null;
+    status = null;
+    url = null;
+  }
+
+  /**
+   * The date and time this request/response.
+   */
+  public String getDate() {
+    return date;
+  }
+
+  /**
+   * The metadata.
+   */
+  public ResponseMetadata getMetadata() {
+    return metadata;
+  }
+
+  /**
+   * The name of the service.
+   */
+  public String getName() {
+    return name;
+  }
+
+  /**
+   * The request object.
+   */
+  public T getRequest() {
+    return request;
+  }
+
+  /**
+   * The response object.
+   */
+  public V getResponse() {
+    return response;
+  }
+
+  /**
+   * The response status.
+   */
+  public String getStatus() {
+    return status;
+  }
+
+  /**
+   * The URL used to call the service
+   */
+  public String getUrl() {
+    return url;
+  }
+
+  /**
+   * Create a new builder initialized to an error response.
+   *
+   * @param <T> The request type
+   * @param <V> The response type
+   */
+  public static <T, V> Builder<T, V> error() {
+    return new Builder<T, V>(Status.ERROR);
+  }
+
+  /**
+   * Create a new builder initialized to a success response.
+   *
+   * @param <T> The request type
+   * @param <V> The response type
+   */
+  public static <T, V> Builder<T, V> success() {
+    return new Builder<T, V>(Status.SUCCESS);
+  }
+
+  /**
+   * Create a new builder initialized to a usage response.
+   *
+   * @param <T> The request type
+   * @param <V> The response type
+   */
+  public static <T, V> Builder<T, V> usage() {
+    return new Builder<T, V>(Status.USAGE);
+  }
+
+  /**
+   * A {@code ResponseBody} builder.
+   *
+   * @param <T> The request type
+   * @param <V> The response type
+   */
+  public static class Builder<T, V> {
+
+    private String name;
+    private String status;
+    private String url;
+    private ResponseMetadata metadata;
+    private T request;
+    private V response;
+
+    private Builder(String status) {
+      this.status = status;
+    }
+
+    /**
+     * Set the metadata.
+     *
+     * @param metadata The servie metadata
+     */
+    public Builder<T, V> metadata(ResponseMetadata metadata) {
+      this.metadata = metadata;
+      return this;
+    }
+
+    /**
+     * Set the service name.
+     *
+     * @param name of the service being called
+     */
+    public Builder<T, V> name(String name) {
+      this.name = name;
+      return this;
+    }
+
+    /**
+     * Set the request object.
+     *
+     * @param request object
+     */
+    public Builder<T, V> request(T request) {
+      this.request = request;
+      return this;
+    }
+
+    /**
+     * Set the response object.
+     *
+     * @param response object
+     */
+    public Builder<T, V> response(V response) {
+      this.response = response;
+      return this;
+    }
+
+    /**
+     * Set the url used to call the service.
+     *
+     * @param url used to generate this response
+     */
+    public Builder<T, V> url(String url) {
+      this.url = url;
+      return this;
+    }
+
+    /**
+     * Returns a new Response
+     */
+    public ResponseBody<T, V> build() {
+      checkNotNull(metadata);
+      checkNotNull(name);
+      checkNotNull(request);
+      checkNotNull(response);
+      checkNotNull(url);
+      checkNotNull(status);
+      return new ResponseBody<T, V>(this);
+    }
+  }
+}
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/internal/www/ResponseMetadata.java b/src/main/java/gov/usgs/earthquake/nshmp/internal/www/ResponseMetadata.java
new file mode 100644
index 00000000..f526c118
--- /dev/null
+++ b/src/main/java/gov/usgs/earthquake/nshmp/internal/www/ResponseMetadata.java
@@ -0,0 +1,18 @@
+package gov.usgs.earthquake.nshmp.internal.www;
+
+import gov.usgs.earthquake.nshmp.internal.AppVersion.VersionInfo;
+
+/**
+ * The response metadata with version info.
+ */
+public class ResponseMetadata {
+  public final VersionInfo[] repositories;
+
+  public ResponseMetadata(VersionInfo... repositories) {
+    this.repositories = repositories;
+  }
+
+  public VersionInfo[] getRepositories() {
+    return repositories;
+  }
+}
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/internal/www/WsUtils.java b/src/main/java/gov/usgs/earthquake/nshmp/internal/www/WsUtils.java
new file mode 100644
index 00000000..e45eb6f8
--- /dev/null
+++ b/src/main/java/gov/usgs/earthquake/nshmp/internal/www/WsUtils.java
@@ -0,0 +1,100 @@
+package gov.usgs.earthquake.nshmp.internal.www;
+
+import java.lang.reflect.Type;
+import java.time.format.DateTimeFormatter;
+import java.util.Optional;
+
+import com.google.common.collect.Range;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+
+import gov.usgs.earthquake.nshmp.gmm.GmmInput;
+import gov.usgs.earthquake.nshmp.gmm.GmmInput.Field;
+
+/**
+ * Web service utilities.
+ *
+ * @author U.S. Geological Survey
+ */
+public class WsUtils {
+
+  public static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern(
+      "yyyy-MM-dd'T'HH:mm:ssXXX");
+
+  public static <T, E extends Enum<E>> T checkValue(E key, T value) {
+    if (value == null) {
+      throw new IllegalStateException("Missing [" + key.toString() + "]");
+    }
+
+    return value;
+  }
+
+  /* Constrain all doubles to 8 decimal places */
+  public static final class DoubleSerializer implements JsonSerializer<Double> {
+    @Override
+    public JsonElement serialize(Double d, Type type, JsonSerializationContext context) {
+      double dOut = Double.valueOf(String.format("%.8g", d));
+      return new JsonPrimitive(dOut);
+    }
+  }
+
+  /* Convert NaN to null */
+  public static final class NaNSerializer implements JsonSerializer<Double> {
+    @Override
+    public JsonElement serialize(Double d, Type type, JsonSerializationContext context) {
+      return Double.isNaN(d) ? null : new JsonPrimitive(d);
+    }
+  }
+
+  public static final class ConstraintsSerializer implements JsonSerializer<GmmInput.Constraints> {
+    @Override
+    public JsonElement serialize(
+        GmmInput.Constraints constraints,
+        Type type,
+        JsonSerializationContext context) {
+      JsonArray json = new JsonArray();
+
+      for (Field field : Field.values()) {
+        Optional<?> opt = constraints.get(field);
+        if (opt.isPresent()) {
+          Range<?> value = (Range<?>) opt.orElseThrow();
+          Constraint constraint = new Constraint(
+              field.id,
+              value.lowerEndpoint(),
+              value.upperEndpoint());
+          json.add(context.serialize(constraint));
+        }
+      }
+
+      return json;
+    }
+  }
+
+  public static final class EnumSerializer<E extends Enum<E>> implements JsonSerializer<E> {
+    @Override
+    public JsonElement serialize(E src, Type type, JsonSerializationContext context) {
+      JsonObject jObj = new JsonObject();
+      jObj.addProperty("value", src.name());
+      jObj.addProperty("display", src.toString());
+
+      return jObj;
+    }
+  }
+
+  @SuppressWarnings("unused")
+  private static class Constraint {
+    final String id;
+    final Object min;
+    final Object max;
+
+    Constraint(String id, Object min, Object max) {
+      this.id = id;
+      this.min = min;
+      this.max = max;
+    }
+  }
+}
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/internal/www/meta/EnumParameter.java b/src/main/java/gov/usgs/earthquake/nshmp/internal/www/meta/EnumParameter.java
new file mode 100644
index 00000000..2e0956fe
--- /dev/null
+++ b/src/main/java/gov/usgs/earthquake/nshmp/internal/www/meta/EnumParameter.java
@@ -0,0 +1,22 @@
+package gov.usgs.earthquake.nshmp.internal.www.meta;
+
+import java.util.Set;
+
+/**
+ * An enum parameter.
+ *
+ * @author U.S. Geological Survey
+ *
+ * @param <E> The enum type
+ */
+public final class EnumParameter<E extends Enum<E>> {
+
+  private final String label;
+  private final Set<E> values;
+
+  public EnumParameter(String label, Set<E> values) {
+    this.label = label;
+    this.values = values;
+  }
+
+}
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/internal/www/meta/Status.java b/src/main/java/gov/usgs/earthquake/nshmp/internal/www/meta/Status.java
new file mode 100644
index 00000000..b41c2059
--- /dev/null
+++ b/src/main/java/gov/usgs/earthquake/nshmp/internal/www/meta/Status.java
@@ -0,0 +1,18 @@
+package gov.usgs.earthquake.nshmp.internal.www.meta;
+
+/**
+ * Service request status identifier.
+ *
+ * @author U.S. Geological Survey
+ */
+public class Status {
+
+  /** Error reponse status. */
+  public static final String ERROR = "error";
+
+  /** Success reponse status. */
+  public static final String SUCCESS = "success";
+
+  /** Usage reponse status. */
+  public static final String USAGE = "usage";
+}
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/internal/www/meta/StringParameter.java b/src/main/java/gov/usgs/earthquake/nshmp/internal/www/meta/StringParameter.java
new file mode 100644
index 00000000..14a5ab1f
--- /dev/null
+++ b/src/main/java/gov/usgs/earthquake/nshmp/internal/www/meta/StringParameter.java
@@ -0,0 +1,20 @@
+package gov.usgs.earthquake.nshmp.internal.www.meta;
+
+import java.util.Set;
+
+/**
+ * A string parameter.
+ *
+ * @author U.S. Geological Survey
+ */
+public class StringParameter {
+
+  public final String label;
+  public final Set<String> values;
+
+  public StringParameter(String label, Set<String> values) {
+    this.label = label;
+    this.values = values;
+  }
+
+}
-- 
GitLab