diff --git a/.github/workflows/BuildAndRun.yml b/.github/workflows/BuildAndRun.yml index becdedd00..022240df0 100644 --- a/.github/workflows/BuildAndRun.yml +++ b/.github/workflows/BuildAndRun.yml @@ -10,21 +10,21 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v2 - + - name: Set SHORT_HASH run: | - echo "::set-output name=VALUE::${LONG_HASH:0:8}" - echo "RELEASE_TAG=${LONG_HASH:0:8}-$(TZ=UTC-8 date +"%Y.%m.%d")" >> $GITHUB_ENV + echo "::set-output name=VALUE::${LONG_HASH:0:8}" + echo "RELEASE_TAG=${LONG_HASH:0:8}-$(TZ=UTC-8 date +"%Y.%m.%d")" >> $GITHUB_ENV id: short_hash env: - LONG_HASH: ${{ github.sha }} + LONG_HASH: ${{ github.sha }} - name: Set up JDK uses: actions/setup-java@v2 with: java-version: '17' distribution: 'adopt' - + - name: Set up Maven uses: stCarolas/setup-maven@v4.5 with: @@ -33,34 +33,31 @@ jobs: - name: Create output folder run: mkdir BuildOutput +# - name: Build flyfly project +# working-directory: ./FlySpring/flyfly +# run: mvn clean package - - name: Build flyfly project - working-directory: ./FlySpring/flyfly - run: mvn clean package - - - - - name: Copy flyfly JAR to Examples folder and rename - run: cp ./FlySpring/flyfly/target/flyfly-0.0.1-SNAPSHOT.jar ././BuildOutput/flyfly.jar +# - name: Copy flyfly JAR to Examples folder and rename +# run: cp ./FlySpring/flyfly/target/flyfly-0.0.1-SNAPSHOT.jar ././BuildOutput/flyfly.jar - name: Build edgechain-app project working-directory: ./FlySpring/edgechain-app -# run: mvn -Djavacpp.platform=linux-x86_64 clean package -DskipTests + # run: mvn -Djavacpp.platform=linux-x86_64 clean package -DskipTests run: mvn clean package -DskipTests - + - name: Run edgechain testcases working-directory: ./FlySpring/edgechain-app run: mvn test - name: Copy edgechain-app JAR to Examples folder - run: cp ./FlySpring/edgechain-app/target/edgechain-app-1.0.0.jar ./BuildOutput/ + run: cp ./FlySpring/edgechain-app/target/edgechain.jar ./BuildOutput/ - name: Upload Examples folder as artifact uses: actions/upload-artifact@v3 with: name: Output path: ./BuildOutput/ - + release: name: Release jar needs: build_and_run @@ -75,24 +72,24 @@ jobs: - name: Display structure of downloaded files run: ls -R -# - name: Create Release -# id: create_release -# uses: actions/create-release@v1.1.4 -# env: -# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -# with: -# tag_name: ${{ github.ref }} -# release_name: ${{ github.ref }} -# - name: Upload Release jar -# id: upload_release_asset -# uses: actions/upload-release-asset@v1.0.1 -# env: -# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -# with: -# upload_url: ${{ steps.create_release.outputs.upload_url }} -# asset_path: Examples/ -# asset_name: Examples -# asset_content_type: application/zip + # - name: Create Release + # id: create_release + # uses: actions/create-release@v1.1.4 + # env: + # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # with: + # tag_name: ${{ github.ref }} + # release_name: ${{ github.ref }} + # - name: Upload Release jar + # id: upload_release_asset + # uses: actions/upload-release-asset@v1.0.1 + # env: + # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # with: + # upload_url: ${{ steps.create_release.outputs.upload_url }} + # asset_path: Examples/ + # asset_name: Examples + # asset_content_type: application/zip - name: 'Get variables' id: vars run: | @@ -100,10 +97,10 @@ jobs: - name: Release uses: softprops/action-gh-release@v1 with: - tag_name: ${{ env.RELEASE_TAG }} -# body: 🚀 Automated build - files: | - ./Output/**/*.* - - # tag_name: ${{needs.build_and_run.steps.short_hash.outputs.VALUE}} + tag_name: ${{ env.RELEASE_TAG }} + # body: 🚀 Automated build + files: | + ./Output/**/*.* + + # tag_name: ${{needs.build_and_run.steps.short_hash.outputs.VALUE}} diff --git a/FlySpring/edgechain-app/.gitignore b/FlySpring/edgechain-app/.gitignore index 076818c37..138525d2a 100644 --- a/FlySpring/edgechain-app/.gitignore +++ b/FlySpring/edgechain-app/.gitignore @@ -34,3 +34,4 @@ build/ ### VS Code ### .vscode/ +/src/main/resources/jbang.jar diff --git a/FlySpring/edgechain-app/pom.xml b/FlySpring/edgechain-app/pom.xml index 0d9aa5fb7..f790cf5bc 100644 --- a/FlySpring/edgechain-app/pom.xml +++ b/FlySpring/edgechain-app/pom.xml @@ -10,7 +10,6 @@ jar 17 - 2022.0.3 17 17 @@ -36,6 +35,12 @@ spring-boot-starter-webflux + + org.springframework.boot + spring-boot-starter-data-jpa + + + redis.clients jedis @@ -78,18 +83,6 @@ 2.3.1 - - com.zaxxer - HikariCP - 5.0.1 - - - - - org.springframework - spring-jdbc - - com.squareup.retrofit2 @@ -241,6 +234,35 @@ 0.4.2 + + + info.picocli + picocli-spring-boot-starter + 4.7.0 + + + + net.lingala.zip4j + zip4j + 2.11.3 + + + + org.zeroturnaround + zt-exec + 1.12 + + + + org.testcontainers + testcontainers + 1.17.6 + + + + org.testcontainers + postgresql + org.springframework.boot @@ -263,13 +285,6 @@ import - - org.springframework.cloud - spring-cloud-dependencies - ${spring-cloud.version} - pom - import - @@ -314,6 +329,7 @@ shade + edgechain @@ -326,14 +342,56 @@ - - - + + com.edgechain.EdgeChainApplication + + + + + org.apache.maven.plugins + maven-antrun-plugin + 1.8 + + + download-and-unpack-jbang + generate-resources + + run + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/FlySpring/edgechain-app/src/main/java/com/edgechain/EdgeChainApplication.java b/FlySpring/edgechain-app/src/main/java/com/edgechain/EdgeChainApplication.java new file mode 100644 index 000000000..dd29eb5cd --- /dev/null +++ b/FlySpring/edgechain-app/src/main/java/com/edgechain/EdgeChainApplication.java @@ -0,0 +1,55 @@ +package com.edgechain; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.WebApplicationType; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.web.servlet.handler.HandlerMappingIntrospector; + +import java.net.URL; +import java.nio.file.Paths; + +@SpringBootApplication +public class EdgeChainApplication { + + private static final Logger logger = LoggerFactory.getLogger(EdgeChainApplication.class); + + public static void main(String[] args) { + + System.setProperty("jar.name", getJarFileName(EdgeChainApplication.class)); + + logger.info("Executed jar file: "+System.getProperty("jar.name")); + + SpringApplication springApplication = + new SpringApplicationBuilder() + .sources(EdgeChainApplication.class).web(WebApplicationType.NONE) + .build(); + + springApplication.run(args); + } + + @Bean(name = "mvcHandlerMappingIntrospector") + public HandlerMappingIntrospector mvcHandlerMappingIntrospector() { + return new HandlerMappingIntrospector(); + } + + private static String getJarFileName(Class clazz) { + URL classResource = clazz.getResource(clazz.getSimpleName() + ".class"); + if (classResource == null) { + throw new RuntimeException("class resource is null"); + } + String url = classResource.toString(); + if (url.startsWith("jar:file:")) { + String path = url.replaceAll("^jar:(file:.*[.]jar)!/.*", "$1"); + try { + return Paths.get(new URL(path).toURI()).toString(); + } catch (Exception e) { + throw new RuntimeException("Invalid jar file"); + } + } + throw new RuntimeException("Invalid jar file"); + } +} diff --git a/FlySpring/edgechain-app/src/main/java/com/edgechain/lib/context/client/impl/PostgreSQLHistoryContextClient.java b/FlySpring/edgechain-app/src/main/java/com/edgechain/lib/context/client/impl/PostgreSQLHistoryContextClient.java index 32355c59c..08b5e7538 100644 --- a/FlySpring/edgechain-app/src/main/java/com/edgechain/lib/context/client/impl/PostgreSQLHistoryContextClient.java +++ b/FlySpring/edgechain-app/src/main/java/com/edgechain/lib/context/client/impl/PostgreSQLHistoryContextClient.java @@ -9,7 +9,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; import java.util.Objects; @@ -20,7 +22,9 @@ public class PostgreSQLHistoryContextClient private Logger logger = LoggerFactory.getLogger(this.getClass()); - @Autowired private PostgreSQLHistoryContextRepository repository; + @Autowired private PostgreSQLHistoryContextRepository historyContextRepository; + + @Autowired private JdbcTemplate jdbcTemplate; private static final String PREFIX = "historycontext:"; @@ -34,12 +38,10 @@ public EdgeChain create(String id, PostgreSQLHistoryContextEndpo if (Objects.isNull(id) || id.isEmpty()) throw new RuntimeException("Postgres history_context id cannot be empty or null"); - this.repository.createTable(); // Create Table IF NOT EXISTS; + this.createTable(); // Create Table IF NOT EXISTS; HistoryContext context = new HistoryContext(PREFIX + id, "", LocalDateTime.now()); - this.repository.insert(context); - - emitter.onNext(context); + emitter.onNext(historyContextRepository.save(context)); emitter.onComplete(); } catch (final Exception e) { @@ -61,12 +63,12 @@ public EdgeChain put( String input = response.replaceAll("'", ""); historyContext.setResponse(input); - this.repository.update(historyContext); - + HistoryContext returnValue = this.historyContextRepository.save(historyContext); logger.info(String.format("%s is updated", id)); - emitter.onNext(historyContext); + emitter.onNext(returnValue); emitter.onComplete(); + } catch (final Exception e) { emitter.onError(e); } @@ -81,7 +83,7 @@ public EdgeChain get(String id, PostgreSQLHistoryContextEndpoint emitter -> { try { emitter.onNext( - this.repository + this.historyContextRepository .findById(id) .orElseThrow( () -> @@ -101,7 +103,10 @@ public EdgeChain delete(String id, PostgreSQLHistoryContextEndpoint endp Observable.create( emitter -> { try { - this.repository.delete(id); + + HistoryContext historyContext = this.get(id, null).get(); + this.historyContextRepository.delete(historyContext); + emitter.onNext(""); emitter.onComplete(); } catch (final Exception e) { @@ -110,4 +115,11 @@ public EdgeChain delete(String id, PostgreSQLHistoryContextEndpoint endp }), endpoint); } + + @Transactional + public void createTable() { + jdbcTemplate.execute( + "CREATE TABLE IF NOT EXISTS history_context (id TEXT PRIMARY KEY, response TEXT, created_at" + + " timestamp)"); + } } diff --git a/FlySpring/edgechain-app/src/main/java/com/edgechain/lib/context/client/repositories/PostgreSQLHistoryContextRepository.java b/FlySpring/edgechain-app/src/main/java/com/edgechain/lib/context/client/repositories/PostgreSQLHistoryContextRepository.java index b296c03fe..9df6a0ee8 100644 --- a/FlySpring/edgechain-app/src/main/java/com/edgechain/lib/context/client/repositories/PostgreSQLHistoryContextRepository.java +++ b/FlySpring/edgechain-app/src/main/java/com/edgechain/lib/context/client/repositories/PostgreSQLHistoryContextRepository.java @@ -3,6 +3,7 @@ import com.edgechain.lib.context.client.impl.jdbc.HistoryContextRowMapper; import com.edgechain.lib.context.domain.HistoryContext; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; @@ -11,45 +12,7 @@ import java.util.Optional; @Repository -public class PostgreSQLHistoryContextRepository { +public interface PostgreSQLHistoryContextRepository extends JpaRepository { - @Autowired private JdbcTemplate jdbcTemplate; - @Transactional - public void createTable() { - jdbcTemplate.execute( - "CREATE TABLE IF NOT EXISTS history_context (id TEXT PRIMARY KEY, response TEXT, created_at" - + " timestamp)"); - } - - @Transactional - public void insert(HistoryContext context) { - jdbcTemplate.execute( - String.format( - "INSERT INTO history_context (id, response, created_at) values ('%s','%s', '%s')", - context.getId(), context.getResponse(), context.getCreatedAt())); - } - - @Transactional - public void update(HistoryContext context) { - jdbcTemplate.execute( - String.format( - "INSERT INTO history_context (id, response) values ('%s','%s')\n" - + " ON CONFLICT (id) DO UPDATE SET response = EXCLUDED.response;", - context.getId(), context.getResponse())); - } - - @Transactional(readOnly = true) - public Optional findById(String id) { - String sql = String.format("select * from history_context where id='%s'", id); - List contextList = jdbcTemplate.query(sql, new HistoryContextRowMapper()); - - if (contextList.size() > 0) return Optional.ofNullable(contextList.get(0)); - else return Optional.empty(); - } - - @Transactional - public void delete(String id) { - jdbcTemplate.execute(String.format("delete from history_context where id='%s'", id)); - } } diff --git a/FlySpring/edgechain-app/src/main/java/com/edgechain/lib/context/domain/HistoryContext.java b/FlySpring/edgechain-app/src/main/java/com/edgechain/lib/context/domain/HistoryContext.java index 8d5c3886e..86cc1b29c 100644 --- a/FlySpring/edgechain-app/src/main/java/com/edgechain/lib/context/domain/HistoryContext.java +++ b/FlySpring/edgechain-app/src/main/java/com/edgechain/lib/context/domain/HistoryContext.java @@ -1,12 +1,19 @@ package com.edgechain.lib.context.domain; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; + import java.io.Serializable; import java.time.LocalDateTime; +@Entity(name = "HistoryContext") +@Table(name = "history_context") public class HistoryContext implements Serializable { private static final long serialVersionUID = 2819947915596690671L; + @Id private String id; private String response; diff --git a/FlySpring/edgechain-app/src/main/java/com/edgechain/lib/flyfly/CustomApplicationRunner.java b/FlySpring/edgechain-app/src/main/java/com/edgechain/lib/flyfly/CustomApplicationRunner.java new file mode 100644 index 000000000..b03c8f945 --- /dev/null +++ b/FlySpring/edgechain-app/src/main/java/com/edgechain/lib/flyfly/CustomApplicationRunner.java @@ -0,0 +1,25 @@ +package com.edgechain.lib.flyfly; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.*; +import org.springframework.stereotype.Component; +import picocli.CommandLine; +import picocli.CommandLine.IFactory; + +@Component +public class CustomApplicationRunner implements CommandLineRunner, ExitCodeGenerator { + @Autowired private FlyflyCommand runCommand; + @Autowired private IFactory factory; // auto-configured to inject PicocliSpringFactory + + private int exitCode; + + @Override + public void run(String... args) throws Exception { + exitCode = new CommandLine(runCommand, factory).execute(args); + } + + @Override + public int getExitCode() { + return exitCode; + } +} diff --git a/FlySpring/edgechain-app/src/main/java/com/edgechain/lib/flyfly/FlyflyCommand.java b/FlySpring/edgechain-app/src/main/java/com/edgechain/lib/flyfly/FlyflyCommand.java new file mode 100644 index 000000000..313ce25a0 --- /dev/null +++ b/FlySpring/edgechain-app/src/main/java/com/edgechain/lib/flyfly/FlyflyCommand.java @@ -0,0 +1,19 @@ +package com.edgechain.lib.flyfly; + +import com.edgechain.lib.flyfly.commands.format.FormatCommand; +import com.edgechain.lib.flyfly.commands.jbang.JbangCommand; +import com.edgechain.lib.flyfly.commands.run.RunCommand; +import org.springframework.stereotype.Component; +import picocli.CommandLine; +import picocli.CommandLine.Command; + +@Component +@Command( + name = "edgechain", + subcommands = { + RunCommand.class, + FormatCommand.class, + JbangCommand.class, + CommandLine.HelpCommand.class + }) +public class FlyflyCommand {} diff --git a/FlySpring/edgechain-app/src/main/java/com/edgechain/lib/flyfly/commands/format/FormatCommand.java b/FlySpring/edgechain-app/src/main/java/com/edgechain/lib/flyfly/commands/format/FormatCommand.java new file mode 100644 index 000000000..6b21492a3 --- /dev/null +++ b/FlySpring/edgechain-app/src/main/java/com/edgechain/lib/flyfly/commands/format/FormatCommand.java @@ -0,0 +1,29 @@ +package com.edgechain.lib.flyfly.commands.format; + +import com.edgechain.lib.flyfly.utils.ProjectTypeChecker; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import picocli.CommandLine.Command; + +@Component +@Command(name = "format", description = "Format code with Spotless", hidden = true) +public class FormatCommand implements Runnable { + + private final Logger log = LoggerFactory.getLogger(this.getClass()); + + @Autowired Formatter formatter; + @Autowired + ProjectTypeChecker projectTypeChecker; + + @Override + public void run() { + + if (projectTypeChecker.isGradleProject()) formatter.format(); + else { + log.error("Couldn't find build.gradle"); + log.error("Please try again inside the project directory"); + } + } +} diff --git a/FlySpring/edgechain-app/src/main/java/com/edgechain/lib/flyfly/commands/format/Formatter.java b/FlySpring/edgechain-app/src/main/java/com/edgechain/lib/flyfly/commands/format/Formatter.java new file mode 100644 index 000000000..cd2cdff97 --- /dev/null +++ b/FlySpring/edgechain-app/src/main/java/com/edgechain/lib/flyfly/commands/format/Formatter.java @@ -0,0 +1,38 @@ +package com.edgechain.lib.flyfly.commands.format; + +import com.edgechain.lib.flyfly.utils.ProjectSetup; +import org.apache.commons.lang3.SystemUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class Formatter { + @Autowired + ProjectSetup projectSetup; + + private final Logger log = LoggerFactory.getLogger(this.getClass()); + + void format() { + try { + log.info("Checking formatting configuration"); + if (!projectSetup.formatScriptExists()) { + log.info("Configuring Spotless"); + projectSetup.addFormatScript(); + } + log.info("Running Spotless"); + String[] command; + if (SystemUtils.IS_OS_WINDOWS) + command = new String[] {"cmd", "/c", "gradlew.bat -I .flyfly/format.gradle spotlessApply"}; + else + command = new String[] {"bash", "-c", "./gradlew -I .flyfly/format.gradle spotlessApply"}; + + ProcessBuilder pb = new ProcessBuilder(command); + pb.inheritIO(); + pb.start().waitFor(); + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/FlySpring/edgechain-app/src/main/java/com/edgechain/lib/flyfly/commands/jbang/JbangCommand.java b/FlySpring/edgechain-app/src/main/java/com/edgechain/lib/flyfly/commands/jbang/JbangCommand.java new file mode 100644 index 000000000..0e7f66357 --- /dev/null +++ b/FlySpring/edgechain-app/src/main/java/com/edgechain/lib/flyfly/commands/jbang/JbangCommand.java @@ -0,0 +1,146 @@ +package com.edgechain.lib.flyfly.commands.jbang; + +import java.io.*; +import java.util.HashMap; + +import org.springframework.stereotype.Component; +import picocli.CommandLine.Command; +import picocli.CommandLine.Parameters; + +@Component +@Command(name = "jbang", description = "Activate jbang through the jar placed in resources. Ignore if your application is executed.") +public class JbangCommand implements Runnable { + + @Parameters(description = "Java file to be executed with jbang") + private String javaFile; + +// @Parameters(description = "ClassPath Jar to be used") +// private String classPathJar; + + @Override + public void run() { + String resourcePath = "/jbang.jar"; + try { + File jarFile = extractFileFromResources(resourcePath); + if (jarFile != null) { + runJbang(jarFile, javaFile, System.getProperty("jar.name")); + } else { + System.out.println("Could not find jbang.jar in resources."); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + private File extractFileFromResources(String resourcePath) throws IOException { + InputStream inputStream = getClass().getResourceAsStream(resourcePath); + if (inputStream == null) { + return null; + } + File jarFile = File.createTempFile("jbang", ".jar"); + jarFile.deleteOnExit(); + try (FileOutputStream outputStream = new FileOutputStream(jarFile)) { + inputStream.transferTo(outputStream); + } + return jarFile; + } + + private void runJbang(File jarFile, String javaFile, String classPathJar) { + try { + // Step One: Execute the initial command to get the classpath + ProcessBuilder pb = + new ProcessBuilder( + "java", + "-cp", + jarFile.getAbsolutePath(), + "dev.jbang.Main", + "--cp", + classPathJar, + javaFile); + pb.redirectErrorStream(true); + Process process = pb.start(); + BufferedReader bufferedReader = + new BufferedReader(new InputStreamReader(process.getInputStream())); + + String classPath = extractClassPathFromOutput(bufferedReader); + String mainClass; + + String[] filePath; + + String platformName = System.getProperty("os.name"); + if (platformName.contains("Windows")) { + filePath = classPath.split(";"); + } else { + filePath = classPath.split(":"); + } + + File file = new File(filePath[0]); + String filename = file.getName().split("\\.")[0]; + mainClass = String.format("com.edgechain.%s", filename); + + System.out.println("Extracted Filename: " + filename); + System.out.println("Main Class: " + mainClass); + + process.waitFor(); + + // Step Two: Execute the final command with the extracted classpath + if (!classPath.isEmpty() && mainClass != null && !mainClass.isEmpty()) { + runJavaWithClassPath(classPath, mainClass); + } else { + System.out.println("Could not extract classpath or main class from the output."); + } + } catch (IOException | InterruptedException e) { + e.printStackTrace(); + } + } + + private String extractClassPathFromOutput(BufferedReader bufferedReader) throws IOException { + String line; + HashMap sepMap = new HashMap(); + HashMap cpPatternMap = new HashMap(); + HashMap cpEndPatternMap = new HashMap(); + + sepMap.put("Linux", "/"); + sepMap.put("Windows", "\\"); + + cpPatternMap.put("Linux", "-classpath "); + cpPatternMap.put("Windows", "-classpath '"); + + cpEndPatternMap.put("Linux", " "); + cpEndPatternMap.put("Windows", "\'"); + + String classPath = null; + String platformName = System.getProperty("os.name"); + if (platformName.contains("Windows")) { + platformName = "Windows"; + } else { + // Mac and Linux have the same representations. + platformName = "Linux"; + } + final String pattern = cpPatternMap.get(platformName); + while ((line = bufferedReader.readLine()) != null) { + int startIndex = line.indexOf(pattern); + if (startIndex > -1) { + startIndex += pattern.length(); + int endIndex = line.indexOf(cpEndPatternMap.get(platformName).charAt(0), startIndex); + if (endIndex > startIndex) { + classPath = line.substring(startIndex, endIndex); + break; + } + } + } + + System.out.println("Extracted ClassPath = " + classPath); + return classPath; + } + + private void runJavaWithClassPath(String classPath, String mainClass) { + try { + ProcessBuilder pb = new ProcessBuilder("java", "-classpath", classPath, mainClass); + pb.inheritIO(); + pb.start().waitFor(); + } catch (IOException | InterruptedException e) { + e.printStackTrace(); + } + } +} diff --git a/FlySpring/edgechain-app/src/main/java/com/edgechain/lib/flyfly/commands/run/JarRunner.java b/FlySpring/edgechain-app/src/main/java/com/edgechain/lib/flyfly/commands/run/JarRunner.java new file mode 100644 index 000000000..c2e9d04af --- /dev/null +++ b/FlySpring/edgechain-app/src/main/java/com/edgechain/lib/flyfly/commands/run/JarRunner.java @@ -0,0 +1,48 @@ +package com.edgechain.lib.flyfly.commands.run; + +import java.io.File; +import java.io.IOException; + +import com.edgechain.lib.flyfly.utils.FileTools; +import com.edgechain.lib.flyfly.utils.ProjectSetup; +import org.apache.commons.lang3.SystemUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class JarRunner { + + private final Logger log = LoggerFactory.getLogger(this.getClass()); + + @Autowired + ProjectSetup projectSetup; + @Autowired + FileTools fileTools; + + public void run(File jarFile) { + try { + log.info("Checking if glowroot agent exists"); + if (!projectSetup.glowrootAgentExists()) { + log.info("Agent doesn't exist"); + log.info("Adding glowroot agent"); + projectSetup.addGlowrootAgent(); + } + log.info("Runnng the jar"); + String agentPath = projectSetup.getGlowrootAgentPath(); + String jarPath = jarFile.getAbsolutePath(); + String[] command; + if (SystemUtils.IS_OS_WINDOWS) + command = new String[] {"cmd", "/c", "java -javaagent:" + agentPath + " -jar " + jarPath}; + else + command = new String[] {"bash", "-c", "java -javaagent:" + agentPath + " -jar " + jarPath}; + + ProcessBuilder pb = new ProcessBuilder(command); + pb.inheritIO(); + pb.start().waitFor(); + } catch (IOException | InterruptedException e) { + e.printStackTrace(); + } + } +} diff --git a/FlySpring/edgechain-app/src/main/java/com/edgechain/lib/flyfly/commands/run/ProjectRunner.java b/FlySpring/edgechain-app/src/main/java/com/edgechain/lib/flyfly/commands/run/ProjectRunner.java new file mode 100644 index 000000000..e177880a1 --- /dev/null +++ b/FlySpring/edgechain-app/src/main/java/com/edgechain/lib/flyfly/commands/run/ProjectRunner.java @@ -0,0 +1,190 @@ +package com.edgechain.lib.flyfly.commands.run; + +import static java.nio.file.StandardWatchEventKinds.*; + +import com.edgechain.lib.flyfly.utils.ProjectSetup; +import jakarta.annotation.PreDestroy; +import java.io.*; +import java.nio.file.*; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import org.apache.commons.lang3.SystemUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.zeroturnaround.exec.ProcessExecutor; + +@Component +public class ProjectRunner { + + private final Logger log = LoggerFactory.getLogger(this.getClass()); + + + @Autowired TestContainersStarter testContainersStarter; + @Autowired + ProjectSetup projectSetup; + + Process runningProcess; + WatchService filesWatcher; + WatchService buildFileWatcher; + boolean allowInfrastructureServices; + + public void run() { + try { + log.info("Configuring the project"); + log.info("Checking if initscript exists"); + if (!projectSetup.initscriptExists()) { + log.info("Initscript doesn't exist"); + log.info("Adding flyfly.gradle to initscripts"); + projectSetup.addInitscript(); + } + projectSetup.addAutorouteJar(); + allowInfrastructureServices = isDockerInstalled(); + if (allowInfrastructureServices) checkAndConfigureServices(); + log.debug("registering watcher for src files changes"); + registerFilesWatcher(); + log.debug("registering watcher for build file changes"); + registerBuildFileWatcher(); + log.info("Starting the project"); + runTheProject(); + loop(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + boolean isDockerInstalled() throws IOException, InterruptedException { + log.info("Checking if docker is installed to allow infrastructure services"); + int exitCode; + try { + String[] command; + if (SystemUtils.IS_OS_WINDOWS) command = new String[] {"cmd", "/c", "docker", "info"}; + else command = new String[] {"docker", "info"}; + + exitCode = new ProcessExecutor().command(command).start().getProcess().waitFor(); + } catch (Exception e) { + exitCode = -1; + } + if (exitCode != 0) { + log.warn("Couldn't find docker. Disabling infrastructure services."); + return false; + } + return true; + } + + void runTheProject() throws IOException { + String[] command; + if (SystemUtils.IS_OS_WINDOWS) command = new String[] {"cmd", "/c", "gradlew.bat", "bootRun"}; + else command = new String[] {"./gradlew", "bootRun"}; + + runningProcess = + new ProcessExecutor().command(command).redirectOutput(System.out).start().getProcess(); + } + + void checkAndConfigureServices() throws IOException { + log.info("Checking if services are needed"); +// Set supportedDBGroupIds = +// Set.of("mysql", "com.mysql", "org.postgresql", "org.mariadb.jdbc"); + Set supportedDBGroupIds = + Set.of( "org.postgresql"); + BufferedReader reader = new BufferedReader(new FileReader("build.gradle")); + String line; + while ((line = reader.readLine()) != null) { + if (line.contains("dependencies")) { + while ((line = reader.readLine()) != null) { + int start = line.indexOf("\'"); + int end = line.indexOf(":"); + if (start < 0 || end < 0) continue; + String groupID = line.substring(start + 1, end); + if (supportedDBGroupIds.contains(groupID)) { + if (!testContainersStarter.isServiesNeeded()) break; + log.info("Found : " + groupID); + switch (groupID) { +// case "mysql", "com.mysql" -> testContainersStarter.startMySQL(); + case "org.postgresql" -> testContainersStarter.startPostgreSQL(); +// case "org.mariadb.jdbc" -> testContainersStarter.startMariaDB(); + } + break; + } + } + break; + } + } + reader.close(); + } + + void registerBuildFileWatcher() throws IOException { + Path path = Paths.get(""); + buildFileWatcher = FileSystems.getDefault().newWatchService(); + path.register(buildFileWatcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY); + } + + public void loop() throws IOException, InterruptedException { + while (true) { + if (didFilesChange() && runningProcess.isAlive()) { + reloadTheProject(); + } + if (didBuildFileChange()) { + handleBuildFileChange(); + } + } + } + + void registerFilesWatcher() throws IOException { + Path path = Paths.get("src"); + filesWatcher = FileSystems.getDefault().newWatchService(); + Files.walkFileTree( + path, + new SimpleFileVisitor() { + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) + throws IOException { + dir.register(filesWatcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY); + return FileVisitResult.CONTINUE; + } + }); + } + + void reloadTheProject() throws IOException, InterruptedException { + destroyRunningProcess(); + runTheProject(); + } + + boolean didFilesChange() throws InterruptedException { + WatchKey key = filesWatcher.poll(500, TimeUnit.MILLISECONDS); + if (key == null) return false; + for (WatchEvent event : key.pollEvents()) {} + key.reset(); + if (!runningProcess.isAlive()) return false; + return true; + } + + boolean didBuildFileChange() throws InterruptedException { + WatchKey key = buildFileWatcher.poll(500, TimeUnit.MILLISECONDS); + if (key == null) return false; + boolean found = false; + for (WatchEvent event : key.pollEvents()) { + Path p = (Path) event.context(); + if (p.endsWith("build.gradle")) found = true; + } + key.reset(); + if (found) log.info("Detected build file change ..."); + return found; + } + + void handleBuildFileChange() throws IOException, InterruptedException { + destroyRunningProcess(); + if (allowInfrastructureServices) checkAndConfigureServices(); + runTheProject(); + } + + @PreDestroy + void destroyRunningProcess() throws InterruptedException, IOException { + if (runningProcess == null) return; + if (SystemUtils.IS_OS_WINDOWS) { + Runtime.getRuntime().exec("cmd.exe /c taskkill /f /t /pid " + runningProcess.pid()).waitFor(); + } else runningProcess.destroy(); + } +} diff --git a/FlySpring/edgechain-app/src/main/java/com/edgechain/lib/flyfly/commands/run/RunCommand.java b/FlySpring/edgechain-app/src/main/java/com/edgechain/lib/flyfly/commands/run/RunCommand.java new file mode 100644 index 000000000..afde3efff --- /dev/null +++ b/FlySpring/edgechain-app/src/main/java/com/edgechain/lib/flyfly/commands/run/RunCommand.java @@ -0,0 +1,35 @@ +package com.edgechain.lib.flyfly.commands.run; + +import java.io.File; + +import com.edgechain.lib.flyfly.utils.ProjectTypeChecker; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import picocli.CommandLine.Command; +import picocli.CommandLine.Parameters; + +@Component +@Command(name = "run", description = "Run a JAR or Gradle Spring Boot Application. Ignore if your application is executed.") +public class RunCommand implements Runnable { + + private final Logger log = LoggerFactory.getLogger(this.getClass()); + + @Parameters(hidden = true) + File[] files; + + @Autowired ProjectRunner projectRunner; + @Autowired ProjectTypeChecker projectTypeChecker; + @Autowired JarRunner jarRunner; + + @Override + public void run() { + if (files != null && files.length > 0) jarRunner.run(files[0]); + else if (projectTypeChecker.isGradleProject()) projectRunner.run(); + else { + log.error("Couldn't find build.gradle"); + log.error("Please try again inside the project directory"); + } + } +} diff --git a/FlySpring/edgechain-app/src/main/java/com/edgechain/lib/flyfly/commands/run/TestContainersStarter.java b/FlySpring/edgechain-app/src/main/java/com/edgechain/lib/flyfly/commands/run/TestContainersStarter.java new file mode 100644 index 000000000..ad7c0080b --- /dev/null +++ b/FlySpring/edgechain-app/src/main/java/com/edgechain/lib/flyfly/commands/run/TestContainersStarter.java @@ -0,0 +1,155 @@ +package com.edgechain.lib.flyfly.commands.run; + +import jakarta.annotation.PreDestroy; +import java.io.*; +import java.nio.file.FileSystems; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; +import org.testcontainers.containers.PostgreSQLContainer; + + +@Component +public class TestContainersStarter { + + private final Logger log = LoggerFactory.getLogger(this.getClass()); + + private static final String dbName = "test"; + private static final String userName = "test"; + private static final String password = "test"; +// private MySQLContainer mysql; + private PostgreSQLContainer postgre; +// private MariaDBContainer mariaDB; + private String flyflyTempTag = "#flyfly_temp_property"; + +// public void startMySQL() throws IOException { +// if (mysql != null && mysql.isRunning()) return; +// log.info("Starting a temporary MySQL database."); +// mysql = +// new MySQLContainer<>(DockerImageName.parse("mysql:5.7")) +// .withDatabaseName(dbName) +// .withUsername(userName) +// .withPassword(password); +// mysql.addParameter("TC_MY_CNF", null); +// mysql.start(); +// log.info("Database started."); +// log.info("DB URL: " + mysql.getJdbcUrl()); +// log.info("DB Username: " + mysql.getUsername()); +// log.info("DB Password: " + mysql.getPassword()); +// addTempProperties(mysql.getJdbcUrl()); +// } + + public void startPostgreSQL() throws IOException { + if (postgre != null && postgre.isRunning()) return; + log.info("Starting a temporary PostgreSQL database."); + postgre = + new PostgreSQLContainer<>("postgres:14.5") + .withDatabaseName(dbName) + .withUsername(userName) + .withPassword(password); + postgre.addParameter("TC_MY_CNF", null); + postgre.start(); + log.info("Database started."); + log.info("DB URL: " + postgre.getJdbcUrl()); + log.info("DB Username: " + postgre.getUsername()); + log.info("DB Password: " + postgre.getPassword()); + addTempProperties(postgre.getJdbcUrl()); + } + +// public void startMariaDB() throws IOException { +// if (postgre != null && postgre.isRunning()) return; +// log.info("Starting a temporary MariaDB database."); +// mariaDB = +// new MariaDBContainer<>("mariadb:10.3.6") +// .withDatabaseName(dbName) +// .withUsername(userName) +// .withPassword(password); +// mariaDB.addParameter("TC_MY_CNF", null); +// mariaDB.start(); +// log.info("Database started."); +// log.info("DB URL: " + mariaDB.getJdbcUrl()); +// log.info("DB Username: " + mariaDB.getUsername()); +// log.info("DB Password: " + mariaDB.getPassword()); +// addTempProperties(mariaDB.getJdbcUrl()); +// } + + public void addTempProperties(String url) throws IOException { + log.info("Appending temporary DB configuration to application.properties"); + BufferedWriter writer = new BufferedWriter(new FileWriter(getPropertiesPath(), true)); + writer.newLine(); + writer.append(flyflyTempTag); + writer.newLine(); + writer.append("spring.datasource.url=" + url); + writer.newLine(); + writer.append(flyflyTempTag); + writer.newLine(); + writer.append("spring.datasource.username=" + userName); + writer.newLine(); + writer.append(flyflyTempTag); + writer.newLine(); + writer.append("spring.datasource.password=" + password); + writer.flush(); + writer.close(); + } + + public void removeTempProperties() throws IOException { + BufferedReader reader = new BufferedReader(new FileReader(getPropertiesPath())); + StringBuilder sb = new StringBuilder(); + boolean tempNotFound = true; + String line; + while ((line = reader.readLine()) != null) { + if (line.contains(flyflyTempTag)) { + tempNotFound = false; + reader.readLine(); + continue; + } + sb.append(line + "\n"); + } + reader.close(); + if (tempNotFound) return; + + BufferedWriter writer = new BufferedWriter(new FileWriter(getPropertiesPath())); + writer.write(sb.toString()); + writer.flush(); + writer.close(); + } + + public boolean isServiesNeeded() throws IOException { + BufferedReader reader = new BufferedReader(new FileReader(getPropertiesPath())); + String line; + String datafield = "spring.datasource.url"; + while ((line = reader.readLine()) != null) { + if (line.contains(datafield)) { + reader.close(); + return false; + } + } + reader.close(); + return true; + } + + public String getPropertiesPath() { + String separator = FileSystems.getDefault().getSeparator(); + return System.getProperty("user.dir") + + separator + + "src" + + separator + + "main" + + separator + + "resources" + + separator + + "application.properties"; + } + + @PreDestroy + public void destroy() { + try { + removeTempProperties(); + } catch (IOException e) { + } +// if (mysql != null && mysql.isRunning()) mysql.close(); + if (postgre != null && postgre.isRunning()) postgre.close(); +// if (mariaDB != null && mariaDB.isRunning()) mariaDB.close(); + } +} diff --git a/FlySpring/edgechain-app/src/main/java/com/edgechain/lib/flyfly/utils/FileTools.java b/FlySpring/edgechain-app/src/main/java/com/edgechain/lib/flyfly/utils/FileTools.java new file mode 100644 index 000000000..046eeb69b --- /dev/null +++ b/FlySpring/edgechain-app/src/main/java/com/edgechain/lib/flyfly/utils/FileTools.java @@ -0,0 +1,31 @@ +package com.edgechain.lib.flyfly.utils; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import net.lingala.zip4j.ZipFile; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +@Component +public class FileTools { + + private final Logger log = LoggerFactory.getLogger(this.getClass()); + + public void exportFileTo(String file, String dest) throws IOException { + log.debug("Exporting " + file + " To " + dest); + InputStream resource = FileTools.class.getClassLoader().getResourceAsStream(file); + Files.copy(resource, Path.of(dest)); + log.debug("Exported successfully"); + } + + public void unzip(String zipFilePath, String destDir) throws IOException { + log.debug("Unzipping " + zipFilePath + " into " + destDir); + ZipFile zipFile = new ZipFile(zipFilePath); + zipFile.extractAll(destDir); + zipFile.close(); + log.debug("Unzipped successfully"); + } +} diff --git a/FlySpring/edgechain-app/src/main/java/com/edgechain/lib/flyfly/utils/ProjectSetup.java b/FlySpring/edgechain-app/src/main/java/com/edgechain/lib/flyfly/utils/ProjectSetup.java new file mode 100644 index 000000000..d6b86789d --- /dev/null +++ b/FlySpring/edgechain-app/src/main/java/com/edgechain/lib/flyfly/utils/ProjectSetup.java @@ -0,0 +1,74 @@ +package com.edgechain.lib.flyfly.utils; + +import java.io.File; +import java.io.IOException; +import java.nio.file.FileSystems; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class ProjectSetup { + + private final Logger log = LoggerFactory.getLogger(this.getClass()); + + @Autowired FileTools fileTools; + + private String userHome = System.getProperty("user.home"); + private String separator = FileSystems.getDefault().getSeparator(); + private String initscriptDir = + userHome + separator + ".gradle" + separator + "init.d" + separator + "flyfly.gradle"; + private String flyflyDir = System.getProperty("user.dir") + separator + ".flyfly"; + private String formatScriptDir = flyflyDir + separator + "format.gradle"; + private String autorouteDir = flyflyDir + separator + "autoroute.jar"; + private String glowrootDir = flyflyDir + separator + "glowroot"; + + public boolean initscriptExists() { + log.debug("Checking if flyfly.gradle exists in " + initscriptDir); + return new File(initscriptDir).exists(); + } + + public void addInitscript() throws IOException { + File initDir = new File(userHome + separator + ".gradle" + separator + "init.d"); + if (!initDir.exists()) initDir.mkdirs(); + fileTools.exportFileTo("flyfly.gradle", initscriptDir); + } + + public void addAutorouteJar() throws IOException { + File flyflyFolder = new File(flyflyDir); + if (!flyflyFolder.exists()) flyflyFolder.mkdirs(); + File autorouteFile = new File(autorouteDir); + if (!autorouteFile.exists() || (autorouteFile.exists() && autorouteFile.delete())) + fileTools.exportFileTo("autoroute.jar", autorouteDir); + } + + public boolean formatScriptExists() { + log.debug("Checking if format.gradle exists in " + formatScriptDir); + return new File(formatScriptDir).exists(); + } + + public void addFormatScript() throws IOException { + File flyflyFolder = new File(flyflyDir); + if (!flyflyFolder.exists()) flyflyFolder.mkdirs(); + fileTools.exportFileTo("format.gradle", formatScriptDir); + } + + public boolean glowrootAgentExists() { + log.debug("Checking if glowroot folder exists in " + glowrootDir); + return new File(glowrootDir).exists(); + } + + public void addGlowrootAgent() throws IOException { + File flyflyFolder = new File(flyflyDir); + if (!flyflyFolder.exists()) flyflyFolder.mkdirs(); + String zipDir = flyflyDir + separator + "glowroot.zip"; + fileTools.exportFileTo("glowroot.zip", zipDir); + fileTools.unzip(zipDir, flyflyDir); + new File(zipDir).delete(); + } + + public String getGlowrootAgentPath() { + return flyflyDir + separator + "glowroot" + separator + "glowroot.jar"; + } +} diff --git a/FlySpring/edgechain-app/src/main/java/com/edgechain/lib/flyfly/utils/ProjectTypeChecker.java b/FlySpring/edgechain-app/src/main/java/com/edgechain/lib/flyfly/utils/ProjectTypeChecker.java new file mode 100644 index 000000000..1fa9857bf --- /dev/null +++ b/FlySpring/edgechain-app/src/main/java/com/edgechain/lib/flyfly/utils/ProjectTypeChecker.java @@ -0,0 +1,20 @@ +package com.edgechain.lib.flyfly.utils; + +import java.io.File; +import org.springframework.stereotype.Component; + +@Component +public class ProjectTypeChecker { + + public boolean isMavenProject() { + File dir = new File(System.getProperty("user.dir")); + for (File file : dir.listFiles()) if (file.getName().equals("pom.xml")) return true; + return false; + } + + public boolean isGradleProject() { + File dir = new File(System.getProperty("user.dir")); + for (File file : dir.listFiles()) if (file.getName().equals("build.gradle")) return true; + return false; + } +} diff --git a/FlySpring/edgechain-app/src/main/resources/application.properties b/FlySpring/edgechain-app/src/main/resources/application.properties index 81f3f10ac..3efd93515 100644 --- a/FlySpring/edgechain-app/src/main/resources/application.properties +++ b/FlySpring/edgechain-app/src/main/resources/application.properties @@ -1,8 +1,12 @@ spring.main.allow-bean-definition-overriding=true +spring.banner.location=classpath:banner.txt spring.mvc.async.request-timeout=1200000 spring.servlet.multipart.max-file-size=70MB spring.servlet.multipart.max-request-size=100MB spring.jackson.default-property-inclusion=NON_NULL logging.level.org.apache.tika.parser.pdf=error logging.level.org.apache.pdfbox=error -logging.level.org.apache.fontbox.ttf.CmapSubtable=error \ No newline at end of file +logging.level.org.apache.fontbox.ttf.CmapSubtable=error +spring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults=false +spring.jpa.hibernate.ddl-auto=none +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect \ No newline at end of file diff --git a/FlySpring/edgechain-app/src/main/resources/banner.txt b/FlySpring/edgechain-app/src/main/resources/banner.txt new file mode 100644 index 000000000..e3580b9cc --- /dev/null +++ b/FlySpring/edgechain-app/src/main/resources/banner.txt @@ -0,0 +1,6 @@ + ______ __ ________ _ + / ____/___/ /___ ____ / ____/ /_ ____ _(_)___ + / __/ / __ / __ `/ _ \ / / / __ \/ __ `/ / __ \ + / /___/ /_/ / /_/ / __/ / /___/ / / / /_/ / / / / / +/_____/\__,_/\__, /\___/ \____/_/ /_/\__,_/_/_/ /_/ + /____/ diff --git a/FlySpring/edgechain-app/src/main/resources/glowroot.zip b/FlySpring/edgechain-app/src/main/resources/glowroot.zip new file mode 100644 index 000000000..00b2b7fc1 Binary files /dev/null and b/FlySpring/edgechain-app/src/main/resources/glowroot.zip differ diff --git a/FlySpring/edgechain-app/src/test/java/com/edgechain/EdgeChainRunnerTest.java b/FlySpring/edgechain-app/src/test/java/com/edgechain/EdgeChainRunnerTest.java deleted file mode 100644 index fe5aba9a6..000000000 --- a/FlySpring/edgechain-app/src/test/java/com/edgechain/EdgeChainRunnerTest.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.edgechain; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -@SpringBootApplication -public class EdgeChainRunnerTest { - - public static void main(String[] args) { - SpringApplication.run(EdgeChainRunnerTest.class, args); - } -}