MYFACES-4734 Quarkus: FileUpload broken when Faces Servlet defined in web.xml
diff --git a/extensions/quarkus/deployment/src/main/java/org/apache/myfaces/core/extensions/quarkus/deployment/MyFacesProcessor.java b/extensions/quarkus/deployment/src/main/java/org/apache/myfaces/core/extensions/quarkus/deployment/MyFacesProcessor.java
index 8708689..b6c9e60 100644
--- a/extensions/quarkus/deployment/src/main/java/org/apache/myfaces/core/extensions/quarkus/deployment/MyFacesProcessor.java
+++ b/extensions/quarkus/deployment/src/main/java/org/apache/myfaces/core/extensions/quarkus/deployment/MyFacesProcessor.java
@@ -23,6 +23,7 @@
 import java.nio.file.Files;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -44,6 +45,7 @@
 import jakarta.faces.annotation.View;
 import jakarta.faces.application.Application;
 import jakarta.faces.application.ProjectStage;
+import jakarta.faces.application.ViewHandler;
 import jakarta.faces.component.FacesComponent;
 import jakarta.faces.component.StateHolder;
 import jakarta.faces.component.UIComponent;
@@ -233,26 +235,63 @@ void buildFeature(BuildProducer<FeatureBuildItem> feature)
 
     @BuildStep
     void buildServlet(WebMetadataBuildItem  webMetaDataBuildItem,
-            BuildProducer<FeatureBuildItem> feature,
-            BuildProducer<ServletBuildItem> servlet,
-            BuildProducer<ListenerBuildItem> listener)
+                      BuildProducer<FeatureBuildItem> feature,
+                      BuildProducer<ServletBuildItem> servlet,
+                      BuildProducer<ListenerBuildItem> listener) throws IOException
     {
         WebMetaData webMetaData = webMetaDataBuildItem.getWebMetaData();
-        ServletMetaData facesServlet = null;
-        if (webMetaData.getServlets() != null)
+
+        boolean extensionlessViews = webMetaData.getContextParams().stream().anyMatch(p ->
+                FacesServlet.AUTOMATIC_EXTENSIONLESS_MAPPING_PARAM_NAME.equals(p.getParamName())
+                        && "true".equals(p.getParamValue()));
+
+        // handle jakarta.faces.AUTOMATIC_EXTENSIONLESS_MAPPING
+        Set<String> extensionlessViewMappings = extensionlessViews
+                ? findTopLevelViews().stream()
+                    .map(view -> view.replace(ViewHandler.DEFAULT_FACELETS_SUFFIX, ""))
+                    .collect(Collectors.toSet())
+                : Collections.emptySet();
+
+        ServletMetaData facesServlet;
+
+        // try to find already registered FacesServlet
+        if (webMetaData.getServlets() == null)
+        {
+            facesServlet = null;
+        }
+        else
         {
             facesServlet = webMetaData.getServlets().stream()
-                .filter(servletMeta -> FacesServlet.class.getName().equals(servletMeta.getServletClass()))
-                .findFirst()
-                .orElse(null);
+                    .filter(servletMeta -> FacesServlet.class.getName().equals(servletMeta.getServletClass()))
+                    .findFirst()
+                    .orElse(null);
         }
+
+        ServletBuildItem.Builder builder;
         if (facesServlet == null)
         {
-            // Only define here if not explicitly defined in web.xml
-            servlet.produce(ServletBuildItem.builder("Faces Servlet", FacesServlet.class.getName())
+            // add a default "Faces Servlet"
+            builder = ServletBuildItem.builder("Faces Servlet", FacesServlet.class.getName())
                     .setMultipartConfig(new MultipartConfigElement(""))
-                    .addMapping("*.xhtml")
-                    .build());
+                    .addMapping("*.xhtml");
+
+            if (!extensionlessViewMappings.isEmpty())
+            {
+                extensionlessViewMappings.forEach(builder::addMapping);
+            }
+
+            servlet.produce(builder.build());
+        }
+        else if (!extensionlessViewMappings.isEmpty())
+        {
+            // in case when there are extensionless views enabled, we have add mappings for it
+            // this is currently only possible by re-register the FacesServlet
+            // https://github.com/quarkusio/quarkus/issues/49705
+            builder = createServletBuilderFromMetaData(webMetaData, facesServlet);
+
+            extensionlessViewMappings.forEach(builder::addMapping);
+
+            servlet.produce(builder.build());
         }
 
         // sometimes Quarkus doesn't scan web-fragments?! lets add it manually
@@ -434,6 +473,12 @@ void buildFlowScopedMapping(MyFacesRecorder recorder,
     @Record(ExecutionTime.STATIC_INIT)
     void collectTopLevelViews(MyFacesRecorder recorder) throws IOException
     {
+        findTopLevelViews().forEach(recorder::registerTopLevelView);
+    }
+
+    Set<String> findTopLevelViews() throws IOException
+    {
+        Set<String> topLevelViews = new HashSet<>();
         visitRuntimeMetaInfResources(visit ->
         {
             if (Files.isDirectory(visit.getPath()))
@@ -454,11 +499,12 @@ else if (!Files.isDirectory(visit.getPath()))
                     return;
                 }
 
-                String viewId = visit.getRelativePath().toString()
+                String viewId = "/" + visit.getRelativePath().toString()
                         .replace("META-INF/resources/", "");
-                recorder.registerTopLevelView(viewId);
+                topLevelViews.add(viewId);
             }
         });
+        return topLevelViews;
     }
 
     @BuildStep
