├── .github └── workflows │ └── build-and-test.yml ├── .gitignore ├── LICENSE ├── README.md ├── build.gradle ├── docs └── system-requirements.md ├── examples ├── entropy-demo │ ├── .gitignore │ ├── README.md │ ├── build.gradle │ ├── settings.gradle │ └── src │ │ ├── main │ │ └── java │ │ │ └── one │ │ │ └── microproject │ │ │ └── entropy │ │ │ └── EntropyCalculator.java │ │ └── test │ │ └── java │ │ └── one │ │ └── microproject │ │ └── entropy │ │ └── tests │ │ └── EntropyTests.java ├── files-compare-demo │ ├── README.md │ ├── build.gradle │ ├── settings.gradle │ └── src │ │ └── main │ │ └── java │ │ └── one │ │ └── microproject │ │ └── filescompare │ │ ├── CompareDirContext.java │ │ ├── FileUtils.java │ │ └── Main.java ├── jce-demo │ ├── .gitignore │ ├── README.md │ ├── build.gradle │ └── src │ │ ├── main │ │ └── java │ │ │ └── itx │ │ │ └── examples │ │ │ └── jce │ │ │ ├── JCEUtils.java │ │ │ ├── KeyPairHolder.java │ │ │ └── PKIException.java │ │ └── test │ │ ├── java │ │ └── itx │ │ │ └── examples │ │ │ └── jce │ │ │ └── tests │ │ │ └── JCETests.java │ │ └── resources │ │ └── keystore.jks ├── proxy-server │ ├── README.md │ ├── build-native.sh │ ├── build.gradle │ ├── reflectconfig.json │ ├── scripts │ │ ├── proxy-server-start.sh │ │ ├── proxy-server-stop.sh │ │ └── proxy-server.service │ ├── settings.gradle │ ├── src │ │ ├── main │ │ │ ├── java │ │ │ │ └── one │ │ │ │ │ └── microproject │ │ │ │ │ └── proxyserver │ │ │ │ │ ├── Main.java │ │ │ │ │ ├── ProxyServer.java │ │ │ │ │ ├── impl │ │ │ │ │ ├── CloseListener.java │ │ │ │ │ ├── Configuration.java │ │ │ │ │ ├── ConnectionRegistry.java │ │ │ │ │ ├── ProxyConfiguration.java │ │ │ │ │ ├── TCPProxyImpl.java │ │ │ │ │ ├── TcpActiveConnection.java │ │ │ │ │ ├── TcpDataForwarder.java │ │ │ │ │ ├── TcpMain.java │ │ │ │ │ ├── UDPProxyImpl.java │ │ │ │ │ ├── UdpDataHandlerForward.java │ │ │ │ │ └── UdpDataHandlerReverse.java │ │ │ │ │ └── test │ │ │ │ │ ├── Constants.java │ │ │ │ │ ├── TCPClient.java │ │ │ │ │ ├── TCPServer.java │ │ │ │ │ ├── TCPServerMain.java │ │ │ │ │ ├── TCPTestClientMain.java │ │ │ │ │ ├── UDPClient.java │ │ │ │ │ ├── UDPServer.java │ │ │ │ │ ├── UDPServerMain.java │ │ │ │ │ └── UDPTestClientMain.java │ │ │ └── resources │ │ │ │ └── proxy-server-config.json │ │ └── test │ │ │ └── java │ │ │ └── one │ │ │ └── microproject │ │ │ └── proxyserver │ │ │ └── tests │ │ │ └── ProxyServerTests.java │ └── tools │ │ ├── start-proxy-server.sh │ │ ├── start-tcp-test-client.sh │ │ ├── start-tcp-test-server.sh │ │ ├── start-udp-test-client.sh │ │ └── start-udp-test-server.sh ├── timezone-demo │ ├── build.gradle │ ├── settings.gradle │ └── src │ │ ├── main │ │ └── java │ │ │ └── one │ │ │ └── microproject │ │ │ └── timezone │ │ │ └── examples │ │ │ └── TimeZoneUtils.java │ │ └── test │ │ └── java │ │ └── one │ │ └── microproject │ │ └── timezone │ │ └── examples │ │ └── tests │ │ └── TimeZoneUtilTests.java └── web-server-demo │ ├── README.md │ ├── build-native.sh │ ├── build.gradle │ ├── settings.gradle │ └── src │ ├── main │ ├── java │ │ └── one │ │ │ └── microproject │ │ │ └── webserver │ │ │ └── Main.java │ └── resources │ │ └── web-server-config.json │ └── test │ └── java │ └── one │ └── microproject │ └── webserver │ └── tests │ └── WebServerTests.java ├── jep-examples ├── jep-358_helpful-npe │ ├── README.md │ ├── build.gradle │ └── src │ │ └── test │ │ └── java │ │ └── itx │ │ └── examples │ │ └── helpfulnpe │ │ └── tests │ │ ├── HelpfulNPETests.java │ │ └── StringWrapper.java ├── jep-378_text-blocks │ ├── README.md │ ├── build.gradle │ └── src │ │ └── test │ │ └── java │ │ └── itx │ │ └── examples │ │ └── textblocks │ │ └── tests │ │ └── TextBlocksTest.java └── jep-384_records │ ├── README.md │ ├── build.gradle │ └── src │ ├── main │ └── java │ │ └── itx │ │ └── examples │ │ └── records │ │ ├── DataRecord.java │ │ └── RecordId.java │ └── test │ └── java │ └── itx │ └── examples │ └── records │ └── tests │ └── RecordTests.java └── settings.gradle /.github/workflows/build-and-test.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Gradle 2 | name: Build and Test 3 | 4 | on: 5 | push: 6 | branches: [ master ] 7 | pull_request: 8 | branches: [ master ] 9 | 10 | jobs: 11 | build: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Set up JDK 17 18 | uses: actions/setup-java@v1 19 | with: 20 | java-version: 17 21 | - name: Check Java version 22 | run: java -version 23 | - name: Check Gradle version 24 | run: gradle -version 25 | - name: Run Gradle Build and tests 26 | run: gradle clean build test 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.nar 17 | *.ear 18 | *.zip 19 | *.tar.gz 20 | *.rar 21 | 22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 23 | hs_err_pid* 24 | 25 | .idea 26 | build 27 | .gradle 28 | gradle 29 | gradlew 30 | gradlew.bat 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) 2 | [![Java15](https://img.shields.io/badge/java-17-blue)](https://img.shields.io/badge/java-17-blue) 3 | [![Gradle](https://img.shields.io/badge/gradle-v7-blue)](https://img.shields.io/badge/gradle-v7-blue) 4 | ![Build and Test](https://github.com/jveverka/java-17-examples/workflows/Build%20and%20Test/badge.svg) 5 | 6 | # Java 17 examples 7 | 8 | *This project is __WIP__* ! Stay tuned. 9 | 10 | This project aggregates examples of new Java and JVM features between versions 12 and 17 11 | as well as practical java examples tailored for Java 17. 12 | 13 | ### Environment setup 14 | Make sure following software is installed on your PC. 15 | * [OpenJDK 17](https://adoptium.net/releases.html?variant=openjdk17&jvmVariant=hotspot) or later. 16 | * [Gradle 7.3](https://gradle.org/install/) or later 17 | * [docker.io 20.x](https://www.docker.com/) or later 18 | 19 | Please check [system requirements](docs/system-requirements.md) before. 20 | 21 | ### Compile & Test 22 | Most examples are build by top-level gradle project. 23 | ``` 24 | gradle clean build test installDist distZip 25 | ``` 26 | 27 | ### Examples 28 | * Most interesting [JEPs](http://openjdk.java.net/jeps/1) implemented in JDK12 - JDK17 29 | * [__JEP 359, 384: Records__](jep-examples/jep-384_records) 30 | * [JEP 325, 354, 361: Switch Expressions](https://openjdk.java.net/jeps/361) 31 | * [JEP 353: Reimplement the Legacy Socket API](https://openjdk.java.net/jeps/353) 32 | * [__JEP 355, 368, 378: Text Blocks__](jep-examples/jep-378_text-blocks) 33 | * [JEP 305: Pattern Matching for instanceof](https://openjdk.java.net/jeps/305) 34 | * [__JEP 358: Helpful NullPointerExceptions__](jep-examples/jep-358_helpful-npe) 35 | * [JEP 373: Reimplement the Legacy DatagramSocket API](https://openjdk.java.net/jeps/373) 36 | * [JEP 370, 383: Foreign-Memory Access API](https://openjdk.java.net/jeps/383) 37 | * [JEP 339: Edwards-Curve Digital Signature Algorithm (EdDSA)](https://openjdk.java.net/jeps/339) 38 | * [JEP 360: Sealed Classes (Preview)](https://openjdk.java.net/jeps/360) 39 | * [JEP 371: Hidden Classes](https://openjdk.java.net/jeps/371) 40 | 41 | ### JDK12 - JDK17 Features 42 | * JDK12 [2019-03-19] [Feature list](https://openjdk.java.net/projects/jdk/12/) 43 | * JDK13 [2019-09-17] [Feature list](https://openjdk.java.net/projects/jdk/13/) 44 | * JDK14 [2020-03-17] [Feature list](https://openjdk.java.net/projects/jdk/14/) 45 | * JDK15 [2020-09-15] [Feature list](https://openjdk.java.net/projects/jdk/15/) 46 | * JDK16 [2021-03-16] [Feature list](https://openjdk.java.net/projects/jdk/16/) 47 | * JDK17 [2021-09-14] [Feature list](https://openjdk.java.net/projects/jdk/17/) 48 | 49 | ### References 50 | [Java 11 examples](https://github.com/jveverka/java-11-examples) 51 | [A peek into Java 17](https://blogs.oracle.com/javamagazine/java-runtime-encapsulation-internals) 52 | 53 | _Enjoy !_ 54 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | subprojects { 2 | apply plugin: "java" 3 | 4 | sourceCompatibility = JavaVersion.VERSION_17 5 | targetCompatibility = JavaVersion.VERSION_17 6 | 7 | repositories { 8 | mavenCentral() 9 | } 10 | 11 | dependencies { 12 | implementation 'org.slf4j:slf4j-api:2.0.3' 13 | implementation 'org.slf4j:slf4j-simple:2.0.3' 14 | 15 | testImplementation 'org.junit.jupiter:junit-jupiter:5.9.1' 16 | } 17 | 18 | test { 19 | useJUnitPlatform() 20 | testLogging { 21 | events "passed", "skipped", "failed" 22 | } 23 | } 24 | 25 | tasks.withType(JavaCompile) { 26 | options.compilerArgs += "--enable-preview" 27 | } 28 | 29 | tasks.withType(Test) { 30 | jvmArgs += "--enable-preview" 31 | } 32 | 33 | tasks.withType(JavaExec) { 34 | jvmArgs += '--enable-preview' 35 | } 36 | 37 | } 38 | 39 | project(':jep-384_records') {} 40 | project(':jep-358_helpful-npe') {} 41 | project(':jep-378_text-blocks') {} 42 | 43 | project(':proxy-server') {} 44 | project(':entropy-demo') {} 45 | project(':web-server-demo') {} 46 | project(':jce-demo') {} 47 | project(':timezone-demo') {} 48 | project(':files-compare-demo') {} -------------------------------------------------------------------------------- /docs/system-requirements.md: -------------------------------------------------------------------------------- 1 | # System requirements 2 | 3 | * [git-smc](https://git-scm.com/) 2.x or later. 4 | * [OpenJDK 16](https://adoptopenjdk.net/releases.html?variant=openjdk15&jvmVariant=hotspot) or later. 5 | * [Gradle 7.0](https://gradle.org/install/) or later 6 | * [docker.io 20.x](https://www.docker.com/) or later 7 | * [docker-compose](https://linuxconfig.org/how-to-install-docker-compose-on-ubuntu-20-04-focal-fossa-linux) 1.25.0 or later. 8 | 9 | ### Verify System Requirements 10 | ``` 11 | git --version 12 | java --version 13 | gradle --version 14 | docker --version 15 | docker-compose --version 16 | ``` 17 | -------------------------------------------------------------------------------- /examples/entropy-demo/.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | .settings 3 | bin 4 | out 5 | build 6 | target 7 | .classpath 8 | .project 9 | .idea 10 | *.iml 11 | 12 | -------------------------------------------------------------------------------- /examples/entropy-demo/README.md: -------------------------------------------------------------------------------- 1 | # Entropy demo 2 | This simple demo shows how to calculate entropy vale for variable data. 3 | 4 | ``` 5 | E = abs( sum( frequency(char(i)) * log2(frequency(char(i)) ) ) ) 6 | ``` 7 | * __E__ - information entropy 8 | * __abs( )__ - absolute value 9 | * __sum( )__ - sum of values 10 | * __frequency(char(i))__ - frequency of character at position i 11 | 12 | ### Build and test 13 | ``` 14 | gradle clean build test pitest 15 | ``` 16 | 17 | ## References 18 | * [Shannon entropy calculator](https://www.shannonentropy.netmark.pl/) 19 | -------------------------------------------------------------------------------- /examples/entropy-demo/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'info.solidsoft.pitest' version '1.7.0' 4 | } 5 | 6 | repositories { 7 | mavenCentral() 8 | } 9 | 10 | sourceCompatibility = JavaVersion.VERSION_17 11 | targetCompatibility = JavaVersion.VERSION_17 12 | 13 | dependencies { 14 | implementation 'org.slf4j:slf4j-api:2.0.3' 15 | implementation 'org.slf4j:slf4j-simple:2.0.3' 16 | testImplementation 'org.testng:testng:7.6.1' 17 | } 18 | 19 | test { 20 | useTestNG() 21 | //testLogging.showStandardStreams = true 22 | testLogging { 23 | events "passed", "skipped", "failed" 24 | } 25 | } 26 | 27 | pitest { 28 | targetClasses = ['one.microproject.entropy.*'] 29 | junit5PluginVersion = '0.15' 30 | } 31 | -------------------------------------------------------------------------------- /examples/entropy-demo/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'entropy-demo' 2 | -------------------------------------------------------------------------------- /examples/entropy-demo/src/main/java/one/microproject/entropy/EntropyCalculator.java: -------------------------------------------------------------------------------- 1 | package one.microproject.entropy; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | public final class EntropyCalculator { 7 | 8 | private EntropyCalculator() { 9 | } 10 | 11 | public static Double calculateEntropy(Integer data) { 12 | if (data == null) { 13 | return 0D; 14 | } 15 | return calculateEntropy(Integer.toString(data)); 16 | } 17 | 18 | public static Double calculateEntropy(Long data) { 19 | if (data == null) { 20 | return 0D; 21 | } 22 | return calculateEntropy(Long.toString(data)); 23 | } 24 | 25 | public static Double calculateEntropy(String data) { 26 | if (data == null || data.isEmpty()) { 27 | return 0D; 28 | } 29 | Map frequencies = new HashMap<>(); 30 | for(int i=0; i entry: frequencies.entrySet()) { 42 | double frequency = entry.getValue() / dataLength; 43 | double log = frequency * (Math.log10(frequency) / Math.log10(2)); 44 | entropy = entropy + log; 45 | } 46 | return Math.abs(entropy); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /examples/entropy-demo/src/test/java/one/microproject/entropy/tests/EntropyTests.java: -------------------------------------------------------------------------------- 1 | package one.microproject.entropy.tests; 2 | 3 | 4 | import one.microproject.entropy.EntropyCalculator; 5 | import org.testng.Assert; 6 | import org.testng.annotations.DataProvider; 7 | import org.testng.annotations.Test; 8 | 9 | public class EntropyTests { 10 | 11 | @DataProvider(name = "EntropyStringData") 12 | public static Object[][] getEntropyStringData() { 13 | return new Object[][]{ 14 | { null, 0D }, 15 | { "", 0D }, 16 | { "A", 0D }, 17 | { "AA", 0D }, 18 | { "AAA", 0D }, 19 | { "AB", 1D }, 20 | { "ABB", 0.9182958340544894D }, 21 | { "long data text", 3.1820058147602124D }, 22 | { "aeSauyaepee7ohsooTee5noquequeezu", 3.3973683589017414D }, 23 | { "ephai8agh9eiYa5r", 3.327819531114783D }, 24 | { "abcdefghijklmnopqrstuvwyz", 4.643856189774723D } 25 | }; 26 | } 27 | 28 | @Test(dataProvider = "EntropyStringData") 29 | public void testStringEntropyCalculation(String data, Double expectedEntropy) { 30 | Double entropy = EntropyCalculator.calculateEntropy(data); 31 | Assert.assertEquals(entropy, expectedEntropy); 32 | } 33 | 34 | @DataProvider(name = "EntropyIntegerData") 35 | public static Object[][] getEntropyIntegerData() { 36 | return new Object[][]{ 37 | { null, 0D }, 38 | { 0, 0D }, 39 | { 1, 0D }, 40 | { 11, 0D }, 41 | { 12, 1D }, 42 | { 122, 0.9182958340544894D }, 43 | { 2020, 1D }, 44 | { 12345, 2.321928094887362D }, 45 | { 1234567890, 3.321928094887362D } 46 | }; 47 | } 48 | 49 | @Test(dataProvider = "EntropyIntegerData") 50 | public void testIntegerEntropyCalculation(Integer data, Double expectedEntropy) { 51 | Double entropy = EntropyCalculator.calculateEntropy(data); 52 | Assert.assertEquals(entropy, expectedEntropy); 53 | } 54 | 55 | @DataProvider(name = "EntropyLongData") 56 | public static Object[][] getEntropyLongData() { 57 | return new Object[][]{ 58 | { null, 0D }, 59 | { 0L, 0D }, 60 | { 1L, 0D }, 61 | { 11L, 0D }, 62 | { 12L, 1D }, 63 | { 122L, 0.9182958340544894D }, 64 | { 2020L, 1D }, 65 | { 12345L, 2.321928094887362D }, 66 | { 1234567890L, 3.321928094887362D }, 67 | { 12345678901L, 3.2776134368191157D }, 68 | { 123456789012L, 3.2516291673878226D } 69 | }; 70 | } 71 | 72 | @Test(dataProvider = "EntropyLongData") 73 | public void testLongEntropyCalculation(Long data, Double expectedEntropy) { 74 | Double entropy = EntropyCalculator.calculateEntropy(data); 75 | Assert.assertEquals(entropy, expectedEntropy); 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /examples/files-compare-demo/README.md: -------------------------------------------------------------------------------- 1 | # Compare Files 2 | Recursively compare two directories for file and data content. 3 | 4 | ### Requirements 5 | * Java 17 6 | * Gradle 7.3 or later 7 | 8 | ### Build 9 | ``` 10 | gradle clean build test installDist distZip 11 | ``` 12 | 13 | ### Run 14 | ```shell 15 | ./build/install/files-compare-demo/bin/files-compare-demo 16 | ``` -------------------------------------------------------------------------------- /examples/files-compare-demo/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | plugins { 3 | id 'java' 4 | id 'maven-publish' 5 | id 'application' 6 | } 7 | 8 | sourceCompatibility = JavaVersion.VERSION_17 9 | targetCompatibility = JavaVersion.VERSION_17 10 | 11 | repositories { 12 | mavenCentral() 13 | mavenLocal() 14 | } 15 | 16 | dependencies { 17 | implementation 'org.slf4j:slf4j-api:2.0.3' 18 | implementation 'org.slf4j:slf4j-simple:2.0.3' 19 | testImplementation 'org.junit.jupiter:junit-jupiter:5.9.1' 20 | } 21 | 22 | application { 23 | mainClass = 'one.microproject.filescompare.Main' 24 | } 25 | 26 | test { 27 | useJUnitPlatform() 28 | testLogging { 29 | events "passed", "skipped", "failed" 30 | } 31 | } 32 | 33 | -------------------------------------------------------------------------------- /examples/files-compare-demo/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'files-compare-demo' -------------------------------------------------------------------------------- /examples/files-compare-demo/src/main/java/one/microproject/filescompare/CompareDirContext.java: -------------------------------------------------------------------------------- 1 | package one.microproject.filescompare; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.io.File; 7 | import java.nio.file.Path; 8 | import java.util.ArrayList; 9 | import java.util.HashMap; 10 | import java.util.LinkedList; 11 | import java.util.List; 12 | import java.util.Map; 13 | import java.util.Queue; 14 | 15 | public class CompareDirContext { 16 | 17 | private static final Logger LOGGER = LoggerFactory.getLogger(CompareDirContext.class); 18 | 19 | private long fileCounter; 20 | private long directoryCounter; 21 | private long otherCounter; 22 | private long dataBytes; 23 | private Map extensions; 24 | private Queue paths; 25 | private long startTimeStamp; 26 | private long durationMs; 27 | 28 | private List errors; 29 | 30 | public CompareDirContext() { 31 | this.fileCounter = 0; 32 | this.directoryCounter = 0; 33 | this.dataBytes = 0; 34 | this.otherCounter = 0; 35 | this.extensions = new HashMap<>(); 36 | this.paths = new LinkedList<>(); 37 | this.errors = new ArrayList<>(); 38 | this.startTimeStamp = System.currentTimeMillis(); 39 | this.durationMs = 0; 40 | } 41 | 42 | public void addPaths(File[] files) { 43 | LOGGER.info("ADD: {} files", files.length); 44 | for (int i=0; i getExtensions() { 93 | return extensions; 94 | } 95 | 96 | public void logError(String error) { 97 | errors.add(error); 98 | } 99 | 100 | public List getErrors() { 101 | return errors; 102 | } 103 | 104 | public boolean hasErrors() { 105 | return !errors.isEmpty(); 106 | } 107 | 108 | public void close() { 109 | this.durationMs = System.currentTimeMillis() - this.startTimeStamp; 110 | } 111 | 112 | public float getDurationSec() { 113 | return this.durationMs / 1000f; 114 | } 115 | 116 | } 117 | -------------------------------------------------------------------------------- /examples/files-compare-demo/src/main/java/one/microproject/filescompare/FileUtils.java: -------------------------------------------------------------------------------- 1 | package one.microproject.filescompare; 2 | 3 | import java.io.FileInputStream; 4 | import java.io.IOException; 5 | import java.nio.file.Path; 6 | import java.security.MessageDigest; 7 | import java.security.NoSuchAlgorithmException; 8 | 9 | public final class FileUtils { 10 | 11 | private FileUtils() { 12 | } 13 | 14 | public static String getFileChecksum(Path file) throws NoSuchAlgorithmException, IOException { 15 | MessageDigest digest = MessageDigest.getInstance("SHA-256"); 16 | FileInputStream fis = new FileInputStream(file.toFile()); 17 | 18 | byte[] byteArray = new byte[102400]; 19 | int bytesCount = 0; 20 | while ((bytesCount = fis.read(byteArray)) != -1) { 21 | digest.update(byteArray, 0, bytesCount); 22 | }; 23 | fis.close(); 24 | 25 | byte[] bytes = digest.digest(); 26 | StringBuilder sb = new StringBuilder(); 27 | 28 | for (int i = 0; i < bytes.length; i++) { 29 | sb.append(Integer 30 | .toString((bytes[i] & 0xff) + 0x100, 16) 31 | .substring(1)); 32 | } 33 | 34 | return sb.toString(); 35 | } 36 | 37 | public static Path getComparePath(String baseSrcPath, Path dstPath, Path currentPath) { 38 | String delta = currentPath.toString().substring(baseSrcPath.length()); 39 | return Path.of(dstPath.toString(), delta).normalize(); 40 | } 41 | 42 | public static String getHumanReadableSize(Float size) { 43 | if (size <= 1024f) { 44 | return size + " B"; 45 | } else { 46 | int counter = 0; 47 | float result = size; 48 | while (result > 1024f) { 49 | counter = counter + 1; 50 | result = result / 1024; 51 | } 52 | String unit = "??"; 53 | if (counter == 1) { 54 | unit = "KB"; 55 | } 56 | if (counter == 2) { 57 | unit = "MB"; 58 | } 59 | if (counter == 3) { 60 | unit = "GB"; 61 | } 62 | if (counter == 4) { 63 | unit = "TB"; 64 | } 65 | if (counter == 5) { 66 | unit = "PB"; 67 | } 68 | String formattedResult = String.format("%.03f", result); 69 | return formattedResult + " " + unit; 70 | } 71 | 72 | } 73 | 74 | public static String getDuration(float durationSec) { 75 | if (durationSec < 60) { 76 | String formattedResult = String.format("%.03f", durationSec); 77 | return formattedResult + " s"; 78 | } else if (durationSec < 3600) { 79 | int minutes = (int)(durationSec / 60); 80 | float reminder = durationSec - (minutes * 60); 81 | String formattedReminder = String.format("%.03f", reminder); 82 | return minutes + ":" + formattedReminder; 83 | } else if (durationSec < 86400) { 84 | int hours = (int)(durationSec / 3600); 85 | int minutes = (int)(durationSec - (hours * 3600)) / 60; 86 | float reminder = durationSec - (hours * 3600) - (minutes * 60); 87 | String formattedReminder = String.format("%.03f", reminder); 88 | return hours + ":" + minutes + ":" + formattedReminder; 89 | } else { 90 | int days = (int)(durationSec / 86400); 91 | int hours = (int)(durationSec - (days * 86400)) / 3600; 92 | int minutes = (int)(durationSec - (days * 86400) - (hours * 3600)) / 60; 93 | float reminder = durationSec - (days * 86400) - (hours * 3600) - (minutes * 60); 94 | String formattedReminder = String.format("%.03f", reminder); 95 | return days + " days " + hours + ":" + minutes + ":" + formattedReminder; 96 | } 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /examples/files-compare-demo/src/main/java/one/microproject/filescompare/Main.java: -------------------------------------------------------------------------------- 1 | package one.microproject.filescompare; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.io.File; 7 | import java.io.IOException; 8 | import java.nio.file.Path; 9 | import java.security.NoSuchAlgorithmException; 10 | 11 | public class Main { 12 | 13 | private static final Logger LOGGER = LoggerFactory.getLogger(Main.class); 14 | 15 | public static void main(String[] args) throws NoSuchAlgorithmException, IOException { 16 | LOGGER.info("Starting file compare"); 17 | Path srcPath = Path.of(args[0]).normalize(); 18 | Path dstPath = Path.of(args[1]).normalize(); 19 | if (srcPath.equals(dstPath)) { 20 | LOGGER.error("ERROR: source and destination directory must be different directories !"); 21 | return; 22 | } 23 | LOGGER.info("SRC: {}", srcPath); 24 | LOGGER.info("DST: {}", dstPath); 25 | LOGGER.info("Compare directories: {} {}", srcPath.toString(), dstPath.toString()); 26 | 27 | File src = srcPath.toFile(); 28 | File dst = dstPath.toFile(); 29 | 30 | if (!src.isDirectory()) { 31 | LOGGER.error("ERROR: source must be directory !"); 32 | return; 33 | } 34 | 35 | if (!dst.isDirectory()) { 36 | LOGGER.error("ERROR: destination must be directory !"); 37 | return; 38 | } 39 | 40 | CompareDirContext context = new CompareDirContext(); 41 | context.addPaths(src.listFiles()); 42 | String baseSrcPath = srcPath.toString(); 43 | while (context.hasPaths()) { 44 | Path currentPath = context.getNext(); 45 | Path comparePath = FileUtils.getComparePath(baseSrcPath, dstPath, currentPath); 46 | File currentFile = currentPath.toFile(); 47 | File compareFile = comparePath.toFile(); 48 | LOGGER.info("PATH: {} {}", currentPath, comparePath); 49 | if (currentFile.isDirectory()) { 50 | context.addDirectory(); 51 | context.addPaths(currentFile.listFiles()); 52 | if (!compareFile.isDirectory()) { 53 | context.logError("DST: Directory NOT found: " + comparePath); 54 | } 55 | } else if (currentFile.isFile()) { 56 | String path = currentFile.getPath().toString(); 57 | String extension = path.substring(path.lastIndexOf(".") + 1); 58 | context.addFile(currentFile.length(), extension); 59 | if (!compareFile.isFile()) { 60 | context.logError("DST: File NOT found: " + comparePath); 61 | } else { 62 | String currentChecksum = FileUtils.getFileChecksum(currentPath); 63 | String compareChecksum = FileUtils.getFileChecksum(comparePath); 64 | if (!currentChecksum.equals(compareChecksum)) { 65 | context.logError("Files differ: " + currentPath + " " + comparePath); 66 | } 67 | Long currentFileSize = currentFile.length(); 68 | Long compareFileSize = compareFile.length(); 69 | if (!currentFileSize.equals(compareFileSize)) { 70 | context.logError("File sizes differ: " + currentPath + " " + comparePath); 71 | } 72 | } 73 | } else { 74 | context.addOther(); 75 | if (!compareFile.exists()) { 76 | context.logError("DST: NOT found: " + comparePath); 77 | } 78 | } 79 | } 80 | context.close(); 81 | Float duration = context.getDurationSec(); 82 | Float bytesPerSec = context.getDataBytes() / duration; 83 | 84 | LOGGER.info("#**************************************************"); 85 | LOGGER.info("File extensions: {}", context.getExtensions().size()); 86 | long cumulativeCounter = 0; 87 | for (String key: context.getExtensions().keySet()) { 88 | if (key.length() <= 4) { 89 | LOGGER.info(" {}: {}", key, context.getExtensions().get(key)); 90 | } else { 91 | cumulativeCounter = cumulativeCounter + context.getExtensions().get(key); 92 | } 93 | } 94 | LOGGER.info(" others: {}", cumulativeCounter); 95 | 96 | LOGGER.info("#**************************************************"); 97 | LOGGER.info("# SRC: {}", srcPath); 98 | LOGGER.info("# DST: {}", dstPath); 99 | LOGGER.info("# Dirs scanned: {}", context.getDirectoryCounter()); 100 | LOGGER.info("# Files scanned: {}", context.getFileCounter()); 101 | LOGGER.info("# Others scanned: {}", context.getOtherCounter()); 102 | LOGGER.info("# Bytes scanned: {} Bytes", context.getDataBytes()); 103 | LOGGER.info("# Bytes scanned: {}", FileUtils.getHumanReadableSize(context.getDataBytes() / 1f)); 104 | LOGGER.info("# Scan speed : {}/s", FileUtils.getHumanReadableSize(bytesPerSec)); 105 | LOGGER.info("# Duration : {} s", duration); 106 | LOGGER.info("# Duration : {}", FileUtils.getDuration(duration)); 107 | LOGGER.info("#**************************************************"); 108 | 109 | if (context.hasErrors()) { 110 | LOGGER.error("ERRORS found: {}", context.getErrors().size()); 111 | for (String error : context.getErrors()) { 112 | LOGGER.error("ERROR: {}", error); 113 | } 114 | } else { 115 | LOGGER.info("ALL OK: DST directory contains all files in SRC directory !"); 116 | } 117 | 118 | } 119 | 120 | } 121 | -------------------------------------------------------------------------------- /examples/jce-demo/.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | .settings 3 | bin 4 | out 5 | build 6 | build_gradle 7 | target 8 | .classpath 9 | .project 10 | .idea 11 | *.iml 12 | 13 | -------------------------------------------------------------------------------- /examples/jce-demo/README.md: -------------------------------------------------------------------------------- 1 | # Java Cryptography Extension -demo 2 | Java Cryptography Extension ([JCE](https://en.wikipedia.org/wiki/Java_Cryptography_Extension)) - demo covering use cases like: 3 | 4 | * Keypair generation. 5 | * Self-signed X509 certificate generation (CA certificate) and verification. 6 | * CA signed X509 certificate generation and verification. 7 | * Digital signature with verification. 8 | * Data encryption and decryption. 9 | * Loading of private key and X509 certificate from Java Key Store (JKS). 10 | * Serialization and deserialization of X509 certificates. 11 | * Serialization and deserialization of private keys. 12 | 13 | This demo uses [Bouncy Castle](https://www.bouncycastle.org/java.html) JCE provider. 14 | -------------------------------------------------------------------------------- /examples/jce-demo/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | plugins { 3 | id 'java' 4 | id 'maven-publish' 5 | } 6 | 7 | sourceCompatibility = JavaVersion.VERSION_17 8 | targetCompatibility = JavaVersion.VERSION_17 9 | 10 | repositories { 11 | mavenCentral() 12 | mavenLocal() 13 | } 14 | 15 | dependencies { 16 | implementation 'org.slf4j:slf4j-api:2.0.3' 17 | implementation 'org.slf4j:slf4j-simple:2.0.3' 18 | implementation 'org.bouncycastle:bcpg-jdk15on:1.70' 19 | implementation 'org.bouncycastle:bcpkix-jdk15on:1.70' 20 | testImplementation 'org.junit.jupiter:junit-jupiter:5.9.1' 21 | } 22 | 23 | test { 24 | useJUnitPlatform() 25 | testLogging { 26 | events "passed", "skipped", "failed" 27 | } 28 | } 29 | 30 | -------------------------------------------------------------------------------- /examples/jce-demo/src/main/java/itx/examples/jce/JCEUtils.java: -------------------------------------------------------------------------------- 1 | package itx.examples.jce; 2 | 3 | import org.bouncycastle.asn1.x500.X500Name; 4 | import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; 5 | import org.bouncycastle.cert.X509v3CertificateBuilder; 6 | import org.bouncycastle.operator.ContentSigner; 7 | import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; 8 | 9 | import javax.crypto.Cipher; 10 | import java.io.ByteArrayInputStream; 11 | import java.io.ByteArrayOutputStream; 12 | import java.io.InputStream; 13 | import java.math.BigInteger; 14 | import java.security.Key; 15 | import java.security.KeyFactory; 16 | import java.security.KeyPair; 17 | import java.security.KeyPairGenerator; 18 | import java.security.KeyStore; 19 | import java.security.NoSuchAlgorithmException; 20 | import java.security.NoSuchProviderException; 21 | import java.security.PrivateKey; 22 | import java.security.PublicKey; 23 | import java.security.SecureRandom; 24 | import java.security.Signature; 25 | import java.security.cert.Certificate; 26 | import java.security.cert.CertificateException; 27 | import java.security.cert.CertificateFactory; 28 | import java.security.cert.X509Certificate; 29 | import java.security.spec.InvalidKeySpecException; 30 | import java.security.spec.PKCS8EncodedKeySpec; 31 | import java.util.Date; 32 | import java.util.concurrent.TimeUnit; 33 | 34 | public final class JCEUtils { 35 | 36 | private JCEUtils() { 37 | throw new UnsupportedOperationException(); 38 | } 39 | 40 | private static final String BC_PROVIDER = "BC"; 41 | private static final String SHA256_RSA = "SHA256withRSA"; 42 | private static final String CN_NAME = "CN="; 43 | private static final String X509 = "X.509"; 44 | private static final String TRANSFORMATION = "RSA/None/OAEPWITHSHA-256ANDMGF1PADDING"; 45 | private static final String KEYSTORE_TYPE = "JKS"; 46 | private static final String ALGORITHM = "RSA"; 47 | private static final String RANDOM_ALGORITHM = "NativePRNG"; 48 | 49 | public static KeyPair generateKeyPair() throws PKIException { 50 | try { 51 | KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(ALGORITHM, BC_PROVIDER); 52 | SecureRandom secureRandom = SecureRandom.getInstance(RANDOM_ALGORITHM); 53 | keyPairGenerator.initialize(2048, secureRandom); 54 | return keyPairGenerator.generateKeyPair(); 55 | } catch (Exception e) { 56 | throw new PKIException(e); 57 | } 58 | } 59 | 60 | public static KeyPairHolder generateSelfSignedKeyPairHolder(String issuerAndSubject, Date notBefore, Long duration, TimeUnit timeUnit) throws PKIException { 61 | KeyPair keyPair = generateKeyPair(); 62 | X509Certificate x509Certificate = createSelfSignedCertificate(issuerAndSubject, notBefore, duration, timeUnit, keyPair); 63 | return new KeyPairHolder(keyPair.getPrivate(), x509Certificate); 64 | } 65 | 66 | public static X509Certificate createSignedCertificate(String issuerName, String subjectName, Date notBefore, Long duration, TimeUnit timeUnit, PublicKey publicKey, PrivateKey privateKey) throws PKIException { 67 | try { 68 | X500Name issuer = new X500Name(CN_NAME + issuerName); 69 | BigInteger serial = BigInteger.valueOf(System.currentTimeMillis()); 70 | Date notAfter = new Date(notBefore.getTime() + timeUnit.toMillis(duration)); 71 | X500Name subject = new X500Name(CN_NAME + subjectName); 72 | SubjectPublicKeyInfo publicKeyInfo = SubjectPublicKeyInfo.getInstance(publicKey.getEncoded()); 73 | X509v3CertificateBuilder certBuilder = new X509v3CertificateBuilder(issuer, serial, notBefore, notAfter, subject, publicKeyInfo); 74 | JcaContentSignerBuilder jcaContentSignerBuilder = new JcaContentSignerBuilder(SHA256_RSA); 75 | ContentSigner signer = jcaContentSignerBuilder.build(privateKey); 76 | CertificateFactory certificateFactory = CertificateFactory.getInstance(X509, BC_PROVIDER); 77 | byte[] certBytes = certBuilder.build(signer).getEncoded(); 78 | return (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(certBytes)); 79 | } catch (Exception e) { 80 | throw new PKIException(e); 81 | } 82 | } 83 | 84 | public static X509Certificate createSelfSignedCertificate(String issuerAndSubject, Date notBefore, Long duration, TimeUnit timeUnit, KeyPair keyPair) throws PKIException { 85 | return createSignedCertificate(issuerAndSubject, issuerAndSubject, notBefore, duration, timeUnit, keyPair.getPublic(), keyPair.getPrivate()); 86 | } 87 | 88 | public static boolean verifySelfSignedCertificate(X509Certificate certificate) { 89 | return verifySignedCertificate(certificate, certificate); 90 | } 91 | 92 | public static boolean verifySignedCertificate(X509Certificate issuerCertificate, X509Certificate signedCertificate) { 93 | try { 94 | issuerCertificate.checkValidity(); 95 | signedCertificate.checkValidity(); 96 | signedCertificate.verify(issuerCertificate.getPublicKey()); 97 | return true; 98 | } catch (Exception e) { 99 | return false; 100 | } 101 | } 102 | 103 | public static byte[] createDigitalSignature(byte[] data, PrivateKey privateKey) throws PKIException { 104 | try { 105 | Signature signature = Signature.getInstance(SHA256_RSA, BC_PROVIDER); //"SHA256withECDSA" 106 | signature.initSign(privateKey); 107 | signature.update(data); 108 | return signature.sign(); 109 | } catch (Exception e) { 110 | throw new PKIException(e); 111 | } 112 | } 113 | 114 | public static boolean verifyDigitalSignature(byte[] data, byte[] signatureData, X509Certificate certificate) throws PKIException { 115 | try { 116 | Signature signature = Signature.getInstance(SHA256_RSA, BC_PROVIDER); //"SHA256withECDSA" 117 | signature.initVerify(certificate); 118 | signature.update(data); 119 | return signature.verify(signatureData); 120 | } catch (Exception e) { 121 | throw new PKIException(e); 122 | } 123 | } 124 | 125 | public static byte[] encrypt(byte[] data, Key key) throws PKIException { 126 | try { 127 | Cipher cipher = Cipher.getInstance(TRANSFORMATION, BC_PROVIDER); 128 | cipher.init(Cipher.ENCRYPT_MODE, key); 129 | return cipher.doFinal(data); 130 | } catch (Exception e) { 131 | throw new PKIException(e); 132 | } 133 | } 134 | 135 | public static byte[] decrypt(byte[] data, Key key) throws PKIException { 136 | try { 137 | Cipher cipher = Cipher.getInstance(TRANSFORMATION, BC_PROVIDER); 138 | cipher.init(Cipher.DECRYPT_MODE, key); 139 | return cipher.doFinal(data); 140 | } catch (Exception e) { 141 | throw new PKIException(e); 142 | } 143 | } 144 | 145 | public static KeyPairHolder loadPrivateKeyAndCertificateFromJKS(String keystorePath, String alias, String keystorePassword, String privateKeyPassword) throws PKIException { 146 | try { 147 | KeyStore keystore = KeyStore.getInstance(KEYSTORE_TYPE); 148 | InputStream is = JCEUtils.class.getResourceAsStream(keystorePath); 149 | keystore.load(is, keystorePassword.toCharArray()); 150 | X509Certificate certificate = (X509Certificate) keystore.getCertificate(alias); 151 | PrivateKey privateKey = (PrivateKey) keystore.getKey(alias, privateKeyPassword.toCharArray()); 152 | return new KeyPairHolder(privateKey, certificate); 153 | } catch (Exception e) { 154 | throw new PKIException(e); 155 | } 156 | } 157 | 158 | public static byte[] serializeX509Certificate(X509Certificate certificate) throws PKIException { 159 | try { 160 | return certificate.getEncoded(); 161 | } catch(Exception e) { 162 | throw new PKIException(e); 163 | } 164 | } 165 | 166 | public static X509Certificate deserializeX509Certificate(byte[] data) throws PKIException { 167 | try { 168 | CertificateFactory certificateFactory = CertificateFactory.getInstance(X509, BC_PROVIDER); 169 | return (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(data)); 170 | } catch(Exception e) { 171 | throw new PKIException(e); 172 | } 173 | } 174 | 175 | public static byte[] serializePrivateKey(PrivateKey privateKey) { 176 | return privateKey.getEncoded(); 177 | } 178 | 179 | public static PrivateKey deserializePrivateKey(byte[] data) throws PKIException { 180 | try { 181 | KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM, BC_PROVIDER); 182 | PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(data); 183 | return keyFactory.generatePrivate(pkcs8EncodedKeySpec); 184 | } catch(Exception e) { 185 | throw new PKIException(e); 186 | } 187 | } 188 | 189 | public static byte[] createJKSWithPrivateKeyAndCertificate(String alias, String keystorePassword, String privateKeyPassword, KeyPairHolder keyPairHolder) throws PKIException { 190 | try { 191 | ByteArrayOutputStream bas = new ByteArrayOutputStream(); 192 | Certificate[] certificates = new Certificate[] { keyPairHolder.getCertificate() }; 193 | KeyStore keystore = KeyStore.getInstance(KEYSTORE_TYPE); 194 | keystore.load(null, keystorePassword.toCharArray()); 195 | keystore.setKeyEntry(alias, keyPairHolder.getPrivateKey(), privateKeyPassword.toCharArray(), certificates); 196 | keystore.store(bas, keystorePassword.toCharArray()); 197 | bas.flush(); 198 | return bas.toByteArray(); 199 | } catch(Exception e) { 200 | throw new PKIException(e); 201 | } 202 | } 203 | 204 | public static KeyPairHolder loadKeypairFromJKS(String alias, String keystorePassword, String privateKeyPassword, byte[] jksData) throws PKIException { 205 | try { 206 | KeyStore keystore = KeyStore.getInstance(KEYSTORE_TYPE); 207 | ByteArrayInputStream bis = new ByteArrayInputStream(jksData); 208 | keystore.load(bis, keystorePassword.toCharArray()); 209 | X509Certificate certificate = (X509Certificate) keystore.getCertificate(alias); 210 | PrivateKey privateKey = (PrivateKey) keystore.getKey(alias, privateKeyPassword.toCharArray()); 211 | return new KeyPairHolder(privateKey, certificate); 212 | } catch(Exception e) { 213 | throw new PKIException(e); 214 | } 215 | } 216 | 217 | } 218 | -------------------------------------------------------------------------------- /examples/jce-demo/src/main/java/itx/examples/jce/KeyPairHolder.java: -------------------------------------------------------------------------------- 1 | package itx.examples.jce; 2 | 3 | import java.security.KeyPair; 4 | import java.security.PrivateKey; 5 | import java.security.PublicKey; 6 | import java.security.cert.X509Certificate; 7 | import java.util.Objects; 8 | 9 | public class KeyPairHolder { 10 | 11 | private final PrivateKey privateKey; 12 | private final X509Certificate certificate; 13 | 14 | public KeyPairHolder(PrivateKey privateKey, X509Certificate certificate) { 15 | this.privateKey = privateKey; 16 | this.certificate = certificate; 17 | } 18 | 19 | public KeyPair getKeyPair() { 20 | return new KeyPair(certificate.getPublicKey(), privateKey); 21 | } 22 | 23 | public X509Certificate getCertificate() { 24 | return certificate; 25 | } 26 | 27 | public PrivateKey getPrivateKey() { 28 | return privateKey; 29 | } 30 | 31 | public PublicKey getPublicKey() { 32 | return certificate.getPublicKey(); 33 | } 34 | 35 | @Override 36 | public boolean equals(Object o) { 37 | if (this == o) return true; 38 | if (o == null || getClass() != o.getClass()) return false; 39 | KeyPairHolder that = (KeyPairHolder) o; 40 | return Objects.equals(privateKey, that.privateKey) && 41 | Objects.equals(certificate, that.certificate); 42 | } 43 | 44 | @Override 45 | public int hashCode() { 46 | return Objects.hash(privateKey, certificate); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /examples/jce-demo/src/main/java/itx/examples/jce/PKIException.java: -------------------------------------------------------------------------------- 1 | package itx.examples.jce; 2 | 3 | public class PKIException extends Exception { 4 | 5 | public PKIException(Exception e) { 6 | super(e); 7 | } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /examples/jce-demo/src/test/java/itx/examples/jce/tests/JCETests.java: -------------------------------------------------------------------------------- 1 | package itx.examples.jce.tests; 2 | 3 | import itx.examples.jce.JCEUtils; 4 | import itx.examples.jce.KeyPairHolder; 5 | import itx.examples.jce.PKIException; 6 | import org.bouncycastle.jce.provider.BouncyCastleProvider; 7 | import org.junit.jupiter.api.BeforeAll; 8 | import org.junit.jupiter.api.MethodOrderer; 9 | import org.junit.jupiter.api.Order; 10 | import org.junit.jupiter.api.Test; 11 | import org.junit.jupiter.api.TestMethodOrder; 12 | 13 | import java.nio.charset.StandardCharsets; 14 | import java.security.KeyPair; 15 | import java.security.NoSuchAlgorithmException; 16 | import java.security.NoSuchProviderException; 17 | import java.security.PrivateKey; 18 | import java.security.Security; 19 | import java.security.cert.CertificateEncodingException; 20 | import java.security.cert.CertificateException; 21 | import java.security.cert.X509Certificate; 22 | import java.security.spec.InvalidKeySpecException; 23 | import java.util.Base64; 24 | import java.util.Date; 25 | import java.util.HashMap; 26 | import java.util.Map; 27 | import java.util.concurrent.TimeUnit; 28 | 29 | import static org.junit.jupiter.api.Assertions.assertEquals; 30 | import static org.junit.jupiter.api.Assertions.assertFalse; 31 | import static org.junit.jupiter.api.Assertions.assertNotNull; 32 | import static org.junit.jupiter.api.Assertions.assertTrue; 33 | 34 | @TestMethodOrder(MethodOrderer.OrderAnnotation.class) 35 | public class JCETests { 36 | 37 | private static KeyPair CAKeyPair; 38 | private static X509Certificate CACertificate; 39 | private static Map keyPairs = new HashMap<>(); 40 | private static KeyPairHolder keyPairHolder; 41 | 42 | @BeforeAll 43 | public static void init() { 44 | Security.addProvider(new BouncyCastleProvider()); 45 | } 46 | 47 | @Test 48 | @Order(1) 49 | public void testGenerateKeyPair() throws PKIException { 50 | CAKeyPair = JCEUtils.generateKeyPair(); 51 | assertNotNull(CAKeyPair); 52 | } 53 | 54 | @Test 55 | @Order(2) 56 | public void testGenerateSelfSignedX509CACertificate() throws PKIException { 57 | CACertificate = JCEUtils.createSelfSignedCertificate("ca-subject", new Date(), 1L, TimeUnit.HOURS, CAKeyPair); 58 | assertNotNull(CACertificate); 59 | } 60 | 61 | @Test 62 | @Order(3) 63 | public void testGenerateClientKeypairAndSignedX509Certificate() throws PKIException { 64 | KeyPair clientKeyPair = JCEUtils.generateKeyPair(); 65 | X509Certificate clientCertificate = JCEUtils.createSignedCertificate(CACertificate.getIssuerDN().getName(), 66 | "client-01", new Date(), 1L, TimeUnit.HOURS, clientKeyPair.getPublic(), CAKeyPair.getPrivate()); 67 | keyPairs.put("client-01", new KeyPairHolder(clientKeyPair.getPrivate(), clientCertificate)); 68 | assertNotNull(clientKeyPair); 69 | assertNotNull(clientCertificate); 70 | } 71 | 72 | @Test 73 | @Order(4) 74 | public void verifyClientCertificateValidity() throws PKIException { 75 | KeyPairHolder keyPairHolder = keyPairs.get("client-01"); 76 | X509Certificate clientCertificate = keyPairHolder.getCertificate(); 77 | int version = clientCertificate.getVersion(); 78 | assertTrue(version == 3); 79 | boolean result = JCEUtils.verifySignedCertificate(CACertificate, clientCertificate); 80 | assertTrue(result); 81 | } 82 | 83 | @Test 84 | @Order(5) 85 | public void verifyCACertificateValidity() throws PKIException { 86 | boolean result = JCEUtils.verifySelfSignedCertificate(CACertificate); 87 | assertTrue(result); 88 | } 89 | 90 | @Test 91 | @Order(6) 92 | public void testDigitalSignature() throws PKIException { 93 | byte[] data = "Data String".getBytes(); 94 | KeyPairHolder keyPairHolder = keyPairs.get("client-01"); 95 | byte[] digitalSignature = JCEUtils.createDigitalSignature(data, keyPairHolder.getKeyPair().getPrivate()); 96 | boolean valid = JCEUtils.verifyDigitalSignature(data, digitalSignature, keyPairHolder.getCertificate()); 97 | assertTrue(valid); 98 | valid = JCEUtils.verifyDigitalSignature("Other Data".getBytes(), digitalSignature, keyPairHolder.getCertificate()); 99 | assertFalse(valid); 100 | } 101 | 102 | @Test 103 | @Order(7) 104 | public void testEncryptAndDecryptDataPrivatePublic() throws PKIException { 105 | String dataString = "Data String"; 106 | KeyPairHolder keyPairHolder = keyPairs.get("client-01"); 107 | byte[] encryptedData = JCEUtils.encrypt(dataString.getBytes(StandardCharsets.UTF_8), keyPairHolder.getPrivateKey()); 108 | byte[] decryptedData = JCEUtils.decrypt(encryptedData, keyPairHolder.getPublicKey()); 109 | String decryptedString = new String(decryptedData, StandardCharsets.UTF_8); 110 | assertEquals(dataString, decryptedString); 111 | } 112 | 113 | @Test 114 | @Order(8) 115 | public void testEncryptAndDecryptDataPublicPrivate() throws PKIException { 116 | String dataString = "Data String"; 117 | KeyPairHolder keyPairHolder = keyPairs.get("client-01"); 118 | byte[] encryptedData = JCEUtils.encrypt(dataString.getBytes(StandardCharsets.UTF_8), keyPairHolder.getPublicKey()); 119 | byte[] decryptedData = JCEUtils.decrypt(encryptedData, keyPairHolder.getPrivateKey()); 120 | String decryptedString = new String(decryptedData, StandardCharsets.UTF_8); 121 | assertEquals(dataString, decryptedString); 122 | } 123 | 124 | @Test 125 | @Order(9) 126 | public void loadPrivateKeyAndCertificateFromJKS() throws PKIException { 127 | String keystorePath = "/keystore.jks"; 128 | String alias = "organization"; 129 | String keystorePassword = "secret"; 130 | String privateKeyPassword = "secret"; 131 | keyPairHolder = JCEUtils.loadPrivateKeyAndCertificateFromJKS(keystorePath, alias, keystorePassword, privateKeyPassword); 132 | assertNotNull(keyPairHolder); 133 | assertNotNull(keyPairHolder.getCertificate()); 134 | assertNotNull(keyPairHolder.getPrivateKey()); 135 | } 136 | 137 | @Test 138 | @Order(10) 139 | public void certificateSerializationAndDeserialization() throws PKIException { 140 | byte[] encoded = JCEUtils.serializeX509Certificate(keyPairHolder.getCertificate()); 141 | X509Certificate certificate = JCEUtils.deserializeX509Certificate(encoded); 142 | assertNotNull(certificate); 143 | assertEquals(keyPairHolder.getCertificate(), certificate); 144 | } 145 | 146 | @Test 147 | @Order(11) 148 | public void privateKeySerializationAndDeserialization() throws PKIException { 149 | byte[] encoded = JCEUtils.serializePrivateKey(keyPairHolder.getPrivateKey()); 150 | PrivateKey privateKey = JCEUtils.deserializePrivateKey(encoded); 151 | assertNotNull(privateKey); 152 | assertEquals(keyPairHolder.getPrivateKey(), privateKey); 153 | } 154 | 155 | @Test 156 | @Order(12) 157 | public void createJKSWithPrivateKeyAndCertificate() throws PKIException { 158 | KeyPairHolder keyPair = JCEUtils.generateSelfSignedKeyPairHolder("issuerAndSubject", new Date(), 1L, TimeUnit.HOURS); 159 | byte[] jksBytes = JCEUtils.createJKSWithPrivateKeyAndCertificate("alias", "secret", "secret", keyPair); 160 | assertNotNull(jksBytes); 161 | KeyPairHolder loadedKeyPair = JCEUtils.loadKeypairFromJKS("alias", "secret", "secret", jksBytes); 162 | assertNotNull(loadedKeyPair); 163 | assertNotNull(loadedKeyPair.getCertificate()); 164 | assertNotNull(loadedKeyPair.getKeyPair()); 165 | assertNotNull(loadedKeyPair.getPrivateKey()); 166 | assertEquals(keyPair, loadedKeyPair); 167 | assertEquals(keyPair.getCertificate(), loadedKeyPair.getCertificate()); 168 | assertEquals(keyPair.getPrivateKey(), loadedKeyPair.getPrivateKey()); 169 | } 170 | 171 | } 172 | -------------------------------------------------------------------------------- /examples/jce-demo/src/test/resources/keystore.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jveverka/java-17-examples/2c4f3c39e49d17f2e7e75be4f9aa7ab38ce1fe6c/examples/jce-demo/src/test/resources/keystore.jks -------------------------------------------------------------------------------- /examples/proxy-server/README.md: -------------------------------------------------------------------------------- 1 | # TCP Proxy Server 2 | 3 | This proxy server forwards TCP or UDP connections from bind address and port to another server. 4 | See [configuration example](src/test/resources/proxy-server-config.json). 5 | 6 | ### Requirements 7 | * Java 17 8 | * Gradle 7.3 or later 9 | 10 | ### Build 11 | ``` 12 | gradle clean build test installDist distZip 13 | ``` 14 | 15 | ### Run and Test 16 | ``` 17 | ./build/install/proxy-server/bin/proxy-server src/main/resources/proxy-server-config.json 18 | ``` 19 | ``` 20 | ssh user@127.0.0.1 -p 10111 21 | nc -z -v 127.0.0.1 10111 22 | nc -z -v -u 127.0.0.1 20222 23 | ``` 24 | 25 | ## Native Build 26 | It is possible to create native binary build of ``proxy-server`` using Graal's ``native-image`` tool. 27 | 28 | ### Requirements 29 | * Graal JVM 17 30 | * Gradle 7.3 or later 31 | 32 | ### Build 33 | ``` 34 | ./build-native.sh 35 | ``` 36 | 37 | ### Run 38 | ``` 39 | build/distributions/proxy-server src/main/resources/proxy-server-config.json 40 | ``` -------------------------------------------------------------------------------- /examples/proxy-server/build-native.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Requires installed graalvm and gradle 3 | 4 | gradle clean build test installDist distZip 5 | 6 | JARS_DIR=build/install/proxy-server/lib/ 7 | CLASS_PATH="" 8 | 9 | for JAR in $(ls -1 $JARS_DIR); do 10 | CLASS_PATH=${JARS_DIR}${JAR}:${CLASS_PATH} 11 | done 12 | 13 | echo "CLASS PATH: ${CLASS_PATH}" 14 | 15 | native-image --no-fallback \ 16 | -H:ReflectionConfigurationFiles=reflectconfig.json \ 17 | -cp ${CLASS_PATH} one.microproject.proxyserver.Main 18 | 19 | rm one.microproject.proxyserver.main.build_artifacts.txt 20 | mv one.microproject.proxyserver.main build/distributions/proxy-server 21 | 22 | # run binary proxy-server 23 | # build/distributions/proxy-server src/main/resources/proxy-server-config.json 24 | -------------------------------------------------------------------------------- /examples/proxy-server/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'application' 4 | } 5 | 6 | sourceCompatibility = JavaVersion.VERSION_17 7 | targetCompatibility = JavaVersion.VERSION_17 8 | 9 | group = 'one.microproject.proxy.server' 10 | version = '1.0.0' 11 | mainClassName = 'one.microproject.proxy.server.Main' 12 | applicationDefaultJvmArgs = ['-Xms32m', '-Xmx32m', '-XX:MaxMetaspaceSize=32m'] 13 | 14 | repositories { 15 | mavenCentral() 16 | } 17 | 18 | dependencies { 19 | implementation 'org.slf4j:slf4j-api:2.0.3' 20 | implementation 'org.slf4j:slf4j-simple:2.0.3' 21 | implementation 'com.fasterxml.jackson.core:jackson-core:2.14.0' 22 | implementation 'com.fasterxml.jackson.core:jackson-databind:2.14.0' 23 | implementation 'com.fasterxml.jackson.core:jackson-annotations:2.14.0' 24 | 25 | testImplementation 'org.testng:testng:7.6.1' 26 | } 27 | 28 | test { 29 | // enable TestNG support (default is JUnit) 30 | useTestNG() 31 | testLogging { 32 | events "passed", "skipped", "failed" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /examples/proxy-server/reflectconfig.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "one.microproject.proxyserver.impl.Configuration", 4 | "allPublicFields": true, 5 | "allDeclaredFields": true, 6 | "allDeclaredConstructors": true, 7 | "allPublicConstructors": true, 8 | "allDeclaredMethods": true, 9 | "allPublicMethods": true 10 | }, 11 | { 12 | "name": "one.microproject.proxyserver.impl.ProxyConfiguration", 13 | "allPublicFields": true, 14 | "allDeclaredFields": true, 15 | "allDeclaredConstructors": true, 16 | "allPublicConstructors": true, 17 | "allDeclaredMethods": true, 18 | "allPublicMethods": true 19 | } 20 | ] -------------------------------------------------------------------------------- /examples/proxy-server/scripts/proxy-server-start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export JAVA_HOME=/opt/proxy-server/jdk-17.0.1+12 4 | export PATH=$JAAVA_HOME/bin:$PATH 5 | 6 | LOG_FILE=/opt/proxy-server/proxy-server.log 7 | CONFIG=/opt/proxy-server/proxy-server-config.json 8 | 9 | mv $LOG_FILE $LOG_FILE.old 10 | 11 | /opt/proxy-server/proxy-server-1.0.0/bin/proxy-server $CONFIG > $LOG_FILE 2>&1 12 | 13 | -------------------------------------------------------------------------------- /examples/proxy-server/scripts/proxy-server-stop.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | killall java 4 | -------------------------------------------------------------------------------- /examples/proxy-server/scripts/proxy-server.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Proxy Server Service 3 | After=network.target 4 | After=systemd-user-sessions.service 5 | After=network-online.target 6 | 7 | [Service] 8 | User=root 9 | Type=simple 10 | WorkingDirectory=/opt/proxy-server 11 | ExecStart=/opt/proxy-server/proxy-server-start.sh 12 | ExecStop=/opt/proxy-server/proxy-server-stop.sh 13 | TimeoutSec=30 14 | Restart=on-failure 15 | RestartSec=30 16 | StartLimitInterval=350 17 | StartLimitBurst=10 18 | 19 | [Install] 20 | WantedBy=multi-user.target 21 | -------------------------------------------------------------------------------- /examples/proxy-server/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'proxy-server' -------------------------------------------------------------------------------- /examples/proxy-server/src/main/java/one/microproject/proxyserver/Main.java: -------------------------------------------------------------------------------- 1 | package one.microproject.proxyserver; 2 | 3 | import com.fasterxml.jackson.databind.DeserializationFeature; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import one.microproject.proxyserver.impl.Configuration; 6 | import one.microproject.proxyserver.impl.UDPProxyImpl; 7 | import one.microproject.proxyserver.impl.TCPProxyImpl; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import java.io.FileInputStream; 12 | import java.io.FileNotFoundException; 13 | import java.io.IOException; 14 | import java.io.InputStream; 15 | import java.util.Map; 16 | import java.util.UUID; 17 | import java.util.concurrent.ConcurrentHashMap; 18 | import java.util.concurrent.CountDownLatch; 19 | 20 | public class Main { 21 | 22 | private static final Logger LOG = LoggerFactory.getLogger(Main.class); 23 | 24 | private static final Map proxies = new ConcurrentHashMap<>(); 25 | private static final CountDownLatch cl = new CountDownLatch(1); 26 | 27 | public static void main(String[] args) throws IOException, InterruptedException { 28 | Runtime.getRuntime().addShutdownHook(new Thread(Main::stopAll)); 29 | Configuration configuration = loadConfigOrGetDefault(args); 30 | startAll(configuration); 31 | cl.await(); 32 | LOG.info("Main terminated !"); 33 | } 34 | 35 | public static void startAll(Configuration configuration) throws IOException { 36 | LOG.info("Starting Proxy Server id={} name=\"{}\" proxies={}", configuration.id(), configuration.name(), configuration.proxies().size()); 37 | configuration.proxies().forEach(c -> { 38 | LOG.info("Proxy Config: {} {}:{} -> {}:{} maxConnections={}", c.protocol(), c.serverHost(), c.serverPort(), c.targetHost(), c.targetPort(), c.maxConnections()); 39 | try { 40 | String protocol = c.protocol().trim().toUpperCase(); 41 | ProxyServer proxyServer = null; 42 | if ("UDP".equals(protocol)) { 43 | proxyServer = new UDPProxyImpl(c); 44 | } else if ("TCP".equals(protocol)) { 45 | proxyServer = new TCPProxyImpl(c); 46 | } else { 47 | throw new UnsupportedOperationException("Unsupported protocol type: " + c.protocol()); 48 | } 49 | String id = UUID.randomUUID().toString(); 50 | proxyServer.start(); 51 | proxies.put(id, proxyServer); 52 | } catch (IOException e) { 53 | LOG.error("Proxy Server start ERROR: ", e); 54 | } catch (UnsupportedOperationException e) { 55 | LOG.error(e.getMessage()); 56 | } 57 | }); 58 | } 59 | 60 | public static void stopAll() { 61 | try { 62 | LOG.info("Proxy Server shutdown triggered !"); 63 | cl.countDown(); 64 | proxies.forEach((k,v) -> { 65 | try { 66 | LOG.info("Closing Proxy Server {}", k); 67 | v.close(); 68 | } catch (Exception e) { 69 | LOG.error("Proxy Server close ERROR: ", e); 70 | } 71 | }); 72 | LOG.info("done."); 73 | } catch (Exception e) { 74 | LOG.error("Proxy Server shutdown ERROR: ", e); 75 | } 76 | } 77 | 78 | public static Configuration loadConfigOrGetDefault(String[] args) throws IOException { 79 | ObjectMapper mapper = new ObjectMapper(); 80 | mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); 81 | if (args.length > 0) { 82 | LOG.info("Loading configuration from: {}", args[0]); 83 | try(FileInputStream configFile = new FileInputStream(args[0])) { 84 | return mapper.readValue(configFile, Configuration.class); 85 | } 86 | } else { 87 | LOG.info("No configuration specified,using DEFAULT configuration !"); 88 | InputStream is = Main.class.getClassLoader().getResourceAsStream("proxy-server-config.json"); 89 | return mapper.readValue(is, Configuration.class); 90 | } 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /examples/proxy-server/src/main/java/one/microproject/proxyserver/ProxyServer.java: -------------------------------------------------------------------------------- 1 | package one.microproject.proxyserver; 2 | 3 | import java.io.IOException; 4 | 5 | public interface ProxyServer extends AutoCloseable { 6 | 7 | void start() throws IOException; 8 | 9 | } 10 | -------------------------------------------------------------------------------- /examples/proxy-server/src/main/java/one/microproject/proxyserver/impl/CloseListener.java: -------------------------------------------------------------------------------- 1 | package one.microproject.proxyserver.impl; 2 | 3 | public interface CloseListener { 4 | 5 | void onClose(); 6 | 7 | } 8 | -------------------------------------------------------------------------------- /examples/proxy-server/src/main/java/one/microproject/proxyserver/impl/Configuration.java: -------------------------------------------------------------------------------- 1 | package one.microproject.proxyserver.impl; 2 | 3 | import java.util.List; 4 | 5 | public record Configuration(String id, String name, List proxies) { 6 | } 7 | -------------------------------------------------------------------------------- /examples/proxy-server/src/main/java/one/microproject/proxyserver/impl/ConnectionRegistry.java: -------------------------------------------------------------------------------- 1 | package one.microproject.proxyserver.impl; 2 | 3 | public interface ConnectionRegistry { 4 | 5 | void register(TcpActiveConnection activeConnection); 6 | 7 | int getActiveConnections(); 8 | 9 | void unregister(String id); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /examples/proxy-server/src/main/java/one/microproject/proxyserver/impl/ProxyConfiguration.java: -------------------------------------------------------------------------------- 1 | package one.microproject.proxyserver.impl; 2 | 3 | public record ProxyConfiguration(String protocol, String serverHost, Integer serverPort, String targetHost, Integer targetPort, Integer maxConnections) { 4 | } 5 | -------------------------------------------------------------------------------- /examples/proxy-server/src/main/java/one/microproject/proxyserver/impl/TCPProxyImpl.java: -------------------------------------------------------------------------------- 1 | package one.microproject.proxyserver.impl; 2 | 3 | import one.microproject.proxyserver.ProxyServer; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | import java.io.IOException; 8 | import java.util.Map; 9 | import java.util.concurrent.ConcurrentHashMap; 10 | import java.util.concurrent.ExecutorService; 11 | import java.util.concurrent.Executors; 12 | import java.util.concurrent.TimeUnit; 13 | 14 | public class TCPProxyImpl implements ProxyServer, ConnectionRegistry { 15 | 16 | private static final Logger LOG = LoggerFactory.getLogger(TCPProxyImpl.class); 17 | 18 | private final String serverHost; 19 | private final Integer serverPort; 20 | private final String targetHost; 21 | private final Integer targetPort; 22 | private final Integer maxConnections; 23 | private final Map activeConnections; 24 | 25 | private TcpMain tcpMain; 26 | private ExecutorService processors; 27 | 28 | public TCPProxyImpl(ProxyConfiguration configuration) { 29 | this.serverHost = configuration.serverHost(); 30 | this.serverPort = configuration.serverPort(); 31 | this.targetHost = configuration.targetHost(); 32 | this.targetPort = configuration.targetPort(); 33 | this.maxConnections = configuration.maxConnections(); 34 | this.activeConnections = new ConcurrentHashMap<>(); 35 | } 36 | 37 | @Override 38 | public void start() throws IOException { 39 | LOG.info("Starting TCP proxy ..."); 40 | int threadPoolSize = 1 + (maxConnections*2); 41 | LOG.info("Starting internal threadpool size={}", threadPoolSize); 42 | this.processors = Executors.newFixedThreadPool(threadPoolSize); 43 | this.tcpMain = new TcpMain(this, serverHost, serverPort, processors, targetHost, targetPort, maxConnections); 44 | processors.submit(tcpMain); 45 | LOG.info("TCP proxy started."); 46 | } 47 | 48 | @Override 49 | public void close() throws Exception { 50 | LOG.info("Closing TCP proxy ..."); 51 | activeConnections.forEach((k,v) -> { 52 | try { 53 | v.close(); 54 | } catch (Exception e) { 55 | LOG.error("On close Exception: ", e); 56 | } 57 | }); 58 | tcpMain.close(); 59 | processors.shutdown(); 60 | processors.awaitTermination(10, TimeUnit.SECONDS); 61 | LOG.info("TCP proxy stopped."); 62 | } 63 | 64 | @Override 65 | public synchronized void register(TcpActiveConnection activeConnection) { 66 | activeConnections.put(activeConnection.getId(), activeConnection); 67 | LOG.info("Active connections: {}", activeConnections.size()); 68 | } 69 | 70 | @Override 71 | public synchronized int getActiveConnections() { 72 | return activeConnections.size(); 73 | } 74 | 75 | @Override 76 | public synchronized void unregister(String id) { 77 | activeConnections.remove(id); 78 | LOG.info("Active connections: {}", activeConnections.size()); 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /examples/proxy-server/src/main/java/one/microproject/proxyserver/impl/TcpActiveConnection.java: -------------------------------------------------------------------------------- 1 | package one.microproject.proxyserver.impl; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.net.Socket; 7 | import java.util.concurrent.atomic.AtomicBoolean; 8 | 9 | public class TcpActiveConnection implements AutoCloseable, CloseListener { 10 | 11 | private static final Logger LOG = LoggerFactory.getLogger(TcpActiveConnection.class); 12 | 13 | private final String id; 14 | private final Socket socket; 15 | private final String targetHost; 16 | private final Integer targetPort; 17 | private final TcpDataForwarder forwardPipe; 18 | private final TcpDataForwarder reversePipe; 19 | private final ConnectionRegistry connectionRegistry; 20 | private final AtomicBoolean closed; 21 | 22 | public TcpActiveConnection(String id, Socket socket, String targetHost, Integer targetPort, 23 | TcpDataForwarder forwardPipe, TcpDataForwarder reversePipe, ConnectionRegistry connectionRegistry) { 24 | this.id = id; 25 | this.socket = socket; 26 | this.targetHost = targetHost; 27 | this.targetPort = targetPort; 28 | this.forwardPipe = forwardPipe; 29 | this.reversePipe = reversePipe; 30 | this.connectionRegistry = connectionRegistry; 31 | this.forwardPipe.add(this); 32 | this.reversePipe.add(this); 33 | this.closed = new AtomicBoolean(Boolean.FALSE); 34 | } 35 | 36 | public String getId() { 37 | return id; 38 | } 39 | 40 | @Override 41 | public void close() throws Exception { 42 | LOG.info("Closing connection to {}:{}", targetHost, targetPort); 43 | connectionRegistry.unregister(this.id); 44 | forwardPipe.close(); 45 | reversePipe.close(); 46 | socket.close(); 47 | } 48 | 49 | @Override 50 | public synchronized void onClose() { 51 | try { 52 | boolean isClosed = closed.getAndSet(Boolean.TRUE); 53 | if (Boolean.FALSE.equals(isClosed)) { 54 | close(); 55 | } 56 | } catch (Exception e) { 57 | LOG.error("On Connection ERROR: ", e); 58 | } 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /examples/proxy-server/src/main/java/one/microproject/proxyserver/impl/TcpDataForwarder.java: -------------------------------------------------------------------------------- 1 | package one.microproject.proxyserver.impl; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.io.OutputStream; 9 | 10 | public class TcpDataForwarder implements Runnable, AutoCloseable { 11 | 12 | private static final Logger LOG = LoggerFactory.getLogger(TcpDataForwarder.class); 13 | 14 | private final String name; 15 | private final InputStream inputStream; 16 | private final OutputStream outputStream; 17 | private CloseListener closeListener; 18 | 19 | public TcpDataForwarder(String name, InputStream inputStream, OutputStream outputStream) { 20 | this.name = name; 21 | this.inputStream = inputStream; 22 | this.outputStream = outputStream; 23 | } 24 | 25 | public void add(CloseListener closeListener) { 26 | this.closeListener = closeListener; 27 | } 28 | 29 | @Override 30 | public void run() { 31 | LOG.info("Forwarding data {} ...", name); 32 | try { 33 | int dataByte; 34 | while ((dataByte = inputStream.read()) != -1) { 35 | outputStream.write(dataByte); 36 | } 37 | } catch (IOException e) { 38 | LOG.info("DataForwarder IOException: close"); 39 | } finally { 40 | try { 41 | close(); 42 | } catch (Exception e) { 43 | LOG.info("DataForwarder IOException: on finally"); 44 | } 45 | } 46 | } 47 | 48 | @Override 49 | public void close() throws Exception { 50 | LOG.info("Closing data forwarder {}", name); 51 | outputStream.flush(); 52 | inputStream.close(); 53 | outputStream.close(); 54 | if (closeListener != null) { 55 | closeListener.onClose(); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /examples/proxy-server/src/main/java/one/microproject/proxyserver/impl/TcpMain.java: -------------------------------------------------------------------------------- 1 | package one.microproject.proxyserver.impl; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.io.IOException; 7 | import java.net.InetAddress; 8 | import java.net.ServerSocket; 9 | import java.net.Socket; 10 | import java.util.UUID; 11 | import java.util.concurrent.ExecutorService; 12 | 13 | public class TcpMain implements Runnable, AutoCloseable { 14 | 15 | private static final Logger LOG = LoggerFactory.getLogger(TcpMain.class); 16 | 17 | private final ConnectionRegistry connectionRegistry; 18 | private final ExecutorService processors; 19 | private final String serverHost; 20 | private final Integer serverPort; 21 | private final String targetHost; 22 | private final Integer targetPort; 23 | private final Integer maxConnections; 24 | 25 | private boolean active; 26 | private ServerSocket serverSocket; 27 | 28 | public TcpMain(ConnectionRegistry connectionRegistry, String serverHost, Integer serverPort, 29 | ExecutorService processors, String targetHost, Integer targetPort, Integer maxConnections) { 30 | this.connectionRegistry = connectionRegistry; 31 | this.serverHost = serverHost; 32 | this.serverPort = serverPort; 33 | this.processors = processors; 34 | this.targetHost = targetHost; 35 | this.targetPort = targetPort; 36 | this.active = true; 37 | this.maxConnections = maxConnections; 38 | } 39 | 40 | @Override 41 | public void run() { 42 | Socket clientSocket = null; 43 | try { 44 | LOG.info("Creating TCP socket"); 45 | serverSocket = new ServerSocket(serverPort, maxConnections*2, InetAddress.getByName(serverHost)); 46 | while (active) { 47 | LOG.info("Waiting for incoming TCP connections on {} ...", serverSocket.getLocalPort()); 48 | clientSocket = serverSocket.accept(); 49 | LOG.info("TCP connection accepted !"); 50 | if (connectionRegistry.getActiveConnections() >= maxConnections) { 51 | LOG.info("Max connections {} per server exceeded, closing connection {}:{} !", maxConnections, clientSocket.getRemoteSocketAddress(), clientSocket.getPort()); 52 | clientSocket.close(); 53 | continue; 54 | } 55 | String id = UUID.randomUUID().toString(); 56 | LOG.info("Connection id={} from {}:{} to {}:{} in progress ...", id, clientSocket.getRemoteSocketAddress(), clientSocket.getPort(), targetHost, targetPort); 57 | Socket socket = new Socket(targetHost, targetPort); 58 | LOG.info("Connection id={} from {}:{} to {}:{} established.", id, clientSocket.getRemoteSocketAddress(), clientSocket.getPort(), targetHost, targetPort); 59 | TcpDataForwarder forwardPipe = new TcpDataForwarder(" -> ", clientSocket.getInputStream(), socket.getOutputStream()); 60 | TcpDataForwarder reversePipe = new TcpDataForwarder(" <- ", socket.getInputStream(), clientSocket.getOutputStream()); 61 | TcpActiveConnection activeConnection = new TcpActiveConnection(id, socket, targetHost, targetPort, forwardPipe, reversePipe, connectionRegistry); 62 | processors.submit(forwardPipe); 63 | processors.submit(reversePipe); 64 | connectionRegistry.register(activeConnection); 65 | LOG.info("Connection id={} data forwarders created", id); 66 | } 67 | } catch (IOException e) { 68 | try { 69 | LOG.error("TcpMain IOException: ", e); 70 | if (clientSocket != null) { 71 | clientSocket.close(); 72 | } 73 | close(); 74 | } catch (Exception ex) { 75 | LOG.error("TcpMain Exception: ", e); 76 | } 77 | } 78 | } 79 | 80 | @Override 81 | public void close() throws Exception { 82 | LOG.info("Closing TCP Server ..."); 83 | this.active = false; 84 | this.serverSocket.close(); 85 | this.processors.shutdown(); 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /examples/proxy-server/src/main/java/one/microproject/proxyserver/impl/UDPProxyImpl.java: -------------------------------------------------------------------------------- 1 | package one.microproject.proxyserver.impl; 2 | 3 | import one.microproject.proxyserver.ProxyServer; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | import java.io.IOException; 8 | import java.net.DatagramSocket; 9 | import java.net.InetAddress; 10 | import java.util.concurrent.ExecutorService; 11 | import java.util.concurrent.Executors; 12 | import java.util.concurrent.TimeUnit; 13 | 14 | public class UDPProxyImpl implements ProxyServer { 15 | 16 | private static final Logger LOG = LoggerFactory.getLogger(UDPProxyImpl.class); 17 | 18 | private final String serverHost; 19 | private final Integer serverPort; 20 | private final String targetHost; 21 | private final Integer targetPort; 22 | 23 | private ExecutorService processors; 24 | private DatagramSocket serverSocket; 25 | private DatagramSocket clientSocket; 26 | private UdpDataHandlerForward handlerForward; 27 | private UdpDataHandlerReverse handlerReverse; 28 | 29 | public UDPProxyImpl(ProxyConfiguration configuration) { 30 | this.serverHost = configuration.serverHost(); 31 | this.serverPort = configuration.serverPort(); 32 | this.targetHost = configuration.targetHost(); 33 | this.targetPort = configuration.targetPort(); 34 | } 35 | 36 | @Override 37 | public void start() throws IOException { 38 | LOG.info("Starting UDP proxy ..."); 39 | this.processors = Executors.newFixedThreadPool(2); 40 | this.serverSocket = new DatagramSocket(serverPort, InetAddress.getByName(serverHost)); 41 | this.clientSocket = new DatagramSocket(); 42 | handlerForward = new UdpDataHandlerForward(serverSocket, clientSocket, targetHost, targetPort); 43 | handlerReverse = new UdpDataHandlerReverse(serverSocket, clientSocket, serverHost, serverPort); 44 | processors.submit(handlerForward); 45 | processors.submit(handlerReverse); 46 | LOG.info("UDP proxy started."); 47 | } 48 | 49 | @Override 50 | public void close() throws Exception { 51 | LOG.info("Closing UDP proxy ..."); 52 | handlerForward.close(); 53 | handlerReverse.close(); 54 | serverSocket.close(); 55 | clientSocket.close(); 56 | processors.shutdown(); 57 | processors.awaitTermination(10, TimeUnit.SECONDS); 58 | LOG.info("UDP proxy stopped."); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /examples/proxy-server/src/main/java/one/microproject/proxyserver/impl/UdpDataHandlerForward.java: -------------------------------------------------------------------------------- 1 | package one.microproject.proxyserver.impl; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.io.IOException; 7 | import java.net.DatagramPacket; 8 | import java.net.DatagramSocket; 9 | import java.net.InetAddress; 10 | 11 | public class UdpDataHandlerForward implements Runnable, AutoCloseable { 12 | 13 | private static final Logger LOG = LoggerFactory.getLogger(UdpDataHandlerForward.class); 14 | 15 | private final DatagramSocket serverSocket; 16 | private final DatagramSocket clientSocket; 17 | private final String targetHost; 18 | private final Integer targetPort; 19 | 20 | private boolean active; 21 | 22 | public UdpDataHandlerForward(DatagramSocket serverSocket, DatagramSocket clientSocket, String targetHost, Integer targetPort) { 23 | this.serverSocket = serverSocket; 24 | this.clientSocket = clientSocket; 25 | this.active = true; 26 | this.targetHost = targetHost; 27 | this.targetPort = targetPort; 28 | } 29 | 30 | @Override 31 | public void close() throws Exception { 32 | this.active = false; 33 | } 34 | 35 | @Override 36 | public void run() { 37 | try { 38 | InetAddress clientAddress = InetAddress.getByName(targetHost); 39 | byte[] serverBuffer = new byte[2048]; 40 | while (active) { 41 | DatagramPacket serverPacket = new DatagramPacket(serverBuffer, serverBuffer.length); 42 | serverSocket.receive(serverPacket); 43 | byte[] payload = serverPacket.getData(); 44 | DatagramPacket clientPacket = new DatagramPacket(payload, payload.length, clientAddress, targetPort); 45 | clientSocket.send(clientPacket); 46 | } 47 | } catch (IOException e) { 48 | LOG.error("UdpDataHandlerForward ERROR:", e); 49 | } 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /examples/proxy-server/src/main/java/one/microproject/proxyserver/impl/UdpDataHandlerReverse.java: -------------------------------------------------------------------------------- 1 | package one.microproject.proxyserver.impl; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.io.IOException; 7 | import java.net.DatagramPacket; 8 | import java.net.DatagramSocket; 9 | import java.net.InetAddress; 10 | 11 | public class UdpDataHandlerReverse implements Runnable, AutoCloseable { 12 | 13 | private static final Logger LOG = LoggerFactory.getLogger(UdpDataHandlerReverse.class); 14 | 15 | private final DatagramSocket serverSocket; 16 | private final DatagramSocket clientSocket; 17 | private final String serverHost; 18 | private final Integer serverPort; 19 | 20 | private boolean active; 21 | 22 | public UdpDataHandlerReverse(DatagramSocket serverSocket, DatagramSocket clientSocket, String serverHost, Integer serverPort) { 23 | this.serverSocket = serverSocket; 24 | this.clientSocket = clientSocket; 25 | this.active = true; 26 | this.serverHost = serverHost; 27 | this.serverPort = serverPort; 28 | } 29 | 30 | @Override 31 | public void close() throws Exception { 32 | this.active = false; 33 | } 34 | 35 | @Override 36 | public void run() { 37 | try { 38 | InetAddress serverAddress = InetAddress.getByName(serverHost); 39 | byte[] clientBuffer = new byte[2048]; 40 | while (active) { 41 | DatagramPacket clientPacket = new DatagramPacket(clientBuffer, clientBuffer.length); 42 | clientSocket.receive(clientPacket); 43 | byte[] payload = clientPacket.getData(); 44 | DatagramPacket serverPacket = new DatagramPacket(payload, payload.length, serverAddress, serverPort); 45 | serverSocket.send(serverPacket); 46 | } 47 | } catch (IOException e) { 48 | LOG.error("UdpDataHandlerReverse ERROR:", e); 49 | } 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /examples/proxy-server/src/main/java/one/microproject/proxyserver/test/Constants.java: -------------------------------------------------------------------------------- 1 | package one.microproject.proxyserver.test; 2 | 3 | public final class Constants { 4 | 5 | private Constants() { 6 | } 7 | 8 | public static final int MESSAGE_END = 0x03; 9 | public static final int MESSAGE_EOF = -1; 10 | 11 | public static String getHost(String[] args) { 12 | if (args.length == 0) { 13 | return "localhost"; 14 | } else { 15 | return args[0]; 16 | } 17 | } 18 | 19 | public static int getPort(String[] args, int defaultPort) { 20 | if (args.length < 2) { 21 | return defaultPort; 22 | } else { 23 | try { 24 | return Integer.parseInt(args[1]); 25 | } catch (NumberFormatException e) { 26 | return defaultPort; 27 | } 28 | } 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /examples/proxy-server/src/main/java/one/microproject/proxyserver/test/TCPClient.java: -------------------------------------------------------------------------------- 1 | package one.microproject.proxyserver.test; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.io.ByteArrayOutputStream; 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.io.OutputStream; 10 | import java.net.Socket; 11 | import java.nio.charset.StandardCharsets; 12 | import java.util.concurrent.CountDownLatch; 13 | import java.util.concurrent.ExecutorService; 14 | import java.util.concurrent.Executors; 15 | 16 | import static one.microproject.proxyserver.test.Constants.MESSAGE_END; 17 | import static one.microproject.proxyserver.test.Constants.MESSAGE_EOF; 18 | 19 | public class TCPClient { 20 | 21 | private static final Logger LOGGER = LoggerFactory.getLogger(TCPClient.class); 22 | 23 | private final String host; 24 | private final Integer port; 25 | private Socket socket; 26 | private ExecutorService executor; 27 | 28 | public TCPClient(String host, Integer port) { 29 | this.host = host; 30 | this.port = port; 31 | } 32 | 33 | public void connect() throws IOException { 34 | LOGGER.info("Starting TEST TCP Client: {}:{}", host, port); 35 | socket = new Socket(host, port); 36 | executor = Executors.newSingleThreadExecutor(); 37 | } 38 | 39 | public String sendAndWaitForResponse(String message) throws IOException, InterruptedException { 40 | LOGGER.info("TEST TCP Client: {}:{} - sending data", host, port); 41 | SocketListener socketListener = new SocketListener(socket.getInputStream()); 42 | executor.submit(socketListener); 43 | OutputStream outputStream = socket.getOutputStream(); 44 | outputStream.write(message.getBytes(StandardCharsets.UTF_8)); 45 | outputStream.write(MESSAGE_END); 46 | outputStream.flush(); 47 | return new String(socketListener.waitAndGetResponse(), StandardCharsets.UTF_8); 48 | } 49 | 50 | public void close() throws IOException { 51 | socket.close(); 52 | executor.shutdown(); 53 | } 54 | 55 | private static class SocketListener implements Runnable { 56 | private final InputStream in; 57 | private final CountDownLatch cl; 58 | private byte[] response; 59 | private SocketListener(InputStream in) { 60 | this.in = in; 61 | this.cl = new CountDownLatch(1); 62 | } 63 | private byte[] waitAndGetResponse() throws InterruptedException { 64 | cl.await(); 65 | return response; 66 | } 67 | @Override 68 | public void run() { 69 | try { 70 | int nextByte = 0; 71 | ByteArrayOutputStream messageBuffer = new ByteArrayOutputStream(1024); 72 | while (nextByte != MESSAGE_EOF) { 73 | nextByte = in.read(); 74 | switch (nextByte) { 75 | case MESSAGE_END: 76 | messageBuffer.flush(); 77 | response = messageBuffer.toByteArray(); 78 | this.cl.countDown(); 79 | return; 80 | default: 81 | messageBuffer.write(nextByte); 82 | } 83 | } 84 | } catch (Exception e) { 85 | throw new UnsupportedOperationException(e); 86 | } 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /examples/proxy-server/src/main/java/one/microproject/proxyserver/test/TCPServer.java: -------------------------------------------------------------------------------- 1 | package one.microproject.proxyserver.test; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.io.ByteArrayOutputStream; 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.io.OutputStream; 10 | import java.net.InetAddress; 11 | import java.net.ServerSocket; 12 | import java.net.Socket; 13 | 14 | import static one.microproject.proxyserver.test.Constants.MESSAGE_END; 15 | 16 | public class TCPServer implements Runnable, AutoCloseable { 17 | 18 | private static final Logger LOG = LoggerFactory.getLogger(TCPServer.class); 19 | 20 | private final String host; 21 | private final Integer port; 22 | 23 | private boolean active; 24 | private ServerSocket serverSocket; 25 | private Socket clientSocket; 26 | 27 | public TCPServer(String host, Integer port) { 28 | this.host = host; 29 | this.port = port; 30 | this.active = true; 31 | } 32 | 33 | @Override 34 | public void run() { 35 | try { 36 | LOG.info("Starting TEST TCP Server: {}:{}", host, port); 37 | serverSocket = new ServerSocket(port, 2, InetAddress.getByName(host)); 38 | clientSocket = serverSocket.accept(); 39 | LOG.info("TEST TCP Server: {}:{} - connection accepted !", host, port); 40 | while(active) { 41 | InputStream is = clientSocket.getInputStream(); 42 | int dataByte; 43 | ByteArrayOutputStream baos = new ByteArrayOutputStream(1024); 44 | LOG.info("TEST TCP Server: {}:{} - reading request.", host, port); 45 | while((dataByte = is.read()) != MESSAGE_END) { 46 | baos.write(dataByte); 47 | } 48 | baos.flush(); 49 | LOG.info("TEST TCP Server: {}:{} - writing response.", host, port); 50 | OutputStream os = clientSocket.getOutputStream(); 51 | os.write(baos.toByteArray()); 52 | os.write(MESSAGE_END); 53 | os.flush(); 54 | } 55 | } catch (IOException e) { 56 | e.printStackTrace(); 57 | } finally { 58 | try { 59 | close(); 60 | } catch (Exception e) { 61 | e.printStackTrace(); 62 | } 63 | } 64 | } 65 | 66 | @Override 67 | public void close() throws Exception { 68 | active = false; 69 | if (clientSocket != null) { 70 | clientSocket.close(); 71 | } 72 | if (serverSocket != null) { 73 | serverSocket.close(); 74 | } 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /examples/proxy-server/src/main/java/one/microproject/proxyserver/test/TCPServerMain.java: -------------------------------------------------------------------------------- 1 | package one.microproject.proxyserver.test; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.util.concurrent.ExecutorService; 7 | import java.util.concurrent.Executors; 8 | 9 | public class TCPServerMain { 10 | 11 | private static final Logger LOG = LoggerFactory.getLogger(TCPServerMain.class); 12 | 13 | private static final ExecutorService executorService = Executors.newSingleThreadExecutor(); 14 | 15 | private static TCPServer tcpServer; 16 | 17 | public static void main(String[] args) { 18 | Runtime.getRuntime().addShutdownHook(new Thread(TCPServerMain::stopAll)); 19 | String host = Constants.getHost(args); 20 | Integer port = Constants.getPort(args, 2222); 21 | LOG.info("Starting TEST TCP Server {}:{}", host, port); 22 | tcpServer = new TCPServer(host, port); 23 | executorService.submit(tcpServer); 24 | } 25 | 26 | public static void stopAll() { 27 | try { 28 | LOG.info("Stopping TEST TCP Server"); 29 | tcpServer.close(); 30 | } catch (Exception e) { 31 | LOG.error("TEST TCP Server stop ERROR:", e); 32 | } 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /examples/proxy-server/src/main/java/one/microproject/proxyserver/test/TCPTestClientMain.java: -------------------------------------------------------------------------------- 1 | package one.microproject.proxyserver.test; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.io.IOException; 7 | 8 | public class TCPTestClientMain { 9 | 10 | private static final Logger LOG = LoggerFactory.getLogger(TCPTestClientMain.class); 11 | 12 | private static TCPClient tcpClient; 13 | 14 | public static void main(String[] args) throws IOException, InterruptedException { 15 | Runtime.getRuntime().addShutdownHook(new Thread(TCPTestClientMain::stopAll)); 16 | String host = Constants.getHost(args); 17 | Integer port = Constants.getPort(args, 20222); 18 | LOG.info("Starting TEST TCP Client {}:{}", host, port); 19 | tcpClient = new TCPClient(host, port); 20 | tcpClient.connect(); 21 | String request = "hi"; 22 | for (int i=0; i<100; i++) { 23 | String response = tcpClient.sendAndWaitForResponse(request + i); 24 | if (response.equals(request + i)) { 25 | LOG.info("TEST {} OK !", i); 26 | } else { 27 | LOG.error("TEST {} FAILED !", i); 28 | } 29 | } 30 | LOG.info("done."); 31 | } 32 | 33 | public static void stopAll() { 34 | try { 35 | LOG.info("Stopping TEST TCP Client"); 36 | tcpClient.close(); 37 | } catch (Exception e) { 38 | LOG.error("TEST TCP Client stop ERROR:", e); 39 | } 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /examples/proxy-server/src/main/java/one/microproject/proxyserver/test/UDPClient.java: -------------------------------------------------------------------------------- 1 | package one.microproject.proxyserver.test; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.io.ByteArrayOutputStream; 7 | import java.io.IOException; 8 | import java.net.DatagramPacket; 9 | import java.net.DatagramSocket; 10 | import java.net.InetAddress; 11 | import java.nio.charset.StandardCharsets; 12 | 13 | 14 | public class UDPClient { 15 | 16 | private static final Logger LOGGER = LoggerFactory.getLogger(UDPClient.class); 17 | 18 | private final String host; 19 | private final Integer port; 20 | 21 | private DatagramSocket socket; 22 | 23 | public UDPClient(String host, Integer port) { 24 | this.host = host; 25 | this.port = port; 26 | } 27 | 28 | public void connect() throws IOException { 29 | LOGGER.info("Starting TEST UDP Client: {}:{}", host, port); 30 | socket = new DatagramSocket(port, InetAddress.getByName(host)); 31 | } 32 | 33 | public String sendAndWaitForResponse(String message) throws IOException { 34 | LOGGER.info("TEST UDP Client: {}:{} - sending data", host, port); 35 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 36 | baos.write(message.getBytes(StandardCharsets.UTF_8)); 37 | baos.flush(); 38 | byte[] data = baos.toByteArray(); 39 | DatagramPacket packet = new DatagramPacket(data, data.length, InetAddress.getByName(host), port); 40 | socket.send(packet); 41 | data = new byte[2048]; 42 | packet = new DatagramPacket(data, data.length); 43 | socket.receive(packet); 44 | return new String(map(packet.getData())); 45 | } 46 | 47 | public void close() throws IOException { 48 | socket.close(); 49 | } 50 | 51 | private static String map(byte[] bytes) { 52 | StringBuffer buffer = new StringBuffer(); 53 | for (byte by: bytes) { 54 | if (by != 0) { 55 | buffer.append((char)by); 56 | } 57 | } 58 | return buffer.toString(); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /examples/proxy-server/src/main/java/one/microproject/proxyserver/test/UDPServer.java: -------------------------------------------------------------------------------- 1 | package one.microproject.proxyserver.test; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.io.IOException; 7 | import java.net.DatagramPacket; 8 | import java.net.DatagramSocket; 9 | import java.net.InetAddress; 10 | 11 | public class UDPServer implements Runnable, AutoCloseable { 12 | 13 | private static final Logger LOG = LoggerFactory.getLogger(UDPServer.class); 14 | 15 | private final String host; 16 | private final Integer port; 17 | 18 | private boolean active; 19 | private DatagramSocket serverSocket; 20 | 21 | public UDPServer(String host, Integer port) { 22 | this.host = host; 23 | this.port = port; 24 | this.active = true; 25 | } 26 | 27 | @Override 28 | public void run() { 29 | try { 30 | LOG.info("Starting TEST UDP Server: {}:{}", host, port); 31 | byte[] buf = new byte[2046]; 32 | while (active) { 33 | serverSocket = new DatagramSocket(port, InetAddress.getByName(host)); 34 | DatagramPacket requestPacket = new DatagramPacket(buf, buf.length); 35 | LOG.info("TEST UDP Server: {}:{} - waiting for message.", host, port); 36 | serverSocket.receive(requestPacket); 37 | InetAddress responseAddress = requestPacket.getAddress(); 38 | int responsePort = requestPacket.getPort(); 39 | byte[] payload = requestPacket.getData(); 40 | DatagramPacket responsePacket = new DatagramPacket(payload, payload.length, responseAddress, responsePort); 41 | LOG.info("TEST UDP Server: {}:{} - sending response.", host, port); 42 | serverSocket.send(responsePacket); 43 | } 44 | } catch (IOException e) { 45 | try { 46 | close(); 47 | } catch (Exception ex) { 48 | ex.printStackTrace(); 49 | } 50 | } 51 | } 52 | 53 | @Override 54 | public void close() throws Exception { 55 | this.active = false; 56 | if (serverSocket != null) { 57 | serverSocket.close(); 58 | } 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /examples/proxy-server/src/main/java/one/microproject/proxyserver/test/UDPServerMain.java: -------------------------------------------------------------------------------- 1 | package one.microproject.proxyserver.test; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.util.concurrent.ExecutorService; 7 | import java.util.concurrent.Executors; 8 | 9 | public class UDPServerMain { 10 | 11 | private static final Logger LOG = LoggerFactory.getLogger(UDPServerMain.class); 12 | 13 | private static final ExecutorService executorService = Executors.newSingleThreadExecutor(); 14 | 15 | private static UDPServer udpServer; 16 | 17 | public static void main(String[] args) { 18 | Runtime.getRuntime().addShutdownHook(new Thread(UDPServerMain::stopAll)); 19 | String host = Constants.getHost(args); 20 | Integer port = Constants.getPort(args, 3333); 21 | LOG.info("Starting TEST UDP Server {}:{}", host, port); 22 | udpServer = new UDPServer(host, port); 23 | executorService.submit(udpServer); 24 | } 25 | 26 | public static void stopAll() { 27 | try { 28 | LOG.info("Stopping TEST UDP Server"); 29 | udpServer.close(); 30 | } catch (Exception e) { 31 | LOG.error("TEST UDP Server stop ERROR:", e); 32 | } 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /examples/proxy-server/src/main/java/one/microproject/proxyserver/test/UDPTestClientMain.java: -------------------------------------------------------------------------------- 1 | package one.microproject.proxyserver.test; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.io.IOException; 7 | 8 | public class UDPTestClientMain { 9 | 10 | private static final Logger LOG = LoggerFactory.getLogger(UDPTestClientMain.class); 11 | 12 | private static UDPClient udpClient; 13 | 14 | public static void main(String[] args) throws IOException { 15 | Runtime.getRuntime().addShutdownHook(new Thread(UDPTestClientMain::stopAll)); 16 | String host = Constants.getHost(args); 17 | Integer port = Constants.getPort(args, 30333); 18 | LOG.info("Starting TEST UDP Client {}:{}", host, port); 19 | udpClient = new UDPClient(host, port); 20 | udpClient.connect(); 21 | String request = "hi"; 22 | for (int i=0; i<100; i++) { 23 | String response = udpClient.sendAndWaitForResponse(request + i); 24 | if (response.equals(request + i)) { 25 | LOG.info("TEST {} OK !", i); 26 | } else { 27 | LOG.error("TEST {} FAILED !", i); 28 | } 29 | } 30 | LOG.info("done."); 31 | } 32 | 33 | public static void stopAll() { 34 | try { 35 | LOG.info("Stopping TEST UDP Client"); 36 | udpClient.close(); 37 | } catch (Exception e) { 38 | LOG.error("TEST UDP Client stop ERROR:", e); 39 | } 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /examples/proxy-server/src/main/resources/proxy-server-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "server-001", 3 | "name": "Proxy Server", 4 | "proxies": [ 5 | { 6 | "protocol": "tcp", 7 | "serverHost": "127.0.0.1", 8 | "serverPort": 20222, 9 | "targetHost": "127.0.0.1", 10 | "targetPort": 2222, 11 | "maxConnections": 2 12 | }, 13 | { 14 | "protocol": "udp", 15 | "serverHost": "127.0.0.1", 16 | "serverPort": 30333, 17 | "targetHost": "127.0.0.1", 18 | "targetPort": 3333, 19 | "maxConnections": 1 20 | } 21 | ] 22 | } -------------------------------------------------------------------------------- /examples/proxy-server/src/test/java/one/microproject/proxyserver/tests/ProxyServerTests.java: -------------------------------------------------------------------------------- 1 | package one.microproject.proxyserver.tests; 2 | 3 | import com.fasterxml.jackson.databind.DeserializationFeature; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import one.microproject.proxyserver.Main; 6 | import one.microproject.proxyserver.impl.Configuration; 7 | import one.microproject.proxyserver.impl.ProxyConfiguration; 8 | import one.microproject.proxyserver.test.TCPClient; 9 | import one.microproject.proxyserver.test.TCPServer; 10 | import one.microproject.proxyserver.test.UDPClient; 11 | import one.microproject.proxyserver.test.UDPServer; 12 | import org.testng.annotations.AfterClass; 13 | import org.testng.annotations.BeforeClass; 14 | import org.testng.annotations.Test; 15 | 16 | import java.io.IOException; 17 | import java.io.InputStream; 18 | import java.util.Optional; 19 | import java.util.concurrent.ExecutorService; 20 | import java.util.concurrent.Executors; 21 | import java.util.concurrent.TimeUnit; 22 | 23 | import static org.junit.jupiter.api.Assertions.assertEquals; 24 | 25 | public class ProxyServerTests { 26 | 27 | private ExecutorService executor; 28 | private TCPServer tcpServer; 29 | private UDPServer udpServer; 30 | private ProxyConfiguration tcpConfig; 31 | private ProxyConfiguration udpConfig; 32 | 33 | @BeforeClass 34 | public void init() throws IOException { 35 | InputStream is = ProxyServerTests.class.getClassLoader().getResourceAsStream("proxy-server-config.json"); 36 | ObjectMapper mapper = new ObjectMapper(); 37 | mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); 38 | Configuration configuration = mapper.readValue(is, Configuration.class); 39 | Main.startAll(configuration); 40 | executor = Executors.newFixedThreadPool(32); 41 | Optional tcpConfigOptional = configuration.proxies().stream().filter(p -> p.protocol().equals("tcp")).findFirst(); 42 | if (tcpConfigOptional.isEmpty()) { 43 | throw new UnsupportedOperationException("Missing TCP Configuration !"); 44 | } else { 45 | tcpConfig = tcpConfigOptional.get(); 46 | tcpServer = new TCPServer(tcpConfig.targetHost(), tcpConfig.targetPort()); 47 | executor.submit(tcpServer); 48 | } 49 | Optional udpConfigOptional = configuration.proxies().stream().filter(p -> p.protocol().equals("udp")).findFirst(); 50 | if (udpConfigOptional.isEmpty()) { 51 | throw new UnsupportedOperationException("Missing UDP Configuration !"); 52 | } else { 53 | udpConfig = udpConfigOptional.get(); 54 | udpServer = new UDPServer(udpConfig.targetHost(), udpConfig.targetPort()); 55 | executor.submit(udpServer); 56 | } 57 | } 58 | 59 | @Test 60 | public void testTCPConnection() throws IOException, InterruptedException { 61 | String request = "hi"; 62 | TCPClient tcpClient = new TCPClient(tcpConfig.serverHost(), tcpConfig.serverPort()); 63 | tcpClient.connect(); 64 | for (int i=0; i<100; i++) { 65 | String response = tcpClient.sendAndWaitForResponse(request + i); 66 | assertEquals((request + i), response); 67 | } 68 | tcpClient.close(); 69 | } 70 | 71 | @Test 72 | public void testUDPConnection() throws IOException, InterruptedException { 73 | String request = "hi"; 74 | UDPClient udpClient = new UDPClient(tcpConfig.serverHost(), tcpConfig.serverPort()); 75 | udpClient.connect(); 76 | for (int i=0; i<100; i++) { 77 | String response = udpClient.sendAndWaitForResponse(request + i); 78 | assertEquals((request + i), response); 79 | } 80 | udpClient.close(); 81 | } 82 | 83 | @AfterClass 84 | public void shutdown() throws InterruptedException { 85 | Main.stopAll(); 86 | if (tcpServer != null) { 87 | try { 88 | tcpServer.close(); 89 | } catch (Exception e) { 90 | e.printStackTrace(); 91 | } 92 | } 93 | if (udpServer != null) { 94 | try { 95 | udpServer.close(); 96 | } catch (Exception e) { 97 | e.printStackTrace(); 98 | } 99 | } 100 | if (executor != null) { 101 | executor.shutdown(); 102 | executor.awaitTermination(10, TimeUnit.SECONDS); 103 | } 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /examples/proxy-server/tools/start-proxy-server.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | CP_ROOT=../build/install/proxy-server/lib 4 | 5 | CLASSPATH="" 6 | for JAR_FILE in $(ls -1 $CP_ROOT); do 7 | CLASSPATH="${CLASSPATH}:${CP_ROOT}/${JAR_FILE}" 8 | done 9 | 10 | java -cp ${CLASSPATH} one.microproject.proxyserver.Main $1 11 | -------------------------------------------------------------------------------- /examples/proxy-server/tools/start-tcp-test-client.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | CP_ROOT=../build/install/proxy-server/lib 4 | 5 | CLASSPATH="" 6 | for JAR_FILE in $(ls -1 $CP_ROOT); do 7 | CLASSPATH="${CLASSPATH}:${CP_ROOT}/${JAR_FILE}" 8 | done 9 | 10 | java -cp ${CLASSPATH} one.microproject.proxyserver.test.TCPTestClientMain $1 $2 11 | -------------------------------------------------------------------------------- /examples/proxy-server/tools/start-tcp-test-server.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | CP_ROOT=../build/install/proxy-server/lib 4 | 5 | CLASSPATH="" 6 | for JAR_FILE in $(ls -1 $CP_ROOT); do 7 | CLASSPATH="${CLASSPATH}:${CP_ROOT}/${JAR_FILE}" 8 | done 9 | 10 | java -cp ${CLASSPATH} one.microproject.proxyserver.test.TCPServerMain $1 $2 11 | -------------------------------------------------------------------------------- /examples/proxy-server/tools/start-udp-test-client.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | CP_ROOT=../build/install/proxy-server/lib 4 | 5 | CLASSPATH="" 6 | for JAR_FILE in $(ls -1 $CP_ROOT); do 7 | CLASSPATH="${CLASSPATH}:${CP_ROOT}/${JAR_FILE}" 8 | done 9 | 10 | java -cp ${CLASSPATH} one.microproject.proxyserver.test.UDPTestClientMain $1 $2 11 | -------------------------------------------------------------------------------- /examples/proxy-server/tools/start-udp-test-server.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | CP_ROOT=../build/install/proxy-server/lib 4 | 5 | CLASSPATH="" 6 | for JAR_FILE in $(ls -1 $CP_ROOT); do 7 | CLASSPATH="${CLASSPATH}:${CP_ROOT}/${JAR_FILE}" 8 | done 9 | 10 | java -cp ${CLASSPATH} one.microproject.proxyserver.test.UDPServerMain $1 $2 11 | -------------------------------------------------------------------------------- /examples/timezone-demo/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | dependencies { 3 | testImplementation 'org.junit.jupiter:junit-jupiter:5.9.1' 4 | } -------------------------------------------------------------------------------- /examples/timezone-demo/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'timezone-demo' -------------------------------------------------------------------------------- /examples/timezone-demo/src/main/java/one/microproject/timezone/examples/TimeZoneUtils.java: -------------------------------------------------------------------------------- 1 | package one.microproject.timezone.examples; 2 | 3 | import java.time.ZoneId; 4 | import java.time.ZonedDateTime; 5 | import java.time.format.DateTimeFormatter; 6 | import java.time.format.DateTimeFormatterBuilder; 7 | import java.time.format.ResolverStyle; 8 | import java.util.Map; 9 | import java.util.TimeZone; 10 | import java.util.concurrent.ConcurrentHashMap; 11 | 12 | public class TimeZoneUtils { 13 | 14 | private static final Map DATE_TIME_FORMATTERS = new ConcurrentHashMap<>(); 15 | 16 | public static TimeZone getDefault() { 17 | return TimeZone.getDefault(); 18 | } 19 | 20 | public static ZonedDateTime convertToUtc(String timeStampPattern, String zoneId, String timestamp) { 21 | final ZonedDateTime timeStampWithAppZone = parseZonedDateTime(timestamp, timeStampPattern, getZoneId(zoneId)); 22 | return timeStampWithAppZone.withZoneSameInstant(getZoneId("UTC")); 23 | } 24 | 25 | public static ZoneId getZoneId(String timeZone) { 26 | return TimeZone.getTimeZone(timeZone).toZoneId(); 27 | } 28 | 29 | public static ZonedDateTime parseZonedDateTime(String date, String pattern, ZoneId zoneId) { 30 | return ZonedDateTime.parse(date, getFormatter(pattern, zoneId)); 31 | } 32 | 33 | public static DateTimeFormatter getFormatter(String pattern, ZoneId zoneId) { 34 | final String key = pattern + zoneId; 35 | DATE_TIME_FORMATTERS.putIfAbsent(key, createFormatter(pattern, zoneId)); 36 | return DATE_TIME_FORMATTERS.get(key); 37 | } 38 | 39 | public static DateTimeFormatter createFormatter(String pattern, ZoneId zoneId) { 40 | return new DateTimeFormatterBuilder() 41 | .parseStrict() 42 | .appendPattern(pattern) 43 | .toFormatter() 44 | .withZone(zoneId) 45 | .withResolverStyle(ResolverStyle.STRICT); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /examples/timezone-demo/src/test/java/one/microproject/timezone/examples/tests/TimeZoneUtilTests.java: -------------------------------------------------------------------------------- 1 | package one.microproject.timezone.examples.tests; 2 | 3 | import one.microproject.timezone.examples.TimeZoneUtils; 4 | import org.junit.jupiter.params.ParameterizedTest; 5 | import org.junit.jupiter.params.provider.Arguments; 6 | import org.junit.jupiter.params.provider.MethodSource; 7 | 8 | import java.time.ZonedDateTime; 9 | import java.util.stream.Stream; 10 | 11 | import static org.junit.jupiter.api.Assertions.assertEquals; 12 | import static org.junit.jupiter.api.Assertions.assertNotNull; 13 | 14 | public class TimeZoneUtilTests { 15 | 16 | private static Stream provideStringsForIsBlank() { 17 | return Stream.of( 18 | Arguments.of("uuuu-MM-dd'T'HH:mm:ss.SSS'Z'", "CET", "2022-01-01T12:00:00.000Z", "2022-01-01T11:00Z[UTC]"), 19 | Arguments.of("uuuu-MM-dd'T'HH:mm:ss.SSS'Z'", "CET", "2022-02-01T12:00:00.000Z", "2022-02-01T11:00Z[UTC]"), 20 | Arguments.of("uuuu-MM-dd'T'HH:mm:ss.SSS'Z'", "CET", "2022-03-01T12:00:00.000Z", "2022-03-01T11:00Z[UTC]"), 21 | Arguments.of("uuuu-MM-dd'T'HH:mm:ss.SSS'Z'", "CET", "2022-04-01T12:00:00.000Z", "2022-04-01T10:00Z[UTC]"), 22 | Arguments.of("uuuu-MM-dd'T'HH:mm:ss.SSS'Z'", "CET", "2022-05-01T12:00:00.000Z", "2022-05-01T10:00Z[UTC]"), 23 | Arguments.of("uuuu-MM-dd'T'HH:mm:ss.SSS'Z'", "CET", "2022-06-01T12:00:00.000Z", "2022-06-01T10:00Z[UTC]"), 24 | Arguments.of("uuuu-MM-dd'T'HH:mm:ss.SSS'Z'", "CET", "2022-07-01T12:00:00.000Z", "2022-07-01T10:00Z[UTC]"), 25 | Arguments.of("uuuu-MM-dd'T'HH:mm:ss.SSS'Z'", "CET", "2022-08-01T12:00:00.000Z", "2022-08-01T10:00Z[UTC]"), 26 | Arguments.of("uuuu-MM-dd'T'HH:mm:ss.SSS'Z'", "CET", "2022-09-01T12:00:00.000Z", "2022-09-01T10:00Z[UTC]"), 27 | Arguments.of("uuuu-MM-dd'T'HH:mm:ss.SSS'Z'", "CET", "2022-10-01T12:00:00.000Z", "2022-10-01T10:00Z[UTC]"), 28 | Arguments.of("uuuu-MM-dd'T'HH:mm:ss.SSS'Z'", "CET", "2022-11-01T12:00:00.000Z", "2022-11-01T11:00Z[UTC]"), 29 | Arguments.of("uuuu-MM-dd'T'HH:mm:ss.SSS'Z'", "CET", "2022-12-01T12:00:00.000Z", "2022-12-01T11:00Z[UTC]"), 30 | 31 | Arguments.of("uuuu-MM-dd'T'HH:mm:ss.SSS'Z'", "GMT+01", "2022-01-01T12:00:00.000Z", "2022-01-01T11:00Z[UTC]"), 32 | Arguments.of("uuuu-MM-dd'T'HH:mm:ss.SSS'Z'", "GMT+01", "2022-02-01T12:00:00.000Z", "2022-02-01T11:00Z[UTC]"), 33 | Arguments.of("uuuu-MM-dd'T'HH:mm:ss.SSS'Z'", "GMT+01", "2022-03-01T12:00:00.000Z", "2022-03-01T11:00Z[UTC]"), 34 | Arguments.of("uuuu-MM-dd'T'HH:mm:ss.SSS'Z'", "GMT+01", "2022-04-01T12:00:00.000Z", "2022-04-01T11:00Z[UTC]"), 35 | Arguments.of("uuuu-MM-dd'T'HH:mm:ss.SSS'Z'", "GMT+01", "2022-05-01T12:00:00.000Z", "2022-05-01T11:00Z[UTC]"), 36 | Arguments.of("uuuu-MM-dd'T'HH:mm:ss.SSS'Z'", "GMT+01", "2022-06-01T12:00:00.000Z", "2022-06-01T11:00Z[UTC]"), 37 | Arguments.of("uuuu-MM-dd'T'HH:mm:ss.SSS'Z'", "GMT+01", "2022-07-01T12:00:00.000Z", "2022-07-01T11:00Z[UTC]"), 38 | Arguments.of("uuuu-MM-dd'T'HH:mm:ss.SSS'Z'", "GMT+01", "2022-08-01T12:00:00.000Z", "2022-08-01T11:00Z[UTC]"), 39 | Arguments.of("uuuu-MM-dd'T'HH:mm:ss.SSS'Z'", "GMT+01", "2022-09-01T12:00:00.000Z", "2022-09-01T11:00Z[UTC]"), 40 | Arguments.of("uuuu-MM-dd'T'HH:mm:ss.SSS'Z'", "GMT+01", "2022-10-01T12:00:00.000Z", "2022-10-01T11:00Z[UTC]"), 41 | Arguments.of("uuuu-MM-dd'T'HH:mm:ss.SSS'Z'", "GMT+01", "2022-11-01T12:00:00.000Z", "2022-11-01T11:00Z[UTC]"), 42 | Arguments.of("uuuu-MM-dd'T'HH:mm:ss.SSS'Z'", "GMT+01", "2022-12-01T12:00:00.000Z", "2022-12-01T11:00Z[UTC]") 43 | ); 44 | } 45 | 46 | @ParameterizedTest 47 | @MethodSource("provideStringsForIsBlank") 48 | public void testTimeZones(String timeStampPattern, String zoneId, String timestamp, String convertedTimestamp) { 49 | ZonedDateTime zonedDateTime = TimeZoneUtils.convertToUtc(timeStampPattern, zoneId, timestamp); 50 | assertNotNull(zonedDateTime); 51 | assertEquals(convertedTimestamp, zonedDateTime.toString()); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /examples/web-server-demo/README.md: -------------------------------------------------------------------------------- 1 | 2 | ``` 3 | gradle clean build test installDist distZip 4 | ``` -------------------------------------------------------------------------------- /examples/web-server-demo/build-native.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Requires installed graalvm and gradle 3 | 4 | gradle clean build test installDist distZip 5 | 6 | JARS_DIR=build/install/web-server-demo/lib/ 7 | CLASS_PATH="" 8 | 9 | for JAR in $(ls -1 $JARS_DIR); do 10 | CLASS_PATH=${JARS_DIR}${JAR}:${CLASS_PATH} 11 | done 12 | 13 | echo "CLASS PATH: ${CLASS_PATH}" 14 | 15 | native-image --no-fallback \ 16 | -cp ${CLASS_PATH} one.microproject.webserver.Main 17 | 18 | rm one.microproject.webserver.main.build_artifacts.txt 19 | mv one.microproject.webserver.main build/distributions/web-server-demo 20 | 21 | # run binary proxy-server 22 | # build/distributions/proxy-server src/main/resources/web-server-config.json 23 | -------------------------------------------------------------------------------- /examples/web-server-demo/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | id 'application' 4 | } 5 | 6 | sourceCompatibility = JavaVersion.VERSION_17 7 | targetCompatibility = JavaVersion.VERSION_17 8 | 9 | group = 'one.microproject.webserver' 10 | version = '1.0.0' 11 | mainClassName = 'one.microproject.webserver.Main' 12 | applicationDefaultJvmArgs = ['-Xms32m', '-Xmx32m', '-XX:MaxMetaspaceSize=32m'] 13 | 14 | repositories { 15 | mavenCentral() 16 | } 17 | 18 | dependencies { 19 | implementation 'org.slf4j:slf4j-api:2.0.3' 20 | implementation 'org.slf4j:slf4j-simple:2.0.3' 21 | implementation 'io.undertow:undertow-core:2.3.0.Final' 22 | implementation 'io.undertow:undertow-websockets-jsr:2.3.0.Final' 23 | implementation 'com.fasterxml.jackson.core:jackson-core:2.14.0' 24 | implementation 'com.fasterxml.jackson.core:jackson-databind:2.14.0' 25 | implementation 'com.fasterxml.jackson.core:jackson-annotations:2.14.0' 26 | 27 | testImplementation 'org.testng:testng:7.6.1' 28 | } 29 | 30 | test { 31 | // enable TestNG support (default is JUnit) 32 | useTestNG() 33 | testLogging { 34 | events "passed", "skipped", "failed" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /examples/web-server-demo/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'web-server-demo' -------------------------------------------------------------------------------- /examples/web-server-demo/src/main/java/one/microproject/webserver/Main.java: -------------------------------------------------------------------------------- 1 | package one.microproject.webserver; 2 | 3 | import io.undertow.Undertow; 4 | import io.undertow.server.HttpHandler; 5 | import io.undertow.server.HttpServerExchange; 6 | import io.undertow.util.Headers; 7 | 8 | public class Main { 9 | 10 | public static void main(final String[] args) { 11 | Undertow server = Undertow.builder() 12 | .addHttpListener(8080, "0.0.0.0") 13 | .setHandler(new HttpHandler() { 14 | @Override 15 | public void handleRequest(final HttpServerExchange exchange) throws Exception { 16 | exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain"); 17 | exchange.getResponseSender().send("Hello World"); 18 | } 19 | }).build(); 20 | server.start(); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /examples/web-server-demo/src/main/resources/web-server-config.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | } -------------------------------------------------------------------------------- /examples/web-server-demo/src/test/java/one/microproject/webserver/tests/WebServerTests.java: -------------------------------------------------------------------------------- 1 | package one.microproject.webserver.tests; 2 | 3 | public class WebServerTests { 4 | } 5 | -------------------------------------------------------------------------------- /jep-examples/jep-358_helpful-npe/README.md: -------------------------------------------------------------------------------- 1 | # Helpful NullPointer Exceptions 2 | This demo shows how new Helpful NullPointer Exceptions is implemented. 3 | 4 | ### JEPs 5 | * [JEP 358: Helpful NullPointerExceptions](https://openjdk.java.net/jeps/358) 6 | -------------------------------------------------------------------------------- /jep-examples/jep-358_helpful-npe/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /jep-examples/jep-358_helpful-npe/src/test/java/itx/examples/helpfulnpe/tests/HelpfulNPETests.java: -------------------------------------------------------------------------------- 1 | package itx.examples.helpfulnpe.tests; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.assertEquals; 6 | import static org.junit.jupiter.api.Assertions.assertThrows; 7 | import static org.junit.jupiter.api.Assertions.assertTrue; 8 | 9 | public class HelpfulNPETests { 10 | 11 | @Test 12 | public void testNPEAssignment() { 13 | StringWrapper sw = null; 14 | NullPointerException npe = assertThrows(NullPointerException.class, () -> { 15 | sw.data = "hi"; 16 | }); 17 | assertTrue(npe instanceof NullPointerException); 18 | npe.printStackTrace(); 19 | String message = npe.getMessage(); 20 | assertEquals("Cannot assign field \"data\" because \"sw\" is null", message); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /jep-examples/jep-358_helpful-npe/src/test/java/itx/examples/helpfulnpe/tests/StringWrapper.java: -------------------------------------------------------------------------------- 1 | package itx.examples.helpfulnpe.tests; 2 | 3 | public class StringWrapper { 4 | 5 | public String data; 6 | 7 | } 8 | -------------------------------------------------------------------------------- /jep-examples/jep-378_text-blocks/README.md: -------------------------------------------------------------------------------- 1 | # Text Blocks 2 | This demo shows how Text Blocks is implemented. 3 | 4 | ### JEPs 5 | * [JEP 355: Text Blocks](https://openjdk.java.net/jeps/355) 6 | * [JEP 368: Text Blocks](https://openjdk.java.net/jeps/368) 7 | * [JEP 378: Text Blocks](https://openjdk.java.net/jeps/378) 8 | -------------------------------------------------------------------------------- /jep-examples/jep-378_text-blocks/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /jep-examples/jep-378_text-blocks/src/test/java/itx/examples/textblocks/tests/TextBlocksTest.java: -------------------------------------------------------------------------------- 1 | package itx.examples.textblocks.tests; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.assertEquals; 6 | import static org.junit.jupiter.api.Assertions.assertNotNull; 7 | 8 | public class TextBlocksTest { 9 | 10 | private static String html = 11 | """ 12 | 13 | 14 |

Hello, world

15 | 16 | 17 | """; 18 | 19 | private static String query = 20 | """ 21 | SELECT "EMP_ID", "LAST_NAME" FROM "EMPLOYEE_TB" 22 | WHERE "CITY" = 'INDIANAPOLIS' 23 | ORDER BY "EMP_ID", "LAST_NAME"; 24 | """; 25 | 26 | @Test 27 | public void testStrings() { 28 | assertNotNull(html); 29 | assertNotNull(query); 30 | assertEquals(66, html.length()); 31 | assertEquals(110, query.length()); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /jep-examples/jep-384_records/README.md: -------------------------------------------------------------------------------- 1 | # Java Records 2 | This demo shows how to use Java records, which are classes that act as transparent carriers for immutable data. 3 | 4 | ### JEPs 5 | * [JEP 359: Records](https://openjdk.java.net/jeps/359) 6 | * [JEP 384: Records](https://openjdk.java.net/jeps/384) 7 | -------------------------------------------------------------------------------- /jep-examples/jep-384_records/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /jep-examples/jep-384_records/src/main/java/itx/examples/records/DataRecord.java: -------------------------------------------------------------------------------- 1 | package itx.examples.records; 2 | 3 | public record DataRecord(RecordId id, Long timeStamp, String data) { 4 | } 5 | -------------------------------------------------------------------------------- /jep-examples/jep-384_records/src/main/java/itx/examples/records/RecordId.java: -------------------------------------------------------------------------------- 1 | package itx.examples.records; 2 | 3 | public record RecordId(String id) { 4 | } 5 | -------------------------------------------------------------------------------- /jep-examples/jep-384_records/src/test/java/itx/examples/records/tests/RecordTests.java: -------------------------------------------------------------------------------- 1 | package itx.examples.records.tests; 2 | 3 | import itx.examples.records.RecordId; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static org.junit.jupiter.api.Assertions.assertEquals; 7 | import static org.junit.jupiter.api.Assertions.assertFalse; 8 | import static org.junit.jupiter.api.Assertions.assertNotEquals; 9 | import static org.junit.jupiter.api.Assertions.assertNotNull; 10 | import static org.junit.jupiter.api.Assertions.assertTrue; 11 | 12 | public class RecordTests { 13 | 14 | @Test 15 | public void testEquals() { 16 | RecordId recordId01 = new RecordId("r-01"); 17 | RecordId recordId02 = new RecordId("r-02"); 18 | RecordId recordId03 = new RecordId("r-01"); 19 | RecordId recordId04 = new RecordId("r-01"); 20 | //reflective 21 | assertTrue(recordId01.equals(recordId01)); 22 | //symmetric 23 | assertTrue(recordId01.equals(recordId03)); 24 | assertTrue(recordId03.equals(recordId01)); 25 | //transitive 26 | assertTrue(recordId01.equals(recordId03)); 27 | assertTrue(recordId03.equals(recordId04)); 28 | assertTrue(recordId03.equals(recordId01)); 29 | //consistent 30 | assertTrue(recordId01.equals(recordId01)); 31 | assertTrue(recordId01.equals(recordId03)); 32 | //null 33 | assertFalse(recordId01.equals(null)); 34 | assertFalse(recordId02.equals(null)); 35 | assertFalse(recordId03.equals(null)); 36 | //negative 37 | assertFalse(recordId01.equals(recordId02)); 38 | assertFalse(recordId02.equals(recordId01)); 39 | } 40 | 41 | @Test 42 | public void testHashCode() { 43 | RecordId recordId01 = new RecordId("r-01"); 44 | RecordId recordId02 = new RecordId("r-02"); 45 | RecordId recordId03 = new RecordId("r-01"); 46 | assertEquals(recordId01.hashCode(), recordId01.hashCode()); 47 | assertEquals(recordId01.hashCode(), recordId03.hashCode()); 48 | assertNotEquals(recordId01.hashCode(), recordId02.hashCode()); 49 | } 50 | 51 | @Test 52 | public void testToString() { 53 | RecordId recordId = new RecordId("r-01"); 54 | assertNotNull(recordId.toString()); 55 | } 56 | 57 | @Test 58 | public void testRecordId() { 59 | RecordId recordId = new RecordId("r-01"); 60 | assertNotNull(recordId.id()); 61 | assertEquals(recordId.id(), "r-01"); 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | 2 | include "jep-384_records" 3 | project(":jep-384_records").projectDir = new File("jep-examples/jep-384_records") 4 | 5 | include "jep-358_helpful-npe" 6 | project(":jep-358_helpful-npe").projectDir = new File("jep-examples/jep-358_helpful-npe") 7 | 8 | include "jep-378_text-blocks" 9 | project(":jep-378_text-blocks").projectDir = new File("jep-examples/jep-378_text-blocks") 10 | 11 | include "proxy-server" 12 | project(":proxy-server").projectDir = new File("examples/proxy-server") 13 | 14 | include "entropy-demo" 15 | project(":entropy-demo").projectDir = new File("examples/entropy-demo") 16 | 17 | include "web-server-demo" 18 | project(":web-server-demo").projectDir = new File("examples/web-server-demo") 19 | 20 | include "jce-demo" 21 | project(":jce-demo").projectDir = new File("examples/jce-demo") 22 | 23 | include "timezone-demo" 24 | project(":timezone-demo").projectDir = new File("examples/timezone-demo") 25 | 26 | include "files-compare-demo" 27 | project(":files-compare-demo").projectDir = new File("examples/files-compare-demo") 28 | --------------------------------------------------------------------------------