From d7c26bd5a90f3fcfd7551db2036079041322b84a Mon Sep 17 00:00:00 2001 From: Brandon Clayton <bclayton@usgs.gov> Date: Wed, 5 Mar 2025 10:03:59 -0700 Subject: [PATCH 1/3] add micronaut deps --- build.gradle | 3 ++- gradle.properties | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 53cefe6b..9e74507d 100644 --- a/build.gradle +++ b/build.gradle @@ -25,7 +25,8 @@ apply from: "${projectDir}/gradle/spotless.gradle" dependencies { api "com.google.guava:guava:${guavaVersion}" api "com.google.code.gson:gson:${gsonVersion}" - implementation "com.amazonaws:aws-lambda-java-core:${awsCoreVersion}" + implementation "io.micronaut:micronaut-http-client:${micronautVersion}" + implementation "io.micronaut.rxjava3:micronaut-rxjava3:${micronautRxVersion}" testImplementation "org.junit.jupiter:junit-jupiter:${junitVersion}" } diff --git a/gradle.properties b/gradle.properties index e4cc4563..ba77d2de 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,9 +1,10 @@ -awsCoreVersion = 1.2.1 junitVersion = 5.8.2 githooksVersion = 1.2.0 gitVersionVersion = 0.15.0 gsonVersion = 2.8.9 guavaVersion = 31.1-jre +micronautVersion = 3.2.3 +micronautRxVersion = 2.1.1 nodePluginVersion = 3.1.1 nodeVersion = 16.3.0 spotbugsVersion = 4.7.0 -- GitLab 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 2/3] 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 From 734cfdfdd03b7fad67a7a3094693b60027daa8f5 Mon Sep 17 00:00:00 2001 From: Brandon Clayton <bclayton@usgs.gov> Date: Wed, 5 Mar 2025 10:06:34 -0700 Subject: [PATCH 3/3] remove lambda function --- .../nshmp/calc/GroundMotionToCurveLambda.java | 216 ------------------ 1 file changed, 216 deletions(-) delete mode 100644 src/main/java/gov/usgs/earthquake/nshmp/calc/GroundMotionToCurveLambda.java diff --git a/src/main/java/gov/usgs/earthquake/nshmp/calc/GroundMotionToCurveLambda.java b/src/main/java/gov/usgs/earthquake/nshmp/calc/GroundMotionToCurveLambda.java deleted file mode 100644 index 2a1c6335..00000000 --- a/src/main/java/gov/usgs/earthquake/nshmp/calc/GroundMotionToCurveLambda.java +++ /dev/null @@ -1,216 +0,0 @@ -package gov.usgs.earthquake.nshmp.calc; - -import static com.google.common.base.Preconditions.checkNotNull; - -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; - -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.RequestStreamHandler; -import com.google.common.base.Charsets; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; - -import gov.usgs.earthquake.nshmp.data.MutableXySequence; -import gov.usgs.earthquake.nshmp.data.XySequence; -import gov.usgs.earthquake.nshmp.gmm.Gmm; -import gov.usgs.earthquake.nshmp.gmm.GroundMotion; -import gov.usgs.earthquake.nshmp.gmm.Imt; -import gov.usgs.earthquake.nshmp.tree.LogicTree; - -/** - * AWS Lambda function to perform ground motion to curve transformation. - * - * @see Transforms.GroundMotionsToCurves - * - * @author U.S. Geological Survey - */ -public class GroundMotionToCurveLambda implements RequestStreamHandler { - private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); - - /** - * Lambda handler to convert GroundMotion to HazardCurve. - * - * @see Transforms.GroundMotionsToCurves#apply - * - * @param inputStream The Lambda input stream - * @param outputStream The Lambda output stream - * @param context The Lambda context - * @throws IOException - */ - @Override - public void handleRequest( - InputStream inputStream, - OutputStream outputStream, - Context context) throws IOException { - var logger = context.getLogger(); - var reader = new BufferedReader(new InputStreamReader(inputStream, Charsets.UTF_8)); - var writer = - new PrintWriter(new BufferedWriter(new OutputStreamWriter(outputStream, Charsets.UTF_8))); - - var event = GSON.fromJson(reader, GmmCurve.class); - logger.log("Event: " + GSON.toJson(event)); - - event.exceedanceModel.treeExceedanceCombined( - event.logicTree, - event.truncationLevel, - event.σScale, - event.imt, - event.utilCurve); - event.utilCurve.multiply(event.rate); - - var hazardCurve = new HazardCurve(event.imt, event.gmm, event.utilCurve); - logger.log("GMM Curve: " + GSON.toJson(hazardCurve)); - - writer.write(GSON.toJson(hazardCurve, HazardCurve.class)); - - reader.close(); - writer.close(); - } - - /** - * Wrapper class for Lambda input. - */ - static class GmmCurve { - private final ExceedanceModel exceedanceModel; - private final Gmm gmm; - private final Imt imt; - private final LogicTree<GroundMotion> logicTree; - private final double rate; - private final double truncationLevel; - private final MutableXySequence utilCurve; - private final double σScale; - - private GmmCurve(Builder builder) { - exceedanceModel = builder.exceedanceModel; - gmm = builder.gmm; - imt = builder.imt; - logicTree = builder.logicTree; - rate = builder.rate; - truncationLevel = builder.truncationLevel; - utilCurve = builder.utilCurve; - σScale = builder.σScale; - } - - /** - * Returns a new GmmCurve builder. - */ - Builder builder() { - return new Builder(); - } - - /** - * Build a GmmCurve for Lambda handler. - * - * @see GroundMotionToCurveLambda#groundMotionsToCurvesHandler - */ - static class Builder { - private ExceedanceModel exceedanceModel; - private Gmm gmm; - private Imt imt; - private LogicTree<GroundMotion> logicTree; - private double rate; - private double truncationLevel; - private MutableXySequence utilCurve; - private double σScale; - - private Builder() {} - - Builder exceedanceModel(ExceedanceModel exceedanceModel) { - this.exceedanceModel = exceedanceModel; - return this; - } - - Builder gmm(Gmm gmm) { - this.gmm = gmm; - return this; - } - - Builder imt(Imt imt) { - this.imt = imt; - return this; - } - - Builder logicTree(LogicTree<GroundMotion> logicTree) { - this.logicTree = logicTree; - return this; - } - - Builder rate(double rate) { - this.rate = rate; - return this; - } - - Builder truncationLevel(double truncationLevel) { - this.truncationLevel = truncationLevel; - return this; - } - - Builder utilCurve(MutableXySequence utilCurve) { - this.utilCurve = utilCurve; - return this; - } - - Builder σScale(double σScale) { - this.σScale = σScale; - return this; - } - - GmmCurve build() { - checkNotNull(exceedanceModel); - checkNotNull(gmm); - checkNotNull(imt); - checkNotNull(logicTree); - checkNotNull(rate); - checkNotNull(truncationLevel); - checkNotNull(utilCurve); - checkNotNull(σScale); - return new GmmCurve(this); - } - } - } - - /** - * Single hazard curve returned from the Lambda function. - * - * @see GroundMotionToCurveLambda#groundMotionsToCurvesHandler - */ - static class HazardCurve { - private final Imt imt; - private final Gmm gmm; - private final XySequence curve; - - private HazardCurve(Imt imt, Gmm gmm, XySequence curve) { - this.imt = imt; - this.gmm = gmm; - this.curve = curve; - } - - /** - * Returns the Imt associated with the hazard curve. - */ - Imt imt() { - return imt; - } - - /** - * Returns the Gmm associated with the hazard curve. - */ - Gmm gmm() { - return gmm; - } - - /** - * Returns the curve for a specific Imt and Gmm. - */ - XySequence curve() { - return curve; - } - } -} -- GitLab