@@ -943,4 +989,53 @@ private static void visitRuntimeMetaInfResources(PathVisitor visitor)
             }
         }
     }
+
+    private ServletBuildItem.Builder createServletBuilderFromMetaData(WebMetaData webMetaData,
+                                                                      ServletMetaData servletMeta)
+    {
+        ServletBuildItem.Builder builder = ServletBuildItem.builder(servletMeta.getName(),
+                servletMeta.getServletClass());
+
+        webMetaData.getServletMappings().forEach(mapping ->
+        {
+            if (servletMeta.getName().equals(mapping.getServletName()))
+            {
+                mapping.getUrlPatterns().forEach(builder::addMapping);
+            }
+        });
+        if (servletMeta.getInitParam() != null)
+        {
+            servletMeta.getInitParam()
+                    .forEach(param -> builder.addInitParam(param.getParamName(),  param.getParamValue()));
+        }
+        if (servletMeta.getLoadOnStartup() != null)
+        {
+            builder.setLoadOnStartup(Integer.parseInt(servletMeta.getLoadOnStartup()));
+        }
+        if (servletMeta.isAsyncSupported())
+        {
+            builder.setAsyncSupported(servletMeta.isAsyncSupported());
+        }
+        if (servletMeta.getMultipartConfig() != null)
+        {
+            MultipartConfigElement multipartConfigElement;
+            if (servletMeta.getMultipartConfig().getFileSizeThresholdSet()
+                    || servletMeta.getMultipartConfig().getMaxFileSizeSet()
+                    || servletMeta.getMultipartConfig().getMaxRequestSizeSet())
+            {
+                multipartConfigElement = new MultipartConfigElement(servletMeta.getMultipartConfig().getLocation());
+            }
+            else
+            {
+                multipartConfigElement = new MultipartConfigElement(servletMeta.getMultipartConfig().getLocation(),
+                        servletMeta.getMultipartConfig().getMaxFileSize(),
+                        servletMeta.getMultipartConfig().getMaxRequestSize(),
+                        servletMeta.getMultipartConfig().getFileSizeThreshold());
+            }
+
+            builder.setMultipartConfig(multipartConfigElement);
+        }
+
+        return builder;
+    }
 }
\ No newline at end of file
diff --git a/extensions/quarkus/pom.xml b/extensions/quarkus/pom.xml
index 84d2edc..9697e62 100644
--- a/extensions/quarkus/pom.xml
+++ b/extensions/quarkus/pom.xml
@@ -41,7 +41,7 @@
     </modules>
 
     <properties>
-        <quarkus.version>3.15.1</quarkus.version>
+        <quarkus.version>3.28.3</quarkus.version>
         <xml-combiner.version>3.0.0</xml-combiner.version>
     </properties>
 
diff --git a/extensions/quarkus/showcase/pom.xml b/extensions/quarkus/showcase/pom.xml
index 4c61207..e9310fa 100644
--- a/extensions/quarkus/showcase/pom.xml
+++ b/extensions/quarkus/showcase/pom.xml
@@ -80,7 +80,7 @@
         <dependency>
             <groupId>org.junit.jupiter</groupId>
             <artifactId>junit-jupiter-api</artifactId>
-            <version>5.10.2</version>
+            <version>5.13.4</version>
             <scope>test</scope>
         </dependency>
         <dependency>
diff --git a/extensions/quarkus/showcase/src/main/java/org/apache/myfaces/core/extensions/quarkus/showcase/view/FileView.java b/extensions/quarkus/showcase/src/main/java/org/apache/myfaces/core/extensions/quarkus/showcase/view/FileView.java
index 3b3ec35..05e6bae 100644
--- a/extensions/quarkus/showcase/src/main/java/org/apache/myfaces/core/extensions/quarkus/showcase/view/FileView.java
+++ b/extensions/quarkus/showcase/src/main/java/org/apache/myfaces/core/extensions/quarkus/showcase/view/FileView.java
@@ -18,6 +18,8 @@
  */
 package org.apache.myfaces.core.extensions.quarkus.showcase.view;
 
