From bdbc51e99a3867a133991442ebb525fe79d46e41 Mon Sep 17 00:00:00 2001 From: Gaurav Gupta Date: Wed, 29 May 2024 22:23:29 +0530 Subject: [PATCH 1/2] FISH-8747 Payara Starter handle requests with maxAsync limit of 5 --- ...esource.java => ApplicationGenerator.java} | 103 ++++++++---------- .../resources/ApplicationResource.java | 97 +++++++++++++++++ 2 files changed, 141 insertions(+), 59 deletions(-) rename starter-ui/src/main/java/fish/payara/starter/resources/{ApplicationGeneratorResource.java => ApplicationGenerator.java} (76%) create mode 100644 starter-ui/src/main/java/fish/payara/starter/resources/ApplicationResource.java diff --git a/starter-ui/src/main/java/fish/payara/starter/resources/ApplicationGeneratorResource.java b/starter-ui/src/main/java/fish/payara/starter/resources/ApplicationGenerator.java similarity index 76% rename from starter-ui/src/main/java/fish/payara/starter/resources/ApplicationGeneratorResource.java rename to starter-ui/src/main/java/fish/payara/starter/resources/ApplicationGenerator.java index a764e98..3fc6c60 100644 --- a/starter-ui/src/main/java/fish/payara/starter/resources/ApplicationGeneratorResource.java +++ b/starter-ui/src/main/java/fish/payara/starter/resources/ApplicationGenerator.java @@ -1,6 +1,6 @@ /* * - * Copyright (c) 2023 Payara Foundation and/or its affiliates. All rights reserved. + * Copyright (c) 2024 Payara Foundation and/or its affiliates. All rights reserved. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common Development @@ -57,16 +57,12 @@ import static fish.payara.starter.resources.ApplicationConfiguration.PLATFORM; import static fish.payara.starter.resources.ApplicationConfiguration.PROFILE; import static fish.payara.starter.resources.ApplicationConfiguration.VERSION; -import jakarta.ws.rs.Consumes; -import jakarta.ws.rs.POST; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.core.MediaType; -import jakarta.ws.rs.core.Response; -import jakarta.ws.rs.core.StreamingOutput; +import jakarta.annotation.Resource; +import jakarta.enterprise.concurrent.ManagedExecutorDefinition; +import jakarta.enterprise.concurrent.ManagedExecutorService; +import jakarta.enterprise.context.ApplicationScoped; import java.io.File; import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.nio.file.Files; @@ -74,16 +70,25 @@ import java.util.LinkedList; import java.util.List; import java.util.Properties; +import java.util.concurrent.Future; import java.util.logging.Level; import java.util.logging.Logger; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import org.apache.maven.cli.MavenCli; -@Path("starter") -public class ApplicationGeneratorResource { - - private static final Logger LOGGER = Logger.getLogger(ApplicationGeneratorResource.class.getName()); +/** + * + * @author Gaurav Gupta + */ +@ManagedExecutorDefinition( + name = "java:comp/DefaultManagedExecutorService", + maxAsync = 5 +) +@ApplicationScoped +public class ApplicationGenerator { + + private static final Logger LOGGER = Logger.getLogger(ApplicationGenerator.class.getName()); private static final String WORKING_DIR_PREFIX = "payara-starter-"; private static final String ARCHETYPE_GROUP_ID = "fish.payara.starter"; private static final String ARCHETYPE_ARTIFACT_ID = "payara-starter-archetype"; @@ -91,30 +96,32 @@ public class ApplicationGeneratorResource { private static final String MAVEN_ARCHETYPE_CMD = "archetype:generate"; private static final String ZIP_EXTENSION = ".zip"; - @POST - @Consumes(MediaType.APPLICATION_JSON) - @Produces(MediaType.APPLICATION_OCTET_STREAM) - public Response generate(ApplicationConfiguration appProperties) { - File applicationDir = null; - try { - File workingDirectory = Files.createTempDirectory(WORKING_DIR_PREFIX).toFile(); - workingDirectory.deleteOnExit(); - LOGGER.log(Level.INFO, "Executing Maven Archetype from working directory: {0}", new Object[]{workingDirectory.getAbsolutePath()}); - Properties properties = buildMavenProperties(appProperties); - invokeMavenArchetype(ARCHETYPE_GROUP_ID, ARCHETYPE_ARTIFACT_ID, ARCHETYPE_VERSION, - properties, workingDirectory); - - LOGGER.info("Creating a compressed application bundle."); - applicationDir = new File(workingDirectory, appProperties.getArtifactId()); - File zipFile = zipDirectory(applicationDir, workingDirectory); - return buildResponse(zipFile, appProperties.getArtifactId()); - } catch (IOException ie) { - throw new RuntimeException("Failed to generate application.", ie); - } finally { - if (applicationDir != null) { - deleteDirectory(applicationDir); + @Resource(name = "java:comp/DefaultManagedExecutorService") + private ManagedExecutorService executorService; + + public Future generate(ApplicationConfiguration appProperties) { + System.out.println("executorService " + executorService); + return executorService.submit(() -> { + File applicationDir = null; + try { + File workingDirectory = Files.createTempDirectory(WORKING_DIR_PREFIX).toFile(); + workingDirectory.deleteOnExit(); + LOGGER.log(Level.INFO, "Executing Maven Archetype from working directory: {0}", new Object[]{workingDirectory.getAbsolutePath()}); + Properties properties = buildMavenProperties(appProperties); + invokeMavenArchetype(ARCHETYPE_GROUP_ID, ARCHETYPE_ARTIFACT_ID, ARCHETYPE_VERSION, + properties, workingDirectory); + + LOGGER.info("Creating a compressed application bundle."); + applicationDir = new File(workingDirectory, appProperties.getArtifactId()); + return zipDirectory(applicationDir, workingDirectory); + } catch (IOException ie) { + throw new RuntimeException("Failed to generate application.", ie); + } finally { + if (applicationDir != null) { + deleteDirectory(applicationDir); + } } - } + }); } // Utility method to delete a directory and its contents @@ -156,7 +163,7 @@ private Properties buildMavenProperties(ApplicationConfiguration appProperties) return properties; } - public void invokeMavenArchetype(String archetypeGroupId, String archetypeArtifactId, + private void invokeMavenArchetype(String archetypeGroupId, String archetypeArtifactId, String archetypeVersion, Properties properties, File workingDirectory) { System.setProperty(MavenCli.MULTIMODULE_PROJECT_DIRECTORY, workingDirectory.getAbsolutePath()); @@ -212,26 +219,4 @@ private void zipFile(File fileToZip, String fileName, ZipOutputStream zipOut) th } } - private Response buildResponse(File zipFile, String artifactId) throws FileNotFoundException { - StreamingOutput streamingOutput = output -> { - try (FileInputStream fis = new FileInputStream(zipFile)) { - byte[] buffer = new byte[1024]; - int bytesRead; - while ((bytesRead = fis.read(buffer)) != -1) { - output.write(buffer, 0, bytesRead); - } - } catch (IOException e) { - throw new RuntimeException("Failed to stream zip file.", e); - } finally { - zipFile.delete(); - zipFile.getParentFile().delete(); - } - }; - - return Response.ok(streamingOutput) - .header("Content-Disposition", "attachment; filename=\"" + artifactId + ".zip\"") - .header("Content-Type", "application/octet-stream") - .build(); - } - } diff --git a/starter-ui/src/main/java/fish/payara/starter/resources/ApplicationResource.java b/starter-ui/src/main/java/fish/payara/starter/resources/ApplicationResource.java new file mode 100644 index 0000000..052044f --- /dev/null +++ b/starter-ui/src/main/java/fish/payara/starter/resources/ApplicationResource.java @@ -0,0 +1,97 @@ +/* + * + * Copyright (c) 2023-24 Payara Foundation and/or its affiliates. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common Development + * and Distribution License("CDDL") (collectively, the "License"). You + * may not use this file except in compliance with the License. You can + * obtain a copy of the License at + * https://github.com/payara/Payara/blob/master/LICENSE.txt + * See the License for the specific + * language governing permissions and limitations under the License. + * + * When distributing the software, include this License Header Notice in each + * file and include the License file at glassfish/legal/LICENSE.txt. + * + * GPL Classpath Exception: + * The Payara Foundation designates this particular file as subject to the "Classpath" + * exception as provided by the Payara Foundation in the GPL Version 2 section of the License + * file that accompanied this code. + * + * Modifications: + * If applicable, add the following below the License Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyright [year] [name of copyright owner]" + * + * Contributor(s): + * If you wish your version of this file to be governed by only the CDDL or + * only the GPL Version 2, indicate your decision by adding "[Contributor] + * elects to include this software in this distribution under the [CDDL or GPL + * Version 2] license." If you don't indicate a single choice of license, a + * recipient has the option to distribute your version of this file under + * either the CDDL, the GPL Version 2 or to extend the choice of license to + * its licensees as provided above. However, if you add GPL Version 2 code + * and therefore, elected the GPL Version 2 license, then the option applies + * only if the new code is made subject to such option by the copyright + * holder. + */ +package fish.payara.starter.resources; + +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.StreamingOutput; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import jakarta.inject.Inject; + +@Path("starter") +public class ApplicationResource { + + @Inject + private ApplicationGenerator gen; + + @POST + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_OCTET_STREAM) + public Response generate(ApplicationConfiguration appProperties) { + try { + Future future = gen.generate(appProperties); + File zipFile = future.get(); + return buildResponse(zipFile, appProperties.getArtifactId()); + } catch (FileNotFoundException | InterruptedException | ExecutionException e) { + return Response.serverError().entity(e.getMessage()).build(); + } + } + + private Response buildResponse(File zipFile, String artifactId) throws FileNotFoundException { + StreamingOutput streamingOutput = output -> { + try (FileInputStream fis = new FileInputStream(zipFile)) { + byte[] buffer = new byte[1024]; + int bytesRead; + while ((bytesRead = fis.read(buffer)) != -1) { + output.write(buffer, 0, bytesRead); + } + } catch (IOException e) { + throw new RuntimeException("Failed to stream zip file.", e); + } finally { + zipFile.delete(); + zipFile.getParentFile().delete(); + } + }; + + return Response.ok(streamingOutput) + .header("Content-Disposition", "attachment; filename=\"" + artifactId + ".zip\"") + .header("Content-Type", "application/octet-stream") + .build(); + } + +} From c444166720142f6d1c4f4e1869ef01f8eb77c05b Mon Sep 17 00:00:00 2001 From: Gaurav Gupta Date: Fri, 31 May 2024 02:52:32 +0530 Subject: [PATCH 2/2] FISH-8747 Add user notification for request queuing --- starter-ui/src/main/webapp/index.html | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/starter-ui/src/main/webapp/index.html b/starter-ui/src/main/webapp/index.html index efd336a..3fb812e 100644 --- a/starter-ui/src/main/webapp/index.html +++ b/starter-ui/src/main/webapp/index.html @@ -430,10 +430,12 @@

Simply select your options, and click ‘Generate’ - and Payara Pl