diff --git a/api/pom.xml b/api/pom.xml
index 3ff053461..f5167fd90 100644
--- a/api/pom.xml
+++ b/api/pom.xml
@@ -21,7 +21,7 @@
com.google.appengine
parent
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
true
@@ -147,12 +147,43 @@
guava-testlib
test
+
jakarta.servlet
jakarta.servlet-api
+
+ io.grpc
+ grpc-netty-shaded
+
+
+ io.grpc
+ grpc-protobuf
+
+
+ io.grpc
+ grpc-stub
+
+
+ io.grpc
+ grpc-auth
+
+
+ com.google.auth
+ google-auth-library-oauth2-http
+
+
+ io.grpc
+ grpc-testing
+ test
+
+
+ com.google.cloud
+ google-cloud-storage
+
+
org.apache.maven.plugins
@@ -213,8 +244,13 @@
false
+
+
+
+
+
docFX
@@ -266,12 +302,12 @@
com.google.auto.service
auto-service
- 1.1.1
+ ${auto-service.version}
com.google.auto.value
auto-value
- 1.11.1
+ ${auto-value.version}
diff --git a/api/src/main/java/com/google/appengine/api/SystemEnvironmentProvider.java b/api/src/main/java/com/google/appengine/api/SystemEnvironmentProvider.java
new file mode 100644
index 000000000..a27c345d0
--- /dev/null
+++ b/api/src/main/java/com/google/appengine/api/SystemEnvironmentProvider.java
@@ -0,0 +1,45 @@
+/*
+ * 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;
+
+/** A simple wrapper around {@link System} to allow for easier testing. */
+public class SystemEnvironmentProvider implements EnvironmentProvider {
+ /**
+ * Gets the value of the specified environment variable.
+ *
+ * @param name the name of the environment variable
+ * @return the string value of the variable, or {@code null} if the variable is not defined
+ */
+ @Override
+ public String getenv(String name) {
+ return System.getenv(name);
+ }
+
+ /**
+ * Gets the value of the specified environment variable, returning a default value if the variable
+ * is not defined.
+ *
+ * @param name the name of the environment variable
+ * @param defaultValue the default value to return
+ * @return the string value of the variable, or the default value if the variable is not defined
+ */
+ @Override
+ public String getenv(String name, String defaultValue) {
+ String value = System.getenv(name);
+ return value != null ? value : defaultValue;
+ }
+}
diff --git a/api/src/main/java/com/google/appengine/api/images/Composite.java b/api/src/main/java/com/google/appengine/api/images/Composite.java
index b169b9899..bb5f1da7b 100644
--- a/api/src/main/java/com/google/appengine/api/images/Composite.java
+++ b/api/src/main/java/com/google/appengine/api/images/Composite.java
@@ -16,7 +16,9 @@
package com.google.appengine.api.images;
+import com.google.appengine.api.images.ImagesServicePb.ImageData;
import java.util.Map;
+import java.util.function.Function;
/**
* A {@code Composite} represents a composition of an image onto a canvas.
@@ -34,10 +36,13 @@ public static enum Anchor {TOP_LEFT, TOP_CENTER, TOP_RIGHT, CENTER_LEFT,
/**
* Adds this compositing operation to a Composite request.
+ *
* @param request Request for this composite to be added to.
* @param imageIndexMap Map of images and their indexes in the request.
+ * @param imageDataConverter Function to convert an Image to ImageData.
*/
- abstract void apply(ImagesServicePb.ImagesCompositeRequest.Builder request,
- Map imageIndexMap);
-
+ abstract void apply(
+ ImagesServicePb.ImagesCompositeRequest.Builder request,
+ Map imageIndexMap,
+ Function imageDataConverter);
}
diff --git a/api/src/main/java/com/google/appengine/api/images/CompositeImpl.java b/api/src/main/java/com/google/appengine/api/images/CompositeImpl.java
index ec5de5c58..468e74555 100644
--- a/api/src/main/java/com/google/appengine/api/images/CompositeImpl.java
+++ b/api/src/main/java/com/google/appengine/api/images/CompositeImpl.java
@@ -19,8 +19,10 @@
import static java.util.Objects.requireNonNull;
import com.google.appengine.api.images.ImagesServicePb.CompositeImageOptions;
+import com.google.appengine.api.images.ImagesServicePb.ImageData;
import com.google.appengine.api.images.ImagesServicePb.ImagesCompositeRequest;
import java.util.Map;
+import java.util.function.Function;
/**
* Implementation of Composite using alpha blending.
@@ -68,11 +70,14 @@ final class CompositeImpl extends Composite {
/** {@inheritDoc} */
@Override
- void apply(ImagesCompositeRequest.Builder request, Map imageIndexMap) {
+ void apply(
+ ImagesCompositeRequest.Builder request,
+ Map imageIndexMap,
+ Function imageDataConverter) {
// TODO: What is the purpose of this map?
if (!imageIndexMap.containsKey(image)) {
imageIndexMap.put(image, request.build().getImageCount());
- request.addImage(ImagesServiceImpl.convertImageData(image));
+ request.addImage(imageDataConverter.apply(image));
}
CompositeImageOptions.Builder options = CompositeImageOptions.newBuilder();
int sourceId = requireNonNull(imageIndexMap.get(image));
diff --git a/api/src/main/java/com/google/appengine/api/images/GrpcImagesClient.java b/api/src/main/java/com/google/appengine/api/images/GrpcImagesClient.java
new file mode 100644
index 000000000..5bf3d22a9
--- /dev/null
+++ b/api/src/main/java/com/google/appengine/api/images/GrpcImagesClient.java
@@ -0,0 +1,142 @@
+// 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.images;
+
+import com.google.appengine.api.EnvironmentProvider;
+import com.google.appengine.api.SystemEnvironmentProvider;
+import com.google.appengine.api.images.proto.ImagesServiceGrpc;
+import com.google.auth.oauth2.GoogleCredentials;
+import com.google.auth.oauth2.IdTokenCredentials;
+import com.google.auth.oauth2.IdTokenProvider;
+import com.google.common.annotations.VisibleForTesting;
+import static com.google.common.base.Strings.isNullOrEmpty;
+import io.grpc.CallCredentials;
+import io.grpc.ManagedChannel;
+import io.grpc.auth.MoreCallCredentials;
+import io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder;
+import java.io.IOException;
+import java.net.URI;
+import static java.util.concurrent.TimeUnit.SECONDS;
+import java.net.URISyntaxException;
+
+/** Client for interacting with the gRPC based Images service. */
+class GrpcImagesClient {
+
+ private static final int MAX_MESSAGE_SIZE = 32 * 1024 * 1024; // 32MB
+ private final ManagedChannel channel;
+ private final EnvironmentProvider environmentProvider;
+ private final CallCredentials callCredentials;
+
+ // private ImagesServiceGrpc.ImagesServiceBlockingStub blockingStub;
+
+ public GrpcImagesClient() {
+ this(new SystemEnvironmentProvider(), getApplicationDefaultCredentials());
+ }
+
+ // Constructor for production
+ GrpcImagesClient(EnvironmentProvider environmentProvider, GoogleCredentials googleCredentials) {
+ this(environmentProvider, createOidcCredentials(environmentProvider, googleCredentials));
+ }
+
+ // Constructor for testing
+ @VisibleForTesting
+ GrpcImagesClient(EnvironmentProvider environmentProvider, CallCredentials callCredentials) {
+ this.environmentProvider = environmentProvider;
+ this.callCredentials = callCredentials;
+ String target = getTarget();
+ this.channel =
+ NettyChannelBuilder.forTarget(target)
+ .maxInboundMessageSize(MAX_MESSAGE_SIZE)
+ .keepAliveTime(60, SECONDS)
+ .useTransportSecurity()
+ .build();
+ }
+
+ private static GoogleCredentials getApplicationDefaultCredentials() {
+ try {
+ return GoogleCredentials.getApplicationDefault();
+ } catch (IOException e) {
+ throw new IllegalStateException("Failed to get Application Default Credentials", e);
+ }
+ }
+
+ private String getTarget() {
+ String endpoint =
+ environmentProvider.getenv(ImagesServiceFactoryImpl.IMAGES_SERVICE_ENDPOINT_ENV);
+ if (isNullOrEmpty(endpoint)) {
+ throw new IllegalStateException(
+ ImagesServiceFactoryImpl.IMAGES_SERVICE_ENDPOINT_ENV + " environment variable not set.");
+ }
+ try {
+ URI uri = new URI(endpoint);
+ String host = uri.getHost();
+ if (host == null) {
+ throw new IllegalStateException(
+ "Invalid URI in "
+ + ImagesServiceFactoryImpl.IMAGES_SERVICE_ENDPOINT_ENV
+ + ": "
+ + endpoint);
+ }
+ return host + ":443";
+ } catch (URISyntaxException e) {
+ throw new IllegalStateException(
+ "Invalid URI in "
+ + ImagesServiceFactoryImpl.IMAGES_SERVICE_ENDPOINT_ENV
+ + ": "
+ + endpoint,
+ e);
+ }
+ }
+
+ private static CallCredentials createOidcCredentials(
+ EnvironmentProvider environmentProvider, GoogleCredentials googleCredentials) {
+ String endpoint =
+ environmentProvider.getenv(ImagesServiceFactoryImpl.IMAGES_SERVICE_ENDPOINT_ENV);
+ if (isNullOrEmpty(endpoint)) {
+ throw new IllegalStateException(
+ ImagesServiceFactoryImpl.IMAGES_SERVICE_ENDPOINT_ENV + " environment variable not set.");
+ }
+
+ if (!(googleCredentials instanceof IdTokenProvider idTokenProvider)) {
+ throw new IllegalStateException(
+ "The Application Default Credentials do not support OIDC ID token generation.");
+ }
+
+ IdTokenCredentials idTokenCredentials =
+ IdTokenCredentials.newBuilder()
+ .setTargetAudience(endpoint)
+ .setIdTokenProvider(idTokenProvider)
+ .build();
+ return MoreCallCredentials.from(idTokenCredentials);
+ }
+
+ public ImagesServiceGrpc.ImagesServiceBlockingStub getBlockingStub() {
+ return ImagesServiceGrpc.newBlockingStub(channel).withCallCredentials(callCredentials);
+ }
+
+ public ImagesServiceGrpc.ImagesServiceFutureStub getFutureStub() {
+ return ImagesServiceGrpc.newFutureStub(channel).withCallCredentials(callCredentials);
+ }
+
+ public void shutdown() {
+ if (channel != null && !channel.isShutdown()) {
+ try {
+ channel.shutdown().awaitTermination(5, SECONDS);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ // Handle exception
+ }
+ }
+ }
+}
diff --git a/api/src/main/java/com/google/appengine/api/images/ImagesServiceFactoryImpl.java b/api/src/main/java/com/google/appengine/api/images/ImagesServiceFactoryImpl.java
index 8851452d7..98ef881fd 100644
--- a/api/src/main/java/com/google/appengine/api/images/ImagesServiceFactoryImpl.java
+++ b/api/src/main/java/com/google/appengine/api/images/ImagesServiceFactoryImpl.java
@@ -16,8 +16,13 @@
package com.google.appengine.api.images;
+import com.google.appengine.api.EnvironmentProvider;
+import com.google.appengine.api.SystemEnvironmentProvider;
import com.google.appengine.api.blobstore.BlobKey;
import com.google.appengine.api.blobstore.BlobstoreServiceFactory;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Supplier;
+import com.google.common.base.Suppliers;
import java.util.Collection;
/**
@@ -27,9 +32,30 @@
*/
final class ImagesServiceFactoryImpl implements IImagesServiceFactory {
+ @VisibleForTesting
+ static final String USE_CUSTOM_IMAGES_GRPC_SERVICE_ENV =
+ "APPENGINE_USE_CUSTOM_IMAGES_GRPC_SERVICE";
+
+ @VisibleForTesting
+ static final String IMAGES_SERVICE_ENDPOINT_ENV = "APPENGINE_IMAGES_SERVICE_ENDPOINT";
+
+ private EnvironmentProvider environmentProvider = new SystemEnvironmentProvider();
+
+ private static final Supplier grpcClientSupplier =
+ Suppliers.memoize(() -> new GrpcImagesClient());
+
+ @VisibleForTesting
+ void setEnvironmentProvider(EnvironmentProvider environmentProvider) {
+ this.environmentProvider = environmentProvider;
+ }
+
@Override
public ImagesService getImagesService() {
- return new ImagesServiceImpl();
+ GrpcImagesClient client = null;
+ if (Boolean.parseBoolean(environmentProvider.getenv(USE_CUSTOM_IMAGES_GRPC_SERVICE_ENV))) {
+ client = grpcClientSupplier.get();
+ }
+ return new ImagesServiceImpl(environmentProvider, client);
}
@Override
@@ -44,6 +70,12 @@ public Image makeImageFromBlob(BlobKey blobKey) {
@Override
public Image makeImageFromFilename(String filename) {
+ if (Boolean.parseBoolean(environmentProvider.getenv(USE_CUSTOM_IMAGES_GRPC_SERVICE_ENV))) {
+ if (!filename.startsWith("/gs/")) {
+ throw new IllegalArgumentException("Google storage filenames must be prefixed with /gs/");
+ }
+ return new ImageImpl(new BlobKey(filename));
+ }
BlobKey blobKey = BlobstoreServiceFactory.getBlobstoreService().createGsBlobKey(filename);
return new ImageImpl(blobKey);
}
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 dc402dd80..23fac426f 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
@@ -18,7 +18,14 @@
import static java.util.Objects.requireNonNull;
+import com.google.appengine.api.EnvironmentProvider;
+import com.google.appengine.api.SystemEnvironmentProvider;
+import com.google.appengine.api.blobstore.BlobInfo;
+import com.google.appengine.api.blobstore.BlobInfoFactory;
import com.google.appengine.api.blobstore.BlobKey;
+import com.google.appengine.api.blobstore.BlobstoreFailureException;
+import com.google.appengine.api.blobstore.BlobstoreInputStream;
+import com.google.appengine.api.blobstore.BlobstoreService;
import com.google.appengine.api.blobstore.BlobstoreServiceFactory;
import com.google.appengine.api.images.ImagesServicePb.ImageData;
import com.google.appengine.api.images.ImagesServicePb.ImagesCompositeRequest;
@@ -35,23 +42,132 @@
import com.google.appengine.api.images.ImagesServicePb.ImagesTransformResponse;
import com.google.appengine.api.images.ImagesServicePb.InputSettings.ORIENTATION_CORRECTION_TYPE;
import com.google.appengine.api.images.ImagesServicePb.OutputSettings.MIME_TYPE;
+import com.google.appengine.api.images.proto.ImagesServiceGrpc.ImagesServiceBlockingStub;
import com.google.appengine.api.utils.FutureWrapper;
import com.google.apphosting.api.ApiProxy;
+import com.google.cloud.storage.Blob;
+import com.google.cloud.storage.BlobId;
+import com.google.cloud.storage.Storage;
+import com.google.cloud.storage.StorageOptions;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.util.concurrent.ListenableFuture;
import com.google.protobuf.ByteString;
+import com.google.protobuf.ExtensionRegistry;
import com.google.protobuf.InvalidProtocolBufferException;
+import io.grpc.Status;
+import io.grpc.StatusRuntimeException;
import java.io.IOException;
+import java.io.InputStream;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Future;
+import java.util.logging.Logger;
import org.jspecify.annotations.Nullable;
-/**
- * Implementation of the ImagesService interface.
- *
- */
+/** Implementation of the ImagesService interface. */
final class ImagesServiceImpl implements ImagesService {
+ private static final Logger logger = Logger.getLogger(ImagesServiceImpl.class.getName());
+
static final String PACKAGE = "images";
+ private static final int MAX_BLOB_SIZE = 32 * 1024 * 1024; // 32MB
+
+ private final EnvironmentProvider environmentProvider;
+ private volatile GrpcImagesClient grpcClient;
+
+
+ private final BlobstoreService blobstoreService;
+ private final BlobstoreReference blobstoreReference;
+ private final BlobInfoFactory blobInfoFactory;
+ private final @Nullable Storage storage;
+
+ interface BlobstoreReference {
+ InputStream openStream(BlobKey key) throws IOException;
+ }
+
+ public ImagesServiceImpl() {
+ this(new SystemEnvironmentProvider(), null, null, null, null);
+ }
+
+ @VisibleForTesting
+ ImagesServiceImpl(EnvironmentProvider environmentProvider, GrpcImagesClient grpcClient) {
+ this(environmentProvider, grpcClient, null, null, null);
+ }
+
+ @VisibleForTesting
+ ImagesServiceImpl(
+ EnvironmentProvider environmentProvider,
+ GrpcImagesClient grpcClient,
+ BlobstoreService blobstoreService) {
+ this(environmentProvider, grpcClient, blobstoreService, null, null);
+ }
+
+ @VisibleForTesting
+ ImagesServiceImpl(
+ EnvironmentProvider environmentProvider,
+ GrpcImagesClient grpcClient,
+ BlobstoreService blobstoreService,
+ BlobstoreReference blobstoreReference) {
+ this(environmentProvider, grpcClient, blobstoreService, blobstoreReference, null);
+ }
+
+ @VisibleForTesting
+ ImagesServiceImpl(
+ EnvironmentProvider environmentProvider,
+ GrpcImagesClient grpcClient,
+ BlobstoreService blobstoreService,
+ BlobstoreReference blobstoreReference,
+ Storage storage) {
+ this(environmentProvider, grpcClient, blobstoreService, blobstoreReference, storage, null);
+ }
+
+ @VisibleForTesting
+ ImagesServiceImpl(
+ EnvironmentProvider environmentProvider,
+ GrpcImagesClient grpcClient,
+ BlobstoreService blobstoreService,
+ BlobstoreReference blobstoreReference,
+ Storage storage,
+ BlobInfoFactory blobInfoFactory) {
+ this.environmentProvider = environmentProvider;
+ this.grpcClient = grpcClient;
+ this.blobstoreService = blobstoreService;
+ this.storage = storage;
+ this.blobInfoFactory = blobInfoFactory != null ? blobInfoFactory : new BlobInfoFactory();
+ this.blobstoreReference =
+ blobstoreReference != null
+ ? blobstoreReference
+ : new BlobstoreReference() {
+ @Override
+ public InputStream openStream(BlobKey key) throws IOException {
+ return new BlobstoreInputStream(key);
+ }
+ };
+ }
+
+ // A package-private method to get the GrpcImagesClient instance, visible for testing.
+ @VisibleForTesting
+ GrpcImagesClient getGrpcClient() {
+ if (grpcClient == null) {
+ synchronized (this) {
+ if (grpcClient == null) {
+ grpcClient = new GrpcImagesClient();
+ }
+ }
+ }
+ return grpcClient;
+ }
+
+ private ImagesServiceBlockingStub getGrpcStub() {
+ return getGrpcClient().getBlockingStub();
+ }
+
+ @VisibleForTesting
+ boolean useGrpc() {
+ String envVar =
+ environmentProvider.getenv(ImagesServiceFactoryImpl.USE_CUSTOM_IMAGES_GRPC_SERVICE_ENV);
+ return Boolean.parseBoolean(envVar);
+ }
/** {@inheritDoc} */
@Override
@@ -98,23 +214,39 @@ public Image applyTransform(
Image image,
InputSettings inputSettings,
OutputSettings outputSettings) {
+ if (useGrpc()) {
+ try {
+ ImageData imageData = loadImageData(image, getBlobstoreService());
+ ImagesTransformRequest request =
+ generateImagesTransformRequest(transform, imageData, inputSettings, outputSettings)
+ .build();
+ ImagesTransformResponse response = getGrpcStub().transform(request);
+ image.setImageData(response.getImage().getContent().toByteArray());
+ return image;
+ } catch (StatusRuntimeException e) {
+ throw convertGrpcException(e);
+ }
+ }
ImagesTransformRequest.Builder request =
- generateImagesTransformRequest(transform, image, inputSettings, outputSettings);
+ generateImagesTransformRequest(
+ transform, convertImageData(image), inputSettings, outputSettings);
ImagesTransformResponse.Builder response = ImagesTransformResponse.newBuilder();
try {
- byte[] responseBytes = ApiProxy.makeSyncCall(PACKAGE, "Transform",
- request.build().toByteArray());
- response.mergeFrom(responseBytes);
+ byte[] responseBytes =
+ ApiProxy.makeSyncCall(PACKAGE, "Transform", request.build().toByteArray());
+ response.mergeFrom(responseBytes, ExtensionRegistry.getEmptyRegistry());
} catch (InvalidProtocolBufferException ex) {
throw new ImagesServiceFailureException("Invalid protocol buffer:", ex);
} catch (ApiProxy.ApplicationException ex) {
- throw convertApplicationException(request, ex);
+ throw convertApplicationException(ex);
}
image.setImageData(response.getImage().getContent().toByteArray());
return image;
}
+
+
/** {@inheritDoc} */
@Override
public Future applyTransformAsync(
@@ -122,17 +254,42 @@ public Future applyTransformAsync(
final Image image,
InputSettings inputSettings,
OutputSettings outputSettings) {
+ if (useGrpc()) {
+ ImageData imageData = loadImageData(image, getBlobstoreService());
+ ImagesTransformRequest request =
+ generateImagesTransformRequest(transform, imageData, inputSettings, outputSettings)
+ .build();
+ ListenableFuture responseFuture =
+ getGrpcClient().getFutureStub().transform(request);
+
+ return new FutureWrapper(responseFuture) {
+ @Override
+ protected Image wrap(ImagesTransformResponse response) {
+ image.setImageData(response.getImage().getContent().toByteArray());
+ return image;
+ }
+
+ @Override
+ protected Throwable convertException(Throwable cause) {
+ if (cause instanceof StatusRuntimeException statusRuntimeException) {
+ return convertGrpcException(statusRuntimeException);
+ }
+ return cause;
+ }
+ };
+ }
final ImagesTransformRequest.Builder request =
- generateImagesTransformRequest(transform, image, inputSettings, outputSettings);
+ generateImagesTransformRequest(
+ transform, convertImageData(image), inputSettings, outputSettings);
- Future responseBytes = ApiProxy.makeAsyncCall(PACKAGE, "Transform",
- request.build().toByteArray());
- return new FutureWrapper(responseBytes){
+ Future responseBytes =
+ ApiProxy.makeAsyncCall(PACKAGE, "Transform", request.build().toByteArray());
+ return new FutureWrapper(responseBytes) {
@Override
- protected Image wrap(byte @Nullable[] responseBytes) throws IOException {
+ protected Image wrap(byte @Nullable [] responseBytes) throws IOException {
ImagesTransformResponse.Builder response =
- ImagesTransformResponse.newBuilder()
- .mergeFrom(responseBytes);
+ ImagesTransformResponse.newBuilder()
+ .mergeFrom(responseBytes, ExtensionRegistry.getEmptyRegistry());
image.setImageData(response.getImage().getContent().toByteArray());
return image;
@@ -141,13 +298,15 @@ protected Image wrap(byte @Nullable[] responseBytes) throws IOException {
@Override
protected Throwable convertException(Throwable cause) {
if (cause instanceof ApiProxy.ApplicationException applicationException) {
- return convertApplicationException(request, applicationException);
+ return convertApplicationException(applicationException);
}
return cause;
}
};
}
+
+
/** {@inheritDoc} */
@Override
public Image composite(Collection composites, int width, int height, long color) {
@@ -200,15 +359,29 @@ public Image composite(
canvas.setOutput(convertOutputSettings(settings));
request.setCanvas(canvas);
- Map imageIdMap = new HashMap();
+ if (useGrpc()) {
+ Map imageIdMap = new HashMap<>();
+ BlobstoreService blobstoreService = getBlobstoreService();
+ for (Composite composite : composites) {
+ composite.apply(request, imageIdMap, img -> loadImageData(img, blobstoreService));
+ }
+ try {
+ ImagesCompositeResponse grpcResponse = getGrpcStub().composite(request.build());
+ return ImagesServiceFactory.makeImage(grpcResponse.getImage().getContent().toByteArray());
+ } catch (StatusRuntimeException e) {
+ throw convertGrpcException(e);
+ }
+ }
+
+ Map imageIdMap = new HashMap<>();
for (Composite composite : composites) {
- composite.apply(request, imageIdMap);
+ composite.apply(request, imageIdMap, ImagesServiceImpl::convertImageData);
}
try {
byte[] responseBytes = ApiProxy.makeSyncCall(PACKAGE, "Composite",
request.build().toByteArray());
- response.mergeFrom(responseBytes);
+ response.mergeFrom(responseBytes, ExtensionRegistry.getEmptyRegistry());
} catch (InvalidProtocolBufferException ex) {
throw new ImagesServiceFailureException("Invalid protocol buffer:", ex);
} catch (ApiProxy.ApplicationException ex) {
@@ -226,13 +399,43 @@ public Image composite(
/** {@inheritDoc} */
@Override
public int[][] histogram(Image image) {
+ if (useGrpc()) {
+ ImagesHistogramRequest.Builder request =
+ ImagesHistogramRequest.newBuilder()
+ .setImage(loadImageData(image, BlobstoreServiceFactory.getBlobstoreService()));
+ ImagesHistogramResponse response;
+ try {
+ response = getGrpcStub().histogram(request.build());
+ } catch (StatusRuntimeException e) {
+ throw convertGrpcException(e);
+ }
+ ImagesHistogram histogram = response.getHistogram();
+ int[][] result = new int[3][];
+ for (int i = 0; i < 3; i++) {
+ result[i] = new int[256];
+ }
+ int redCount = Math.min(256, histogram.getRedCount());
+ int greenCount = Math.min(256, histogram.getGreenCount());
+ int blueCount = Math.min(256, histogram.getBlueCount());
+
+ for (int i = 0; i < redCount; i++) {
+ result[0][i] = histogram.getRed(i);
+ }
+ for (int i = 0; i < greenCount; i++) {
+ result[1][i] = histogram.getGreen(i);
+ }
+ for (int i = 0; i < blueCount; i++) {
+ result[2][i] = histogram.getBlue(i);
+ }
+ return result;
+ }
ImagesHistogramRequest.Builder request = ImagesHistogramRequest.newBuilder();
ImagesHistogramResponse.Builder response = ImagesHistogramResponse.newBuilder();
request.setImage(convertImageData(image));
try {
byte[] responseBytes = ApiProxy.makeSyncCall(PACKAGE, "Histogram",
request.build().toByteArray());
- response.mergeFrom(responseBytes);
+ response.mergeFrom(responseBytes, ExtensionRegistry.getEmptyRegistry());
} catch (InvalidProtocolBufferException ex) {
throw new ImagesServiceFailureException("Invalid protocol buffer:", ex);
} catch (ApiProxy.ApplicationException ex) {
@@ -258,11 +461,19 @@ public int[][] histogram(Image image) {
/** {@inheritDoc} */
public String getServingUrl(BlobKey blobKey) {
+ if (useGrpc()) {
+ throw new UnsupportedOperationException(
+ "getServingUrl is not supported when using the gRPC Images Service.");
+ }
return getServingUrl(blobKey, false);
}
/** {@inheritDoc} */
public String getServingUrl(BlobKey blobKey, boolean secureUrl) {
+ if (useGrpc()) {
+ throw new UnsupportedOperationException(
+ "getServingUrl is not supported when using the gRPC Images Service.");
+ }
// The following check maintains the pre-existing contract for this method.
if (blobKey == null) {
throw new NullPointerException("blobKey cannot be null");
@@ -275,12 +486,20 @@ public String getServingUrl(BlobKey blobKey, boolean secureUrl) {
/** {@inheritDoc} */
@Override
public String getServingUrl(BlobKey blobKey, int imageSize, boolean crop) {
+ if (useGrpc()) {
+ throw new UnsupportedOperationException(
+ "getServingUrl is not supported when using the gRPC Images Service.");
+ }
return getServingUrl(blobKey, imageSize, crop, false);
}
/** {@inheritDoc} */
@Override
public String getServingUrl(BlobKey blobKey, int imageSize, boolean crop, boolean secureUrl) {
+ if (useGrpc()) {
+ throw new UnsupportedOperationException(
+ "getServingUrl is not supported when using the gRPC Images Service.");
+ }
// The following check maintains the pre-existing contract for this method.
if (blobKey == null) {
throw new NullPointerException("blobKey cannot be null");
@@ -295,6 +514,10 @@ public String getServingUrl(BlobKey blobKey, int imageSize, boolean crop, boolea
@Override
public String getServingUrl(ServingUrlOptions options) {
+ if (useGrpc()) {
+ throw new UnsupportedOperationException(
+ "getServingUrl is not supported when using the gRPC Images Service.");
+ }
ImagesGetUrlBaseRequest.Builder request = ImagesGetUrlBaseRequest.newBuilder();
ImagesGetUrlBaseResponse.Builder response = ImagesGetUrlBaseResponse.newBuilder();
@@ -316,7 +539,7 @@ public String getServingUrl(ServingUrlOptions options) {
try {
byte[] responseBytes = ApiProxy.makeSyncCall(PACKAGE, "GetUrlBase",
request.build().toByteArray());
- response.mergeFrom(responseBytes);
+ response.mergeFrom(responseBytes, ExtensionRegistry.getEmptyRegistry());
} catch (InvalidProtocolBufferException ex) {
throw new ImagesServiceFailureException("Invalid protocol buffer:", ex);
} catch (ApiProxy.ApplicationException ex) {
@@ -342,8 +565,11 @@ public String getServingUrl(ServingUrlOptions options) {
/** {@inheritDoc} */
@Override
public void deleteServingUrl(BlobKey blobKey) {
+ if (useGrpc()) {
+ throw new UnsupportedOperationException(
+ "deleteServingUrl is not supported when using the gRPC Images Service.");
+ }
ImagesDeleteUrlBaseRequest.Builder request = ImagesDeleteUrlBaseRequest.newBuilder();
- ImagesDeleteUrlBaseResponse.Builder response = ImagesDeleteUrlBaseResponse.newBuilder();
if (blobKey == null) {
throw new NullPointerException();
}
@@ -351,7 +577,7 @@ public void deleteServingUrl(BlobKey blobKey) {
try {
byte[] responseBytes = ApiProxy.makeSyncCall(PACKAGE, "DeleteUrlBase",
request.build().toByteArray());
- response.mergeFrom(responseBytes);
+ ImagesDeleteUrlBaseResponse unused = ImagesDeleteUrlBaseResponse.parseFrom(responseBytes, ExtensionRegistry.getEmptyRegistry());
} catch (InvalidProtocolBufferException ex) {
throw new ImagesServiceFailureException("Invalid protocol buffer:", ex);
} catch (ApiProxy.ApplicationException ex) {
@@ -364,6 +590,71 @@ public void deleteServingUrl(BlobKey blobKey) {
}
}
+ @VisibleForTesting
+ ImageData loadImageData(Image image, BlobstoreService blobstoreService) {
+ logger.info("loadImageData image: " + image);
+ BlobKey blobKey = image.getBlobKey();
+ logger.info("blobKey: " + blobKey);
+ if (blobKey != null) {
+ String keyString = blobKey.getKeyString();
+ logger.info("keyString: " + keyString);
+ if (keyString.startsWith("/gs/")) {
+ return fetchGcsImageData(keyString);
+ }
+
+ // Check if BlobInfo has GCS object name
+ BlobInfo blobInfo = blobInfoFactory.loadBlobInfo(blobKey);
+ logger.info(
+ "blobInfo: "
+ + blobInfo
+ + " GsObjectName: "
+ + (blobInfo != null ? blobInfo.getGsObjectName() : "null"));
+ if (blobInfo != null && blobInfo.getGsObjectName() != null) {
+ String gsObjectName = blobInfo.getGsObjectName();
+ logger.fine("Found GCS object name for BlobKey: " + gsObjectName);
+ try {
+ return fetchGcsImageData(gsObjectName);
+ } catch (ImagesServiceFailureException e) {
+ logger.fine(
+ "Failed to fetch GCS blob data, falling back to BlobstoreInputStream: "
+ + e.getMessage());
+ }
+ }
+ logger.fine("No GCS object name found for BlobKey, falling back to BlobstoreInputStream");
+
+ try (InputStream inputStream = blobstoreReference.openStream(blobKey)) {
+ return ImageData.newBuilder().setContent(ByteString.readFrom(inputStream)).build();
+ } catch (IOException | BlobstoreFailureException e) {
+ throw new ImagesServiceFailureException("Failed to fetch blob data", e);
+ }
+ }
+ return convertImageData(image);
+ }
+
+ private ImageData fetchGcsImageData(String keyString) {
+ try {
+ return ImageData.newBuilder()
+ .setContent(ByteString.copyFrom(fetchGcsContent(keyString)))
+ .setBlobKey(keyString)
+ .build();
+ } catch (RuntimeException e) {
+ throw new ImagesServiceFailureException("Failed to fetch GCS blob data", e);
+ }
+ }
+
+ private RuntimeException convertGrpcException(StatusRuntimeException e) {
+ Status.Code code = e.getStatus().getCode();
+ if (code == Status.Code.UNAUTHENTICATED || code == Status.Code.PERMISSION_DENIED) {
+ return new ImagesServiceFailureException("Authentication failed for Images Service", e);
+ } else if (code == Status.Code.RESOURCE_EXHAUSTED) {
+ return new ImagesServiceFailureException("Image too large", e);
+ } else if (code == Status.Code.INVALID_ARGUMENT) {
+ return new IllegalArgumentException(e.getStatus().getDescription(), e);
+ } else {
+ return new ImagesServiceFailureException(e.getStatus().getDescription(), e);
+ }
+ }
+
static ImageData convertImageData(Image image) {
ImageData.Builder builder = ImageData.newBuilder();
BlobKey blobKey = image.getBlobKey();
@@ -379,20 +670,22 @@ static ImageData convertImageData(Image image) {
private ImagesTransformRequest.Builder generateImagesTransformRequest(
Transform transform,
- Image image,
+ ImageData imageData,
InputSettings inputSettings,
OutputSettings outputSettings) {
ImagesTransformRequest.Builder request =
- ImagesTransformRequest.newBuilder()
- .setImage(convertImageData(image))
- .setOutput(convertOutputSettings(outputSettings))
- .setInput(convertInputSettings(inputSettings));
+ ImagesTransformRequest.newBuilder()
+ .setImage(imageData)
+ .setOutput(convertOutputSettings(outputSettings))
+ .setInput(convertInputSettings(inputSettings));
transform.apply(request);
if (request.getTransformCount() > MAX_TRANSFORMS_PER_REQUEST) {
throw new IllegalArgumentException(
- "A maximum of " + MAX_TRANSFORMS_PER_REQUEST + " basic transforms "
- + "can be requested in a single transform request");
+ "A maximum of "
+ + MAX_TRANSFORMS_PER_REQUEST
+ + " basic transforms "
+ + "can be requested in a single transform request");
}
return request;
}
@@ -414,7 +707,7 @@ private ImagesServicePb.OutputSettings convertOutputSettings(OutputSettings sett
pbSettings.setQuality(settings.getQuality());
}
}
- default -> throw new IllegalArgumentException("Invalid output encoding requested");
+
}
return pbSettings.build();
}
@@ -430,8 +723,7 @@ private ImagesServicePb.InputSettings convertInputSettings(InputSettings setting
return pbSettings.build();
}
- private RuntimeException convertApplicationException(ImagesTransformRequest.Builder request,
- ApiProxy.ApplicationException ex) {
+ private RuntimeException convertApplicationException(ApiProxy.ApplicationException ex) {
ErrorCode errorCode = ErrorCode.forNumber(ex.getApplicationError());
if (errorCode != null && errorCode != ErrorCode.UNSPECIFIED_ERROR) {
return new IllegalArgumentException(ex.getErrorDetail());
@@ -439,4 +731,72 @@ private RuntimeException convertApplicationException(ImagesTransformRequest.Buil
return new ImagesServiceFailureException(ex.getErrorDetail());
}
}
+
+ private BlobstoreService getBlobstoreService() {
+ return blobstoreService != null
+ ? blobstoreService
+ : BlobstoreServiceFactory.getBlobstoreService();
+ }
+
+ // We use double-checked locking here to allow lazy initialization and
+ // mock injection in tests without triggering eager class loading of heavy
+ // Storage clients which might fail in test environments.
+ private static class StorageHolder {
+ private static volatile Storage instance;
+
+ static Storage get() {
+ if (instance == null) {
+ synchronized (StorageHolder.class) {
+ if (instance == null) {
+ instance = StorageOptions.getDefaultInstance().getService();
+ }
+ }
+ }
+ return instance;
+ }
+
+ @VisibleForTesting
+ static void setForTesting(Storage storage) {
+ instance = storage;
+ }
+ }
+
+ Storage getStorage() {
+ return storage != null ? storage : StorageHolder.get();
+ }
+
+ @VisibleForTesting
+ static void setStorageForTesting(Storage storage) {
+ StorageHolder.setForTesting(storage);
+ }
+
+ private byte[] fetchGcsContent(String keyString) {
+ // keyString is like "/gs/bucket/object"
+ // Remove "/gs/" prefix if present
+ String path = keyString;
+ if (path.startsWith("/gs/")) {
+ path = path.substring(4);
+ } else if (path.startsWith("/")) {
+ path = path.substring(1);
+ }
+ int index = path.indexOf('/');
+ if (index == -1) {
+ throw new IllegalArgumentException("Invalid GCS key: " + keyString);
+ }
+ String bucket = path.substring(0, index);
+ String object = path.substring(index + 1);
+
+ Blob blob = getStorage().get(BlobId.of(bucket, object));
+ if (blob == null) {
+ throw new ImagesServiceFailureException("GCS blob not found: " + keyString);
+ }
+ if (blob.getSize() > MAX_BLOB_SIZE) {
+ throw new ImagesServiceFailureException(
+ "GCS blob is too large: " + blob.getSize() + " bytes");
+ }
+ byte[] content = blob.getContent();
+ logger.info(
+ "Successfully fetched GCS blob content for key: " + keyString + " size: " + content.length);
+ return content;
+ }
}
diff --git a/api/src/test/java/com/google/appengine/api/images/GrpcImagesClientTest.java b/api/src/test/java/com/google/appengine/api/images/GrpcImagesClientTest.java
new file mode 100644
index 000000000..12be765bf
--- /dev/null
+++ b/api/src/test/java/com/google/appengine/api/images/GrpcImagesClientTest.java
@@ -0,0 +1,94 @@
+// 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.images;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
+import static org.mockito.Mockito.when;
+
+import com.google.appengine.api.EnvironmentProvider;
+import io.grpc.CallCredentials;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@RunWith(JUnit4.class)
+public class GrpcImagesClientTest {
+
+ @Rule public final MockitoRule mocks = MockitoJUnit.rule();
+
+ @Mock private EnvironmentProvider mockEnvironmentProvider;
+ @Mock private CallCredentials mockCallCredentials;
+
+ @Test
+ public void constructor_validEndpointAndCreds_success() {
+ when(mockEnvironmentProvider.getenv(ImagesServiceFactoryImpl.IMAGES_SERVICE_ENDPOINT_ENV))
+ .thenReturn("https://my-service.run.app");
+
+ GrpcImagesClient client = new GrpcImagesClient(mockEnvironmentProvider, mockCallCredentials);
+ assertThat(client).isNotNull();
+ }
+
+ @Test
+ public void constructor_endpointNotSet_throwsException() {
+ when(mockEnvironmentProvider.getenv(ImagesServiceFactoryImpl.IMAGES_SERVICE_ENDPOINT_ENV))
+ .thenReturn(null);
+ IllegalStateException e =
+ assertThrows(
+ IllegalStateException.class,
+ () -> new GrpcImagesClient(mockEnvironmentProvider, mockCallCredentials));
+ assertThat(e)
+ .hasMessageThat()
+ .contains(
+ ImagesServiceFactoryImpl.IMAGES_SERVICE_ENDPOINT_ENV + " environment variable not set");
+ }
+
+ @Test
+ public void constructor_invalidEndpoint_throwsException() {
+ when(mockEnvironmentProvider.getenv(ImagesServiceFactoryImpl.IMAGES_SERVICE_ENDPOINT_ENV))
+ .thenReturn("://my-service");
+ IllegalStateException e =
+ assertThrows(
+ IllegalStateException.class,
+ () -> new GrpcImagesClient(mockEnvironmentProvider, mockCallCredentials));
+ assertThat(e)
+ .hasMessageThat()
+ .contains("Invalid URI in " + ImagesServiceFactoryImpl.IMAGES_SERVICE_ENDPOINT_ENV);
+ }
+
+ @Test
+ public void constructor_endpointMissingHost_throwsException() {
+ when(mockEnvironmentProvider.getenv(ImagesServiceFactoryImpl.IMAGES_SERVICE_ENDPOINT_ENV))
+ .thenReturn("https://");
+ IllegalStateException e =
+ assertThrows(
+ IllegalStateException.class,
+ () -> new GrpcImagesClient(mockEnvironmentProvider, mockCallCredentials));
+ assertThat(e)
+ .hasMessageThat()
+ .contains("Invalid URI in " + ImagesServiceFactoryImpl.IMAGES_SERVICE_ENDPOINT_ENV);
+ }
+
+ @Test
+ public void getBlockingStub_returnsStub() {
+ when(mockEnvironmentProvider.getenv(ImagesServiceFactoryImpl.IMAGES_SERVICE_ENDPOINT_ENV))
+ .thenReturn("https://my-service.run.app");
+ GrpcImagesClient client = new GrpcImagesClient(mockEnvironmentProvider, mockCallCredentials);
+ assertThat(client.getBlockingStub()).isNotNull();
+ }
+}
diff --git a/api/src/test/java/com/google/appengine/api/images/ImagesServiceFactoryImplTest.java b/api/src/test/java/com/google/appengine/api/images/ImagesServiceFactoryImplTest.java
new file mode 100644
index 000000000..0ae60314c
--- /dev/null
+++ b/api/src/test/java/com/google/appengine/api/images/ImagesServiceFactoryImplTest.java
@@ -0,0 +1,67 @@
+/*
+ * 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.images;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.google.appengine.api.EnvironmentProvider;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class ImagesServiceFactoryImplTest {
+
+ private ImagesServiceFactoryImpl factory;
+ private EnvironmentProvider mockEnvironmentProvider;
+
+ @Before
+ public void setUp() {
+ factory = new ImagesServiceFactoryImpl();
+ mockEnvironmentProvider = mock(EnvironmentProvider.class);
+ factory.setEnvironmentProvider(mockEnvironmentProvider);
+ }
+
+ @Test
+ public void makeImageFromFilename_newBehavior_trueEnv_gsPrefix() {
+ when(mockEnvironmentProvider.getenv(
+ ImagesServiceFactoryImpl.USE_CUSTOM_IMAGES_GRPC_SERVICE_ENV))
+ .thenReturn("true");
+ String filename = "/gs/bucket/object";
+
+ // Should NOT call BlobstoreServiceFactory (which would fail in this env)
+ Image image = factory.makeImageFromFilename(filename);
+
+ assertThat(image.getBlobKey()).isNotNull();
+ assertThat(image.getBlobKey().getKeyString()).isEqualTo(filename);
+ }
+
+ @Test
+ public void makeImageFromFilename_newBehavior_trueEnv_noGsPrefix_throwsException() {
+ when(mockEnvironmentProvider.getenv(
+ ImagesServiceFactoryImpl.USE_CUSTOM_IMAGES_GRPC_SERVICE_ENV))
+ .thenReturn("true");
+ String filename = "not/gs/path";
+
+ IllegalArgumentException e =
+ assertThrows(IllegalArgumentException.class, () -> factory.makeImageFromFilename(filename));
+ assertThat(e).hasMessageThat().contains("must be prefixed with /gs/");
+ }
+}
diff --git a/api/src/test/java/com/google/appengine/api/images/ImagesServiceImplTest.java b/api/src/test/java/com/google/appengine/api/images/ImagesServiceImplTest.java
new file mode 100644
index 000000000..27bb5bc8b
--- /dev/null
+++ b/api/src/test/java/com/google/appengine/api/images/ImagesServiceImplTest.java
@@ -0,0 +1,524 @@
+// Copyright 2024 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.images;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+import java.util.concurrent.ExecutionException;
+
+import com.google.appengine.api.EnvironmentProvider;
+import com.google.appengine.api.blobstore.BlobInfo;
+import com.google.appengine.api.blobstore.BlobInfoFactory;
+import com.google.appengine.api.blobstore.BlobKey;
+import com.google.appengine.api.blobstore.BlobstoreFailureException;
+import com.google.appengine.api.blobstore.BlobstoreService;
+import com.google.appengine.api.images.ImagesService.OutputEncoding;
+import com.google.appengine.api.images.ImagesServicePb.ImageData;
+import com.google.appengine.api.images.ImagesServicePb.ImagesHistogram;
+import com.google.appengine.api.images.ImagesServicePb.ImagesHistogramRequest;
+import com.google.appengine.api.images.ImagesServicePb.ImagesHistogramResponse;
+import com.google.appengine.api.images.ImagesServicePb.ImagesTransformRequest;
+import com.google.appengine.api.images.ImagesServicePb.ImagesTransformResponse;
+import com.google.appengine.api.images.proto.ImagesServiceGrpc;
+import com.google.cloud.storage.Blob;
+import com.google.cloud.storage.BlobId;
+import com.google.cloud.storage.Storage;
+import com.google.protobuf.ByteString;
+import io.grpc.ManagedChannel;
+import io.grpc.Status;
+import io.grpc.StatusRuntimeException;
+import io.grpc.inprocess.InProcessChannelBuilder;
+import io.grpc.inprocess.InProcessServerBuilder;
+import io.grpc.stub.StreamObserver;
+import io.grpc.testing.GrpcCleanupRule;
+import java.io.ByteArrayInputStream;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import java.time.Instant;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.Future;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+@RunWith(JUnit4.class)
+public class ImagesServiceImplTest {
+
+ @Rule public final MockitoRule mocks = MockitoJUnit.rule();
+ @Rule public final GrpcCleanupRule grpcCleanup = new GrpcCleanupRule();
+
+ @Mock private EnvironmentProvider mockEnvironmentProvider;
+ @Mock private GrpcImagesClient mockGrpcImagesClient;
+ @Mock private BlobstoreService mockBlobstoreService;
+ @Mock private BlobInfoFactory mockBlobInfoFactory;
+ @Mock private ImagesServiceImpl.BlobstoreReference mockBlobstoreReference;
+ @Mock private Storage mockStorage;
+ @Mock private Blob mockBlob;
+
+ private ImagesServiceImpl imagesService;
+ private ManagedChannel channel;
+ private ImagesServiceGrpc.ImagesServiceBlockingStub blockingStub;
+ private ImagesServiceGrpc.ImagesServiceFutureStub futureStub;
+
+ // Mock implementation of the ImagesService.
+ private final ImagesServiceGrpc.ImagesServiceImplBase serviceImpl =
+ new ImagesServiceGrpc.ImagesServiceImplBase() {
+ @Override
+ public void transform(
+ ImagesTransformRequest request,
+ StreamObserver responseObserver) {
+ // For now, just return a default response
+ ImagesTransformResponse response =
+ ImagesTransformResponse.newBuilder()
+ .setImage(
+ ImageData.newBuilder().setContent(ByteString.copyFromUtf8("transformed")))
+ .build();
+ responseObserver.onNext(response);
+ responseObserver.onCompleted();
+ }
+
+ @Override
+ public void composite(
+ ImagesServicePb.ImagesCompositeRequest request,
+ StreamObserver responseObserver) {
+ ImagesServicePb.ImagesCompositeResponse response =
+ ImagesServicePb.ImagesCompositeResponse.newBuilder()
+ .setImage(
+ ImageData.newBuilder().setContent(ByteString.copyFromUtf8("composited")))
+ .build();
+ responseObserver.onNext(response);
+ responseObserver.onCompleted();
+ }
+
+ @Override
+ public void histogram(
+ ImagesHistogramRequest request,
+ StreamObserver responseObserver) {
+
+ // Fill the rest with 0s to match 256 size if needed, but the loop in impl handles it.
+ // Actually, proto repeated fields are just lists. The array copy loop assumes 256 size or
+ // less?
+ // "for (int i = 0; i < 256; i++) { result[0][i] = histogram.getRed(i); }"
+ // If the list is shorter than 256, getRed(i) might throw IndexOutOfBoundsException or
+ // return default?
+ // Protobuf list access: getRedList().get(i).
+ // Let's populate 256 values to be safe and correct.
+ ImagesHistogram.Builder histogramBuilder = ImagesHistogram.newBuilder();
+ for (int i = 0; i < 256; i++) {
+ histogramBuilder.addRed(i).addGreen(i).addBlue(i);
+ }
+
+ ImagesHistogramResponse response =
+ ImagesHistogramResponse.newBuilder().setHistogram(histogramBuilder).build();
+ responseObserver.onNext(response);
+ responseObserver.onCompleted();
+ }
+ };
+
+ @Test
+ public void histogram_grpcPath_success() throws Exception {
+ setUpGrpc(true);
+ Image image = ImagesServiceFactory.makeImage(new byte[] {1, 2, 3});
+
+ int[][] result = imagesService.histogram(image);
+
+ assertThat(result).hasLength(3);
+ assertThat(result[0]).hasLength(256);
+ assertThat(result[1]).hasLength(256);
+ assertThat(result[2]).hasLength(256);
+
+ // Check a few values based on mock
+ assertThat(result[0][0]).isEqualTo(0);
+ assertThat(result[0][255]).isEqualTo(255);
+ assertThat(result[1][100]).isEqualTo(100);
+ assertThat(result[2][50]).isEqualTo(50);
+ }
+
+ // Re-write to allow dynamic behavior of mock service
+ private void setupGrpcService(ImagesServiceGrpc.ImagesServiceImplBase serviceImplementation)
+ throws Exception {
+ String serverName = InProcessServerBuilder.generateName();
+ grpcCleanup.register(
+ InProcessServerBuilder.forName(serverName)
+ .directExecutor()
+ .addService(serviceImplementation)
+ .build()
+ .start());
+ channel =
+ grpcCleanup.register(InProcessChannelBuilder.forName(serverName).directExecutor().build());
+ blockingStub = ImagesServiceGrpc.newBlockingStub(channel);
+ futureStub = ImagesServiceGrpc.newFutureStub(channel);
+ when(mockGrpcImagesClient.getBlockingStub()).thenReturn(blockingStub);
+ when(mockGrpcImagesClient.getFutureStub()).thenReturn(futureStub);
+ imagesService =
+ new ImagesServiceImpl(
+ mockEnvironmentProvider, mockGrpcImagesClient, null, mockBlobstoreReference);
+
+ when(mockEnvironmentProvider.getenv(
+ ImagesServiceFactoryImpl.USE_CUSTOM_IMAGES_GRPC_SERVICE_ENV))
+ .thenReturn("true");
+ }
+
+ @Test
+ public void applyTransformAsync_grpcPath_authError_test() throws Exception {
+ ImagesServiceGrpc.ImagesServiceImplBase errorService =
+ new ImagesServiceGrpc.ImagesServiceImplBase() {
+ @Override
+ public void transform(
+ ImagesTransformRequest request,
+ StreamObserver responseObserver) {
+ responseObserver.onError(new StatusRuntimeException(Status.UNAUTHENTICATED));
+ }
+ };
+ setupGrpcService(errorService);
+
+ Image originalImage = ImagesServiceFactory.makeImage(new byte[] {1, 2, 3});
+ Transform transform = new Crop(0, 0, 1, 1);
+ OutputSettings settings = new OutputSettings(OutputEncoding.PNG);
+
+ ExecutionException e =
+ assertThrows(
+ ExecutionException.class,
+ () -> imagesService.applyTransformAsync(transform, originalImage, settings).get());
+ assertThat(e).hasCauseThat().isInstanceOf(ImagesServiceFailureException.class);
+ assertThat(e).hasCauseThat().hasMessageThat().contains("Authentication failed");
+ }
+
+ @Test
+ public void applyTransformAsync_grpcPath_tooLargeError_test() throws Exception {
+ ImagesServiceGrpc.ImagesServiceImplBase errorService =
+ new ImagesServiceGrpc.ImagesServiceImplBase() {
+ @Override
+ public void transform(
+ ImagesTransformRequest request,
+ StreamObserver responseObserver) {
+ responseObserver.onError(new StatusRuntimeException(Status.RESOURCE_EXHAUSTED));
+ }
+ };
+ setupGrpcService(errorService);
+
+ Image originalImage = ImagesServiceFactory.makeImage(new byte[] {1, 2, 3});
+ Transform transform = new Crop(0, 0, 1, 1);
+ OutputSettings settings = new OutputSettings(OutputEncoding.PNG);
+
+ ExecutionException e =
+ assertThrows(
+ ExecutionException.class,
+ () -> imagesService.applyTransformAsync(transform, originalImage, settings).get());
+ assertThat(e).hasCauseThat().isInstanceOf(ImagesServiceFailureException.class);
+ assertThat(e).hasCauseThat().hasMessageThat().contains("Image too large");
+ }
+
+ @Test
+ public void applyTransformAsync_grpcPath_invalidArgument_test() throws Exception {
+ ImagesServiceGrpc.ImagesServiceImplBase errorService =
+ new ImagesServiceGrpc.ImagesServiceImplBase() {
+ @Override
+ public void transform(
+ ImagesTransformRequest request,
+ StreamObserver responseObserver) {
+ responseObserver.onError(
+ new StatusRuntimeException(Status.INVALID_ARGUMENT.withDescription("Bad arg")));
+ }
+ };
+ setupGrpcService(errorService);
+
+ Image originalImage = ImagesServiceFactory.makeImage(new byte[] {1, 2, 3});
+ Transform transform = new Crop(0, 0, 1, 1);
+ OutputSettings settings = new OutputSettings(OutputEncoding.PNG);
+
+ ExecutionException e =
+ assertThrows(
+ ExecutionException.class,
+ () -> imagesService.applyTransformAsync(transform, originalImage, settings).get());
+ assertThat(e).hasCauseThat().isInstanceOf(IllegalArgumentException.class);
+ assertThat(e).hasCauseThat().hasMessageThat().contains("Bad arg");
+ }
+
+ @Test
+ public void composite_grpcPath_success() throws Exception {
+ setUpGrpc(true);
+ Image image1 = ImagesServiceFactory.makeImage(new byte[] {1});
+ Image image2 = ImagesServiceFactory.makeImage(new byte[] {2});
+ Composite composite1 =
+ ImagesServiceFactory.makeComposite(image1, 0, 0, 1.0f, Composite.Anchor.TOP_LEFT);
+ Composite composite2 =
+ ImagesServiceFactory.makeComposite(image2, 10, 10, 0.5f, Composite.Anchor.CENTER_CENTER);
+ List composites = Arrays.asList(composite1, composite2);
+ OutputSettings settings = new OutputSettings(OutputEncoding.PNG);
+
+ Image result = imagesService.composite(composites, 100, 100, 0L, settings);
+
+ assertThat(result).isNotNull();
+ assertThat(result.getImageData())
+ .isEqualTo(ByteString.copyFromUtf8("composited").toByteArray());
+ }
+
+ @Test
+ public void loadImageData_withImageContent() throws Exception {
+ setUpGrpc(false);
+ byte[] content = new byte[] {1, 2, 3};
+ Image image = ImagesServiceFactory.makeImage(content);
+
+ ImageData imageData = imagesService.loadImageData(image, mockBlobstoreService);
+
+ assertThat(imageData.hasBlobKey()).isFalse();
+ assertThat(imageData.getContent().toByteArray()).isEqualTo(content);
+ }
+
+ @Test
+ public void loadImageData_withGsBlobKey() throws Exception {
+ setUpGrpc(false);
+ String gsKey = "/gs/bucket/object";
+ BlobKey blobKey = new BlobKey(gsKey);
+ Image image = ImagesServiceFactory.makeImageFromBlob(blobKey);
+ byte[] content = new byte[] {1, 2, 3};
+
+ when(mockStorage.get(BlobId.of("bucket", "object"))).thenReturn(mockBlob);
+ when(mockBlob.getContent()).thenReturn(content);
+
+ ImageData imageData = imagesService.loadImageData(image, mockBlobstoreService);
+
+ assertThat(imageData.getBlobKey()).isEqualTo(gsKey);
+ assertThat(imageData.getContent().toByteArray()).isEqualTo(content);
+ }
+
+ @Test
+ public void loadImageData_withBlobInfoFallback_success() throws Exception {
+ setUpGrpc(false);
+ BlobKey blobKey = new BlobKey("some_opaque_key");
+ Image image = ImagesServiceFactory.makeImageFromBlob(blobKey);
+ BlobInfo blobInfo =
+ new BlobInfo(
+ blobKey, "type", Date.from(Instant.now()), "file", 0, "hash", "/gs/bucket/object");
+ when(mockBlobInfoFactory.loadBlobInfo(blobKey)).thenReturn(blobInfo);
+ when(mockStorage.get(BlobId.of("bucket", "object"))).thenReturn(mockBlob);
+ when(mockBlob.getContent()).thenReturn("blobContent".getBytes(UTF_8));
+ ImageData imageData = imagesService.loadImageData(image, mockBlobstoreService);
+ assertThat(imageData.getBlobKey()).isEqualTo("/gs/bucket/object");
+ assertThat(imageData.getContent().isEmpty()).isFalse();
+ assertThat(imageData.getContent().toStringUtf8()).isEqualTo("blobContent");
+ }
+
+ @Test
+ public void loadImageData_withLegacyBlobKey_success() throws Exception {
+ setUpGrpc(false);
+
+ BlobKey blobKey = new BlobKey("legacy-blob-key");
+ byte[] fetchedData = new byte[] {4, 5, 6};
+
+ when(mockBlobstoreReference.openStream(eq(blobKey)))
+ .thenReturn(new ByteArrayInputStream(fetchedData));
+
+ Image image = ImagesServiceFactory.makeImageFromBlob(blobKey);
+
+ ImageData imageData = imagesService.loadImageData(image, mockBlobstoreService);
+
+ assertThat(imageData.hasBlobKey()).isFalse();
+ assertThat(imageData.getContent().toByteArray()).isEqualTo(fetchedData);
+ }
+
+ @Test
+ public void loadImageData_withLegacyBlobKey_failure() throws Exception {
+ setUpGrpc(false);
+ String legacyKey = "legacy-blob-key";
+ BlobKey blobKey = new BlobKey(legacyKey);
+ Image image = ImagesServiceFactory.makeImageFromBlob(blobKey);
+
+ when(mockBlobstoreReference.openStream(eq(blobKey)))
+ .thenThrow(new BlobstoreFailureException("Fetch failed"));
+
+ ImagesServiceFailureException e =
+ assertThrows(
+ ImagesServiceFailureException.class,
+ () -> imagesService.loadImageData(image, mockBlobstoreService));
+ assertThat(e).hasMessageThat().contains("Failed to fetch blob data");
+ assertThat(e).hasCauseThat().isInstanceOf(BlobstoreFailureException.class);
+ }
+
+ public void setUpGrpc(boolean useGrpc) throws Exception {
+ ImagesServiceImpl.setStorageForTesting(mockStorage);
+ when(mockEnvironmentProvider.getenv(
+ ImagesServiceFactoryImpl.USE_CUSTOM_IMAGES_GRPC_SERVICE_ENV))
+ .thenReturn(Boolean.toString(useGrpc));
+
+ if (useGrpc) {
+ String serverName = InProcessServerBuilder.generateName();
+ grpcCleanup.register(
+ InProcessServerBuilder.forName(serverName)
+ .directExecutor()
+ .addService(serviceImpl)
+ .build()
+ .start());
+ channel =
+ grpcCleanup.register(
+ InProcessChannelBuilder.forName(serverName).directExecutor().build());
+ blockingStub = ImagesServiceGrpc.newBlockingStub(channel);
+ futureStub = ImagesServiceGrpc.newFutureStub(channel);
+ when(mockGrpcImagesClient.getBlockingStub()).thenReturn(blockingStub);
+ when(mockGrpcImagesClient.getFutureStub()).thenReturn(futureStub);
+
+ imagesService =
+ new ImagesServiceImpl(
+ mockEnvironmentProvider,
+ mockGrpcImagesClient,
+ mockBlobstoreService,
+ mockBlobstoreReference,
+ mockStorage,
+ mockBlobInfoFactory);
+ } else {
+ imagesService =
+ new ImagesServiceImpl(
+ mockEnvironmentProvider,
+ null,
+ null,
+ mockBlobstoreReference,
+ mockStorage,
+ mockBlobInfoFactory);
+ }
+ }
+
+ @Test
+ public void useGrpc_envVarSetTrue_returnsTrue() throws Exception {
+ setUpGrpc(true);
+ assertThat(imagesService.useGrpc()).isTrue();
+ }
+
+ @Test
+ public void useGrpc_envVarSetFalse_returnsFalse() throws Exception {
+ setUpGrpc(false);
+ assertThat(imagesService.useGrpc()).isFalse();
+ }
+
+ @Test
+ public void useGrpc_envVarNotSet_returnsFalse() {
+ when(mockEnvironmentProvider.getenv(
+ ImagesServiceFactoryImpl.USE_CUSTOM_IMAGES_GRPC_SERVICE_ENV))
+ .thenReturn(null);
+ imagesService =
+ new ImagesServiceImpl(
+ mockEnvironmentProvider, null, null, mockBlobstoreReference, null, mockBlobInfoFactory);
+ assertThat(imagesService.useGrpc()).isFalse();
+ }
+
+ @Test
+ public void useGrpc_envVarInvalid_returnsFalse() {
+ when(mockEnvironmentProvider.getenv(
+ ImagesServiceFactoryImpl.USE_CUSTOM_IMAGES_GRPC_SERVICE_ENV))
+ .thenReturn("yes");
+ imagesService =
+ new ImagesServiceImpl(
+ mockEnvironmentProvider, null, null, mockBlobstoreReference, null, mockBlobInfoFactory);
+ assertThat(imagesService.useGrpc()).isFalse();
+ }
+
+ @Test
+ public void applyTransformAsync_grpcPath_success() throws Exception {
+ setUpGrpc(true);
+ Image originalImage = ImagesServiceFactory.makeImage(new byte[] {1, 2, 3});
+ Transform transform = new Crop(0, 0, 1, 1);
+ OutputSettings settings = new OutputSettings(OutputEncoding.PNG);
+
+ Future future = imagesService.applyTransformAsync(transform, originalImage, settings);
+ Image transformedImage = future.get();
+
+ assertThat(transformedImage).isNotNull();
+ assertThat(transformedImage.getImageData())
+ .isEqualTo(ByteString.copyFromUtf8("transformed").toByteArray());
+ }
+
+ @Test
+ public void applyTransformAsync_grpcPath_withLegacyBlobKey_success() throws Exception {
+ setUpGrpc(true);
+ String legacyKey = "legacy-blob-key";
+ BlobKey blobKey = new BlobKey(legacyKey);
+ Image originalImage = ImagesServiceFactory.makeImageFromBlob(blobKey);
+ Transform transform = new Crop(0, 0, 1, 1);
+ OutputSettings settings = new OutputSettings(OutputEncoding.PNG);
+ byte[] fetchedData = new byte[] {4, 5, 6};
+
+ when(mockBlobstoreReference.openStream(eq(blobKey)))
+ .thenReturn(new ByteArrayInputStream(fetchedData));
+
+ Future future = imagesService.applyTransformAsync(transform, originalImage, settings);
+ Image transformedImage = future.get();
+
+ assertThat(transformedImage).isNotNull();
+ assertThat(transformedImage.getImageData())
+ .isEqualTo(ByteString.copyFromUtf8("transformed").toByteArray());
+ }
+
+ @Test
+ // Suppressing deprecation warnings as we are testing deprecated methods or options,
+ // and UnnecessaryJavacSuppressWarnings to avoid warnings in different compiler versions.
+ @SuppressWarnings({"deprecation", "UnnecessaryJavacSuppressWarnings"})
+ public void getServingUrl_grpcPath_throwsUnsupportedOperationException() throws Exception {
+ setUpGrpc(true);
+ // Use a valid blob key format to avoid other validations if they were to run (though they
+ // shouldn't)
+ String gsKey = "/gs/bucket/object";
+ BlobKey blobKey = new BlobKey(gsKey);
+
+ // Test getServingUrl(BlobKey)
+ UnsupportedOperationException e1 =
+ assertThrows(
+ UnsupportedOperationException.class, () -> imagesService.getServingUrl(blobKey));
+ assertThat(e1).hasMessageThat().contains("getServingUrl is not supported");
+
+ // Test getServingUrl(BlobKey, boolean)
+ UnsupportedOperationException e2 =
+ assertThrows(
+ UnsupportedOperationException.class, () -> imagesService.getServingUrl(blobKey, true));
+ assertThat(e2).hasMessageThat().contains("getServingUrl is not supported");
+
+ // Test getServingUrl(BlobKey, int, boolean)
+ UnsupportedOperationException e3 =
+ assertThrows(
+ UnsupportedOperationException.class,
+ () -> imagesService.getServingUrl(blobKey, 32, true));
+ assertThat(e3).hasMessageThat().contains("getServingUrl is not supported");
+
+ // Test getServingUrl(BlobKey, int, boolean, boolean)
+ UnsupportedOperationException e4 =
+ assertThrows(
+ UnsupportedOperationException.class,
+ () -> imagesService.getServingUrl(blobKey, 32, true, true));
+ assertThat(e4).hasMessageThat().contains("getServingUrl is not supported");
+
+ // Test getServingUrl(ServingUrlOptions)
+ ServingUrlOptions options = ServingUrlOptions.Builder.withBlobKey(blobKey);
+ UnsupportedOperationException e5 =
+ assertThrows(
+ UnsupportedOperationException.class, () -> imagesService.getServingUrl(options));
+ assertThat(e5).hasMessageThat().contains("getServingUrl is not supported");
+ }
+
+ @Test
+ public void deleteServingUrl_grpcPath_throwsUnsupportedOperationException() throws Exception {
+ setUpGrpc(true);
+ BlobKey blobKey = new BlobKey("blob-key");
+
+ UnsupportedOperationException e =
+ assertThrows(
+ UnsupportedOperationException.class, () -> imagesService.deleteServingUrl(blobKey));
+ assertThat(e).hasMessageThat().contains("deleteServingUrl is not supported");
+ }
+}
diff --git a/api_dev/pom.xml b/api_dev/pom.xml
index 6194e10d5..a8bdb6ee3 100644
--- a/api_dev/pom.xml
+++ b/api_dev/pom.xml
@@ -23,7 +23,7 @@
com.google.appengine
parent
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
jar
diff --git a/api_dev/src/test/java/com/google/appengine/api/images/CompositeImplTest.java b/api_dev/src/test/java/com/google/appengine/api/images/CompositeImplTest.java
index 917706152..3c36eb7e7 100644
--- a/api_dev/src/test/java/com/google/appengine/api/images/CompositeImplTest.java
+++ b/api_dev/src/test/java/com/google/appengine/api/images/CompositeImplTest.java
@@ -19,6 +19,8 @@
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
+import com.google.appengine.api.images.ImagesServicePb.ImageData;
+import com.google.protobuf.ByteString;
import java.util.HashMap;
import java.util.Map;
import org.junit.Before;
@@ -58,7 +60,10 @@ public void testApply_singleImage() throws Exception {
float opacity = 0.8f;
Composite.Anchor anchor = Composite.Anchor.CENTER_LEFT;
Composite composite = new CompositeImpl(image, xOffset, yOffset, opacity, anchor);
- composite.apply(request, imageIndexMap);
+ composite.apply(
+ request,
+ imageIndexMap,
+ img -> ImageData.newBuilder().setContent(ByteString.copyFrom(img.getImageData())).build());
assertThat(request.getOptionsCount()).isEqualTo(1);
assertThat(request.getOptions(0).getXOffset()).isEqualTo(xOffset);
assertThat(request.getOptions(0).getYOffset()).isEqualTo(yOffset);
@@ -81,9 +86,15 @@ public void testApply_singleImageMultipleOptions() throws Exception {
float otherOpacity = 0.1f;
Composite.Anchor otherAnchor = Composite.Anchor.BOTTOM_RIGHT;
Composite composite = new CompositeImpl(image, xOffset, yOffset, opacity, anchor);
- composite.apply(request, imageIndexMap);
+ composite.apply(
+ request,
+ imageIndexMap,
+ img -> ImageData.newBuilder().setContent(ByteString.copyFrom(img.getImageData())).build());
composite = new CompositeImpl(image, otherXOffset, otherYOffset, otherOpacity, otherAnchor);
- composite.apply(request, imageIndexMap);
+ composite.apply(
+ request,
+ imageIndexMap,
+ img -> ImageData.newBuilder().setContent(ByteString.copyFrom(img.getImageData())).build());
assertThat(request.getOptionsCount()).isEqualTo(2);
assertThat(request.getOptions(0).getXOffset()).isEqualTo(xOffset);
assertThat(request.getOptions(0).getYOffset()).isEqualTo(yOffset);
@@ -116,10 +127,16 @@ public void testApply_identicalImagesMultipleOptions() throws Exception {
float otherOpacity = 0.3f;
Composite.Anchor otherAnchor = Composite.Anchor.BOTTOM_RIGHT;
Composite composite = new CompositeImpl(image, xOffset, yOffset, opacity, anchor);
- composite.apply(request, imageIndexMap);
+ composite.apply(
+ request,
+ imageIndexMap,
+ img -> ImageData.newBuilder().setContent(ByteString.copyFrom(img.getImageData())).build());
composite =
new CompositeImpl(otherImage, otherXOffset, otherYOffset, otherOpacity, otherAnchor);
- composite.apply(request, imageIndexMap);
+ composite.apply(
+ request,
+ imageIndexMap,
+ img -> ImageData.newBuilder().setContent(ByteString.copyFrom(img.getImageData())).build());
assertThat(request.getOptionsCount()).isEqualTo(2);
assertThat(request.getOptions(0).getXOffset()).isEqualTo(xOffset);
assertThat(request.getOptions(0).getYOffset()).isEqualTo(yOffset);
@@ -152,10 +169,16 @@ public void testApply_multipleImagesSingleOptions() throws Exception {
float otherOpacity = 0.1111f;
Composite.Anchor otherAnchor = Composite.Anchor.BOTTOM_RIGHT;
Composite composite = new CompositeImpl(image, xOffset, yOffset, opacity, anchor);
- composite.apply(request, imageIndexMap);
+ composite.apply(
+ request,
+ imageIndexMap,
+ img -> ImageData.newBuilder().setContent(ByteString.copyFrom(img.getImageData())).build());
composite =
new CompositeImpl(otherImage, otherXOffset, otherYOffset, otherOpacity, otherAnchor);
- composite.apply(request, imageIndexMap);
+ composite.apply(
+ request,
+ imageIndexMap,
+ img -> ImageData.newBuilder().setContent(ByteString.copyFrom(img.getImageData())).build());
assertThat(request.getOptionsCount()).isEqualTo(2);
assertThat(request.getOptions(0).getXOffset()).isEqualTo(xOffset);
assertThat(request.getOptions(0).getYOffset()).isEqualTo(yOffset);
@@ -200,21 +223,41 @@ public void testApply_multipleImagesLotsOfOptions() throws Exception {
float opacity4 = 0.9f;
Composite.Anchor anchor4 = Composite.Anchor.TOP_CENTER;
new CompositeImpl(new ImageImpl(imageData), xOffset, yOffset, opacity, anchor)
- .apply(request, imageIndexMap);
+ .apply(
+ request,
+ imageIndexMap,
+ img ->
+ ImageData.newBuilder().setContent(ByteString.copyFrom(img.getImageData())).build());
new CompositeImpl(
new ImageImpl(otherImageData), otherXOffset, otherYOffset, otherOpacity, otherAnchor)
- .apply(request, imageIndexMap);
+ .apply(
+ request,
+ imageIndexMap,
+ img ->
+ ImageData.newBuilder().setContent(ByteString.copyFrom(img.getImageData())).build());
new CompositeImpl(
new ImageImpl(imageData),
yetAnotherXOffset,
yetAnotherYOffset,
yetAnotherOpacity,
yetAnotherAnchor)
- .apply(request, imageIndexMap);
+ .apply(
+ request,
+ imageIndexMap,
+ img ->
+ ImageData.newBuilder().setContent(ByteString.copyFrom(img.getImageData())).build());
new CompositeImpl(new ImageImpl(otherImageData), xOffset3, yOffset3, opacity3, anchor3)
- .apply(request, imageIndexMap);
+ .apply(
+ request,
+ imageIndexMap,
+ img ->
+ ImageData.newBuilder().setContent(ByteString.copyFrom(img.getImageData())).build());
new CompositeImpl(new ImageImpl(otherImageData), xOffset4, yOffset4, opacity4, anchor4)
- .apply(request, imageIndexMap);
+ .apply(
+ request,
+ imageIndexMap,
+ img ->
+ ImageData.newBuilder().setContent(ByteString.copyFrom(img.getImageData())).build());
assertThat(request.getOptionsCount()).isEqualTo(5);
assertThat(request.getOptions(0).getXOffset()).isEqualTo(xOffset);
assertThat(request.getOptions(0).getYOffset()).isEqualTo(yOffset);
diff --git a/api_dev/src/test/java/com/google/appengine/api/images/ImagesServiceImplTest.java b/api_dev/src/test/java/com/google/appengine/api/images/ImagesServiceImplTest.java
index 7dac4425a..dbb6107b7 100644
--- a/api_dev/src/test/java/com/google/appengine/api/images/ImagesServiceImplTest.java
+++ b/api_dev/src/test/java/com/google/appengine/api/images/ImagesServiceImplTest.java
@@ -61,6 +61,7 @@
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
+import java.util.function.Function;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -698,8 +699,10 @@ static class FakeComposite extends Composite {
@Override
void apply(
- ImagesServicePb.ImagesCompositeRequest.Builder request, Map imageIndexMap) {
- delegate.apply(request, imageIndexMap);
+ ImagesServicePb.ImagesCompositeRequest.Builder request,
+ Map imageIndexMap,
+ Function imageDataConverter) {
+ delegate.apply(request, imageIndexMap, imageDataConverter);
applications++;
}
}
diff --git a/api_legacy/pom.xml b/api_legacy/pom.xml
index 4b7991a3e..2aaebbf14 100644
--- a/api_legacy/pom.xml
+++ b/api_legacy/pom.xml
@@ -22,7 +22,7 @@
com.google.appengine
parent
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
jar
diff --git a/appengine-api-1.0-sdk/pom.xml b/appengine-api-1.0-sdk/pom.xml
index b1a41a361..09ad1b6e0 100644
--- a/appengine-api-1.0-sdk/pom.xml
+++ b/appengine-api-1.0-sdk/pom.xml
@@ -20,7 +20,7 @@
com.google.appengine
parent
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
jar
AppEngine :: appengine-api-1.0-sdk
@@ -75,6 +75,10 @@
com.google.api.services.datastore
com.google.appengine.repackaged.com.google.api.services.datastore
+
+ com.google.api.services.storage
+ com.google.appengine.repackaged.com.google.api.services.storage
+
com.google.datastore.v1
@@ -104,6 +108,10 @@
com.google.appengine.api.search.proto.SearchServicePb
com.google.appengine.repackaged.com.google.appengine.api.search.proto.SearchServicePb
+
+ com.google.auth
+ com.google.appengine.repackaged.com.google.auth
+
com.google.borg.borgcron
com.google.appengine.repackaged.com.google.cron
@@ -272,6 +280,34 @@
io.grpc
com.google.appengine.repackaged.io.grpc
+
+ com.google.cloud
+ com.google.appengine.repackaged.com.google.cloud
+
+
+ com.google.api.gax
+ com.google.appengine.repackaged.com.google.api.gax
+
+
+ com.google.api.core
+ com.google.appengine.repackaged.com.google.api.core
+
+
+ com.google.api.pathtemplate
+ com.google.appengine.repackaged.com.google.api.pathtemplate
+
+
+ com.google.api.resourcenames
+ com.google.appengine.repackaged.com.google.api.resourcenames
+
+
+ org.threeten.bp
+ com.google.appengine.repackaged.org.threeten.bp
+
+
+ io.opentelemetry
+ com.google.appengine.repackaged.io.opentelemetry
+
@@ -292,6 +328,7 @@
com/google/appengine/api/blobstore/jakarta/*
com/google/appengine/api/capabilities/*
com/google/appengine/api/images/*
+ com/google/appengine/api/images/proto/*
com/google/appengine/api/internal/*
com/google/appengine/api/log/*
com/google/appengine/api/mail/*
@@ -470,6 +507,8 @@
com.google.http-client:google-http-client-jackson:*
com.google.http-client:google-http-client-protobuf:*
com.google.http-client:google-http-client:*
+ com.google.http-client:google-http-client-appengine:*
+ com.google.apis:google-api-services-storage:*
com.google.oauth-client:google-oauth-client:*
com.google.protobuf:protobuf-java:*
commons-codec:commons-codec:*
@@ -483,8 +522,31 @@
io.opencensus:opencensus-contrib-http-util:*
io.grpc:grpc-api:*
+ io.grpc:grpc-auth:*
+ io.grpc:grpc-stub:*
+ io.grpc:grpc-protobuf:*
+ io.grpc:grpc-protobuf-lite:*
+ io.grpc:grpc-core:*
+ io.grpc:grpc-netty-shaded:*
+ io.grpc:grpc-util:*
+ io.grpc:grpc-context:*
+ com.google.auth:google-auth-library-oauth2-http:*
+ com.google.auth:google-auth-library-credentials:*
+ com.google.cloud:google-cloud-storage:*
+ com.google.cloud:google-cloud-core:*
+ com.google.cloud:google-cloud-core-http:*
+ com.google.api:gax:*
+ com.google.api:gax-httpjson:*
+ com.google.api:api-common:*
+ org.threeten:threetenbp:*
+ io.opentelemetry:opentelemetry-api:*
+ io.opentelemetry:opentelemetry-context:*
+
+
+
+
diff --git a/appengine-api-stubs/pom.xml b/appengine-api-stubs/pom.xml
index 3ef7c9ef1..30e450889 100644
--- a/appengine-api-stubs/pom.xml
+++ b/appengine-api-stubs/pom.xml
@@ -23,7 +23,7 @@
com.google.appengine
parent
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
jar
diff --git a/appengine_init/pom.xml b/appengine_init/pom.xml
index 3d103aa8a..faa701f56 100644
--- a/appengine_init/pom.xml
+++ b/appengine_init/pom.xml
@@ -23,7 +23,7 @@
com.google.appengine
parent
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
jar
diff --git a/appengine_jsr107/pom.xml b/appengine_jsr107/pom.xml
index 02a08add2..faa2cb851 100644
--- a/appengine_jsr107/pom.xml
+++ b/appengine_jsr107/pom.xml
@@ -26,7 +26,7 @@
com.google.appengine
parent
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
diff --git a/appengine_resources/pom.xml b/appengine_resources/pom.xml
index 155d26b1a..c4750b500 100644
--- a/appengine_resources/pom.xml
+++ b/appengine_resources/pom.xml
@@ -21,7 +21,7 @@
com.google.appengine
parent
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
jar
AppEngine :: appengine-resources
diff --git a/appengine_testing/pom.xml b/appengine_testing/pom.xml
index 347315d87..6fee41001 100644
--- a/appengine_testing/pom.xml
+++ b/appengine_testing/pom.xml
@@ -23,7 +23,7 @@
com.google.appengine
parent
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
jar
diff --git a/appengine_testing_tests/pom.xml b/appengine_testing_tests/pom.xml
index 172f99c1c..99556f251 100644
--- a/appengine_testing_tests/pom.xml
+++ b/appengine_testing_tests/pom.xml
@@ -23,7 +23,7 @@
com.google.appengine
parent
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
jar
diff --git a/applications/guestbook/pom.xml b/applications/guestbook/pom.xml
index d51fc6a72..3929e6ab6 100644
--- a/applications/guestbook/pom.xml
+++ b/applications/guestbook/pom.xml
@@ -24,7 +24,7 @@
com.google.appengine
applications
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
com.google.appengine.demos
guestbook
diff --git a/applications/guestbook_jakarta/pom.xml b/applications/guestbook_jakarta/pom.xml
index 2b703d47c..9b900929b 100644
--- a/applications/guestbook_jakarta/pom.xml
+++ b/applications/guestbook_jakarta/pom.xml
@@ -24,7 +24,7 @@
com.google.appengine
applications
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
com.google.appengine.demos
guestbook_jakarta
diff --git a/applications/jaxrs/pom.xml b/applications/jaxrs/pom.xml
index 8cbe50262..98ce4b41f 100644
--- a/applications/jaxrs/pom.xml
+++ b/applications/jaxrs/pom.xml
@@ -24,7 +24,7 @@
com.google.appengine
applications
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
com.google.appengine.demos
jaxrs
diff --git a/applications/pom.xml b/applications/pom.xml
index 55a7e2f56..04abe4b79 100644
--- a/applications/pom.xml
+++ b/applications/pom.xml
@@ -24,7 +24,7 @@
com.google.appengine
parent
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
pom
diff --git a/applications/proberapp/pom.xml b/applications/proberapp/pom.xml
index d8ed33fb4..92fcd5855 100644
--- a/applications/proberapp/pom.xml
+++ b/applications/proberapp/pom.xml
@@ -29,7 +29,7 @@
com.google.appengine
applications
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
diff --git a/applications/proberapp_jakarta/pom.xml b/applications/proberapp_jakarta/pom.xml
index ee693bbc9..bbd281194 100644
--- a/applications/proberapp_jakarta/pom.xml
+++ b/applications/proberapp_jakarta/pom.xml
@@ -29,7 +29,7 @@
com.google.appengine
applications
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
diff --git a/applications/servletasyncapp/pom.xml b/applications/servletasyncapp/pom.xml
index ebd5c0ee0..7b68e2771 100644
--- a/applications/servletasyncapp/pom.xml
+++ b/applications/servletasyncapp/pom.xml
@@ -24,7 +24,7 @@
com.google.appengine
applications
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
com.google.appengine.demos
servletasyncapp
diff --git a/applications/servletasyncappjakarta/pom.xml b/applications/servletasyncappjakarta/pom.xml
index f286d15b7..4afcba6d5 100644
--- a/applications/servletasyncappjakarta/pom.xml
+++ b/applications/servletasyncappjakarta/pom.xml
@@ -24,7 +24,7 @@
com.google.appengine
applications
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
com.google.appengine.demos
servletasyncappjakarta
diff --git a/applications/springboot/pom.xml b/applications/springboot/pom.xml
index d05f44bf6..1c4bc0597 100644
--- a/applications/springboot/pom.xml
+++ b/applications/springboot/pom.xml
@@ -24,7 +24,7 @@
com.google.appengine
applications
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
war
diff --git a/e2etests/devappservertests/pom.xml b/e2etests/devappservertests/pom.xml
index fcea82825..86bb4c6d9 100644
--- a/e2etests/devappservertests/pom.xml
+++ b/e2etests/devappservertests/pom.xml
@@ -22,7 +22,7 @@
com.google.appengine
e2etests
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
jar
diff --git a/e2etests/pom.xml b/e2etests/pom.xml
index 6dfe5e552..d6db47161 100644
--- a/e2etests/pom.xml
+++ b/e2etests/pom.xml
@@ -23,7 +23,7 @@
com.google.appengine
parent
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
AppEngine :: e2e tests
https://github.com/GoogleCloudPlatform/appengine-java-standard/
diff --git a/e2etests/stagingtests/pom.xml b/e2etests/stagingtests/pom.xml
index 53c3ba28e..e546b8873 100644
--- a/e2etests/stagingtests/pom.xml
+++ b/e2etests/stagingtests/pom.xml
@@ -22,7 +22,7 @@
com.google.appengine
e2etests
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
jar
diff --git a/e2etests/testlocalapps/allinone/pom.xml b/e2etests/testlocalapps/allinone/pom.xml
index 1c162995e..f1d69be46 100644
--- a/e2etests/testlocalapps/allinone/pom.xml
+++ b/e2etests/testlocalapps/allinone/pom.xml
@@ -24,7 +24,7 @@
com.google.appengine
testlocalapps
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
war
diff --git a/e2etests/testlocalapps/allinone_jakarta/pom.xml b/e2etests/testlocalapps/allinone_jakarta/pom.xml
index e23cadbe0..87f1021ec 100644
--- a/e2etests/testlocalapps/allinone_jakarta/pom.xml
+++ b/e2etests/testlocalapps/allinone_jakarta/pom.xml
@@ -24,7 +24,7 @@
com.google.appengine
testlocalapps
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
war
diff --git a/e2etests/testlocalapps/badcron/pom.xml b/e2etests/testlocalapps/badcron/pom.xml
index 41704184c..638404af7 100644
--- a/e2etests/testlocalapps/badcron/pom.xml
+++ b/e2etests/testlocalapps/badcron/pom.xml
@@ -24,7 +24,7 @@
com.google.appengine
testlocalapps
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
war
diff --git a/e2etests/testlocalapps/bundle_standard/pom.xml b/e2etests/testlocalapps/bundle_standard/pom.xml
index 3b1341b94..41db18461 100644
--- a/e2etests/testlocalapps/bundle_standard/pom.xml
+++ b/e2etests/testlocalapps/bundle_standard/pom.xml
@@ -24,7 +24,7 @@
com.google.appengine
testlocalapps
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
war
diff --git a/e2etests/testlocalapps/bundle_standard_with_container_initializer/pom.xml b/e2etests/testlocalapps/bundle_standard_with_container_initializer/pom.xml
index 6caeb47b1..5936f0b82 100644
--- a/e2etests/testlocalapps/bundle_standard_with_container_initializer/pom.xml
+++ b/e2etests/testlocalapps/bundle_standard_with_container_initializer/pom.xml
@@ -25,7 +25,7 @@
com.google.appengine
testlocalapps
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
war
diff --git a/e2etests/testlocalapps/bundle_standard_with_container_initializer_jakarta/pom.xml b/e2etests/testlocalapps/bundle_standard_with_container_initializer_jakarta/pom.xml
index bc4e474c3..ff2459e5d 100644
--- a/e2etests/testlocalapps/bundle_standard_with_container_initializer_jakarta/pom.xml
+++ b/e2etests/testlocalapps/bundle_standard_with_container_initializer_jakarta/pom.xml
@@ -25,7 +25,7 @@
com.google.appengine
testlocalapps
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
war
diff --git a/e2etests/testlocalapps/bundle_standard_with_no_jsp/pom.xml b/e2etests/testlocalapps/bundle_standard_with_no_jsp/pom.xml
index 855b403a4..ca566bd66 100644
--- a/e2etests/testlocalapps/bundle_standard_with_no_jsp/pom.xml
+++ b/e2etests/testlocalapps/bundle_standard_with_no_jsp/pom.xml
@@ -24,7 +24,7 @@
com.google.appengine
testlocalapps
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
war
diff --git a/e2etests/testlocalapps/bundle_standard_with_weblistener_memcache/pom.xml b/e2etests/testlocalapps/bundle_standard_with_weblistener_memcache/pom.xml
index 171aebd9c..05485655d 100644
--- a/e2etests/testlocalapps/bundle_standard_with_weblistener_memcache/pom.xml
+++ b/e2etests/testlocalapps/bundle_standard_with_weblistener_memcache/pom.xml
@@ -24,7 +24,7 @@
com.google.appengine
testlocalapps
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
war
diff --git a/e2etests/testlocalapps/cron-bad-job-age-limit/pom.xml b/e2etests/testlocalapps/cron-bad-job-age-limit/pom.xml
index 5f65c90e6..66736fee6 100644
--- a/e2etests/testlocalapps/cron-bad-job-age-limit/pom.xml
+++ b/e2etests/testlocalapps/cron-bad-job-age-limit/pom.xml
@@ -24,7 +24,7 @@
com.google.appengine
testlocalapps
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
war
diff --git a/e2etests/testlocalapps/cron-good-retry-parameters/pom.xml b/e2etests/testlocalapps/cron-good-retry-parameters/pom.xml
index 08a48401a..3af9f9788 100644
--- a/e2etests/testlocalapps/cron-good-retry-parameters/pom.xml
+++ b/e2etests/testlocalapps/cron-good-retry-parameters/pom.xml
@@ -24,7 +24,7 @@
com.google.appengine
testlocalapps
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
war
diff --git a/e2etests/testlocalapps/cron-negative-max-backoff/pom.xml b/e2etests/testlocalapps/cron-negative-max-backoff/pom.xml
index f222ce6ee..2abfefdc1 100644
--- a/e2etests/testlocalapps/cron-negative-max-backoff/pom.xml
+++ b/e2etests/testlocalapps/cron-negative-max-backoff/pom.xml
@@ -24,7 +24,7 @@
com.google.appengine
testlocalapps
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
war
diff --git a/e2etests/testlocalapps/cron-negative-retry-limit/pom.xml b/e2etests/testlocalapps/cron-negative-retry-limit/pom.xml
index 55d991a71..29bf2314d 100644
--- a/e2etests/testlocalapps/cron-negative-retry-limit/pom.xml
+++ b/e2etests/testlocalapps/cron-negative-retry-limit/pom.xml
@@ -24,7 +24,7 @@
com.google.appengine
testlocalapps
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
war
diff --git a/e2etests/testlocalapps/cron-two-max-doublings/pom.xml b/e2etests/testlocalapps/cron-two-max-doublings/pom.xml
index fd8237d69..0e465a04e 100644
--- a/e2etests/testlocalapps/cron-two-max-doublings/pom.xml
+++ b/e2etests/testlocalapps/cron-two-max-doublings/pom.xml
@@ -24,7 +24,7 @@
com.google.appengine
testlocalapps
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
war
diff --git a/e2etests/testlocalapps/http-headers/pom.xml b/e2etests/testlocalapps/http-headers/pom.xml
index 022874f69..9dcb5621f 100644
--- a/e2etests/testlocalapps/http-headers/pom.xml
+++ b/e2etests/testlocalapps/http-headers/pom.xml
@@ -24,7 +24,7 @@
com.google.appengine
testlocalapps
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
war
diff --git a/e2etests/testlocalapps/java8-jar/pom.xml b/e2etests/testlocalapps/java8-jar/pom.xml
index fa04276b0..7a24610d7 100644
--- a/e2etests/testlocalapps/java8-jar/pom.xml
+++ b/e2etests/testlocalapps/java8-jar/pom.xml
@@ -24,7 +24,7 @@
com.google.appengine
testlocalapps
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
war
diff --git a/e2etests/testlocalapps/java8-no-webxml/pom.xml b/e2etests/testlocalapps/java8-no-webxml/pom.xml
index 31508028a..eebe02d7a 100644
--- a/e2etests/testlocalapps/java8-no-webxml/pom.xml
+++ b/e2etests/testlocalapps/java8-no-webxml/pom.xml
@@ -24,7 +24,7 @@
com.google.appengine
testlocalapps
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
war
diff --git a/e2etests/testlocalapps/pom.xml b/e2etests/testlocalapps/pom.xml
index 292152bc7..353d71192 100644
--- a/e2etests/testlocalapps/pom.xml
+++ b/e2etests/testlocalapps/pom.xml
@@ -24,7 +24,7 @@
com.google.appengine
e2etests
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
pom
diff --git a/e2etests/testlocalapps/sample-badaeweb/pom.xml b/e2etests/testlocalapps/sample-badaeweb/pom.xml
index 11e90cc8b..393f3c3dc 100644
--- a/e2etests/testlocalapps/sample-badaeweb/pom.xml
+++ b/e2etests/testlocalapps/sample-badaeweb/pom.xml
@@ -24,7 +24,7 @@
com.google.appengine
testlocalapps
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
war
diff --git a/e2etests/testlocalapps/sample-baddispatch-yaml/pom.xml b/e2etests/testlocalapps/sample-baddispatch-yaml/pom.xml
index e5b0650ba..a82ec2b33 100644
--- a/e2etests/testlocalapps/sample-baddispatch-yaml/pom.xml
+++ b/e2etests/testlocalapps/sample-baddispatch-yaml/pom.xml
@@ -24,7 +24,7 @@
com.google.appengine
testlocalapps
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
war
diff --git a/e2etests/testlocalapps/sample-baddispatch/pom.xml b/e2etests/testlocalapps/sample-baddispatch/pom.xml
index f3702ed43..4eec32b5d 100644
--- a/e2etests/testlocalapps/sample-baddispatch/pom.xml
+++ b/e2etests/testlocalapps/sample-baddispatch/pom.xml
@@ -24,7 +24,7 @@
com.google.appengine
testlocalapps
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
war
diff --git a/e2etests/testlocalapps/sample-badindexes/pom.xml b/e2etests/testlocalapps/sample-badindexes/pom.xml
index 6f9a1c151..d50c9f914 100644
--- a/e2etests/testlocalapps/sample-badindexes/pom.xml
+++ b/e2etests/testlocalapps/sample-badindexes/pom.xml
@@ -24,7 +24,7 @@
com.google.appengine
testlocalapps
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
war
diff --git a/e2etests/testlocalapps/sample-badweb/pom.xml b/e2etests/testlocalapps/sample-badweb/pom.xml
index e9779e3f3..9a9bdd4cd 100644
--- a/e2etests/testlocalapps/sample-badweb/pom.xml
+++ b/e2etests/testlocalapps/sample-badweb/pom.xml
@@ -24,7 +24,7 @@
com.google.appengine
testlocalapps
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
war
diff --git a/e2etests/testlocalapps/sample-default-auto-ids/pom.xml b/e2etests/testlocalapps/sample-default-auto-ids/pom.xml
index 609ec5757..dadf441aa 100644
--- a/e2etests/testlocalapps/sample-default-auto-ids/pom.xml
+++ b/e2etests/testlocalapps/sample-default-auto-ids/pom.xml
@@ -24,7 +24,7 @@
com.google.appengine
testlocalapps
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
war
diff --git a/e2etests/testlocalapps/sample-error-in-tag-file/pom.xml b/e2etests/testlocalapps/sample-error-in-tag-file/pom.xml
index 5910a3cc2..aaddc3734 100644
--- a/e2etests/testlocalapps/sample-error-in-tag-file/pom.xml
+++ b/e2etests/testlocalapps/sample-error-in-tag-file/pom.xml
@@ -24,7 +24,7 @@
com.google.appengine
testlocalapps
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
war
diff --git a/e2etests/testlocalapps/sample-java11/pom.xml b/e2etests/testlocalapps/sample-java11/pom.xml
index 9d56a9f97..d1dfd5357 100644
--- a/e2etests/testlocalapps/sample-java11/pom.xml
+++ b/e2etests/testlocalapps/sample-java11/pom.xml
@@ -24,7 +24,7 @@
com.google.appengine
testlocalapps
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
war
diff --git a/e2etests/testlocalapps/sample-java17/pom.xml b/e2etests/testlocalapps/sample-java17/pom.xml
index 0a62889a9..fc702f9a7 100644
--- a/e2etests/testlocalapps/sample-java17/pom.xml
+++ b/e2etests/testlocalapps/sample-java17/pom.xml
@@ -24,7 +24,7 @@
com.google.appengine
testlocalapps
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
war
diff --git a/e2etests/testlocalapps/sample-jsptaglibrary/pom.xml b/e2etests/testlocalapps/sample-jsptaglibrary/pom.xml
index 266d64c38..8fe402b43 100644
--- a/e2etests/testlocalapps/sample-jsptaglibrary/pom.xml
+++ b/e2etests/testlocalapps/sample-jsptaglibrary/pom.xml
@@ -24,7 +24,7 @@
com.google.appengine
testlocalapps
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
war
diff --git a/e2etests/testlocalapps/sample-jspx/pom.xml b/e2etests/testlocalapps/sample-jspx/pom.xml
index bfb8dbcf2..6590022cf 100644
--- a/e2etests/testlocalapps/sample-jspx/pom.xml
+++ b/e2etests/testlocalapps/sample-jspx/pom.xml
@@ -24,7 +24,7 @@
com.google.appengine
testlocalapps
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
war
diff --git a/e2etests/testlocalapps/sample-legacy-auto-ids/pom.xml b/e2etests/testlocalapps/sample-legacy-auto-ids/pom.xml
index b53dabf1c..631e8c8f6 100644
--- a/e2etests/testlocalapps/sample-legacy-auto-ids/pom.xml
+++ b/e2etests/testlocalapps/sample-legacy-auto-ids/pom.xml
@@ -24,7 +24,7 @@
com.google.appengine
testlocalapps
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
war
diff --git a/e2etests/testlocalapps/sample-missingappid/pom.xml b/e2etests/testlocalapps/sample-missingappid/pom.xml
index 62ada0fa8..06f887b65 100644
--- a/e2etests/testlocalapps/sample-missingappid/pom.xml
+++ b/e2etests/testlocalapps/sample-missingappid/pom.xml
@@ -24,7 +24,7 @@
com.google.appengine
testlocalapps
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
war
diff --git a/e2etests/testlocalapps/sample-nojsps/pom.xml b/e2etests/testlocalapps/sample-nojsps/pom.xml
index ab963fa62..2273bdf44 100644
--- a/e2etests/testlocalapps/sample-nojsps/pom.xml
+++ b/e2etests/testlocalapps/sample-nojsps/pom.xml
@@ -24,7 +24,7 @@
com.google.appengine
testlocalapps
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
war
diff --git a/e2etests/testlocalapps/sample-unspecified-auto-ids/pom.xml b/e2etests/testlocalapps/sample-unspecified-auto-ids/pom.xml
index 4d298cbec..d3ca6b880 100644
--- a/e2etests/testlocalapps/sample-unspecified-auto-ids/pom.xml
+++ b/e2etests/testlocalapps/sample-unspecified-auto-ids/pom.xml
@@ -24,7 +24,7 @@
com.google.appengine
testlocalapps
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
war
diff --git a/e2etests/testlocalapps/sample-with-classes/pom.xml b/e2etests/testlocalapps/sample-with-classes/pom.xml
index c5482cca0..3a94287b2 100644
--- a/e2etests/testlocalapps/sample-with-classes/pom.xml
+++ b/e2etests/testlocalapps/sample-with-classes/pom.xml
@@ -24,7 +24,7 @@
com.google.appengine
testlocalapps
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
war
diff --git a/e2etests/testlocalapps/sampleapp-automatic-module/pom.xml b/e2etests/testlocalapps/sampleapp-automatic-module/pom.xml
index c3904d53f..55a9d0ca8 100644
--- a/e2etests/testlocalapps/sampleapp-automatic-module/pom.xml
+++ b/e2etests/testlocalapps/sampleapp-automatic-module/pom.xml
@@ -24,7 +24,7 @@
com.google.appengine
testlocalapps
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
war
diff --git a/e2etests/testlocalapps/sampleapp-backends/pom.xml b/e2etests/testlocalapps/sampleapp-backends/pom.xml
index 0f5332890..36f12a11e 100644
--- a/e2etests/testlocalapps/sampleapp-backends/pom.xml
+++ b/e2etests/testlocalapps/sampleapp-backends/pom.xml
@@ -24,7 +24,7 @@
com.google.appengine
testlocalapps
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
war
AppEngine :: sampleapp-backends
diff --git a/e2etests/testlocalapps/sampleapp-basic-module/pom.xml b/e2etests/testlocalapps/sampleapp-basic-module/pom.xml
index 5fd954aef..d6368a9f2 100644
--- a/e2etests/testlocalapps/sampleapp-basic-module/pom.xml
+++ b/e2etests/testlocalapps/sampleapp-basic-module/pom.xml
@@ -24,7 +24,7 @@
com.google.appengine
testlocalapps
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
war
diff --git a/e2etests/testlocalapps/sampleapp-manual-module/pom.xml b/e2etests/testlocalapps/sampleapp-manual-module/pom.xml
index 449704500..f75bc53af 100644
--- a/e2etests/testlocalapps/sampleapp-manual-module/pom.xml
+++ b/e2etests/testlocalapps/sampleapp-manual-module/pom.xml
@@ -24,7 +24,7 @@
com.google.appengine
testlocalapps
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
war
diff --git a/e2etests/testlocalapps/sampleapp-runtime/pom.xml b/e2etests/testlocalapps/sampleapp-runtime/pom.xml
index bb82a50c3..0cef0c28f 100644
--- a/e2etests/testlocalapps/sampleapp-runtime/pom.xml
+++ b/e2etests/testlocalapps/sampleapp-runtime/pom.xml
@@ -24,7 +24,7 @@
com.google.appengine
testlocalapps
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
war
diff --git a/e2etests/testlocalapps/sampleapp/pom.xml b/e2etests/testlocalapps/sampleapp/pom.xml
index e333a1667..b254d504c 100644
--- a/e2etests/testlocalapps/sampleapp/pom.xml
+++ b/e2etests/testlocalapps/sampleapp/pom.xml
@@ -25,7 +25,7 @@
com.google.appengine
testlocalapps
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
AppEngine :: sampleapp
https://github.com/GoogleCloudPlatform/appengine-java-standard/
diff --git a/e2etests/testlocalapps/stage-sampleapp/pom.xml b/e2etests/testlocalapps/stage-sampleapp/pom.xml
index 251630139..07d72ee49 100644
--- a/e2etests/testlocalapps/stage-sampleapp/pom.xml
+++ b/e2etests/testlocalapps/stage-sampleapp/pom.xml
@@ -24,7 +24,7 @@
com.google.appengine
testlocalapps
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
war
diff --git a/e2etests/testlocalapps/stage-with-staging-options/pom.xml b/e2etests/testlocalapps/stage-with-staging-options/pom.xml
index 3be7d6512..7b790187a 100644
--- a/e2etests/testlocalapps/stage-with-staging-options/pom.xml
+++ b/e2etests/testlocalapps/stage-with-staging-options/pom.xml
@@ -24,7 +24,7 @@
com.google.appengine
testlocalapps
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
war
diff --git a/e2etests/testlocalapps/xmlorder/pom.xml b/e2etests/testlocalapps/xmlorder/pom.xml
index e7db085a5..d3dd4e10e 100644
--- a/e2etests/testlocalapps/xmlorder/pom.xml
+++ b/e2etests/testlocalapps/xmlorder/pom.xml
@@ -24,7 +24,7 @@
com.google.appengine
testlocalapps
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
war
AppEngine :: xmlorder
diff --git a/external/geronimo_javamail/pom.xml b/external/geronimo_javamail/pom.xml
index 6a09eca53..1843bb0f1 100644
--- a/external/geronimo_javamail/pom.xml
+++ b/external/geronimo_javamail/pom.xml
@@ -22,7 +22,7 @@
com.google.appengine
parent
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
../../pom.xml
diff --git a/jetty121_assembly/pom.xml b/jetty121_assembly/pom.xml
index c94b8dc51..6fbf98a21 100644
--- a/jetty121_assembly/pom.xml
+++ b/jetty121_assembly/pom.xml
@@ -20,7 +20,7 @@
com.google.appengine
parent
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
4.0.0
jetty121-assembly
diff --git a/lib/pom.xml b/lib/pom.xml
index e9a9868bd..3551be09f 100644
--- a/lib/pom.xml
+++ b/lib/pom.xml
@@ -24,7 +24,7 @@
com.google.appengine
parent
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
pom
diff --git a/lib/tools_api/pom.xml b/lib/tools_api/pom.xml
index 20d530421..5a16095bc 100644
--- a/lib/tools_api/pom.xml
+++ b/lib/tools_api/pom.xml
@@ -23,7 +23,7 @@
com.google.appengine
lib-parent
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
jar
diff --git a/lib/xml_validator/pom.xml b/lib/xml_validator/pom.xml
index 890937be2..619f66227 100644
--- a/lib/xml_validator/pom.xml
+++ b/lib/xml_validator/pom.xml
@@ -22,7 +22,7 @@
com.google.appengine
lib-parent
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
jar
AppEngine :: libxmlvalidator
diff --git a/lib/xml_validator_test/pom.xml b/lib/xml_validator_test/pom.xml
index 111ff96cc..400be5470 100644
--- a/lib/xml_validator_test/pom.xml
+++ b/lib/xml_validator_test/pom.xml
@@ -22,7 +22,7 @@
com.google.appengine
lib-parent
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
jar
AppEngine :: libxmlvalidator_test
diff --git a/local_runtime_shared_jetty12/pom.xml b/local_runtime_shared_jetty12/pom.xml
index 1a4fdfe6e..216773a20 100644
--- a/local_runtime_shared_jetty12/pom.xml
+++ b/local_runtime_shared_jetty12/pom.xml
@@ -23,7 +23,7 @@
com.google.appengine
parent
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
jar
AppEngine :: appengine-local-runtime-shared Jakarta
diff --git a/pom.xml b/pom.xml
index 3ffe60a3a..2d211f26c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -19,7 +19,7 @@
4.0.0
com.google.appengine
parent
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
pom
AppEngine :: Parent project
https://github.com/GoogleCloudPlatform/appengine-java-standard/
@@ -66,6 +66,9 @@
17
17
UTF-8
+ 1.1.1
+ 1.11.1
+ 1.81.0
9.4.58.v20250814
12.0.36
12.1.10
@@ -278,6 +281,32 @@
+
+ io.grpc
+ grpc-netty-shaded
+ ${grpc.version}
+
+
+ io.grpc
+ grpc-protobuf
+ ${grpc.version}
+
+
+ io.grpc
+ grpc-stub
+ ${grpc.version}
+
+
+ io.grpc
+ grpc-auth
+ ${grpc.version}
+
+
+ io.grpc
+ grpc-testing
+ ${grpc.version}
+ test
+
com.google.appengine
appengine-init
@@ -443,25 +472,25 @@
com.google.auto.service
auto-service
- 1.1.1
+ ${auto-service.version}
provided
com.google.auto.service
auto-service-annotations
- 1.1.1
+ ${auto-service.version}
provided
com.google.auto.value
auto-value
- 1.11.1
+ ${auto-value.version}
provided
com.google.auto.value
auto-value-annotations
- 1.11.1
+ ${auto-value.version}
com.contrastsecurity
@@ -716,7 +745,21 @@
commons-lang3
3.20.0
-
+
+ javax.annotation
+ javax.annotation-api
+ 1.3.2
+
+
+ com.google.auth
+ google-auth-library-oauth2-http
+ 1.25.0
+
+
+ com.google.cloud
+ google-cloud-storage
+ 2.63.0
+
diff --git a/protobuf/api/images_service_rpc.proto b/protobuf/api/images_service_rpc.proto
new file mode 100644
index 000000000..ea475f0dc
--- /dev/null
+++ b/protobuf/api/images_service_rpc.proto
@@ -0,0 +1,57 @@
+// 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.
+// Copyright 2008 Google Inc. All Rights Reserved.
+
+syntax = "proto2";
+
+// Some generic_services option(s) added automatically.
+// See: http://go/proto2-generic-services-default
+package apphosting;
+
+import "images_service.proto";
+
+option java_package = "com.google.appengine.api.images.proto";
+option java_outer_classname = "ImagesServiceRPC";
+option java_multiple_files = true;
+
+service ImagesService {
+ // Perform transformations on the given image and return the result.
+ rpc Transform(java.apphosting.ImagesTransformRequest)
+ returns (java.apphosting.ImagesTransformResponse) {}
+
+ // Composite images onto a canvas and return the result.
+ rpc Composite(java.apphosting.ImagesCompositeRequest)
+ returns (java.apphosting.ImagesCompositeResponse) {}
+
+ // Calculate the histogram of an image and return it.
+ rpc Histogram(java.apphosting.ImagesHistogramRequest)
+ returns (java.apphosting.ImagesHistogramResponse) {}
+
+ // Obtains a URL that can serve the image dynamically at different sizes.
+ // The URL format will look like the following:
+ //
+ // http://lh3.ggpht.com/SomeEncryptedString
+ //
+ // to serve this image at different sizes just append the size option:
+ //
+ // http://lh3.ggpht.com/SomeEncryptedString=s128 (serves a 128 sized image)
+ //
+ // The allowed thumbnail sizes are any integer in the range [0, 1600].
+ rpc GetUrlBase(java.apphosting.ImagesGetUrlBaseRequest)
+ returns (java.apphosting.ImagesGetUrlBaseResponse) {}
+
+ // Delete a URL that was created with GetUrlBase
+ rpc DeleteUrlBase(java.apphosting.ImagesDeleteUrlBaseRequest)
+ returns (java.apphosting.ImagesDeleteUrlBaseResponse) {}
+}
diff --git a/protobuf/pom.xml b/protobuf/pom.xml
index 810a4a772..436d025e1 100644
--- a/protobuf/pom.xml
+++ b/protobuf/pom.xml
@@ -23,7 +23,7 @@
com.google.appengine
parent
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
jar
@@ -36,6 +36,18 @@
com.google.protobuf
protobuf-java
+
+ io.grpc
+ grpc-protobuf
+
+
+ io.grpc
+ grpc-stub
+
+
+ javax.annotation
+ javax.annotation-api
+
@@ -44,12 +56,21 @@
com.github.os72
protoc-jar-maven-plugin
-
- .
-
-
- ./api
-
+
+ .
+
+
+ ./api
+
+
+
+ java
+
+
+ grpc-java
+ io.grpc:protoc-gen-grpc-java:${grpc.version}
+
+
diff --git a/quickstartgenerator_jetty121_ee11/pom.xml b/quickstartgenerator_jetty121_ee11/pom.xml
index 872890959..286a544d5 100644
--- a/quickstartgenerator_jetty121_ee11/pom.xml
+++ b/quickstartgenerator_jetty121_ee11/pom.xml
@@ -23,7 +23,7 @@
com.google.appengine
parent
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
jar
diff --git a/quickstartgenerator_jetty121_ee8/pom.xml b/quickstartgenerator_jetty121_ee8/pom.xml
index c3540e422..7df4a818d 100644
--- a/quickstartgenerator_jetty121_ee8/pom.xml
+++ b/quickstartgenerator_jetty121_ee8/pom.xml
@@ -23,7 +23,7 @@
com.google.appengine
parent
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
jar
diff --git a/remoteapi/pom.xml b/remoteapi/pom.xml
index 17cbbaf2e..382082754 100644
--- a/remoteapi/pom.xml
+++ b/remoteapi/pom.xml
@@ -20,7 +20,7 @@
com.google.appengine
parent
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
jar
AppEngine :: appengine-remote-api
diff --git a/runtime/annotationscanningwebapp/pom.xml b/runtime/annotationscanningwebapp/pom.xml
index fe112481a..322fc3ffb 100644
--- a/runtime/annotationscanningwebapp/pom.xml
+++ b/runtime/annotationscanningwebapp/pom.xml
@@ -23,7 +23,7 @@
com.google.appengine
runtime-parent
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
com.google.appengine.demos
annotationscanningwebapp
diff --git a/runtime/annotationscanningwebappjakarta/pom.xml b/runtime/annotationscanningwebappjakarta/pom.xml
index 429ae3678..803b128dd 100644
--- a/runtime/annotationscanningwebappjakarta/pom.xml
+++ b/runtime/annotationscanningwebappjakarta/pom.xml
@@ -23,7 +23,7 @@
com.google.appengine
runtime-parent
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
com.google.appengine.demos
annotationscanningwebappjakarta
diff --git a/runtime/deployment/pom.xml b/runtime/deployment/pom.xml
index 72e98e318..a356c9539 100644
--- a/runtime/deployment/pom.xml
+++ b/runtime/deployment/pom.xml
@@ -22,7 +22,7 @@
com.google.appengine
runtime-parent
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
pom
diff --git a/runtime/failinitfilterwebapp/pom.xml b/runtime/failinitfilterwebapp/pom.xml
index 868dc1f45..1199945ca 100644
--- a/runtime/failinitfilterwebapp/pom.xml
+++ b/runtime/failinitfilterwebapp/pom.xml
@@ -23,7 +23,7 @@
com.google.appengine
runtime-parent
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
com.google.appengine.demos
failinitfilterwebapp
diff --git a/runtime/failinitfilterwebappjakarta/pom.xml b/runtime/failinitfilterwebappjakarta/pom.xml
index e625e907d..2b216ac6b 100644
--- a/runtime/failinitfilterwebappjakarta/pom.xml
+++ b/runtime/failinitfilterwebappjakarta/pom.xml
@@ -23,7 +23,7 @@
com.google.appengine
runtime-parent
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
com.google.appengine.demos
failinitfilterwebappjakarta
diff --git a/runtime/impl/pom.xml b/runtime/impl/pom.xml
index 2ec433bfc..b9b4a457e 100644
--- a/runtime/impl/pom.xml
+++ b/runtime/impl/pom.xml
@@ -23,7 +23,7 @@
com.google.appengine
runtime-parent
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
jar
diff --git a/runtime/local_jetty121/pom.xml b/runtime/local_jetty121/pom.xml
index c23ec04bc..6df2b6418 100644
--- a/runtime/local_jetty121/pom.xml
+++ b/runtime/local_jetty121/pom.xml
@@ -23,7 +23,7 @@
com.google.appengine
runtime-parent
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
jar
diff --git a/runtime/local_jetty121_ee11/pom.xml b/runtime/local_jetty121_ee11/pom.xml
index 6f95a3eb8..a14e2e027 100644
--- a/runtime/local_jetty121_ee11/pom.xml
+++ b/runtime/local_jetty121_ee11/pom.xml
@@ -23,7 +23,7 @@
com.google.appengine
runtime-parent
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
jar
diff --git a/runtime/main/pom.xml b/runtime/main/pom.xml
index 17cbd6e5d..69b6d2648 100644
--- a/runtime/main/pom.xml
+++ b/runtime/main/pom.xml
@@ -23,7 +23,7 @@
com.google.appengine
runtime-parent
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
jar
diff --git a/runtime/nogaeapiswebapp/pom.xml b/runtime/nogaeapiswebapp/pom.xml
index 8f71f8676..1b228d693 100644
--- a/runtime/nogaeapiswebapp/pom.xml
+++ b/runtime/nogaeapiswebapp/pom.xml
@@ -23,7 +23,7 @@
com.google.appengine
runtime-parent
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
com.google.appengine.demos
nogaeapiswebapp
diff --git a/runtime/nogaeapiswebappjakarta/pom.xml b/runtime/nogaeapiswebappjakarta/pom.xml
index 37ffb16fe..ab2edde13 100644
--- a/runtime/nogaeapiswebappjakarta/pom.xml
+++ b/runtime/nogaeapiswebappjakarta/pom.xml
@@ -23,7 +23,7 @@
com.google.appengine
runtime-parent
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
com.google.appengine.demos
nogaeapiswebappjakarta
diff --git a/runtime/pom.xml b/runtime/pom.xml
index 364e71ab5..05ead1bb5 100644
--- a/runtime/pom.xml
+++ b/runtime/pom.xml
@@ -23,7 +23,7 @@
com.google.appengine
parent
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
AppEngine :: runtime projects
https://github.com/GoogleCloudPlatform/appengine-java-standard/
diff --git a/runtime/runtime_impl_jetty12/pom.xml b/runtime/runtime_impl_jetty12/pom.xml
index 9d61d4afa..a3940efd6 100644
--- a/runtime/runtime_impl_jetty12/pom.xml
+++ b/runtime/runtime_impl_jetty12/pom.xml
@@ -23,7 +23,7 @@
com.google.appengine
runtime-parent
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
jar
diff --git a/runtime/runtime_impl_jetty121/pom.xml b/runtime/runtime_impl_jetty121/pom.xml
index cd96cbb44..368acd3aa 100644
--- a/runtime/runtime_impl_jetty121/pom.xml
+++ b/runtime/runtime_impl_jetty121/pom.xml
@@ -23,7 +23,7 @@
com.google.appengine
runtime-parent
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
jar
diff --git a/runtime/test/pom.xml b/runtime/test/pom.xml
index a1195f517..d4cf3f715 100644
--- a/runtime/test/pom.xml
+++ b/runtime/test/pom.xml
@@ -22,7 +22,7 @@
com.google.appengine
runtime-parent
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
jar
diff --git a/runtime/testapps/pom.xml b/runtime/testapps/pom.xml
index 1ef403f5b..10759fa30 100644
--- a/runtime/testapps/pom.xml
+++ b/runtime/testapps/pom.xml
@@ -22,7 +22,7 @@
com.google.appengine
runtime-parent
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
jar
diff --git a/runtime/util/pom.xml b/runtime/util/pom.xml
index 9f632d1ef..8215fb0a5 100644
--- a/runtime/util/pom.xml
+++ b/runtime/util/pom.xml
@@ -22,7 +22,7 @@
com.google.appengine
runtime-parent
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
jar
diff --git a/runtime_shared/pom.xml b/runtime_shared/pom.xml
index aa13a7ae9..f8b120467 100644
--- a/runtime_shared/pom.xml
+++ b/runtime_shared/pom.xml
@@ -22,7 +22,7 @@
com.google.appengine
parent
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
jar
diff --git a/runtime_shared_jetty12/pom.xml b/runtime_shared_jetty12/pom.xml
index e2ba85c3c..c4b3207a7 100644
--- a/runtime_shared_jetty12/pom.xml
+++ b/runtime_shared_jetty12/pom.xml
@@ -22,7 +22,7 @@
com.google.appengine
parent
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
jar
diff --git a/runtime_shared_jetty121_ee11/pom.xml b/runtime_shared_jetty121_ee11/pom.xml
index 962603ff6..2c76d8635 100644
--- a/runtime_shared_jetty121_ee11/pom.xml
+++ b/runtime_shared_jetty121_ee11/pom.xml
@@ -22,7 +22,7 @@
com.google.appengine
parent
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
jar
diff --git a/runtime_shared_jetty121_ee8/pom.xml b/runtime_shared_jetty121_ee8/pom.xml
index 9af947be3..2cd5d2b52 100644
--- a/runtime_shared_jetty121_ee8/pom.xml
+++ b/runtime_shared_jetty121_ee8/pom.xml
@@ -24,7 +24,7 @@
com.google.appengine
parent
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
jar
diff --git a/runtime_shared_jetty12_ee10/pom.xml b/runtime_shared_jetty12_ee10/pom.xml
index a5935cd93..126e48b5f 100644
--- a/runtime_shared_jetty12_ee10/pom.xml
+++ b/runtime_shared_jetty12_ee10/pom.xml
@@ -22,7 +22,7 @@
com.google.appengine
parent
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
jar
diff --git a/sdk_assembly/pom.xml b/sdk_assembly/pom.xml
index 7ca92d80f..8fb948ea0 100644
--- a/sdk_assembly/pom.xml
+++ b/sdk_assembly/pom.xml
@@ -20,7 +20,7 @@
com.google.appengine
parent
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
4.0.0
appengine-java-sdk
diff --git a/sessiondata/pom.xml b/sessiondata/pom.xml
index 7e7a5d55d..7e0e1aa26 100644
--- a/sessiondata/pom.xml
+++ b/sessiondata/pom.xml
@@ -23,7 +23,7 @@
com.google.appengine
parent
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
jar
diff --git a/shared_sdk/pom.xml b/shared_sdk/pom.xml
index bf2d2d68f..46883c2ff 100644
--- a/shared_sdk/pom.xml
+++ b/shared_sdk/pom.xml
@@ -21,7 +21,7 @@
com.google.appengine
parent
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
jar
diff --git a/shared_sdk_jetty12/pom.xml b/shared_sdk_jetty12/pom.xml
index 5709e1891..3949c6890 100644
--- a/shared_sdk_jetty12/pom.xml
+++ b/shared_sdk_jetty12/pom.xml
@@ -21,7 +21,7 @@
com.google.appengine
parent
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
jar
diff --git a/shared_sdk_jetty121/pom.xml b/shared_sdk_jetty121/pom.xml
index 8d8f5b3a0..1f158849a 100644
--- a/shared_sdk_jetty121/pom.xml
+++ b/shared_sdk_jetty121/pom.xml
@@ -21,7 +21,7 @@
com.google.appengine
parent
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
jar
diff --git a/utils/pom.xml b/utils/pom.xml
index 0fdfba46b..0fb3a6e6a 100644
--- a/utils/pom.xml
+++ b/utils/pom.xml
@@ -21,7 +21,7 @@
com.google.appengine
parent
- 5.0.5-SNAPSHOT
+ 5.0.5-beta.1-SNAPSHOT
true