+import jakarta.faces.application.FacesMessage;
+import jakarta.faces.context.FacesContext;
 import org.primefaces.event.FileUploadEvent;
 import org.primefaces.model.file.UploadedFile;
 
@@ -45,12 +47,18 @@ public void upload()
         {
             System.out.println(file.getFileName());
         }
+
+        FacesMessage facesMessage = new FacesMessage(FacesMessage.Severity.INFO, "File Uploaded", "File Uploaded");
+        FacesContext.getCurrentInstance().addMessage(null, facesMessage);
     }
 
     public void handleFileUpload(FileUploadEvent event)
     {
         System.out.println("auto file upload start");
         System.out.println(event.getFile().getFileName());
+
+        FacesMessage facesMessage = new FacesMessage(FacesMessage.Severity.INFO, "File Uploaded", "File Uploaded");
+        FacesContext.getCurrentInstance().addMessage(null, facesMessage);
     }
 
     public UploadedFile getFile()
diff --git a/extensions/quarkus/showcase/src/main/resources/META-INF/resources/file.xhtml b/extensions/quarkus/showcase/src/main/resources/META-INF/resources/file.xhtml
index d2c5645..4224d3b 100644
--- a/extensions/quarkus/showcase/src/main/resources/META-INF/resources/file.xhtml
+++ b/extensions/quarkus/showcase/src/main/resources/META-INF/resources/file.xhtml
@@ -22,20 +22,22 @@
       xmlns:f="http://java.sun.com/jsf/core"
       xmlns:ui="http://java.sun.com/jsf/facelets"
       xmlns:p="http://primefaces.org/ui">
-    
+
     <f:view contentType="text/html">
         <h:head>
 
         </h:head>
 
-        <h:body>            
+        <h:body>
             Advanced:
             <h:form id="test" enctype="multipart/form-data">
+                <p:messages />
+
                 <p:fileUpload
-                    label="test"
-                    mode="advanced"
-                    skinSimple="true" listener="#{fileView.handleFileUpload}"
-                    auto="true"/>
+                        label="test"
+                        mode="advanced"
+                        skinSimple="true" listener="#{fileView.handleFileUpload}"
+                        auto="true"/>
             </h:form>
 
             <br />
@@ -45,10 +47,22 @@
             <h:form id="test1" enctype="multipart/form-data">
                 <p:fileUpload value="#{fileView.file}" mode="simple" skinSimple="false"/>
 
-                <p:commandButton id="submit1" value="Submit" ajax="false" action="#{fileView.upload}"/>        
+                <p:commandButton id="submit1" value="Submit" ajax="false" action="#{fileView.upload}"/>
                 <h:commandButton id="submit2" value="Submit" action="#{fileView.upload}"/>
 
             </h:form>
+
+            <br />
+            <br />
+
+            Simple AJAX:
+            <h:form id="test2" enctype="multipart/form-data">
+                <p:messages />
+
+                <p:fileUpload value="#{fileView.file}" mode="simple" skinSimple="false"/>
+
+                <p:commandButton id="submit3" value="Submit" action="#{fileView.upload}" process="@this" update="@form"/>
+            </h:form>
         </h:body>
     </f:view>
 </html>
\ No newline at end of file
diff --git a/extensions/quarkus/showcase/src/main/resources/META-INF/web.xml b/extensions/quarkus/showcase/src/main/resources/META-INF/web.xml
index 7b18923..63e735e 100644
--- a/extensions/quarkus/showcase/src/main/resources/META-INF/web.xml
+++ b/extensions/quarkus/showcase/src/main/resources/META-INF/web.xml
@@ -35,12 +35,28 @@
     </context-param>
 
     <context-param>
-        <param-name>primefaces.THEME</param-name>
-        <param-value>luna-amber</param-value>
-    </context-param>
-    <context-param>
         <param-name>jakarta.faces.AUTOMATIC_EXTENSIONLESS_MAPPING</param-name>
         <param-value>true</param-value>
     </context-param>
-    
+
+
+    <servlet>
+        <servlet-name>Faces Servlet</servlet-name>
+        <servlet-class>jakarta.faces.webapp.FacesServlet</servlet-class>
+        <multipart-config>
+            <max-file-size>52428800</max-file-size>        <!-- 50 MB -->
+            <max-request-size>104857600</max-request-size> <!-- 100 MB -->
+            <file-size-threshold>1048576</file-size-threshold> <!-- 1 MB -->
+        </multipart-config>
+    </servlet>
+
+    <servlet-mapping>
+        <servlet-name>Faces Servlet</servlet-name>
+        <url-pattern>/</url-pattern>
+    </servlet-mapping>
+    <servlet-mapping>
+        <servlet-name>Faces Servlet</servlet-name>
+        <url-pattern>*.xhtml</url-pattern>
+    </servlet-mapping>
+
 </web-app>
\ No newline at end of file