diff --git a/appengine-java21/firebase-backend/README.md b/appengine-java21/firebase-backend/README.md
new file mode 100644
index 00000000000..4384c037e30
--- /dev/null
+++ b/appengine-java21/firebase-backend/README.md
@@ -0,0 +1,162 @@
+# Build a mobile app using Firebase and App Engine flexible environment
+
+
+
+This repository contains Android client sample code for the [Build a Mobile App
+Using Firebase and App Engine Flexible
+Environment](https://cloud.google.com/solutions/mobile/mobile-firebase-app-engine-flexible)
+solution. You can find the sample code for the Android client code in the
+[firebase-android-client](../../../firebase-android-client) repository.
+
+## Deployment requirements
+
+- Enable the following services in the [Google Cloud Platform
+ console](https://console.cloud.google.com):
+ - Google App Engine
+ - Google Compute Engine
+- Sign up for [Firebase](https://firebase.google.com/) and create a new project
+ in the [Firebase console](https://console.firebase.google.com/).
+- Install the following tools in your development environment:
+ - [Java 21](https://www.oracle.com/java/technologies/downloads/)
+ - [Apache Maven](https://maven.apache.org/)
+ - [Google Cloud SDK](https://cloud.google.com/sdk/)
+
+> **Note**: Firebase is a Google product, independent from Google Cloud
+> Platform.
+
+A Java application deployed to App Engine Flexible Environment [needs to use Java 21 Runtime](https://cloud.google.com/appengine/docs/flexible/java/setting-up-environment).
+However, in your local development environment you can
+use JDK 21 or newer as long as your JDK is able to produce Java 21 class files.
+
+## Google Cloud SDK setup
+
+Configure the SDK to access the Google Cloud Platform by using the following
+command:
+
+```bash
+gcloud auth login
+```
+
+Get the project ID from the settings page of your Firebase project. Use the
+following command to set your Firebase project as the active project for the
+SDK:
+
+```bash
+gcloud config set project [project-id]
+```
+
+## Configuration
+
+Enable the Google sign-in provider by following these steps:
+
+1. Sign in to the [Firebase console](https://console.firebase.google.com) and
+ select your project.
+1. In the **Develop** section, select **Authentication**.
+1. In the **Authentication** page, select **Sign-in Method**.
+1. Select and enable the **Google** sign-in provider.
+
+Follow these steps to configure a service account for the backend application:
+
+1. Go to your project settings page on the [Firebase
+ console](https://console.firebase.google.com).
+1. Click the **Settings** gear next to 'Project Overview' and then **Project settings**.
+1. Select **Service accounts** and click the link **Manage service account permissions**.
+1. In the **IAM & admin** page click **Create service account**.
+1. In the dialog, create an account with the following parameters:
+ * Enter *playchat-servlet* in the **Service account name** field.
+ * Select **Project** > **Owner** in the **Role** menu.
+ > **Caution**: The owner role gives the service account full access to all
+ > resources in the project. In a production app, you should change the role
+ > to the minimum access that your service account requires.
+1. After the service account is created, click it and choose **Create new key** in the **ADD KEY** dropdown button.
+ * Choose **JSON** as the key type.
+ * Click **CREATE** to download the key.
+1. After you finish creating the account, your browser downloads the service
+ account's private key to your computer as a JSON file. Move the file to the
+ `src/main/webapp/WEB-INF` folder in the backend project.
+1. From the left menu of the [Firebase
+ console](https://console.firebase.google.com),
+ select **Database** in the **Develop** group.
+
+1. In the **Database** page, click **Create database** in the **Realtime Database** section.
+
+1. In the **Security rules for Realtime Database** dialog, select **Start in
+ test mode** and click **Enable**.
+
+ Caution: Test mode allows anyone with your database reference to perform
+ read and write operations to your database. If test mode isn't appropriate
+ for your purposes, you can write security rules to manage access to your
+ data. For more information, see
+ [Get Started with Database Rules](https://firebase.google.com/docs/database/security/quickstart)
+ in the Firebase documentation.
+
+ This step displays the data you’ve stored in Firebase. In later steps of
+ this tutorial, you can revisit this web page to see data added and updated
+ by the client app and backend servlet.
+1. In the **Rules** tab of the database, make sure you have the security rules for read/write. For example:
+ ```json
+ {
+ "rules": {
+ ".read": true,
+ ".write": true
+ }
+ }
+ ```
+1. Make a note of the Firebase URL for your project, which is in the form
+ `https://[project-id].firebaseio.com/` and appears next to a
+ link icon.
+1. Open the `src/main/webapp/WEB-INF/web.xml` file and do the following:
+ * Replace the `JSON_FILE_NAME` placeholder with the JSON file from that
+ stores the service account's private key.
+ * Replace the `FIREBASE_URL` placeholder with the URL of the Realtime
+ Database from the previous step.
+
+ The following example shows the placeholders in the `web.xml` file:
+ ```xml
+
+ credential
+ /WEB-INF/JSON_FILE_NAME
+
+
+ databaseUrl
+ FIREBASE_URL
+
+ ```
+
+
+## Build and deploy
+
+To build and run the backend module locally:
+
+```bash
+mvn clean package appengine:run
+```
+
+To deploy the backend module to App Engine:
+
+```bash
+mvn clean package appengine:deploy
+```
+
+## View user event logs
+
+Run the Android client app, perform some activities such as signing in and
+switching channels, and go to the following URL to view user event logs:
+
+```bash
+https://[project-id].appspot.com/printLogs
+```
+
+## License
+
+Copyright 2018 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 http://www.apache.org/licenses/LICENSE-2.0 Unless required by
+applicable law or agreed to in writing, software distributed under the License
+is distributed on an "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the specific language
+governing permissions and limitations under the License.
+
+This is not an official Google product.
diff --git a/appengine-java21/firebase-backend/pom.xml b/appengine-java21/firebase-backend/pom.xml
new file mode 100644
index 00000000000..10e551e9366
--- /dev/null
+++ b/appengine-java21/firebase-backend/pom.xml
@@ -0,0 +1,128 @@
+
+ 4.0.0
+ com.example.appengine
+ firebase-backend
+ war
+ 0.0.1-SNAPSHOT
+ App Engine Backend module for Firebase
+ https://cloud.google.com
+
+
+ UTF-8
+ UTF-8
+ 1.8
+ 1.8
+ true
+ true
+ false
+
+
+ 3.5
+
+
+
+ com.google.cloud.samples
+ shared-configuration
+ 1.2.0
+
+
+
+
+
+ com.google.cloud
+ libraries-bom
+ 26.28.0
+ pom
+ import
+
+
+
+
+
+
+
+
+ com.google.cloud
+ google-cloud-logging
+
+
+ javax.servlet
+ javax.servlet-api
+ 3.1.0
+ provided
+
+
+ javax.servlet.jsp
+ javax.servlet.jsp-api
+ 2.3.3
+ provided
+
+
+ jstl
+ jstl
+ 1.2
+
+
+ com.fasterxml.jackson.core
+ jackson-annotations
+ 2.16.0
+
+
+ com.google.firebase
+ firebase-server-sdk
+ [3.0.0,]
+
+
+ junit
+ junit
+ 4.13.2
+ test
+
+
+
+
+ backend
+ target/${project.artifactId}-${project.version}/WEB-INF/classes
+
+
+ org.apache.maven.plugins
+ 3.11.0
+ maven-compiler-plugin
+
+ 21
+ 21
+
+
+
+ org.apache.maven.plugins
+ maven-war-plugin
+ 3.4.0
+
+ true
+
+
+
+ ${basedir}/src/main/webapp/WEB-INF
+ true
+ WEB-INF
+
+
+
+
+
+ com.google.cloud.tools
+ appengine-maven-plugin
+ 2.5.0
+
+ GCLOUD_CONFIG
+ GCLOUD_CONFIG
+ true
+
+
+
+
+
diff --git a/appengine-java21/firebase-backend/src/main/java/com/google/cloud/solutions/flexenv/backend/MessageProcessorServlet.java b/appengine-java21/firebase-backend/src/main/java/com/google/cloud/solutions/flexenv/backend/MessageProcessorServlet.java
new file mode 100644
index 00000000000..153c7f5a21b
--- /dev/null
+++ b/appengine-java21/firebase-backend/src/main/java/com/google/cloud/solutions/flexenv/backend/MessageProcessorServlet.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright 2018 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
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.cloud.solutions.flexenv.backend;
+
+import com.google.cloud.solutions.flexenv.common.LogEntry;
+import com.google.firebase.FirebaseApp;
+import com.google.firebase.FirebaseOptions;
+import com.google.firebase.database.ChildEventListener;
+import com.google.firebase.database.DataSnapshot;
+import com.google.firebase.database.DatabaseError;
+import com.google.firebase.database.DatabaseReference;
+import com.google.firebase.database.FirebaseDatabase;
+import com.google.firebase.database.MutableData;
+import com.google.firebase.database.Transaction;
+import java.io.IOException;
+import java.lang.Override;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.Random;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.logging.Logger;
+import javax.servlet.ServletConfig;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * MessageProcessorServlet is responsible for receiving user event logs
+ * from clients and printing logs when requested.
+ *
+ * @author teppeiy
+ */
+public class MessageProcessorServlet extends HttpServlet {
+ private static final long serialVersionUID = 8126789192972477663L;
+
+ // Firebase keys shared with client apps
+ private static final String IBX = "inbox";
+ private static final String CH = "channels";
+ private static final String REQLOG = "requestLogger";
+
+ // The Logger object records application-level events. The events are
+ // displayed in the local console if the servlet is running on the local
+ // server, or in GCP Console if the servlet is running on the cloud.
+ private static Logger localLog = Logger.getLogger(MessageProcessorServlet.class.getName());
+ private DatabaseReference firebase;
+
+ private String channels;
+ private String inbox;
+
+ // If the number of messages or user events in each channel exceeds
+ // the value of purgeLogs, they are purged.
+ private int purgeLogs;
+ // Purger is invoked with every "purgeInterval".
+ private int purgeInterval;
+ private MessagePurger purger;
+
+ private ConcurrentLinkedQueue logs;
+
+ @Override
+ public void init(ServletConfig config) {
+ final String credential = config.getInitParameter("credential");
+ final String databaseUrl = config.getInitParameter("databaseUrl");
+ channels = config.getInitParameter("channels");
+ purgeLogs = Integer.parseInt(config.getInitParameter("purgeLogs"));
+ purgeInterval = Integer.parseInt(config.getInitParameter("purgeInterval"));
+
+ logs = new ConcurrentLinkedQueue();
+ generateUniqueId();
+
+ localLog.info("Credential file : " + credential);
+ FirebaseOptions options = new FirebaseOptions.Builder()
+ .setServiceAccount(config.getServletContext().getResourceAsStream(credential))
+ .setDatabaseUrl(databaseUrl)
+ .build();
+ FirebaseApp.initializeApp(options);
+ firebase = FirebaseDatabase.getInstance().getReference();
+
+ // [START gae_java21_firebase-backend_replyToRequest]
+ /*
+ * Receive a request from a client and reply back its inbox ID.
+ * Using a transaction ensures that only a single servlet instance replies
+ * to the client. This lets the client know to which servlet instance
+ * send consecutive user event logs.
+ */
+ firebase.child(REQLOG).addChildEventListener(new ChildEventListener() {
+ public void onChildAdded(DataSnapshot snapshot, String prevKey) {
+ firebase.child(IBX + "/" + snapshot.getValue()).runTransaction(new Transaction.Handler() {
+ public Transaction.Result doTransaction(MutableData currentData) {
+ // Only the first servlet instance writes its ID to the client inbox.
+ if (currentData.getValue() == null) {
+ currentData.setValue(inbox);
+ }
+ return Transaction.success(currentData);
+ }
+
+ public void onComplete(DatabaseError error, boolean committed, DataSnapshot snapshot) {}
+ });
+ firebase.child(REQLOG).removeValue();
+ }
+ // [START_EXCLUDE]
+
+ public void onCancelled(DatabaseError error) {
+ localLog.warning(error.getDetails());
+ }
+
+ public void onChildChanged(DataSnapshot snapshot, String prevKey) {}
+
+ public void onChildMoved(DataSnapshot snapshot, String prevKey) {}
+
+ public void onChildRemoved(DataSnapshot snapshot) {}
+ // [END_EXCLUDE]
+ });
+ // [END gae_java21_firebase-backend_replyToRequest]
+
+ purger = new MessagePurger(firebase, purgeInterval, purgeLogs);
+ String[] channelArray = channels.split(",");
+ for (int i = 0; i < channelArray.length; i++) {
+ purger.registerBranch(CH + "/" + channelArray[i]);
+ }
+ initLogger();
+ purger.setPriority(Thread.MIN_PRIORITY);
+ purger.start();
+ }
+
+ /*
+ * To generate a unique ID for each servlet instance and clients
+ * push messages to "/inbox/".
+ */
+ private void generateUniqueId() {
+ Random rand = new Random();
+ StringBuffer buf = new StringBuffer();
+ for (int i = 0; i < 16; i++) {
+ buf.append(Integer.toString(rand.nextInt(10)));
+ }
+ inbox = buf.toString();
+ }
+
+ // [START gae_java21_firebase-backend_initializeEventLogger]
+ /*
+ * Initialize user event logger. This is just a sample implementation to
+ * demonstrate receiving updates. A production version of this app should
+ * transform, filter, or load to another data store such as Google BigQuery.
+ */
+ private void initLogger() {
+ String loggerKey = IBX + "/" + inbox + "/logs";
+ purger.registerBranch(loggerKey);
+ firebase.child(loggerKey).addChildEventListener(new ChildEventListener() {
+ public void onChildAdded(DataSnapshot snapshot, String prevKey) {
+ if (snapshot.exists()) {
+ LogEntry entry = snapshot.getValue(LogEntry.class);
+ logs.add(entry);
+ }
+ }
+
+ public void onCancelled(DatabaseError error) {
+ localLog.warning(error.getDetails());
+ }
+
+ public void onChildChanged(DataSnapshot arg0, String arg1) {}
+
+ public void onChildMoved(DataSnapshot arg0, String arg1) {}
+
+ public void onChildRemoved(DataSnapshot arg0) {}
+ });
+ }
+ // [END gae_java21_firebase-backend_initializeEventLogger]
+
+ @Override
+ public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+ doPost(req, resp);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest,
+ * javax.servlet.http.HttpServletResponse)
+ * Just printing all user event logs stored in memory of this servlet instance.
+ */
+ @Override
+ public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+ resp.setContentType("text/plain");
+ resp.getWriter().println("Inbox : " + inbox);
+
+ for (Iterator iter = logs.iterator(); iter.hasNext();) {
+ LogEntry entry = (LogEntry) iter.next();
+ resp.getWriter().println(new Date(entry.getTimeLong()).toString() + "(id=" + entry.getTag()
+ + ")" + " : " + entry.getLog());
+ }
+ }
+
+ @Override
+ public void destroy() {
+ purger.interrupt();
+ firebase.child(IBX + "/" + inbox).removeValue();
+ }
+}
diff --git a/appengine-java21/firebase-backend/src/main/java/com/google/cloud/solutions/flexenv/backend/MessagePurger.java b/appengine-java21/firebase-backend/src/main/java/com/google/cloud/solutions/flexenv/backend/MessagePurger.java
new file mode 100644
index 00000000000..37e2fa09a7f
--- /dev/null
+++ b/appengine-java21/firebase-backend/src/main/java/com/google/cloud/solutions/flexenv/backend/MessagePurger.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2018 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
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.cloud.solutions.flexenv.backend;
+
+import com.google.firebase.database.DataSnapshot;
+import com.google.firebase.database.DatabaseError;
+import com.google.firebase.database.DatabaseReference;
+import com.google.firebase.database.Query;
+import com.google.firebase.database.ValueEventListener;
+import java.lang.Override;
+import java.util.Iterator;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.logging.Logger;
+
+/**
+ * MessagePurger is responsible for purging messages pushed under
+ * registered parent keys. If the number of entries exceeds "maxLogs",
+ * the excess entries are purged. It checks each registered
+ * parent key under regular interval, "purgeInterval".
+ *
+ * @author teppeiy
+ */
+public class MessagePurger extends Thread {
+ private static Logger logger = Logger.getLogger(MessagePurger.class.getName());
+
+ private DatabaseReference firebase;
+ private int purgeInterval;
+ private int purgeLogs;
+ private ConcurrentLinkedQueue branches;
+
+ public MessagePurger(DatabaseReference firebase, int purgeInterval, int purgeLogs) {
+ this.setDaemon(true);
+ this.firebase = firebase;
+ this.purgeInterval = purgeInterval;
+ this.purgeLogs = purgeLogs;
+ branches = new ConcurrentLinkedQueue<>();
+ }
+
+ public void registerBranch(String branchKey) {
+ branches.add(branchKey);
+ }
+
+ public void run() {
+ while (true) {
+ try {
+ Thread.sleep(purgeInterval);
+
+ Iterator iter = branches.iterator();
+ while (iter.hasNext()) {
+ final String branchKey = (String) iter.next();
+ // Query to check whether entries exceed "maxLogs".
+ Query query = firebase.child(branchKey).orderByKey().limitToFirst(purgeLogs);
+ query.addListenerForSingleValueEvent(new ValueEventListener() {
+ @Override
+ public void onDataChange(DataSnapshot snapshot) {
+ // If entries are less than "maxLogs", do nothing.
+ if (snapshot.getChildrenCount() == purgeLogs) {
+ for (DataSnapshot child : snapshot.getChildren()) {
+ firebase.child(branchKey + "/" + child.getKey()).removeValue();
+ }
+ }
+ }
+
+ @Override
+ public void onCancelled(DatabaseError error) {
+ logger.warning(error.getDetails());
+ }
+ });
+ }
+ } catch (InterruptedException ie) {
+ logger.warning(ie.getMessage());
+ break;
+ }
+ }
+ }
+}
diff --git a/appengine-java21/firebase-backend/src/main/java/com/google/cloud/solutions/flexenv/common/LogEntry.java b/appengine-java21/firebase-backend/src/main/java/com/google/cloud/solutions/flexenv/common/LogEntry.java
new file mode 100644
index 00000000000..ff188e2413c
--- /dev/null
+++ b/appengine-java21/firebase-backend/src/main/java/com/google/cloud/solutions/flexenv/common/LogEntry.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2018 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
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.cloud.solutions.flexenv.common;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.google.firebase.database.ServerValue;
+import java.util.Map;
+
+/*
+ * An instance of LogEntry represents a user event log, such as signin/out and switching a channel.
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class LogEntry {
+ private String tag;
+ private String log;
+ private Long time;
+
+ public LogEntry() {}
+
+ public LogEntry(String tag, String log) {
+ this.tag = tag;
+ this.log = log;
+ }
+
+ public String getTag() {
+ return tag;
+ }
+
+ public void setTag(String tag) {
+ this.tag = tag;
+ }
+
+ public String getLog() {
+ return log;
+ }
+
+ public void setLog(String log) {
+ this.log = log;
+ }
+
+ public Map getTime() {
+ return ServerValue.TIMESTAMP;
+ }
+
+ public void setTime(Long time) {
+ this.time = time;
+ }
+
+ @JsonIgnore
+ public Long getTimeLong() {
+ return time;
+ }
+}
diff --git a/appengine-java21/firebase-backend/src/main/java/com/google/cloud/solutions/flexenv/common/Message.java b/appengine-java21/firebase-backend/src/main/java/com/google/cloud/solutions/flexenv/common/Message.java
new file mode 100644
index 00000000000..8ad28d27417
--- /dev/null
+++ b/appengine-java21/firebase-backend/src/main/java/com/google/cloud/solutions/flexenv/common/Message.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2018 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
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.cloud.solutions.flexenv.common;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.google.firebase.database.ServerValue;
+import java.util.Map;
+
+/*
+ * An instance of Message represents an actual message pushed to a channel.
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class Message {
+ private String text;
+ private String displayName;
+ private Long time;
+
+ public Message() {}
+
+ public Message(String text, String displayName) {
+ this.text = text;
+ this.displayName = displayName;
+ }
+
+ public String getText() {
+ return text;
+ }
+
+ public void setText(String text) {
+ this.text = text;
+ }
+
+ public String getDisplayName() {
+ return displayName;
+ }
+
+ public void setDisplayName(String displayName) {
+ this.displayName = displayName;
+ }
+
+ public Map getTime() {
+ return ServerValue.TIMESTAMP;
+ }
+
+ public void setTime(Long time) {
+ this.time = time;
+ }
+
+ @JsonIgnore
+ public Long getTimeLong() {
+ return time;
+ }
+}
diff --git a/appengine-java21/firebase-backend/src/main/webapp/WEB-INF/appengine-web.xml b/appengine-java21/firebase-backend/src/main/webapp/WEB-INF/appengine-web.xml
new file mode 100644
index 00000000000..636961b7058
--- /dev/null
+++ b/appengine-java21/firebase-backend/src/main/webapp/WEB-INF/appengine-web.xml
@@ -0,0 +1,32 @@
+
+
+
+
+ true
+ false
+ false
+
+ 3
+
+
+
+
+
+
+
+ java21
+
diff --git a/appengine-java21/firebase-backend/src/main/webapp/WEB-INF/logging.properties b/appengine-java21/firebase-backend/src/main/webapp/WEB-INF/logging.properties
new file mode 100644
index 00000000000..db5482d74f3
--- /dev/null
+++ b/appengine-java21/firebase-backend/src/main/webapp/WEB-INF/logging.properties
@@ -0,0 +1,13 @@
+# A default java.util.logging configuration.
+# (All App Engine logging is through java.util.logging by default).
+#
+# To use this configuration, copy it into your application's WEB-INF
+# folder and add the following to your appengine-web.xml:
+#
+#
+#
+#
+#
+
+# Set the default logging level for all loggers to INFO
+.level = INFO
diff --git a/appengine-java21/firebase-backend/src/main/webapp/WEB-INF/web.xml b/appengine-java21/firebase-backend/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 00000000000..955bbb12b4c
--- /dev/null
+++ b/appengine-java21/firebase-backend/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,32 @@
+
+
+
+ MessageProcessor
+ com.google.cloud.solutions.flexenv.backend.MessageProcessorServlet
+
+ channels
+ books,game,music,sports,travel
+
+
+ credential
+ /WEB-INF/JSON_FILE_NAME
+
+
+ databaseUrl
+ FIREBASE_URL
+
+
+ purgeLogs
+ 100
+
+
+ purgeInterval
+ 20000
+
+ 0
+
+
+ MessageProcessor
+ /printLogs
+
+