diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml
index 1ee6d49cc..68c5d945a 100644
--- a/.github/workflows/maven.yml
+++ b/.github/workflows/maven.yml
@@ -31,8 +31,15 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest]
- java: [17, 21, 22]
+ java: [17, 21, 25-ea]
jdk: [temurin]
+ include:
+ - java: 17
+ maven_profile: ""
+ - java: 21
+ maven_profile: "-Pjdk21"
+ - java: 25-ea
+ maven_profile: "-Pjdk25"
fail-fast: false
runs-on: ${{ matrix.os }}
@@ -48,7 +55,15 @@ jobs:
java-version: ${{ matrix.java }}
cache: 'maven'
+ - name: Set JAVA_HOME for specific versions
+ run: |
+ if [[ "${{ matrix.java }}" == "21" ]]; then
+ echo "JAVA_HOME_21=$JAVA_HOME" >> $GITHUB_ENV
+ elif [[ "${{ matrix.java }}" == "25-ea" ]]; then
+ echo "JAVA_HOME_25=$JAVA_HOME" >> $GITHUB_ENV
+ fi
+
- name: Build with Maven
run: |
- ./mvnw clean install -B -q
+ ./mvnw clean install -B -q ${{ matrix.maven_profile }}
echo "done"
diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties
index ecbbc633c..44f3cf2c1 100644
--- a/.mvn/wrapper/maven-wrapper.properties
+++ b/.mvn/wrapper/maven-wrapper.properties
@@ -1,18 +1,2 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-wrapperVersion=3.3.1
-distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.8/apache-maven-3.9.8-bin.zip
+distributionType=only-script
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.11/apache-maven-3.9.11-bin.zip
diff --git a/README.md b/README.md
index 69853c857..6e7d075a1 100644
--- a/README.md
+++ b/README.md
@@ -17,7 +17,7 @@
[![Maven][maven-version-image]][maven-version-link]
[](https://github.com/GoogleCloudPlatform/appengine-java-standard/blob/main/CODE_OF_CONDUCT.md)
-# Google App Engine Standard Environment Source Code for Java 8, Java 11, Java 17, Java 21.
+# Google App Engine Standard Environment Source Code for Java 17, Java 21, Java 25.
This repository contains the Java Source Code for [Google App Engine
@@ -27,13 +27,13 @@ standard environment][ae-docs], the production runtime, the AppEngine APIs, and
## Prerequisites
-### Use a JDK8 environment, so it can build the Java8 GAE runtime.
+### Use a JDK17 environment, so it can build the Java17 GAE runtime.
-[jdk8](https://adoptium.net/), but using a JDK11 or JDK17 of JDK21 is also possible.
+[jdk8](https://adoptium.net/), but using a JDK21 or JDK25 is also possible.
-The shared code base is also used for GAE Java 11, Java 17 and Java 21 build and test targets, using GitHub actions:
+The shared code base is also used for GAE Java 17, Java 17 and Java 25 build and test targets, using GitHub actions:
-- [Java 17/21 Continuous Integration](https://github.com/GoogleCloudPlatform/appengine-java-standard/actions/workflows/maven.yml)
+- [Java 17/21/25 Continuous Integration](https://github.com/GoogleCloudPlatform/appengine-java-standard/actions/workflows/maven.yml)
## Releases
@@ -48,7 +48,7 @@ Soon we will stop entirely pushing internal 1.9.xx artifacts and encourage all A
Orange items are public modules artifacts and yellow are internal ones.
Modules ending with * are only used on the production server side.
-
+
### App Engine Java APIs
@@ -69,7 +69,7 @@ Source code for all public APIs for com.google.appengine.api.* packages.
com.google.appengineappengine-api-1.0-sdk
- 2.0.28
+ 2.0.39javax.servlet
@@ -89,23 +89,44 @@ Source code for all public APIs for com.google.appengine.api.* packages.
com.google.appengineappengine-api-1.0-sdk
- 2.0.28
+ 2.0.39
- javax.servlet
- jakarta.servlet-api
+ jakarta.servlet
+ jakarta.servlet-api6.0.0provided
-
+
...
```
-* Java 21 with Jakarta or javax appengine-web.xml
+* Maven Java 25 Alpha with Jakarta EE 11 support pom.xml (EE10 is not supported in Java25, EE11 is fully compatible with EE10)
+
+ ```
+ war
+ ...
+
+
+ com.google.appengine
+ appengine-api-1.0-sdk
+ 3.0.0-beta
+
+
+ jakarta.servlet
+ jakarta.servlet-api
+ 6.1.0
+ provided
+
+ ...
+ ```
+
+
+* Java 21/25 with javax EE8 profile appengine-web.xml
```
- java21
+ java21 <-- or java25 alpha-->
true
+ 2.0.39
```
@@ -211,7 +243,7 @@ We moved `com.google.appengine.api.memcache.stdimpl` and its old dependency
com.google.appengineappengine-api-legacy.jar/artifactId>
- 2.0.28
+ 2.0.39
```
@@ -226,19 +258,19 @@ We moved `com.google.appengine.api.memcache.stdimpl` and its old dependency
com.google.appengineappengine-testing
- 2.0.28
+ 2.0.39testcom.google.appengineappengine-api-stubs
- 2.0.28
+ 2.0.39testcom.google.appengineappengine-tools-sdk
- 2.0.28
+ 2.0.39test
```
@@ -276,9 +308,9 @@ Source code for the App Engine production application server and utilities. It i
- [End-to-End tests](https://github.com/GoogleCloudPlatform/appengine-java-standard/tree/master/runtime/test)
- [Source Code for runtime utilities](https://github.com/GoogleCloudPlatform/appengine-java-standard/tree/master/runtime/util)
-## Default entrypoint used by Java11, Java17 and Java21
+## Default entrypoint used by Java17, Java21 and Java25
-The Java 11, Java 17 and 21 runtimes can benefit from extra user configuration when starting the JVM for web apps.
+The Java 17, Java 21 and 25 runtimes can benefit from extra user configuration when starting the JVM for web apps.
The default entrypoint used to boot the JVM is generated by App Engine Buildpacks.
Essentially, it is equivalent to define this entrypoint in the `appengine-web.xml` file. For example:
@@ -292,7 +324,7 @@ By default, we use `--add-opens java.base/java.lang=ALL-UNNAMED --add-opens jav
## Entry Point Features
-The entry point for the Java 11, Java 17, 21 runtimes can be customized with user-defined environment variables added in the `appengine-web.xml` configuration file.
+The entry point for the Java 17, Java 21, 25 runtimes can be customized with user-defined environment variables added in the `appengine-web.xml` configuration file.
The following table indicates the environment variables that can be used to enable/disable/configure features, and the default values if they are not set:
diff --git a/THIRD-PARTY.txt b/THIRD-PARTY.txt
index ca9dfa897..1febffad1 100644
--- a/THIRD-PARTY.txt
+++ b/THIRD-PARTY.txt
@@ -1,227 +1,701 @@
-
The repository contains 3rd-party code under the following licenses:
+ Apache 2.0 License
+
+ * Apache MINA Core (org.apache.mina:mina-core:2.2.3 - https://mina.apache.org/mina-core/)
+
+ Apache License, version 2.0
+
+ * JBoss Logging 3 (org.jboss.logging:jboss-logging:3.4.3.Final - http://www.jboss.org)
+
Apache License, Version 2.0
- * Apache Ant Core (org.apache.ant:ant:1.10.12 - https://ant.apache.org/)
- * Apache Ant Launcher (org.apache.ant:ant-launcher:1.10.12 - https://ant.apache.org/)
- * Apache Commons Codec (commons-codec:commons-codec:1.15 - https://commons.apache.org/proper/commons-codec/)
+ * Apache Ant Core (org.apache.ant:ant:1.10.15 - https://ant.apache.org/)
+ * Apache Ant Launcher (org.apache.ant:ant-launcher:1.10.15 - https://ant.apache.org/)
+ * Apache Commons Codec (commons-codec:commons-codec:1.19.0 - https://commons.apache.org/proper/commons-codec/)
+ * Apache Commons Collections (org.apache.commons:commons-collections4:4.4 - https://commons.apache.org/proper/commons-collections/)
+ * Apache Commons Lang (org.apache.commons:commons-lang3:3.13.0 - https://commons.apache.org/proper/commons-lang/)
* Apache Commons Logging (commons-logging:commons-logging:1.2 - http://commons.apache.org/proper/commons-logging/)
- * Apache HttpClient (org.apache.httpcomponents:httpclient:4.5.13 - http://hc.apache.org/httpcomponents-client)
- * Apache HttpClient Mime (org.apache.httpcomponents:httpmime:4.5.13 - http://hc.apache.org/httpcomponents-client)
- * Apache HttpCore (org.apache.httpcomponents:httpcore:4.4.15 - http://hc.apache.org/httpcomponents-core-ga)
- * Apache HttpCore NIO (org.apache.httpcomponents:httpcore-nio:4.4.15 - http://hc.apache.org/httpcomponents-core-ga)
- * Apache HTTP transport v2 for the Google HTTP Client Library for Java. (com.google.http-client:google-http-client-apache-v2:1.42.1 - https://github.com/googleapis/google-http-java-client/google-http-client-apache-v2)
+ * Apache Directory API ASN.1 API (org.apache.directory.api:api-asn1-api:2.1.5 - https://directory.apache.org/api-parent/api-asn1-parent/api-asn1-api/)
+ * Apache Directory API ASN.1 BER (org.apache.directory.api:api-asn1-ber:2.1.5 - https://directory.apache.org/api-parent/api-asn1-parent/api-asn1-ber/)
+ * Apache Directory LDAP API I18n (org.apache.directory.api:api-i18n:2.1.5 - https://directory.apache.org/api-parent/api-i18n/)
+ * Apache Directory LDAP API Model (org.apache.directory.api:api-ldap-model:2.1.5 - https://directory.apache.org/api-parent/api-ldap-parent/api-ldap-model/)
+ * Apache Directory LDAP API Utilities (org.apache.directory.api:api-util:2.1.5 - https://directory.apache.org/api-parent/api-util/)
+ * Apache HttpClient (org.apache.httpcomponents:httpclient:4.5.14 - http://hc.apache.org/httpcomponents-client-ga)
+ * Apache HttpClient Mime (org.apache.httpcomponents:httpmime:4.5.14 - http://hc.apache.org/httpcomponents-client-ga)
+ * Apache HttpCore (org.apache.httpcomponents:httpcore:4.4.16 - http://hc.apache.org/httpcomponents-core-ga)
+ * Apache HttpCore NIO (org.apache.httpcomponents:httpcore-nio:4.4.16 - http://hc.apache.org/httpcomponents-core-ga)
+ * Apache HTTP transport v2 for the Google HTTP Client Library for Java. (com.google.http-client:google-http-client-apache-v2:2.0.0 - https://github.com/googleapis/google-http-java-client/google-http-client-apache-v2)
* Apache Log4j API (org.apache.logging.log4j:log4j-api:2.17.2 - https://logging.apache.org/log4j/2.x/log4j-api/)
* Apache Log4j to SLF4J Adapter (org.apache.logging.log4j:log4j-to-slf4j:2.17.2 - https://logging.apache.org/log4j/2.x/log4j-to-slf4j/)
- * ASM based accessors helper used by json-smart (net.minidev:accessors-smart:2.4.8 - https://urielch.github.io/)
+ * Apache Lucene (module: common) (org.apache.lucene:lucene-analysis-common:9.11.1 - https://lucene.apache.org/)
+ * Apache Lucene (module: facet) (org.apache.lucene:lucene-facet:9.11.1 - https://lucene.apache.org/)
+ * Apache Lucene (module: highlighter) (org.apache.lucene:lucene-highlighter:9.11.1 - https://lucene.apache.org/)
+ * Apache Lucene (module: join) (org.apache.lucene:lucene-join:9.11.1 - https://lucene.apache.org/)
+ * Apache Lucene (module: memory) (org.apache.lucene:lucene-memory:9.11.1 - https://lucene.apache.org/)
+ * Apache Lucene (module: queries) (org.apache.lucene:lucene-queries:9.11.1 - https://lucene.apache.org/)
+ * Apache Lucene (module: queryparser) (org.apache.lucene:lucene-queryparser:9.11.1 - https://lucene.apache.org/)
+ * Apache Lucene (module: sandbox) (org.apache.lucene:lucene-sandbox:9.11.1 - https://lucene.apache.org/)
+ * Apache ServiceMix :: Bundles :: antlr (org.apache.servicemix.bundles:org.apache.servicemix.bundles.antlr:2.7.7_5 - http://servicemix.apache.org/bundles-pom/org.apache.servicemix.bundles.antlr/)
+ * Apache Standard Taglib Implementation (org.apache.taglibs:taglibs-standard-impl:1.2.5 - http://tomcat.apache.org/taglibs/standard-1.2.5/taglibs-standard-impl)
+ * Apache Standard Taglib Specification API (org.apache.taglibs:taglibs-standard-spec:1.2.5 - http://tomcat.apache.org/taglibs/standard-1.2.5/taglibs-standard-spec)
+ * Arrow Format (org.apache.arrow:arrow-format:17.0.0 - https://arrow.apache.org/arrow-format/)
+ * Arrow Memory - Core (org.apache.arrow:arrow-memory-core:17.0.0 - https://arrow.apache.org/arrow-memory/arrow-memory-core/)
+ * Arrow Memory - Netty (org.apache.arrow:arrow-memory-netty:17.0.0 - https://arrow.apache.org/arrow-memory/arrow-memory-netty/)
+ * Arrow Memory - Netty Buffer (org.apache.arrow:arrow-memory-netty-buffer-patch:17.0.0 - https://arrow.apache.org/arrow-memory/arrow-memory-netty-buffer-patch/)
+ * Arrow Vectors (org.apache.arrow:arrow-vector:17.0.0 - https://arrow.apache.org/arrow-vector/)
+ * ASM based accessors helper used by json-smart (net.minidev:accessors-smart:2.4.11 - https://urielch.github.io/)
* AssertJ fluent assertions (org.assertj:assertj-core:3.22.0 - https://assertj.github.io/doc/assertj-core/)
- * Auto Common Libraries (com.google.auto:auto-common:1.2 - https://github.com/google/auto/tree/master/common)
- * AutoService (com.google.auto.service:auto-service-annotations:1.0.1 - https://github.com/google/auto/tree/master/service)
- * AutoService Processor (com.google.auto.service:auto-service:1.0.1 - https://github.com/google/auto/tree/master/service)
- * AutoValue Annotations (com.google.auto.value:auto-value-annotations:1.9 - https://github.com/google/auto/tree/master/value)
- * AutoValue Processor (com.google.auto.value:auto-value:1.9 - https://github.com/google/auto/tree/master/value)
- * BigQuery (com.google.cloud:google-cloud-bigquery:2.10.10 - https://github.com/googleapis/java-bigquery)
- * BigQuery API v2-rev20220326-1.32.1 (com.google.apis:google-api-services-bigquery:v2-rev20220326-1.32.1 - http://nexus.sonatype.org/oss-repository-hosting.html/google-api-services-bigquery)
- * Byte Buddy (without dependencies) (net.bytebuddy:byte-buddy:1.12.12 - https://bytebuddy.net/byte-buddy)
- * Byte Buddy (without dependencies) (net.bytebuddy:byte-buddy:1.12.13 - https://bytebuddy.net/byte-buddy)
- * Byte Buddy agent (net.bytebuddy:byte-buddy-agent:1.12.12 - https://bytebuddy.net/byte-buddy-agent)
- * Byte Buddy agent (net.bytebuddy:byte-buddy-agent:1.12.13 - https://bytebuddy.net/byte-buddy-agent)
+ * Auto Common Libraries (com.google.auto:auto-common:1.2.1 - https://github.com/google/auto/tree/master/common)
+ * AutoService (com.google.auto.service:auto-service-annotations:1.1.1 - https://github.com/google/auto/tree/main/service)
+ * AutoService Processor (com.google.auto.service:auto-service:1.1.1 - https://github.com/google/auto/tree/main/service)
+ * AutoValue Annotations (com.google.auto.value:auto-value-annotations:1.11.0 - https://github.com/google/auto/tree/main/value)
+ * AutoValue Processor (com.google.auto.value:auto-value:1.11.0 - https://github.com/google/auto/tree/main/value)
+ * Awaitility (org.awaitility:awaitility:4.3.0 - http://awaitility.org)
+ * BigQuery (com.google.cloud:google-cloud-bigquery:2.54.2 - https://github.com/googleapis/java-bigquery)
+ * BigQuery API v2-rev20250706-2.0.0 (com.google.apis:google-api-services-bigquery:v2-rev20250706-2.0.0 - http://nexus.sonatype.org/oss-repository-hosting.html/google-api-services-bigquery)
+ * BigQuery Storage (com.google.cloud:google-cloud-bigquerystorage:3.16.3 - https://github.com/googleapis/java-bigquerystorage)
+ * brotli4j (com.aayushatharva.brotli4j:brotli4j:1.17.0 - https://github.com/hyperxpro/Brotli4j/brotli4j)
+ * Byte Buddy (without dependencies) (net.bytebuddy:byte-buddy:1.12.23 - https://bytebuddy.net/byte-buddy)
+ * Byte Buddy (without dependencies) (net.bytebuddy:byte-buddy:1.17.5 - https://bytebuddy.net/byte-buddy)
+ * Byte Buddy (without dependencies) (net.bytebuddy:byte-buddy:1.17.6 - https://bytebuddy.net/byte-buddy)
+ * Byte Buddy agent (net.bytebuddy:byte-buddy-agent:1.12.23 - https://bytebuddy.net/byte-buddy-agent)
+ * Byte Buddy agent (net.bytebuddy:byte-buddy-agent:1.17.6 - https://bytebuddy.net/byte-buddy-agent)
+ * Caffeine cache (com.github.ben-manes.caffeine:caffeine:2.9.3 - https://github.com/ben-manes/caffeine)
+ * Caffeine cache (com.github.ben-manes.caffeine:caffeine:3.2.0 - https://github.com/ben-manes/caffeine)
+ * CDI APIs (jakarta.enterprise:jakarta.enterprise.cdi-api:4.0.1 - http://cdi-spec.org)
+ * CDI APIs (jakarta.enterprise:jakarta.enterprise.cdi-api:4.1.0 - http://cdi-spec.org)
+ * CDI Language Model (jakarta.enterprise:jakarta.enterprise.lang-model:4.0.1 - https://projects.eclipse.org/projects/ee4j/jakarta.enterprise.cdi-parent/jakarta.enterprise.lang-model)
+ * CDI Language Model (jakarta.enterprise:jakarta.enterprise.lang-model:4.1.0 - https://projects.eclipse.org/projects/ee4j/jakarta.enterprise.cdi-parent/jakarta.enterprise.lang-model)
* Cloud SQL Admin API v1beta4-rev20220310-1.32.1 (com.google.apis:google-api-services-sqladmin:v1beta4-rev20220310-1.32.1 - http://nexus.sonatype.org/oss-repository-hosting.html/google-api-services-sqladmin)
* Cloud SQL Core Socket Factory (Core Library, don't depend on this directly) (com.google.cloud.sql:jdbc-socket-factory-core:1.5.0 - https://github.com/GoogleCloudPlatform/cloud-sql-mysql-socket-factory/jdbc-socket-factory-core)
* Cloud SQL MySQL Socket Factory (for Connector/J 5.x) (com.google.cloud.sql:mysql-socket-factory:1.5.0 - https://github.com/GoogleCloudPlatform/cloud-sql-mysql-socket-factory/mysql-socket-factory)
- * Cloud Storage JSON API v1-rev20220401-1.32.1 (com.google.apis:google-api-services-storage:v1-rev20220401-1.32.1 - http://nexus.sonatype.org/oss-repository-hosting.html/google-api-services-storage)
- * datastore-v1-proto-client (com.google.cloud.datastore:datastore-v1-proto-client:2.10.1 - https://github.com/googleapis/java-datastore/datastore-v1-proto-client)
- * EasyMock (org.easymock:easymock:4.3 - http://easymock.org/easymock)
- * error-prone annotations (com.google.errorprone:error_prone_annotations:2.15.0 - https://errorprone.info/error_prone_annotations)
+ * Cloud Storage JSON API v1-rev20250815-2.0.0 (com.google.apis:google-api-services-storage:v1-rev20250815-2.0.0 - http://nexus.sonatype.org/oss-repository-hosting.html/google-api-services-storage)
+ * datastore-v1-proto-client (com.google.cloud.datastore:datastore-v1-proto-client:2.18.2 - https://github.com/googleapis/java-datastore/datastore-v1-proto-client)
+ * EasyMock (org.easymock:easymock:5.6.0 - http://easymock.org/easymock)
+ * error-prone annotations (com.google.errorprone:error_prone_annotations:2.41.0 - https://errorprone.info/error_prone_annotations)
* FindBugs-jsr305 (com.google.code.findbugs:jsr305:3.0.2 - http://findbugs.sourceforge.net/)
- * Flogger (com.google.flogger:flogger:0.7.4 - https://github.com/google/flogger)
- * Flogger System Backend (com.google.flogger:flogger-system-backend:0.7.4 - https://github.com/google/flogger)
+ * Flogger (com.google.flogger:flogger:0.9 - https://github.com/google/flogger)
+ * Flogger System Backend (com.google.flogger:flogger-system-backend:0.9 - https://github.com/google/flogger)
+ * gapic-google-cloud-storage-v2 (com.google.api.grpc:gapic-google-cloud-storage-v2:2.57.0 - https://github.com/googleapis/java-storage/gapic-google-cloud-storage-v2)
* Google Android Annotations Library (com.google.android:annotations:4.1.1.4 - http://source.android.com/)
- * Google APIs Client Library for Java (com.google.api-client:google-api-client:2.0.0 - https://github.com/googleapis/google-api-java-client/google-api-client)
- * Google App Engine extensions to the Google API Client Library for Java. (com.google.api-client:google-api-client-appengine:2.0.0 - https://github.com/googleapis/google-api-java-client/google-api-client-appengine)
- * Google App Engine extensions to the Google HTTP Client Library for Java. (com.google.http-client:google-http-client-appengine:1.42.2 - https://github.com/googleapis/google-http-java-client/google-http-client-appengine)
- * Google Cloud Core (com.google.cloud:google-cloud-core:2.6.1 - https://github.com/googleapis/java-core)
- * Google Cloud Core gRPC (com.google.cloud:google-cloud-core-grpc:2.3.3 - https://github.com/googleapis/java-core)
- * Google Cloud Core HTTP (com.google.cloud:google-cloud-core-http:2.6.0 - https://github.com/googleapis/java-core)
- * Google Cloud Datastore (com.google.cloud:google-cloud-datastore:2.4.0 - https://github.com/googleapis/java-datastore)
- * Google Cloud Logging (com.google.cloud:google-cloud-logging:3.7.5 - https://github.com/googleapis/java-logging)
- * Google Cloud Spanner (com.google.cloud:google-cloud-spanner:6.17.3 - https://github.com/googleapis/java-spanner)
- * Google Cloud Storage (com.google.cloud:google-cloud-storage:2.6.1 - https://github.com/googleapis/java-storage)
- * Google HTTP Client Library for Java (com.google.http-client:google-http-client:1.42.2 - https://github.com/googleapis/google-http-java-client/google-http-client)
- * Google Logger (com.google.flogger:google-extensions:0.7.4 - https://github.com/google/flogger)
- * Google OAuth Client Library for Java (com.google.oauth-client:google-oauth-client:1.34.1 - https://github.com/googleapis/google-oauth-java-client/google-oauth-client)
- * gRPC extension library for Google Cloud Platform (com.google.cloud:grpc-gcp:1.1.0 - https://github.com/GoogleCloudPlatform/grpc-gcp-java/tree/master/grpc-gcp)
- * grpc-google-cloud-spanner-admin-database-v1 (com.google.api.grpc:grpc-google-cloud-spanner-admin-database-v1:6.17.3 - https://github.com/googleapis/java-spanner/grpc-google-cloud-spanner-admin-database-v1)
- * grpc-google-cloud-spanner-admin-instance-v1 (com.google.api.grpc:grpc-google-cloud-spanner-admin-instance-v1:6.17.3 - https://github.com/googleapis/java-spanner/grpc-google-cloud-spanner-admin-instance-v1)
- * grpc-google-cloud-spanner-v1 (com.google.api.grpc:grpc-google-cloud-spanner-v1:6.17.3 - https://github.com/googleapis/java-spanner/grpc-google-cloud-spanner-v1)
- * grpc-google-common-protos (com.google.api.grpc:grpc-google-common-protos:2.7.0 - https://github.com/googleapis/java-iam/grpc-google-common-protos)
- * Gson (com.google.code.gson:gson:2.9.0 - https://github.com/google/gson/gson)
- * GSON extensions to the Google HTTP Client Library for Java. (com.google.http-client:google-http-client-gson:1.40.1 - https://github.com/googleapis/google-http-java-client/google-http-client-gson)
- * GSON extensions to the Google HTTP Client Library for Java. (com.google.http-client:google-http-client-gson:1.42.0 - https://github.com/googleapis/google-http-java-client/google-http-client-gson)
- * GSON extensions to the Google HTTP Client Library for Java. (com.google.http-client:google-http-client-gson:1.42.1 - https://github.com/googleapis/google-http-java-client/google-http-client-gson)
- * Guava: Google Core Libraries for Java (com.google.guava:guava:31.1-jre - https://github.com/google/guava)
- * Guava InternalFutureFailureAccess and InternalFutures (com.google.guava:failureaccess:1.0.1 - https://github.com/google/guava/failureaccess)
+ * Google APIs Client Library for Java (com.google.api-client:google-api-client:2.8.1 - https://github.com/googleapis/google-api-java-client/google-api-client)
+ * Google App Engine extensions to the Google API Client Library for Java. (com.google.api-client:google-api-client-appengine:2.8.1 - https://github.com/googleapis/google-api-java-client/google-api-client-appengine)
+ * Google App Engine extensions to the Google HTTP Client Library for Java. (com.google.http-client:google-http-client-appengine:1.47.1 - https://github.com/googleapis/google-http-java-client/google-http-client-appengine)
+ * Google Cloud Core (com.google.cloud:google-cloud-core:2.48.0 - https://github.com/googleapis/sdk-platform-java)
+ * Google Cloud Core (com.google.cloud:google-cloud-core:2.60.1 - https://github.com/googleapis/sdk-platform-java)
+ * Google Cloud Core gRPC (com.google.cloud:google-cloud-core-grpc:2.60.0 - https://github.com/googleapis/sdk-platform-java)
+ * Google Cloud Core HTTP (com.google.cloud:google-cloud-core-http:2.48.0 - https://github.com/googleapis/sdk-platform-java)
+ * Google Cloud Core HTTP (com.google.cloud:google-cloud-core-http:2.60.0 - https://github.com/googleapis/sdk-platform-java)
+ * Google Cloud Datastore (com.google.cloud:google-cloud-datastore:2.24.3 - https://github.com/googleapis/java-datastore)
+ * Google Cloud Datastore (com.google.cloud:google-cloud-datastore:2.31.4 - https://github.com/googleapis/java-datastore)
+ * Google Cloud Logging (com.google.cloud:google-cloud-logging:3.23.3 - https://github.com/googleapis/java-logging)
+ * Google Cloud Monitoring (com.google.cloud:google-cloud-monitoring:3.63.0 - https://github.com/googleapis/google-cloud-java)
+ * Google Cloud Spanner (com.google.cloud:google-cloud-spanner:6.99.0 - https://github.com/googleapis/java-spanner)
+ * Google Cloud Storage (com.google.cloud:google-cloud-storage:2.57.0 - https://github.com/googleapis/java-storage)
+ * Google HTTP Client Library for Java (com.google.http-client:google-http-client:1.47.1 - https://github.com/googleapis/google-http-java-client/google-http-client)
+ * Google Logger (com.google.flogger:google-extensions:0.9 - https://github.com/google/flogger)
+ * Google OAuth Client Library for Java (com.google.oauth-client:google-oauth-client:1.39.0 - https://github.com/googleapis/google-oauth-java-client/google-oauth-client)
+ * gRPC extension library for Google Cloud Platform (com.google.cloud:grpc-gcp:1.6.1 - https://github.com/GoogleCloudPlatform/grpc-gcp-java/tree/master/grpc-gcp)
+ * grpc-google-cloud-bigquerystorage-v1 (com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1:3.16.3 - https://github.com/googleapis/java-bigquerystorage/grpc-google-cloud-bigquerystorage-v1)
+ * grpc-google-cloud-bigquerystorage-v1beta1 (com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1beta1:0.188.3 - https://github.com/googleapis/java-bigquerystorage/grpc-google-cloud-bigquerystorage-v1beta1)
+ * grpc-google-cloud-bigquerystorage-v1beta2 (com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1beta2:0.188.3 - https://github.com/googleapis/java-bigquerystorage/grpc-google-cloud-bigquerystorage-v1beta2)
+ * grpc-google-cloud-datastore-admin-v1 (com.google.api.grpc:grpc-google-cloud-datastore-admin-v1:2.24.3 - https://github.com/googleapis/java-datastore/grpc-google-cloud-datastore-admin-v1)
+ * grpc-google-cloud-datastore-admin-v1 (com.google.api.grpc:grpc-google-cloud-datastore-admin-v1:2.31.4 - https://github.com/googleapis/java-datastore/grpc-google-cloud-datastore-admin-v1)
+ * grpc-google-cloud-datastore-v1 (com.google.api.grpc:grpc-google-cloud-datastore-v1:2.31.4 - https://github.com/googleapis/java-datastore/grpc-google-cloud-datastore-v1)
+ * grpc-google-cloud-spanner-admin-database-v1 (com.google.api.grpc:grpc-google-cloud-spanner-admin-database-v1:6.99.0 - https://github.com/googleapis/java-spanner/grpc-google-cloud-spanner-admin-database-v1)
+ * grpc-google-cloud-spanner-admin-instance-v1 (com.google.api.grpc:grpc-google-cloud-spanner-admin-instance-v1:6.99.0 - https://github.com/googleapis/java-spanner/grpc-google-cloud-spanner-admin-instance-v1)
+ * grpc-google-cloud-spanner-v1 (com.google.api.grpc:grpc-google-cloud-spanner-v1:6.99.0 - https://github.com/googleapis/java-spanner/grpc-google-cloud-spanner-v1)
+ * grpc-google-cloud-storage-v2 (com.google.api.grpc:grpc-google-cloud-storage-v2:2.57.0 - https://github.com/googleapis/java-storage/grpc-google-cloud-storage-v2)
+ * grpc-google-common-protos (com.google.api.grpc:grpc-google-common-protos:2.61.0 - https://github.com/googleapis/sdk-platform-java)
+ * Gson (com.google.code.gson:gson:2.13.2 - https://github.com/google/gson)
+ * GSON extensions to the Google HTTP Client Library for Java. (com.google.http-client:google-http-client-gson:1.43.3 - https://github.com/googleapis/google-http-java-client/google-http-client-gson)
+ * GSON extensions to the Google HTTP Client Library for Java. (com.google.http-client:google-http-client-gson:1.47.1 - https://github.com/googleapis/google-http-java-client/google-http-client-gson)
+ * GSON extensions to the Google HTTP Client Library for Java. (com.google.http-client:google-http-client-gson:2.0.0 - https://github.com/googleapis/google-http-java-client/google-http-client-gson)
+ * Guava: Google Core Libraries for Java (com.google.guava:guava:33.4.8-jre - https://github.com/google/guava)
+ * Guava InternalFutureFailureAccess and InternalFutures (com.google.guava:failureaccess:1.0.2 - https://github.com/google/guava/failureaccess)
+ * Guava InternalFutureFailureAccess and InternalFutures (com.google.guava:failureaccess:1.0.3 - https://github.com/google/guava/failureaccess)
* Guava ListenableFuture only (com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava - https://github.com/google/guava/listenablefuture)
- * Guava Testing Library (com.google.guava:guava-testlib:31.1-jre - https://github.com/google/guava/guava-testlib)
- * io.grpc:grpc-alts (io.grpc:grpc-alts:1.45.1 - https://github.com/grpc/grpc-java)
- * io.grpc:grpc-api (io.grpc:grpc-api:1.49.0 - https://github.com/grpc/grpc-java)
- * io.grpc:grpc-auth (io.grpc:grpc-auth:1.42.1 - https://github.com/grpc/grpc-java)
- * io.grpc:grpc-context (io.grpc:grpc-context:1.27.2 - https://github.com/grpc/grpc-java)
- * io.grpc:grpc-context (io.grpc:grpc-context:1.42.1 - https://github.com/grpc/grpc-java)
- * io.grpc:grpc-context (io.grpc:grpc-context:1.49.0 - https://github.com/grpc/grpc-java)
- * io.grpc:grpc-core (io.grpc:grpc-core:1.45.1 - https://github.com/grpc/grpc-java)
- * io.grpc:grpc-core (io.grpc:grpc-core:1.49.0 - https://github.com/grpc/grpc-java)
- * io.grpc:grpc-googleapis (io.grpc:grpc-googleapis:1.45.1 - https://github.com/grpc/grpc-java)
- * io.grpc:grpc-grpclb (io.grpc:grpc-grpclb:1.42.1 - https://github.com/grpc/grpc-java)
- * io.grpc:grpc-netty (io.grpc:grpc-netty:1.49.0 - https://github.com/grpc/grpc-java)
- * io.grpc:grpc-netty-shaded (io.grpc:grpc-netty-shaded:1.45.1 - https://github.com/grpc/grpc-java)
- * io.grpc:grpc-protobuf (io.grpc:grpc-protobuf:1.49.0 - https://github.com/grpc/grpc-java)
- * io.grpc:grpc-protobuf-lite (io.grpc:grpc-protobuf-lite:1.42.1 - https://github.com/grpc/grpc-java)
- * io.grpc:grpc-protobuf-lite (io.grpc:grpc-protobuf-lite:1.49.0 - https://github.com/grpc/grpc-java)
- * io.grpc:grpc-services (io.grpc:grpc-services:1.42.1 - https://github.com/grpc/grpc-java)
- * io.grpc:grpc-stub (io.grpc:grpc-stub:1.49.0 - https://github.com/grpc/grpc-java)
- * io.grpc:grpc-xds (io.grpc:grpc-xds:1.45.1 - https://github.com/grpc/grpc-java)
- * J2ObjC Annotations (com.google.j2objc:j2objc-annotations:1.3 - https://github.com/google/j2objc/)
+ * Guava Testing Library (com.google.guava:guava-testlib:33.4.8-jre - https://github.com/google/guava/guava-testlib)
+ * hazelcast (com.hazelcast:hazelcast:3.12.12 - http://www.hazelcast.com/hazelcast/)
+ * hazelcast-client (com.hazelcast:hazelcast-client:3.12.12 - http://www.hazelcast.com/hazelcast-client/)
+ * Hibernate Commons Annotations (org.hibernate.common:hibernate-commons-annotations:7.0.3.Final - http://hibernate.org)
+ * Hibernate Search 5 Migration Helper - Engine (org.hibernate.search:hibernate-search-v5migrationhelper-engine:7.2.3.Final - https://hibernate.org/search/)
+ * Hibernate Search Backend - Lucene (org.hibernate.search:hibernate-search-backend-lucene:7.2.3.Final - https://hibernate.org/search/)
+ * Hibernate Search Engine (org.hibernate.search:hibernate-search-engine:7.2.4.Final - https://hibernate.org/search/)
+ * Hibernate Search Mapper - POJO Base (org.hibernate.search:hibernate-search-mapper-pojo-base:7.2.4.Final - https://hibernate.org/search/)
+ * Hibernate Search Utils - Common (org.hibernate.search:hibernate-search-util-common:7.2.4.Final - https://hibernate.org/search/)
+ * High Performance Primitive Collections (com.carrotsearch:hppc:0.10.0 - https://github.com/carrotsearch/hppc)
+ * Infinispan API (org.infinispan:infinispan-api:15.2.4.Final - https://infinispan.org/infinispan-api)
+ * Infinispan Commons (org.infinispan:infinispan-commons:15.2.4.Final - https://infinispan.org/infinispan-commons-parent/infinispan-commons)
+ * Infinispan Commons SPI (org.infinispan:infinispan-commons-spi:15.2.4.Final - https://infinispan.org/infinispan-commons-parent/infinispan-commons-spi)
+ * Infinispan Core (org.infinispan:infinispan-core:15.2.4.Final - https://infinispan.org/infinispan-core)
+ * Infinispan Counter API (org.infinispan:infinispan-counter-api:15.2.4.Final - https://infinispan.org/infinispan-counter-api)
+ * Infinispan Hot Rod Client (org.infinispan:infinispan-client-hotrod:15.2.4.Final - https://infinispan.org/infinispan-client-hotrod)
+ * Infinispan Object Querying and Filtering API (org.infinispan:infinispan-objectfilter:15.2.4.Final - https://infinispan.org/infinispan-objectfilter)
+ * Infinispan Query (org.infinispan:infinispan-query:15.2.4.Final - https://infinispan.org/infinispan-query)
+ * Infinispan Query-core (org.infinispan:infinispan-query-core:15.2.4.Final - https://infinispan.org/infinispan-query-core)
+ * Infinispan Query DSL API (org.infinispan:infinispan-query-dsl:15.2.4.Final - https://infinispan.org/infinispan-query-dsl)
+ * Infinispan Remote Query Client (org.infinispan:infinispan-remote-query-client:15.2.4.Final - https://infinispan.org/infinispan-remote-query-client)
+ * io.grpc:grpc-alts (io.grpc:grpc-alts:1.68.1 - https://github.com/grpc/grpc-java)
+ * io.grpc:grpc-alts (io.grpc:grpc-alts:1.71.0 - https://github.com/grpc/grpc-java)
+ * io.grpc:grpc-api (io.grpc:grpc-api:1.68.1 - https://github.com/grpc/grpc-java)
+ * io.grpc:grpc-api (io.grpc:grpc-api:1.70.0 - https://github.com/grpc/grpc-java)
+ * io.grpc:grpc-api (io.grpc:grpc-api:1.71.0 - https://github.com/grpc/grpc-java)
+ * io.grpc:grpc-api (io.grpc:grpc-api:1.75.0 - https://github.com/grpc/grpc-java)
+ * io.grpc:grpc-auth (io.grpc:grpc-auth:1.68.1 - https://github.com/grpc/grpc-java)
+ * io.grpc:grpc-auth (io.grpc:grpc-auth:1.71.0 - https://github.com/grpc/grpc-java)
+ * io.grpc:grpc-context (io.grpc:grpc-context:1.70.0 - https://github.com/grpc/grpc-java)
+ * io.grpc:grpc-context (io.grpc:grpc-context:1.71.0 - https://github.com/grpc/grpc-java)
+ * io.grpc:grpc-core (io.grpc:grpc-core:1.68.1 - https://github.com/grpc/grpc-java)
+ * io.grpc:grpc-core (io.grpc:grpc-core:1.71.0 - https://github.com/grpc/grpc-java)
+ * io.grpc:grpc-googleapis (io.grpc:grpc-googleapis:1.68.1 - https://github.com/grpc/grpc-java)
+ * io.grpc:grpc-googleapis (io.grpc:grpc-googleapis:1.71.0 - https://github.com/grpc/grpc-java)
+ * io.grpc:grpc-grpclb (io.grpc:grpc-grpclb:1.68.1 - https://github.com/grpc/grpc-java)
+ * io.grpc:grpc-grpclb (io.grpc:grpc-grpclb:1.71.0 - https://github.com/grpc/grpc-java)
+ * io.grpc:grpc-inprocess (io.grpc:grpc-inprocess:1.68.1 - https://github.com/grpc/grpc-java)
+ * io.grpc:grpc-inprocess (io.grpc:grpc-inprocess:1.71.0 - https://github.com/grpc/grpc-java)
+ * io.grpc:grpc-netty-shaded (io.grpc:grpc-netty-shaded:1.68.1 - https://github.com/grpc/grpc-java)
+ * io.grpc:grpc-netty-shaded (io.grpc:grpc-netty-shaded:1.71.0 - https://github.com/grpc/grpc-java)
+ * io.grpc:grpc-opentelemetry (io.grpc:grpc-opentelemetry:1.71.0 - https://github.com/grpc/grpc-java)
+ * io.grpc:grpc-protobuf (io.grpc:grpc-protobuf:1.68.1 - https://github.com/grpc/grpc-java)
+ * io.grpc:grpc-protobuf (io.grpc:grpc-protobuf:1.71.0 - https://github.com/grpc/grpc-java)
+ * io.grpc:grpc-protobuf-lite (io.grpc:grpc-protobuf-lite:1.68.1 - https://github.com/grpc/grpc-java)
+ * io.grpc:grpc-protobuf-lite (io.grpc:grpc-protobuf-lite:1.71.0 - https://github.com/grpc/grpc-java)
+ * io.grpc:grpc-rls (io.grpc:grpc-rls:1.71.0 - https://github.com/grpc/grpc-java)
+ * io.grpc:grpc-s2a (io.grpc:grpc-s2a:1.68.1 - https://github.com/grpc/grpc-java)
+ * io.grpc:grpc-services (io.grpc:grpc-services:1.68.1 - https://github.com/grpc/grpc-java)
+ * io.grpc:grpc-services (io.grpc:grpc-services:1.71.0 - https://github.com/grpc/grpc-java)
+ * io.grpc:grpc-stub (io.grpc:grpc-stub:1.68.1 - https://github.com/grpc/grpc-java)
+ * io.grpc:grpc-stub (io.grpc:grpc-stub:1.71.0 - https://github.com/grpc/grpc-java)
+ * io.grpc:grpc-util (io.grpc:grpc-util:1.68.1 - https://github.com/grpc/grpc-java)
+ * io.grpc:grpc-util (io.grpc:grpc-util:1.71.0 - https://github.com/grpc/grpc-java)
+ * io.grpc:grpc-xds (io.grpc:grpc-xds:1.68.1 - https://github.com/grpc/grpc-java)
+ * io.grpc:grpc-xds (io.grpc:grpc-xds:1.71.0 - https://github.com/grpc/grpc-java)
+ * J2ObjC Annotations (com.google.j2objc:j2objc-annotations:3.0.0 - https://github.com/google/j2objc/)
* Jackson (org.codehaus.jackson:jackson-core-asl:1.9.13 - http://jackson.codehaus.org)
- * Jackson 2 extensions to the Google HTTP Client Library for Java. (com.google.http-client:google-http-client-jackson2:1.41.7 - https://github.com/googleapis/google-http-java-client/google-http-client-jackson2)
- * Jackson 2 extensions to the Google HTTP Client Library for Java. (com.google.http-client:google-http-client-jackson2:1.42.0 - https://github.com/googleapis/google-http-java-client/google-http-client-jackson2)
- * Jackson-annotations (com.fasterxml.jackson.core:jackson-annotations:2.13.3 - http://github.com/FasterXML/jackson)
- * Jackson-core (com.fasterxml.jackson.core:jackson-core:2.13.3 - https://github.com/FasterXML/jackson-core)
- * jackson-databind (com.fasterxml.jackson.core:jackson-databind:2.13.3 - http://github.com/FasterXML/jackson)
- * Jackson datatype: jdk8 (com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.13.3 - https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jdk8)
- * Jackson datatype: JSR310 (com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.13.3 - https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jsr310)
+ * Jackson 2 extensions to the Google HTTP Client Library for Java. (com.google.http-client:google-http-client-jackson2:1.45.0 - https://github.com/googleapis/google-http-java-client/google-http-client-jackson2)
+ * Jackson 2 extensions to the Google HTTP Client Library for Java. (com.google.http-client:google-http-client-jackson2:1.47.1 - https://github.com/googleapis/google-http-java-client/google-http-client-jackson2)
+ * Jackson-annotations (com.fasterxml.jackson.core:jackson-annotations:2.13.5 - http://github.com/FasterXML/jackson)
+ * Jackson-annotations (com.fasterxml.jackson.core:jackson-annotations:2.18.2 - https://github.com/FasterXML/jackson)
+ * Jackson-core (com.fasterxml.jackson.core:jackson-core:2.20.0 - https://github.com/FasterXML/jackson-core)
+ * jackson-databind (com.fasterxml.jackson.core:jackson-databind:2.13.5 - http://github.com/FasterXML/jackson)
+ * jackson-databind (com.fasterxml.jackson.core:jackson-databind:2.18.2 - https://github.com/FasterXML/jackson)
+ * Jackson datatype: jdk8 (com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.13.5 - https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jdk8)
+ * Jackson datatype: JSR310 (com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.13.5 - https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jsr310)
+ * Jackson datatype: JSR310 (com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.18.2 - https://github.com/FasterXML/jackson-modules-java8/jackson-datatype-jsr310)
* Jackson extensions to the Google HTTP Client Library for Java. (com.google.http-client:google-http-client-jackson:1.29.2 - https://github.com/googleapis/google-http-java-client/google-http-client-jackson)
- * Jackson-module-parameter-names (com.fasterxml.jackson.module:jackson-module-parameter-names:2.13.3 - https://github.com/FasterXML/jackson-modules-java8/jackson-module-parameter-names)
- * Java 6 (and higher) extensions to the Google OAuth Client Library for Java. (com.google.oauth-client:google-oauth-client-java6:1.34.1 - https://github.com/googleapis/google-oauth-java-client/google-oauth-client-java6)
- * JCommander (com.beust:jcommander:1.48 - http://beust.com/jcommander)
+ * Jackson-module-parameter-names (com.fasterxml.jackson.module:jackson-module-parameter-names:2.13.5 - https://github.com/FasterXML/jackson-modules-java8/jackson-module-parameter-names)
+ * Jakarta Dependency Injection (jakarta.inject:jakarta.inject-api:2.0.1 - https://github.com/eclipse-ee4j/injection-api)
+ * Jandex: Core (io.smallrye:jandex:3.2.0 - https://smallrye.io)
+ * Java 6 (and higher) extensions to the Google OAuth Client Library for Java. (com.google.oauth-client:google-oauth-client-java6:1.39.0 - https://github.com/googleapis/google-oauth-java-client/google-oauth-client-java6)
+ * Java Concurrency Tools Core Library (org.jctools:jctools-core:4.0.5 - https://github.com/JCTools)
+ * JBoss Logging 3 (org.jboss.logging:jboss-logging:3.6.0.Final - http://www.jboss.org)
+ * JBoss Threads (org.jboss.threads:jboss-threads:3.6.1.Final - http://www.jboss.org)
+ * jcommander (com.beust:jcommander:1.82 - https://jcommander.org)
* jffi (com.github.jnr:jffi:1.3.9 - http://github.com/jnr/jffi)
+ * JGroups (org.jgroups:jgroups:5.4.5.Final - http://www.jgroups.org)
* jnr-a64asm (com.github.jnr:jnr-a64asm:1.0.0 - http://nexus.sonatype.org/oss-repository-hosting.html/jnr-a64asm)
* jnr-constants (com.github.jnr:jnr-constants:0.10.3 - http://github.com/jnr/jnr-constants)
+ * jnr-constants (com.github.jnr:jnr-constants:0.10.4 - http://github.com/jnr/jnr-constants)
* jnr-enxio (com.github.jnr:jnr-enxio:0.32.13 - http://github.com/jnr/jnr-enxio)
+ * jnr-enxio (com.github.jnr:jnr-enxio:0.32.18 - http://github.com/jnr/jnr-enxio)
* jnr-ffi (com.github.jnr:jnr-ffi:2.2.11 - http://github.com/jnr/jnr-ffi)
+ * jnr-ffi (com.github.jnr:jnr-ffi:2.2.17 - http://github.com/jnr/jnr-ffi)
* jnr-unixsocket (com.github.jnr:jnr-unixsocket:0.38.17 - http://github.com/jnr/jnr-unixsocket)
- * Joda-Time (joda-time:joda-time:2.11.1 - https://www.joda.org/joda-time/)
+ * jnr-unixsocket (com.github.jnr:jnr-unixsocket:0.38.23 - http://github.com/jnr/jnr-unixsocket)
+ * Joda-Time (joda-time:joda-time:2.14.0 - https://www.joda.org/joda-time/)
* JSONassert (org.skyscreamer:jsonassert:1.5.1 - https://github.com/skyscreamer/JSONassert)
* JSON library from Android SDK (com.vaadin.external.google:android-json:0.0.20131108.vaadin1 - http://developer.android.com/sdk)
- * JSON Small and Fast Parser (net.minidev:json-smart:2.4.8 - https://urielch.github.io/)
+ * JSON Small and Fast Parser (net.minidev:json-smart:2.4.11 - https://urielch.github.io/)
+ * JSpecify annotations (org.jspecify:jspecify:1.0.0 - http://jspecify.org/)
* jsr107cache (net.sf.jsr107cache:jsr107cache:1.1 - http://maven.apache.org)
* juli (org.apache.tomcat:juli:6.0.53 - http://tomcat.apache.org/)
* Logging (commons-logging:commons-logging:1.0.4 - http://jakarta.apache.org/commons/logging/)
* Lucene Analyzers (org.apache.lucene:lucene-analyzers:2.9.4 - http://lucene.apache.org/java/lucene-contrib/lucene-analyzers)
* Lucene Core (org.apache.lucene:lucene-core:2.9.4 - http://lucene.apache.org/java/lucene-core)
+ * MongoDB Java Driver (org.mongodb:mongo-java-driver:2.14.3 - http://www.mongodb.org)
+ * MortBay :: Apache EL :: API and Implementation (org.mortbay.jasper:apache-el:10.1.16 - https://github.com/jetty-project/jasper-jsp/apache-el)
+ * MortBay :: Apache EL :: API and Implementation (org.mortbay.jasper:apache-el:11.0.9 - https://github.com/jetty-project/jasper-jsp/apache-el)
* MortBay :: Apache EL :: API and Implementation (org.mortbay.jasper:apache-el:8.5.70 - https://github.com/jetty-project/jasper-jsp/apache-el)
+ * MortBay :: Apache EL :: API and Implementation (org.mortbay.jasper:apache-el:9.0.52 - https://github.com/jetty-project/jasper-jsp/apache-el)
+ * MortBay :: Apache EL :: API and Implementation (org.mortbay.jasper:mortbay-apache-el:11.0.10.1 - https://github.com/jetty-project/jasper-jsp/mortbay-apache-el)
+ * MortBay :: Apache EL :: API and Implementation (org.mortbay.jasper:mortbay-apache-el:9.0.108.1 - https://github.com/jetty-project/jasper-jsp/mortbay-apache-el)
+ * MortBay :: Apache Jasper :: JSP Implementation (org.mortbay.jasper:apache-jsp:10.1.16 - https://github.com/jetty-project/jasper-jsp/apache-jsp)
+ * MortBay :: Apache Jasper :: JSP Implementation (org.mortbay.jasper:apache-jsp:10.1.7 - https://github.com/jetty-project/jasper-jsp/apache-jsp)
+ * MortBay :: Apache Jasper :: JSP Implementation (org.mortbay.jasper:apache-jsp:11.0.9 - https://github.com/jetty-project/jasper-jsp/apache-jsp)
* MortBay :: Apache Jasper :: JSP Implementation (org.mortbay.jasper:apache-jsp:8.5.70 - https://github.com/jetty-project/jasper-jsp/apache-jsp)
- * Netty/Buffer (io.netty:netty-buffer:4.1.77.Final - https://netty.io/netty-buffer/)
- * Netty/Codec/HTTP (io.netty:netty-codec-http:4.1.77.Final - https://netty.io/netty-codec-http/)
- * Netty/Codec/HTTP2 (io.netty:netty-codec-http2:4.1.77.Final - https://netty.io/netty-codec-http2/)
- * Netty/Codec/Socks (io.netty:netty-codec-socks:4.1.77.Final - https://netty.io/netty-codec-socks/)
- * Netty/Codec (io.netty:netty-codec:4.1.77.Final - https://netty.io/netty-codec/)
- * Netty/Common (io.netty:netty-common:4.1.77.Final - https://netty.io/netty-common/)
- * Netty/Handler/Proxy (io.netty:netty-handler-proxy:4.1.77.Final - https://netty.io/netty-handler-proxy/)
- * Netty/Handler (io.netty:netty-handler:4.1.77.Final - https://netty.io/netty-handler/)
- * Netty/Resolver (io.netty:netty-resolver:4.1.77.Final - https://netty.io/netty-resolver/)
- * Netty/Transport/Native/Unix/Common (io.netty:netty-transport-native-unix-common:4.1.77.Final - https://netty.io/netty-transport-native-unix-common/)
- * Netty/Transport (io.netty:netty-transport:4.1.77.Final - https://netty.io/netty-transport/)
+ * MortBay :: Apache Jasper :: JSP Implementation (org.mortbay.jasper:apache-jsp:9.0.52 - https://github.com/jetty-project/jasper-jsp/apache-jsp)
+ * MortBay :: Apache Jasper :: JSP Implementation (org.mortbay.jasper:mortbay-apache-jsp:11.0.10.1 - https://github.com/jetty-project/jasper-jsp/mortbay-apache-jsp)
+ * MortBay :: Apache Jasper :: JSP Implementation (org.mortbay.jasper:mortbay-apache-jsp:9.0.108.1 - https://github.com/jetty-project/jasper-jsp/mortbay-apache-jsp)
+ * native-linux-x86_64 (com.aayushatharva.brotli4j:native-linux-x86_64:1.17.0 - https://github.com/hyperxpro/Brotli4j/natives/native-linux-x86_64)
+ * Netty/Buffer (io.netty:netty-buffer:4.1.110.Final - https://netty.io/netty-buffer/)
+ * Netty/Buffer (io.netty:netty-buffer:4.1.119.Final - https://netty.io/netty-buffer/)
+ * Netty/Codec/DNS (io.netty:netty-codec-dns:4.1.119.Final - https://netty.io/netty-codec-dns/)
+ * Netty/Codec (io.netty:netty-codec:4.1.119.Final - https://netty.io/netty-codec/)
+ * Netty/Common (io.netty:netty-common:4.1.110.Final - https://netty.io/netty-common/)
+ * Netty/Common (io.netty:netty-common:4.1.119.Final - https://netty.io/netty-common/)
+ * Netty/Handler (io.netty:netty-handler:4.1.119.Final - https://netty.io/netty-handler/)
+ * Netty/Resolver/DNS (io.netty:netty-resolver-dns:4.1.119.Final - https://netty.io/netty-resolver-dns/)
+ * Netty/Resolver (io.netty:netty-resolver:4.1.119.Final - https://netty.io/netty-resolver/)
+ * Netty/Transport/Classes/Epoll (io.netty:netty-transport-classes-epoll:4.1.119.Final - https://netty.io/netty-transport-classes-epoll/)
+ * Netty/Transport/Native/Epoll (io.netty:netty-transport-native-epoll:4.1.119.Final - https://netty.io/netty-transport-native-epoll/)
+ * Netty/Transport/Native/Unix/Common (io.netty:netty-transport-native-unix-common:4.1.119.Final - https://netty.io/netty-transport-native-unix-common/)
+ * Netty/Transport (io.netty:netty-transport:4.1.119.Final - https://netty.io/netty-transport/)
* Objenesis (org.objenesis:objenesis:3.2 - http://objenesis.org/objenesis)
- * OpenCensus (io.opencensus:opencensus-api:0.30.0 - https://github.com/census-instrumentation/opencensus-java)
+ * Objenesis (org.objenesis:objenesis:3.3 - http://objenesis.org/objenesis)
+ * Objenesis (org.objenesis:objenesis:3.4 - https://objenesis.org/objenesis)
+ * Obsolete Truth Extension for Java8 (com.google.truth.extensions:truth-java8-extension:1.4.5 - http://github.com/google/truth/truth-extensions-parent/truth-java8-extension)
* OpenCensus (io.opencensus:opencensus-api:0.31.1 - https://github.com/census-instrumentation/opencensus-java)
- * OpenCensus (io.opencensus:opencensus-contrib-grpc-util:0.30.0 - https://github.com/census-instrumentation/opencensus-java)
- * OpenCensus (io.opencensus:opencensus-contrib-http-util:0.28.0 - https://github.com/census-instrumentation/opencensus-java)
+ * OpenCensus (io.opencensus:opencensus-contrib-grpc-util:0.31.1 - https://github.com/census-instrumentation/opencensus-java)
* OpenCensus (io.opencensus:opencensus-contrib-http-util:0.31.1 - https://github.com/census-instrumentation/opencensus-java)
- * OpenCensus (io.opencensus:opencensus-proto:0.2.0 - https://github.com/census-instrumentation/opencensus-proto)
+ * OpenTelemetry Instrumentation for Java (io.opentelemetry.instrumentation:opentelemetry-grpc-1.6:2.1.0-alpha - https://github.com/open-telemetry/opentelemetry-java-instrumentation)
+ * OpenTelemetry Instrumentation for Java (io.opentelemetry.instrumentation:opentelemetry-instrumentation-api:2.1.0 - https://github.com/open-telemetry/opentelemetry-java-instrumentation)
+ * OpenTelemetry Instrumentation for Java (io.opentelemetry.instrumentation:opentelemetry-instrumentation-api-incubator:2.1.0-alpha - https://github.com/open-telemetry/opentelemetry-java-instrumentation)
+ * OpenTelemetry Java (io.opentelemetry:opentelemetry-api:1.42.1 - https://github.com/open-telemetry/opentelemetry-java)
+ * OpenTelemetry Java (io.opentelemetry:opentelemetry-api:1.47.0 - https://github.com/open-telemetry/opentelemetry-java)
+ * OpenTelemetry Java (io.opentelemetry:opentelemetry-context:1.42.1 - https://github.com/open-telemetry/opentelemetry-java)
+ * OpenTelemetry Java (io.opentelemetry:opentelemetry-context:1.47.0 - https://github.com/open-telemetry/opentelemetry-java)
+ * OpenTelemetry Java (io.opentelemetry:opentelemetry-extension-incubator:1.35.0-alpha - https://github.com/open-telemetry/opentelemetry-java)
+ * OpenTelemetry Java (io.opentelemetry:opentelemetry-sdk:1.47.0 - https://github.com/open-telemetry/opentelemetry-java)
+ * OpenTelemetry Java (io.opentelemetry:opentelemetry-sdk-common:1.47.0 - https://github.com/open-telemetry/opentelemetry-java)
+ * OpenTelemetry Java (io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi:1.47.0 - https://github.com/open-telemetry/opentelemetry-java)
+ * OpenTelemetry Java (io.opentelemetry:opentelemetry-sdk-logs:1.47.0 - https://github.com/open-telemetry/opentelemetry-java)
+ * OpenTelemetry Java (io.opentelemetry:opentelemetry-sdk-metrics:1.47.0 - https://github.com/open-telemetry/opentelemetry-java)
+ * OpenTelemetry Java (io.opentelemetry:opentelemetry-sdk-trace:1.47.0 - https://github.com/open-telemetry/opentelemetry-java)
+ * OpenTelemetry Java Contrib (io.opentelemetry.contrib:opentelemetry-gcp-resources:1.37.0-alpha - https://github.com/open-telemetry/opentelemetry-java-contrib)
+ * OpenTelemetry Operations Java (com.google.cloud.opentelemetry:detector-resources-support:0.33.0 - https://github.com/GoogleCloudPlatform/opentelemetry-operations-java)
+ * OpenTelemetry Operations Java (com.google.cloud.opentelemetry:exporter-metrics:0.33.0 - https://github.com/GoogleCloudPlatform/opentelemetry-operations-java)
+ * OpenTelemetry Operations Java (com.google.cloud.opentelemetry:shared-resourcemapping:0.33.0 - https://github.com/GoogleCloudPlatform/opentelemetry-operations-java)
+ * OpenTelemetry Semantic Conventions Java (io.opentelemetry.semconv:opentelemetry-semconv:1.29.0-alpha - https://github.com/open-telemetry/semantic-conventions-java)
* org.apiguardian:apiguardian-api (org.apiguardian:apiguardian-api:1.1.2 - https://github.com/apiguardian-team/apiguardian)
- * org.conscrypt:conscrypt-openjdk-uber (org.conscrypt:conscrypt-openjdk-uber:2.5.1 - https://conscrypt.org/)
+ * org.conscrypt:conscrypt-openjdk-uber (org.conscrypt:conscrypt-openjdk-uber:2.5.2 - https://conscrypt.org/)
* org.opentest4j:opentest4j (org.opentest4j:opentest4j:1.2.0 - https://github.com/ota4j-team/opentest4j)
- * org.xmlunit:xmlunit-core (org.xmlunit:xmlunit-core:2.9.0 - https://www.xmlunit.org/)
- * perfmark:perfmark-api (io.perfmark:perfmark-api:0.23.0 - https://github.com/perfmark/perfmark)
- * perfmark:perfmark-api (io.perfmark:perfmark-api:0.25.0 - https://github.com/perfmark/perfmark)
+ * org.opentest4j:opentest4j (org.opentest4j:opentest4j:1.3.0 - https://github.com/ota4j-team/opentest4j)
+ * org.xmlunit:xmlunit-core (org.xmlunit:xmlunit-core:2.9.1 - https://www.xmlunit.org/)
+ * perfmark:perfmark-api (io.perfmark:perfmark-api:0.27.0 - https://github.com/perfmark/perfmark)
* project ':json-path' (com.jayway.jsonpath:json-path:2.7.0 - https://github.com/jayway/JsonPath)
- * Protocol Buffer extensions to the Google HTTP Client Library for Java. (com.google.http-client:google-http-client-protobuf:1.41.7 - https://github.com/googleapis/google-http-java-client/google-http-client-protobuf)
- * Protocol Buffer extensions to the Google HTTP Client Library for Java. (com.google.http-client:google-http-client-protobuf:1.42.0 - https://github.com/googleapis/google-http-java-client/google-http-client-protobuf)
- * proto-google-cloud-datastore-v1 (com.google.api.grpc:proto-google-cloud-datastore-v1:0.101.1 - https://github.com/googleapis/java-datastore/proto-google-cloud-datastore-v1)
- * proto-google-cloud-logging-v2 (com.google.api.grpc:proto-google-cloud-logging-v2:0.96.5 - https://github.com/googleapis/java-logging/proto-google-cloud-logging-v2)
- * proto-google-cloud-spanner-admin-database-v1 (com.google.api.grpc:proto-google-cloud-spanner-admin-database-v1:6.17.3 - https://github.com/googleapis/java-spanner/proto-google-cloud-spanner-admin-database-v1)
- * proto-google-cloud-spanner-admin-instance-v1 (com.google.api.grpc:proto-google-cloud-spanner-admin-instance-v1:6.17.3 - https://github.com/googleapis/java-spanner/proto-google-cloud-spanner-admin-instance-v1)
- * proto-google-cloud-spanner-v1 (com.google.api.grpc:proto-google-cloud-spanner-v1:6.17.3 - https://github.com/googleapis/java-spanner/proto-google-cloud-spanner-v1)
- * proto-google-common-protos (com.google.api.grpc:proto-google-common-protos:2.9.2 - https://github.com/googleapis/java-iam/proto-google-common-protos)
- * proto-google-iam-v1 (com.google.api.grpc:proto-google-iam-v1:1.1.7 - https://github.com/googleapis/java-iam/proto-google-iam-v1)
+ * Protocol Buffer extensions to the Google HTTP Client Library for Java. (com.google.http-client:google-http-client-protobuf:1.43.3 - https://github.com/googleapis/google-http-java-client/google-http-client-protobuf)
+ * Protocol Buffer extensions to the Google HTTP Client Library for Java. (com.google.http-client:google-http-client-protobuf:1.47.1 - https://github.com/googleapis/google-http-java-client/google-http-client-protobuf)
+ * proto-google-cloud-bigquerystorage-v1 (com.google.api.grpc:proto-google-cloud-bigquerystorage-v1:3.16.3 - https://github.com/googleapis/java-bigquerystorage/proto-google-cloud-bigquerystorage-v1)
+ * proto-google-cloud-bigquerystorage-v1alpha (com.google.api.grpc:proto-google-cloud-bigquerystorage-v1alpha:3.16.3 - https://github.com/googleapis/java-bigquerystorage/proto-google-cloud-bigquerystorage-v1alpha)
+ * proto-google-cloud-bigquerystorage-v1beta (com.google.api.grpc:proto-google-cloud-bigquerystorage-v1beta:3.16.3 - https://github.com/googleapis/java-bigquerystorage/proto-google-cloud-bigquerystorage-v1beta)
+ * proto-google-cloud-bigquerystorage-v1beta1 (com.google.api.grpc:proto-google-cloud-bigquerystorage-v1beta1:0.188.3 - https://github.com/googleapis/java-bigquerystorage/proto-google-cloud-bigquerystorage-v1beta1)
+ * proto-google-cloud-bigquerystorage-v1beta2 (com.google.api.grpc:proto-google-cloud-bigquerystorage-v1beta2:0.188.3 - https://github.com/googleapis/java-bigquerystorage/proto-google-cloud-bigquerystorage-v1beta2)
+ * proto-google-cloud-datastore-admin-v1 (com.google.api.grpc:proto-google-cloud-datastore-admin-v1:2.24.3 - https://github.com/googleapis/java-datastore/proto-google-cloud-datastore-admin-v1)
+ * proto-google-cloud-datastore-admin-v1 (com.google.api.grpc:proto-google-cloud-datastore-admin-v1:2.31.4 - https://github.com/googleapis/java-datastore/proto-google-cloud-datastore-admin-v1)
+ * proto-google-cloud-datastore-v1 (com.google.api.grpc:proto-google-cloud-datastore-v1:0.108.5 - https://github.com/googleapis/java-datastore/proto-google-cloud-datastore-v1)
+ * proto-google-cloud-logging-v2 (com.google.api.grpc:proto-google-cloud-logging-v2:0.112.3 - https://github.com/googleapis/java-logging/proto-google-cloud-logging-v2)
+ * proto-google-cloud-monitoring-v3 (com.google.api.grpc:proto-google-cloud-monitoring-v3:3.63.0 - https://github.com/googleapis/google-cloud-java)
+ * proto-google-cloud-spanner-admin-database-v1 (com.google.api.grpc:proto-google-cloud-spanner-admin-database-v1:6.99.0 - https://github.com/googleapis/java-spanner/proto-google-cloud-spanner-admin-database-v1)
+ * proto-google-cloud-spanner-admin-instance-v1 (com.google.api.grpc:proto-google-cloud-spanner-admin-instance-v1:6.99.0 - https://github.com/googleapis/java-spanner/proto-google-cloud-spanner-admin-instance-v1)
+ * proto-google-cloud-spanner-v1 (com.google.api.grpc:proto-google-cloud-spanner-v1:6.99.0 - https://github.com/googleapis/java-spanner/proto-google-cloud-spanner-v1)
+ * proto-google-cloud-storage-v2 (com.google.api.grpc:proto-google-cloud-storage-v2:2.57.0 - https://github.com/googleapis/java-storage/proto-google-cloud-storage-v2)
+ * proto-google-common-protos (com.google.api.grpc:proto-google-common-protos:2.32.0 - https://github.com/googleapis/sdk-platform-java)
+ * proto-google-iam-v1 (com.google.api.grpc:proto-google-iam-v1:1.44.0 - https://github.com/googleapis/sdk-platform-java)
+ * proto-google-iam-v1 (com.google.api.grpc:proto-google-iam-v1:1.56.0 - https://github.com/googleapis/sdk-platform-java)
+ * ProtoStream - annotation processor (org.infinispan.protostream:protostream-processor:5.0.13.Final - https://github.com/infinispan/protostream)
+ * ProtoStream - builtin types (org.infinispan.protostream:protostream-types:5.0.13.Final - https://infinispan.org)
+ * ProtoStream - core (org.infinispan.protostream:protostream:5.0.13.Final - https://github.com/infinispan/protostream)
* quartz (quartz:quartz:1.5.2 - no url defined)
+ * RxJava (io.reactivex.rxjava3:rxjava:3.1.10 - https://github.com/ReactiveX/RxJava)
* S2 Geometry Library for Java (com.google.geometry:s2-geometry:2.0.0 - https://github.com/google/s2-geometry-library-java)
- * Servlet and JDO extensions to the Google API Client Library for Java. (com.google.api-client:google-api-client-servlet:2.0.0 - https://github.com/googleapis/google-api-java-client/google-api-client-servlet)
+ * service (com.aayushatharva.brotli4j:service:1.17.0 - https://github.com/hyperxpro/Brotli4j/service)
+ * Servlet and JDO extensions to the Google API Client Library for Java. (com.google.api-client:google-api-client-servlet:2.8.1 - https://github.com/googleapis/google-api-java-client/google-api-client-servlet)
+ * SmallRye Common: Annotations (io.smallrye.common:smallrye-common-annotation:2.8.0 - http://smallrye.io)
+ * SmallRye Mutiny - Core library (io.smallrye.reactive:mutiny:2.8.0 - https://smallrye.io/smallrye-mutiny)
* SnakeYAML (org.yaml:snakeyaml:1.30 - https://bitbucket.org/snakeyaml/snakeyaml)
- * Spring AOP (org.springframework:spring-aop:5.3.22 - https://github.com/spring-projects/spring-framework)
- * Spring Beans (org.springframework:spring-beans:5.3.22 - https://github.com/spring-projects/spring-framework)
- * spring-boot (org.springframework.boot:spring-boot:2.7.2 - https://spring.io/projects/spring-boot)
- * spring-boot-autoconfigure (org.springframework.boot:spring-boot-autoconfigure:2.7.2 - https://spring.io/projects/spring-boot)
- * spring-boot-starter (org.springframework.boot:spring-boot-starter:2.7.2 - https://spring.io/projects/spring-boot)
- * spring-boot-starter-json (org.springframework.boot:spring-boot-starter-json:2.7.2 - https://spring.io/projects/spring-boot)
- * spring-boot-starter-logging (org.springframework.boot:spring-boot-starter-logging:2.7.2 - https://spring.io/projects/spring-boot)
- * spring-boot-starter-test (org.springframework.boot:spring-boot-starter-test:2.7.2 - https://spring.io/projects/spring-boot)
- * spring-boot-starter-web (org.springframework.boot:spring-boot-starter-web:2.7.2 - https://spring.io/projects/spring-boot)
- * spring-boot-test (org.springframework.boot:spring-boot-test:2.7.2 - https://spring.io/projects/spring-boot)
- * spring-boot-test-autoconfigure (org.springframework.boot:spring-boot-test-autoconfigure:2.7.2 - https://spring.io/projects/spring-boot)
- * Spring Commons Logging Bridge (org.springframework:spring-jcl:5.3.22 - https://github.com/spring-projects/spring-framework)
- * Spring Context (org.springframework:spring-context:5.3.22 - https://github.com/spring-projects/spring-framework)
- * Spring Core (org.springframework:spring-core:5.3.22 - https://github.com/spring-projects/spring-framework)
- * Spring Expression Language (SpEL) (org.springframework:spring-expression:5.3.22 - https://github.com/spring-projects/spring-framework)
- * Spring TestContext Framework (org.springframework:spring-test:5.3.22 - https://github.com/spring-projects/spring-framework)
- * Spring Web (org.springframework:spring-web:5.3.22 - https://github.com/spring-projects/spring-framework)
- * Spring Web MVC (org.springframework:spring-webmvc:5.3.22 - https://github.com/spring-projects/spring-framework)
- * Truth Core (com.google.truth:truth:1.1.3 - http://github.com/google/truth/truth)
- * Truth Extension for Java8 (com.google.truth.extensions:truth-java8-extension:1.1.3 - http://github.com/google/truth/truth-extensions-parent/truth-java8-extension)
+ * Spring AOP (org.springframework:spring-aop:5.3.31 - https://github.com/spring-projects/spring-framework)
+ * Spring Beans (org.springframework:spring-beans:5.3.31 - https://github.com/spring-projects/spring-framework)
+ * Spring Beans (org.springframework:spring-beans:5.3.39 - https://github.com/spring-projects/spring-framework)
+ * spring-boot (org.springframework.boot:spring-boot:2.7.18 - https://spring.io/projects/spring-boot)
+ * spring-boot-autoconfigure (org.springframework.boot:spring-boot-autoconfigure:2.7.18 - https://spring.io/projects/spring-boot)
+ * spring-boot-starter (org.springframework.boot:spring-boot-starter:2.7.18 - https://spring.io/projects/spring-boot)
+ * spring-boot-starter-json (org.springframework.boot:spring-boot-starter-json:2.7.18 - https://spring.io/projects/spring-boot)
+ * spring-boot-starter-logging (org.springframework.boot:spring-boot-starter-logging:2.7.18 - https://spring.io/projects/spring-boot)
+ * spring-boot-starter-test (org.springframework.boot:spring-boot-starter-test:2.7.18 - https://spring.io/projects/spring-boot)
+ * spring-boot-starter-web (org.springframework.boot:spring-boot-starter-web:2.7.18 - https://spring.io/projects/spring-boot)
+ * spring-boot-test (org.springframework.boot:spring-boot-test:2.7.18 - https://spring.io/projects/spring-boot)
+ * spring-boot-test-autoconfigure (org.springframework.boot:spring-boot-test-autoconfigure:2.7.18 - https://spring.io/projects/spring-boot)
+ * Spring Commons Logging Bridge (org.springframework:spring-jcl:5.3.31 - https://github.com/spring-projects/spring-framework)
+ * Spring Commons Logging Bridge (org.springframework:spring-jcl:5.3.39 - https://github.com/spring-projects/spring-framework)
+ * Spring Context (org.springframework:spring-context:5.3.31 - https://github.com/spring-projects/spring-framework)
+ * Spring Core (org.springframework:spring-core:5.3.31 - https://github.com/spring-projects/spring-framework)
+ * Spring Core (org.springframework:spring-core:5.3.39 - https://github.com/spring-projects/spring-framework)
+ * Spring Expression Language (SpEL) (org.springframework:spring-expression:5.3.31 - https://github.com/spring-projects/spring-framework)
+ * Spring TestContext Framework (org.springframework:spring-test:5.3.31 - https://github.com/spring-projects/spring-framework)
+ * Spring Web (org.springframework:spring-web:5.3.31 - https://github.com/spring-projects/spring-framework)
+ * Spring Web MVC (org.springframework:spring-webmvc:5.3.31 - https://github.com/spring-projects/spring-framework)
+ * Truth Core (com.google.truth:truth:1.4.5 - http://github.com/google/truth/truth)
+ * wildfly-common (org.wildfly.common:wildfly-common:1.6.0.Final - http://www.jboss.org/wildfly-common)
+ * WildFly Elytron - ASN.1 (org.wildfly.security:wildfly-elytron-asn1:2.6.2.Final - http://www.jboss.org/wildfly-elytron-parent/wildfly-elytron-asn1)
+ * WildFly Elytron - Auth (org.wildfly.security:wildfly-elytron-auth:2.6.2.Final - http://www.jboss.org/wildfly-elytron-parent/wildfly-elytron-auth)
+ * WildFly Elytron - Auth Server (org.wildfly.security:wildfly-elytron-auth-server:2.6.2.Final - http://www.jboss.org/wildfly-elytron-parent/wildfly-elytron-auth-server)
+ * WildFly Elytron - Base (org.wildfly.security:wildfly-elytron-base:2.6.2.Final - http://www.jboss.org/wildfly-elytron-parent/wildfly-elytron-base)
+ * WildFly Elytron - Credential (org.wildfly.security:wildfly-elytron-credential:2.6.2.Final - http://www.jboss.org/wildfly-elytron-parent/wildfly-elytron-credential)
+ * WildFly Elytron - Credential (org.wildfly.security:wildfly-elytron-keystore:2.6.2.Final - http://www.jboss.org/wildfly-elytron-parent/wildfly-elytron-keystore)
+ * WildFly Elytron - HTTP (org.wildfly.security:wildfly-elytron-http:2.6.2.Final - http://www.jboss.org/wildfly-elytron-parent/wildfly-elytron-http)
+ * WildFly Elytron - Mechanism (org.wildfly.security:wildfly-elytron-mechanism:2.6.2.Final - http://www.jboss.org/wildfly-elytron-parent/wildfly-elytron-mechanism)
+ * WildFly Elytron - Mechanism Digest (org.wildfly.security:wildfly-elytron-mechanism-digest:2.6.2.Final - http://www.jboss.org/wildfly-elytron-parent/wildfly-elytron-mechanism-digest)
+ * WildFly Elytron - Mechanism GSSAPI (org.wildfly.security:wildfly-elytron-mechanism-gssapi:2.6.2.Final - http://www.jboss.org/wildfly-elytron-parent/wildfly-elytron-mechanism-gssapi)
+ * WildFly Elytron - Mechanism OAuth2 (org.wildfly.security:wildfly-elytron-mechanism-oauth2:2.6.2.Final - http://www.jboss.org/wildfly-elytron-parent/wildfly-elytron-mechanism-oauth2)
+ * WildFly Elytron - Mechanism SCRAM (org.wildfly.security:wildfly-elytron-mechanism-scram:2.6.2.Final - http://www.jboss.org/wildfly-elytron-parent/wildfly-elytron-mechanism-scram)
+ * WildFly Elytron - Password Implementation (org.wildfly.security:wildfly-elytron-password-impl:2.6.2.Final - http://www.jboss.org/wildfly-elytron-parent/wildfly-elytron-password-impl)
+ * WildFly Elytron - Permission (org.wildfly.security:wildfly-elytron-permission:2.6.2.Final - http://www.jboss.org/wildfly-elytron-parent/wildfly-elytron-permission)
+ * WildFly Elytron - Provider Util (org.wildfly.security:wildfly-elytron-provider-util:2.6.2.Final - http://www.jboss.org/wildfly-elytron-parent/wildfly-elytron-provider-util)
+ * WildFly Elytron - SASL (org.wildfly.security:wildfly-elytron-sasl:2.6.2.Final - http://www.jboss.org/wildfly-elytron-parent/wildfly-elytron-sasl)
+ * WildFly Elytron - SASL Digest (org.wildfly.security:wildfly-elytron-sasl-digest:2.6.2.Final - http://www.jboss.org/wildfly-elytron-parent/wildfly-elytron-sasl-digest)
+ * WildFly Elytron - SASL External (org.wildfly.security:wildfly-elytron-sasl-external:2.6.2.Final - http://www.jboss.org/wildfly-elytron-parent/wildfly-elytron-sasl-external)
+ * WildFly Elytron - SASL GS2 (org.wildfly.security:wildfly-elytron-sasl-gs2:2.6.2.Final - http://www.jboss.org/wildfly-elytron-parent/wildfly-elytron-sasl-gs2)
+ * WildFly Elytron - SASL GSSAPI (org.wildfly.security:wildfly-elytron-sasl-gssapi:2.6.2.Final - http://www.jboss.org/wildfly-elytron-parent/wildfly-elytron-sasl-gssapi)
+ * WildFly Elytron - SASL OAuth2 (org.wildfly.security:wildfly-elytron-sasl-oauth2:2.6.2.Final - http://www.jboss.org/wildfly-elytron-parent/wildfly-elytron-sasl-oauth2)
+ * WildFly Elytron - SASL Plain (org.wildfly.security:wildfly-elytron-sasl-plain:2.6.2.Final - http://www.jboss.org/wildfly-elytron-parent/wildfly-elytron-sasl-plain)
+ * WildFly Elytron - SASL SCRAM (org.wildfly.security:wildfly-elytron-sasl-scram:2.6.2.Final - http://www.jboss.org/wildfly-elytron-parent/wildfly-elytron-sasl-scram)
+ * WildFly Elytron - Security Manager Action (org.wildfly.security:wildfly-elytron-security-manager-action:2.6.2.Final - http://www.jboss.org/wildfly-elytron-parent/wildfly-elytron-security-manager-action)
+ * WildFly Elytron - SSL (org.wildfly.security:wildfly-elytron-ssl:2.6.2.Final - http://www.jboss.org/wildfly-elytron-parent/wildfly-elytron-ssl)
+ * WildFly Elytron - Util (org.wildfly.security:wildfly-elytron-util:2.6.2.Final - http://www.jboss.org/wildfly-elytron-parent/wildfly-elytron-util)
+ * WildFly Elytron - X.500 (org.wildfly.security:wildfly-elytron-x500:2.6.2.Final - http://www.jboss.org/wildfly-elytron-parent/wildfly-elytron-x500)
+ * WildFly Elytron - X.500 Certificates (org.wildfly.security:wildfly-elytron-x500-cert:2.6.2.Final - http://www.jboss.org/wildfly-elytron-parent/wildfly-elytron-x500-cert)
+ * WildFly Elytron - X.500 Certificate Utility Classes (org.wildfly.security:wildfly-elytron-x500-cert-util:2.6.2.Final - http://www.jboss.org/wildfly-elytron-parent/wildfly-elytron-x500-cert-util)
+ * xmemcached (com.googlecode.xmemcached:xmemcached:2.4.8 - https://github.com/killme2008/xmemcached)
Apache License, Version 2.0, Eclipse Public License - Version 1.0
- * Jetty :: Apache JSP Implementation (org.eclipse.jetty:apache-jsp:9.4.49.v20220914 - https://eclipse.org/jetty/apache-jsp)
- * Jetty :: Asynchronous HTTP Client (org.eclipse.jetty:jetty-client:9.4.49.v20220914 - https://eclipse.org/jetty/jetty-client)
- * Jetty :: Continuation (org.eclipse.jetty:jetty-continuation:9.4.49.v20220914 - https://eclipse.org/jetty/jetty-continuation)
- * Jetty :: Distribution Assemblies (org.eclipse.jetty:jetty-distribution:9.4.49.v20220914 - https://eclipse.org/jetty/jetty-distribution)
- * Jetty :: Http Utility (org.eclipse.jetty:jetty-http:9.4.49.v20220914 - https://eclipse.org/jetty/jetty-http)
- * Jetty :: IO Utility (org.eclipse.jetty:jetty-io:9.4.49.v20220914 - https://eclipse.org/jetty/jetty-io)
- * Jetty :: JMX Management (org.eclipse.jetty:jetty-jmx:9.4.49.v20220914 - https://eclipse.org/jetty/jetty-jmx)
- * Jetty :: JNDI Naming (org.eclipse.jetty:jetty-jndi:9.4.49.v20220914 - https://eclipse.org/jetty/jetty-jndi)
- * Jetty :: Plus (org.eclipse.jetty:jetty-plus:9.4.49.v20220914 - https://eclipse.org/jetty/jetty-plus)
- * Jetty :: Quick Start (org.eclipse.jetty:jetty-quickstart:9.4.49.v20220914 - https://eclipse.org/jetty/jetty-quickstart)
+ * Apache :: JSTL module (org.eclipse.jetty:apache-jstl:9.4.58.v20250814 - http://tomcat.apache.org/taglibs/standard/)
+ * Example :: Jetty Spring (org.eclipse.jetty:jetty-spring:9.4.58.v20250814 - https://jetty.org/jetty-spring/)
+ * Jetty :: ALPN :: Client (org.eclipse.jetty:jetty-alpn-client:9.4.58.v20250814 - https://jetty.org/jetty-alpn-parent/jetty-alpn-client/)
+ * Jetty :: ALPN :: Conscrypt Server Implementation (org.eclipse.jetty:jetty-alpn-conscrypt-server:9.4.58.v20250814 - https://jetty.org/jetty-alpn-parent/jetty-alpn-conscrypt-server/)
+ * Jetty :: ALPN :: JDK9 Client Implementation (org.eclipse.jetty:jetty-alpn-java-client:9.4.58.v20250814 - https://jetty.org/jetty-alpn-parent/jetty-alpn-java-client/)
+ * Jetty :: ALPN :: JDK9 Server Implementation (org.eclipse.jetty:jetty-alpn-java-server:9.4.58.v20250814 - https://jetty.org/jetty-alpn-parent/jetty-alpn-java-server/)
+ * Jetty :: ALPN :: OpenJDK8 Server Implementation (org.eclipse.jetty:jetty-alpn-openjdk8-server:9.4.58.v20250814 - https://jetty.org/jetty-alpn-parent/jetty-alpn-openjdk8-server/)
+ * Jetty :: ALPN :: Server (org.eclipse.jetty:jetty-alpn-server:9.4.58.v20250814 - https://jetty.org/jetty-alpn-parent/jetty-alpn-server/)
+ * Jetty :: Apache JSP Implementation (org.eclipse.jetty:apache-jsp:9.4.58.v20250814 - https://jetty.org/apache-jsp/)
+ * Jetty :: Asynchronous HTTP Client (org.eclipse.jetty:jetty-client:9.4.58.v20250814 - https://jetty.org/jetty-client/)
+ * Jetty :: CDI (org.eclipse.jetty:jetty-cdi:9.4.58.v20250814 - https://jetty.org/jetty-cdi/)
+ * Jetty :: Continuation (org.eclipse.jetty:jetty-continuation:9.4.58.v20250814 - https://jetty.org/jetty-continuation/)
+ * Jetty :: Deployers (org.eclipse.jetty:jetty-deploy:9.4.58.v20250814 - https://jetty.org/jetty-deploy/)
+ * Jetty :: Distribution Assemblies (org.eclipse.jetty:jetty-distribution:9.4.58.v20250814 - https://jetty.org/jetty-distribution/)
+ * Jetty :: FastCGI :: Client (org.eclipse.jetty.fcgi:fcgi-client:9.4.58.v20250814 - https://jetty.org/fcgi-parent/fcgi-client/)
+ * Jetty :: FastCGI :: Server (org.eclipse.jetty.fcgi:fcgi-server:9.4.58.v20250814 - https://jetty.org/fcgi-parent/fcgi-server/)
+ * Jetty :: GCloud :: Session Manager (org.eclipse.jetty.gcloud:jetty-gcloud-session-manager:9.4.58.v20250814 - https://jetty.org/gcloud-parent/jetty-gcloud-session-manager/)
+ * Jetty :: Hazelcast Session Manager (org.eclipse.jetty:jetty-hazelcast:9.4.58.v20250814 - https://jetty.org/jetty-hazelcast/)
+ * Jetty :: Home Assembly (org.eclipse.jetty:jetty-home:9.4.58.v20250814 - https://jetty.org/jetty-home/)
+ * Jetty :: HTTP2 :: Common (org.eclipse.jetty.http2:http2-common:9.4.58.v20250814 - https://jetty.org/http2-parent/http2-common/)
+ * Jetty :: HTTP2 :: HPACK (org.eclipse.jetty.http2:http2-hpack:9.4.58.v20250814 - https://jetty.org/http2-parent/http2-hpack/)
+ * Jetty :: HTTP2 :: Server (org.eclipse.jetty.http2:http2-server:9.4.58.v20250814 - https://jetty.org/http2-parent/http2-server/)
+ * Jetty :: Http Utility (org.eclipse.jetty:jetty-http:9.4.58.v20250814 - https://jetty.org/jetty-http/)
+ * Jetty :: IO Utility (org.eclipse.jetty:jetty-io:9.4.58.v20250814 - https://jetty.org/jetty-io/)
+ * Jetty :: JAAS (org.eclipse.jetty:jetty-jaas:9.4.58.v20250814 - https://jetty.org/jetty-jaas/)
+ * Jetty :: Jakarta Servlet API and Schemas for JPMS and OSGi (org.eclipse.jetty.toolchain:jetty-jakarta-servlet-api:5.0.2 - https://eclipse.org/jetty/jetty-jakarta-servlet-api)
+ * Jetty :: Jakarta WebSocket API for JPMS and OSGi (org.eclipse.jetty.toolchain:jetty-jakarta-websocket-api:2.0.0 - https://eclipse.org/jetty/jetty-jakarta-websocket-api)
+ * Jetty :: JASPI Security (org.eclipse.jetty:jetty-jaspi:9.4.58.v20250814 - https://jetty.org/jetty-jaspi/)
+ * Jetty :: JMX Management (org.eclipse.jetty:jetty-jmx:9.4.58.v20250814 - https://jetty.org/jetty-jmx/)
+ * Jetty :: JNDI Naming (org.eclipse.jetty:jetty-jndi:9.4.58.v20250814 - https://jetty.org/jetty-jndi/)
+ * Jetty :: Memcached :: Sessions (org.eclipse.jetty.memcached:jetty-memcached-sessions:9.4.58.v20250814 - https://jetty.org/memcached-parent/jetty-memcached-sessions/)
+ * Jetty :: NoSQL Session Managers (org.eclipse.jetty:jetty-nosql:9.4.58.v20250814 - https://jetty.org/jetty-nosql/)
+ * Jetty :: OpenID (org.eclipse.jetty:jetty-openid:9.4.58.v20250814 - https://jetty.org/jetty-openid/)
+ * Jetty :: Plus (org.eclipse.jetty:jetty-plus:9.4.58.v20250814 - https://jetty.org/jetty-plus/)
+ * Jetty :: Proxy (org.eclipse.jetty:jetty-proxy:9.4.58.v20250814 - https://jetty.org/jetty-proxy/)
+ * Jetty :: Quick Start (org.eclipse.jetty:jetty-quickstart:9.4.58.v20250814 - https://jetty.org/jetty-quickstart/)
+ * Jetty :: Rewrite Handler (org.eclipse.jetty:jetty-rewrite:9.4.58.v20250814 - https://jetty.org/jetty-rewrite/)
* Jetty :: Schemas (org.eclipse.jetty.toolchain:jetty-schemas:3.1 - http://www.eclipse.org/jetty/jetty-toolchain/jetty-schemas)
- * Jetty :: Security (org.eclipse.jetty:jetty-security:9.4.49.v20220914 - https://eclipse.org/jetty/jetty-security)
- * Jetty :: Server Core (org.eclipse.jetty:jetty-server:9.4.49.v20220914 - https://eclipse.org/jetty/jetty-server)
- * Jetty :: Servlet Annotations (org.eclipse.jetty:jetty-annotations:9.4.49.v20220914 - https://eclipse.org/jetty/jetty-annotations)
- * Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:9.4.49.v20220914 - https://eclipse.org/jetty/jetty-servlet)
- * Jetty :: Utilities :: Ajax(JSON) (org.eclipse.jetty:jetty-util-ajax:9.4.49.v20220914 - https://eclipse.org/jetty/jetty-util-ajax)
- * Jetty :: Utilities (org.eclipse.jetty:jetty-util:9.4.49.v20220914 - https://eclipse.org/jetty/jetty-util)
- * Jetty :: Utility Servlets and Filters (org.eclipse.jetty:jetty-servlets:9.4.49.v20220914 - https://eclipse.org/jetty/jetty-servlets)
- * Jetty :: Webapp Application Support (org.eclipse.jetty:jetty-webapp:9.4.49.v20220914 - https://eclipse.org/jetty/jetty-webapp)
- * Jetty :: XML utilities (org.eclipse.jetty:jetty-xml:9.4.49.v20220914 - https://eclipse.org/jetty/jetty-xml)
+ * Jetty :: Schemas (org.eclipse.jetty.toolchain:jetty-schemas:5.2 - https://eclipse.org/jetty/jetty-schemas)
+ * Jetty :: Security (org.eclipse.jetty:jetty-security:9.4.58.v20250814 - https://jetty.org/jetty-security/)
+ * Jetty :: Server Core (org.eclipse.jetty:jetty-server:9.4.58.v20250814 - https://jetty.org/jetty-server/)
+ * Jetty :: Servlet Annotations (org.eclipse.jetty:jetty-annotations:9.4.58.v20250814 - https://jetty.org/jetty-annotations/)
+ * Jetty :: Servlet API and Schemas for JPMS and OSGi (org.eclipse.jetty.toolchain:jetty-servlet-api:4.0.6 - https://eclipse.org/jetty/jetty-servlet-api)
+ * Jetty :: Servlet Handling (org.eclipse.jetty:jetty-servlet:9.4.58.v20250814 - https://jetty.org/jetty-servlet/)
+ * Jetty :: Start (org.eclipse.jetty:jetty-start:9.4.58.v20250814 - https://jetty.org/jetty-start/)
+ * Jetty :: UnixSocket (org.eclipse.jetty:jetty-unixsocket:9.4.58.v20250814 - https://jetty.org/jetty-unixsocket/)
+ * Jetty :: Utilities :: Ajax(JSON) (org.eclipse.jetty:jetty-util-ajax:9.4.58.v20250814 - https://jetty.org/jetty-util-ajax/)
+ * Jetty :: Utilities (org.eclipse.jetty:jetty-util:9.4.58.v20250814 - https://jetty.org/jetty-util/)
+ * Jetty :: Utility Servlets and Filters (org.eclipse.jetty:jetty-servlets:9.4.58.v20250814 - https://jetty.org/jetty-servlets/)
+ * Jetty :: Webapp Application Support (org.eclipse.jetty:jetty-webapp:9.4.58.v20250814 - https://jetty.org/jetty-webapp/)
+ * Jetty :: Websocket :: API (org.eclipse.jetty.websocket:websocket-api:9.4.58.v20250814 - https://jetty.org/websocket-parent/websocket-api/)
+ * Jetty :: Websocket :: Client (org.eclipse.jetty.websocket:websocket-client:9.4.58.v20250814 - https://jetty.org/websocket-parent/websocket-client/)
+ * Jetty :: Websocket :: Common (org.eclipse.jetty.websocket:websocket-common:9.4.58.v20250814 - https://jetty.org/websocket-parent/websocket-common/)
+ * Jetty :: Websocket :: javax.websocket :: Client Implementation (org.eclipse.jetty.websocket:javax-websocket-client-impl:9.4.58.v20250814 - https://jetty.org/websocket-parent/javax-websocket-client-impl/)
+ * Jetty :: Websocket :: javax.websocket.server :: Server Implementation (org.eclipse.jetty.websocket:javax-websocket-server-impl:9.4.58.v20250814 - https://jetty.org/websocket-parent/javax-websocket-server-impl/)
+ * Jetty :: Websocket :: Server (org.eclipse.jetty.websocket:websocket-server:9.4.58.v20250814 - https://jetty.org/websocket-parent/websocket-server/)
+ * Jetty :: Websocket :: Servlet Interface (org.eclipse.jetty.websocket:websocket-servlet:9.4.58.v20250814 - https://jetty.org/websocket-parent/websocket-servlet/)
+ * Jetty :: WebSocket API for JPMS and OSGi (org.eclipse.jetty.toolchain:jetty-javax-websocket-api:1.1.2 - https://eclipse.org/jetty/jetty-javax-websocket-api)
+ * Jetty :: XML utilities (org.eclipse.jetty:jetty-xml:9.4.58.v20250814 - https://jetty.org/jetty-xml/)
+ * Jetty Orbit :: Activation (org.eclipse.jetty.orbit:javax.activation:1.1.0.v201105071233 - http://www.eclipse.org/jetty/jetty-orbit/javax.activation)
+ * Jetty Orbit :: Glassfish Mail (org.eclipse.jetty.orbit:javax.mail.glassfish:1.4.1.v201005082020 - http://www.eclipse.org/jetty/jetty-orbit/javax.mail.glassfish)
+ * Jetty Orbit :: JASPI API (org.eclipse.jetty.orbit:javax.security.auth.message:1.0.0.v201108011116 - http://www.eclipse.org/jetty/jetty-orbit/javax.security.auth.message)
+
+ Apache License, Version 2.0, Eclipse Public License - Version 2.0
+
+ * Core :: ALPN :: Bouncy Castle Server (org.eclipse.jetty:jetty-alpn-bouncycastle-server:12.0.26 - https://jetty.org/jetty-core/jetty-alpn/jetty-alpn-bouncycastle-server)
+ * Core :: ALPN :: Bouncy Castle Server (org.eclipse.jetty:jetty-alpn-bouncycastle-server:12.1.1 - https://jetty.org/jetty-core/jetty-alpn/jetty-alpn-bouncycastle-server)
+ * Core :: Annotations (org.eclipse.jetty:jetty-annotations:12.1.1 - https://jetty.org/jetty-core/jetty-annotations)
+ * Core :: App (org.eclipse.jetty:jetty-coreapp:12.1.1 - https://jetty.org/jetty-core/jetty-coreapp)
+ * Core :: Compression :: Brotli Support (org.eclipse.jetty.compression:jetty-compression-brotli:12.1.1 - https://jetty.org/jetty-core/jetty-compression/jetty-compression-brotli)
+ * Core :: Compression :: Common (org.eclipse.jetty.compression:jetty-compression-common:12.1.1 - https://jetty.org/jetty-core/jetty-compression/jetty-compression-common)
+ * Core :: Compression :: Gzip Support (org.eclipse.jetty.compression:jetty-compression-gzip:12.1.1 - https://jetty.org/jetty-core/jetty-compression/jetty-compression-gzip)
+ * Core :: Compression :: Server (org.eclipse.jetty.compression:jetty-compression-server:12.1.1 - https://jetty.org/jetty-core/jetty-compression/jetty-compression-server)
+ * Core :: Compression :: Zstandard Support (org.eclipse.jetty.compression:jetty-compression-zstandard:12.1.1 - https://jetty.org/jetty-core/jetty-compression/jetty-compression-zstandard)
+ * Core :: EE Common :: Webapp (org.eclipse.jetty.ee:jetty-ee-webapp:12.1.1 - https://jetty.org/jetty-core/jetty-ee/jetty-ee-webapp)
+ * Core :: EE Common (org.eclipse.jetty:jetty-ee:12.0.26 - https://jetty.org/jetty-core/jetty-ee)
+ * Core :: HTTP (org.eclipse.jetty:jetty-http:12.0.26 - https://jetty.org/jetty-core/jetty-http)
+ * Core :: HTTP (org.eclipse.jetty:jetty-http:12.1.1 - https://jetty.org/jetty-core/jetty-http)
+ * Core :: HTTP2 :: Client (org.eclipse.jetty.http2:jetty-http2-client:12.0.26 - https://jetty.org/jetty-core/jetty-http2/jetty-http2-client)
+ * Core :: HTTP2 :: Client (org.eclipse.jetty.http2:jetty-http2-client:12.1.1 - https://jetty.org/jetty-core/jetty-http2/jetty-http2-client)
+ * Core :: HTTP2 :: Client Transport (org.eclipse.jetty.http2:jetty-http2-client-transport:12.0.26 - https://jetty.org/jetty-core/jetty-http2/jetty-http2-client-transport)
+ * Core :: HTTP2 :: Client Transport (org.eclipse.jetty.http2:jetty-http2-client-transport:12.1.1 - https://jetty.org/jetty-core/jetty-http2/jetty-http2-client-transport)
+ * Core :: HTTP2 :: Common (org.eclipse.jetty.http2:jetty-http2-common:12.0.26 - https://jetty.org/jetty-core/jetty-http2/jetty-http2-common)
+ * Core :: HTTP2 :: Common (org.eclipse.jetty.http2:jetty-http2-common:12.1.1 - https://jetty.org/jetty-core/jetty-http2/jetty-http2-common)
+ * Core :: HTTP2 :: HPACK (org.eclipse.jetty.http2:jetty-http2-hpack:12.0.26 - https://jetty.org/jetty-core/jetty-http2/jetty-http2-hpack)
+ * Core :: HTTP2 :: HPACK (org.eclipse.jetty.http2:jetty-http2-hpack:12.1.1 - https://jetty.org/jetty-core/jetty-http2/jetty-http2-hpack)
+ * Core :: HTTP2 :: Server (org.eclipse.jetty.http2:jetty-http2-server:12.0.26 - https://jetty.org/jetty-core/jetty-http2/jetty-http2-server)
+ * Core :: HTTP2 :: Server (org.eclipse.jetty.http2:jetty-http2-server:12.1.1 - https://jetty.org/jetty-core/jetty-http2/jetty-http2-server)
+ * Core :: HTTP3 :: Common (org.eclipse.jetty.http3:jetty-http3-common:12.0.26 - https://jetty.org/jetty-core/jetty-http3/jetty-http3-common)
+ * Core :: HTTP3 :: Common (org.eclipse.jetty.http3:jetty-http3-common:12.1.1 - https://jetty.org/jetty-core/jetty-http3/jetty-http3-common)
+ * Core :: HTTP3 :: QPACK (org.eclipse.jetty.http3:jetty-http3-qpack:12.0.26 - https://jetty.org/jetty-core/jetty-http3/jetty-http3-qpack)
+ * Core :: HTTP3 :: QPACK (org.eclipse.jetty.http3:jetty-http3-qpack:12.1.1 - https://jetty.org/jetty-core/jetty-http3/jetty-http3-qpack)
+ * Core :: HTTP3 :: Server (org.eclipse.jetty.http3:jetty-http3-server:12.0.26 - https://jetty.org/jetty-core/jetty-http3/jetty-http3-server)
+ * Core :: HTTP3 :: Server (org.eclipse.jetty.http3:jetty-http3-server:12.1.1 - https://jetty.org/jetty-core/jetty-http3/jetty-http3-server)
+ * Core :: HTTP Client (org.eclipse.jetty:jetty-client:12.0.26 - https://jetty.org/jetty-core/jetty-client)
+ * Core :: HTTP Client (org.eclipse.jetty:jetty-client:12.1.1 - https://jetty.org/jetty-core/jetty-client)
+ * Core :: IO (org.eclipse.jetty:jetty-io:12.0.26 - https://jetty.org/jetty-core/jetty-io)
+ * Core :: IO (org.eclipse.jetty:jetty-io:12.1.1 - https://jetty.org/jetty-core/jetty-io)
+ * Core :: JNDI (org.eclipse.jetty:jetty-jndi:12.0.26 - https://jetty.org/jetty-core/jetty-jndi)
+ * Core :: JNDI (org.eclipse.jetty:jetty-jndi:12.1.1 - https://jetty.org/jetty-core/jetty-jndi)
+ * Core :: Plus (org.eclipse.jetty:jetty-plus:12.0.26 - https://jetty.org/jetty-core/jetty-plus)
+ * Core :: Plus (org.eclipse.jetty:jetty-plus:12.1.1 - https://jetty.org/jetty-core/jetty-plus)
+ * Core :: QUIC :: APIs (org.eclipse.jetty.quic:jetty-quic-api:12.1.1 - https://jetty.org/jetty-core/jetty-quic/jetty-quic-api)
+ * Core :: QUIC :: Common (org.eclipse.jetty.quic:jetty-quic-common:12.0.26 - https://jetty.org/jetty-core/jetty-quic/jetty-quic-common)
+ * Core :: QUIC :: Common (org.eclipse.jetty.quic:jetty-quic-common:12.1.1 - https://jetty.org/jetty-core/jetty-quic/jetty-quic-common)
+ * Core :: QUIC :: Quiche :: Common (org.eclipse.jetty.quic:jetty-quic-quiche-common:12.0.26 - https://jetty.org/jetty-core/jetty-quic/jetty-quic-quiche/jetty-quic-quiche-common)
+ * Core :: QUIC :: Quiche :: Common (org.eclipse.jetty.quic:jetty-quic-quiche-common:12.1.1 - https://jetty.org/jetty-core/jetty-quic/jetty-quic-quiche/jetty-quic-quiche-common)
+ * Core :: QUIC :: Quiche :: Foreign (org.eclipse.jetty.quic:jetty-quic-quiche-foreign:12.0.26 - https://jetty.org/jetty-core/jetty-quic/jetty-quic-quiche/jetty-quic-quiche-foreign)
+ * Core :: QUIC :: Quiche :: Foreign Binding (org.eclipse.jetty.quic:jetty-quic-quiche-foreign:12.1.1 - https://jetty.org/jetty-core/jetty-quic/jetty-quic-quiche/jetty-quic-quiche-foreign)
+ * Core :: QUIC :: Quiche :: JNA Binding (org.eclipse.jetty.quic:jetty-quic-quiche-jna:12.0.26 - https://jetty.org/jetty-core/jetty-quic/jetty-quic-quiche/jetty-quic-quiche-jna)
+ * Core :: QUIC :: Quiche :: JNA Binding (org.eclipse.jetty.quic:jetty-quic-quiche-jna:12.1.1 - https://jetty.org/jetty-core/jetty-quic/jetty-quic-quiche/jetty-quic-quiche-jna)
+ * Core :: QUIC :: Quiche :: Server (org.eclipse.jetty.quic:jetty-quic-quiche-server:12.1.1 - https://jetty.org/jetty-core/jetty-quic/jetty-quic-quiche/jetty-quic-quiche-server)
+ * Core :: QUIC :: Server (org.eclipse.jetty.quic:jetty-quic-server:12.0.26 - https://jetty.org/jetty-core/jetty-quic/jetty-quic-server)
+ * Core :: QUIC :: Server (org.eclipse.jetty.quic:jetty-quic-server:12.1.1 - https://jetty.org/jetty-core/jetty-quic/jetty-quic-server)
+ * Core :: QUIC :: Utilities (org.eclipse.jetty.quic:jetty-quic-util:12.1.1 - https://jetty.org/jetty-core/jetty-quic/jetty-quic-util)
+ * Core :: Security (org.eclipse.jetty:jetty-security:12.0.26 - https://jetty.org/jetty-core/jetty-security)
+ * Core :: Security (org.eclipse.jetty:jetty-security:12.1.1 - https://jetty.org/jetty-core/jetty-security)
+ * Core :: Server (org.eclipse.jetty:jetty-server:12.0.26 - https://jetty.org/jetty-core/jetty-server)
+ * Core :: Server (org.eclipse.jetty:jetty-server:12.1.1 - https://jetty.org/jetty-core/jetty-server)
+ * Core :: Sessions (org.eclipse.jetty:jetty-session:12.0.26 - https://jetty.org/jetty-core/jetty-session)
+ * Core :: Sessions (org.eclipse.jetty:jetty-session:12.1.1 - https://jetty.org/jetty-core/jetty-session)
+ * Core :: Sign-In with Ethereum (org.eclipse.jetty:jetty-ethereum:12.1.1 - https://jetty.org/jetty-integrations/jetty-ethereum)
+ * Core :: Start (org.eclipse.jetty:jetty-start:12.0.26 - https://jetty.org/jetty-core/jetty-start)
+ * Core :: Start (org.eclipse.jetty:jetty-start:12.1.1 - https://jetty.org/jetty-core/jetty-start)
+ * Core :: Static App (org.eclipse.jetty:jetty-staticapp:12.1.1 - https://jetty.org/jetty-core/jetty-staticapp)
+ * Core :: Utilities (org.eclipse.jetty:jetty-util:12.0.26 - https://jetty.org/jetty-core/jetty-util)
+ * Core :: Utilities (org.eclipse.jetty:jetty-util:12.1.1 - https://jetty.org/jetty-core/jetty-util)
+ * Core :: Websocket :: Client (org.eclipse.jetty.websocket:jetty-websocket-core-client:12.0.26 - https://jetty.org/jetty-core/jetty-websocket/jetty-websocket-core-client)
+ * Core :: Websocket :: Client (org.eclipse.jetty.websocket:jetty-websocket-core-client:12.1.1 - https://jetty.org/jetty-core/jetty-websocket/jetty-websocket-core-client)
+ * Core :: Websocket :: Common (org.eclipse.jetty.websocket:jetty-websocket-core-common:12.0.26 - https://jetty.org/jetty-core/jetty-websocket/jetty-websocket-core-common)
+ * Core :: Websocket :: Common (org.eclipse.jetty.websocket:jetty-websocket-core-common:12.1.1 - https://jetty.org/jetty-core/jetty-websocket/jetty-websocket-core-common)
+ * Core :: Websocket :: Jetty API (org.eclipse.jetty.websocket:jetty-websocket-jetty-api:12.0.26 - https://jetty.org/jetty-core/jetty-websocket/jetty-websocket-jetty-api)
+ * Core :: Websocket :: Jetty API (org.eclipse.jetty.websocket:jetty-websocket-jetty-api:12.1.1 - https://jetty.org/jetty-core/jetty-websocket/jetty-websocket-jetty-api)
+ * Core :: Websocket :: Jetty Client (org.eclipse.jetty.websocket:jetty-websocket-jetty-client:12.0.26 - https://jetty.org/jetty-core/jetty-websocket/jetty-websocket-jetty-client)
+ * Core :: Websocket :: Jetty Client (org.eclipse.jetty.websocket:jetty-websocket-jetty-client:12.1.1 - https://jetty.org/jetty-core/jetty-websocket/jetty-websocket-jetty-client)
+ * Core :: Websocket :: Jetty Common (org.eclipse.jetty.websocket:jetty-websocket-jetty-common:12.0.26 - https://jetty.org/jetty-core/jetty-websocket/jetty-websocket-jetty-common)
+ * Core :: Websocket :: Jetty Common (org.eclipse.jetty.websocket:jetty-websocket-jetty-common:12.1.1 - https://jetty.org/jetty-core/jetty-websocket/jetty-websocket-jetty-common)
+ * Core :: Websocket :: Jetty Server (org.eclipse.jetty.websocket:jetty-websocket-jetty-server:12.0.26 - https://jetty.org/jetty-core/jetty-websocket/jetty-websocket-jetty-server)
+ * Core :: Websocket :: Jetty Server (org.eclipse.jetty.websocket:jetty-websocket-jetty-server:12.1.1 - https://jetty.org/jetty-core/jetty-websocket/jetty-websocket-jetty-server)
+ * Core :: Websocket :: Server (org.eclipse.jetty.websocket:jetty-websocket-core-server:12.0.26 - https://jetty.org/jetty-core/jetty-websocket/jetty-websocket-core-server)
+ * Core :: Websocket :: Server (org.eclipse.jetty.websocket:jetty-websocket-core-server:12.1.1 - https://jetty.org/jetty-core/jetty-websocket/jetty-websocket-core-server)
+ * Core :: XML (org.eclipse.jetty:jetty-xml:12.0.26 - https://jetty.org/jetty-core/jetty-xml)
+ * Core :: XML (org.eclipse.jetty:jetty-xml:12.1.1 - https://jetty.org/jetty-core/jetty-xml)
+ * EE10 :: Apache JSP (org.eclipse.jetty.ee10:jetty-ee10-apache-jsp:12.0.26 - https://jetty.org/jetty-ee10/jetty-ee10-apache-jsp)
+ * EE10 :: Apache JSP (org.eclipse.jetty.ee10:jetty-ee10-apache-jsp:12.1.1 - https://jetty.org/jetty-ee10/jetty-ee10-apache-jsp)
+ * EE10 :: Glassfish JSTL (org.eclipse.jetty.ee10:jetty-ee10-glassfish-jstl:12.0.26 - https://projects.eclipse.org/projects/ee4j.glassfish)
+ * EE10 :: Glassfish JSTL (org.eclipse.jetty.ee10:jetty-ee10-glassfish-jstl:12.1.1 - https://projects.eclipse.org/projects/ee4j.glassfish)
+ * EE10 :: Home Assembly (org.eclipse.jetty.ee10:jetty-ee10-home:12.0.26 - https://jetty.org/jetty-ee10/jetty-ee10-home)
+ * EE10 :: Home Assembly (org.eclipse.jetty.ee10:jetty-ee10-home:12.1.1 - https://jetty.org/jetty-ee10/jetty-ee10-home)
+ * EE10 :: Plus (org.eclipse.jetty.ee10:jetty-ee10-plus:12.0.26 - https://jetty.org/jetty-ee10/jetty-ee10-plus)
+ * EE10 :: Plus (org.eclipse.jetty.ee10:jetty-ee10-plus:12.1.1 - https://jetty.org/jetty-ee10/jetty-ee10-plus)
+ * EE10 :: Proxy (org.eclipse.jetty.ee10:jetty-ee10-proxy:12.0.26 - https://jetty.org/jetty-ee10/jetty-ee10-proxy)
+ * EE10 :: Proxy (org.eclipse.jetty.ee10:jetty-ee10-proxy:12.1.1 - https://jetty.org/jetty-ee10/jetty-ee10-proxy)
+ * EE10 :: Quick Start (org.eclipse.jetty.ee10:jetty-ee10-quickstart:12.0.26 - https://jetty.org/jetty-ee10/jetty-ee10-quickstart)
+ * EE10 :: Quick Start (org.eclipse.jetty.ee10:jetty-ee10-quickstart:12.1.1 - https://jetty.org/jetty-ee10/jetty-ee10-quickstart)
+ * EE10 :: Servlet (org.eclipse.jetty.ee10:jetty-ee10-servlet:12.0.26 - https://jetty.org/jetty-ee10/jetty-ee10-servlet)
+ * EE10 :: Servlet (org.eclipse.jetty.ee10:jetty-ee10-servlet:12.1.1 - https://jetty.org/jetty-ee10/jetty-ee10-servlet)
+ * EE10 :: Servlet Annotations (org.eclipse.jetty.ee10:jetty-ee10-annotations:12.0.26 - https://jetty.org/jetty-ee10/jetty-ee10-annotations)
+ * EE10 :: Servlet Annotations (org.eclipse.jetty.ee10:jetty-ee10-annotations:12.1.1 - https://jetty.org/jetty-ee10/jetty-ee10-annotations)
+ * EE10 :: Utility Servlets and Filters (org.eclipse.jetty.ee10:jetty-ee10-servlets:12.0.26 - https://jetty.org/jetty-ee10/jetty-ee10-servlets)
+ * EE10 :: Utility Servlets and Filters (org.eclipse.jetty.ee10:jetty-ee10-servlets:12.1.1 - https://jetty.org/jetty-ee10/jetty-ee10-servlets)
+ * EE10 :: WebApp (org.eclipse.jetty.ee10:jetty-ee10-webapp:12.0.26 - https://jetty.org/jetty-ee10/jetty-ee10-webapp)
+ * EE10 :: WebApp (org.eclipse.jetty.ee10:jetty-ee10-webapp:12.1.1 - https://jetty.org/jetty-ee10/jetty-ee10-webapp)
+ * EE10 :: Websocket :: Jakarta Client (org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-jakarta-client:12.0.26 - https://jetty.org/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-client)
+ * EE10 :: Websocket :: Jakarta Client (org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-jakarta-client:12.1.1 - https://jetty.org/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-client)
+ * EE10 :: Websocket :: Jakarta Common (org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-jakarta-common:12.0.26 - https://jetty.org/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-common)
+ * EE10 :: Websocket :: Jakarta Common (org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-jakarta-common:12.1.1 - https://jetty.org/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-common)
+ * EE10 :: Websocket :: Jakarta Server (org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-jakarta-server:12.0.26 - https://jetty.org/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-server)
+ * EE10 :: Websocket :: Jakarta Server (org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-jakarta-server:12.1.1 - https://jetty.org/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jakarta-server)
+ * EE10 :: Websocket :: Jetty Client WebApp (org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-jetty-client-webapp:12.0.26 - https://jetty.org/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-client-webapp)
+ * EE10 :: Websocket :: Jetty Client WebApp (org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-jetty-client-webapp:12.1.1 - https://jetty.org/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-client-webapp)
+ * EE10 :: Websocket :: Jetty Server (org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-jetty-server:12.0.26 - https://jetty.org/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-server)
+ * EE10 :: Websocket :: Jetty Server (org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-jetty-server:12.1.1 - https://jetty.org/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-server)
+ * EE10 :: Websocket :: Servlet (org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-servlet:12.0.26 - https://jetty.org/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-servlet)
+ * EE10 :: Websocket :: Servlet (org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-servlet:12.1.1 - https://jetty.org/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-servlet)
+ * EE11 :: Apache JSP (org.eclipse.jetty.ee11:jetty-ee11-apache-jsp:12.1.1 - https://jetty.org/jetty-ee11/jetty-ee11-apache-jsp)
+ * EE11 :: Glassfish JSTL (org.eclipse.jetty.ee11:jetty-ee11-glassfish-jstl:12.1.1 - https://projects.eclipse.org/projects/ee4j.glassfish)
+ * EE11 :: Home Assembly (org.eclipse.jetty.ee11:jetty-ee11-home:12.1.1 - https://jetty.org/jetty-ee11/jetty-ee11-home)
+ * EE11 :: Plus (org.eclipse.jetty.ee11:jetty-ee11-plus:12.1.1 - https://jetty.org/jetty-ee11/jetty-ee11-plus)
+ * EE11 :: Proxy (org.eclipse.jetty.ee11:jetty-ee11-proxy:12.1.1 - https://jetty.org/jetty-ee11/jetty-ee11-proxy)
+ * EE11 :: Quick Start (org.eclipse.jetty.ee11:jetty-ee11-quickstart:12.1.1 - https://jetty.org/jetty-ee11/jetty-ee11-quickstart)
+ * EE11 :: Servlet (org.eclipse.jetty.ee11:jetty-ee11-servlet:12.1.1 - https://jetty.org/jetty-ee11/jetty-ee11-servlet)
+ * EE11 :: Servlet Annotations (org.eclipse.jetty.ee11:jetty-ee11-annotations:12.1.1 - https://jetty.org/jetty-ee11/jetty-ee11-annotations)
+ * EE11 :: Utility Servlets and Filters (org.eclipse.jetty.ee11:jetty-ee11-servlets:12.1.1 - https://jetty.org/jetty-ee11/jetty-ee11-servlets)
+ * EE11 :: WebApp (org.eclipse.jetty.ee11:jetty-ee11-webapp:12.1.1 - https://jetty.org/jetty-ee11/jetty-ee11-webapp)
+ * EE11 :: Websocket :: Jakarta Client (org.eclipse.jetty.ee11.websocket:jetty-ee11-websocket-jakarta-client:12.1.1 - https://jetty.org/jetty-ee11/jetty-ee11-websocket/jetty-ee11-websocket-jakarta-client)
+ * EE11 :: Websocket :: Jakarta Common (org.eclipse.jetty.ee11.websocket:jetty-ee11-websocket-jakarta-common:12.1.1 - https://jetty.org/jetty-ee11/jetty-ee11-websocket/jetty-ee11-websocket-jakarta-common)
+ * EE11 :: Websocket :: Jakarta Server (org.eclipse.jetty.ee11.websocket:jetty-ee11-websocket-jakarta-server:12.1.1 - https://jetty.org/jetty-ee11/jetty-ee11-websocket/jetty-ee11-websocket-jakarta-server)
+ * EE11 :: Websocket :: Jetty Client WebApp (org.eclipse.jetty.ee11.websocket:jetty-ee11-websocket-jetty-client-webapp:12.1.1 - https://jetty.org/jetty-ee11/jetty-ee11-websocket/jetty-ee11-websocket-jetty-client-webapp)
+ * EE11 :: Websocket :: Jetty Server (org.eclipse.jetty.ee11.websocket:jetty-ee11-websocket-jetty-server:12.1.1 - https://jetty.org/jetty-ee11/jetty-ee11-websocket/jetty-ee11-websocket-jetty-server)
+ * EE11 :: Websocket :: Servlet (org.eclipse.jetty.ee11.websocket:jetty-ee11-websocket-servlet:12.1.1 - https://jetty.org/jetty-ee11/jetty-ee11-websocket/jetty-ee11-websocket-servlet)
+ * EE8 :: Apache JSP (org.eclipse.jetty.ee8:jetty-ee8-apache-jsp:12.0.26 - https://jetty.org/jetty-ee8/jetty-ee8-apache-jsp)
+ * EE8 :: Apache JSP (org.eclipse.jetty.ee8:jetty-ee8-apache-jsp:12.1.1 - https://jetty.org/jetty-ee8/jetty-ee8-apache-jsp)
+ * EE8 :: Glassfish JSTL (org.eclipse.jetty.ee8:jetty-ee8-glassfish-jstl:12.0.26 - https://projects.eclipse.org/projects/ee4j.glassfish)
+ * EE8 :: Glassfish JSTL (org.eclipse.jetty.ee8:jetty-ee8-glassfish-jstl:12.1.1 - https://projects.eclipse.org/projects/ee4j.glassfish)
+ * EE8 :: Home Assembly (org.eclipse.jetty.ee8:jetty-ee8-home:12.0.26 - https://jetty.org/jetty-ee8/jetty-ee8-home)
+ * EE8 :: Home Assembly (org.eclipse.jetty.ee8:jetty-ee8-home:12.1.1 - https://jetty.org/jetty-ee8/jetty-ee8-home)
+ * EE8 :: Nested (org.eclipse.jetty.ee8:jetty-ee8-nested:12.0.26 - https://jetty.org/jetty-ee8/jetty-ee8-nested)
+ * EE8 :: Nested (org.eclipse.jetty.ee8:jetty-ee8-nested:12.1.1 - https://jetty.org/jetty-ee8/jetty-ee8-nested)
+ * EE8 :: Plus (org.eclipse.jetty.ee8:jetty-ee8-plus:12.0.26 - https://jetty.org/jetty-ee8/jetty-ee8-plus)
+ * EE8 :: Plus (org.eclipse.jetty.ee8:jetty-ee8-plus:12.1.1 - https://jetty.org/jetty-ee8/jetty-ee8-plus)
+ * EE8 :: Proxy (org.eclipse.jetty.ee8:jetty-ee8-proxy:12.0.26 - https://jetty.org/jetty-ee8/jetty-ee8-proxy)
+ * EE8 :: Proxy (org.eclipse.jetty.ee8:jetty-ee8-proxy:12.1.1 - https://jetty.org/jetty-ee8/jetty-ee8-proxy)
+ * EE8 :: Quick Start (org.eclipse.jetty.ee8:jetty-ee8-quickstart:12.0.26 - https://jetty.org/jetty-ee8/jetty-ee8-quickstart)
+ * EE8 :: Quick Start (org.eclipse.jetty.ee8:jetty-ee8-quickstart:12.1.1 - https://jetty.org/jetty-ee8/jetty-ee8-quickstart)
+ * EE8 :: Security (org.eclipse.jetty.ee8:jetty-ee8-security:12.0.26 - https://jetty.org/jetty-ee8/jetty-ee8-security)
+ * EE8 :: Security (org.eclipse.jetty.ee8:jetty-ee8-security:12.1.1 - https://jetty.org/jetty-ee8/jetty-ee8-security)
+ * EE8 :: Servlet (org.eclipse.jetty.ee8:jetty-ee8-servlet:12.0.26 - https://jetty.org/jetty-ee8/jetty-ee8-servlet)
+ * EE8 :: Servlet (org.eclipse.jetty.ee8:jetty-ee8-servlet:12.1.1 - https://jetty.org/jetty-ee8/jetty-ee8-servlet)
+ * EE8 :: Servlet Annotations (org.eclipse.jetty.ee8:jetty-ee8-annotations:12.0.26 - https://jetty.org/jetty-ee8/jetty-ee8-annotations)
+ * EE8 :: Servlet Annotations (org.eclipse.jetty.ee8:jetty-ee8-annotations:12.1.1 - https://jetty.org/jetty-ee8/jetty-ee8-annotations)
+ * EE8 :: Utility Servlets and Filters (org.eclipse.jetty.ee8:jetty-ee8-servlets:12.0.26 - https://jetty.org/jetty-ee8/jetty-ee8-servlets)
+ * EE8 :: Utility Servlets and Filters (org.eclipse.jetty.ee8:jetty-ee8-servlets:12.1.1 - https://jetty.org/jetty-ee8/jetty-ee8-servlets)
+ * EE8 :: WebApp (org.eclipse.jetty.ee8:jetty-ee8-webapp:12.0.26 - https://jetty.org/jetty-ee8/jetty-ee8-webapp)
+ * EE8 :: WebApp (org.eclipse.jetty.ee8:jetty-ee8-webapp:12.1.1 - https://jetty.org/jetty-ee8/jetty-ee8-webapp)
+ * EE8 :: Websocket :: Javax Client (org.eclipse.jetty.ee8.websocket:jetty-ee8-websocket-javax-client:12.0.26 - https://jetty.org/jetty-ee8/jetty-ee8-websocket/jetty-ee8-websocket-javax-client)
+ * EE8 :: Websocket :: Javax Client (org.eclipse.jetty.ee8.websocket:jetty-ee8-websocket-javax-client:12.1.1 - https://jetty.org/jetty-ee8/jetty-ee8-websocket/jetty-ee8-websocket-javax-client)
+ * EE8 :: Websocket :: Javax Common (org.eclipse.jetty.ee8.websocket:jetty-ee8-websocket-javax-common:12.0.26 - https://jetty.org/jetty-ee8/jetty-ee8-websocket/jetty-ee8-websocket-javax-common)
+ * EE8 :: Websocket :: Javax Common (org.eclipse.jetty.ee8.websocket:jetty-ee8-websocket-javax-common:12.1.1 - https://jetty.org/jetty-ee8/jetty-ee8-websocket/jetty-ee8-websocket-javax-common)
+ * EE8 :: Websocket :: Javax Server (org.eclipse.jetty.ee8.websocket:jetty-ee8-websocket-javax-server:12.0.26 - https://jetty.org/jetty-ee8/jetty-ee8-websocket/jetty-ee8-websocket-javax-server)
+ * EE8 :: Websocket :: Javax Server (org.eclipse.jetty.ee8.websocket:jetty-ee8-websocket-javax-server:12.1.1 - https://jetty.org/jetty-ee8/jetty-ee8-websocket/jetty-ee8-websocket-javax-server)
+ * EE8 :: Websocket :: Jetty API (org.eclipse.jetty.ee8.websocket:jetty-ee8-websocket-jetty-api:12.0.26 - https://jetty.org/jetty-ee8/jetty-ee8-websocket/jetty-ee8-websocket-jetty-api)
+ * EE8 :: Websocket :: Jetty API (org.eclipse.jetty.ee8.websocket:jetty-ee8-websocket-jetty-api:12.1.1 - https://jetty.org/jetty-ee8/jetty-ee8-websocket/jetty-ee8-websocket-jetty-api)
+ * EE8 :: Websocket :: Jetty Client (org.eclipse.jetty.ee8.websocket:jetty-ee8-websocket-jetty-client:12.0.26 - https://jetty.org/jetty-ee8/jetty-ee8-websocket/jetty-ee8-websocket-jetty-client)
+ * EE8 :: Websocket :: Jetty Client (org.eclipse.jetty.ee8.websocket:jetty-ee8-websocket-jetty-client:12.1.1 - https://jetty.org/jetty-ee8/jetty-ee8-websocket/jetty-ee8-websocket-jetty-client)
+ * EE8 :: Websocket :: Jetty Client WebApp (org.eclipse.jetty.ee8.websocket:jetty-ee8-websocket-jetty-client-webapp:12.0.26 - https://jetty.org/jetty-ee8/jetty-ee8-websocket/jetty-ee8-websocket-jetty-client-webapp)
+ * EE8 :: Websocket :: Jetty Client WebApp (org.eclipse.jetty.ee8.websocket:jetty-ee8-websocket-jetty-client-webapp:12.1.1 - https://jetty.org/jetty-ee8/jetty-ee8-websocket/jetty-ee8-websocket-jetty-client-webapp)
+ * EE8 :: Websocket :: Jetty Common (org.eclipse.jetty.ee8.websocket:jetty-ee8-websocket-jetty-common:12.0.26 - https://jetty.org/jetty-ee8/jetty-ee8-websocket/jetty-ee8-websocket-jetty-common)
+ * EE8 :: Websocket :: Jetty Common (org.eclipse.jetty.ee8.websocket:jetty-ee8-websocket-jetty-common:12.1.1 - https://jetty.org/jetty-ee8/jetty-ee8-websocket/jetty-ee8-websocket-jetty-common)
+ * EE8 :: Websocket :: Jetty Server (org.eclipse.jetty.ee8.websocket:jetty-ee8-websocket-jetty-server:12.0.26 - https://jetty.org/jetty-ee8/jetty-ee8-websocket/jetty-ee8-websocket-jetty-server)
+ * EE8 :: Websocket :: Jetty Server (org.eclipse.jetty.ee8.websocket:jetty-ee8-websocket-jetty-server:12.1.1 - https://jetty.org/jetty-ee8/jetty-ee8-websocket/jetty-ee8-websocket-jetty-server)
+ * EE8 :: Websocket :: Servlet (org.eclipse.jetty.ee8.websocket:jetty-ee8-websocket-servlet:12.0.26 - https://jetty.org/jetty-ee8/jetty-ee8-websocket/jetty-ee8-websocket-servlet)
+ * EE8 :: Websocket :: Servlet (org.eclipse.jetty.ee8.websocket:jetty-ee8-websocket-servlet:12.1.1 - https://jetty.org/jetty-ee8/jetty-ee8-websocket/jetty-ee8-websocket-servlet)
+ * EE9 :: Apache JSP (org.eclipse.jetty.ee9:jetty-ee9-apache-jsp:12.0.26 - https://jetty.org/jetty-ee9/jetty-ee9-apache-jsp)
+ * EE9 :: Apache JSP (org.eclipse.jetty.ee9:jetty-ee9-apache-jsp:12.1.1 - https://jetty.org/jetty-ee9/jetty-ee9-apache-jsp)
+ * EE9 :: Glassfish JSTL (org.eclipse.jetty.ee9:jetty-ee9-glassfish-jstl:12.0.26 - https://projects.eclipse.org/projects/ee4j.glassfish)
+ * EE9 :: Glassfish JSTL (org.eclipse.jetty.ee9:jetty-ee9-glassfish-jstl:12.1.1 - https://projects.eclipse.org/projects/ee4j.glassfish)
+ * EE9 :: Home Assembly (org.eclipse.jetty.ee9:jetty-ee9-home:12.0.26 - https://jetty.org/jetty-ee9/jetty-ee9-home)
+ * EE9 :: Home Assembly (org.eclipse.jetty.ee9:jetty-ee9-home:12.1.1 - https://jetty.org/jetty-ee9/jetty-ee9-home)
+ * EE9 :: Nested (org.eclipse.jetty.ee9:jetty-ee9-nested:12.0.26 - https://jetty.org/jetty-ee9/jetty-ee9-nested)
+ * EE9 :: Nested (org.eclipse.jetty.ee9:jetty-ee9-nested:12.1.1 - https://jetty.org/jetty-ee9/jetty-ee9-nested)
+ * EE9 :: Plus (org.eclipse.jetty.ee9:jetty-ee9-plus:12.0.26 - https://jetty.org/jetty-ee9/jetty-ee9-plus)
+ * EE9 :: Plus (org.eclipse.jetty.ee9:jetty-ee9-plus:12.1.1 - https://jetty.org/jetty-ee9/jetty-ee9-plus)
+ * EE9 :: Proxy (org.eclipse.jetty.ee9:jetty-ee9-proxy:12.0.26 - https://jetty.org/jetty-ee9/jetty-ee9-proxy)
+ * EE9 :: Proxy (org.eclipse.jetty.ee9:jetty-ee9-proxy:12.1.1 - https://jetty.org/jetty-ee9/jetty-ee9-proxy)
+ * EE9 :: Quick Start (org.eclipse.jetty.ee9:jetty-ee9-quickstart:12.0.26 - https://jetty.org/jetty-ee9/jetty-ee9-quickstart)
+ * EE9 :: Quick Start (org.eclipse.jetty.ee9:jetty-ee9-quickstart:12.1.1 - https://jetty.org/jetty-ee9/jetty-ee9-quickstart)
+ * EE9 :: Security (org.eclipse.jetty.ee9:jetty-ee9-security:12.0.26 - https://jetty.org/jetty-ee9/jetty-ee9-security)
+ * EE9 :: Security (org.eclipse.jetty.ee9:jetty-ee9-security:12.1.1 - https://jetty.org/jetty-ee9/jetty-ee9-security)
+ * EE9 :: Servlet (org.eclipse.jetty.ee9:jetty-ee9-servlet:12.0.26 - https://jetty.org/jetty-ee9/jetty-ee9-servlet)
+ * EE9 :: Servlet (org.eclipse.jetty.ee9:jetty-ee9-servlet:12.1.1 - https://jetty.org/jetty-ee9/jetty-ee9-servlet)
+ * EE9 :: Servlet Annotations (org.eclipse.jetty.ee9:jetty-ee9-annotations:12.0.26 - https://jetty.org/jetty-ee9/jetty-ee9-annotations)
+ * EE9 :: Servlet Annotations (org.eclipse.jetty.ee9:jetty-ee9-annotations:12.1.1 - https://jetty.org/jetty-ee9/jetty-ee9-annotations)
+ * EE9 :: Utility Servlets and Filters (org.eclipse.jetty.ee9:jetty-ee9-servlets:12.0.26 - https://jetty.org/jetty-ee9/jetty-ee9-servlets)
+ * EE9 :: Utility Servlets and Filters (org.eclipse.jetty.ee9:jetty-ee9-servlets:12.1.1 - https://jetty.org/jetty-ee9/jetty-ee9-servlets)
+ * EE9 :: WebApp (org.eclipse.jetty.ee9:jetty-ee9-webapp:12.0.26 - https://jetty.org/jetty-ee9/jetty-ee9-webapp)
+ * EE9 :: WebApp (org.eclipse.jetty.ee9:jetty-ee9-webapp:12.1.1 - https://jetty.org/jetty-ee9/jetty-ee9-webapp)
+ * EE9 :: Websocket :: Jakarta Client (org.eclipse.jetty.ee9.websocket:jetty-ee9-websocket-jakarta-client:12.0.26 - https://jetty.org/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-client)
+ * EE9 :: Websocket :: Jakarta Client (org.eclipse.jetty.ee9.websocket:jetty-ee9-websocket-jakarta-client:12.1.1 - https://jetty.org/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-client)
+ * EE9 :: Websocket :: Jakarta Common (org.eclipse.jetty.ee9.websocket:jetty-ee9-websocket-jakarta-common:12.0.26 - https://jetty.org/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common)
+ * EE9 :: Websocket :: Jakarta Common (org.eclipse.jetty.ee9.websocket:jetty-ee9-websocket-jakarta-common:12.1.1 - https://jetty.org/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-common)
+ * EE9 :: Websocket :: Jakarta Server (org.eclipse.jetty.ee9.websocket:jetty-ee9-websocket-jakarta-server:12.0.26 - https://jetty.org/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-server)
+ * EE9 :: Websocket :: Jakarta Server (org.eclipse.jetty.ee9.websocket:jetty-ee9-websocket-jakarta-server:12.1.1 - https://jetty.org/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jakarta-server)
+ * EE9 :: Websocket :: Jetty API (org.eclipse.jetty.ee9.websocket:jetty-ee9-websocket-jetty-api:12.0.26 - https://jetty.org/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-api)
+ * EE9 :: Websocket :: Jetty API (org.eclipse.jetty.ee9.websocket:jetty-ee9-websocket-jetty-api:12.1.1 - https://jetty.org/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-api)
+ * EE9 :: Websocket :: Jetty Client (org.eclipse.jetty.ee9.websocket:jetty-ee9-websocket-jetty-client:12.0.26 - https://jetty.org/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-client)
+ * EE9 :: Websocket :: Jetty Client (org.eclipse.jetty.ee9.websocket:jetty-ee9-websocket-jetty-client:12.1.1 - https://jetty.org/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-client)
+ * EE9 :: Websocket :: Jetty Client WebApp (org.eclipse.jetty.ee9.websocket:jetty-ee9-websocket-jetty-client-webapp:12.0.26 - https://jetty.org/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-client-webapp)
+ * EE9 :: Websocket :: Jetty Client WebApp (org.eclipse.jetty.ee9.websocket:jetty-ee9-websocket-jetty-client-webapp:12.1.1 - https://jetty.org/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-client-webapp)
+ * EE9 :: Websocket :: Jetty Common (org.eclipse.jetty.ee9.websocket:jetty-ee9-websocket-jetty-common:12.0.26 - https://jetty.org/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-common)
+ * EE9 :: Websocket :: Jetty Common (org.eclipse.jetty.ee9.websocket:jetty-ee9-websocket-jetty-common:12.1.1 - https://jetty.org/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-common)
+ * EE9 :: Websocket :: Jetty Server (org.eclipse.jetty.ee9.websocket:jetty-ee9-websocket-jetty-server:12.0.26 - https://jetty.org/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-server)
+ * EE9 :: Websocket :: Jetty Server (org.eclipse.jetty.ee9.websocket:jetty-ee9-websocket-jetty-server:12.1.1 - https://jetty.org/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-server)
+ * EE9 :: Websocket :: Servlet (org.eclipse.jetty.ee9.websocket:jetty-ee9-websocket-servlet:12.0.26 - https://jetty.org/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-servlet)
+ * EE9 :: Websocket :: Servlet (org.eclipse.jetty.ee9.websocket:jetty-ee9-websocket-servlet:12.1.1 - https://jetty.org/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-servlet)
+ * Home (org.eclipse.jetty:jetty-home:12.0.26 - https://jetty.org/jetty-home)
+ * Home (org.eclipse.jetty:jetty-home:12.1.1 - https://jetty.org/jetty-home)
+ * Integrations :: Infinispan :: Remote with Querying (org.eclipse.jetty:jetty-infinispan-remote-query:12.0.26 - https://jetty.org/jetty-integrations/jetty-infinispan/jetty-infinispan-remote-query)
+ * Integrations :: Infinispan :: Remote with Querying (org.eclipse.jetty:jetty-infinispan-remote-query:12.1.1 - https://jetty.org/jetty-integrations/jetty-infinispan/jetty-infinispan-remote-query)
+ * Integrations :: Infinispan :: Sessions (org.eclipse.jetty:jetty-infinispan-common:12.0.26 - https://jetty.org/jetty-integrations/jetty-infinispan/jetty-infinispan-common)
+ * Integrations :: Infinispan :: Sessions (org.eclipse.jetty:jetty-infinispan-common:12.1.1 - https://jetty.org/jetty-integrations/jetty-infinispan/jetty-infinispan-common)
+ * Jetty :: SetUID JNA (org.eclipse.jetty.toolchain.setuid:jetty-setuid-jna:2.0.3 - https://eclipse.org/jetty/jetty-setuid-jna)
+
+ Apache License, Version 2.0, GNU Lesser General Public License version 3
+
+ * jffi (com.github.jnr:jffi:1.3.13 - http://github.com/jnr/jffi)
+
+ Apache License, Version 2.0, LGPL-2.1-or-later
+
+ * Java Native Access (net.java.dev.jna:jna-jpms:5.14.0 - https://github.com/java-native-access/jna)
+ * Java Native Access (net.java.dev.jna:jna-jpms:5.17.0 - https://github.com/java-native-access/jna)
+
+ Apache License V2.0
+
+ * FlatBuffers Java API (com.google.flatbuffers:flatbuffers-java:24.3.25 - https://github.com/google/flatbuffers)
Bouncy Castle Licence
- * Bouncy Castle PKIX, CMS, EAC, TSP, PKCS, OCSP, CMP, and CRMF APIs (org.bouncycastle:bcpkix-jdk15on:1.67 - http://www.bouncycastle.org/java.html)
- * Bouncy Castle Provider (org.bouncycastle:bcprov-jdk15on:1.67 - http://www.bouncycastle.org/java.html)
+ * Bouncy Castle Provider (org.bouncycastle:bcprov-jdk18on:1.81 - https://www.bouncycastle.org/download/bouncy-castle-java/)
+
+ BSD 2-Clause "Simplified" License
+
+ * Jetty :: Quiche Native Library (org.mortbay.jetty.quiche:jetty-quiche-native:0.24.5 - https://github.com/jetty-project/jetty-quiche-native)
+
+ BSD 2-Clause License
+
+ * zstd-jni (com.github.luben:zstd-jni:1.5.6-4 - https://github.com/luben/zstd-jni)
+
+ BSD Licence 3
+
+ * Hamcrest (org.hamcrest:hamcrest:2.1 - http://hamcrest.org/JavaHamcrest/)
BSD License
* ANTLR 3 Runtime (org.antlr:antlr-runtime:3.5.3 - http://www.antlr.org)
- * API Common (com.google.api:api-common:2.1.1 - https://github.com/googleapis/api-common-java)
- * API Common (com.google.api:api-common:2.2.1 - https://github.com/googleapis/api-common-java)
- * GAX (Google Api eXtensions) for Java (com.google.api:gax:2.16.0 - https://github.com/googleapis/gax-java)
- * GAX (Google Api eXtensions) for Java (com.google.api:gax-grpc:2.16.0 - https://github.com/googleapis/gax-java)
- * GAX (Google Api eXtensions) for Java (com.google.api:gax-httpjson:0.101.0 - https://github.com/googleapis/gax-java)
CDDL + GPLv2
* JavaServer Pages(TM) Standard Tag Library API (javax.servlet.jsp.jstl:javax.servlet.jsp.jstl-api:1.2.2 - http://jcp.org/en/jsr/detail?id=52)
+ * JavaServer Pages (TM) TagLib Implementation (org.glassfish.web:javax.servlet.jsp.jstl:1.2.5 - http://jstl.java.net)
* Java Servlet API (javax.servlet:javax.servlet-api:3.1.0 - http://servlet-spec.java.net)
* javax.annotation API (javax.annotation:javax.annotation-api:1.3.2 - http://jcp.org/en/jsr/detail?id=250)
* javax.transaction API (javax.transaction:javax.transaction-api:1.3 - http://jta-spec.java.net)
- * jstl (jstl:jstl:1.2 - no url defined)
* servlet-api (javax.servlet:servlet-api:2.5 - no url defined)
COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0
* JavaBeans(TM) Activation Framework (javax.activation:activation:1.1.1 - http://java.sun.com/javase/technologies/desktop/javabeans/jaf/index.jsp)
+ Dual license consisting of the CDDL v1.1 and GPL v2
+
+ * WebSocket client API (javax.websocket:javax.websocket-client-api:1.0 - http://websocket-spec.java.net)
+ * WebSocket server API (javax.websocket:javax.websocket-api:1.0 - http://websocket-spec.java.net)
+
Eclipse Distribution License 1.0 (BSD)
* Jakarta Activation API jar (jakarta.activation:jakarta.activation-api:1.2.2 - https://github.com/eclipse-ee4j/jaf/jakarta.activation-api)
@@ -233,77 +707,142 @@ The repository contains 3rd-party code under the following licenses:
Eclipse Public License 1.0, GNU Lesser General Public License
- * Logback Classic Module (ch.qos.logback:logback-classic:1.2.11 - http://logback.qos.ch/logback-classic)
- * Logback Core Module (ch.qos.logback:logback-core:1.2.11 - http://logback.qos.ch/logback-core)
+ * Logback Classic Module (ch.qos.logback:logback-classic:1.2.12 - http://logback.qos.ch/logback-classic)
+ * Logback Core Module (ch.qos.logback:logback-core:1.2.12 - http://logback.qos.ch/logback-core)
Eclipse Public License 2.0
* Eclipse Compiler for Java(TM) (org.eclipse.jdt:ecj:3.19.0 - http://www.eclipse.org/jdt)
+ * Eclipse Compiler for Java(TM) (org.eclipse.jdt:ecj:3.26.0 - http://www.eclipse.org/jdt)
+ * Eclipse Compiler for Java(TM) (org.eclipse.jdt:ecj:3.33.0 - https://projects.eclipse.org/projects/eclipse.jdt)
+ * JUnit Jupiter (Aggregator) (org.junit.jupiter:junit-jupiter:5.13.2 - https://junit.org/)
* JUnit Jupiter (Aggregator) (org.junit.jupiter:junit-jupiter:5.8.2 - https://junit.org/junit5/)
+ * JUnit Jupiter API (org.junit.jupiter:junit-jupiter-api:5.13.2 - https://junit.org/)
+ * JUnit Jupiter API (org.junit.jupiter:junit-jupiter-api:5.13.4 - https://junit.org/)
* JUnit Jupiter API (org.junit.jupiter:junit-jupiter-api:5.8.2 - https://junit.org/junit5/)
+ * JUnit Jupiter Engine (org.junit.jupiter:junit-jupiter-engine:5.13.2 - https://junit.org/)
* JUnit Jupiter Engine (org.junit.jupiter:junit-jupiter-engine:5.8.2 - https://junit.org/junit5/)
+ * JUnit Jupiter Params (org.junit.jupiter:junit-jupiter-params:5.13.2 - https://junit.org/)
* JUnit Jupiter Params (org.junit.jupiter:junit-jupiter-params:5.8.2 - https://junit.org/junit5/)
+ * JUnit Platform Commons (org.junit.platform:junit-platform-commons:1.13.2 - https://junit.org/)
+ * JUnit Platform Commons (org.junit.platform:junit-platform-commons:1.13.4 - https://junit.org/)
* JUnit Platform Commons (org.junit.platform:junit-platform-commons:1.8.2 - https://junit.org/junit5/)
+ * JUnit Platform Engine API (org.junit.platform:junit-platform-engine:1.13.2 - https://junit.org/)
* JUnit Platform Engine API (org.junit.platform:junit-platform-engine:1.8.2 - https://junit.org/junit5/)
Eclipse Public License 2.0, GNU General Public License, version 2
+ * jakarta.transaction API (jakarta.transaction:jakarta.transaction-api:2.0.1 - https://projects.eclipse.org/projects/ee4j.jta)
* Jakarta Annotations API (jakarta.annotation:jakarta.annotation-api:1.3.5 - https://projects.eclipse.org/projects/ee4j.ca)
+ * Jakarta Annotations API (jakarta.annotation:jakarta.annotation-api:2.1.1 - https://projects.eclipse.org/projects/ee4j.ca)
+ * Jakarta Annotations API (jakarta.annotation:jakarta.annotation-api:3.0.0 - https://projects.eclipse.org/projects/ee4j.ca)
+ * Jakarta Interceptors (jakarta.interceptor:jakarta.interceptor-api:2.1.0 - https://github.com/eclipse-ee4j/interceptor-api)
+ * Jakarta Interceptors (jakarta.interceptor:jakarta.interceptor-api:2.2.0 - https://github.com/jakartaee/interceptors)
+ * Jakarta Servlet (jakarta.servlet:jakarta.servlet-api:4.0.4 - https://projects.eclipse.org/projects/ee4j.servlet)
+ * Jakarta Servlet (jakarta.servlet:jakarta.servlet-api:6.0.0 - https://projects.eclipse.org/projects/ee4j.servlet)
+ * Jakarta Servlet (jakarta.servlet:jakarta.servlet-api:6.1.0 - https://projects.eclipse.org/projects/ee4j.servlet)
+ * Jakarta Standard Tag Library API (jakarta.servlet.jsp.jstl:jakarta.servlet.jsp.jstl-api:3.0.0 - https://projects.eclipse.org/projects/ee4j.jstl)
+ * Jakarta Standard Tag Library API (jakarta.servlet.jsp.jstl:jakarta.servlet.jsp.jstl-api:3.0.2 - https://projects.eclipse.org/projects/ee4j.jstl)
+ * Jakarta Standard Tag Library Implementation (org.glassfish.web:jakarta.servlet.jsp.jstl:3.0.1 - https://projects.eclipse.org/projects/ee4j.jstl)
+ * javax.transaction API (jakarta.transaction:jakarta.transaction-api:1.3.3 - https://projects.eclipse.org/projects/ee4j.jta)
Eclipse Public License 2.0, GNU General Public License, version 2, GNU Lesser General Public License Version 2.1
* jnr-posix (com.github.jnr:jnr-posix:3.1.15 - http://nexus.sonatype.org/oss-repository-hosting.html/jnr-posix)
+ * jnr-posix (com.github.jnr:jnr-posix:3.1.20 - http://nexus.sonatype.org/oss-repository-hosting.html/jnr-posix)
- GNU General Public License, version 2
+ Eclipse Public License v. 2.0, GNU General Public License, version 2 with the GNU Classpath Exception
- * MySQL Connector/J (mysql:mysql-connector-java:8.0.28 - http://dev.mysql.com/doc/connector-j/en/)
+ * Jakarta Expression Language API (jakarta.el:jakarta.el-api:5.0.0 - https://projects.eclipse.org/projects/ee4j.el)
+ * Jakarta Expression Language API (jakarta.el:jakarta.el-api:6.0.0 - https://projects.eclipse.org/projects/ee4j.el)
+ * Jakarta Server Pages API (jakarta.servlet.jsp:jakarta.servlet.jsp-api:3.1.0 - https://projects.eclipse.org/projects/ee4j.jsp)
+ * Jakarta Server Pages API (jakarta.servlet.jsp:jakarta.servlet.jsp-api:3.1.1 - https://projects.eclipse.org/projects/ee4j.jsp)
+ * Jakarta Server Pages API (jakarta.servlet.jsp:jakarta.servlet.jsp-api:4.0.0 - https://projects.eclipse.org/projects/ee4j.jsp)
+ * Jakarta WebSocket - Client API (jakarta.websocket:jakarta.websocket-client-api:2.1.1 - https://projects.eclipse.org/projects/ee4j.websocket)
+ * Jakarta WebSocket - Client API (jakarta.websocket:jakarta.websocket-client-api:2.2.0 - https://projects.eclipse.org/projects/ee4j.websocket)
+ * Jakarta WebSocket - Server API (jakarta.websocket:jakarta.websocket-api:2.1.1 - https://projects.eclipse.org/projects/ee4j.websocket)
+ * Jakarta WebSocket - Server API (jakarta.websocket:jakarta.websocket-api:2.2.0 - https://projects.eclipse.org/projects/ee4j.websocket)
+ * WaSP (org.glassfish.wasp:wasp:4.0.0 - https://projects.eclipse.org/projects/ee4j.wasp)
- GNU General Public License, version 2, The MIT License
+ EPL-2.0
- * Checker Qual (org.checkerframework:checker-compat-qual:2.5.3 - https://checkerframework.org)
- * Checker Qual (org.checkerframework:checker-compat-qual:2.5.5 - https://checkerframework.org)
+ * Eclipse Compiler for Java(TM) (org.eclipse.jdt:ecj:3.42.0 - https://github.com/eclipse-jdt/eclipse.jdt.core/)
Go License
- * RE2/J (com.google.re2j:re2j:1.5 - http://github.com/google/re2j)
+ * RE2/J (com.google.re2j:re2j:1.7 - http://github.com/google/re2j)
+
+ MIT
+
+ * mockito-core (org.mockito:mockito-core:5.19.0 - https://github.com/mockito/mockito)
+ * mockito-junit-jupiter (org.mockito:mockito-junit-jupiter:5.19.0 - https://github.com/mockito/mockito)
+ * SLF4J API Module (org.slf4j:slf4j-api:2.0.17 - http://www.slf4j.org)
+ * SLF4J JDK14 Provider (org.slf4j:slf4j-jdk14:2.0.17 - http://www.slf4j.org)
+
+ MIT-0
+
+ * reactive-streams (org.reactivestreams:reactive-streams:1.0.4 - http://www.reactive-streams.org/)
+
+ Public Domain
+
+ * JSON in Java (org.json:json:20250107 - https://github.com/douglascrockford/JSON-java)
The 3-Clause BSD License
- * asm (org.ow2.asm:asm:9.1 - http://asm.ow2.io/)
+ * API Common (com.google.api:api-common:2.24.0 - https://github.com/googleapis/sdk-platform-java)
+ * API Common (com.google.api:api-common:2.53.0 - https://github.com/googleapis/sdk-platform-java)
* asm (org.ow2.asm:asm:9.3 - http://asm.ow2.io/)
+ * asm (org.ow2.asm:asm:9.7.1 - http://asm.ow2.io/)
+ * asm (org.ow2.asm:asm:9.8 - http://asm.ow2.io/)
* asm-analysis (org.ow2.asm:asm-analysis:9.2 - http://asm.ow2.io/)
- * asm-analysis (org.ow2.asm:asm-analysis:9.3 - http://asm.ow2.io/)
- * asm-commons (org.ow2.asm:asm-commons:9.2 - http://asm.ow2.io/)
- * asm-commons (org.ow2.asm:asm-commons:9.3 - http://asm.ow2.io/)
- * asm-tree (org.ow2.asm:asm-tree:9.2 - http://asm.ow2.io/)
- * asm-tree (org.ow2.asm:asm-tree:9.3 - http://asm.ow2.io/)
+ * asm-analysis (org.ow2.asm:asm-analysis:9.7.1 - http://asm.ow2.io/)
+ * asm-analysis (org.ow2.asm:asm-analysis:9.8 - http://asm.ow2.io/)
+ * asm-commons (org.ow2.asm:asm-commons:9.7.1 - http://asm.ow2.io/)
+ * asm-commons (org.ow2.asm:asm-commons:9.8 - http://asm.ow2.io/)
+ * asm-tree (org.ow2.asm:asm-tree:9.7.1 - http://asm.ow2.io/)
+ * asm-tree (org.ow2.asm:asm-tree:9.8 - http://asm.ow2.io/)
* asm-util (org.ow2.asm:asm-util:9.2 - http://asm.ow2.io/)
- * Google Auth Library for Java - Credentials (com.google.auth:google-auth-library-credentials:1.3.0 - https://github.com/googleapis/google-auth-library-java/google-auth-library-credentials)
- * Google Auth Library for Java - OAuth2 HTTP (com.google.auth:google-auth-library-oauth2-http:1.3.0 - https://github.com/googleapis/google-auth-library-java/google-auth-library-oauth2-http)
+ * asm-util (org.ow2.asm:asm-util:9.7.1 - http://asm.ow2.io/)
+ * GAX (Google Api eXtensions) for Java (Core) (com.google.api:gax:2.58.0 - https://github.com/googleapis/sdk-platform-java)
+ * GAX (Google Api eXtensions) for Java (Core) (com.google.api:gax:2.70.1 - https://github.com/googleapis/sdk-platform-java)
+ * GAX (Google Api eXtensions) for Java (gRPC) (com.google.api:gax-grpc:2.58.0 - https://github.com/googleapis/sdk-platform-java)
+ * GAX (Google Api eXtensions) for Java (gRPC) (com.google.api:gax-grpc:2.70.1 - https://github.com/googleapis/sdk-platform-java)
+ * GAX (Google Api eXtensions) for Java (HTTP JSON) (com.google.api:gax-httpjson:2.58.0 - https://github.com/googleapis/sdk-platform-java)
+ * GAX (Google Api eXtensions) for Java (HTTP JSON) (com.google.api:gax-httpjson:2.70.1 - https://github.com/googleapis/sdk-platform-java)
+ * Google Auth Library for Java - Credentials (com.google.auth:google-auth-library-credentials:1.30.0 - https://github.com/googleapis/google-auth-library-java/google-auth-library-credentials)
+ * Google Auth Library for Java - Credentials (com.google.auth:google-auth-library-credentials:1.37.1 - https://github.com/googleapis/google-auth-library-java/google-auth-library-credentials)
+ * Google Auth Library for Java - OAuth2 HTTP (com.google.auth:google-auth-library-oauth2-http:1.30.0 - https://github.com/googleapis/google-auth-library-java/google-auth-library-oauth2-http)
+ * Google Auth Library for Java - OAuth2 HTTP (com.google.auth:google-auth-library-oauth2-http:1.37.1 - https://github.com/googleapis/google-auth-library-java/google-auth-library-oauth2-http)
* Hamcrest (org.hamcrest:hamcrest:2.2 - http://hamcrest.org/JavaHamcrest/)
* Hamcrest Core (org.hamcrest:hamcrest-core:1.3 - https://github.com/hamcrest/JavaHamcrest/hamcrest-core)
* Hamcrest Core (org.hamcrest:hamcrest-core:2.2 - http://hamcrest.org/JavaHamcrest/)
- * Protocol Buffers [Core] (com.google.protobuf:protobuf-java:3.21.5 - https://developers.google.com/protocol-buffers/protobuf-java/)
- * Protocol Buffers [Util] (com.google.protobuf:protobuf-java-util:3.21.5 - https://developers.google.com/protocol-buffers/protobuf-java-util/)
- * ThreeTen backport (org.threeten:threetenbp:1.5.2 - https://www.threeten.org/threetenbp)
- * ThreeTen-Extra (org.threeten:threeten-extra:1.7.0 - https://www.threeten.org/threeten-extra)
- * YamlBeans (com.contrastsecurity:yamlbeans:1.11 - https://github.com/Contrast-Security-OSS/yamlbeans)
+ * Protocol Buffers [Core] (com.google.protobuf:protobuf-java:3.21.12 - https://developers.google.com/protocol-buffers/protobuf-java/)
+ * Protocol Buffers [Util] (com.google.protobuf:protobuf-java-util:3.21.12 - https://developers.google.com/protocol-buffers/protobuf-java-util/)
+ * ThreeTen backport (org.threeten:threetenbp:1.7.0 - https://www.threeten.org/threetenbp)
+ * ThreeTen-Extra (org.threeten:threeten-extra:1.8.0 - https://www.threeten.org/threeten-extra)
- The JSON License
+ The GNU General Public License, v2 with Universal FOSS Exception, v1.0
- * JSON in Java (org.json:json:20220320 - https://github.com/douglascrockford/JSON-java)
+ * MySQL Connector/J (com.mysql:mysql-connector-j:9.4.0 - http://dev.mysql.com/doc/connector-j/en/)
The MIT License
- * Animal Sniffer Annotations (org.codehaus.mojo:animal-sniffer-annotations:1.20 - http://www.mojohaus.org/animal-sniffer/animal-sniffer-annotations)
- * Animal Sniffer Annotations (org.codehaus.mojo:animal-sniffer-annotations:1.21 - https://www.mojohaus.org/animal-sniffer/animal-sniffer-annotations)
- * Checker Qual (org.checkerframework:checker-qual:3.24.0 - https://checkerframework.org)
+ * Animal Sniffer Annotations (org.codehaus.mojo:animal-sniffer-annotations:1.24 - https://www.mojohaus.org/animal-sniffer/animal-sniffer-annotations)
+ * Checker Qual (org.checkerframework:checker-compat-qual:2.5.6 - https://checkerframework.org)
+ * Checker Qual (org.checkerframework:checker-qual:3.47.0 - https://checkerframework.org/)
+ * Checker Qual (org.checkerframework:checker-qual:3.49.0 - https://checkerframework.org/)
* jnr-x86asm (com.github.jnr:jnr-x86asm:1.0.2 - http://github.com/jnr/jnr-x86asm)
- * jsoup Java HTML Parser (org.jsoup:jsoup:1.15.3 - https://jsoup.org/)
- * JUL to SLF4J bridge (org.slf4j:jul-to-slf4j:1.7.30 - http://www.slf4j.org)
+ * jsoup Java HTML Parser (org.jsoup:jsoup:1.21.2 - https://jsoup.org/)
+ * JUL to SLF4J bridge (org.slf4j:jul-to-slf4j:1.7.36 - http://www.slf4j.org)
* Mockito (org.mockito:mockito-all:1.10.19 - http://www.mockito.org)
* Mockito (org.mockito:mockito-all:2.0.2-beta - http://www.mockito.org)
* mockito-core (org.mockito:mockito-core:4.5.1 - https://github.com/mockito/mockito)
- * mockito-core (org.mockito:mockito-core:4.7.0 - https://github.com/mockito/mockito)
+ * mockito-inline (org.mockito:mockito-inline:5.2.0 - https://github.com/mockito/mockito)
* mockito-junit-jupiter (org.mockito:mockito-junit-jupiter:4.5.1 - https://github.com/mockito/mockito)
* SLF4J API Module (org.slf4j:slf4j-api:1.7.36 - http://www.slf4j.org)
+ * SLF4J API Module (org.slf4j:slf4j-api:2.0.13 - http://www.slf4j.org)
+ * YamlBeans (com.contrastsecurity:yamlbeans:1.17 - https://github.com/Contrast-Security-OSS/yamlbeans)
+
+ Unknown license
+
+ * jstl (javax.servlet:jstl:1.2 - no url defined)
diff --git a/TRYLATESTBITSINPROD.md b/TRYLATESTBITSINPROD.md
index 9f4a4eea7..cac5768a8 100644
--- a/TRYLATESTBITSINPROD.md
+++ b/TRYLATESTBITSINPROD.md
@@ -31,27 +31,46 @@ are bundled as a Maven assembly under `runtime-deployment
- 2.0.29-SNAPSHOT
+ 3.0.0-SNAPSHOTtarget/${project.artifactId}-${project.version}
...
@@ -91,6 +110,10 @@ deployed web application.
${appengine.runtime.location}/WEB-INF/lib/runtime-impl-jetty12-${appengine.runtime.version}.jar${appengine.runtime.location}/runtime-impl-jetty12.jar
+
+
+ ${appengine.runtime.location}/WEB-INF/lib/runtime-impl-jetty121-${appengine.runtime.version}.jar
+ ${appengine.runtime.location}/runtime-impl-jetty121.jar${appengine.runtime.location}/WEB-INF/lib/runtime-shared-jetty12-${appengine.runtime.version}.jar
@@ -99,6 +122,14 @@ deployed web application.
${appengine.runtime.location}/WEB-INF/lib/runtime-shared-jetty12-ee10-${appengine.runtime.version}.jar${appengine.runtime.location}/runtime-shared-jetty12-ee10.jar
+
+
+ ${appengine.runtime.location}/WEB-INF/lib/runtime-shared-jetty121-ee8-${appengine.runtime.version}.jar
+ ${appengine.runtime.location}/runtime-shared-jetty121-ee8.jar
+
+
+ ${appengine.runtime.location}/WEB-INF/lib/runtime-shared-jetty121-ee11-${appengine.runtime.version}.jar
+ ${appengine.runtime.location}/runtime-shared-jetty121-ee11.jar${appengine.runtime.location}/WEB-INF/lib/runtime-main-${appengine.runtime.version}.jar
@@ -117,7 +148,7 @@ In the appengine-web.xml, modify the entrypoint to use the bundled runtime jars
```
- java17
+ java21true
diff --git a/api/pom.xml b/api/pom.xml
index 25544e889..5968a75ba 100644
--- a/api/pom.xml
+++ b/api/pom.xml
@@ -21,13 +21,14 @@
com.google.appengineparent
- 2.0.29-SNAPSHOT
+ 3.0.0-SNAPSHOTtruejarAppEngine :: appengine-apis
+ https://github.com/GoogleCloudPlatform/appengine-java-standard/API for Google App Engine standard environment
@@ -239,7 +240,7 @@
org.apache.maven.pluginsmaven-javadoc-plugin
- 3.7.0
+ 3.11.3com.microsoft.doclet.DocFxDocletfalse
@@ -265,6 +266,23 @@
+
+ maven-compiler-plugin
+
+
+
+ com.google.auto.service
+ auto-service
+ 1.1.1
+
+
+ com.google.auto.value
+ auto-value
+ 1.11.0
+
+
+
+
diff --git a/api/src/main/java/com/google/appengine/api/appidentity/AppIdentityServiceFailureException.java b/api/src/main/java/com/google/appengine/api/appidentity/AppIdentityServiceFailureException.java
index b0f3659a1..efeb8264b 100644
--- a/api/src/main/java/com/google/appengine/api/appidentity/AppIdentityServiceFailureException.java
+++ b/api/src/main/java/com/google/appengine/api/appidentity/AppIdentityServiceFailureException.java
@@ -16,7 +16,7 @@
package com.google.appengine.api.appidentity;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* {@link AppIdentityServiceFailureException} is thrown when any unknown error occurs while
diff --git a/api/src/main/java/com/google/appengine/api/appidentity/AppIdentityServiceImpl.java b/api/src/main/java/com/google/appengine/api/appidentity/AppIdentityServiceImpl.java
index 3cbf6a491..2a2eb19e6 100644
--- a/api/src/main/java/com/google/appengine/api/appidentity/AppIdentityServiceImpl.java
+++ b/api/src/main/java/com/google/appengine/api/appidentity/AppIdentityServiceImpl.java
@@ -46,7 +46,7 @@
import java.util.Random;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicReference;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/** Implementation of the AppIdentityService interface. */
class AppIdentityServiceImpl implements AppIdentityService {
diff --git a/api/src/main/java/com/google/appengine/api/blobstore/BlobInfo.java b/api/src/main/java/com/google/appengine/api/blobstore/BlobInfo.java
index 433c22506..b289bc897 100644
--- a/api/src/main/java/com/google/appengine/api/blobstore/BlobInfo.java
+++ b/api/src/main/java/com/google/appengine/api/blobstore/BlobInfo.java
@@ -21,7 +21,7 @@
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.util.Date;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* {@code BlobInfo} contains metadata about a blob. This metadata is gathered by
diff --git a/api/src/main/java/com/google/appengine/api/blobstore/BlobInfoFactory.java b/api/src/main/java/com/google/appengine/api/blobstore/BlobInfoFactory.java
index cba413524..b788a4ccb 100644
--- a/api/src/main/java/com/google/appengine/api/blobstore/BlobInfoFactory.java
+++ b/api/src/main/java/com/google/appengine/api/blobstore/BlobInfoFactory.java
@@ -28,7 +28,7 @@
import com.google.appengine.api.datastore.Query;
import java.util.Date;
import java.util.Iterator;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* {@code BlobInfoFactory} provides a trivial interface for retrieving
diff --git a/api/src/main/java/com/google/appengine/api/blobstore/BlobKey.java b/api/src/main/java/com/google/appengine/api/blobstore/BlobKey.java
index a8ca23698..84e0d2e8c 100644
--- a/api/src/main/java/com/google/appengine/api/blobstore/BlobKey.java
+++ b/api/src/main/java/com/google/appengine/api/blobstore/BlobKey.java
@@ -18,7 +18,7 @@
import java.io.Serializable;
import java.util.Objects;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* {@code BlobKey} contains the string identifier of a large (possibly
diff --git a/api/src/main/java/com/google/appengine/api/blobstore/BlobstoreInputStream.java b/api/src/main/java/com/google/appengine/api/blobstore/BlobstoreInputStream.java
index 46684cc23..dc9dfdabe 100644
--- a/api/src/main/java/com/google/appengine/api/blobstore/BlobstoreInputStream.java
+++ b/api/src/main/java/com/google/appengine/api/blobstore/BlobstoreInputStream.java
@@ -22,7 +22,7 @@
import com.google.common.base.Preconditions;
import java.io.IOException;
import java.io.InputStream;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* BlobstoreInputStream provides an InputStream view of a blob in
diff --git a/api/src/main/java/com/google/appengine/api/blobstore/BlobstoreService.java b/api/src/main/java/com/google/appengine/api/blobstore/BlobstoreService.java
index 9af41b484..1bf277db1 100644
--- a/api/src/main/java/com/google/appengine/api/blobstore/BlobstoreService.java
+++ b/api/src/main/java/com/google/appengine/api/blobstore/BlobstoreService.java
@@ -21,7 +21,7 @@
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* {@code BlobstoreService} allows you to manage the creation and
diff --git a/api/src/main/java/com/google/appengine/api/blobstore/BlobstoreServiceImpl.java b/api/src/main/java/com/google/appengine/api/blobstore/BlobstoreServiceImpl.java
index e6797953d..738ae0bf5 100644
--- a/api/src/main/java/com/google/appengine/api/blobstore/BlobstoreServiceImpl.java
+++ b/api/src/main/java/com/google/appengine/api/blobstore/BlobstoreServiceImpl.java
@@ -40,7 +40,7 @@
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* {@code BlobstoreServiceImpl} is an implementation of {@link BlobstoreService} that makes API
diff --git a/api/src/main/java/com/google/appengine/api/blobstore/ByteRange.java b/api/src/main/java/com/google/appengine/api/blobstore/ByteRange.java
index da31d53dd..268a3852c 100644
--- a/api/src/main/java/com/google/appengine/api/blobstore/ByteRange.java
+++ b/api/src/main/java/com/google/appengine/api/blobstore/ByteRange.java
@@ -21,7 +21,7 @@
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* A byte range as parsed from a request Range header. Format produced by this class is
diff --git a/api/src/main/java/com/google/appengine/api/blobstore/FileInfo.java b/api/src/main/java/com/google/appengine/api/blobstore/FileInfo.java
index 98084af7a..8e7d322c4 100644
--- a/api/src/main/java/com/google/appengine/api/blobstore/FileInfo.java
+++ b/api/src/main/java/com/google/appengine/api/blobstore/FileInfo.java
@@ -18,7 +18,7 @@
import com.google.common.base.Objects;
import java.util.Date;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* {@code FileInfo} contains metadata about an uploaded file. This metadata is
diff --git a/api/src/main/java/com/google/appengine/api/blobstore/UploadOptions.java b/api/src/main/java/com/google/appengine/api/blobstore/UploadOptions.java
index 5598787ef..c761b8919 100644
--- a/api/src/main/java/com/google/appengine/api/blobstore/UploadOptions.java
+++ b/api/src/main/java/com/google/appengine/api/blobstore/UploadOptions.java
@@ -17,7 +17,7 @@
package com.google.appengine.api.blobstore;
import java.util.Objects;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* Allows users to customize the behavior of a single upload to the
diff --git a/api/src/main/java/com/google/appengine/api/blobstore/ee10/BlobstoreService.java b/api/src/main/java/com/google/appengine/api/blobstore/ee10/BlobstoreService.java
index 73165c169..37ebfe4cd 100644
--- a/api/src/main/java/com/google/appengine/api/blobstore/ee10/BlobstoreService.java
+++ b/api/src/main/java/com/google/appengine/api/blobstore/ee10/BlobstoreService.java
@@ -26,13 +26,13 @@
import java.io.IOException;
import java.util.List;
import java.util.Map;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
- * {@code BlobstoreService} allows you to manage the creation and
- * serving of large, immutable blobs to users.
- *
+ * @deprecated as of version 3.0, use {@link
+ * com.google.appengine.api.blobstore.jakarta.BlobstoreService} instead.
*/
+@Deprecated(since = "3.0.0")
public interface BlobstoreService {
public static final int MAX_BLOB_FETCH_SIZE = (1 << 20) - (1 << 15); // 1MB - 16K;
diff --git a/api/src/main/java/com/google/appengine/api/blobstore/ee10/BlobstoreServiceFactory.java b/api/src/main/java/com/google/appengine/api/blobstore/ee10/BlobstoreServiceFactory.java
index 33e0d2c70..5ebde48c2 100644
--- a/api/src/main/java/com/google/appengine/api/blobstore/ee10/BlobstoreServiceFactory.java
+++ b/api/src/main/java/com/google/appengine/api/blobstore/ee10/BlobstoreServiceFactory.java
@@ -17,7 +17,11 @@
package com.google.appengine.api.blobstore.ee10;
-/** Creates {@link BlobstoreService} implementations for java EE 10. */
+/**
+ * @deprecated as of version 3.0, use {@link
+ * com.google.appengine.api.blobstore.jakarta.BlobstoreServiceFactory} instead.
+ */
+@Deprecated(since = "3.0.0")
public final class BlobstoreServiceFactory {
/** Creates a {@code BlobstoreService} for java EE 10. */
diff --git a/api/src/main/java/com/google/appengine/api/blobstore/ee10/BlobstoreServiceImpl.java b/api/src/main/java/com/google/appengine/api/blobstore/ee10/BlobstoreServiceImpl.java
index d3cda0e5e..488d7fae4 100644
--- a/api/src/main/java/com/google/appengine/api/blobstore/ee10/BlobstoreServiceImpl.java
+++ b/api/src/main/java/com/google/appengine/api/blobstore/ee10/BlobstoreServiceImpl.java
@@ -47,13 +47,13 @@
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
- * {@code BlobstoreServiceImpl} is an implementation of {@link BlobstoreService} that makes API
- * calls to {@link ApiProxy}.
- *
+ * @deprecated as of version 3.0, use {@link
+ * com.google.appengine.api.blobstore.jakarta.BlobstoreServiceImpl} instead.
*/
+@Deprecated(since = "3.0.0")
class BlobstoreServiceImpl implements BlobstoreService {
static final String PACKAGE = "blobstore";
static final String SERVE_HEADER = "X-AppEngine-BlobKey";
diff --git a/api/src/main/java/com/google/appengine/api/blobstore/jakarta/BlobstoreService.java b/api/src/main/java/com/google/appengine/api/blobstore/jakarta/BlobstoreService.java
new file mode 100644
index 000000000..786630ce2
--- /dev/null
+++ b/api/src/main/java/com/google/appengine/api/blobstore/jakarta/BlobstoreService.java
@@ -0,0 +1,243 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.appengine.api.blobstore.jakarta;
+
+import com.google.appengine.api.blobstore.BlobInfo;
+import com.google.appengine.api.blobstore.BlobKey;
+import com.google.appengine.api.blobstore.ByteRange;
+import com.google.appengine.api.blobstore.FileInfo;
+import com.google.appengine.api.blobstore.UploadOptions;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import org.jspecify.annotations.Nullable;
+
+/**
+ * {@code BlobstoreService} allows you to manage the creation and
+ * serving of large, immutable blobs to users.
+ *
+ */
+public interface BlobstoreService {
+ public static final int MAX_BLOB_FETCH_SIZE = (1 << 20) - (1 << 15); // 1MB - 16K;
+
+ /**
+ * Create an absolute URL that can be used by a user to
+ * asynchronously upload a large blob. Upon completion of the
+ * upload, a callback is made to the specified URL.
+ *
+ * @param successPath A relative URL which will be invoked
+ * after the user successfully uploads a blob. Must start with a "/",
+ * and must be URL-encoded.
+ *
+ * @throws IllegalArgumentException If successPath was not valid.
+ * @throws BlobstoreFailureException If an error occurred while
+ * communicating with the blobstore.
+ */
+ String createUploadUrl(String successPath);
+
+ /**
+ * Create an absolute URL that can be used by a user to
+ * asynchronously upload a large blob. Upon completion of the
+ * upload, a callback is made to the specified URL.
+ *
+ * @param successPath A relative URL which will be invoked
+ * after the user successfully uploads a blob. Must start with a "/".
+ * @param uploadOptions Specific options applicable only for this
+ * upload URL.
+ *
+ * @throws IllegalArgumentException If successPath was not valid.
+ * @throws BlobstoreFailureException If an error occurred while
+ * communicating with the blobstore.
+ */
+ String createUploadUrl(String successPath, UploadOptions uploadOptions);
+
+ /**
+ * Arrange for the specified blob to be served as the response
+ * content for the current request. {@code response} should be
+ * uncommitted before invoking this method, and should be assumed to
+ * be committed after invoking it. Any content written before
+ * calling this method will be ignored. You may, however, append
+ * custom headers before or after calling this method.
+ *
+ *
Range header will be automatically translated from the Content-Range
+ * header in the response.
+ *
+ * @param blobKey Blob-key to serve in response.
+ * @param response HTTP response object.
+ *
+ * @throws IOException If an I/O error occurred.
+ * @throws IllegalStateException If {@code response} was already committed.
+ */
+ void serve(BlobKey blobKey, HttpServletResponse response) throws IOException;
+
+ /**
+ * Arrange for the specified blob to be served as the response
+ * content for the current request. {@code response} should be
+ * uncommitted before invoking this method, and should be assumed to
+ * be committed after invoking it. Any content written before
+ * calling this method will be ignored. You may, however, append
+ * custom headers before or after calling this method.
+ *
+ *
This method will set the App Engine blob range header to serve a
+ * byte range of that blob.
+ *
+ * @param blobKey Blob-key to serve in response.
+ * @param byteRange Byte range to serve in response.
+ * @param response HTTP response object.
+ *
+ * @throws IOException If an I/O error occurred.
+ * @throws IllegalStateException If {@code response} was already committed.
+ */
+ void serve(BlobKey blobKey, @Nullable ByteRange byteRange, HttpServletResponse response)
+ throws IOException;
+
+ /**
+ * Arrange for the specified blob to be served as the response
+ * content for the current request. {@code response} should be
+ * uncommitted before invoking this method, and should be assumed to
+ * be committed after invoking it. Any content written before
+ * calling this method will be ignored. You may, however, append
+ * custom headers before or after calling this method.
+ *
+ *
This method will set the App Engine blob range header to the content
+ * specified.
+ *
+ * @param blobKey Blob-key to serve in response.
+ * @param rangeHeader Content for range header to serve.
+ * @param response HTTP response object.
+ *
+ * @throws IOException If an I/O error occurred.
+ * @throws IllegalStateException If {@code response} was already committed.
+ */
+ void serve(BlobKey blobKey, String rangeHeader, HttpServletResponse response)
+ throws IOException;
+
+ /**
+ * Get byte range from the request.
+ *
+ * @param request HTTP request object.
+ *
+ * @return Byte range as parsed from the HTTP range header. null if there is no header.
+ *
+ * @throws RangeFormatException Unable to parse header because of invalid format.
+ * @throws UnsupportedRangeFormatException Header is a valid HTTP range header, the specific
+ * form is not supported by app engine. This includes unit types other than "bytes" and multiple
+ * ranges.
+ */
+ @Nullable ByteRange getByteRange(HttpServletRequest request);
+
+ /**
+ * Permanently deletes the specified blobs. Deleting unknown blobs is a
+ * no-op.
+ *
+ * @throws BlobstoreFailureException If an error occurred while
+ * communicating with the blobstore.
+ */
+ void delete(BlobKey... blobKeys);
+
+ /**
+ * Returns the {@link BlobKey} for any files that were uploaded, keyed by the
+ * upload form "name" field.
+ *
This method should only be called from within a request served by
+ * the destination of a {@code createUploadUrl} call.
+ *
+ * @throws IllegalStateException If not called from a blob upload
+ * callback request.
+ *
+ * @deprecated Use {@link #getUploads} instead. Note that getUploadedBlobs
+ * does not handle cases where blobs have been uploaded using the
+ * multiple="true" attribute of the file input form element.
+ */
+ @Deprecated Map getUploadedBlobs(HttpServletRequest request);
+
+ /**
+ * Returns the {@link BlobKey} for any files that were uploaded, keyed by the
+ * upload form "name" field.
+ * This method should only be called from within a request served by
+ * the destination of a {@link #createUploadUrl} call.
+ *
+ * @throws IllegalStateException If not called from a blob upload
+ * callback request.
+ * @see #getBlobInfos
+ * @see #getFileInfos
+ */
+ Map> getUploads(HttpServletRequest request);
+
+ /**
+ * Returns the {@link BlobInfo} for any files that were uploaded, keyed by the
+ * upload form "name" field.
+ * This method should only be called from within a request served by
+ * the destination of a {@link #createUploadUrl} call.
+ *
+ * @throws IllegalStateException If not called from a blob upload
+ * callback request.
+ * @see #getFileInfos
+ * @see #getUploads
+ * @since 1.7.5
+ */
+ Map> getBlobInfos(HttpServletRequest request);
+
+ /**
+ * Returns the {@link FileInfo} for any files that were uploaded, keyed by the
+ * upload form "name" field.
+ * This method should only be called from within a request served by
+ * the destination of a {@link #createUploadUrl} call.
+ *
+ * Prefer this method over {@link #getBlobInfos} or {@link #getUploads} if
+ * uploading files to Cloud Storage, as the FileInfo contains the name of the
+ * created filename in Cloud Storage.
+ *
+ * @throws IllegalStateException If not called from a blob upload
+ * callback request.
+ * @see #getBlobInfos
+ * @see #getUploads
+ * @since 1.7.5
+ */
+ Map> getFileInfos(HttpServletRequest request);
+
+ /**
+ * Get fragment from specified blob.
+ *
+ * @param blobKey Blob-key from which to fetch data.
+ * @param startIndex Start index of data to fetch.
+ * @param endIndex End index (inclusive) of data to fetch.
+ * @throws IllegalArgumentException If blob not found, indexes are negative, indexes are inverted
+ * or fetch size is too large.
+ * @throws SecurityException If the application does not have access to the blob.
+ * @throws BlobstoreFailureException If an error occurred while communicating with the blobstore.
+ */
+ byte[] fetchData(BlobKey blobKey, long startIndex, long endIndex);
+
+ /**
+ * Create a {@link BlobKey} for a Google Storage File.
+ *
+ *
The existence of the file represented by filename is not checked, hence a BlobKey can be
+ * created for a file that does not currently exist.
+ *
+ *
You can safely persist the {@link BlobKey} generated by this function.
+ *
+ *
The created {@link BlobKey} can then be used as a parameter in API methods that can support
+ * objects in Google Storage, for example {@link serve}.
+ *
+ * @param filename The Google Storage filename. The filename must be in the format
+ * "/gs/bucket_name/object_name".
+ * @throws IllegalArgumentException If the filename does not have the prefix "/gs/".
+ */
+ BlobKey createGsBlobKey(String filename);
+}
diff --git a/api/src/main/java/com/google/appengine/api/blobstore/jakarta/BlobstoreServiceFactory.java b/api/src/main/java/com/google/appengine/api/blobstore/jakarta/BlobstoreServiceFactory.java
new file mode 100644
index 000000000..22c664ef2
--- /dev/null
+++ b/api/src/main/java/com/google/appengine/api/blobstore/jakarta/BlobstoreServiceFactory.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.appengine.api.blobstore.jakarta;
+
+
+/** Creates {@link BlobstoreService} implementations for java EE 10. */
+public final class BlobstoreServiceFactory {
+
+ /** Creates a {@code BlobstoreService} for java EE 10. */
+ public static BlobstoreService getBlobstoreService() {
+ return new BlobstoreServiceImpl();
+ }
+
+}
diff --git a/api/src/main/java/com/google/appengine/api/blobstore/jakarta/BlobstoreServiceImpl.java b/api/src/main/java/com/google/appengine/api/blobstore/jakarta/BlobstoreServiceImpl.java
new file mode 100644
index 000000000..2d0bb8b20
--- /dev/null
+++ b/api/src/main/java/com/google/appengine/api/blobstore/jakarta/BlobstoreServiceImpl.java
@@ -0,0 +1,403 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.appengine.api.blobstore.jakarta;
+
+import static java.util.Objects.requireNonNull;
+
+import com.google.appengine.api.blobstore.BlobInfo;
+import com.google.appengine.api.blobstore.BlobKey;
+import com.google.appengine.api.blobstore.BlobstoreFailureException;
+import com.google.appengine.api.blobstore.BlobstoreServicePb.BlobstoreServiceError;
+import com.google.appengine.api.blobstore.BlobstoreServicePb.CreateEncodedGoogleStorageKeyRequest;
+import com.google.appengine.api.blobstore.BlobstoreServicePb.CreateEncodedGoogleStorageKeyResponse;
+import com.google.appengine.api.blobstore.BlobstoreServicePb.CreateUploadURLRequest;
+import com.google.appengine.api.blobstore.BlobstoreServicePb.CreateUploadURLResponse;
+import com.google.appengine.api.blobstore.BlobstoreServicePb.DeleteBlobRequest;
+import com.google.appengine.api.blobstore.BlobstoreServicePb.FetchDataRequest;
+import com.google.appengine.api.blobstore.BlobstoreServicePb.FetchDataResponse;
+import com.google.appengine.api.blobstore.ByteRange;
+import com.google.appengine.api.blobstore.FileInfo;
+import com.google.appengine.api.blobstore.UnsupportedRangeFormatException;
+import com.google.appengine.api.blobstore.UploadOptions;
+import com.google.apphosting.api.ApiProxy;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.Maps;
+import com.google.protobuf.ExtensionRegistry;
+import com.google.protobuf.InvalidProtocolBufferException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+import org.jspecify.annotations.Nullable;
+
+/**
+ * {@code BlobstoreServiceImpl} is an implementation of {@link BlobstoreService} that makes API
+ * calls to {@link ApiProxy}.
+ *
+ */
+class BlobstoreServiceImpl implements BlobstoreService {
+ static final String PACKAGE = "blobstore";
+ static final String SERVE_HEADER = "X-AppEngine-BlobKey";
+ static final String UPLOADED_BLOBKEY_ATTR = "com.google.appengine.api.blobstore.upload.blobkeys";
+ static final String UPLOADED_BLOBINFO_ATTR =
+ "com.google.appengine.api.blobstore.upload.blobinfos";
+ static final String BLOB_RANGE_HEADER = "X-AppEngine-BlobRange";
+ static final String CREATION_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss.SSS";
+
+ @Override
+ public String createUploadUrl(String successPath) {
+ return createUploadUrl(successPath, UploadOptions.Builder.withDefaults());
+ }
+
+ @Override
+ public String createUploadUrl(String successPath, UploadOptions uploadOptions) {
+ if (successPath == null) {
+ throw new NullPointerException("Success path must not be null.");
+ }
+
+ CreateUploadURLRequest.Builder request =
+ CreateUploadURLRequest.newBuilder().setSuccessPath(successPath);
+
+ if (uploadOptions.hasMaxUploadSizeBytesPerBlob()) {
+ request.setMaxUploadSizePerBlobBytes(uploadOptions.getMaxUploadSizeBytesPerBlob());
+ }
+
+ if (uploadOptions.hasMaxUploadSizeBytes()) {
+ request.setMaxUploadSizeBytes(uploadOptions.getMaxUploadSizeBytes());
+ }
+
+ if (uploadOptions.hasGoogleStorageBucketName()) {
+ request.setGsBucketName(uploadOptions.getGoogleStorageBucketName());
+ }
+
+ byte[] responseBytes;
+ try {
+ responseBytes =
+ ApiProxy.makeSyncCall(PACKAGE, "CreateUploadURL", request.build().toByteArray());
+ } catch (ApiProxy.ApplicationException ex) {
+ switch (BlobstoreServiceError.ErrorCode.forNumber(ex.getApplicationError())) {
+ case URL_TOO_LONG:
+ throw new IllegalArgumentException("The resulting URL was too long.");
+ case INTERNAL_ERROR:
+ throw new BlobstoreFailureException("An internal blobstore error occurred.");
+ default:
+ throw new BlobstoreFailureException("An unexpected error occurred.", ex);
+ }
+ }
+
+ try {
+ CreateUploadURLResponse response =
+ CreateUploadURLResponse.parseFrom(
+ responseBytes, ExtensionRegistry.getEmptyRegistry());
+ if (!response.isInitialized()) {
+ throw new BlobstoreFailureException("Could not parse CreateUploadURLResponse");
+ }
+ return response.getUrl();
+
+ } catch (InvalidProtocolBufferException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ @Override
+ public void serve(BlobKey blobKey, HttpServletResponse response) {
+ serve(blobKey, (ByteRange) null, response);
+ }
+
+ @Override
+ public void serve(BlobKey blobKey, String rangeHeader, HttpServletResponse response) {
+ serve(blobKey, ByteRange.parse(rangeHeader), response);
+ }
+
+ @Override
+ public void serve(BlobKey blobKey, @Nullable ByteRange byteRange, HttpServletResponse response) {
+ if (response.isCommitted()) {
+ throw new IllegalStateException("Response was already committed.");
+ }
+
+ // N.B.(gregwilkins): Content-Length is not needed by blobstore and causes error in jetty94
+ response.setContentLength(-1);
+
+ // N.B.: Blobstore serving is only enabled for 200 responses.
+ response.setStatus(HttpServletResponse.SC_OK);
+ response.setHeader(SERVE_HEADER, blobKey.getKeyString());
+ if (byteRange != null) {
+ response.setHeader(BLOB_RANGE_HEADER, byteRange.toString());
+ }
+ }
+
+ @Override
+ public @Nullable ByteRange getByteRange(HttpServletRequest request) {
+ @SuppressWarnings("unchecked")
+ Enumeration rangeHeaders = request.getHeaders("range");
+ if (!rangeHeaders.hasMoreElements()) {
+ return null;
+ }
+
+ String rangeHeader = rangeHeaders.nextElement();
+ if (rangeHeaders.hasMoreElements()) {
+ throw new UnsupportedRangeFormatException("Cannot accept multiple range headers.");
+ }
+
+ return ByteRange.parse(rangeHeader);
+ }
+
+ @Override
+ public void delete(BlobKey... blobKeys) {
+ DeleteBlobRequest.Builder request = DeleteBlobRequest.newBuilder();
+ for (BlobKey blobKey : blobKeys) {
+ request.addBlobKey(blobKey.getKeyString());
+ }
+
+ if (request.getBlobKeyCount() == 0) {
+ return;
+ }
+
+ try {
+ ApiProxy.makeSyncCall(PACKAGE, "DeleteBlob", request.build().toByteArray());
+ } catch (ApiProxy.ApplicationException ex) {
+ switch (BlobstoreServiceError.ErrorCode.forNumber(ex.getApplicationError())) {
+ case INTERNAL_ERROR:
+ throw new BlobstoreFailureException("An internal blobstore error occurred.");
+ default:
+ throw new BlobstoreFailureException("An unexpected error occurred.", ex);
+ }
+ }
+ }
+
+ @Override
+ @Deprecated
+ public Map getUploadedBlobs(HttpServletRequest request) {
+ Map> blobKeys = getUploads(request);
+ Map result = Maps.newHashMapWithExpectedSize(blobKeys.size());
+
+ for (Map.Entry> entry : blobKeys.entrySet()) {
+ // In throery it is not possible for the value for an entry to be empty,
+ // and the following check is simply defensive against a possible future
+ // change to that assumption.
+ if (!entry.getValue().isEmpty()) {
+ result.put(entry.getKey(), entry.getValue().get(0));
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public Map> getUploads(HttpServletRequest request) {
+ // N.B.: We're storing strings instead of BlobKey
+ // objects in the request attributes to avoid conflicts between
+ // the BlobKey classes loaded by the two classloaders in the
+ // DevAppServer. We convert back to BlobKey objects here.
+ @SuppressWarnings("unchecked")
+ Map> attributes =
+ (Map>) request.getAttribute(UPLOADED_BLOBKEY_ATTR);
+ if (attributes == null) {
+ throw new IllegalStateException("Must be called from a blob upload callback request.");
+ }
+ Map> blobKeys = Maps.newHashMapWithExpectedSize(attributes.size());
+ for (Map.Entry> attr : attributes.entrySet()) {
+ List blobs = new ArrayList<>(attr.getValue().size());
+ for (String key : attr.getValue()) {
+ blobs.add(new BlobKey(key));
+ }
+ blobKeys.put(attr.getKey(), blobs);
+ }
+ return blobKeys;
+ }
+
+ @Override
+ public Map> getBlobInfos(HttpServletRequest request) {
+ @SuppressWarnings("unchecked")
+ Map>> attributes =
+ (Map>>) request.getAttribute(UPLOADED_BLOBINFO_ATTR);
+ if (attributes == null) {
+ throw new IllegalStateException("Must be called from a blob upload callback request.");
+ }
+ Map> blobInfos = Maps.newHashMapWithExpectedSize(attributes.size());
+ for (Map.Entry>> attr : attributes.entrySet()) {
+ List blobs = new ArrayList<>(attr.getValue().size());
+ for (Map info : attr.getValue()) {
+ BlobKey key = new BlobKey(requireNonNull(info.get("key"), "Missing key attribute"));
+ String contentType =
+ requireNonNull(info.get("content-type"), "Missing content-type attribute");
+ String creationDateAttribute =
+ requireNonNull(info.get("creation-date"), "Missing creation-date attribute");
+ Date creationDate =
+ requireNonNull(
+ parseCreationDate(creationDateAttribute),
+ () -> "Bad creation-date attribute: " + creationDateAttribute);
+ String filename = requireNonNull(info.get("filename"), "Missing filename attribute");
+ int size = Integer.parseInt(requireNonNull(info.get("size"), "Missing size attribute"));
+ String md5Hash = requireNonNull(info.get("md5-hash"), "Missing md5-hash attribute");
+ String gsObjectName = info.get("gs-name");
+ blobs.add(
+ new BlobInfo(key, contentType, creationDate, filename, size, md5Hash, gsObjectName));
+ }
+ blobInfos.put(attr.getKey(), blobs);
+ }
+ return blobInfos;
+ }
+
+ @Override
+ public Map> getFileInfos(HttpServletRequest request) {
+ @SuppressWarnings("unchecked")
+ Map>> attributes =
+ (Map>>) request.getAttribute(UPLOADED_BLOBINFO_ATTR);
+ if (attributes == null) {
+ throw new IllegalStateException("Must be called from a blob upload callback request.");
+ }
+ Map> fileInfos = Maps.newHashMapWithExpectedSize(attributes.size());
+ for (Map.Entry>> attr : attributes.entrySet()) {
+ List files = new ArrayList<>(attr.getValue().size());
+ for (Map info : attr.getValue()) {
+ String contentType =
+ requireNonNull(info.get("content-type"), "Missing content-type attribute");
+ String creationDateAttribute =
+ requireNonNull(info.get("creation-date"), "Missing creation-date attribute");
+ Date creationDate =
+ requireNonNull(
+ parseCreationDate(creationDateAttribute),
+ () -> "Invalid creation-date attribute " + creationDateAttribute);
+ String filename = requireNonNull(info.get("filename"), "Missing filename attribute");
+ long size = Long.parseLong(requireNonNull(info.get("size"), "Missing size attribute"));
+ String md5Hash = requireNonNull(info.get("md5-hash"), "Missing md5-hash attribute");
+ String gsObjectName = info.getOrDefault("gs-name", null);
+ files.add(new FileInfo(contentType, creationDate, filename, size, md5Hash, gsObjectName));
+ }
+ fileInfos.put(attr.getKey(), files);
+ }
+ return fileInfos;
+ }
+
+ @VisibleForTesting
+ protected static @Nullable Date parseCreationDate(String date) {
+ Date creationDate = null;
+ try {
+ date = date.trim().substring(0, CREATION_DATE_FORMAT.length());
+ SimpleDateFormat dateFormat = new SimpleDateFormat(CREATION_DATE_FORMAT);
+ // Enforce strict adherence to the format
+ dateFormat.setLenient(false);
+ creationDate = dateFormat.parse(date);
+ } catch (IndexOutOfBoundsException e) {
+ // This should never happen. We got a date that is shorter than the format.
+ // TODO: add log
+ } catch (ParseException e) {
+ // This should never happen. We got a date that does not match the format.
+ // TODO: add log
+ }
+ return creationDate;
+ }
+
+ @Override
+ public byte[] fetchData(BlobKey blobKey, long startIndex, long endIndex) {
+ if (startIndex < 0) {
+ throw new IllegalArgumentException("Start index must be >= 0.");
+ }
+
+ if (endIndex < startIndex) {
+ throw new IllegalArgumentException("End index must be >= startIndex.");
+ }
+
+ // +1 since endIndex is inclusive
+ long fetchSize = endIndex - startIndex + 1;
+ if (fetchSize > MAX_BLOB_FETCH_SIZE) {
+ throw new IllegalArgumentException(
+ "Blob fetch size "
+ + fetchSize
+ + " is larger "
+ + "than maximum size "
+ + MAX_BLOB_FETCH_SIZE
+ + " bytes.");
+ }
+
+ FetchDataRequest request =
+ FetchDataRequest.newBuilder()
+ .setBlobKey(blobKey.getKeyString())
+ .setStartIndex(startIndex)
+ .setEndIndex(endIndex)
+ .build();
+
+ byte[] responseBytes;
+ try {
+ responseBytes = ApiProxy.makeSyncCall(PACKAGE, "FetchData", request.toByteArray());
+ } catch (ApiProxy.ApplicationException ex) {
+ switch (BlobstoreServiceError.ErrorCode.forNumber(ex.getApplicationError())) {
+ case PERMISSION_DENIED:
+ throw new SecurityException("This application does not have access to that blob.");
+ case BLOB_NOT_FOUND:
+ throw new IllegalArgumentException("Blob not found.");
+ case INTERNAL_ERROR:
+ throw new BlobstoreFailureException("An internal blobstore error occurred.");
+ default:
+ throw new BlobstoreFailureException("An unexpected error occurred.", ex);
+ }
+ }
+
+ try {
+ FetchDataResponse response =
+ FetchDataResponse.parseFrom(responseBytes, ExtensionRegistry.getEmptyRegistry());
+ if (!response.isInitialized()) {
+ throw new BlobstoreFailureException("Could not parse FetchDataResponse");
+ }
+ return response.getData().toByteArray();
+ } catch (InvalidProtocolBufferException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ @Override
+ public BlobKey createGsBlobKey(String filename) {
+
+ if (!filename.startsWith("/gs/")) {
+ throw new IllegalArgumentException(
+ "Google storage filenames must be" + " prefixed with /gs/");
+ }
+ CreateEncodedGoogleStorageKeyRequest request =
+ CreateEncodedGoogleStorageKeyRequest.newBuilder().setFilename(filename).build();
+
+ byte[] responseBytes;
+ try {
+ responseBytes =
+ ApiProxy.makeSyncCall(PACKAGE, "CreateEncodedGoogleStorageKey", request.toByteArray());
+ } catch (ApiProxy.ApplicationException ex) {
+ switch (BlobstoreServiceError.ErrorCode.forNumber(ex.getApplicationError())) {
+ case INTERNAL_ERROR:
+ throw new BlobstoreFailureException("An internal blobstore error occurred.");
+ default:
+ throw new BlobstoreFailureException("An unexpected error occurred.", ex);
+ }
+ }
+
+ try {
+ CreateEncodedGoogleStorageKeyResponse response =
+ CreateEncodedGoogleStorageKeyResponse.parseFrom(
+ responseBytes, ExtensionRegistry.getEmptyRegistry());
+ if (!response.isInitialized()) {
+ throw new BlobstoreFailureException(
+ "Could not parse CreateEncodedGoogleStorageKeyResponse");
+ }
+ return new BlobKey(response.getBlobKey());
+ } catch (InvalidProtocolBufferException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+}
diff --git a/api/src/main/java/com/google/appengine/api/datastore/AdminDatastoreService.java b/api/src/main/java/com/google/appengine/api/datastore/AdminDatastoreService.java
index d99d55050..cbfede301 100644
--- a/api/src/main/java/com/google/appengine/api/datastore/AdminDatastoreService.java
+++ b/api/src/main/java/com/google/appengine/api/datastore/AdminDatastoreService.java
@@ -35,7 +35,7 @@
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Future;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* An AsyncDatastoreService implementation that is pinned to a specific appId and namesapce. This
diff --git a/api/src/main/java/com/google/appengine/api/datastore/AppIdNamespace.java b/api/src/main/java/com/google/appengine/api/datastore/AppIdNamespace.java
index 0c2cc6708..01c938019 100644
--- a/api/src/main/java/com/google/appengine/api/datastore/AppIdNamespace.java
+++ b/api/src/main/java/com/google/appengine/api/datastore/AppIdNamespace.java
@@ -18,7 +18,7 @@
import com.google.apphosting.api.NamespaceResources;
import java.io.Serializable;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* Abstraction for a "mangled" AppId. A mangled AppId is a combination of the application id and the
diff --git a/api/src/main/java/com/google/appengine/api/datastore/AsyncCloudDatastoreV1ServiceImpl.java b/api/src/main/java/com/google/appengine/api/datastore/AsyncCloudDatastoreV1ServiceImpl.java
index a6205fc27..adcf1a723 100644
--- a/api/src/main/java/com/google/appengine/api/datastore/AsyncCloudDatastoreV1ServiceImpl.java
+++ b/api/src/main/java/com/google/appengine/api/datastore/AsyncCloudDatastoreV1ServiceImpl.java
@@ -63,7 +63,7 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.logging.Level;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/** An implementation of {@link AsyncDatastoreService} using the Cloud Datastore v1 API. */
class AsyncCloudDatastoreV1ServiceImpl extends BaseAsyncDatastoreServiceImpl {
diff --git a/api/src/main/java/com/google/appengine/api/datastore/AsyncDatastoreService.java b/api/src/main/java/com/google/appengine/api/datastore/AsyncDatastoreService.java
index c62578958..ad99ed907 100644
--- a/api/src/main/java/com/google/appengine/api/datastore/AsyncDatastoreService.java
+++ b/api/src/main/java/com/google/appengine/api/datastore/AsyncDatastoreService.java
@@ -19,7 +19,7 @@
import java.util.List;
import java.util.Map;
import java.util.concurrent.Future;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* An asynchronous version of {@link DatastoreService}. All methods return immediately and provide
diff --git a/api/src/main/java/com/google/appengine/api/datastore/AsyncDatastoreServiceImpl.java b/api/src/main/java/com/google/appengine/api/datastore/AsyncDatastoreServiceImpl.java
index d24e3d00d..165f4f976 100644
--- a/api/src/main/java/com/google/appengine/api/datastore/AsyncDatastoreServiceImpl.java
+++ b/api/src/main/java/com/google/appengine/api/datastore/AsyncDatastoreServiceImpl.java
@@ -59,7 +59,7 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.logging.Level;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* An implementation of AsyncDatastoreService using the DatastoreV3 API.
diff --git a/api/src/main/java/com/google/appengine/api/datastore/BaseAsyncDatastoreServiceImpl.java b/api/src/main/java/com/google/appengine/api/datastore/BaseAsyncDatastoreServiceImpl.java
index 39977b31c..a278d525e 100644
--- a/api/src/main/java/com/google/appengine/api/datastore/BaseAsyncDatastoreServiceImpl.java
+++ b/api/src/main/java/com/google/appengine/api/datastore/BaseAsyncDatastoreServiceImpl.java
@@ -38,7 +38,7 @@
import java.util.Set;
import java.util.concurrent.Future;
import java.util.logging.Logger;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* State and behavior that is common to all asynchronous Datastore API implementations.
diff --git a/api/src/main/java/com/google/appengine/api/datastore/BaseEntityComparator.java b/api/src/main/java/com/google/appengine/api/datastore/BaseEntityComparator.java
index fb07c6318..970adcfe9 100644
--- a/api/src/main/java/com/google/appengine/api/datastore/BaseEntityComparator.java
+++ b/api/src/main/java/com/google/appengine/api/datastore/BaseEntityComparator.java
@@ -28,7 +28,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/** Base class for Entity comparators. */
abstract class BaseEntityComparator implements Comparator {
diff --git a/api/src/main/java/com/google/appengine/api/datastore/BaseQueryResultsSource.java b/api/src/main/java/com/google/appengine/api/datastore/BaseQueryResultsSource.java
index 7741deea4..19a4a67b0 100644
--- a/api/src/main/java/com/google/appengine/api/datastore/BaseQueryResultsSource.java
+++ b/api/src/main/java/com/google/appengine/api/datastore/BaseQueryResultsSource.java
@@ -23,7 +23,7 @@
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Logger;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* Concrete implementation of QueryResultsSource which knows how to make callbacks back into the
diff --git a/api/src/main/java/com/google/appengine/api/datastore/Blob.java b/api/src/main/java/com/google/appengine/api/datastore/Blob.java
index 8cfd0d898..f72b65df2 100644
--- a/api/src/main/java/com/google/appengine/api/datastore/Blob.java
+++ b/api/src/main/java/com/google/appengine/api/datastore/Blob.java
@@ -18,7 +18,7 @@
import java.io.Serializable;
import java.util.Arrays;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* {@code Blob} contains an array of bytes. This byte array can be no bigger than 1MB. To store
diff --git a/api/src/main/java/com/google/appengine/api/datastore/Category.java b/api/src/main/java/com/google/appengine/api/datastore/Category.java
index 0640c0c17..26f85074f 100644
--- a/api/src/main/java/com/google/appengine/api/datastore/Category.java
+++ b/api/src/main/java/com/google/appengine/api/datastore/Category.java
@@ -17,7 +17,7 @@
package com.google.appengine.api.datastore;
import java.io.Serializable;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* A tag, ie a descriptive word or phrase. Entities may be tagged by users, and later returned by a
diff --git a/api/src/main/java/com/google/appengine/api/datastore/CloudDatastoreRemoteServiceConfig.java b/api/src/main/java/com/google/appengine/api/datastore/CloudDatastoreRemoteServiceConfig.java
index f71ea3505..fba3c7ef8 100644
--- a/api/src/main/java/com/google/appengine/api/datastore/CloudDatastoreRemoteServiceConfig.java
+++ b/api/src/main/java/com/google/appengine/api/datastore/CloudDatastoreRemoteServiceConfig.java
@@ -25,7 +25,7 @@
import com.google.common.collect.ImmutableSet;
import java.security.PrivateKey;
import java.util.Set;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* User-configurable global properties of Cloud Datastore.
diff --git a/api/src/main/java/com/google/appengine/api/datastore/CloudDatastoreV1ClientImpl.java b/api/src/main/java/com/google/appengine/api/datastore/CloudDatastoreV1ClientImpl.java
index 5793f2d23..8f5d5d479 100644
--- a/api/src/main/java/com/google/appengine/api/datastore/CloudDatastoreV1ClientImpl.java
+++ b/api/src/main/java/com/google/appengine/api/datastore/CloudDatastoreV1ClientImpl.java
@@ -60,7 +60,7 @@
import java.util.concurrent.Future;
import java.util.logging.Level;
import java.util.logging.Logger;
-import javax.annotation.Nullable;
+import org.jspecify.annotations.Nullable;
/** A thread-safe {@link CloudDatastoreV1Client} that makes remote proto-over-HTTP calls. */
final class CloudDatastoreV1ClientImpl implements CloudDatastoreV1Client {
diff --git a/api/src/main/java/com/google/appengine/api/datastore/CompositeIndexManager.java b/api/src/main/java/com/google/appengine/api/datastore/CompositeIndexManager.java
index 25cdd7ba4..369e25ef7 100644
--- a/api/src/main/java/com/google/appengine/api/datastore/CompositeIndexManager.java
+++ b/api/src/main/java/com/google/appengine/api/datastore/CompositeIndexManager.java
@@ -37,7 +37,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
// CAUTION: this is one of several files that implement parsing and
// validation of the index definition schema; they all must be kept in
diff --git a/api/src/main/java/com/google/appengine/api/datastore/Cursor.java b/api/src/main/java/com/google/appengine/api/datastore/Cursor.java
index 64e6ad8e2..cd5c48041 100644
--- a/api/src/main/java/com/google/appengine/api/datastore/Cursor.java
+++ b/api/src/main/java/com/google/appengine/api/datastore/Cursor.java
@@ -26,7 +26,7 @@
import com.google.protobuf.ByteString;
import java.io.IOException;
import java.io.Serializable;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* A cursor that represents a position in a query.
diff --git a/api/src/main/java/com/google/appengine/api/datastore/DataTypeTranslator.java b/api/src/main/java/com/google/appengine/api/datastore/DataTypeTranslator.java
index 5b70196f1..8ca83c31b 100644
--- a/api/src/main/java/com/google/appengine/api/datastore/DataTypeTranslator.java
+++ b/api/src/main/java/com/google/appengine/api/datastore/DataTypeTranslator.java
@@ -61,7 +61,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* {@code DataTypeTranslator} is a utility class for converting between the data store's {@code
diff --git a/api/src/main/java/com/google/appengine/api/datastore/DataTypeUtils.java b/api/src/main/java/com/google/appengine/api/datastore/DataTypeUtils.java
index 178ea42c6..814fb34e2 100644
--- a/api/src/main/java/com/google/appengine/api/datastore/DataTypeUtils.java
+++ b/api/src/main/java/com/google/appengine/api/datastore/DataTypeUtils.java
@@ -32,7 +32,7 @@
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Logger;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* {@code DataTypeUtils} presents a simpler interface that allows user-code to determine what
diff --git a/api/src/main/java/com/google/appengine/api/datastore/DatastoreServiceConfig.java b/api/src/main/java/com/google/appengine/api/datastore/DatastoreServiceConfig.java
index e45343adf..10e58c436 100644
--- a/api/src/main/java/com/google/appengine/api/datastore/DatastoreServiceConfig.java
+++ b/api/src/main/java/com/google/appengine/api/datastore/DatastoreServiceConfig.java
@@ -22,7 +22,7 @@
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.Collection;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* User-configurable properties of the datastore.
diff --git a/api/src/main/java/com/google/appengine/api/datastore/DatastoreServiceGlobalConfig.java b/api/src/main/java/com/google/appengine/api/datastore/DatastoreServiceGlobalConfig.java
index 8a292c30d..d0385051b 100644
--- a/api/src/main/java/com/google/appengine/api/datastore/DatastoreServiceGlobalConfig.java
+++ b/api/src/main/java/com/google/appengine/api/datastore/DatastoreServiceGlobalConfig.java
@@ -40,7 +40,7 @@
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
-import javax.annotation.Nullable;
+import org.jspecify.annotations.Nullable;
/** See {@link CloudDatastoreRemoteServiceConfig}. */
// TODO(b/64163395): consider merging with CloudDatastoreRemoteServiceConfig
diff --git a/api/src/main/java/com/google/appengine/api/datastore/Email.java b/api/src/main/java/com/google/appengine/api/datastore/Email.java
index 6e827c0d8..d699be31b 100644
--- a/api/src/main/java/com/google/appengine/api/datastore/Email.java
+++ b/api/src/main/java/com/google/appengine/api/datastore/Email.java
@@ -17,7 +17,7 @@
package com.google.appengine.api.datastore;
import java.io.Serializable;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* An e-mail address datatype. Makes no attempt at validation.
diff --git a/api/src/main/java/com/google/appengine/api/datastore/EmbeddedEntity.java b/api/src/main/java/com/google/appengine/api/datastore/EmbeddedEntity.java
index 65ef9ba65..9623aa021 100644
--- a/api/src/main/java/com/google/appengine/api/datastore/EmbeddedEntity.java
+++ b/api/src/main/java/com/google/appengine/api/datastore/EmbeddedEntity.java
@@ -19,7 +19,7 @@
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* A property value containing embedded entity properties (and optionally a {@link Key}).
diff --git a/api/src/main/java/com/google/appengine/api/datastore/Entities.java b/api/src/main/java/com/google/appengine/api/datastore/Entities.java
index 5a8a930c8..d14f9263e 100644
--- a/api/src/main/java/com/google/appengine/api/datastore/Entities.java
+++ b/api/src/main/java/com/google/appengine/api/datastore/Entities.java
@@ -18,7 +18,7 @@
import static java.util.Objects.requireNonNull;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* Utility functions and constants for entities.
diff --git a/api/src/main/java/com/google/appengine/api/datastore/Entity.java b/api/src/main/java/com/google/appengine/api/datastore/Entity.java
index 53a765cbf..47add6ed5 100644
--- a/api/src/main/java/com/google/appengine/api/datastore/Entity.java
+++ b/api/src/main/java/com/google/appengine/api/datastore/Entity.java
@@ -22,7 +22,7 @@
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* {@code Entity} is the fundamental unit of data storage. It has an immutable identifier (contained
diff --git a/api/src/main/java/com/google/appengine/api/datastore/EntityComparator.java b/api/src/main/java/com/google/appengine/api/datastore/EntityComparator.java
index c86afed74..10ee81bf9 100644
--- a/api/src/main/java/com/google/appengine/api/datastore/EntityComparator.java
+++ b/api/src/main/java/com/google/appengine/api/datastore/EntityComparator.java
@@ -26,7 +26,7 @@
import java.util.Collection;
import java.util.Collections;
import java.util.List;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* A comparator with the same ordering as {@link EntityProtoComparators} which uses Entity objects
diff --git a/api/src/main/java/com/google/appengine/api/datastore/EntityProtoComparators.java b/api/src/main/java/com/google/appengine/api/datastore/EntityProtoComparators.java
index 1d8cc786b..468f506ba 100644
--- a/api/src/main/java/com/google/appengine/api/datastore/EntityProtoComparators.java
+++ b/api/src/main/java/com/google/appengine/api/datastore/EntityProtoComparators.java
@@ -25,7 +25,7 @@
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* Utilities for comparing {@link EntityProto}. This class is only public because the dev appserver
diff --git a/api/src/main/java/com/google/appengine/api/datastore/EntityTranslator.java b/api/src/main/java/com/google/appengine/api/datastore/EntityTranslator.java
index b8b20ed0a..48cbb811b 100644
--- a/api/src/main/java/com/google/appengine/api/datastore/EntityTranslator.java
+++ b/api/src/main/java/com/google/appengine/api/datastore/EntityTranslator.java
@@ -22,7 +22,7 @@
import com.google.storage.onestore.v3.OnestoreEntity.Reference;
import java.util.Collection;
import java.util.Map;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* {@code EntityTranslator} contains the logic to translate an {@code Entity} into the protocol
diff --git a/api/src/main/java/com/google/appengine/api/datastore/FetchOptions.java b/api/src/main/java/com/google/appengine/api/datastore/FetchOptions.java
index e630a24e0..c8b3a4a95 100644
--- a/api/src/main/java/com/google/appengine/api/datastore/FetchOptions.java
+++ b/api/src/main/java/com/google/appengine/api/datastore/FetchOptions.java
@@ -19,7 +19,7 @@
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* Describes the limit, offset, and chunk size to be applied when executing a {@link PreparedQuery}.
diff --git a/api/src/main/java/com/google/appengine/api/datastore/FutureHelper.java b/api/src/main/java/com/google/appengine/api/datastore/FutureHelper.java
index c7efa5db5..4406e5ca5 100644
--- a/api/src/main/java/com/google/appengine/api/datastore/FutureHelper.java
+++ b/api/src/main/java/com/google/appengine/api/datastore/FutureHelper.java
@@ -23,7 +23,7 @@
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* Utilities for working with {@link Future Futures} in the synchronous datastore api.
diff --git a/api/src/main/java/com/google/appengine/api/datastore/GeoPt.java b/api/src/main/java/com/google/appengine/api/datastore/GeoPt.java
index 1a1ee96da..0e13d5b96 100644
--- a/api/src/main/java/com/google/appengine/api/datastore/GeoPt.java
+++ b/api/src/main/java/com/google/appengine/api/datastore/GeoPt.java
@@ -17,7 +17,7 @@
package com.google.appengine.api.datastore;
import java.io.Serializable;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* A geographical point, specified by float latitude and longitude coordinates. Often used to
diff --git a/api/src/main/java/com/google/appengine/api/datastore/GetOrCreateTransactionResult.java b/api/src/main/java/com/google/appengine/api/datastore/GetOrCreateTransactionResult.java
index c790a1473..610f09432 100644
--- a/api/src/main/java/com/google/appengine/api/datastore/GetOrCreateTransactionResult.java
+++ b/api/src/main/java/com/google/appengine/api/datastore/GetOrCreateTransactionResult.java
@@ -16,7 +16,7 @@
package com.google.appengine.api.datastore;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* Helper class used to encapsulate the result of a call to {@link
diff --git a/api/src/main/java/com/google/appengine/api/datastore/IMHandle.java b/api/src/main/java/com/google/appengine/api/datastore/IMHandle.java
index 27ed2cbdc..1496ac8bb 100644
--- a/api/src/main/java/com/google/appengine/api/datastore/IMHandle.java
+++ b/api/src/main/java/com/google/appengine/api/datastore/IMHandle.java
@@ -19,7 +19,7 @@
import java.io.Serializable;
import java.net.MalformedURLException;
import java.net.URL;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* An instant messaging handle. Includes both an address and its protocol. The protocol value is
diff --git a/api/src/main/java/com/google/appengine/api/datastore/Index.java b/api/src/main/java/com/google/appengine/api/datastore/Index.java
index f92aa9822..a84451661 100644
--- a/api/src/main/java/com/google/appengine/api/datastore/Index.java
+++ b/api/src/main/java/com/google/appengine/api/datastore/Index.java
@@ -22,7 +22,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Objects;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* A Datastore {@code Index} definition.
diff --git a/api/src/main/java/com/google/appengine/api/datastore/IndexComponentsOnlyQuery.java b/api/src/main/java/com/google/appengine/api/datastore/IndexComponentsOnlyQuery.java
index c1cbeaf78..4981124ff 100644
--- a/api/src/main/java/com/google/appengine/api/datastore/IndexComponentsOnlyQuery.java
+++ b/api/src/main/java/com/google/appengine/api/datastore/IndexComponentsOnlyQuery.java
@@ -27,7 +27,7 @@
import java.util.Iterator;
import java.util.List;
import java.util.Set;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* A query as it is actually planned on the datastore indices.
diff --git a/api/src/main/java/com/google/appengine/api/datastore/InternalTransactionV3.java b/api/src/main/java/com/google/appengine/api/datastore/InternalTransactionV3.java
index e6ddcfc65..439981173 100644
--- a/api/src/main/java/com/google/appengine/api/datastore/InternalTransactionV3.java
+++ b/api/src/main/java/com/google/appengine/api/datastore/InternalTransactionV3.java
@@ -25,7 +25,7 @@
import com.google.io.protocol.ProtocolMessage;
import com.google.protobuf.MessageLite;
import java.util.concurrent.Future;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* Implementation of the V3-specific logic to handle a {@link Transaction}.
diff --git a/api/src/main/java/com/google/appengine/api/datastore/Key.java b/api/src/main/java/com/google/appengine/api/datastore/Key.java
index a6a5e3aeb..54417dc94 100644
--- a/api/src/main/java/com/google/appengine/api/datastore/Key.java
+++ b/api/src/main/java/com/google/appengine/api/datastore/Key.java
@@ -25,7 +25,7 @@
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Objects;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* The primary key for a datastore entity.
diff --git a/api/src/main/java/com/google/appengine/api/datastore/KeyFactory.java b/api/src/main/java/com/google/appengine/api/datastore/KeyFactory.java
index 9a2dd9e7f..56f0258db 100644
--- a/api/src/main/java/com/google/appengine/api/datastore/KeyFactory.java
+++ b/api/src/main/java/com/google/appengine/api/datastore/KeyFactory.java
@@ -20,7 +20,7 @@
import com.google.common.base.CharMatcher;
import com.google.storage.onestore.v3.OnestoreEntity.Reference;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* This class enables direct creation of {@code Key} objects, both in the root entity group (no
diff --git a/api/src/main/java/com/google/appengine/api/datastore/KeyRange.java b/api/src/main/java/com/google/appengine/api/datastore/KeyRange.java
index 748c3cdfc..055b6e191 100644
--- a/api/src/main/java/com/google/appengine/api/datastore/KeyRange.java
+++ b/api/src/main/java/com/google/appengine/api/datastore/KeyRange.java
@@ -19,7 +19,7 @@
import java.io.Serializable;
import java.util.Iterator;
import java.util.NoSuchElementException;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* Represents a range of unique datastore identifiers from {@code getStart().getId()} to {@code
diff --git a/api/src/main/java/com/google/appengine/api/datastore/LazyList.java b/api/src/main/java/com/google/appengine/api/datastore/LazyList.java
index 6da1d36d4..fdb9e48b8 100644
--- a/api/src/main/java/com/google/appengine/api/datastore/LazyList.java
+++ b/api/src/main/java/com/google/appengine/api/datastore/LazyList.java
@@ -25,7 +25,7 @@
import java.util.List;
import java.util.ListIterator;
import java.util.NoSuchElementException;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* A {@link List} implementation that pulls query results from the server lazily.
diff --git a/api/src/main/java/com/google/appengine/api/datastore/Link.java b/api/src/main/java/com/google/appengine/api/datastore/Link.java
index b907329d9..3903d3327 100644
--- a/api/src/main/java/com/google/appengine/api/datastore/Link.java
+++ b/api/src/main/java/com/google/appengine/api/datastore/Link.java
@@ -17,7 +17,7 @@
package com.google.appengine.api.datastore;
import java.io.Serializable;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* A {@code Link} is a URL of limited length.
diff --git a/api/src/main/java/com/google/appengine/api/datastore/LocationMapper.java b/api/src/main/java/com/google/appengine/api/datastore/LocationMapper.java
index 59a2f9b93..df87eeb27 100644
--- a/api/src/main/java/com/google/appengine/api/datastore/LocationMapper.java
+++ b/api/src/main/java/com/google/appengine/api/datastore/LocationMapper.java
@@ -18,7 +18,7 @@
import com.google.appengine.api.datastore.CloudDatastoreRemoteServiceConfig.AppId.Location;
import com.google.common.collect.ImmutableBiMap;
-import javax.annotation.Nullable;
+import org.jspecify.annotations.Nullable;
class LocationMapper {
diff --git a/api/src/main/java/com/google/appengine/api/datastore/MonitoredIndexUsageTracker.java b/api/src/main/java/com/google/appengine/api/datastore/MonitoredIndexUsageTracker.java
index 8e32208a4..33f912b6f 100644
--- a/api/src/main/java/com/google/appengine/api/datastore/MonitoredIndexUsageTracker.java
+++ b/api/src/main/java/com/google/appengine/api/datastore/MonitoredIndexUsageTracker.java
@@ -36,7 +36,7 @@
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* This class is used to log usages of indexes that have been selected for usage monitoring by the
diff --git a/api/src/main/java/com/google/appengine/api/datastore/MultiQueryIterator.java b/api/src/main/java/com/google/appengine/api/datastore/MultiQueryIterator.java
index a83d25414..b7b0d1950 100644
--- a/api/src/main/java/com/google/appengine/api/datastore/MultiQueryIterator.java
+++ b/api/src/main/java/com/google/appengine/api/datastore/MultiQueryIterator.java
@@ -25,7 +25,7 @@
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* This class constructs lists of filters as defined by the components as needed.
diff --git a/api/src/main/java/com/google/appengine/api/datastore/PhoneNumber.java b/api/src/main/java/com/google/appengine/api/datastore/PhoneNumber.java
index 8871af099..7f8d75ab7 100644
--- a/api/src/main/java/com/google/appengine/api/datastore/PhoneNumber.java
+++ b/api/src/main/java/com/google/appengine/api/datastore/PhoneNumber.java
@@ -17,7 +17,7 @@
package com.google.appengine.api.datastore;
import java.io.Serializable;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* A human-readable phone number. No validation is performed because phone numbers have many
diff --git a/api/src/main/java/com/google/appengine/api/datastore/PostalAddress.java b/api/src/main/java/com/google/appengine/api/datastore/PostalAddress.java
index 6c91a63d7..c20a4fcfd 100644
--- a/api/src/main/java/com/google/appengine/api/datastore/PostalAddress.java
+++ b/api/src/main/java/com/google/appengine/api/datastore/PostalAddress.java
@@ -17,7 +17,7 @@
package com.google.appengine.api.datastore;
import java.io.Serializable;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* A human-readable mailing address. Mailing address formats vary widely so no validation is
diff --git a/api/src/main/java/com/google/appengine/api/datastore/PreQueryContext.java b/api/src/main/java/com/google/appengine/api/datastore/PreQueryContext.java
index 54411b10e..7b57dfa5e 100644
--- a/api/src/main/java/com/google/appengine/api/datastore/PreQueryContext.java
+++ b/api/src/main/java/com/google/appengine/api/datastore/PreQueryContext.java
@@ -17,7 +17,7 @@
package com.google.appengine.api.datastore;
import java.util.Arrays;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* Concrete {@link CallbackContext} implementation that is specific to intercepted queries. Methods
diff --git a/api/src/main/java/com/google/appengine/api/datastore/PreparedMultiQuery.java b/api/src/main/java/com/google/appengine/api/datastore/PreparedMultiQuery.java
index 64b71211b..d9c529a18 100644
--- a/api/src/main/java/com/google/appengine/api/datastore/PreparedMultiQuery.java
+++ b/api/src/main/java/com/google/appengine/api/datastore/PreparedMultiQuery.java
@@ -36,7 +36,7 @@
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.Set;
-import javax.annotation.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* A {@link PreparedQuery} implementation for use with {@link MultiQueryBuilder}.
diff --git a/api/src/main/java/com/google/appengine/api/datastore/PreparedQueryImpl.java b/api/src/main/java/com/google/appengine/api/datastore/PreparedQueryImpl.java
index 4dfa0fdce..5a4831763 100644
--- a/api/src/main/java/com/google/appengine/api/datastore/PreparedQueryImpl.java
+++ b/api/src/main/java/com/google/appengine/api/datastore/PreparedQueryImpl.java
@@ -36,11 +36,6 @@ public PreparedQueryImpl(Query query, Transaction txn, QueryRunner queryRunner)
this.txn = txn;
this.queryRunner = queryRunner;
- // TODO Move this check and the one that follows into the
- // LocalDatastoreService (it may already be there).
- checkArgument(
- txn == null || query.getAncestor() != null,
- "Only ancestor queries are allowed inside transactions.");
TransactionImpl.ensureTxnActive(txn);
}
diff --git a/api/src/main/java/com/google/appengine/api/datastore/Projection.java b/api/src/main/java/com/google/appengine/api/datastore/Projection.java
index ec1f3ec9d..50b95421c 100644
--- a/api/src/main/java/com/google/appengine/api/datastore/Projection.java
+++ b/api/src/main/java/com/google/appengine/api/datastore/Projection.java
@@ -18,7 +18,7 @@
import java.io.Serializable;
import java.util.Map;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* A query projection.
diff --git a/api/src/main/java/com/google/appengine/api/datastore/PropertyContainer.java b/api/src/main/java/com/google/appengine/api/datastore/PropertyContainer.java
index f01532e6e..67a235258 100644
--- a/api/src/main/java/com/google/appengine/api/datastore/PropertyContainer.java
+++ b/api/src/main/java/com/google/appengine/api/datastore/PropertyContainer.java
@@ -28,7 +28,7 @@
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* A mutable property container.
diff --git a/api/src/main/java/com/google/appengine/api/datastore/PropertyProjection.java b/api/src/main/java/com/google/appengine/api/datastore/PropertyProjection.java
index bc2d50e8f..bd8735d9a 100644
--- a/api/src/main/java/com/google/appengine/api/datastore/PropertyProjection.java
+++ b/api/src/main/java/com/google/appengine/api/datastore/PropertyProjection.java
@@ -21,7 +21,7 @@
import java.util.Map;
import java.util.Objects;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* A property projection.
diff --git a/api/src/main/java/com/google/appengine/api/datastore/Query.java b/api/src/main/java/com/google/appengine/api/datastore/Query.java
index 7e76ee796..73b4aae44 100644
--- a/api/src/main/java/com/google/appengine/api/datastore/Query.java
+++ b/api/src/main/java/com/google/appengine/api/datastore/Query.java
@@ -34,7 +34,7 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* {@link Query} encapsulates a request for zero or more {@link Entity} objects out of the
diff --git a/api/src/main/java/com/google/appengine/api/datastore/QueryResultListDelegator.java b/api/src/main/java/com/google/appengine/api/datastore/QueryResultListDelegator.java
index fe8bddad1..b9aa97e4b 100644
--- a/api/src/main/java/com/google/appengine/api/datastore/QueryResultListDelegator.java
+++ b/api/src/main/java/com/google/appengine/api/datastore/QueryResultListDelegator.java
@@ -20,7 +20,7 @@
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* A class that simply forwards {@link QueryResult} methods to one delegate and forwards {@link
diff --git a/api/src/main/java/com/google/appengine/api/datastore/QueryResultsSourceCloudDatastoreV1.java b/api/src/main/java/com/google/appengine/api/datastore/QueryResultsSourceCloudDatastoreV1.java
index 362b8fe23..3fff9ecff 100644
--- a/api/src/main/java/com/google/appengine/api/datastore/QueryResultsSourceCloudDatastoreV1.java
+++ b/api/src/main/java/com/google/appengine/api/datastore/QueryResultsSourceCloudDatastoreV1.java
@@ -20,7 +20,7 @@
import com.google.datastore.v1.RunQueryRequest;
import com.google.datastore.v1.RunQueryResponse;
import java.util.concurrent.Future;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
class QueryResultsSourceCloudDatastoreV1
extends BaseQueryResultsSource {
diff --git a/api/src/main/java/com/google/appengine/api/datastore/QueryResultsSourceV3.java b/api/src/main/java/com/google/appengine/api/datastore/QueryResultsSourceV3.java
index f0a74f9d9..ba092e2a6 100644
--- a/api/src/main/java/com/google/appengine/api/datastore/QueryResultsSourceV3.java
+++ b/api/src/main/java/com/google/appengine/api/datastore/QueryResultsSourceV3.java
@@ -31,7 +31,7 @@
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Future;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* V3 service specific code for iterating query results and requesting more results. Instances can
diff --git a/api/src/main/java/com/google/appengine/api/datastore/QuerySplitComponent.java b/api/src/main/java/com/google/appengine/api/datastore/QuerySplitComponent.java
index 9d8a55a9d..424d7b95d 100644
--- a/api/src/main/java/com/google/appengine/api/datastore/QuerySplitComponent.java
+++ b/api/src/main/java/com/google/appengine/api/datastore/QuerySplitComponent.java
@@ -23,7 +23,7 @@
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* A class that holds information about a given query component that will later be converted into a
diff --git a/api/src/main/java/com/google/appengine/api/datastore/Rating.java b/api/src/main/java/com/google/appengine/api/datastore/Rating.java
index 017960da7..7f9b5f2bb 100644
--- a/api/src/main/java/com/google/appengine/api/datastore/Rating.java
+++ b/api/src/main/java/com/google/appengine/api/datastore/Rating.java
@@ -17,7 +17,7 @@
package com.google.appengine.api.datastore;
import java.io.Serializable;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/** A user-provided integer rating for a piece of content. Normalized to a 0-100 scale. */
// TODO: Make the file GWT-compatible (the method
diff --git a/api/src/main/java/com/google/appengine/api/datastore/RawValue.java b/api/src/main/java/com/google/appengine/api/datastore/RawValue.java
index 4a52e993f..0c15a316d 100644
--- a/api/src/main/java/com/google/appengine/api/datastore/RawValue.java
+++ b/api/src/main/java/com/google/appengine/api/datastore/RawValue.java
@@ -31,7 +31,7 @@
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Arrays;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* A raw datastore value.
diff --git a/api/src/main/java/com/google/appengine/api/datastore/ReadPolicy.java b/api/src/main/java/com/google/appengine/api/datastore/ReadPolicy.java
index d8555e3e0..feff11729 100644
--- a/api/src/main/java/com/google/appengine/api/datastore/ReadPolicy.java
+++ b/api/src/main/java/com/google/appengine/api/datastore/ReadPolicy.java
@@ -16,7 +16,7 @@
package com.google.appengine.api.datastore;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* Policy for reads.
diff --git a/api/src/main/java/com/google/appengine/api/datastore/ShortBlob.java b/api/src/main/java/com/google/appengine/api/datastore/ShortBlob.java
index 935d47275..94f79e972 100644
--- a/api/src/main/java/com/google/appengine/api/datastore/ShortBlob.java
+++ b/api/src/main/java/com/google/appengine/api/datastore/ShortBlob.java
@@ -18,7 +18,7 @@
import java.io.Serializable;
import java.util.Arrays;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* {@code ShortBlob} contains an array of bytes no longer than {@link
diff --git a/api/src/main/java/com/google/appengine/api/datastore/Text.java b/api/src/main/java/com/google/appengine/api/datastore/Text.java
index c95c6ab2c..f5cde6a14 100644
--- a/api/src/main/java/com/google/appengine/api/datastore/Text.java
+++ b/api/src/main/java/com/google/appengine/api/datastore/Text.java
@@ -17,7 +17,7 @@
package com.google.appengine.api.datastore;
import java.io.Serializable;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
// TODO: deprecate in favor of an unindexed String.
/**
diff --git a/api/src/main/java/com/google/appengine/api/datastore/TransactionImpl.java b/api/src/main/java/com/google/appengine/api/datastore/TransactionImpl.java
index 3b88b2a5f..8c753bf8b 100644
--- a/api/src/main/java/com/google/appengine/api/datastore/TransactionImpl.java
+++ b/api/src/main/java/com/google/appengine/api/datastore/TransactionImpl.java
@@ -22,7 +22,7 @@
import java.util.concurrent.Future;
import java.util.logging.Level;
import java.util.logging.Logger;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* State and behavior that is common to all {@link Transaction} implementations.
diff --git a/api/src/main/java/com/google/appengine/api/datastore/TransactionOptions.java b/api/src/main/java/com/google/appengine/api/datastore/TransactionOptions.java
index afb1b8de5..9d04ac58b 100644
--- a/api/src/main/java/com/google/appengine/api/datastore/TransactionOptions.java
+++ b/api/src/main/java/com/google/appengine/api/datastore/TransactionOptions.java
@@ -18,7 +18,7 @@
import java.util.ArrayList;
import java.util.List;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* Describes options for transactions, passed at transaction creation time.
diff --git a/api/src/main/java/com/google/appengine/api/datastore/ValidatedQuery.java b/api/src/main/java/com/google/appengine/api/datastore/ValidatedQuery.java
index ffb5d83e8..5c356fc2f 100644
--- a/api/src/main/java/com/google/appengine/api/datastore/ValidatedQuery.java
+++ b/api/src/main/java/com/google/appengine/api/datastore/ValidatedQuery.java
@@ -31,7 +31,7 @@
import com.google.storage.onestore.v3.OnestoreEntity.Reference;
import java.util.HashSet;
import java.util.Set;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/** Wrapper around {@link Query} that performs validation. */
class ValidatedQuery extends NormalizedQuery {
@@ -93,13 +93,6 @@ private void validateQuery() {
}
}
- // Transaction requires ancestor
- if (query.hasTransaction() && !query.hasAncestor()) {
- throw new IllegalQueryException(
- "Only ancestor queries are allowed inside transactions.",
- IllegalQueryType.TRANSACTION_REQUIRES_ANCESTOR);
- }
-
// Filters and sort orders require kind.
if (!query.hasKind()) {
for (Filter filter : query.filters()) {
diff --git a/api/src/main/java/com/google/appengine/api/images/Image.java b/api/src/main/java/com/google/appengine/api/images/Image.java
index 5778b2cbf..8e7a0c008 100644
--- a/api/src/main/java/com/google/appengine/api/images/Image.java
+++ b/api/src/main/java/com/google/appengine/api/images/Image.java
@@ -18,7 +18,7 @@
import com.google.appengine.api.blobstore.BlobKey;
import java.io.Serializable;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* {@code Image} represents an image that can be manipulated by the
diff --git a/api/src/main/java/com/google/appengine/api/images/ImageImpl.java b/api/src/main/java/com/google/appengine/api/images/ImageImpl.java
index 6cbf787ce..f667de31e 100644
--- a/api/src/main/java/com/google/appengine/api/images/ImageImpl.java
+++ b/api/src/main/java/com/google/appengine/api/images/ImageImpl.java
@@ -22,7 +22,7 @@
import java.nio.ByteOrder;
import java.util.Arrays;
import java.util.Objects;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* Implementation of the {@link Image} interface.
diff --git a/api/src/main/java/com/google/appengine/api/images/ImagesServiceImpl.java b/api/src/main/java/com/google/appengine/api/images/ImagesServiceImpl.java
index 9d98fb306..1d447b78d 100644
--- a/api/src/main/java/com/google/appengine/api/images/ImagesServiceImpl.java
+++ b/api/src/main/java/com/google/appengine/api/images/ImagesServiceImpl.java
@@ -44,7 +44,7 @@
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Future;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* Implementation of the ImagesService interface.
diff --git a/api/src/main/java/com/google/appengine/api/images/ServingUrlOptions.java b/api/src/main/java/com/google/appengine/api/images/ServingUrlOptions.java
index d2d75586e..ef10dab3e 100644
--- a/api/src/main/java/com/google/appengine/api/images/ServingUrlOptions.java
+++ b/api/src/main/java/com/google/appengine/api/images/ServingUrlOptions.java
@@ -20,7 +20,7 @@
import com.google.appengine.api.blobstore.BlobKey;
import java.util.Objects;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* Allow users to customize the behavior of creating a image serving URL using
diff --git a/api/src/main/java/com/google/appengine/api/log/AppLogLine.java b/api/src/main/java/com/google/appengine/api/log/AppLogLine.java
index bac059479..fcfd8b563 100644
--- a/api/src/main/java/com/google/appengine/api/log/AppLogLine.java
+++ b/api/src/main/java/com/google/appengine/api/log/AppLogLine.java
@@ -19,7 +19,7 @@
import com.google.appengine.api.log.LogService.LogLevel;
import java.io.Serializable;
import java.util.Objects;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* An AppLogLine contains all the information for a single application
diff --git a/api/src/main/java/com/google/appengine/api/log/LogQuery.java b/api/src/main/java/com/google/appengine/api/log/LogQuery.java
index 12ed81ed6..7c0611e1b 100644
--- a/api/src/main/java/com/google/appengine/api/log/LogQuery.java
+++ b/api/src/main/java/com/google/appengine/api/log/LogQuery.java
@@ -27,7 +27,7 @@
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* Allows users to customize the behavior of {@link LogService#fetch(LogQuery)}.
diff --git a/api/src/main/java/com/google/appengine/api/log/LogQueryResult.java b/api/src/main/java/com/google/appengine/api/log/LogQueryResult.java
index d07c9b4e6..aa1513cfa 100644
--- a/api/src/main/java/com/google/appengine/api/log/LogQueryResult.java
+++ b/api/src/main/java/com/google/appengine/api/log/LogQueryResult.java
@@ -29,7 +29,7 @@
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* An object that is the result of performing a LogService.fetch() operation. LogQueryResults
diff --git a/api/src/main/java/com/google/appengine/api/log/LogServiceException.java b/api/src/main/java/com/google/appengine/api/log/LogServiceException.java
index 18a05eec0..431157d78 100644
--- a/api/src/main/java/com/google/appengine/api/log/LogServiceException.java
+++ b/api/src/main/java/com/google/appengine/api/log/LogServiceException.java
@@ -16,7 +16,7 @@
package com.google.appengine.api.log;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* Log errors apart from InvalidRequestException. These errors will generally benefit from retrying
diff --git a/api/src/main/java/com/google/appengine/api/log/LogServiceImpl.java b/api/src/main/java/com/google/appengine/api/log/LogServiceImpl.java
index d455e99b2..9a9de68de 100644
--- a/api/src/main/java/com/google/appengine/api/log/LogServiceImpl.java
+++ b/api/src/main/java/com/google/appengine/api/log/LogServiceImpl.java
@@ -33,7 +33,7 @@
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* {@code LogServiceImpl} is an implementation of {@link LogService} that makes API calls to {@link
diff --git a/api/src/main/java/com/google/appengine/api/log/RequestLogs.java b/api/src/main/java/com/google/appengine/api/log/RequestLogs.java
index 246d9a561..be3b2cb11 100644
--- a/api/src/main/java/com/google/appengine/api/log/RequestLogs.java
+++ b/api/src/main/java/com/google/appengine/api/log/RequestLogs.java
@@ -24,7 +24,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* RequestLogs contain all the log information for a single request. This
diff --git a/api/src/main/java/com/google/appengine/api/mail/BounceNotification.java b/api/src/main/java/com/google/appengine/api/mail/BounceNotification.java
index e72ba7617..6a9232bd6 100644
--- a/api/src/main/java/com/google/appengine/api/mail/BounceNotification.java
+++ b/api/src/main/java/com/google/appengine/api/mail/BounceNotification.java
@@ -17,7 +17,7 @@
package com.google.appengine.api.mail;
import javax.mail.internet.MimeMessage;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* The {@code BounceNotification} object represents an incoming bounce
diff --git a/api/src/main/java/com/google/appengine/api/mail/MailService.java b/api/src/main/java/com/google/appengine/api/mail/MailService.java
index 024d124e2..078b7d001 100644
--- a/api/src/main/java/com/google/appengine/api/mail/MailService.java
+++ b/api/src/main/java/com/google/appengine/api/mail/MailService.java
@@ -19,7 +19,7 @@
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* The {@code MailService} provides a way for user code to send emails
diff --git a/api/src/main/java/com/google/appengine/api/mail/ee10/BounceNotificationParser.java b/api/src/main/java/com/google/appengine/api/mail/ee10/BounceNotificationParser.java
index 5d2450cd5..1580bd8e5 100644
--- a/api/src/main/java/com/google/appengine/api/mail/ee10/BounceNotificationParser.java
+++ b/api/src/main/java/com/google/appengine/api/mail/ee10/BounceNotificationParser.java
@@ -28,10 +28,10 @@
import javax.mail.internet.MimeMultipart;
/**
- * The {@code BounceNotificationParser} parses an incoming HTTP request into
- * a description of a bounce notification.
- *
+ * @deprecated as of version 3.0, use {@link
+ * com.google.appengine.api.mail.jakarta.BounceNotificationParser} instead.
*/
+@Deprecated(since = "3.0.0")
public final class BounceNotificationParser extends HttpRequestParser {
/**
* Parse the POST data of the given request to get details about the bounce notification.
diff --git a/api/src/main/java/com/google/appengine/api/mail/jakarta/BounceNotificationParser.java b/api/src/main/java/com/google/appengine/api/mail/jakarta/BounceNotificationParser.java
new file mode 100644
index 000000000..b58a754da
--- /dev/null
+++ b/api/src/main/java/com/google/appengine/api/mail/jakarta/BounceNotificationParser.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.appengine.api.mail.jakarta;
+
+import com.google.appengine.api.mail.BounceNotification;
+import com.google.appengine.api.utils.jakarta.HttpRequestParser;
+import jakarta.servlet.http.HttpServletRequest;
+import java.io.IOException;
+import java.util.Properties;
+import javax.mail.BodyPart;
+import javax.mail.MessagingException;
+import javax.mail.Session;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimeMultipart;
+
+/**
+ * The {@code BounceNotificationParser} parses an incoming HTTP request into
+ * a description of a bounce notification.
+ *
+ */
+public final class BounceNotificationParser extends HttpRequestParser {
+ /**
+ * Parse the POST data of the given request to get details about the bounce notification.
+ *
+ * @param request The {@link HttpServletRequest} whose POST data should be parsed.
+ * @return a BounceNotification
+ * @throws IOException
+ * @throws MessagingException
+ */
+ public static BounceNotification parse(HttpServletRequest request)
+ throws IOException, MessagingException {
+ MimeMultipart multipart = parseMultipartRequest(request);
+
+ BounceNotification.DetailsBuilder originalDetailsBuilder = null;
+ BounceNotification.DetailsBuilder notificationDetailsBuilder = null;
+ BounceNotification.BounceNotificationBuilder bounceNotificationBuilder =
+ new BounceNotification.BounceNotificationBuilder();
+ int parts = multipart.getCount();
+ for (int i = 0; i < parts; i++) {
+ BodyPart part = multipart.getBodyPart(i);
+ String fieldName = getFieldName(part);
+ if ("raw-message".equals(fieldName)) {
+ Session session = Session.getDefaultInstance(new Properties());
+ MimeMessage message = new MimeMessage(session, part.getInputStream());
+ bounceNotificationBuilder.withRawMessage(message);
+ } else {
+ String[] subFields = fieldName.split("-");
+ BounceNotification.DetailsBuilder detailsBuilder = null;
+ if ("original".equals(subFields[0])) {
+ if (originalDetailsBuilder == null) {
+ originalDetailsBuilder = new BounceNotification.DetailsBuilder();
+ }
+ detailsBuilder = originalDetailsBuilder;
+ } else if ("notification".equals(subFields[0])) {
+ if (notificationDetailsBuilder == null) {
+ notificationDetailsBuilder = new BounceNotification.DetailsBuilder();
+ }
+ detailsBuilder = notificationDetailsBuilder;
+ }
+ if (detailsBuilder != null) {
+ String field = subFields[1];
+ String value = getTextContent(part);
+ if ("to".equals(field)) {
+ detailsBuilder.withTo(value);
+ } else if ("from".equals(field)) {
+ detailsBuilder.withFrom(value);
+ } else if ("subject".equals(field)) {
+ detailsBuilder.withSubject(value);
+ } else if ("text".equals(field)) {
+ detailsBuilder.withText(value);
+ } else if ("cc".equals(field)) {
+ detailsBuilder.withCc(value);
+ } else if ("bcc".equals(field)) {
+ detailsBuilder.withBcc(value);
+ }
+ }
+ }
+ }
+
+ if (originalDetailsBuilder != null) {
+ bounceNotificationBuilder.withOriginal(originalDetailsBuilder.build());
+ }
+ if (notificationDetailsBuilder != null) {
+ bounceNotificationBuilder.withNotification(notificationDetailsBuilder.build());
+ }
+ return bounceNotificationBuilder.build();
+ }
+}
diff --git a/api/src/main/java/com/google/appengine/api/search/Cursor.java b/api/src/main/java/com/google/appengine/api/search/Cursor.java
index 7c60b0b43..b9a2e8927 100644
--- a/api/src/main/java/com/google/appengine/api/search/Cursor.java
+++ b/api/src/main/java/com/google/appengine/api/search/Cursor.java
@@ -20,7 +20,7 @@
import com.google.appengine.api.search.proto.SearchServicePb.SearchParams;
import com.google.common.base.Preconditions;
import java.io.Serializable;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* Represents a cursor on the set of results found for executing a {@link Query}
diff --git a/api/src/main/java/com/google/appengine/api/search/Document.java b/api/src/main/java/com/google/appengine/api/search/Document.java
index 0496effd4..2db68bd4a 100644
--- a/api/src/main/java/com/google/appengine/api/search/Document.java
+++ b/api/src/main/java/com/google/appengine/api/search/Document.java
@@ -34,7 +34,7 @@
import java.util.Locale;
import java.util.Map;
import java.util.Set;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* Represents a user generated document. The following example shows how to
diff --git a/api/src/main/java/com/google/appengine/api/search/Facet.java b/api/src/main/java/com/google/appengine/api/search/Facet.java
index a69add370..db5d61605 100644
--- a/api/src/main/java/com/google/appengine/api/search/Facet.java
+++ b/api/src/main/java/com/google/appengine/api/search/Facet.java
@@ -23,7 +23,7 @@
import com.google.common.base.Preconditions;
import java.io.Serializable;
import java.util.Objects;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* A {@code Facet} can be used to categorize a {@link Document}. It is not a {@link Field}.
diff --git a/api/src/main/java/com/google/appengine/api/search/Field.java b/api/src/main/java/com/google/appengine/api/search/Field.java
index 9c5818d17..7cf6b01af 100644
--- a/api/src/main/java/com/google/appengine/api/search/Field.java
+++ b/api/src/main/java/com/google/appengine/api/search/Field.java
@@ -30,7 +30,7 @@
import java.util.Date;
import java.util.List;
import java.util.Locale;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* Represents a field of a {@link Document}, which is a name, an optional locale, and at most one
diff --git a/api/src/main/java/com/google/appengine/api/search/GetIndexesRequest.java b/api/src/main/java/com/google/appengine/api/search/GetIndexesRequest.java
index ef49147ba..472400666 100644
--- a/api/src/main/java/com/google/appengine/api/search/GetIndexesRequest.java
+++ b/api/src/main/java/com/google/appengine/api/search/GetIndexesRequest.java
@@ -20,7 +20,7 @@
import com.google.appengine.api.search.checkers.SearchApiLimits;
import com.google.appengine.api.search.proto.SearchServicePb.ListIndexesParams;
import com.google.common.base.Preconditions;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* A request to get a range of indexes. You can specify a number of
diff --git a/api/src/main/java/com/google/appengine/api/search/GetRequest.java b/api/src/main/java/com/google/appengine/api/search/GetRequest.java
index d9a562c34..b403c6a86 100644
--- a/api/src/main/java/com/google/appengine/api/search/GetRequest.java
+++ b/api/src/main/java/com/google/appengine/api/search/GetRequest.java
@@ -19,7 +19,7 @@
import com.google.appengine.api.search.checkers.GetRequestChecker;
import com.google.appengine.api.search.checkers.SearchApiLimits;
import com.google.appengine.api.search.proto.SearchServicePb.ListDocumentsParams;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* A request to list objects in an index. You can specify a number of
diff --git a/api/src/main/java/com/google/appengine/api/search/Query.java b/api/src/main/java/com/google/appengine/api/search/Query.java
index 88bd36f43..1cef41da4 100644
--- a/api/src/main/java/com/google/appengine/api/search/Query.java
+++ b/api/src/main/java/com/google/appengine/api/search/Query.java
@@ -22,7 +22,7 @@
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.List;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* A query to search an index for documents which match,
diff --git a/api/src/main/java/com/google/appengine/api/search/QueryOptions.java b/api/src/main/java/com/google/appengine/api/search/QueryOptions.java
index cb0b1c519..a53709196 100644
--- a/api/src/main/java/com/google/appengine/api/search/QueryOptions.java
+++ b/api/src/main/java/com/google/appengine/api/search/QueryOptions.java
@@ -25,7 +25,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* Represents options which control where and what in the search results to return, from restricting
diff --git a/api/src/main/java/com/google/appengine/api/search/ScoredDocument.java b/api/src/main/java/com/google/appengine/api/search/ScoredDocument.java
index b7c95fb76..d856be3c2 100644
--- a/api/src/main/java/com/google/appengine/api/search/ScoredDocument.java
+++ b/api/src/main/java/com/google/appengine/api/search/ScoredDocument.java
@@ -23,7 +23,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* Represents a document which may have been scored, possibly
diff --git a/api/src/main/java/com/google/appengine/api/search/SearchApiHelper.java b/api/src/main/java/com/google/appengine/api/search/SearchApiHelper.java
index 462e29953..69db5ff07 100644
--- a/api/src/main/java/com/google/appengine/api/search/SearchApiHelper.java
+++ b/api/src/main/java/com/google/appengine/api/search/SearchApiHelper.java
@@ -26,7 +26,7 @@
import com.google.protobuf.ExtensionRegistry;
import com.google.protobuf.InvalidProtocolBufferException;
import java.util.concurrent.Future;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/** Provides support for translation of calls between userland and appserver land. */
class SearchApiHelper {
diff --git a/api/src/main/java/com/google/appengine/api/search/SortExpression.java b/api/src/main/java/com/google/appengine/api/search/SortExpression.java
index a795e4465..3a4ef2287 100644
--- a/api/src/main/java/com/google/appengine/api/search/SortExpression.java
+++ b/api/src/main/java/com/google/appengine/api/search/SortExpression.java
@@ -20,7 +20,7 @@
import com.google.appengine.api.search.proto.SearchServicePb;
import com.google.common.base.Preconditions;
import java.util.Date;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* Sorting specification for a single dimension. Multi-dimensional sorting
diff --git a/api/src/main/java/com/google/appengine/api/search/SortOptions.java b/api/src/main/java/com/google/appengine/api/search/SortOptions.java
index 3dd68d78f..75c2659e0 100644
--- a/api/src/main/java/com/google/appengine/api/search/SortOptions.java
+++ b/api/src/main/java/com/google/appengine/api/search/SortOptions.java
@@ -24,7 +24,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* Definition of how to sort documents. You may specify zero or more sort
diff --git a/api/src/main/java/com/google/appengine/api/search/checkers/Preconditions.java b/api/src/main/java/com/google/appengine/api/search/checkers/Preconditions.java
index d7a511543..d83307661 100644
--- a/api/src/main/java/com/google/appengine/api/search/checkers/Preconditions.java
+++ b/api/src/main/java/com/google/appengine/api/search/checkers/Preconditions.java
@@ -16,7 +16,7 @@
package com.google.appengine.api.search.checkers;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* Simple static methods to be called at the start of your own methods to verify correct arguments
diff --git a/api/src/main/java/com/google/appengine/api/taskqueue/LeaseOptions.java b/api/src/main/java/com/google/appengine/api/taskqueue/LeaseOptions.java
index 9065a8eec..9d3e71b8a 100644
--- a/api/src/main/java/com/google/appengine/api/taskqueue/LeaseOptions.java
+++ b/api/src/main/java/com/google/appengine/api/taskqueue/LeaseOptions.java
@@ -18,7 +18,7 @@
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* Contains various options for lease requests following the builder pattern. Calls to {@link
diff --git a/api/src/main/java/com/google/appengine/api/taskqueue/Queue.java b/api/src/main/java/com/google/appengine/api/taskqueue/Queue.java
index 83dfe9bd8..2d98ac29a 100644
--- a/api/src/main/java/com/google/appengine/api/taskqueue/Queue.java
+++ b/api/src/main/java/com/google/appengine/api/taskqueue/Queue.java
@@ -20,7 +20,7 @@
import java.util.List;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* {@link Queue} is used to manage a task queue.
diff --git a/api/src/main/java/com/google/appengine/api/taskqueue/QueueImpl.java b/api/src/main/java/com/google/appengine/api/taskqueue/QueueImpl.java
index 4b4810de6..d52d67568 100644
--- a/api/src/main/java/com/google/appengine/api/taskqueue/QueueImpl.java
+++ b/api/src/main/java/com/google/appengine/api/taskqueue/QueueImpl.java
@@ -59,7 +59,7 @@
import java.util.Set;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* Implements the {@link Queue} interface. {@link QueueImpl} is thread safe.
diff --git a/api/src/main/java/com/google/appengine/api/taskqueue/TaskHandle.java b/api/src/main/java/com/google/appengine/api/taskqueue/TaskHandle.java
index 5049a4237..91551f631 100644
--- a/api/src/main/java/com/google/appengine/api/taskqueue/TaskHandle.java
+++ b/api/src/main/java/com/google/appengine/api/taskqueue/TaskHandle.java
@@ -24,7 +24,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* Created from {@link Queue#add(TaskOptions)}. Contains the task name (generated if otherwise
diff --git a/api/src/main/java/com/google/appengine/api/taskqueue/TaskOptions.java b/api/src/main/java/com/google/appengine/api/taskqueue/TaskOptions.java
index c5c862096..7a14b3660 100644
--- a/api/src/main/java/com/google/appengine/api/taskqueue/TaskOptions.java
+++ b/api/src/main/java/com/google/appengine/api/taskqueue/TaskOptions.java
@@ -37,7 +37,7 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* Contains various options for a task following the builder pattern. Calls to {@link TaskOptions}
diff --git a/api/src/main/java/com/google/appengine/api/taskqueue/ee10/DeferredTaskContext.java b/api/src/main/java/com/google/appengine/api/taskqueue/ee10/DeferredTaskContext.java
index 67411378a..a791d91c2 100644
--- a/api/src/main/java/com/google/appengine/api/taskqueue/ee10/DeferredTaskContext.java
+++ b/api/src/main/java/com/google/appengine/api/taskqueue/ee10/DeferredTaskContext.java
@@ -23,9 +23,10 @@
import java.util.Map;
/**
- * Resources for managing {@link DeferredTask}.
- *
+ * @deprecated as of version 3.0, use {@link
+ * com.google.appengine.api.taskqueue.jakarta.DeferredTaskContext} instead.
*/
+@Deprecated(since = "3.0.0")
public class DeferredTaskContext {
/** The content type of a serialized {@link DeferredTask}. */
public static final String RUNNABLE_TASK_CONTENT_TYPE =
diff --git a/api/src/main/java/com/google/appengine/api/taskqueue/jakarta/DeferredTaskContext.java b/api/src/main/java/com/google/appengine/api/taskqueue/jakarta/DeferredTaskContext.java
new file mode 100644
index 000000000..8cbdc5c09
--- /dev/null
+++ b/api/src/main/java/com/google/appengine/api/taskqueue/jakarta/DeferredTaskContext.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.appengine.api.taskqueue.jakarta;
+
+import com.google.apphosting.api.ApiProxy;
+import jakarta.servlet.http.HttpServlet;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import java.util.Map;
+
+/**
+ * Resources for managing {@link DeferredTask}.
+ *
+ */
+public class DeferredTaskContext {
+ /** The content type of a serialized {@link DeferredTask}. */
+ public static final String RUNNABLE_TASK_CONTENT_TYPE =
+ "application/x-binary-app-engine-java-runnable-task";
+
+ /** The URL the DeferredTask servlet is mapped to by default. */
+ public static final String DEFAULT_DEFERRED_URL = "/_ah/queue/__deferred__";
+
+ static final String DEFERRED_TASK_SERVLET_KEY =
+ DeferredTaskContext.class.getName() + ".httpServlet";
+ static final String DEFERRED_TASK_REQUEST_KEY =
+ DeferredTaskContext.class.getName() + ".httpServletRequest";
+ static final String DEFERRED_TASK_RESPONSE_KEY =
+ DeferredTaskContext.class.getName() + ".httpServletResponse";
+ static final String DEFERRED_DO_NOT_RETRY_KEY =
+ DeferredTaskContext.class.getName() + ".doNotRetry";
+ static final String DEFERRED_MARK_RETRY_KEY = DeferredTaskContext.class.getName() + ".markRetry";
+
+ /**
+ * Returns the {@link HttpServlet} instance for the current running deferred task for the current
+ * thread or {@code null} if there is no current deferred task active for this thread.
+ */
+ public static HttpServlet getCurrentServlet() {
+ Map attributes = getCurrentEnvironmentOrThrow().getAttributes();
+ return (HttpServlet) attributes.get(DEFERRED_TASK_SERVLET_KEY);
+ }
+
+ /**
+ * Returns the {@link HttpServletRequest} instance for the current running deferred task for the
+ * current thread or {@code null} if there is no current deferred task active for this thread.
+ */
+ public static HttpServletRequest getCurrentRequest() {
+ Map attributes = getCurrentEnvironmentOrThrow().getAttributes();
+ return (HttpServletRequest) attributes.get(DEFERRED_TASK_REQUEST_KEY);
+ }
+
+ /**
+ * Returns the {@link HttpServletResponse} instance for the current running deferred task for the
+ * current thread or {@code null} if there is no current deferred task active for this thread.
+ */
+ public static HttpServletResponse getCurrentResponse() {
+ Map attributes = getCurrentEnvironmentOrThrow().getAttributes();
+ return (HttpServletResponse) attributes.get(DEFERRED_TASK_RESPONSE_KEY);
+ }
+
+ /**
+ * Sets the action on task failure. Normally when an exception is thrown, the task will be
+ * retried, however if {@code setDoNotRetry} is set to {@code true}, the task will not be retried.
+ */
+ public static void setDoNotRetry(boolean value) {
+ Map attributes = getCurrentEnvironmentOrThrow().getAttributes();
+ attributes.put(DEFERRED_DO_NOT_RETRY_KEY, value);
+ }
+
+ /**
+ * Request a retry of this task, even if an exception was not thrown. If an exception was thrown
+ * and {@link #setDoNotRetry} is set to {@code true} the request will not be retried.
+ */
+ public static void markForRetry() {
+ Map attributes = getCurrentEnvironmentOrThrow().getAttributes();
+ attributes.put(DEFERRED_MARK_RETRY_KEY, true);
+ }
+
+ private static ApiProxy.Environment getCurrentEnvironmentOrThrow() {
+ ApiProxy.Environment environment = ApiProxy.getCurrentEnvironment();
+ if (environment == null) {
+ throw new IllegalStateException(
+ "Operation not allowed in a thread that is neither the original request thread "
+ + "nor a thread created by ThreadManager");
+ }
+ return environment;
+ }
+
+ private DeferredTaskContext() {}
+}
diff --git a/api/src/main/java/com/google/appengine/api/urlfetch/FetchOptions.java b/api/src/main/java/com/google/appengine/api/urlfetch/FetchOptions.java
index e8036174d..ea7301de3 100644
--- a/api/src/main/java/com/google/appengine/api/urlfetch/FetchOptions.java
+++ b/api/src/main/java/com/google/appengine/api/urlfetch/FetchOptions.java
@@ -17,7 +17,7 @@
package com.google.appengine.api.urlfetch;
import java.io.Serializable;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* Allows users to customize the behavior of {@link URLFetchService}
diff --git a/api/src/main/java/com/google/appengine/api/urlfetch/HTTPRequest.java b/api/src/main/java/com/google/appengine/api/urlfetch/HTTPRequest.java
index 40c2cb64a..81fe4e475 100644
--- a/api/src/main/java/com/google/appengine/api/urlfetch/HTTPRequest.java
+++ b/api/src/main/java/com/google/appengine/api/urlfetch/HTTPRequest.java
@@ -21,7 +21,7 @@
import java.net.URL;
import java.util.LinkedHashMap;
import java.util.List;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* {@code HTTPRequest} encapsulates a single HTTP request that is made
diff --git a/api/src/main/java/com/google/appengine/api/urlfetch/HTTPResponse.java b/api/src/main/java/com/google/appengine/api/urlfetch/HTTPResponse.java
index 2b02fb333..cec4e2bd1 100644
--- a/api/src/main/java/com/google/appengine/api/urlfetch/HTTPResponse.java
+++ b/api/src/main/java/com/google/appengine/api/urlfetch/HTTPResponse.java
@@ -24,7 +24,7 @@
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* {@code HTTPResponse} encapsulates the results of a {@code
diff --git a/api/src/main/java/com/google/appengine/api/urlfetch/URLFetchServiceImpl.java b/api/src/main/java/com/google/appengine/api/urlfetch/URLFetchServiceImpl.java
index 80ec4505c..b72f17081 100644
--- a/api/src/main/java/com/google/appengine/api/urlfetch/URLFetchServiceImpl.java
+++ b/api/src/main/java/com/google/appengine/api/urlfetch/URLFetchServiceImpl.java
@@ -35,7 +35,7 @@
import java.util.concurrent.Future;
import java.util.logging.Logger;
import javax.net.ssl.SSLHandshakeException;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
class URLFetchServiceImpl implements URLFetchService {
static final String PACKAGE = "urlfetch";
diff --git a/api/src/main/java/com/google/appengine/api/users/User.java b/api/src/main/java/com/google/appengine/api/users/User.java
index de7a7ae9e..7646ea78a 100644
--- a/api/src/main/java/com/google/appengine/api/users/User.java
+++ b/api/src/main/java/com/google/appengine/api/users/User.java
@@ -17,7 +17,7 @@
package com.google.appengine.api.users;
import java.io.Serializable;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* {@code User} represents a specific user, represented by the
diff --git a/api/src/main/java/com/google/appengine/api/users/UserService.java b/api/src/main/java/com/google/appengine/api/users/UserService.java
index c8e5e1e32..31c48ce32 100644
--- a/api/src/main/java/com/google/appengine/api/users/UserService.java
+++ b/api/src/main/java/com/google/appengine/api/users/UserService.java
@@ -17,7 +17,7 @@
package com.google.appengine.api.users;
import java.util.Set;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* The UserService provides information useful for forcing a user to
diff --git a/api/src/main/java/com/google/appengine/api/users/UserServiceImpl.java b/api/src/main/java/com/google/appengine/api/users/UserServiceImpl.java
index b6c8e4709..d9f2f368c 100644
--- a/api/src/main/java/com/google/appengine/api/users/UserServiceImpl.java
+++ b/api/src/main/java/com/google/appengine/api/users/UserServiceImpl.java
@@ -29,7 +29,7 @@
import com.google.protobuf.MessageLite;
import com.google.protobuf.UninitializedMessageException;
import java.util.Set;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* The UserService provides information useful for forcing a user to
diff --git a/api/src/main/java/com/google/appengine/api/utils/FutureWrapper.java b/api/src/main/java/com/google/appengine/api/utils/FutureWrapper.java
index 235132ba7..11a2b17b7 100644
--- a/api/src/main/java/com/google/appengine/api/utils/FutureWrapper.java
+++ b/api/src/main/java/com/google/appengine/api/utils/FutureWrapper.java
@@ -22,7 +22,7 @@
import java.util.concurrent.TimeoutException;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* {@code FutureWrapper} is a simple {@link Future} that wraps a
diff --git a/api/src/main/java/com/google/appengine/api/utils/SystemProperty.java b/api/src/main/java/com/google/appengine/api/utils/SystemProperty.java
index 25773b51c..87952a044 100644
--- a/api/src/main/java/com/google/appengine/api/utils/SystemProperty.java
+++ b/api/src/main/java/com/google/appengine/api/utils/SystemProperty.java
@@ -16,7 +16,7 @@
package com.google.appengine.api.utils;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* Global system properties which are set by App Engine.
diff --git a/api/src/main/java/com/google/appengine/api/utils/jakarta/HttpRequestParser.java b/api/src/main/java/com/google/appengine/api/utils/jakarta/HttpRequestParser.java
new file mode 100644
index 000000000..c138840cc
--- /dev/null
+++ b/api/src/main/java/com/google/appengine/api/utils/jakarta/HttpRequestParser.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.appengine.api.utils.jakarta;
+
+import jakarta.servlet.ServletInputStream;
+import jakarta.servlet.http.HttpServletRequest;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import javax.activation.DataSource;
+import javax.mail.BodyPart;
+import javax.mail.MessagingException;
+import javax.mail.internet.ContentDisposition;
+import javax.mail.internet.ContentType;
+import javax.mail.internet.MimeMultipart;
+
+/**
+ * {@code HttpRequestParser} encapsulates helper methods used to parse incoming {@code
+ * multipart/form-data} HTTP requests. Subclasses should use these methods to parse specific
+ * requests into useful data structures.
+ *
+ */
+public class HttpRequestParser {
+ /**
+ * Parse input stream of the given request into a MimeMultipart object.
+ *
+ * @params req The HttpServletRequest whose POST data should be parsed.
+ *
+ * @return A MimeMultipart object representing the POST data.
+ *
+ * @throws IOException if the input stream cannot be read.
+ * @throws MessagingException if the input stream cannot be parsed.
+ * @throws IllegalStateException if the request's input stream has already been
+ * read (eg. by calling getReader() or getInputStream()).
+ */
+ protected static MimeMultipart parseMultipartRequest(HttpServletRequest req)
+ throws IOException, MessagingException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ ServletInputStream inputStream = req.getInputStream();
+ copy(inputStream, baos);
+ if (baos.size() == 0) {
+ throw new IllegalStateException("Input stream already read, or empty.");
+ }
+
+ return new MimeMultipart(new StaticDataSource(req.getContentType(), baos.toByteArray()));
+ }
+
+ protected static String getFieldName(BodyPart part) throws MessagingException {
+ String[] values = part.getHeader("Content-Disposition");
+ String name = null;
+ if (values != null && values.length > 0) {
+ name = new ContentDisposition(values[0]).getParameter("name");
+ }
+ return (name != null) ? name : "unknown";
+ }
+
+ protected static String getTextContent(BodyPart part) throws MessagingException, IOException {
+ ContentType contentType = new ContentType(part.getContentType());
+ String charset = contentType.getParameter("charset");
+ if (charset == null) {
+ // N.B.: The MIME spec doesn't seem to provide a
+ // default charset, but the default charset for HTTP is
+ // ISO-8859-1. That seems like a reasonable default.
+ charset = "ISO-8859-1";
+ }
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ copy(part.getInputStream(), baos);
+ try {
+ return new String(baos.toByteArray(), charset);
+ } catch (UnsupportedEncodingException ex) {
+ return new String(baos.toByteArray());
+ }
+ }
+
+ /**
+ * Copies all bytes from the input stream to the output stream. Does not close or flush either
+ * stream.
+ *
+ * This code is copied from Guava's ByteStreams to avoid direct dependency on the library.
+ * See b/20821034 for details.
+ */
+ private static void copy(InputStream from, OutputStream to) throws IOException {
+ if (from == null) {
+ throw new NullPointerException();
+ }
+ if (to == null) {
+ throw new NullPointerException();
+ }
+ byte[] buf = new byte[8192];
+ while (true) {
+ int r = from.read(buf);
+ if (r == -1) {
+ break;
+ }
+ to.write(buf, 0, r);
+ }
+ }
+
+ /**
+ * A read-only {@link DataSource} backed by a content type and a
+ * fixed byte array.
+ */
+ protected static class StaticDataSource implements DataSource {
+ private final String contentType;
+ private final byte[] bytes;
+
+ public StaticDataSource(String contentType, byte[] bytes) {
+ this.contentType = contentType;
+ this.bytes = bytes;
+ }
+
+ @Override
+ public String getContentType() {
+ return contentType;
+ }
+
+ @Override
+ public InputStream getInputStream() {
+ return new ByteArrayInputStream(bytes);
+ }
+
+ @Override
+ public OutputStream getOutputStream() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getName() {
+ return "request";
+ }
+ }
+}
diff --git a/api/src/main/java/com/google/appengine/setup/ApiProxyDelegate.java b/api/src/main/java/com/google/appengine/setup/ApiProxyDelegate.java
index 07fc18880..588c137da 100644
--- a/api/src/main/java/com/google/appengine/setup/ApiProxyDelegate.java
+++ b/api/src/main/java/com/google/appengine/setup/ApiProxyDelegate.java
@@ -16,13 +16,15 @@
package com.google.appengine.setup;
-import com.google.common.collect.Lists;
import com.google.apphosting.api.ApiProxy;
import com.google.apphosting.api.ApiProxy.ApiConfig;
import com.google.apphosting.api.ApiProxy.ApiProxyException;
import com.google.apphosting.api.ApiProxy.LogRecord;
import com.google.apphosting.api.ApiProxy.RPCFailedException;
-import com.google.apphosting.utils.remoteapi.RemoteApiPb;
+import com.google.apphosting.base.protos.api.RemoteApiPb;
+import com.google.common.collect.Lists;
+import com.google.protobuf.ByteString;
+import com.google.protobuf.InvalidProtocolBufferException;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.util.List;
@@ -147,20 +149,22 @@ protected byte[] runSyncCall(LazyApiProxyEnvironment environment, String package
}
}
try (BufferedInputStream bis = new BufferedInputStream(response.getEntity().getContent())) {
- RemoteApiPb.Response remoteResponse = new RemoteApiPb.Response();
- if (!remoteResponse.parseFrom(bis)) {
- logger.info(
- "HTTP ApiProxy unable to parse response for " + packageName + "." + methodName);
+ RemoteApiPb.Response remoteResponse = RemoteApiPb.Response.getDefaultInstance();
+ try {
+ remoteResponse.getParserForType().parseFrom(bis);
+ } catch (InvalidProtocolBufferException e) {
+ logger.info(
+ "HTTP ApiProxy unable to parse response for " + packageName + "." + methodName);
throw new RPCFailedException(packageName, methodName);
}
if (remoteResponse.hasRpcError() || remoteResponse.hasApplicationError()) {
throw convertRemoteError(remoteResponse, packageName, methodName, logger);
}
- return remoteResponse.getResponseAsBytes();
+ return remoteResponse.getResponse().toByteArray();
}
} catch (IOException e) {
- logger.info(
- "HTTP ApiProxy I/O error for " + packageName + "." + methodName + ": " + e.getMessage());
+ logger.info(
+ "HTTP ApiProxy I/O error for " + packageName + "." + methodName + ": " + e.getMessage());
throw new RPCFailedException(packageName, methodName);
} finally {
request.releaseConnection();
@@ -179,12 +183,13 @@ protected byte[] runSyncCall(LazyApiProxyEnvironment environment, String package
*/
static HttpPost createRequest(LazyApiProxyEnvironment environment, String packageName,
String methodName, byte[] requestData, int timeoutMs) {
- RemoteApiPb.Request remoteRequest = new RemoteApiPb.Request();
+ RemoteApiPb.Request.Builder remoteRequest = RemoteApiPb.Request.newBuilder();
remoteRequest.setServiceName(packageName);
remoteRequest.setMethod(methodName);
- // Commenting below line to validate the use-cases where security ticket may be needed. So far we did not need.
- //remoteRequest.setRequestId(environment.getTicket());
- remoteRequest.setRequestAsBytes(requestData);
+ // Commenting below line to validate the use-cases where security ticket may be needed. So far
+ // we did not need.
+ // remoteRequest.setRequestId(environment.getTicket());
+ remoteRequest.setRequest(ByteString.copyFrom(requestData));
HttpPost request = new HttpPost("http://" + environment.getServer() + REQUEST_ENDPOINT);
request.setHeader(RPC_STUB_ID_HEADER, REQUEST_STUB_ID);
@@ -217,8 +222,9 @@ static HttpPost createRequest(LazyApiProxyEnvironment environment, String packag
ApiProxyEnvironment.AttributeMapping.DAPPER_ID.headerKey, (String) dapperHeader);
}
- ByteArrayEntity postPayload = new ByteArrayEntity(remoteRequest.toByteArray(),
- ContentType.APPLICATION_OCTET_STREAM);
+ ByteArrayEntity postPayload =
+ new ByteArrayEntity(
+ remoteRequest.getRequest().toByteArray(), ContentType.APPLICATION_OCTET_STREAM);
postPayload.setChunked(false);
request.setEntity(postPayload);
@@ -374,7 +380,7 @@ public List getRequestThreads(LazyApiProxyEnvironment environment) {
if (threadFactory != null && threadFactory instanceof RequestThreadFactory) {
return ((RequestThreadFactory) threadFactory).getRequestThreads();
}
- logger.warning("Got a call to getRequestThreads() but no VmRequestThreadFactory is available");
+ logger.warning("Got a call to getRequestThreads() but no VmRequestThreadFactory is available");
return Lists.newLinkedList();
}
diff --git a/api/src/main/java/com/google/appengine/spi/ServiceFactoryFactory.java b/api/src/main/java/com/google/appengine/spi/ServiceFactoryFactory.java
index cc842bdba..e087c8f2e 100644
--- a/api/src/main/java/com/google/appengine/spi/ServiceFactoryFactory.java
+++ b/api/src/main/java/com/google/appengine/spi/ServiceFactoryFactory.java
@@ -17,8 +17,6 @@
package com.google.appengine.spi;
import com.google.common.base.Preconditions;
-import java.security.AccessController;
-import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -137,44 +135,36 @@ private static final class RuntimeRegistry {
}
}
- /**
- * Retrieves the list of factory providers from the classpath
- */
+ /** Retrieves the list of factory providers from the classpath */
private static List> getProvidersUsingServiceLoader() {
- return AccessController.doPrivileged(
- new PrivilegedAction>>() {
- @Override
- public List> run() {
- List> result = new ArrayList>();
-
- // Sandboxed applications use the classloader of this class (ServiceFactoryFactory).
- // VM runtime applications use the thread context classloader (if not null) and fall
- // back to
- // the ServiceFactoryFactory classloader otherwise.
- //
- ClassLoader classLoader = null;
- if (Boolean.getBoolean(USE_THREAD_CONTEXT_CLASSLOADER_PROPERTY)) {
- classLoader = Thread.currentThread().getContextClassLoader();
- }
- if (classLoader == null) {
- // If the classloader isn't set (or set to null). Use the classloader of this class.
- classLoader = ServiceFactoryFactory.class.getClassLoader();
- }
-
- // Can't use parameterized types in ServiceLoader.load
- //
- @SuppressWarnings("rawtypes")
- ServiceLoader providers =
- ServiceLoader.load(FactoryProvider.class, classLoader);
-
- if (providers != null) {
- for (FactoryProvider> provider : providers) {
- result.add(provider);
- }
- }
-
- return result;
- }
- });
+ List> result = new ArrayList>();
+
+ // Sandboxed applications use the classloader of this class (ServiceFactoryFactory).
+ // VM runtime applications use the thread context classloader (if not null) and fall
+ // back to
+ // the ServiceFactoryFactory classloader otherwise.
+ //
+ ClassLoader classLoader = null;
+ if (Boolean.getBoolean(USE_THREAD_CONTEXT_CLASSLOADER_PROPERTY)) {
+ classLoader = Thread.currentThread().getContextClassLoader();
+ }
+ if (classLoader == null) {
+ // If the classloader isn't set (or set to null). Use the classloader of this class.
+ classLoader = ServiceFactoryFactory.class.getClassLoader();
+ }
+
+ // Can't use parameterized types in ServiceLoader.load
+ //
+ @SuppressWarnings("rawtypes")
+ ServiceLoader providers =
+ ServiceLoader.load(FactoryProvider.class, classLoader);
+
+ if (providers != null) {
+ for (FactoryProvider> provider : providers) {
+ result.add(provider);
+ }
+ }
+
+ return result;
}
}
diff --git a/api/src/main/java/com/google/appengine/tools/compilation/DatastoreCallbacksConfigWriter.java b/api/src/main/java/com/google/appengine/tools/compilation/DatastoreCallbacksConfigWriter.java
index 3b90e586e..f2313fc2a 100644
--- a/api/src/main/java/com/google/appengine/tools/compilation/DatastoreCallbacksConfigWriter.java
+++ b/api/src/main/java/com/google/appengine/tools/compilation/DatastoreCallbacksConfigWriter.java
@@ -35,7 +35,7 @@
import java.util.Map;
import java.util.Properties;
import java.util.Set;
-import org.checkerframework.checker.nullness.qual.Nullable;
+import org.jspecify.annotations.Nullable;
/**
* Helper that keeps track of the callbacks we encounter and writes them out in
diff --git a/api/src/main/java/com/google/apphosting/utils/remoteapi/EE10RemoteApiServlet.java b/api/src/main/java/com/google/apphosting/utils/remoteapi/EE10RemoteApiServlet.java
index 2e7282d06..04f2cdb1c 100644
--- a/api/src/main/java/com/google/apphosting/utils/remoteapi/EE10RemoteApiServlet.java
+++ b/api/src/main/java/com/google/apphosting/utils/remoteapi/EE10RemoteApiServlet.java
@@ -13,495 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package com.google.apphosting.utils.remoteapi;
-import static com.google.apphosting.datastore.DatastoreV3Pb.Error.ErrorCode.BAD_REQUEST;
-import static com.google.apphosting.datastore.DatastoreV3Pb.Error.ErrorCode.CONCURRENT_TRANSACTION;
-
-import com.google.appengine.api.oauth.OAuthRequestException;
-import com.google.appengine.api.oauth.OAuthService;
-import com.google.appengine.api.oauth.OAuthServiceFactory;
-import com.google.appengine.api.users.UserService;
-import com.google.appengine.api.users.UserServiceFactory;
-import com.google.apphosting.api.ApiProxy;
-import com.google.apphosting.datastore.DatastoreV3Pb.BeginTransactionRequest;
-import com.google.apphosting.datastore.DatastoreV3Pb.DeleteRequest;
-import com.google.apphosting.datastore.DatastoreV3Pb.GetRequest;
-import com.google.apphosting.datastore.DatastoreV3Pb.GetResponse;
-import com.google.apphosting.datastore.DatastoreV3Pb.NextRequest;
-import com.google.apphosting.datastore.DatastoreV3Pb.PutRequest;
-import com.google.apphosting.datastore.DatastoreV3Pb.Query;
-import com.google.apphosting.datastore.DatastoreV3Pb.QueryResult;
-import com.google.apphosting.utils.remoteapi.RemoteApiPb.ApplicationError;
-import com.google.apphosting.utils.remoteapi.RemoteApiPb.Request;
-import com.google.apphosting.utils.remoteapi.RemoteApiPb.Response;
-import com.google.apphosting.utils.remoteapi.RemoteApiPb.TransactionQueryResult;
-import com.google.apphosting.utils.remoteapi.RemoteApiPb.TransactionRequest;
-import com.google.apphosting.utils.remoteapi.RemoteApiPb.TransactionRequest.Precondition;
-import com.google.io.protocol.ProtocolMessage;
-//
-import com.google.storage.onestore.v3.OnestoreEntity;
-import com.google.storage.onestore.v3.OnestoreEntity.EntityProto;
-import com.google.storage.onestore.v3.OnestoreEntity.Path.Element;
-import jakarta.servlet.http.HttpServlet;
-import jakarta.servlet.http.HttpServletRequest;
-import jakarta.servlet.http.HttpServletResponse;
-import java.io.ByteArrayOutputStream;
-import java.io.InputStream;
-import java.io.ObjectOutput;
-import java.io.ObjectOutputStream;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.List;
-import java.util.logging.Logger;
-
-/** Remote API servlet handler. */
-public class EE10RemoteApiServlet extends HttpServlet {
- private static final Logger log = Logger.getLogger(EE10RemoteApiServlet.class.getName());
-
- private static final String[] OAUTH_SCOPES = new String[] {
- "https://www.googleapis.com/auth/appengine.apis",
- "https://www.googleapis.com/auth/cloud-platform",
- };
- private static final String INBOUND_APP_SYSTEM_PROPERTY = "HTTP_X_APPENGINE_INBOUND_APPID";
- private static final String INBOUND_APP_HEADER_NAME = "X-AppEngine-Inbound-AppId";
-
- private HashSet allowedApps = null;
- private final OAuthService oauthService;
-
- public EE10RemoteApiServlet() {
- this(OAuthServiceFactory.getOAuthService());
- }
-
- // @VisibleForTesting
- EE10RemoteApiServlet(OAuthService oauthService) {
- this.oauthService = oauthService;
- }
-
- /** Exception for unknown errors from a Python remote_api handler. */
- public static class UnknownPythonServerException extends RuntimeException {
- public UnknownPythonServerException(String message) {
- super(message);
- }
- }
-
- /**
- * Checks if the inbound request is valid.
- *
- * @param req the {@link HttpServletRequest}
- * @param res the {@link HttpServletResponse}
- * @return true if the application is known.
- * @throws java.io.IOException
- */
- boolean checkIsValidRequest(HttpServletRequest req, HttpServletResponse res)
- throws java.io.IOException {
- if (!checkIsKnownInbound(req) && !checkIsAdmin(req, res)) {
- return false;
- }
- return checkIsValidHeader(req, res);
- }
-
- /**
- * Checks if the request is coming from a known application.
- *
- * @param req the {@link HttpServletRequest}
- * @return true if the application is known.
- * @throws java.io.IOException
- */
- private synchronized boolean checkIsKnownInbound(HttpServletRequest req)
- throws java.io.IOException {
- if (allowedApps == null) {
- allowedApps = new HashSet();
- String allowedAppsStr = System.getProperty(INBOUND_APP_SYSTEM_PROPERTY);
- if (allowedAppsStr != null) {
- String[] apps = allowedAppsStr.split(",");
- for (String app : apps) {
- allowedApps.add(app);
- }
- }
- }
- String inboundAppId = req.getHeader(INBOUND_APP_HEADER_NAME);
- return inboundAppId != null && allowedApps.contains(inboundAppId);
- }
-
- /**
- * Checks for the api-version header to prevent XSRF
- *
- * @param req the {@link HttpServletRequest}
- * @param res the {@link HttpServletResponse}
- * @return true if the header exists.
- * @throws java.io.IOException
- */
- private boolean checkIsValidHeader(HttpServletRequest req, HttpServletResponse res)
- throws java.io.IOException {
- if (req.getHeader("X-appcfg-api-version") == null) {
- res.setStatus(403);
- res.setContentType("text/plain");
- res.getWriter().println("This request did not contain a necessary header");
- return false;
- }
- return true;
- }
-
- /**
- * Check that the current user is signed is with admin access.
- *
- * @return true if the current user is logged in with admin access, false
- * otherwise.
- */
- private boolean checkIsAdmin(HttpServletRequest req, HttpServletResponse res)
- throws java.io.IOException {
- UserService userService = UserServiceFactory.getUserService();
-
- // Check for regular (cookie-based) authentication.
- if (userService.getCurrentUser() != null) {
- if (userService.isUserAdmin()) {
- return true;
- } else {
- respondNotAdmin(res);
- return false;
- }
- }
-
- // Check for OAuth-based authentication.
- try {
- if (oauthService.isUserAdmin(OAUTH_SCOPES)) {
- return true;
- } else {
- respondNotAdmin(res);
- return false;
- }
- } catch (OAuthRequestException e) {
- // Invalid OAuth request; fall through to sending redirect.
- }
-
- res.sendRedirect(userService.createLoginURL(req.getRequestURI()));
- return false;
- }
-
- private void respondNotAdmin(HttpServletResponse res) throws java.io.IOException {
- res.setStatus(401);
- res.setContentType("text/plain");
- res.getWriter().println(
- "You must be logged in as an administrator, or access from an approved application.");
- }
-
- /**
- * Serve GET requests with a YAML encoding of the app-id and a validation
- * token.
- */
- @Override
- public void doGet(HttpServletRequest req, HttpServletResponse res) throws java.io.IOException {
- if (!checkIsValidRequest(req, res)) {
- return;
- }
- res.setContentType("text/plain");
- String appId = ApiProxy.getCurrentEnvironment().getAppId();
- StringBuilder outYaml =
- new StringBuilder().append("{rtok: ").append(req.getParameter("rtok")).append(", app_id: ")
- .append(appId).append("}");
- res.getWriter().println(outYaml);
- }
-
- /**
- * Serve POST requests by forwarding calls to ApiProxy.
- */
- @Override
- public void doPost(HttpServletRequest req, HttpServletResponse res) throws java.io.IOException {
- if (!checkIsValidRequest(req, res)) {
- return;
- }
- res.setContentType("application/octet-stream");
-
- Response response = new Response();
-
- try {
- byte[] responseData = executeRequest(req);
- response.setResponseAsBytes(responseData);
- res.setStatus(200);
- } catch (Exception e) {
- log.warning("Caught exception while executing remote_api command:\n" + e);
- res.setStatus(200);
- ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
- ObjectOutput out = new ObjectOutputStream(byteStream);
- out.writeObject(e);
- out.close();
- byte[] serializedException = byteStream.toByteArray();
- response.setJavaExceptionAsBytes(serializedException);
- if (e instanceof ApiProxy.ApplicationException) {
- ApiProxy.ApplicationException ae = (ApiProxy.ApplicationException) e;
- ApplicationError appError = response.getMutableApplicationError();
- appError.setCode(ae.getApplicationError());
- appError.setDetail(ae.getErrorDetail());
- }
- }
- res.getOutputStream().write(response.toByteArray());
- }
-
- private byte[] executeRunQuery(Request request) {
- Query queryRequest = new Query();
- parseFromBytes(queryRequest, request.getRequestAsBytes());
- int batchSize = Math.max(1000, queryRequest.getLimit());
- queryRequest.setCount(batchSize);
-
- QueryResult runQueryResponse = new QueryResult();
- byte[] res = ApiProxy.makeSyncCall("datastore_v3", "RunQuery", request.getRequestAsBytes());
- parseFromBytes(runQueryResponse, res);
-
- if (queryRequest.hasLimit()) {
- // Try to pull all results
- while (runQueryResponse.isMoreResults()) {
- NextRequest nextRequest = new NextRequest();
- nextRequest.getMutableCursor().mergeFrom(runQueryResponse.getCursor());
- nextRequest.setCount(batchSize);
- byte[] nextRes = ApiProxy.makeSyncCall("datastore_v3", "Next", nextRequest.toByteArray());
- parseFromBytes(runQueryResponse, nextRes);
- }
- }
- return runQueryResponse.toByteArray();
- }
-
- private byte[] executeTxQuery(Request request) {
- TransactionQueryResult result = new TransactionQueryResult();
-
- Query query = new Query();
- parseFromBytes(query, request.getRequestAsBytes());
-
- if (!query.hasAncestor()) {
- throw new ApiProxy.ApplicationException(BAD_REQUEST.getValue(),
- "No ancestor in transactional query.");
- }
- // Make __entity_group__ key
- OnestoreEntity.Reference egKey =
- result.getMutableEntityGroupKey().mergeFrom(query.getAncestor());
- OnestoreEntity.Path.Element root = egKey.getPath().getElement(0);
- egKey.getMutablePath().clearElement().addElement(root);
- OnestoreEntity.Path.Element egElement = new OnestoreEntity.Path.Element();
- egElement.setType("__entity_group__").setId(1);
- egKey.getMutablePath().addElement(egElement);
-
- // And then perform the transaction with the ancestor query and __entity_group__ fetch.
- byte[] tx = beginTransaction(false);
- parseFromBytes(query.getMutableTransaction(), tx);
- byte[] queryBytes = ApiProxy.makeSyncCall("datastore_v3", "RunQuery", query.toByteArray());
- parseFromBytes(result.getMutableResult(), queryBytes);
-
- GetRequest egRequest = new GetRequest();
- egRequest.addKey(egKey);
- GetResponse egResponse = txGet(tx, egRequest);
- if (egResponse.getEntity(0).hasEntity()) {
- result.setEntityGroup(egResponse.getEntity(0).getEntity());
- }
- rollback(tx);
-
- return result.toByteArray();
- }
-
- /**
- * Throws a CONCURRENT_TRANSACTION exception if the entity does not match the precondition.
- */
- private void assertEntityResultMatchesPrecondition(
- GetResponse.Entity entityResult, Precondition precondition) {
- // This handles the case where the Entity was missing in one of the two params.
- if (precondition.hasHash() != entityResult.hasEntity()) {
- throw new ApiProxy.ApplicationException(CONCURRENT_TRANSACTION.getValue(),
- "Transaction precondition failed");
- }
-
- if (entityResult.hasEntity()) {
- // Both params have an Entity. Make sure the Entities match using a SHA-1 hash.
- EntityProto entity = entityResult.getEntity();
- if (Arrays.equals(precondition.getHashAsBytes(), computeSha1(entity))) {
- // They match. We're done.
- return;
- }
-
- // See javadoc of computeSha1OmittingLastByteForBackwardsCompatibility for explanation.
- byte[] backwardsCompatibleHash = computeSha1OmittingLastByteForBackwardsCompatibility(entity);
- if (!Arrays.equals(precondition.getHashAsBytes(), backwardsCompatibleHash)) {
- throw new ApiProxy.ApplicationException(
- CONCURRENT_TRANSACTION.getValue(), "Transaction precondition failed");
- }
- }
- // Else, the Entity was missing from both.
- }
-
- private byte[] executeTx(Request request) {
- TransactionRequest txRequest = new TransactionRequest();
- parseFromBytes(txRequest, request.getRequestAsBytes());
-
- byte[] tx = beginTransaction(txRequest.isAllowMultipleEg());
-
- List preconditions = txRequest.preconditions();
-
- // Check transaction preconditions
- if (!preconditions.isEmpty()) {
- GetRequest getRequest = new GetRequest();
- for (Precondition precondition : preconditions) {
- OnestoreEntity.Reference key = precondition.getKey();
- OnestoreEntity.Reference requestKey = getRequest.addKey();
- requestKey.mergeFrom(key);
- }
-
- GetResponse getResponse = txGet(tx, getRequest);
- List entities = getResponse.entitys();
-
- // Note that this is guaranteed because we don't specify allow_deferred on the GetRequest.
- // TODO: Consider supporting deferred gets here.
- assert (entities.size() == preconditions.size());
- for (int i = 0; i < entities.size(); i++) {
- // Throw an exception if any of the Entities don't match the Precondition specification.
- assertEntityResultMatchesPrecondition(entities.get(i), preconditions.get(i));
- }
- }
- // Preconditions OK.
- // Perform puts.
- byte[] res = new byte[0]; // a serialized VoidProto
- if (txRequest.hasPuts()) {
- PutRequest putRequest = txRequest.getPuts();
- parseFromBytes(putRequest.getMutableTransaction(), tx);
- res = ApiProxy.makeSyncCall("datastore_v3", "Put", putRequest.toByteArray());
- }
- // Perform deletes.
- if (txRequest.hasDeletes()) {
- DeleteRequest deleteRequest = txRequest.getDeletes();
- parseFromBytes(deleteRequest.getMutableTransaction(), tx);
- ApiProxy.makeSyncCall("datastore_v3", "Delete", deleteRequest.toByteArray());
- }
- // Commit transaction.
- ApiProxy.makeSyncCall("datastore_v3", "Commit", tx);
- return res;
- }
-
- private byte[] executeGetIDs(Request request, boolean isXG) {
- PutRequest putRequest = new PutRequest();
- parseFromBytes(putRequest, request.getRequestAsBytes());
- for (EntityProto entity : putRequest.entitys()) {
- assert (entity.propertySize() == 0);
- assert (entity.rawPropertySize() == 0);
- assert (entity.getEntityGroup().elementSize() == 0);
- List elementList = entity.getKey().getPath().elements();
- Element lastPart = elementList.get(elementList.size() - 1);
- assert (lastPart.getId() == 0);
- assert (!lastPart.hasName());
- }
-
- // Start a Transaction.
-
- // TODO: Shouldn't this use allocateIds instead?
- byte[] tx = beginTransaction(isXG);
- parseFromBytes(putRequest.getMutableTransaction(), tx);
-
- // Make a put request for a bunch of empty entities with the requisite
- // paths.
- byte[] res = ApiProxy.makeSyncCall("datastore_v3", "Put", putRequest.toByteArray());
-
- // Roll back the transaction so we don't actually insert anything.
- rollback(tx);
- return res;
- }
-
- private byte[] executeRequest(HttpServletRequest req) throws java.io.IOException {
- Request request = new Request();
- parseFromInputStream(request, req.getInputStream());
- String service = request.getServiceName();
- String method = request.getMethod();
-
- log.fine("remote API call: " + service + ", " + method);
-
- if (service.equals("remote_datastore")) {
- if (method.equals("RunQuery")) {
- return executeRunQuery(request);
- } else if (method.equals("Transaction")) {
- return executeTx(request);
- } else if (method.equals("TransactionQuery")) {
- return executeTxQuery(request);
- } else if (method.equals("GetIDs")) {
- return executeGetIDs(request, false);
- } else if (method.equals("GetIDsXG")) {
- return executeGetIDs(request, true);
- } else {
- throw new ApiProxy.CallNotFoundException(service, method);
- }
- } else {
- return ApiProxy.makeSyncCall(service, method, request.getRequestAsBytes());
- }
- }
-
- // Datastore utility functions.
-
- private static byte[] beginTransaction(boolean allowMultipleEg) {
- String appId = ApiProxy.getCurrentEnvironment().getAppId();
- byte[] req = new BeginTransactionRequest().setApp(appId)
- .setAllowMultipleEg(allowMultipleEg).toByteArray();
- return ApiProxy.makeSyncCall("datastore_v3", "BeginTransaction", req);
- }
-
- private static void rollback(byte[] tx) {
- ApiProxy.makeSyncCall("datastore_v3", "Rollback", tx);
- }
-
- private static GetResponse txGet(byte[] tx, GetRequest request) {
- parseFromBytes(request.getMutableTransaction(), tx);
- GetResponse response = new GetResponse();
- byte[] resultBytes = ApiProxy.makeSyncCall("datastore_v3", "Get", request.toByteArray());
- parseFromBytes(response, resultBytes);
- return response;
- }
-
- // @VisibleForTesting
- static byte[] computeSha1(EntityProto entity) {
- byte[] entityBytes = entity.toByteArray();
- return computeSha1(entityBytes, entityBytes.length);
- }
-
- /**
- * This is a HACK. There used to be a bug in RemoteDatastore.java in that it would omit the last
- * byte of the Entity when calculating the hash for the Precondition. If an app has not updated
- * that library, we may still receive hashes like this. For backwards compatibility, we'll
- * consider the transaction valid if omitting the last byte of the Entity matches the
- * Precondition.
- */
- // @VisibleForTesting
- static byte[] computeSha1OmittingLastByteForBackwardsCompatibility(EntityProto entity) {
- byte[] entityBytes = entity.toByteArray();
- return computeSha1(entityBytes, entityBytes.length - 1);
- }
-
- //
- private static byte[] computeSha1(byte[] bytes, int length) {
- MessageDigest md;
- try {
- md = MessageDigest.getInstance("SHA-1");
- } catch (NoSuchAlgorithmException e) {
- throw new ApiProxy.ApplicationException(
- CONCURRENT_TRANSACTION.getValue(), "Transaction precondition could not be computed");
- }
-
- md.update(bytes, 0, length);
- return md.digest();
- }
-
- private static void parseFromBytes(ProtocolMessage> message, byte[] bytes) {
- boolean parsed = message.parseFrom(bytes);
- checkParse(message, parsed);
- }
-
- private static void parseFromInputStream(ProtocolMessage> message, InputStream inputStream) {
- boolean parsed = message.parseFrom(inputStream);
- checkParse(message, parsed);
- }
-
- private static void checkParse(ProtocolMessage> message, boolean parsed) {
- if (!parsed) {
- throw new ApiProxy.ApiProxyException("Could not parse protobuf");
- }
- String error = message.findInitializationError();
- if (error != null) {
- throw new ApiProxy.ApiProxyException("Could not parse protobuf: " + error);
- }
- }
-}
+/**
+ * @deprecated as of version 3.0.0, use {@link JakartaRemoteApiServlet} instead.
+ */
+@Deprecated(since = "3.0.0")
+public class EE10RemoteApiServlet extends JakartaRemoteApiServlet {}
diff --git a/api/src/main/java/com/google/apphosting/utils/remoteapi/JakartaRemoteApiServlet.java b/api/src/main/java/com/google/apphosting/utils/remoteapi/JakartaRemoteApiServlet.java
new file mode 100644
index 000000000..536d32a0e
--- /dev/null
+++ b/api/src/main/java/com/google/apphosting/utils/remoteapi/JakartaRemoteApiServlet.java
@@ -0,0 +1,499 @@
+
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.apphosting.utils.remoteapi;
+
+import static com.google.apphosting.datastore.proto2api.DatastoreV3Pb.Error.ErrorCode.BAD_REQUEST;
+import static com.google.apphosting.datastore.proto2api.DatastoreV3Pb.Error.ErrorCode.CONCURRENT_TRANSACTION;
+import static com.google.common.base.Verify.verify;
+
+import com.google.appengine.api.oauth.OAuthRequestException;
+import com.google.appengine.api.oauth.OAuthService;
+import com.google.appengine.api.oauth.OAuthServiceFactory;
+import com.google.appengine.api.users.UserService;
+import com.google.appengine.api.users.UserServiceFactory;
+import com.google.apphosting.api.ApiProxy;
+import com.google.apphosting.base.protos.api.RemoteApiPb.Request;
+import com.google.apphosting.base.protos.api.RemoteApiPb.Response;
+import com.google.apphosting.base.protos.api.RemoteApiPb.TransactionQueryResult;
+import com.google.apphosting.base.protos.api.RemoteApiPb.TransactionRequest;
+import com.google.apphosting.base.protos.api.RemoteApiPb.TransactionRequest.Precondition;
+import com.google.apphosting.datastore.proto2api.DatastoreV3Pb.BeginTransactionRequest;
+import com.google.apphosting.datastore.proto2api.DatastoreV3Pb.DeleteRequest;
+import com.google.apphosting.datastore.proto2api.DatastoreV3Pb.GetRequest;
+import com.google.apphosting.datastore.proto2api.DatastoreV3Pb.GetResponse;
+import com.google.apphosting.datastore.proto2api.DatastoreV3Pb.NextRequest;
+import com.google.apphosting.datastore.proto2api.DatastoreV3Pb.PutRequest;
+import com.google.apphosting.datastore.proto2api.DatastoreV3Pb.Query;
+import com.google.apphosting.datastore.proto2api.DatastoreV3Pb.QueryResult;
+import com.google.protobuf.ByteString;
+import com.google.protobuf.ExtensionRegistry;
+import com.google.protobuf.Message;
+//
+import com.google.storage.onestore.v3.proto2api.OnestoreEntity;
+import com.google.storage.onestore.v3.proto2api.OnestoreEntity.EntityProto;
+import com.google.storage.onestore.v3.proto2api.OnestoreEntity.Path.Element;
+import jakarta.servlet.http.HttpServlet;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectOutput;
+import java.io.ObjectOutputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.logging.Logger;
+
+/**
+ * Remote API servlet handler for Jarkata EE APIs.
+ *
+ */
+public class JakartaRemoteApiServlet extends HttpServlet {
+ private static final Logger log = Logger.getLogger(JakartaRemoteApiServlet.class.getName());
+
+ private static final String[] OAUTH_SCOPES = new String[] {
+ "https://www.googleapis.com/auth/appengine.apis",
+ "https://www.googleapis.com/auth/cloud-platform",
+ };
+ private static final String INBOUND_APP_SYSTEM_PROPERTY = "HTTP_X_APPENGINE_INBOUND_APPID";
+ private static final String INBOUND_APP_HEADER_NAME = "X-AppEngine-Inbound-AppId";
+
+ private HashSet allowedApps = null;
+ private final OAuthService oauthService;
+
+ public JakartaRemoteApiServlet() {
+ this(OAuthServiceFactory.getOAuthService());
+ }
+
+ // @VisibleForTesting
+ JakartaRemoteApiServlet(OAuthService oauthService) {
+ this.oauthService = oauthService;
+ }
+
+ /** Exception for unknown errors from a Python remote_api handler. */
+ public static class UnknownPythonServerException extends RuntimeException {
+ public UnknownPythonServerException(String message) {
+ super(message);
+ }
+ }
+
+ /**
+ * Checks if the inbound request is valid.
+ *
+ * @param req the {@link HttpServletRequest}
+ * @param res the {@link HttpServletResponse}
+ * @return true if the application is known.
+ */
+ boolean checkIsValidRequest(HttpServletRequest req, HttpServletResponse res) throws IOException {
+ if (!checkIsKnownInbound(req) && !checkIsAdmin(req, res)) {
+ return false;
+ }
+ return checkIsValidHeader(req, res);
+ }
+
+ /**
+ * Checks if the request is coming from a known application.
+ *
+ * @param req the {@link HttpServletRequest}
+ * @return true if the application is known.
+ */
+ private synchronized boolean checkIsKnownInbound(HttpServletRequest req) {
+ if (allowedApps == null) {
+ allowedApps = new HashSet();
+ String allowedAppsStr = System.getProperty(INBOUND_APP_SYSTEM_PROPERTY);
+ if (allowedAppsStr != null) {
+ String[] apps = allowedAppsStr.split(",");
+ for (String app : apps) {
+ allowedApps.add(app);
+ }
+ }
+ }
+ String inboundAppId = req.getHeader(INBOUND_APP_HEADER_NAME);
+ return inboundAppId != null && allowedApps.contains(inboundAppId);
+ }
+
+ /**
+ * Checks for the api-version header to prevent XSRF
+ *
+ * @param req the {@link HttpServletRequest}
+ * @param res the {@link HttpServletResponse}
+ * @return true if the header exists.
+ */
+ private boolean checkIsValidHeader(HttpServletRequest req, HttpServletResponse res)
+ throws IOException {
+ if (req.getHeader("X-appcfg-api-version") == null) {
+ res.setStatus(403);
+ res.setContentType("text/plain");
+ res.getWriter().println("This request did not contain a necessary header");
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Check that the current user is signed is with admin access.
+ *
+ * @return true if the current user is logged in with admin access, false otherwise.
+ */
+ private boolean checkIsAdmin(HttpServletRequest req, HttpServletResponse res) throws IOException {
+ UserService userService = UserServiceFactory.getUserService();
+
+ // Check for regular (cookie-based) authentication.
+ if (userService.getCurrentUser() != null) {
+ if (userService.isUserAdmin()) {
+ return true;
+ } else {
+ respondNotAdmin(res);
+ return false;
+ }
+ }
+
+ // Check for OAuth-based authentication.
+ try {
+ if (oauthService.isUserAdmin(OAUTH_SCOPES)) {
+ return true;
+ } else {
+ respondNotAdmin(res);
+ return false;
+ }
+ } catch (OAuthRequestException e) {
+ // Invalid OAuth request; fall through to sending redirect.
+ }
+
+ res.sendRedirect(userService.createLoginURL(req.getRequestURI()));
+ return false;
+ }
+
+ private void respondNotAdmin(HttpServletResponse res) throws IOException {
+ res.setStatus(401);
+ res.setContentType("text/plain");
+ res.getWriter().println(
+ "You must be logged in as an administrator, or access from an approved application.");
+ }
+
+ /** Serve GET requests with a YAML encoding of the app-id and a validation token. */
+ @Override
+ public void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
+ if (!checkIsValidRequest(req, res)) {
+ return;
+ }
+ res.setContentType("text/plain");
+ String appId = ApiProxy.getCurrentEnvironment().getAppId();
+ StringBuilder outYaml =
+ new StringBuilder().append("{rtok: ").append(req.getParameter("rtok")).append(", app_id: ")
+ .append(appId).append("}");
+ res.getWriter().println(outYaml);
+ }
+
+ /** Serve POST requests by forwarding calls to ApiProxy. */
+ @Override
+ public void doPost(HttpServletRequest req, HttpServletResponse res) throws IOException {
+ if (!checkIsValidRequest(req, res)) {
+ return;
+ }
+ res.setContentType("application/octet-stream");
+ Response.Builder response = Response.newBuilder();
+ try {
+ byte[] responseData = executeRequest(req);
+ response.setResponse(ByteString.copyFrom(responseData));
+ res.setStatus(200);
+ } catch (Exception e) {
+ log.warning("Caught exception while executing remote_api command:\n" + e);
+ res.setStatus(200);
+ ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
+ ObjectOutput out = new ObjectOutputStream(byteStream);
+ out.writeObject(e);
+ out.close();
+ byte[] serializedException = byteStream.toByteArray();
+ response.setJavaException(ByteString.copyFrom(serializedException));
+ if (e instanceof ApiProxy.ApplicationException) {
+ ApiProxy.ApplicationException ae = (ApiProxy.ApplicationException) e;
+ response
+ .getApplicationErrorBuilder()
+ .setCode(ae.getApplicationError())
+ .setDetail(ae.getErrorDetail());
+ }
+ }
+ response.build().writeTo(res.getOutputStream());
+ }
+
+ private byte[] executeRunQuery(Request.Builder request) {
+ Query.Builder queryRequest = Query.newBuilder();
+ parseFromBytes(queryRequest, request.getRequestIdBytes().toByteArray());
+ int batchSize = Math.max(1000, queryRequest.getLimit());
+ queryRequest.setCount(batchSize);
+ QueryResult.Builder runQueryResponse = QueryResult.newBuilder();
+ byte[] res =
+ ApiProxy.makeSyncCall("datastore_v3", "RunQuery", request.getRequest().toByteArray());
+ parseFromBytes(runQueryResponse, res);
+ if (queryRequest.hasLimit()) {
+ // Try to pull all results
+ while (runQueryResponse.getMoreResults()) {
+ NextRequest.Builder nextRequest = NextRequest.newBuilder();
+ nextRequest.getCursorBuilder().mergeFrom(runQueryResponse.getCursor());
+ nextRequest.setCount(batchSize);
+ byte[] nextRes =
+ ApiProxy.makeSyncCall("datastore_v3", "Next", nextRequest.build().toByteArray());
+ parseFromBytes(runQueryResponse, nextRes);
+ }
+ }
+ return runQueryResponse.build().toByteArray();
+ }
+
+ private byte[] executeTxQuery(Request.Builder request) {
+ TransactionQueryResult.Builder result = TransactionQueryResult.newBuilder();
+ Query.Builder query = Query.newBuilder();
+ parseFromBytes(query, request.getRequest().toByteArray());
+ if (!query.hasAncestor()) {
+ throw new ApiProxy.ApplicationException(
+ BAD_REQUEST.getNumber(), "No ancestor in transactional query.");
+ }
+ // Make __entity_group__ key
+ OnestoreEntity.Reference.Builder egKey =
+ result.getEntityGroupKeyBuilder().mergeFrom(query.getAncestor());
+ OnestoreEntity.Path.Element root = egKey.getPath().getElement(0);
+ egKey.getPathBuilder().clearElement().addElement(root);
+ Element egElement =
+ OnestoreEntity.Path.Element.newBuilder().setType("__entity_group__").setId(1).build();
+ egKey.getPathBuilder().addElement(egElement);
+ // And then perform the transaction with the ancestor query and __entity_group__ fetch.
+ byte[] tx = beginTransaction(false);
+ parseFromBytes(query.getTransactionBuilder(), tx);
+ byte[] queryBytes =
+ ApiProxy.makeSyncCall("datastore_v3", "RunQuery", query.build().toByteArray());
+ parseFromBytes(result.getResultBuilder(), queryBytes);
+ GetRequest.Builder egRequest = GetRequest.newBuilder();
+ egRequest.addKey(egKey);
+ GetResponse.Builder egResponse = txGet(tx, egRequest);
+ if (egResponse.getEntity(0).hasEntity()) {
+ result.setEntityGroup(egResponse.getEntity(0).getEntity());
+ }
+ rollback(tx);
+ return result.build().toByteArray();
+ }
+
+ /**
+ * Throws a CONCURRENT_TRANSACTION exception if the entity does not match the precondition.
+ */
+ private void assertEntityResultMatchesPrecondition(
+ GetResponse.Entity entityResult, Precondition precondition) {
+ // This handles the case where the Entity was missing in one of the two params.
+ if (precondition.hasHash() != entityResult.hasEntity()) {
+ throw new ApiProxy.ApplicationException(
+ CONCURRENT_TRANSACTION.getNumber(), "Transaction precondition failed");
+ }
+ if (entityResult.hasEntity()) {
+ // Both params have an Entity. Make sure the Entities match using a SHA-1 hash.
+ EntityProto entity = entityResult.getEntity();
+ if (Arrays.equals(precondition.getHashBytes().toByteArray(), computeSha1(entity))) {
+ // They match. We're done.
+ return;
+ }
+ // See javadoc of computeSha1OmittingLastByteForBackwardsCompatibility for explanation.
+ byte[] backwardsCompatibleHash = computeSha1OmittingLastByteForBackwardsCompatibility(entity);
+ if (!Arrays.equals(precondition.getHashBytes().toByteArray(), backwardsCompatibleHash)) {
+ throw new ApiProxy.ApplicationException(
+ CONCURRENT_TRANSACTION.getNumber(), "Transaction precondition failed");
+ }
+ }
+ // Else, the Entity was missing from both.
+ }
+
+ private byte[] executeTx(Request.Builder request) {
+ TransactionRequest.Builder txRequest = TransactionRequest.newBuilder();
+ parseFromBytes(txRequest, request.getRequest().toByteArray());
+ byte[] tx = beginTransaction(txRequest.getAllowMultipleEg());
+ List preconditions = txRequest.getPreconditionList();
+ // Check transaction preconditions
+ if (!preconditions.isEmpty()) {
+ GetRequest.Builder getRequest = GetRequest.newBuilder();
+ for (Precondition precondition : preconditions) {
+ OnestoreEntity.Reference key = precondition.getKey();
+ getRequest.addKeyBuilder().mergeFrom(key);
+ }
+ GetResponse.Builder getResponse = txGet(tx, getRequest);
+ List entities = getResponse.getEntityList();
+ // Note that this is guaranteed because we don't specify allow_deferred on the GetRequest.
+ // TODO: Consider supporting deferred gets here.
+ assert (entities.size() == preconditions.size());
+ for (int i = 0; i < entities.size(); i++) {
+ // Throw an exception if any of the Entities don't match the Precondition specification.
+ assertEntityResultMatchesPrecondition(entities.get(i), preconditions.get(i));
+ }
+ }
+ // Preconditions OK.
+ // Perform puts.
+ byte[] res = new byte[0]; // a serialized VoidProto
+ if (txRequest.hasPuts()) {
+ PutRequest.Builder putRequest = txRequest.getPutsBuilder();
+ parseFromBytes(putRequest.getTransactionBuilder(), tx);
+ res = ApiProxy.makeSyncCall("datastore_v3", "Put", putRequest.build().toByteArray());
+ }
+ // Perform deletes.
+ if (txRequest.hasDeletes()) {
+ DeleteRequest.Builder deleteRequest = txRequest.getDeletesBuilder();
+ parseFromBytes(deleteRequest.getTransactionBuilder(), tx);
+ ApiProxy.makeSyncCall("datastore_v3", "Delete", deleteRequest.build().toByteArray());
+ }
+ // Commit transaction.
+ ApiProxy.makeSyncCall("datastore_v3", "Commit", tx);
+ return res;
+ }
+
+ private byte[] executeGetIDs(Request.Builder request, boolean isXg) {
+ PutRequest.Builder putRequest = PutRequest.newBuilder();
+ parseFromBytes(putRequest, request.getRequest().toByteArray());
+ for (EntityProto entity : putRequest.getEntityList()) {
+ verify(entity.getPropertyCount() == 0);
+ verify(entity.getRawPropertyCount() == 0);
+ verify(entity.getEntityGroup().getElementCount() == 0);
+ List elementList = entity.getKey().getPath().getElementList();
+ Element lastPart = elementList.get(elementList.size() - 1);
+ verify(lastPart.getId() == 0);
+ verify(!lastPart.hasName());
+ }
+ // Start a Transaction.
+ // TODO: Shouldn't this use allocateIds instead?
+ byte[] tx = beginTransaction(isXg);
+ parseFromBytes(putRequest.getTransactionBuilder(), tx);
+ // Make a put request for a bunch of empty entities with the requisite
+ // paths.
+ byte[] res = ApiProxy.makeSyncCall("datastore_v3", "Put", putRequest.build().toByteArray());
+ // Roll back the transaction so we don't actually insert anything.
+ rollback(tx);
+ return res;
+ }
+
+ private byte[] executeRequest(HttpServletRequest req) throws IOException {
+ Request.Builder request = Request.newBuilder();
+ parseFromInputStream(request, req.getInputStream());
+ String service = request.getServiceName();
+ String method = request.getMethod();
+
+ log.fine("remote API call: " + service + ", " + method);
+
+ if (service.equals("remote_datastore")) {
+ if (method.equals("RunQuery")) {
+ return executeRunQuery(request);
+ } else if (method.equals("Transaction")) {
+ return executeTx(request);
+ } else if (method.equals("TransactionQuery")) {
+ return executeTxQuery(request);
+ } else if (method.equals("GetIDs")) {
+ return executeGetIDs(request, false);
+ } else if (method.equals("GetIDsXG")) {
+ return executeGetIDs(request, true);
+ } else {
+ throw new ApiProxy.CallNotFoundException(service, method);
+ }
+ } else {
+ return ApiProxy.makeSyncCall(service, method, request.getRequest().toByteArray());
+ }
+ }
+
+ // Datastore utility functions.
+
+ private static byte[] beginTransaction(boolean allowMultipleEg) {
+ String appId = ApiProxy.getCurrentEnvironment().getAppId();
+ byte[] req =
+ BeginTransactionRequest.newBuilder()
+ .setApp(appId)
+ .setAllowMultipleEg(allowMultipleEg)
+ .build()
+ .toByteArray();
+ return ApiProxy.makeSyncCall("datastore_v3", "BeginTransaction", req);
+ }
+
+ private static void rollback(byte[] tx) {
+ ApiProxy.makeSyncCall("datastore_v3", "Rollback", tx);
+ }
+
+ private static GetResponse.Builder txGet(byte[] tx, GetRequest.Builder request) {
+ parseFromBytes(request.getTransactionBuilder(), tx);
+ GetResponse.Builder response = GetResponse.newBuilder();
+ byte[] resultBytes = ApiProxy.makeSyncCall("datastore_v3", "Get", request.build().toByteArray());
+ parseFromBytes(response, resultBytes);
+ return response;
+ }
+
+ // @VisibleForTesting
+ static byte[] computeSha1(EntityProto entity) {
+ byte[] entityBytes = entity.toByteArray();
+ return computeSha1(entityBytes, entityBytes.length);
+ }
+
+ /**
+ * This is a HACK. There used to be a bug in RemoteDatastore.java in that it would omit the last
+ * byte of the Entity when calculating the hash for the Precondition. If an app has not updated
+ * that library, we may still receive hashes like this. For backwards compatibility, we'll
+ * consider the transaction valid if omitting the last byte of the Entity matches the
+ * Precondition.
+ */
+ // @VisibleForTesting
+ static byte[] computeSha1OmittingLastByteForBackwardsCompatibility(EntityProto entity) {
+ byte[] entityBytes = entity.toByteArray();
+ return computeSha1(entityBytes, entityBytes.length - 1);
+ }
+
+ //
+ private static byte[] computeSha1(byte[] bytes, int length) {
+ MessageDigest md;
+ try {
+ md = MessageDigest.getInstance("SHA-1");
+ } catch (NoSuchAlgorithmException e) {
+ throw new ApiProxy.ApplicationException(
+ CONCURRENT_TRANSACTION.getNumber(), "Transaction precondition could not be computed");
+ }
+
+ md.update(bytes, 0, length);
+ return md.digest();
+ }
+
+ private static void parseFromBytes(Message.Builder message, byte[] bytes) {
+ boolean parsed = true;
+ try {
+ message.mergeFrom(bytes, ExtensionRegistry.getEmptyRegistry());
+ } catch (IOException e) {
+ parsed = false;
+ }
+ checkParse(message.build(), parsed);
+ }
+
+ private static void parseFromInputStream(Message.Builder message, InputStream inputStream) {
+ boolean parsed = true;
+ try {
+ message.mergeFrom(inputStream, ExtensionRegistry.getEmptyRegistry());
+ } catch (IOException e) {
+ parsed = false;
+ }
+ checkParse(message.build(), parsed);
+ }
+
+
+ private static void checkParse(Message message, boolean parsed) {
+ if (!parsed) {
+ throw new ApiProxy.ApiProxyException("Could not parse protobuf");
+ }
+ List errors = message.findInitializationErrors();
+ if (errors != null && !errors.isEmpty()) {
+ throw new ApiProxy.ApiProxyException("Could not parse protobuf: " + errors);
+ }
+ }
+}
diff --git a/api/src/main/java/com/google/apphosting/utils/remoteapi/RemoteApiServlet.java b/api/src/main/java/com/google/apphosting/utils/remoteapi/RemoteApiServlet.java
index 745837d2b..f21cded48 100644
--- a/api/src/main/java/com/google/apphosting/utils/remoteapi/RemoteApiServlet.java
+++ b/api/src/main/java/com/google/apphosting/utils/remoteapi/RemoteApiServlet.java
@@ -1,3 +1,4 @@
+
/*
* Copyright 2021 Google LLC
*
@@ -13,11 +14,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package com.google.apphosting.utils.remoteapi;
-import static com.google.apphosting.datastore.DatastoreV3Pb.Error.ErrorCode.BAD_REQUEST;
-import static com.google.apphosting.datastore.DatastoreV3Pb.Error.ErrorCode.CONCURRENT_TRANSACTION;
+import static com.google.apphosting.datastore.proto2api.DatastoreV3Pb.Error.ErrorCode.BAD_REQUEST;
+import static com.google.apphosting.datastore.proto2api.DatastoreV3Pb.Error.ErrorCode.CONCURRENT_TRANSACTION;
+import static com.google.common.base.Verify.verify;
import com.google.appengine.api.oauth.OAuthRequestException;
import com.google.appengine.api.oauth.OAuthService;
@@ -25,26 +26,28 @@
import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;
import com.google.apphosting.api.ApiProxy;
-import com.google.apphosting.datastore.DatastoreV3Pb.BeginTransactionRequest;
-import com.google.apphosting.datastore.DatastoreV3Pb.DeleteRequest;
-import com.google.apphosting.datastore.DatastoreV3Pb.GetRequest;
-import com.google.apphosting.datastore.DatastoreV3Pb.GetResponse;
-import com.google.apphosting.datastore.DatastoreV3Pb.NextRequest;
-import com.google.apphosting.datastore.DatastoreV3Pb.PutRequest;
-import com.google.apphosting.datastore.DatastoreV3Pb.Query;
-import com.google.apphosting.datastore.DatastoreV3Pb.QueryResult;
-import com.google.apphosting.utils.remoteapi.RemoteApiPb.ApplicationError;
-import com.google.apphosting.utils.remoteapi.RemoteApiPb.Request;
-import com.google.apphosting.utils.remoteapi.RemoteApiPb.Response;
-import com.google.apphosting.utils.remoteapi.RemoteApiPb.TransactionQueryResult;
-import com.google.apphosting.utils.remoteapi.RemoteApiPb.TransactionRequest;
-import com.google.apphosting.utils.remoteapi.RemoteApiPb.TransactionRequest.Precondition;
-import com.google.io.protocol.ProtocolMessage;
+import com.google.apphosting.base.protos.api.RemoteApiPb.Request;
+import com.google.apphosting.base.protos.api.RemoteApiPb.Response;
+import com.google.apphosting.base.protos.api.RemoteApiPb.TransactionQueryResult;
+import com.google.apphosting.base.protos.api.RemoteApiPb.TransactionRequest;
+import com.google.apphosting.base.protos.api.RemoteApiPb.TransactionRequest.Precondition;
+import com.google.apphosting.datastore.proto2api.DatastoreV3Pb.BeginTransactionRequest;
+import com.google.apphosting.datastore.proto2api.DatastoreV3Pb.DeleteRequest;
+import com.google.apphosting.datastore.proto2api.DatastoreV3Pb.GetRequest;
+import com.google.apphosting.datastore.proto2api.DatastoreV3Pb.GetResponse;
+import com.google.apphosting.datastore.proto2api.DatastoreV3Pb.NextRequest;
+import com.google.apphosting.datastore.proto2api.DatastoreV3Pb.PutRequest;
+import com.google.apphosting.datastore.proto2api.DatastoreV3Pb.Query;
+import com.google.apphosting.datastore.proto2api.DatastoreV3Pb.QueryResult;
+import com.google.protobuf.ByteString;
+import com.google.protobuf.ExtensionRegistry;
+import com.google.protobuf.Message;
//
-import com.google.storage.onestore.v3.OnestoreEntity;
-import com.google.storage.onestore.v3.OnestoreEntity.EntityProto;
-import com.google.storage.onestore.v3.OnestoreEntity.Path.Element;
+import com.google.storage.onestore.v3.proto2api.OnestoreEntity;
+import com.google.storage.onestore.v3.proto2api.OnestoreEntity.EntityProto;
+import com.google.storage.onestore.v3.proto2api.OnestoreEntity.Path.Element;
import java.io.ByteArrayOutputStream;
+import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
@@ -97,10 +100,8 @@ public UnknownPythonServerException(String message) {
* @param req the {@link HttpServletRequest}
* @param res the {@link HttpServletResponse}
* @return true if the application is known.
- * @throws java.io.IOException
*/
- boolean checkIsValidRequest(HttpServletRequest req, HttpServletResponse res)
- throws java.io.IOException {
+ boolean checkIsValidRequest(HttpServletRequest req, HttpServletResponse res) throws IOException {
if (!checkIsKnownInbound(req) && !checkIsAdmin(req, res)) {
return false;
}
@@ -112,10 +113,8 @@ boolean checkIsValidRequest(HttpServletRequest req, HttpServletResponse res)
*
* @param req the {@link HttpServletRequest}
* @return true if the application is known.
- * @throws java.io.IOException
*/
- private synchronized boolean checkIsKnownInbound(HttpServletRequest req)
- throws java.io.IOException {
+ private synchronized boolean checkIsKnownInbound(HttpServletRequest req) {
if (allowedApps == null) {
allowedApps = new HashSet();
String allowedAppsStr = System.getProperty(INBOUND_APP_SYSTEM_PROPERTY);
@@ -136,10 +135,9 @@ private synchronized boolean checkIsKnownInbound(HttpServletRequest req)
* @param req the {@link HttpServletRequest}
* @param res the {@link HttpServletResponse}
* @return true if the header exists.
- * @throws java.io.IOException
*/
private boolean checkIsValidHeader(HttpServletRequest req, HttpServletResponse res)
- throws java.io.IOException {
+ throws IOException {
if (req.getHeader("X-appcfg-api-version") == null) {
res.setStatus(403);
res.setContentType("text/plain");
@@ -152,11 +150,9 @@ private boolean checkIsValidHeader(HttpServletRequest req, HttpServletResponse r
/**
* Check that the current user is signed is with admin access.
*
- * @return true if the current user is logged in with admin access, false
- * otherwise.
+ * @return true if the current user is logged in with admin access, false otherwise.
*/
- private boolean checkIsAdmin(HttpServletRequest req, HttpServletResponse res)
- throws java.io.IOException {
+ private boolean checkIsAdmin(HttpServletRequest req, HttpServletResponse res) throws IOException {
UserService userService = UserServiceFactory.getUserService();
// Check for regular (cookie-based) authentication.
@@ -185,19 +181,16 @@ private boolean checkIsAdmin(HttpServletRequest req, HttpServletResponse res)
return false;
}
- private void respondNotAdmin(HttpServletResponse res) throws java.io.IOException {
+ private void respondNotAdmin(HttpServletResponse res) throws IOException {
res.setStatus(401);
res.setContentType("text/plain");
res.getWriter().println(
"You must be logged in as an administrator, or access from an approved application.");
}
- /**
- * Serve GET requests with a YAML encoding of the app-id and a validation
- * token.
- */
+ /** Serve GET requests with a YAML encoding of the app-id and a validation token. */
@Override
- public void doGet(HttpServletRequest req, HttpServletResponse res) throws java.io.IOException {
+ public void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
if (!checkIsValidRequest(req, res)) {
return;
}
@@ -209,21 +202,17 @@ public void doGet(HttpServletRequest req, HttpServletResponse res) throws java.i
res.getWriter().println(outYaml);
}
- /**
- * Serve POST requests by forwarding calls to ApiProxy.
- */
+ /** Serve POST requests by forwarding calls to ApiProxy. */
@Override
- public void doPost(HttpServletRequest req, HttpServletResponse res) throws java.io.IOException {
+ public void doPost(HttpServletRequest req, HttpServletResponse res) throws IOException {
if (!checkIsValidRequest(req, res)) {
return;
}
res.setContentType("application/octet-stream");
-
- Response response = new Response();
-
+ Response.Builder response = Response.newBuilder();
try {
byte[] responseData = executeRequest(req);
- response.setResponseAsBytes(responseData);
+ response.setResponse(ByteString.copyFrom(responseData));
res.setStatus(200);
} catch (Exception e) {
log.warning("Caught exception while executing remote_api command:\n" + e);
@@ -233,74 +222,71 @@ public void doPost(HttpServletRequest req, HttpServletResponse res) throws java.
out.writeObject(e);
out.close();
byte[] serializedException = byteStream.toByteArray();
- response.setJavaExceptionAsBytes(serializedException);
+ response.setJavaException(ByteString.copyFrom(serializedException));
if (e instanceof ApiProxy.ApplicationException) {
ApiProxy.ApplicationException ae = (ApiProxy.ApplicationException) e;
- ApplicationError appError = response.getMutableApplicationError();
- appError.setCode(ae.getApplicationError());
- appError.setDetail(ae.getErrorDetail());
+ response
+ .getApplicationErrorBuilder()
+ .setCode(ae.getApplicationError())
+ .setDetail(ae.getErrorDetail());
}
}
- res.getOutputStream().write(response.toByteArray());
+ response.build().writeTo(res.getOutputStream());
}
- private byte[] executeRunQuery(Request request) {
- Query queryRequest = new Query();
- parseFromBytes(queryRequest, request.getRequestAsBytes());
+ private byte[] executeRunQuery(Request.Builder request) {
+ Query.Builder queryRequest = Query.newBuilder();
+ parseFromBytes(queryRequest, request.getRequestIdBytes().toByteArray());
int batchSize = Math.max(1000, queryRequest.getLimit());
queryRequest.setCount(batchSize);
-
- QueryResult runQueryResponse = new QueryResult();
- byte[] res = ApiProxy.makeSyncCall("datastore_v3", "RunQuery", request.getRequestAsBytes());
+ QueryResult.Builder runQueryResponse = QueryResult.newBuilder();
+ byte[] res =
+ ApiProxy.makeSyncCall("datastore_v3", "RunQuery", request.getRequest().toByteArray());
parseFromBytes(runQueryResponse, res);
-
if (queryRequest.hasLimit()) {
// Try to pull all results
- while (runQueryResponse.isMoreResults()) {
- NextRequest nextRequest = new NextRequest();
- nextRequest.getMutableCursor().mergeFrom(runQueryResponse.getCursor());
+ while (runQueryResponse.getMoreResults()) {
+ NextRequest.Builder nextRequest = NextRequest.newBuilder();
+ nextRequest.getCursorBuilder().mergeFrom(runQueryResponse.getCursor());
nextRequest.setCount(batchSize);
- byte[] nextRes = ApiProxy.makeSyncCall("datastore_v3", "Next", nextRequest.toByteArray());
+ byte[] nextRes =
+ ApiProxy.makeSyncCall("datastore_v3", "Next", nextRequest.build().toByteArray());
parseFromBytes(runQueryResponse, nextRes);
}
}
- return runQueryResponse.toByteArray();
+ return runQueryResponse.build().toByteArray();
}
- private byte[] executeTxQuery(Request request) {
- TransactionQueryResult result = new TransactionQueryResult();
-
- Query query = new Query();
- parseFromBytes(query, request.getRequestAsBytes());
-
+ private byte[] executeTxQuery(Request.Builder request) {
+ TransactionQueryResult.Builder result = TransactionQueryResult.newBuilder();
+ Query.Builder query = Query.newBuilder();
+ parseFromBytes(query, request.getRequest().toByteArray());
if (!query.hasAncestor()) {
- throw new ApiProxy.ApplicationException(BAD_REQUEST.getValue(),
- "No ancestor in transactional query.");
+ throw new ApiProxy.ApplicationException(
+ BAD_REQUEST.getNumber(), "No ancestor in transactional query.");
}
// Make __entity_group__ key
- OnestoreEntity.Reference egKey =
- result.getMutableEntityGroupKey().mergeFrom(query.getAncestor());
+ OnestoreEntity.Reference.Builder egKey =
+ result.getEntityGroupKeyBuilder().mergeFrom(query.getAncestor());
OnestoreEntity.Path.Element root = egKey.getPath().getElement(0);
- egKey.getMutablePath().clearElement().addElement(root);
- OnestoreEntity.Path.Element egElement = new OnestoreEntity.Path.Element();
- egElement.setType("__entity_group__").setId(1);
- egKey.getMutablePath().addElement(egElement);
-
+ egKey.getPathBuilder().clearElement().addElement(root);
+ Element egElement =
+ OnestoreEntity.Path.Element.newBuilder().setType("__entity_group__").setId(1).build();
+ egKey.getPathBuilder().addElement(egElement);
// And then perform the transaction with the ancestor query and __entity_group__ fetch.
byte[] tx = beginTransaction(false);
- parseFromBytes(query.getMutableTransaction(), tx);
- byte[] queryBytes = ApiProxy.makeSyncCall("datastore_v3", "RunQuery", query.toByteArray());
- parseFromBytes(result.getMutableResult(), queryBytes);
-
- GetRequest egRequest = new GetRequest();
+ parseFromBytes(query.getTransactionBuilder(), tx);
+ byte[] queryBytes =
+ ApiProxy.makeSyncCall("datastore_v3", "RunQuery", query.build().toByteArray());
+ parseFromBytes(result.getResultBuilder(), queryBytes);
+ GetRequest.Builder egRequest = GetRequest.newBuilder();
egRequest.addKey(egKey);
- GetResponse egResponse = txGet(tx, egRequest);
+ GetResponse.Builder egResponse = txGet(tx, egRequest);
if (egResponse.getEntity(0).hasEntity()) {
result.setEntityGroup(egResponse.getEntity(0).getEntity());
}
rollback(tx);
-
- return result.toByteArray();
+ return result.build().toByteArray();
}
/**
@@ -310,48 +296,40 @@ private void assertEntityResultMatchesPrecondition(
GetResponse.Entity entityResult, Precondition precondition) {
// This handles the case where the Entity was missing in one of the two params.
if (precondition.hasHash() != entityResult.hasEntity()) {
- throw new ApiProxy.ApplicationException(CONCURRENT_TRANSACTION.getValue(),
- "Transaction precondition failed");
+ throw new ApiProxy.ApplicationException(
+ CONCURRENT_TRANSACTION.getNumber(), "Transaction precondition failed");
}
-
if (entityResult.hasEntity()) {
// Both params have an Entity. Make sure the Entities match using a SHA-1 hash.
EntityProto entity = entityResult.getEntity();
- if (Arrays.equals(precondition.getHashAsBytes(), computeSha1(entity))) {
+ if (Arrays.equals(precondition.getHashBytes().toByteArray(), computeSha1(entity))) {
// They match. We're done.
return;
}
-
// See javadoc of computeSha1OmittingLastByteForBackwardsCompatibility for explanation.
byte[] backwardsCompatibleHash = computeSha1OmittingLastByteForBackwardsCompatibility(entity);
- if (!Arrays.equals(precondition.getHashAsBytes(), backwardsCompatibleHash)) {
+ if (!Arrays.equals(precondition.getHashBytes().toByteArray(), backwardsCompatibleHash)) {
throw new ApiProxy.ApplicationException(
- CONCURRENT_TRANSACTION.getValue(), "Transaction precondition failed");
+ CONCURRENT_TRANSACTION.getNumber(), "Transaction precondition failed");
}
}
// Else, the Entity was missing from both.
}
- private byte[] executeTx(Request request) {
- TransactionRequest txRequest = new TransactionRequest();
- parseFromBytes(txRequest, request.getRequestAsBytes());
-
- byte[] tx = beginTransaction(txRequest.isAllowMultipleEg());
-
- List preconditions = txRequest.preconditions();
-
+ private byte[] executeTx(Request.Builder request) {
+ TransactionRequest.Builder txRequest = TransactionRequest.newBuilder();
+ parseFromBytes(txRequest, request.getRequest().toByteArray());
+ byte[] tx = beginTransaction(txRequest.getAllowMultipleEg());
+ List preconditions = txRequest.getPreconditionList();
// Check transaction preconditions
if (!preconditions.isEmpty()) {
- GetRequest getRequest = new GetRequest();
+ GetRequest.Builder getRequest = GetRequest.newBuilder();
for (Precondition precondition : preconditions) {
OnestoreEntity.Reference key = precondition.getKey();
- OnestoreEntity.Reference requestKey = getRequest.addKey();
- requestKey.mergeFrom(key);
+ getRequest.addKeyBuilder().mergeFrom(key);
}
-
- GetResponse getResponse = txGet(tx, getRequest);
- List entities = getResponse.entitys();
-
+ GetResponse.Builder getResponse = txGet(tx, getRequest);
+ List entities = getResponse.getEntityList();
// Note that this is guaranteed because we don't specify allow_deferred on the GetRequest.
// TODO: Consider supporting deferred gets here.
assert (entities.size() == preconditions.size());
@@ -364,51 +342,47 @@ private byte[] executeTx(Request request) {
// Perform puts.
byte[] res = new byte[0]; // a serialized VoidProto
if (txRequest.hasPuts()) {
- PutRequest putRequest = txRequest.getPuts();
- parseFromBytes(putRequest.getMutableTransaction(), tx);
- res = ApiProxy.makeSyncCall("datastore_v3", "Put", putRequest.toByteArray());
+ PutRequest.Builder putRequest = txRequest.getPutsBuilder();
+ parseFromBytes(putRequest.getTransactionBuilder(), tx);
+ res = ApiProxy.makeSyncCall("datastore_v3", "Put", putRequest.build().toByteArray());
}
// Perform deletes.
if (txRequest.hasDeletes()) {
- DeleteRequest deleteRequest = txRequest.getDeletes();
- parseFromBytes(deleteRequest.getMutableTransaction(), tx);
- ApiProxy.makeSyncCall("datastore_v3", "Delete", deleteRequest.toByteArray());
+ DeleteRequest.Builder deleteRequest = txRequest.getDeletesBuilder();
+ parseFromBytes(deleteRequest.getTransactionBuilder(), tx);
+ ApiProxy.makeSyncCall("datastore_v3", "Delete", deleteRequest.build().toByteArray());
}
// Commit transaction.
ApiProxy.makeSyncCall("datastore_v3", "Commit", tx);
return res;
}
- private byte[] executeGetIDs(Request request, boolean isXG) {
- PutRequest putRequest = new PutRequest();
- parseFromBytes(putRequest, request.getRequestAsBytes());
- for (EntityProto entity : putRequest.entitys()) {
- assert (entity.propertySize() == 0);
- assert (entity.rawPropertySize() == 0);
- assert (entity.getEntityGroup().elementSize() == 0);
- List elementList = entity.getKey().getPath().elements();
+ private byte[] executeGetIDs(Request.Builder request, boolean isXg) {
+ PutRequest.Builder putRequest = PutRequest.newBuilder();
+ parseFromBytes(putRequest, request.getRequest().toByteArray());
+ for (EntityProto entity : putRequest.getEntityList()) {
+ verify(entity.getPropertyCount() == 0);
+ verify(entity.getRawPropertyCount() == 0);
+ verify(entity.getEntityGroup().getElementCount() == 0);
+ List elementList = entity.getKey().getPath().getElementList();
Element lastPart = elementList.get(elementList.size() - 1);
- assert (lastPart.getId() == 0);
- assert (!lastPart.hasName());
+ verify(lastPart.getId() == 0);
+ verify(!lastPart.hasName());
}
-
// Start a Transaction.
-
// TODO: Shouldn't this use allocateIds instead?
- byte[] tx = beginTransaction(isXG);
- parseFromBytes(putRequest.getMutableTransaction(), tx);
-
+ byte[] tx = beginTransaction(isXg);
+ parseFromBytes(putRequest.getTransactionBuilder(), tx);
// Make a put request for a bunch of empty entities with the requisite
// paths.
- byte[] res = ApiProxy.makeSyncCall("datastore_v3", "Put", putRequest.toByteArray());
-
+ byte[] res = ApiProxy.makeSyncCall("datastore_v3", "Put", putRequest.build().toByteArray());
// Roll back the transaction so we don't actually insert anything.
rollback(tx);
return res;
}
- private byte[] executeRequest(HttpServletRequest req) throws java.io.IOException {
- Request request = new Request();
+ private byte[] executeRequest(HttpServletRequest req) throws IOException {
+ Request.Builder request = Request.newBuilder();
parseFromInputStream(request, req.getInputStream());
String service = request.getServiceName();
String method = request.getMethod();
@@ -430,7 +404,7 @@ private byte[] executeRequest(HttpServletRequest req) throws java.io.IOException
throw new ApiProxy.CallNotFoundException(service, method);
}
} else {
- return ApiProxy.makeSyncCall(service, method, request.getRequestAsBytes());
+ return ApiProxy.makeSyncCall(service, method, request.getRequest().toByteArray());
}
}
@@ -438,8 +412,12 @@ private byte[] executeRequest(HttpServletRequest req) throws java.io.IOException
private static byte[] beginTransaction(boolean allowMultipleEg) {
String appId = ApiProxy.getCurrentEnvironment().getAppId();
- byte[] req = new BeginTransactionRequest().setApp(appId)
- .setAllowMultipleEg(allowMultipleEg).toByteArray();
+ byte[] req =
+ BeginTransactionRequest.newBuilder()
+ .setApp(appId)
+ .setAllowMultipleEg(allowMultipleEg)
+ .build()
+ .toByteArray();
return ApiProxy.makeSyncCall("datastore_v3", "BeginTransaction", req);
}
@@ -447,10 +425,10 @@ private static void rollback(byte[] tx) {
ApiProxy.makeSyncCall("datastore_v3", "Rollback", tx);
}
- private static GetResponse txGet(byte[] tx, GetRequest request) {
- parseFromBytes(request.getMutableTransaction(), tx);
- GetResponse response = new GetResponse();
- byte[] resultBytes = ApiProxy.makeSyncCall("datastore_v3", "Get", request.toByteArray());
+ private static GetResponse.Builder txGet(byte[] tx, GetRequest.Builder request) {
+ parseFromBytes(request.getTransactionBuilder(), tx);
+ GetResponse.Builder response = GetResponse.newBuilder();
+ byte[] resultBytes = ApiProxy.makeSyncCall("datastore_v3", "Get", request.build().toByteArray());
parseFromBytes(response, resultBytes);
return response;
}
@@ -481,30 +459,41 @@ private static byte[] computeSha1(byte[] bytes, int length) {
md = MessageDigest.getInstance("SHA-1");
} catch (NoSuchAlgorithmException e) {
throw new ApiProxy.ApplicationException(
- CONCURRENT_TRANSACTION.getValue(), "Transaction precondition could not be computed");
+ CONCURRENT_TRANSACTION.getNumber(), "Transaction precondition could not be computed");
}
md.update(bytes, 0, length);
return md.digest();
}
- private static void parseFromBytes(ProtocolMessage> message, byte[] bytes) {
- boolean parsed = message.parseFrom(bytes);
- checkParse(message, parsed);
+ private static void parseFromBytes(Message.Builder message, byte[] bytes) {
+ boolean parsed = true;
+ try {
+ message.mergeFrom(bytes, ExtensionRegistry.getEmptyRegistry());
+ } catch (IOException e) {
+ parsed = false;
+ }
+ checkParse(message.build(), parsed);
}
- private static void parseFromInputStream(ProtocolMessage> message, InputStream inputStream) {
- boolean parsed = message.parseFrom(inputStream);
- checkParse(message, parsed);
+ private static void parseFromInputStream(Message.Builder message, InputStream inputStream) {
+ boolean parsed = true;
+ try {
+ message.mergeFrom(inputStream, ExtensionRegistry.getEmptyRegistry());
+ } catch (IOException e) {
+ parsed = false;
+ }
+ checkParse(message.build(), parsed);
}
- private static void checkParse(ProtocolMessage> message, boolean parsed) {
+
+ private static void checkParse(Message message, boolean parsed) {
if (!parsed) {
throw new ApiProxy.ApiProxyException("Could not parse protobuf");
}
- String error = message.findInitializationError();
- if (error != null) {
- throw new ApiProxy.ApiProxyException("Could not parse protobuf: " + error);
+ List errors = message.findInitializationErrors();
+ if (errors != null && !errors.isEmpty()) {
+ throw new ApiProxy.ApiProxyException("Could not parse protobuf: " + errors);
}
}
}
diff --git a/api/src/main/java/com/google/apphosting/utils/servlet/ee10/DeferredTaskServlet.java b/api/src/main/java/com/google/apphosting/utils/servlet/ee10/DeferredTaskServlet.java
index 8be000f27..101d64d58 100644
--- a/api/src/main/java/com/google/apphosting/utils/servlet/ee10/DeferredTaskServlet.java
+++ b/api/src/main/java/com/google/apphosting/utils/servlet/ee10/DeferredTaskServlet.java
@@ -16,222 +16,10 @@
package com.google.apphosting.utils.servlet.ee10;
-import com.google.appengine.api.taskqueue.DeferredTask;
-import com.google.appengine.api.taskqueue.ee10.DeferredTaskContext;
-import com.google.apphosting.api.ApiProxy;
-import jakarta.servlet.ServletException;
-import jakarta.servlet.ServletInputStream;
-import jakarta.servlet.http.HttpServlet;
-import jakarta.servlet.http.HttpServletRequest;
-import jakarta.servlet.http.HttpServletResponse;
-import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.io.ObjectStreamClass;
-import java.lang.reflect.Modifier;
-import java.lang.reflect.Proxy;
-import java.net.HttpURLConnection;
-import java.util.Map;
-
/**
- * Implementation of {@link HttpServlet} to dispatch tasks with a {@link DeferredTask} payload; see
- * {@link com.google.appengine.api.taskqueue.TaskOptions#payload(DeferredTask)}.
- *
- *
This servlet is mapped to {@link DeferredTaskContext#DEFAULT_DEFERRED_URL} by default. Below
- * is a snippet of the web.xml configuration.
- *
- *
- *
+ * @deprecated as of version 3.0, use {@link
+ * com.google.apphosting.utils.servlet.jakarta.DeferredTaskServlet} instead.
*/
-public class DeferredTaskServlet extends HttpServlet {
- // Keep this in sync with X_APPENGINE_QUEUENAME and
- // in google3/apphosting/base/http_proto.cc
- static final String X_APPENGINE_QUEUENAME = "X-AppEngine-QueueName";
-
- static final String DEFERRED_TASK_SERVLET_KEY =
- DeferredTaskContext.class.getName() + ".httpServlet";
- static final String DEFERRED_TASK_REQUEST_KEY =
- DeferredTaskContext.class.getName() + ".httpServletRequest";
- static final String DEFERRED_TASK_RESPONSE_KEY =
- DeferredTaskContext.class.getName() + ".httpServletResponse";
- static final String DEFERRED_DO_NOT_RETRY_KEY =
- DeferredTaskContext.class.getName() + ".doNotRetry";
- static final String DEFERRED_MARK_RETRY_KEY = DeferredTaskContext.class.getName() + ".markRetry";
-
- /** Thrown by readRequest when an error occurred during deserialization. */
- protected static class DeferredTaskException extends Exception {
- public DeferredTaskException(Exception e) {
- super(e);
- }
- }
-
- @Override
- protected void service(HttpServletRequest req, HttpServletResponse resp)
- throws ServletException, IOException {
- // See http://b/3479189. All task queue requests have the X-AppEngine-QueueName
- // header set. Non admin users cannot set this header so it's a signal that
- // this came from task queue or an admin smart enough to set the header.
- if (req.getHeader(X_APPENGINE_QUEUENAME) == null) {
- resp.sendError(HttpServletResponse.SC_FORBIDDEN, "Not a taskqueue request.");
- return;
- }
-
- String method = req.getMethod();
- if (!method.equals("POST")) {
- String protocol = req.getProtocol();
- String msg = "DeferredTaskServlet does not support method: " + method;
- if (protocol.endsWith("1.1")) {
- resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
- } else {
- resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
- }
- return;
- }
-
- // Place the current servlet, request and response in the environment for
- // situations where the task may need to get to it.
- Map attributes = ApiProxy.getCurrentEnvironment().getAttributes();
- attributes.put(DEFERRED_TASK_SERVLET_KEY, this);
- attributes.put(DEFERRED_TASK_REQUEST_KEY, req);
- attributes.put(DEFERRED_TASK_RESPONSE_KEY, resp);
- attributes.put(DEFERRED_MARK_RETRY_KEY, false);
-
- try {
- performRequest(req, resp);
- if ((Boolean) attributes.get(DEFERRED_MARK_RETRY_KEY)) {
- resp.setStatus(HttpURLConnection.HTTP_INTERNAL_ERROR);
- } else {
- resp.setStatus(HttpURLConnection.HTTP_OK);
- }
- } catch (DeferredTaskException e) {
- resp.setStatus(HttpURLConnection.HTTP_UNSUPPORTED_TYPE);
- log("Deferred task failed exception: " + e);
- return;
- } catch (RuntimeException e) {
- Boolean doNotRetry = (Boolean) attributes.get(DEFERRED_DO_NOT_RETRY_KEY);
- if (doNotRetry == null || !doNotRetry) {
- throw new ServletException(e);
- } else if (doNotRetry) {
- resp.setStatus(HttpURLConnection.HTTP_NOT_AUTHORITATIVE); // Alternate success code.
- log(
- DeferredTaskServlet.class.getName()
- + " - Deferred task failed but doNotRetry specified. Exception: "
- + e);
- }
- } finally {
- // Clean out the attributes.
- attributes.remove(DEFERRED_TASK_SERVLET_KEY);
- attributes.remove(DEFERRED_TASK_REQUEST_KEY);
- attributes.remove(DEFERRED_TASK_RESPONSE_KEY);
- attributes.remove(DEFERRED_DO_NOT_RETRY_KEY);
- }
- }
-
- /**
- * Performs a task enqueued with {@link TaskOptions#payload(DeferredTask)} by deserializing the
- * input stream of the {@link HttpServletRequest}.
- *
- * @param req The HTTP request.
- * @param resp The HTTP response.
- * @throws DeferredTaskException If an error occurred while deserializing the task.
- *
Note that other exceptions may be thrown by the {@link DeferredTask#run()} method.
- */
- protected void performRequest(HttpServletRequest req, HttpServletResponse resp)
- throws DeferredTaskException {
- readRequest(req, resp).run();
- }
-
- /**
- * De-serializes the {@link DeferredTask} object from the input stream.
- *
- * @throws DeferredTaskException With the chained exception being one of the following:
- *
{@link IllegalArgumentException}: Indicates a content-type header mismatch.
- *
{@link ClassCastException}: Deserialization failure.
- */
- protected Runnable readRequest(HttpServletRequest req, HttpServletResponse resp)
- throws DeferredTaskException {
- String contentType = req.getHeader("content-type");
- if (contentType == null
- || !contentType.equals(DeferredTaskContext.RUNNABLE_TASK_CONTENT_TYPE)) {
- throw new DeferredTaskException(
- new IllegalArgumentException(
- "Invalid content-type header."
- + " received: '"
- + (String.valueOf(contentType))
- + "' expected: '"
- + DeferredTaskContext.RUNNABLE_TASK_CONTENT_TYPE
- + "'"));
- }
-
- try {
- ServletInputStream stream = req.getInputStream();
- ObjectInputStream objectStream =
- new ObjectInputStream(stream) {
- @Override
- protected Class> resolveClass(ObjectStreamClass desc)
- throws IOException, ClassNotFoundException {
- ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
- String name = desc.getName();
- try {
- return Class.forName(name, false, classLoader);
- } catch (ClassNotFoundException ex) {
- // This one should also handle primitive types
- return super.resolveClass(desc);
- }
- }
-
- @Override
- protected Class> resolveProxyClass(String[] interfaces)
- throws IOException, ClassNotFoundException {
- // Note This logic was copied from ObjectInputStream.java in the
- // JDK, and then modified to use the thread context class loader instead of the
- // "latest" loader that is used there.
- ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
- ClassLoader nonPublicLoader = null;
- boolean hasNonPublicInterface = false;
-
- // define proxy in class loader of non-public interface(s), if any
- Class>[] classObjs = new Class>[interfaces.length];
- for (int i = 0; i < interfaces.length; i++) {
- Class> cl = Class.forName(interfaces[i], false, classLoader);
- if ((cl.getModifiers() & Modifier.PUBLIC) == 0) {
- if (hasNonPublicInterface) {
- if (nonPublicLoader != cl.getClassLoader()) {
- throw new IllegalAccessError(
- "conflicting non-public interface class loaders");
- }
- } else {
- nonPublicLoader = cl.getClassLoader();
- hasNonPublicInterface = true;
- }
- }
- classObjs[i] = cl;
- }
- try {
- return Proxy.getProxyClass(
- hasNonPublicInterface ? nonPublicLoader : classLoader, classObjs);
- } catch (IllegalArgumentException e) {
- throw new ClassNotFoundException(null, e);
- }
- }
- };
- // Replacing DeferredTask to Runnable as we have DeferredTask in the 2 classloaders
- // (runtime and application), but we cannot cast one with another one.
- return (Runnable) objectStream.readObject();
- } catch (ClassNotFoundException | IOException | ClassCastException e) {
- throw new DeferredTaskException(e);
- }
- }
-}
+@Deprecated(since = "3.0.0")
+public class DeferredTaskServlet
+ extends com.google.apphosting.utils.servlet.jakarta.DeferredTaskServlet {}
diff --git a/api/src/main/java/com/google/apphosting/utils/servlet/ee10/JdbcMySqlConnectionCleanupFilter.java b/api/src/main/java/com/google/apphosting/utils/servlet/ee10/JdbcMySqlConnectionCleanupFilter.java
index a1b6ea897..8430df85f 100644
--- a/api/src/main/java/com/google/apphosting/utils/servlet/ee10/JdbcMySqlConnectionCleanupFilter.java
+++ b/api/src/main/java/com/google/apphosting/utils/servlet/ee10/JdbcMySqlConnectionCleanupFilter.java
@@ -16,184 +16,10 @@
package com.google.apphosting.utils.servlet.ee10;
-import com.google.apphosting.api.ApiProxy;
-import com.google.apphosting.api.ApiProxy.Environment;
-import jakarta.servlet.Filter;
-import jakarta.servlet.FilterChain;
-import jakarta.servlet.FilterConfig;
-import jakarta.servlet.ServletException;
-import jakarta.servlet.ServletRequest;
-import jakarta.servlet.ServletResponse;
-import java.io.IOException;
-import java.lang.reflect.Method;
-import java.util.Map;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
/**
- * Filter to cleanup any SQL connections that were opened but not closed during the
- * HTTP-request processing.
+ * @deprecated as of version 3.0, use {@link
+ * com.google.apphosting.utils.servlet.jakarta.JdbcMySqlConnectionCleanupFilter} instead.
*/
-public class JdbcMySqlConnectionCleanupFilter implements Filter {
-
- private static final Logger logger = Logger.getLogger(
- JdbcMySqlConnectionCleanupFilter.class.getCanonicalName());
-
- /**
- * The key for looking up the feature on/off flag.
- */
- static final String CLOUD_SQL_JDBC_CONNECTIVITY_ENABLED_KEY =
- "com.google.appengine.runtime.new_database_connectivity";
-
- private final AppEngineApiWrapper appEngineApiWrapper;
-
- private final ConnectionsCleanupWrapper connectionsCleanupWrapper;
-
- private static final String THROW_ERROR_VARIABLE_NAME = "THROW_ERROR_ON_SQL_CLOSE_ERROR";
- private static final String ABANDONED_CONNECTIONS_CLASSNAME =
- "com.mysql.jdbc.AbandonedConnections";
-
- public JdbcMySqlConnectionCleanupFilter() {
- appEngineApiWrapper = new AppEngineApiWrapper();
- connectionsCleanupWrapper = new ConnectionsCleanupWrapper();
- }
-
- // Visible for testing.
- JdbcMySqlConnectionCleanupFilter(
- AppEngineApiWrapper appEngineApiWrapper,
- ConnectionsCleanupWrapper connectionsCleanupWrapper) {
- this.appEngineApiWrapper = appEngineApiWrapper;
- this.connectionsCleanupWrapper = connectionsCleanupWrapper;
- }
-
- @Override
- public void init(FilterConfig filterConfig) throws ServletException {
- // Do Nothing.
- }
-
- @Override
- public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
- throws IOException, ServletException {
- try {
- chain.doFilter(request, response);
- } finally {
- cleanupConnections();
- }
- }
-
- /**
- * Cleanup any SQL connection that was opened but not closed during the HTTP-request processing.
- */
- void cleanupConnections() {
- Map attributes = appEngineApiWrapper.getRequestEnvironmentAttributes();
- if (attributes == null) {
- return;
- }
-
- Object cloudSqlJdbcConnectivityEnabledValue =
- attributes.get(CLOUD_SQL_JDBC_CONNECTIVITY_ENABLED_KEY);
- if (!(cloudSqlJdbcConnectivityEnabledValue instanceof Boolean)) {
- return;
- }
-
- if (!((Boolean) cloudSqlJdbcConnectivityEnabledValue)) {
- // Act as no-op if the flag indicated by CLOUD_SQL_JDBC_CONNECTIVITY_ENABLED_KEY is false.
- return;
- }
-
- try {
- connectionsCleanupWrapper.cleanup();
- } catch (Exception e) {
- logger.log(Level.WARNING, "Unable to cleanup connections", e);
- if (Boolean.getBoolean(THROW_ERROR_VARIABLE_NAME)) {
- throw new IllegalStateException(e);
- }
- }
- }
-
- @Override
- public void destroy() {
- // Do Nothing.
- }
-
- /**
- * Wrapper for ApiProxy static methods.
- * Refactored for testability.
- */
- static class AppEngineApiWrapper {
- /**
- * Utility method that fetches back the attributes map for the HTTP-request being processed.
- *
- * @return The environment attribute map for the current HTTP request, or null if unable to
- * fetch the map
- */
- Map getRequestEnvironmentAttributes() {
- // Check for the current request environment.
- Environment environment = ApiProxy.getCurrentEnvironment();
- if (environment == null) {
- logger.warning("Unable to fetch the request environment.");
- return null;
- }
-
- // Get the environment attributes.
- Map attributes = environment.getAttributes();
- if (attributes == null) {
- logger.warning("Unable to fetch the request environment attributes.");
- return null;
- }
-
- return attributes;
- }
- }
-
- /**
- * Wrapper for the connections cleanup method.
- * Refactored for testability.
- */
- static class ConnectionsCleanupWrapper {
- /**
- * Abandoned connections cleanup method cache.
- */
- private static Method cleanupMethod;
- private static boolean cleanupMethodInitializationAttempted;
-
- void cleanup() throws Exception {
- synchronized (ConnectionsCleanupWrapper.class) {
- // Due to cr/50477083 the cleanup method was invoked by the applications that do
- // not have the native connectivity enabled. For such applications the filter raised
- // ClassNotFound exception when returning a class object associated with the
- // "com.mysql.jdbc.AbandonedConnections" class. By design this class is not loaded for
- // such applications. The exception was logged as warning and polluted the logs.
- //
- // As a quick fix; we ensure that the initialization for cleanupMethod is attempted
- // only once, avoiding exceptions being raised for every request in case of
- // applications mentioned above. We also suppress the ClassNotFound exception that
- // would be raised for such applications thereby not polluting the logs.
- // For the applications having native connectivity enabled the servlet filter would
- // work as expected.
- //
- // As a long term fix we need to use the "use-google-connector-j" flag that user sets
- // in the appengine-web.xml to decide if we should make an early return from the filter.
- if (!cleanupMethodInitializationAttempted) {
- try {
- if (cleanupMethod == null) {
- ClassLoader loader = Thread.currentThread().getContextClassLoader();
- cleanupMethod =
- (loader == null
- ? Class.forName(ABANDONED_CONNECTIONS_CLASSNAME)
- : loader.loadClass(ABANDONED_CONNECTIONS_CLASSNAME))
- .getDeclaredMethod("cleanup");
- }
- } catch (ClassNotFoundException e) {
- // Do nothing.
- } finally {
- cleanupMethodInitializationAttempted = true;
- }
- }
- }
- if (cleanupMethod != null) {
- cleanupMethod.invoke(null);
- }
- }
- }
-}
+@Deprecated(since = "3.0.0")
+public class JdbcMySqlConnectionCleanupFilter
+ extends com.google.apphosting.utils.servlet.jakarta.JdbcMySqlConnectionCleanupFilter {}
\ No newline at end of file
diff --git a/api/src/main/java/com/google/apphosting/utils/servlet/ee10/MultipartMimeUtils.java b/api/src/main/java/com/google/apphosting/utils/servlet/ee10/MultipartMimeUtils.java
index 8c0a0aa9b..f812c258d 100644
--- a/api/src/main/java/com/google/apphosting/utils/servlet/ee10/MultipartMimeUtils.java
+++ b/api/src/main/java/com/google/apphosting/utils/servlet/ee10/MultipartMimeUtils.java
@@ -16,115 +16,10 @@
package com.google.apphosting.utils.servlet.ee10;
-import com.google.common.io.ByteStreams;
-import jakarta.servlet.http.HttpServletRequest;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.UnsupportedEncodingException;
-import javax.activation.DataSource;
-import javax.mail.BodyPart;
-import javax.mail.MessagingException;
-import javax.mail.internet.ContentDisposition;
-import javax.mail.internet.ContentType;
-import javax.mail.internet.MimeMultipart;
-
/**
- * {@code MultipartMimeUtils} is a collection of static utility clases
- * that facilitate the parsing of multipart/form-data and
- * multipart/mixed requests using the {@link MimeMultipart} class
- * provided by JavaMail.
- *
+ * @deprecated as of version 3.0, use {@link
+ * com.google.apphosting.utils.servlet.jakarta.MultipartMimeUtils} instead.
*/
-public class MultipartMimeUtils {
- /**
- * Parse the request body and return a {@link MimeMultipart}
- * representing the request.
- */
- public static MimeMultipart parseMultipartRequest(HttpServletRequest req)
- throws IOException, MessagingException {
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- ByteStreams.copy(req.getInputStream(), baos);
-
- return new MimeMultipart(createDataSource(req.getContentType(), baos.toByteArray()));
- }
-
- /**
- * Create a read-only {@link DataSource} with the specific content type and body.
- */
- public static DataSource createDataSource(String contentType, byte[] data) {
- return new StaticDataSource(contentType, data);
- }
-
- /**
- * Extract the form name from the Content-Disposition in a
- * multipart/form-data request.
- */
- public static String getFieldName(BodyPart part) throws MessagingException {
- String[] values = part.getHeader("Content-Disposition");
- String name = null;
- if (values != null && values.length > 0) {
- name = new ContentDisposition(values[0]).getParameter("name");
- }
- return (name != null) ? name : "unknown";
- }
-
- /**
- * Extract the text content for a {@link BodyPart}, assuming the default
- * encoding.
- */
- public static String getTextContent(BodyPart part) throws MessagingException, IOException {
- ContentType contentType = new ContentType(part.getContentType());
- String charset = contentType.getParameter("charset");
- if (charset == null) {
- // N.B.: The MIME spec doesn't seem to provide a
- // default charset, but the default charset for HTTP is
- // ISO-8859-1. That seems like a reasonable default.
- charset = "ISO-8859-1";
- }
-
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- ByteStreams.copy(part.getInputStream(), baos);
- try {
- return new String(baos.toByteArray(), charset);
- } catch (UnsupportedEncodingException ex) {
- return new String(baos.toByteArray());
- }
- }
-
- /**
- * A read-only {@link DataSource} backed by a content type and a
- * fixed byte array.
- */
- private static class StaticDataSource implements DataSource {
- private final String contentType;
- private final byte[] bytes;
-
- public StaticDataSource(String contentType, byte[] bytes) {
- this.contentType = contentType;
- this.bytes = bytes;
- }
-
- @Override
- public String getContentType() {
- return contentType;
- }
-
- @Override
- public InputStream getInputStream() {
- return new ByteArrayInputStream(bytes);
- }
-
- @Override
- public OutputStream getOutputStream() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public String getName() {
- return "request";
- }
- }
-}
+@Deprecated(since = "3.0.0")
+public class MultipartMimeUtils
+ extends com.google.apphosting.utils.servlet.jakarta.MultipartMimeUtils {}
diff --git a/api/src/main/java/com/google/apphosting/utils/servlet/ee10/ParseBlobUploadFilter.java b/api/src/main/java/com/google/apphosting/utils/servlet/ee10/ParseBlobUploadFilter.java
index 7f6013be8..e78e257f8 100644
--- a/api/src/main/java/com/google/apphosting/utils/servlet/ee10/ParseBlobUploadFilter.java
+++ b/api/src/main/java/com/google/apphosting/utils/servlet/ee10/ParseBlobUploadFilter.java
@@ -16,185 +16,10 @@
package com.google.apphosting.utils.servlet.ee10;
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-import jakarta.servlet.Filter;
-import jakarta.servlet.FilterChain;
-import jakarta.servlet.FilterConfig;
-import jakarta.servlet.ServletException;
-import jakarta.servlet.ServletRequest;
-import jakarta.servlet.ServletResponse;
-import jakarta.servlet.http.HttpServletRequest;
-import jakarta.servlet.http.HttpServletRequestWrapper;
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-import javax.mail.BodyPart;
-import javax.mail.MessagingException;
-import javax.mail.internet.ContentType;
-import javax.mail.internet.MimeBodyPart;
-import javax.mail.internet.MimeMultipart;
-
/**
- * {@code ParseBlobUploadFilter} is responsible for the parsing
- * multipart/form-data or multipart/mixed requests used to make Blob
- * upload callbacks, and storing a set of string-encoded blob keys as
- * a servlet request attribute. This allows the {@code
- * BlobstoreService.getUploadedBlobs()} method to return the
- * appropriate {@code BlobKey} objects.
- *
- *
This filter automatically runs on all dynamic requests in the
- * production environment. In the DevAppServer, the equivalent work
- * is subsumed by {@code UploadBlobServlet}.
- *
+ * @deprecated as of version 3.0, use {@link
+ * com.google.apphosting.utils.servlet.jakarta.ParseBlobUploadFilter} instead.
*/
-public class ParseBlobUploadFilter implements Filter {
- private static final Logger logger = Logger.getLogger(
- ParseBlobUploadFilter.class.getName());
-
- /**
- * An arbitrary HTTP header that is set on all blob upload
- * callbacks.
- */
- static final String UPLOAD_HEADER = "X-AppEngine-BlobUpload";
-
- static final String UPLOADED_BLOBKEY_ATTR = "com.google.appengine.api.blobstore.upload.blobkeys";
-
- static final String UPLOADED_BLOBINFO_ATTR =
- "com.google.appengine.api.blobstore.upload.blobinfos";
-
- // This field has to be the same as X_APPENGINE_CLOUD_STORAGE_OBJECT in http_proto.cc.
- // This header will have the creation date in the format YYYY-MM-DD HH:mm:ss.SSS.
- static final String UPLOAD_CREATION_HEADER = "X-AppEngine-Upload-Creation";
-
- // This field has to be the same as X_APPENGINE_CLOUD_STORAGE_OBJECT in http_proto.cc.
- // This header will have the filename of created the object in Cloud Storage when appropriate.
- static final String CLOUD_STORAGE_OBJECT_HEADER = "X-AppEngine-Cloud-Storage-Object";
-
- static final String CONTENT_LENGTH_HEADER = "Content-Length";
-
- @Override
- public void init(FilterConfig config) {}
-
- @Override
- public void destroy() {}
-
- @Override
- public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
- throws IOException, ServletException {
- HttpServletRequest req = (HttpServletRequest) request;
- if (req.getHeader(UPLOAD_HEADER) != null) {
- Map> blobKeys = new HashMap<>();
- Map>> blobInfos = new HashMap<>();
- Map> otherParams = new HashMap<>();
-
- try {
- MimeMultipart multipart = MultipartMimeUtils.parseMultipartRequest(req);
-
- int parts = multipart.getCount();
- for (int i = 0; i < parts; i++) {
- BodyPart part = multipart.getBodyPart(i);
- String fieldName = MultipartMimeUtils.getFieldName(part);
- if (part.getFileName() != null) {
- ContentType contentType = new ContentType(part.getContentType());
- if ("message/external-body".equals(contentType.getBaseType())) {
- String blobKeyString = contentType.getParameter("blob-key");
- List keys = blobKeys.computeIfAbsent(fieldName, k -> new ArrayList<>());
- keys.add(blobKeyString);
- List