├── docs ├── _config.yml ├── index.html ├── adr │ ├── 001-record-architecture-decision.md │ ├── 003-simple-interface-driving-adapters.md │ ├── 004-ioc-without-annotations.md │ ├── 002-visible-flow-of-control.md │ └── 005-strategy-pattern-for-driven-adapters.md ├── BUILD.md └── jexxa-restructured.architecture ├── .muse.toml ├── jexxa-core ├── src │ ├── test │ │ ├── resources │ │ │ ├── secrets │ │ │ │ ├── jndiUser │ │ │ │ └── jndiPassword │ │ │ ├── jexxa-secrets.properties │ │ │ ├── public │ │ │ │ └── index.html │ │ │ ├── junit-platform.properties │ │ │ ├── DeveloperStack.yaml │ │ │ ├── jexxa-application.properties │ │ │ └── simplelogger.properties │ │ └── java │ │ │ └── io │ │ │ └── jexxa │ │ │ ├── testapplication │ │ │ ├── domain │ │ │ │ └── model │ │ │ │ │ ├── JexxaRecord.java │ │ │ │ │ ├── JexxaEnum.java │ │ │ │ │ ├── JexxaDomainEvent.java │ │ │ │ │ ├── JexxaRecordComparable.java │ │ │ │ │ ├── JexxaEntityRepository.java │ │ │ │ │ ├── JexxaAggregateRepository.java │ │ │ │ │ ├── JexxaEntity.java │ │ │ │ │ ├── SpecialCasesValueObject.java │ │ │ │ │ ├── JexxaAggregate.java │ │ │ │ │ ├── JexxaValueObject.java │ │ │ │ │ └── JexxaObject.java │ │ │ ├── domainservice │ │ │ │ ├── NotUniqueService.java │ │ │ │ ├── NotImplementedService.java │ │ │ │ ├── InvalidConstructorService.java │ │ │ │ ├── InvalidPropertiesService.java │ │ │ │ ├── ValidFactoryMethodService.java │ │ │ │ ├── ValidDefaultConstructorService.java │ │ │ │ ├── InvalidConstructorParameterService.java │ │ │ │ ├── ValidPropertiesConstructorService.java │ │ │ │ ├── ValidDomainSender.java │ │ │ │ ├── BootstrapJexxaEntities.java │ │ │ │ └── BootstrapJexxaAggregates.java │ │ │ ├── infrastructure │ │ │ │ ├── drivenadapter │ │ │ │ │ ├── factory │ │ │ │ │ │ ├── NotUniqueServiceFirstImpl.java │ │ │ │ │ │ ├── NotUniqueServiceSecondImpl.java │ │ │ │ │ │ ├── InvalidConstructorServiceImpl.java │ │ │ │ │ │ ├── ValidDefaultConstructorServiceImpl.java │ │ │ │ │ │ ├── InvalidConstructorParameterServiceImpl.java │ │ │ │ │ │ ├── ValidPropertiesConstructorServiceImpl.java │ │ │ │ │ │ ├── InvalidPropertiesServiceImpl.java │ │ │ │ │ │ └── ValidFactoryMethodServiceImpl.java │ │ │ │ │ ├── persistence │ │ │ │ │ │ ├── JexxaEntityRepositoryImpl.java │ │ │ │ │ │ ├── JexxaAggregateRepositoryImpl.java │ │ │ │ │ │ └── GenericRepositoryImpl.java │ │ │ │ │ └── messaging │ │ │ │ │ │ └── ValidDomainSenderImpl.java │ │ │ │ └── drivingadapter │ │ │ │ │ ├── portadapter │ │ │ │ │ ├── PortAdapter.java │ │ │ │ │ ├── ThrowingPortAdapter.java │ │ │ │ │ ├── PortAdapterWithProperties.java │ │ │ │ │ └── ProxyPortAdapter.java │ │ │ │ │ └── generic │ │ │ │ │ ├── InvalidDrivingAdapter.java │ │ │ │ │ ├── ProxyDrivingAdapter.java │ │ │ │ │ └── ProxyAdapter.java │ │ │ ├── JexxaTestApplication.java │ │ │ ├── applicationservice │ │ │ │ ├── ApplicationServiceWithInvalidDrivenAdapters.java │ │ │ │ ├── InvalidConstructorApplicationService.java │ │ │ │ ├── JexxaApplicationService.java │ │ │ │ ├── IncrementApplicationService.java │ │ │ │ └── ApplicationServiceWithDrivenAdapters.java │ │ │ └── annotation │ │ │ │ ├── ValidApplicationService.java │ │ │ │ ├── InvalidApplicationService.java │ │ │ │ └── UnavailableDuringRuntime.java │ │ │ ├── TestConstants.java │ │ │ └── core │ │ │ ├── factory │ │ │ ├── PackageConstants.java │ │ │ └── DependencyScannerTest.java │ │ │ ├── DrivingAdapterTest.java │ │ │ ├── convention │ │ │ ├── PortConventionTest.java │ │ │ └── AdapterConventionTest.java │ │ │ ├── FluentMonitorTest.java │ │ │ └── BoundedContextTest.java │ └── main │ │ ├── java │ │ └── io │ │ │ └── jexxa │ │ │ ├── core │ │ │ ├── convention │ │ │ │ ├── AdapterConventionViolation.java │ │ │ │ ├── PortConventionViolation.java │ │ │ │ ├── PortConvention.java │ │ │ │ └── AdapterConvention.java │ │ │ ├── factory │ │ │ │ ├── ObjectPool.java │ │ │ │ ├── AmbiguousAdapterException.java │ │ │ │ ├── InvalidAdapterException.java │ │ │ │ ├── MissingAdapterException.java │ │ │ │ └── DependencyScanner.java │ │ │ ├── BootstrapService.java │ │ │ ├── VersionInfo.java │ │ │ ├── FluentMonitor.java │ │ │ ├── FluentInterceptor.java │ │ │ ├── DrivingAdapter.java │ │ │ └── BoundedContext.java │ │ │ └── properties │ │ │ └── JexxaCoreProperties.java │ │ └── java-templates │ │ └── io │ │ └── jexxa │ │ └── core │ │ └── JexxaVersion.java └── pom.xml ├── jexxa-web ├── src │ ├── test │ │ ├── resources │ │ │ ├── certificate │ │ │ │ ├── keystore.jks │ │ │ │ └── testCertificate.csr │ │ │ ├── public │ │ │ │ └── index.html │ │ │ ├── simplelogger.properties │ │ │ └── jexxa-application.properties │ │ └── java │ │ │ └── io │ │ │ └── jexxa │ │ │ └── drivingadapter │ │ │ └── rest │ │ │ ├── RESTConstants.java │ │ │ ├── UnsupportedApplicationService.java │ │ │ ├── MultipleRESTClientsIT.java │ │ │ ├── HttpsRESTfulRPCAdapterIT.java │ │ │ └── RESTfulRPCConventionTest.java │ └── main │ │ └── java │ │ └── io │ │ └── jexxa │ │ └── drivingadapter │ │ └── rest │ │ ├── JexxaWebProperties.java │ │ └── RESTfulRPCConvention.java └── pom.xml ├── jexxa-test ├── src │ ├── test │ │ ├── java │ │ │ └── io │ │ │ │ └── jexxa │ │ │ │ └── jexxatest │ │ │ │ ├── architecture │ │ │ │ ├── validapplication │ │ │ │ │ ├── ValidApplication.java │ │ │ │ │ ├── domain │ │ │ │ │ │ └── valid │ │ │ │ │ │ │ ├── ValidEnum.java │ │ │ │ │ │ │ ├── ValidValueObject.java │ │ │ │ │ │ │ ├── ValidDomainEvent.java │ │ │ │ │ │ │ ├── ValidRepository.java │ │ │ │ │ │ │ ├── ValidEntity.java │ │ │ │ │ │ │ └── ValidAggregate.java │ │ │ │ │ ├── domainservice │ │ │ │ │ │ ├── IDomainEventSender.java │ │ │ │ │ │ └── TestDomainService.java │ │ │ │ │ ├── infrastructure │ │ │ │ │ │ ├── drivenadapter │ │ │ │ │ │ │ └── jms │ │ │ │ │ │ │ │ └── JMSDrivenAdapter.java │ │ │ │ │ │ └── drivingadapter │ │ │ │ │ │ │ ├── jms │ │ │ │ │ │ │ └── JMSDrivingAdapter.java │ │ │ │ │ │ │ └── rest │ │ │ │ │ │ │ └── RESTDrivingAdapter.java │ │ │ │ │ └── applicationservice │ │ │ │ │ │ └── TestApplicationService.java │ │ │ │ ├── invalidapplication │ │ │ │ │ ├── InvalidApplication.java │ │ │ │ │ ├── domain │ │ │ │ │ │ └── invalid │ │ │ │ │ │ │ ├── InvalidValueObject.java │ │ │ │ │ │ │ ├── InvalidDomainEvent.java │ │ │ │ │ │ │ ├── InvalidRepository.java │ │ │ │ │ │ │ └── InvalidAggregate.java │ │ │ │ │ ├── infrastructure │ │ │ │ │ │ └── drivingadapter │ │ │ │ │ │ │ └── rest │ │ │ │ │ │ │ └── InvalidRESTDrivingAdapter.java │ │ │ │ │ └── applicationservice │ │ │ │ │ │ └── InvalidApplicationService.java │ │ │ │ └── ArchitectureTest.java │ │ │ │ ├── application │ │ │ │ ├── JexxaITTestApplication.java │ │ │ │ └── DomainEventPublisher.java │ │ │ │ ├── integrationtest │ │ │ │ └── messaging │ │ │ │ │ └── JMSBindingIT.java │ │ │ │ └── JexxaTestTest.java │ │ └── resources │ │ │ ├── jexxa-test.properties │ │ │ └── simplelogger.properties │ └── main │ │ └── java │ │ └── io │ │ └── jexxa │ │ └── jexxatest │ │ ├── integrationtest │ │ ├── Listener.java │ │ ├── rest │ │ │ ├── BadRequestException.java │ │ │ ├── BoundedContextHandler.java │ │ │ └── RESTBinding.java │ │ └── messaging │ │ │ ├── JMSBinding.java │ │ │ └── JMSListener.java │ │ ├── DomainEventRecorder.java │ │ ├── architecture │ │ ├── PackageName.java │ │ ├── ArchitectureRules.java │ │ ├── ProjectContent.java │ │ └── PortsAndAdapters.java │ │ ├── infrastructure │ │ └── messaging │ │ │ └── recording │ │ │ ├── RecordedMessage.java │ │ │ ├── MessageRecorderManager.java │ │ │ ├── MessageRecorder.java │ │ │ └── MessageRecordingStrategy.java │ │ └── JexxaIntegrationTest.java └── pom.xml ├── .gitignore ├── .github ├── dependabot.yml └── workflows │ ├── autoMerge.yml │ ├── mavenUnitTests.yml │ ├── validateJavaSupport.yml │ ├── maven.yml │ └── codeql-analysis.yml └── mavenVersionRules.xml /docs/_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /.muse.toml: -------------------------------------------------------------------------------- 1 | build = "mvn" 2 | jdk17 = true 3 | -------------------------------------------------------------------------------- /jexxa-core/src/test/resources/secrets/jndiUser: -------------------------------------------------------------------------------- 1 | admin -------------------------------------------------------------------------------- /jexxa-core/src/test/resources/secrets/jndiPassword: -------------------------------------------------------------------------------- 1 | simetraehcapa -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Jexxa 4 | 5 | -------------------------------------------------------------------------------- /jexxa-web/src/test/resources/certificate/keystore.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jexxa-projects/Jexxa/HEAD/jexxa-web/src/test/resources/certificate/keystore.jks -------------------------------------------------------------------------------- /jexxa-core/src/test/resources/jexxa-secrets.properties: -------------------------------------------------------------------------------- 1 | java.naming.file.password=src/test/resources/secrets/jndiPassword 2 | java.naming.file.user=src/test/resources/secrets/jndiUser 3 | -------------------------------------------------------------------------------- /jexxa-core/src/test/java/io/jexxa/testapplication/domain/model/JexxaRecord.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.testapplication.domain.model; 2 | 3 | public record JexxaRecord(String jexxaRecord) { 4 | } 5 | -------------------------------------------------------------------------------- /jexxa-core/src/test/java/io/jexxa/testapplication/domain/model/JexxaEnum.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.testapplication.domain.model; 2 | 3 | public enum JexxaEnum { 4 | ENUM_VALUE1, 5 | ENUM_VALUE2, 6 | ENUM_VALUE3 7 | } 8 | -------------------------------------------------------------------------------- /jexxa-core/src/test/java/io/jexxa/TestConstants.java: -------------------------------------------------------------------------------- 1 | package io.jexxa; 2 | 3 | public class TestConstants 4 | { 5 | public static final String UNIT_TEST = "unit-test"; 6 | public static final String INTEGRATION_TEST = "integration-test"; 7 | } 8 | -------------------------------------------------------------------------------- /jexxa-core/src/test/resources/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Test page for RESTfulRPCAdapterIT 5 | 6 | 7 |
Hello Jexxa
8 | 9 | -------------------------------------------------------------------------------- /jexxa-web/src/test/resources/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Test page for RESTfulRPCAdapterIT 5 | 6 | 7 |
Hello Jexxa
8 | 9 | -------------------------------------------------------------------------------- /jexxa-test/src/test/java/io/jexxa/jexxatest/architecture/validapplication/ValidApplication.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.jexxatest.architecture.validapplication; 2 | 3 | public class ValidApplication 4 | { 5 | //Empty clas for testing architecture/package rules 6 | } 7 | -------------------------------------------------------------------------------- /jexxa-test/src/test/java/io/jexxa/jexxatest/architecture/invalidapplication/InvalidApplication.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.jexxatest.architecture.invalidapplication; 2 | 3 | public class InvalidApplication 4 | { 5 | //Empty clas for testing architecture/package rules 6 | } 7 | -------------------------------------------------------------------------------- /jexxa-core/src/test/resources/junit-platform.properties: -------------------------------------------------------------------------------- 1 | #suppress inspection "UnusedProperty" for whole file 2 | junit.jupiter.execution.parallel.enabled=false 3 | junit.jupiter.execution.parallel.config.strategy=fixed 4 | junit.jupiter.execution.parallel.config.fixed.parallelism=4 -------------------------------------------------------------------------------- /jexxa-core/src/test/java/io/jexxa/testapplication/domainservice/NotUniqueService.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.testapplication.domainservice; 2 | 3 | public interface NotUniqueService 4 | { 5 | //Empty interface for testing different valid/invalid implementations of a domain service 6 | } 7 | -------------------------------------------------------------------------------- /jexxa-core/src/test/java/io/jexxa/testapplication/domainservice/NotImplementedService.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.testapplication.domainservice; 2 | 3 | public interface NotImplementedService 4 | { 5 | //Empty interface for testing different valid/invalid implementations of a domain service 6 | } 7 | -------------------------------------------------------------------------------- /jexxa-web/src/test/java/io/jexxa/drivingadapter/rest/RESTConstants.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.drivingadapter.rest; 2 | 3 | public class RESTConstants 4 | { 5 | public static final String CONTENT_TYPE = "Content-Type"; 6 | public static final String APPLICATION_TYPE = "application/json"; 7 | } 8 | -------------------------------------------------------------------------------- /jexxa-core/src/test/java/io/jexxa/testapplication/domainservice/InvalidConstructorService.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.testapplication.domainservice; 2 | 3 | public interface InvalidConstructorService 4 | { 5 | //Empty interface for testing different valid/invalid implementations of a domain service 6 | } 7 | -------------------------------------------------------------------------------- /jexxa-core/src/test/java/io/jexxa/testapplication/domainservice/InvalidPropertiesService.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.testapplication.domainservice; 2 | 3 | public interface InvalidPropertiesService 4 | { 5 | //Empty interface for testing different valid/invalid implementations of a domain service 6 | } 7 | -------------------------------------------------------------------------------- /jexxa-core/src/test/java/io/jexxa/testapplication/domainservice/ValidFactoryMethodService.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.testapplication.domainservice; 2 | 3 | public interface ValidFactoryMethodService 4 | { 5 | //Empty interface for testing different valid/invalid implementations of a domain service 6 | } 7 | -------------------------------------------------------------------------------- /jexxa-core/src/test/java/io/jexxa/testapplication/domainservice/ValidDefaultConstructorService.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.testapplication.domainservice; 2 | 3 | public interface ValidDefaultConstructorService 4 | { 5 | //Empty interface for testing different valid/invalid implementations of a domain service 6 | } 7 | -------------------------------------------------------------------------------- /jexxa-core/src/test/java/io/jexxa/testapplication/domainservice/InvalidConstructorParameterService.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.testapplication.domainservice; 2 | 3 | public interface InvalidConstructorParameterService 4 | { 5 | //Empty interface for testing different valid/invalid implementations of a domain service 6 | } 7 | -------------------------------------------------------------------------------- /jexxa-core/src/test/java/io/jexxa/testapplication/domainservice/ValidPropertiesConstructorService.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.testapplication.domainservice; 2 | 3 | public interface ValidPropertiesConstructorService 4 | { 5 | //Empty interface for testing different valid/invalid implementations of a domain service 6 | } 7 | -------------------------------------------------------------------------------- /jexxa-test/src/test/java/io/jexxa/jexxatest/architecture/validapplication/domain/valid/ValidEnum.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.jexxatest.architecture.validapplication.domain.valid; 2 | 3 | import io.jexxa.addend.applicationcore.ValueObject; 4 | 5 | @ValueObject 6 | public enum ValidEnum { 7 | VALUE_A, VALUE_B, VALUE_C 8 | } 9 | -------------------------------------------------------------------------------- /jexxa-test/src/test/java/io/jexxa/jexxatest/architecture/validapplication/domain/valid/ValidValueObject.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.jexxatest.architecture.validapplication.domain.valid; 2 | 3 | 4 | import io.jexxa.addend.applicationcore.ValueObject; 5 | 6 | @SuppressWarnings("unused") 7 | @ValueObject 8 | public record ValidValueObject(int value) { 9 | } 10 | -------------------------------------------------------------------------------- /jexxa-core/src/test/java/io/jexxa/testapplication/domain/model/JexxaDomainEvent.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.testapplication.domain.model; 2 | 3 | public record JexxaDomainEvent(JexxaValueObject jexxaValueObject) { 4 | 5 | public static JexxaDomainEvent create(JexxaValueObject jexxaValueObject) { 6 | return new JexxaDomainEvent(jexxaValueObject); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /jexxa-test/src/test/java/io/jexxa/jexxatest/architecture/invalidapplication/domain/invalid/InvalidValueObject.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.jexxatest.architecture.invalidapplication.domain.invalid; 2 | 3 | 4 | import io.jexxa.addend.applicationcore.ValueObject; 5 | 6 | @SuppressWarnings("unused") 7 | @ValueObject 8 | public record InvalidValueObject(int value) { 9 | } 10 | -------------------------------------------------------------------------------- /jexxa-core/src/test/java/io/jexxa/testapplication/domain/model/JexxaRecordComparable.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.testapplication.domain.model; 2 | 3 | public record JexxaRecordComparable(int number) implements Comparable { 4 | @Override 5 | public int compareTo(JexxaRecordComparable o) { 6 | return Integer.compare(number, o.number); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /jexxa-test/src/test/java/io/jexxa/jexxatest/architecture/validapplication/domain/valid/ValidDomainEvent.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.jexxatest.architecture.validapplication.domain.valid; 2 | 3 | 4 | import io.jexxa.addend.applicationcore.DomainEvent; 5 | 6 | @SuppressWarnings("unused") 7 | @DomainEvent 8 | public record ValidDomainEvent(ValidValueObject validValueObject) { 9 | } 10 | -------------------------------------------------------------------------------- /jexxa-core/src/test/java/io/jexxa/testapplication/domainservice/ValidDomainSender.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.testapplication.domainservice; 2 | 3 | import io.jexxa.testapplication.domain.model.JexxaValueObject; 4 | 5 | public interface ValidDomainSender 6 | { 7 | void sendToQueue(JexxaValueObject jexxaValueObject); 8 | 9 | void sendToTopic(JexxaValueObject jexxaValueObject); 10 | } 11 | -------------------------------------------------------------------------------- /jexxa-test/src/main/java/io/jexxa/jexxatest/integrationtest/Listener.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.jexxatest.integrationtest; 2 | 3 | import java.util.List; 4 | import java.util.concurrent.TimeUnit; 5 | 6 | public interface Listener { 7 | Listener awaitMessage(int timeout, TimeUnit timeUnit); 8 | 9 | List getAll(); 10 | 11 | T pop(Class clazz); 12 | void clear(); 13 | } 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | docs/.asciidoctor/ 2 | target/ 3 | pom.xml.tag 4 | pom.xml.releaseBackup 5 | pom.xml.versionsBackup 6 | pom.xml.next 7 | release.properties 8 | dependency-reduced-pom.xml 9 | buildNumber.properties 10 | .mvn/timing.properties 11 | # https://github.com/takari/maven-wrapper#usage-without-binary-jar 12 | .mvn/wrapper/maven-wrapper.jar 13 | .idea/ 14 | *.iml 15 | doc/*.html 16 | doc/images/*.svg 17 | teamscale* 18 | -------------------------------------------------------------------------------- /jexxa-test/src/test/resources/jexxa-test.properties: -------------------------------------------------------------------------------- 1 | io.jexxa.test.project=JexxaTest 2 | ########################################## 3 | #Settings for RESTfulRPCAdapter # 4 | ########################################## 5 | io.jexxa.rest.host=localhost 6 | io.jexxa.rest.port=7500 7 | 8 | io.jexxa.java.naming.factory.initial=org.apache.activemq.artemis.jndi.ActiveMQInitialContextFactory 9 | io.jexxa.java.naming.provider.url=tcp://localhost:61616 -------------------------------------------------------------------------------- /jexxa-core/src/test/java/io/jexxa/testapplication/infrastructure/drivenadapter/factory/NotUniqueServiceFirstImpl.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.testapplication.infrastructure.drivenadapter.factory; 2 | 3 | import io.jexxa.testapplication.domainservice.NotUniqueService; 4 | 5 | @SuppressWarnings("unused") 6 | public class NotUniqueServiceFirstImpl implements NotUniqueService 7 | { 8 | //Empty constructor to check invalid parameter handling 9 | } 10 | -------------------------------------------------------------------------------- /jexxa-core/src/test/java/io/jexxa/testapplication/infrastructure/drivenadapter/factory/NotUniqueServiceSecondImpl.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.testapplication.infrastructure.drivenadapter.factory; 2 | 3 | import io.jexxa.testapplication.domainservice.NotUniqueService; 4 | 5 | @SuppressWarnings("unused") 6 | public class NotUniqueServiceSecondImpl implements NotUniqueService 7 | { 8 | //Empty constructor to check invalid parameter handling 9 | } 10 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # Enable version updates for Gradle 4 | - package-ecosystem: "maven" 5 | # Look for `pom.xml` in the `root` directory 6 | directory: "/" 7 | # Check for updates once daily 8 | schedule: 9 | interval: "daily" 10 | - package-ecosystem: "github-actions" 11 | directory: "/" 12 | schedule: 13 | # Check for updates to GitHub Actions every week 14 | interval: "weekly" -------------------------------------------------------------------------------- /jexxa-core/src/main/java/io/jexxa/core/convention/AdapterConventionViolation.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.core.convention; 2 | 3 | import java.io.Serial; 4 | 5 | public class AdapterConventionViolation extends RuntimeException 6 | { 7 | @Serial 8 | private static final long serialVersionUID = 1L; 9 | 10 | AdapterConventionViolation(Class clazz) 11 | { 12 | super("No suitable constructor available for adapter : " + clazz.getName()); 13 | } 14 | } -------------------------------------------------------------------------------- /jexxa-test/src/main/java/io/jexxa/jexxatest/DomainEventRecorder.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.jexxatest; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public class DomainEventRecorder { 7 | private final List domainEvents = new ArrayList<>(); 8 | 9 | public void receive(T domainEvent) 10 | { 11 | domainEvents.add(domainEvent); 12 | } 13 | 14 | public List get() 15 | { 16 | return domainEvents; 17 | } 18 | } -------------------------------------------------------------------------------- /jexxa-test/src/test/java/io/jexxa/jexxatest/architecture/invalidapplication/domain/invalid/InvalidDomainEvent.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.jexxatest.architecture.invalidapplication.domain.invalid; 2 | 3 | 4 | import io.jexxa.addend.applicationcore.DomainEvent; 5 | import io.jexxa.jexxatest.architecture.validapplication.domain.valid.ValidValueObject; 6 | 7 | @DomainEvent 8 | @SuppressWarnings("unused") 9 | public record InvalidDomainEvent(ValidValueObject validValueObject) { 10 | } 11 | -------------------------------------------------------------------------------- /jexxa-core/src/test/java/io/jexxa/testapplication/JexxaTestApplication.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.testapplication; 2 | 3 | import io.jexxa.core.JexxaMain; 4 | 5 | public final class JexxaTestApplication { 6 | static void main() 7 | { 8 | var jexxaMain = new JexxaMain(JexxaTestApplication.class); 9 | 10 | jexxaMain.run(); 11 | } 12 | 13 | private JexxaTestApplication() 14 | { 15 | //Private constructor since we only offer main 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /jexxa-test/src/test/java/io/jexxa/jexxatest/architecture/validapplication/domainservice/IDomainEventSender.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.jexxatest.architecture.validapplication.domainservice; 2 | 3 | import io.jexxa.addend.applicationcore.InfrastructureService; 4 | import io.jexxa.jexxatest.architecture.validapplication.domain.valid.ValidDomainEvent; 5 | 6 | @InfrastructureService 7 | @SuppressWarnings("unused") 8 | public interface IDomainEventSender { 9 | void sendDomainEvent(ValidDomainEvent validDomainEvent); 10 | } 11 | -------------------------------------------------------------------------------- /jexxa-core/src/test/java/io/jexxa/testapplication/infrastructure/drivingadapter/portadapter/PortAdapter.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.testapplication.infrastructure.drivingadapter.portadapter; 2 | 3 | import io.jexxa.testapplication.applicationservice.ApplicationServiceWithDrivenAdapters; 4 | 5 | public record PortAdapter(ApplicationServiceWithDrivenAdapters applicationServiceWithDrivenAdapters) 6 | { 7 | public ApplicationServiceWithDrivenAdapters getPort() { 8 | return applicationServiceWithDrivenAdapters; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /jexxa-test/src/main/java/io/jexxa/jexxatest/integrationtest/rest/BadRequestException.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.jexxatest.integrationtest.rest; 2 | 3 | import kong.unirest.HttpResponse; 4 | 5 | import java.io.Serial; 6 | 7 | public class BadRequestException extends RuntimeException 8 | { 9 | @Serial 10 | private static final long serialVersionUID = 1L; 11 | 12 | public BadRequestException(HttpResponse httpResponse) { 13 | super("Unknown HTTP URL : " + httpResponse.getRequestSummary().getUrl()); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /jexxa-test/src/test/java/io/jexxa/jexxatest/architecture/validapplication/domain/valid/ValidRepository.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.jexxatest.architecture.validapplication.domain.valid; 2 | 3 | 4 | import io.jexxa.addend.applicationcore.Repository; 5 | 6 | import java.util.List; 7 | 8 | @Repository 9 | @SuppressWarnings("unused") 10 | public interface ValidRepository 11 | { 12 | void add(ValidAggregate validAggregate); 13 | ValidAggregate get(ValidValueObject validValueObject); 14 | 15 | List get(); 16 | } 17 | -------------------------------------------------------------------------------- /jexxa-core/src/test/java/io/jexxa/testapplication/infrastructure/drivenadapter/factory/InvalidConstructorServiceImpl.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.testapplication.infrastructure.drivenadapter.factory; 2 | 3 | 4 | import io.jexxa.testapplication.domainservice.InvalidConstructorService; 5 | 6 | @SuppressWarnings("unused") 7 | public class InvalidConstructorServiceImpl implements InvalidConstructorService 8 | { 9 | InvalidConstructorServiceImpl() 10 | { 11 | //Invalid Adapter because constructor is package private 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /jexxa-core/src/test/java/io/jexxa/testapplication/infrastructure/drivenadapter/factory/ValidDefaultConstructorServiceImpl.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.testapplication.infrastructure.drivenadapter.factory; 2 | 3 | 4 | import io.jexxa.testapplication.domainservice.ValidDefaultConstructorService; 5 | 6 | /** 7 | * Simulate a valid driven adapter with default constructor 8 | */ 9 | public class ValidDefaultConstructorServiceImpl implements ValidDefaultConstructorService 10 | { 11 | //Empty constructor to check invalid parameter handling 12 | } 13 | -------------------------------------------------------------------------------- /jexxa-test/src/test/java/io/jexxa/jexxatest/architecture/invalidapplication/domain/invalid/InvalidRepository.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.jexxatest.architecture.invalidapplication.domain.invalid; 2 | 3 | 4 | import io.jexxa.addend.applicationcore.Repository; 5 | 6 | import java.util.List; 7 | 8 | @SuppressWarnings("unused") 9 | @Repository 10 | public interface InvalidRepository 11 | { 12 | void add(InvalidAggregate invalidAggregate); 13 | InvalidAggregate get(InvalidValueObject invalidValueObject); 14 | List get(); 15 | } 16 | -------------------------------------------------------------------------------- /jexxa-core/src/test/java/io/jexxa/testapplication/applicationservice/ApplicationServiceWithInvalidDrivenAdapters.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.testapplication.applicationservice; 2 | 3 | import io.jexxa.testapplication.domainservice.InvalidConstructorService; 4 | 5 | import java.util.Objects; 6 | 7 | public class ApplicationServiceWithInvalidDrivenAdapters 8 | { 9 | public ApplicationServiceWithInvalidDrivenAdapters(InvalidConstructorService invalidConstructorService) 10 | { 11 | Objects.requireNonNull(invalidConstructorService); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.github/workflows/autoMerge.yml: -------------------------------------------------------------------------------- 1 | name: Merge me! 2 | 3 | on: 4 | workflow_run: 5 | types: 6 | - completed 7 | workflows: 8 | - 'Java Support Matrix' 9 | 10 | jobs: 11 | merge-me: 12 | name: Merge me! 13 | 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: Merge me! 18 | if: ${{ github.event.workflow_run.conclusion == 'success' }} 19 | uses: ridedott/merge-me-action@5c0b670c1da5e606fa66c0834bdeedb7a8c41f5d 20 | with: 21 | GITHUB_TOKEN: ${{ secrets.MERGE_ME_SECRET }} 22 | PRESET: DEPENDABOT_MINOR 23 | -------------------------------------------------------------------------------- /jexxa-core/src/test/java/io/jexxa/testapplication/applicationservice/InvalidConstructorApplicationService.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.testapplication.applicationservice; 2 | 3 | import io.jexxa.testapplication.annotation.InvalidApplicationService; 4 | 5 | @InvalidApplicationService 6 | public class InvalidConstructorApplicationService 7 | { 8 | /** 9 | * Does not provide a public constructor for testing violation of conventions 10 | */ 11 | InvalidConstructorApplicationService() 12 | { 13 | //Empty constructor since class is for testing purpose only 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /jexxa-core/src/test/java/io/jexxa/testapplication/infrastructure/drivingadapter/portadapter/ThrowingPortAdapter.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.testapplication.infrastructure.drivingadapter.portadapter; 2 | 3 | import io.jexxa.testapplication.applicationservice.SimpleApplicationService; 4 | 5 | import java.util.Objects; 6 | 7 | public class ThrowingPortAdapter { 8 | public ThrowingPortAdapter(SimpleApplicationService simpleApplicationService) 9 | { 10 | Objects.requireNonNull(simpleApplicationService); 11 | throw new IllegalStateException("Illegal State"); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /jexxa-core/src/test/java/io/jexxa/testapplication/annotation/ValidApplicationService.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.testapplication.annotation; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.Target; 6 | 7 | import static java.lang.annotation.ElementType.TYPE; 8 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 9 | 10 | /** 11 | * Represents an ApplicationService in tests 12 | * 13 | */ 14 | @Target(TYPE) 15 | @Retention(RUNTIME) 16 | @Documented 17 | public @interface ValidApplicationService 18 | { 19 | 20 | } 21 | -------------------------------------------------------------------------------- /jexxa-test/src/test/java/io/jexxa/jexxatest/architecture/validapplication/infrastructure/drivenadapter/jms/JMSDrivenAdapter.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.jexxatest.architecture.validapplication.infrastructure.drivenadapter.jms; 2 | 3 | 4 | import io.jexxa.addend.infrastructure.DrivenAdapter; 5 | import io.jexxa.jexxatest.architecture.validapplication.domain.valid.ValidValueObject; 6 | 7 | @DrivenAdapter 8 | @SuppressWarnings("unused") 9 | public class JMSDrivenAdapter 10 | { 11 | JMSDrivenAdapter(){ 12 | ValidValueObject validValueObject= new ValidValueObject(42); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /jexxa-test/src/test/java/io/jexxa/jexxatest/architecture/validapplication/infrastructure/drivingadapter/jms/JMSDrivingAdapter.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.jexxatest.architecture.validapplication.infrastructure.drivingadapter.jms; 2 | 3 | 4 | import io.jexxa.addend.infrastructure.DrivingAdapter; 5 | import io.jexxa.jexxatest.architecture.validapplication.domain.valid.ValidValueObject; 6 | 7 | @DrivingAdapter 8 | @SuppressWarnings("unused") 9 | public class JMSDrivingAdapter 10 | { 11 | JMSDrivingAdapter(){ 12 | ValidValueObject validValueObject= new ValidValueObject(42); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /docs/adr/001-record-architecture-decision.md: -------------------------------------------------------------------------------- 1 | # 1. Record architecture decisions 2 | 3 | Date: 2021-12-18 4 | 5 | ## Status 6 | 7 | Accepted 8 | 9 | ## Context 10 | 11 | We need to record the architectural decisions made on this project. Changes in the upcoming major release 4.0.0 should 12 | be much easier to comprehend. 13 | 14 | ## Decision 15 | 16 | We will use Architecture Decision Records, as described by Michael Nygard in this article: http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions 17 | 18 | ## Consequences 19 | 20 | See Michael Nygard's article, linked above. -------------------------------------------------------------------------------- /jexxa-core/src/test/java/io/jexxa/testapplication/domain/model/JexxaEntityRepository.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.testapplication.domain.model; 2 | 3 | import java.util.List; 4 | import java.util.Optional; 5 | 6 | @SuppressWarnings("unused") 7 | public interface JexxaEntityRepository 8 | { 9 | void add(JexxaEntity jexxaEntity); 10 | JexxaEntity get(JexxaValueObject aggregateID); 11 | Optional find(JexxaValueObject aggregateID); 12 | List get(); 13 | void update(JexxaEntity aggregate); 14 | void remove(JexxaEntity aggregate); 15 | void removeAll(); 16 | } 17 | -------------------------------------------------------------------------------- /jexxa-core/src/test/java/io/jexxa/testapplication/infrastructure/drivenadapter/factory/InvalidConstructorParameterServiceImpl.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.testapplication.infrastructure.drivenadapter.factory; 2 | 3 | import io.jexxa.testapplication.domainservice.InvalidConstructorParameterService; 4 | 5 | @SuppressWarnings("unused") 6 | public class InvalidConstructorParameterServiceImpl implements InvalidConstructorParameterService 7 | { 8 | public InvalidConstructorParameterServiceImpl(Integer integer) 9 | { 10 | //Empty constructor to check invalid parameter handling 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /jexxa-core/src/test/java/io/jexxa/testapplication/annotation/InvalidApplicationService.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.testapplication.annotation; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.Target; 6 | 7 | import static java.lang.annotation.ElementType.TYPE; 8 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 9 | 10 | /** 11 | * Represents an ApplicationService which is invalid for testing purpose 12 | * 13 | */ 14 | @Target(TYPE) 15 | @Retention(RUNTIME) 16 | @Documented 17 | public @interface InvalidApplicationService 18 | { 19 | 20 | } 21 | -------------------------------------------------------------------------------- /jexxa-core/src/test/java/io/jexxa/testapplication/domain/model/JexxaAggregateRepository.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.testapplication.domain.model; 2 | 3 | import java.util.List; 4 | import java.util.Optional; 5 | 6 | @SuppressWarnings("unused") 7 | public interface JexxaAggregateRepository 8 | { 9 | void add(JexxaAggregate jexxaEntity); 10 | JexxaAggregate get(JexxaValueObject aggregateID); 11 | Optional find(JexxaValueObject aggregateID); 12 | List get(); 13 | void update(JexxaAggregate aggregate); 14 | void remove(JexxaAggregate aggregate); 15 | void removeAll(); 16 | } 17 | -------------------------------------------------------------------------------- /jexxa-core/src/test/java/io/jexxa/testapplication/infrastructure/drivingadapter/portadapter/PortAdapterWithProperties.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.testapplication.infrastructure.drivingadapter.portadapter; 2 | 3 | import io.jexxa.testapplication.applicationservice.ApplicationServiceWithDrivenAdapters; 4 | 5 | import java.util.Properties; 6 | 7 | public record PortAdapterWithProperties(ApplicationServiceWithDrivenAdapters applicationServiceWithDrivenAdapters, Properties properties) 8 | { 9 | public ApplicationServiceWithDrivenAdapters getPort() { 10 | return applicationServiceWithDrivenAdapters; 11 | } 12 | } 13 | 14 | -------------------------------------------------------------------------------- /jexxa-core/src/test/java/io/jexxa/testapplication/annotation/UnavailableDuringRuntime.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.testapplication.annotation; 2 | 3 | 4 | import java.lang.annotation.Documented; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.Target; 7 | 8 | import static java.lang.annotation.ElementType.TYPE; 9 | import static java.lang.annotation.RetentionPolicy.SOURCE; 10 | 11 | @Target(TYPE) 12 | @Retention(SOURCE) 13 | @Documented 14 | /* 15 | * Annotation that is not available during runtime. This is for testing purpose only 16 | */ 17 | public @interface UnavailableDuringRuntime 18 | { 19 | 20 | } 21 | -------------------------------------------------------------------------------- /mavenVersionRules.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | .*-beta. 7 | .*_ALPHA 8 | .*alpha.* 9 | .*jre.* 10 | .*-rc.* 11 | .*-RC.* 12 | 13 | -------------------------------------------------------------------------------- /jexxa-core/src/test/java/io/jexxa/testapplication/applicationservice/JexxaApplicationService.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.testapplication.applicationservice; 2 | 3 | import io.jexxa.testapplication.domain.model.JexxaEntityRepository; 4 | 5 | public class JexxaApplicationService 6 | { 7 | private final JexxaEntityRepository jexxaAggregateRepository; 8 | 9 | public JexxaApplicationService(JexxaEntityRepository jexxaAggregateRepository) 10 | { 11 | this.jexxaAggregateRepository = jexxaAggregateRepository; 12 | } 13 | 14 | public int getAggregateCount() 15 | { 16 | return jexxaAggregateRepository.get().size(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /jexxa-test/src/test/java/io/jexxa/jexxatest/architecture/validapplication/domain/valid/ValidEntity.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.jexxatest.architecture.validapplication.domain.valid; 2 | 3 | import io.jexxa.addend.applicationcore.Aggregate; 4 | import io.jexxa.addend.applicationcore.AggregateID; 5 | 6 | @Aggregate 7 | @SuppressWarnings("unused") 8 | public class ValidEntity { 9 | private final ValidValueObject aggregateKey; 10 | 11 | public ValidEntity(ValidValueObject aggregateKey) 12 | { 13 | this.aggregateKey = aggregateKey; 14 | } 15 | 16 | @AggregateID 17 | public ValidValueObject getAggregateKey() 18 | { 19 | return aggregateKey; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /jexxa-test/src/test/java/io/jexxa/jexxatest/architecture/invalidapplication/domain/invalid/InvalidAggregate.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.jexxatest.architecture.invalidapplication.domain.invalid; 2 | 3 | import io.jexxa.addend.applicationcore.Aggregate; 4 | import io.jexxa.jexxatest.architecture.validapplication.applicationservice.TestApplicationService; 5 | 6 | @SuppressWarnings("unused") 7 | @Aggregate 8 | public class InvalidAggregate { 9 | @SuppressWarnings({"FieldCanBeLocal", "unused"}) 10 | private final TestApplicationService layerViolation; 11 | 12 | public InvalidAggregate(TestApplicationService layerViolation) 13 | { 14 | this.layerViolation = layerViolation; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /jexxa-core/src/test/java/io/jexxa/testapplication/infrastructure/drivenadapter/factory/ValidPropertiesConstructorServiceImpl.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.testapplication.infrastructure.drivenadapter.factory; 2 | 3 | import io.jexxa.testapplication.domainservice.ValidPropertiesConstructorService; 4 | 5 | import java.util.Objects; 6 | import java.util.Properties; 7 | 8 | /** 9 | * Simulate a valid driven adapter with constructor expecting a Properties 10 | */ 11 | public class ValidPropertiesConstructorServiceImpl implements ValidPropertiesConstructorService 12 | { 13 | public ValidPropertiesConstructorServiceImpl(Properties properties) 14 | { 15 | Objects.requireNonNull(properties); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /jexxa-core/src/test/java/io/jexxa/core/factory/PackageConstants.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.core.factory; 2 | 3 | import io.jexxa.testapplication.JexxaTestApplication; 4 | 5 | class PackageConstants { 6 | 7 | public static final String JEXXA_APPLICATION_SERVICE = JexxaTestApplication.class.getPackageName() + ".applicationservice"; 8 | public static final String JEXXA_DOMAIN_SERVICE = JexxaTestApplication.class.getPackageName() + ".domainservice"; 9 | public static final String JEXXA_DRIVEN_ADAPTER = JexxaTestApplication.class.getPackageName() + ".infrastructure.drivenadapter"; 10 | public static final String JEXXA_DRIVING_ADAPTER = JexxaTestApplication.class.getPackageName() + ".infrastructure.drivingadapter"; 11 | } 12 | -------------------------------------------------------------------------------- /jexxa-core/src/main/java/io/jexxa/core/factory/ObjectPool.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.core.factory; 2 | 3 | import java.util.HashSet; 4 | import java.util.Optional; 5 | import java.util.Set; 6 | 7 | class ObjectPool 8 | { 9 | private final Set objectSet = new HashSet<>(); 10 | 11 | void add(Object object) 12 | { 13 | objectSet.add(object); 14 | } 15 | 16 | Optional getInstance(Class clazz) 17 | { 18 | return objectSet.stream() 19 | .filter( element -> clazz.isAssignableFrom(element.getClass())) 20 | .findFirst() 21 | .map(clazz::cast); 22 | } 23 | 24 | void clear() 25 | { 26 | objectSet.clear(); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /jexxa-core/src/test/java/io/jexxa/testapplication/infrastructure/drivenadapter/persistence/JexxaEntityRepositoryImpl.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.testapplication.infrastructure.drivenadapter.persistence; 2 | 3 | import io.jexxa.testapplication.domain.model.JexxaEntity; 4 | import io.jexxa.testapplication.domain.model.JexxaEntityRepository; 5 | import io.jexxa.testapplication.domain.model.JexxaValueObject; 6 | 7 | import java.util.Properties; 8 | 9 | public final class JexxaEntityRepositoryImpl 10 | extends GenericRepositoryImpl 11 | implements JexxaEntityRepository 12 | { 13 | public JexxaEntityRepositoryImpl(Properties properties) 14 | { 15 | super(JexxaEntity.class, JexxaEntity::getKey, properties); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /jexxa-test/src/test/java/io/jexxa/jexxatest/architecture/invalidapplication/infrastructure/drivingadapter/rest/InvalidRESTDrivingAdapter.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.jexxatest.architecture.invalidapplication.infrastructure.drivingadapter.rest; 2 | 3 | import io.jexxa.addend.infrastructure.DrivingAdapter; 4 | import io.jexxa.jexxatest.architecture.invalidapplication.domain.invalid.InvalidRepository; 5 | 6 | @SuppressWarnings("unused") 7 | @DrivingAdapter 8 | public class InvalidRESTDrivingAdapter { 9 | @SuppressWarnings({"FieldCanBeLocal", "unused"}) 10 | private final InvalidRepository invalidRepository; 11 | 12 | public InvalidRESTDrivingAdapter(InvalidRepository invalidRepository) 13 | { 14 | this.invalidRepository = invalidRepository; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /jexxa-test/src/test/java/io/jexxa/jexxatest/architecture/validapplication/domainservice/TestDomainService.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.jexxatest.architecture.validapplication.domainservice; 2 | 3 | import io.jexxa.addend.applicationcore.DomainService; 4 | import io.jexxa.jexxatest.architecture.validapplication.domain.valid.ValidEnum; 5 | 6 | @SuppressWarnings("unused") 7 | @DomainService 8 | public class TestDomainService 9 | { 10 | //Using enum in a switch statement creates anonymous class 11 | public ValidEnum useEnum(ValidEnum validEnum) 12 | { 13 | return switch (validEnum) { 14 | case VALUE_B -> ValidEnum.VALUE_B; 15 | case VALUE_C -> ValidEnum.VALUE_C; 16 | default -> ValidEnum.VALUE_A; 17 | }; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /jexxa-core/src/test/resources/DeveloperStack.yaml: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | 3 | services: 4 | ActiveMQ: 5 | image: quay.io/artemiscloud/activemq-artemis-broker:latest 6 | restart: always 7 | ports: 8 | - "61616:61616" 9 | - "8161:8161" 10 | environment: 11 | AMQ_USER: admin 12 | AMQ_PASSWORD: admin 13 | 14 | Postgres: 15 | image: postgres:latest 16 | restart: always 17 | volumes: 18 | - postgres-data:/var/lib/postgresql/data 19 | ports: 20 | - "5432:5432" 21 | environment: 22 | POSTGRES_PASSWORD: admin 23 | POSTGRES_USER: postgres 24 | 25 | SwaggerUI: 26 | image: swaggerapi/swagger-ui:latest 27 | restart: always 28 | ports: 29 | - '8080:8080' 30 | 31 | volumes: 32 | postgres-data: 33 | -------------------------------------------------------------------------------- /jexxa-test/src/test/java/io/jexxa/jexxatest/architecture/validapplication/infrastructure/drivingadapter/rest/RESTDrivingAdapter.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.jexxatest.architecture.validapplication.infrastructure.drivingadapter.rest; 2 | 3 | import io.jexxa.addend.infrastructure.DrivingAdapter; 4 | import io.jexxa.jexxatest.architecture.validapplication.applicationservice.TestApplicationService; 5 | 6 | @SuppressWarnings("unused") 7 | @DrivingAdapter 8 | public class RESTDrivingAdapter { 9 | @SuppressWarnings({"FieldCanBeLocal", "unused"}) 10 | private final TestApplicationService testApplicationService; 11 | 12 | public RESTDrivingAdapter(TestApplicationService testApplicationService) 13 | { 14 | this.testApplicationService = testApplicationService; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /jexxa-core/src/test/java/io/jexxa/testapplication/infrastructure/drivenadapter/factory/InvalidPropertiesServiceImpl.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.testapplication.infrastructure.drivenadapter.factory; 2 | 3 | import io.jexxa.testapplication.domainservice.InvalidPropertiesService; 4 | 5 | import java.util.Objects; 6 | import java.util.Properties; 7 | 8 | /** 9 | * Throws an IllegalArgumentException in constructor to simulate invalid properties 10 | */ 11 | @SuppressWarnings("unused") 12 | public class InvalidPropertiesServiceImpl implements InvalidPropertiesService 13 | { 14 | public InvalidPropertiesServiceImpl(Properties properties) 15 | { 16 | Objects.requireNonNull(properties); 17 | throw new IllegalArgumentException("InvalidAdapterProperties test"); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /jexxa-web/src/test/java/io/jexxa/drivingadapter/rest/UnsupportedApplicationService.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.drivingadapter.rest; 2 | 3 | import io.jexxa.testapplication.domain.model.JexxaValueObject; 4 | 5 | /* 6 | * This service is not available via RESTfulRPC because method setSimpleValueObject is available twice 7 | */ 8 | @SuppressWarnings("unused") 9 | class UnsupportedApplicationService 10 | { 11 | private JexxaValueObject first; 12 | 13 | public void setSimpleValueObject(JexxaValueObject simpleValueObject) 14 | { 15 | this.first = simpleValueObject; 16 | } 17 | public void setSimpleValueObject(JexxaValueObject first, JexxaValueObject second) 18 | { 19 | this.first = new JexxaValueObject(first.getValue() * second.getValue()); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /jexxa-test/src/main/java/io/jexxa/jexxatest/architecture/PackageName.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.jexxatest.architecture; 2 | 3 | /** 4 | * This class defines the package names to validate the onion architecture. 5 | *

6 | * In case you use another package structure for your application, please adjust these packages accordingly. 7 | */ 8 | final class PackageName 9 | { 10 | public static final String APPLICATIONSERVICE = "..applicationservice.."; 11 | public static final String DOMAIN_SERVICE = "..domainservice.."; 12 | public static final String DOMAIN = "..domain.."; 13 | public static final String DRIVEN_ADAPTER = "..drivenadapter.."; 14 | public static final String DRIVING_ADAPTER = "..drivingadapter.."; 15 | 16 | private PackageName() 17 | { 18 | //Private Constructor 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /jexxa-core/src/test/java/io/jexxa/testapplication/infrastructure/drivenadapter/persistence/JexxaAggregateRepositoryImpl.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.testapplication.infrastructure.drivenadapter.persistence; 2 | 3 | import io.jexxa.testapplication.domain.model.JexxaAggregate; 4 | import io.jexxa.testapplication.domain.model.JexxaAggregateRepository; 5 | import io.jexxa.testapplication.domain.model.JexxaValueObject; 6 | 7 | import java.util.Properties; 8 | 9 | @SuppressWarnings({"unsused"}) 10 | public final class JexxaAggregateRepositoryImpl 11 | extends GenericRepositoryImpl 12 | implements JexxaAggregateRepository 13 | { 14 | public JexxaAggregateRepositoryImpl(Properties properties) 15 | { 16 | super(JexxaAggregate.class, JexxaAggregate::getKey, properties); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /jexxa-core/src/main/java-templates/io/jexxa/core/JexxaVersion.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.core; 2 | 3 | final class JexxaVersion 4 | { 5 | public static final String VERSION = "${project.version}"; 6 | public static final String REPOSITORY = "${project.scm.developerConnection}"; 7 | public static final String PROJECT_NAME= "${project.name}"; 8 | public static final String BUILD_TIMESTAMP= "${build.timestamp}"; 9 | 10 | private JexxaVersion() 11 | { 12 | //private constructor 13 | } 14 | 15 | public static VersionInfo getJexxaVersion() 16 | { 17 | return VersionInfo.of() 18 | .version(VERSION) 19 | .repository(REPOSITORY) 20 | .buildTimestamp(BUILD_TIMESTAMP) 21 | .projectName(PROJECT_NAME) 22 | .create(); 23 | } 24 | } -------------------------------------------------------------------------------- /jexxa-test/src/main/java/io/jexxa/jexxatest/architecture/ArchitectureRules.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.jexxatest.architecture; 2 | 3 | /** 4 | * This class provides methods to validate the architecture of your application. 5 | */ 6 | @SuppressWarnings("unused") 7 | public final class ArchitectureRules { 8 | public static PortsAndAdapters portsAndAdapters(Class project) 9 | { 10 | return new PortsAndAdapters(project); 11 | } 12 | 13 | public static PatternLanguage patternLanguage(Class project) 14 | { 15 | return new PatternLanguage(project); 16 | } 17 | 18 | public static AggregateRules aggregateRules(Class project) 19 | { 20 | return new AggregateRules(project); 21 | } 22 | 23 | private ArchitectureRules() 24 | { 25 | //Empty private constructor 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /jexxa-test/src/main/java/io/jexxa/jexxatest/infrastructure/messaging/recording/RecordedMessage.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.jexxatest.infrastructure.messaging.recording; 2 | 3 | import io.jexxa.common.drivenadapter.messaging.DestinationType; 4 | import io.jexxa.common.drivenadapter.messaging.MessageSender; 5 | 6 | import java.util.Properties; 7 | 8 | /** 9 | * Stores a message that is sent via JMS messaging API 10 | */ 11 | public record RecordedMessage(Object message, 12 | String serializedMessage, 13 | DestinationType destinationType, 14 | String destinationName, 15 | Properties messageProperties, 16 | MessageSender.MessageType messageType) 17 | { 18 | public T getMessage(Class type) { 19 | return type.cast(message()); 20 | } 21 | } 22 | 23 | -------------------------------------------------------------------------------- /.github/workflows/mavenUnitTests.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven to ensure that no Unit-Test requires an infrastructure in some way 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven 3 | 4 | 5 | name: Java 25 LTS (Unit-Tests only) 6 | 7 | on: 8 | push: 9 | workflow_dispatch: 10 | 11 | jobs: 12 | build: 13 | 14 | runs-on: ubuntu-latest 15 | strategy: 16 | matrix: 17 | # test against LTS Java version: 18 | java: [ '25' ] 19 | 20 | steps: 21 | - uses: actions/checkout@v6 22 | - name: Set up JDK ${{ matrix.java }} 23 | uses: actions/setup-java@v5 24 | with: 25 | distribution: 'temurin' # See 'Supported distributions' for available options 26 | java-version: ${{ matrix.java }} 27 | 28 | - name: Maven build 29 | run: mvn -DskipITs -B clean install 30 | -------------------------------------------------------------------------------- /jexxa-core/src/test/java/io/jexxa/testapplication/domainservice/BootstrapJexxaEntities.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.testapplication.domainservice; 2 | 3 | import io.jexxa.testapplication.domain.model.JexxaEntity; 4 | import io.jexxa.testapplication.domain.model.JexxaEntityRepository; 5 | import io.jexxa.testapplication.domain.model.JexxaValueObject; 6 | 7 | import java.util.stream.IntStream; 8 | 9 | public record BootstrapJexxaEntities(JexxaEntityRepository jexxaEntityRepository) { 10 | 11 | public void initDomainData() { 12 | IntStream.rangeClosed(1, 100) 13 | .boxed() 14 | .forEach(element -> addIfNotAvailable(new JexxaValueObject(element))); 15 | } 16 | 17 | private void addIfNotAvailable(JexxaValueObject aggregateID) { 18 | if (jexxaEntityRepository.find(aggregateID).isEmpty()) { 19 | jexxaEntityRepository.add(JexxaEntity.create(aggregateID)); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /jexxa-core/src/test/java/io/jexxa/testapplication/domainservice/BootstrapJexxaAggregates.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.testapplication.domainservice; 2 | 3 | import io.jexxa.testapplication.domain.model.JexxaAggregate; 4 | import io.jexxa.testapplication.domain.model.JexxaAggregateRepository; 5 | import io.jexxa.testapplication.domain.model.JexxaValueObject; 6 | 7 | import java.util.stream.IntStream; 8 | 9 | public record BootstrapJexxaAggregates(JexxaAggregateRepository jexxaAggregateRepository) { 10 | 11 | public void initDomainData() { 12 | IntStream.rangeClosed(1, 100) 13 | .boxed() 14 | .forEach(element -> addIfNotAvailable(new JexxaValueObject(element))); 15 | } 16 | 17 | private void addIfNotAvailable(JexxaValueObject aggregateID) { 18 | if (jexxaAggregateRepository.find(aggregateID).isEmpty()) { 19 | jexxaAggregateRepository.add(JexxaAggregate.create(aggregateID)); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /jexxa-core/src/test/java/io/jexxa/testapplication/applicationservice/IncrementApplicationService.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.testapplication.applicationservice; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public class IncrementApplicationService 7 | { 8 | private int counter = 0; 9 | private int maxCounter = 1000; //some meaningful value 10 | private final List usedCounter = new ArrayList<>(); 11 | 12 | @SuppressWarnings("unused") 13 | public void increment() 14 | { 15 | if ( counter < maxCounter ) 16 | { 17 | ++counter; 18 | usedCounter.add(counter); 19 | } 20 | } 21 | 22 | public void setMaxCounter(int maxCounter) 23 | { 24 | this.maxCounter = maxCounter; 25 | } 26 | 27 | public int getCounter() 28 | { 29 | return counter; 30 | } 31 | 32 | public List getUsedCounter() 33 | { 34 | return usedCounter; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /docs/adr/003-simple-interface-driving-adapters.md: -------------------------------------------------------------------------------- 1 | # 3. Simple Interface for Driving Adapters 2 | 3 | Date: 2021-12-23 4 | 5 | ## Status 6 | 7 | Accepted 8 | 9 | ## Context 10 | One of the key aspects for durable software systems is the ability to use arbitrary technology stacks which do not exist 11 | at the point in time the application was developed. Jexxa provides a super simple API that allows for the integration of 12 | arbitrary technology stacks as driving adapters. 13 | 14 | ## Decision 15 | 16 | We will use Architecture Decision Records, as described by Michael Nygard in this article: http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions 17 | 18 | ## Consequences 19 | 20 | Students can support your teams with the evaluation and integration of new technology stacks as part of their bachelor or master thesis. 21 | 22 | The possibility to bind driving adapter on an object level allows for the integration and migration of dedicated technology stacks. 23 | 24 | 25 | -------------------------------------------------------------------------------- /jexxa-core/src/main/java/io/jexxa/core/convention/PortConventionViolation.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.core.convention; 2 | 3 | import java.io.Serial; 4 | 5 | public class PortConventionViolation extends RuntimeException 6 | { 7 | @Serial 8 | private static final long serialVersionUID = 1L; 9 | 10 | PortConventionViolation(String message) 11 | { 12 | super(message); 13 | } 14 | PortConventionViolation(Class clazz) 15 | { 16 | super("Public constructor of " + clazz.getName() + " is invalid. \n" + 17 | "In case of an inbound port, all attributes of the constructor must be java-interfaces.\n" + 18 | "In case of a port adapter, the constructor must (1) take a single attribute representing an inbound port and optionally a Properties object and (2) the package must be part of the infrastructure of Jexxa.\n" + 19 | "For more information please refer to https://jexxa-projects.github.io/Jexxa/jexxa_architecture.html#_dependency_injection_di ."); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /jexxa-core/src/test/java/io/jexxa/testapplication/infrastructure/drivingadapter/portadapter/ProxyPortAdapter.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.testapplication.infrastructure.drivingadapter.portadapter; 2 | 3 | import io.jexxa.testapplication.applicationservice.SimpleApplicationService; 4 | 5 | import java.util.Objects; 6 | import java.util.concurrent.atomic.AtomicInteger; 7 | 8 | public class ProxyPortAdapter 9 | { 10 | private static final AtomicInteger INSTANCE_COUNT = new AtomicInteger(); 11 | 12 | public ProxyPortAdapter(SimpleApplicationService simpleApplicationService) 13 | { 14 | Objects.requireNonNull(simpleApplicationService); 15 | incrementInstanceCount(); 16 | } 17 | 18 | public static int getInstanceCount() 19 | { 20 | return INSTANCE_COUNT.get(); 21 | } 22 | 23 | public static void resetInstanceCount() 24 | { 25 | INSTANCE_COUNT.set(0); 26 | } 27 | 28 | private static void incrementInstanceCount() 29 | { 30 | INSTANCE_COUNT.incrementAndGet(); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /jexxa-test/src/test/java/io/jexxa/jexxatest/architecture/validapplication/domain/valid/ValidAggregate.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.jexxatest.architecture.validapplication.domain.valid; 2 | 3 | import io.jexxa.addend.applicationcore.Aggregate; 4 | import io.jexxa.addend.applicationcore.AggregateID; 5 | 6 | @Aggregate 7 | @SuppressWarnings("unused") 8 | public class ValidAggregate { 9 | private final ValidValueObject validValueObjectA; 10 | private final ValidEntity validEntity; 11 | 12 | public ValidAggregate(ValidValueObject validValueObject) 13 | { 14 | this.validValueObjectA = validValueObject; 15 | this.validEntity = new ValidEntity(validValueObject); 16 | } 17 | 18 | @AggregateID 19 | public ValidValueObject getValidValueObjectA() 20 | { 21 | return validValueObjectA; 22 | } 23 | 24 | public void domainLogic() 25 | { 26 | // Empty implementation because method is used to validate architecture rules 27 | } 28 | 29 | public ValidEntity getValidEntity() 30 | { 31 | return validEntity; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /jexxa-test/src/test/java/io/jexxa/jexxatest/architecture/invalidapplication/applicationservice/InvalidApplicationService.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.jexxatest.architecture.invalidapplication.applicationservice; 2 | 3 | import io.jexxa.jexxatest.architecture.invalidapplication.domain.invalid.InvalidAggregate; 4 | import io.jexxa.jexxatest.architecture.invalidapplication.domain.invalid.InvalidRepository; 5 | 6 | import java.util.List; 7 | 8 | @SuppressWarnings("unused") 9 | 10 | public class InvalidApplicationService 11 | { 12 | private final InvalidRepository invalidRepository; 13 | @SuppressWarnings("FieldCanBeLocal") 14 | private InvalidAggregate invalidAggregate; 15 | 16 | public InvalidApplicationService(InvalidRepository invalidRepository) 17 | { 18 | this.invalidRepository = invalidRepository; 19 | } 20 | 21 | public List get() 22 | { 23 | return invalidRepository.get(); 24 | } 25 | 26 | public void get(InvalidAggregate invalidAggregate) 27 | { 28 | this.invalidAggregate = invalidAggregate; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /jexxa-core/src/test/java/io/jexxa/testapplication/infrastructure/drivingadapter/generic/InvalidDrivingAdapter.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.testapplication.infrastructure.drivingadapter.generic; 2 | 3 | import io.jexxa.adapterapi.drivingadapter.IDrivingAdapter; 4 | 5 | /** 6 | * This DrivingAdapter is invalid because it only provides a private constructor. 7 | * So Jexxa cannot create an instance of this class because it does not fulfill its convention. 8 | */ 9 | public final class InvalidDrivingAdapter implements IDrivingAdapter 10 | { 11 | private InvalidDrivingAdapter() 12 | { 13 | //Create an invalid adapter for testing fail-fast approach 14 | } 15 | 16 | @Override 17 | public void start() 18 | { 19 | //Create an invalid adapter for testing fail-fast approach 20 | } 21 | 22 | @Override 23 | public void stop() 24 | { 25 | //Create an invalid adapter for testing fail-fast approach 26 | } 27 | 28 | @Override 29 | public void register(Object port) 30 | { 31 | //Create an invalid adapter for testing fail-fast approach 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /jexxa-core/src/main/java/io/jexxa/core/factory/AmbiguousAdapterException.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.core.factory; 2 | 3 | import java.io.Serial; 4 | import java.util.List; 5 | 6 | public class AmbiguousAdapterException extends RuntimeException 7 | { 8 | @Serial 9 | private static final long serialVersionUID = 1L; 10 | 11 | private final String internalMessage; 12 | 13 | AmbiguousAdapterException(Class clazz, List> implementationList) 14 | { 15 | var stringBuilder = new StringBuilder(); 16 | stringBuilder.append("AmbiguousAdapterException: Outbound port ") 17 | .append(clazz.getName()) 18 | .append(" is implemented by multiple adapters : \n"); 19 | 20 | implementationList.forEach( implementation -> stringBuilder.append(" * ") 21 | .append(implementation.getName()) 22 | .append("\n") 23 | ); 24 | 25 | internalMessage = stringBuilder.toString(); 26 | } 27 | 28 | @Override 29 | public String getMessage() 30 | { 31 | return internalMessage; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /jexxa-core/src/test/java/io/jexxa/testapplication/applicationservice/ApplicationServiceWithDrivenAdapters.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.testapplication.applicationservice; 2 | 3 | import io.jexxa.testapplication.annotation.ValidApplicationService; 4 | import io.jexxa.testapplication.domainservice.ValidDefaultConstructorService; 5 | import io.jexxa.testapplication.domainservice.ValidFactoryMethodService; 6 | import io.jexxa.testapplication.domainservice.ValidPropertiesConstructorService; 7 | 8 | import java.util.Objects; 9 | 10 | @ValidApplicationService 11 | public class ApplicationServiceWithDrivenAdapters 12 | { 13 | public ApplicationServiceWithDrivenAdapters( 14 | ValidDefaultConstructorService validDefaultConstructorService, 15 | ValidFactoryMethodService validFactoryMethodService, 16 | ValidPropertiesConstructorService validPropertiesConstructorService 17 | ) 18 | { 19 | Objects.requireNonNull(validDefaultConstructorService); 20 | Objects.requireNonNull(validFactoryMethodService); 21 | Objects.requireNonNull(validPropertiesConstructorService); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /jexxa-test/src/main/java/io/jexxa/jexxatest/infrastructure/messaging/recording/MessageRecorderManager.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.jexxatest.infrastructure.messaging.recording; 2 | 3 | import io.jexxa.common.facade.utils.annotation.CheckReturnValue; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | /** 9 | * Singleton, which manages all instances of MessageRecorder 10 | */ 11 | public final class MessageRecorderManager 12 | { 13 | private static final Map, MessageRecorder> MESSAGE_RECORDER_MAP = new HashMap<>(); 14 | 15 | @CheckReturnValue 16 | public static MessageRecorder getMessageRecorder(Class type) 17 | { 18 | //If MessageRecorder is not known for a given type, we create it 19 | return MESSAGE_RECORDER_MAP.computeIfAbsent(type, element -> new MessageRecorder()); 20 | } 21 | 22 | 23 | public static void clear() 24 | { 25 | MESSAGE_RECORDER_MAP.forEach(( key, value) -> value.clear() ); 26 | MESSAGE_RECORDER_MAP.clear(); 27 | } 28 | 29 | private MessageRecorderManager() 30 | { 31 | //Private constructor 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /docs/BUILD.md: -------------------------------------------------------------------------------- 1 | # Build Jexxa from scratch 2 | 3 | In case you would like to compile Jexxa by yourself without an integration tests call: 4 | 5 | ```maven 6 | mvn clean install -DskipITs 7 | ``` 8 | 9 | ## Dependencies for integration tests 10 | 11 | For running integration tests, we recommend using local docker containers to provide the following infrastructure: 12 | 13 | * An ActiveMQ instance with default settings: See [here](https://hub.docker.com/r/rmohr/activemq/). 14 | * A PostgresDB database with default settings. Default user/password should be admin/admin: See [here](https://hub.docker.com/_/postgres). 15 | 16 | You can also use the docker stack provided [here](https://github.com/jexxa-projects/Jexxa/blob/master/jexxa-core/src/test/resources/DeveloperStack.yaml) 17 | 18 | Check the status of the running containers: 19 | 20 | ```docker 21 | docker ps -f status=running --format "{{.Names}}" 22 | ``` 23 | 24 | Output should look as follows 25 | 26 | ```docker 27 | ... 28 | Postgres 29 | activemq 30 | ... 31 | ``` 32 | 33 | To build Jexxa with integration tests call: 34 | 35 | ```maven 36 | mvn clean install 37 | ``` 38 | -------------------------------------------------------------------------------- /jexxa-test/src/test/java/io/jexxa/jexxatest/application/JexxaITTestApplication.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.jexxatest.application; 2 | 3 | import io.jexxa.core.JexxaMain; 4 | import io.jexxa.drivingadapter.rest.RESTfulRPCAdapter; 5 | import io.jexxa.testapplication.applicationservice.SimpleApplicationService; 6 | 7 | import java.util.Properties; 8 | 9 | public class JexxaITTestApplication 10 | { 11 | private static JexxaMain jexxaMain; 12 | public static void main(Properties properties) 13 | { 14 | jexxaMain = new JexxaMain(JexxaITTestApplication.class, properties); 15 | 16 | jexxaMain 17 | .bind(RESTfulRPCAdapter.class).to(SimpleApplicationService.class) 18 | .bind(RESTfulRPCAdapter.class).to(jexxaMain.getBoundedContext()) 19 | 20 | .run(); 21 | } 22 | 23 | public static void shutDown() 24 | { 25 | if (jexxaMain != null) 26 | { 27 | jexxaMain.stop(); 28 | jexxaMain = null; 29 | } 30 | } 31 | 32 | private JexxaITTestApplication() 33 | { 34 | //Private constructor since we only offer main 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /jexxa-web/src/test/resources/certificate/testCertificate.csr: -------------------------------------------------------------------------------- 1 | -----BEGIN NEW CERTIFICATE REQUEST----- 2 | MIIC1TCCAb0CAQAwYDELMAkGA1UEBhMCREUxETAPBgNVBAgTCHNhYXJsYW5kMRIw 3 | EAYDVQQHEwlkaWxsaW5nZW4xCzAJBgNVBAoTAnRpMQswCQYDVQQLEwJ0aTEQMA4G 4 | A1UEAxMHbWljaGFlbDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANFl 5 | /s/xGk2oiKxNoWqi9y2LEGakaAlpRR+yNXqwQ2GCyD8yh1boYuKm3JxR89YzhKMk 6 | Y4sRBMkInDEIiPZdMRrnVBmPDpFgE1AyZXG0ga5JF5Bd3mEfiagsgOZS4f2hWOnq 7 | isCWQ1hC45wmw3dAPZJWfEnWtRHEQqh1TR8Qd3zcVdArgBClIauCbI06lxyk6oCk 8 | rzxfJ4BSqHSjxAzbfeLt40y0UH5Ii453PlqUGCbUOn+c0i6Isy/1zELYKILsiVVM 9 | f5m6lKvdyvboZqPT/A0GU91uRftREyhyGMHgDx05x2fdRn+r0rEtX8gKXTPTvL5J 10 | GJmgjMpdg2J1KpYSyZcCAwEAAaAwMC4GCSqGSIb3DQEJDjEhMB8wHQYDVR0OBBYE 11 | FC3KHKeFA5r5gvRdKI4FlVQc175BMA0GCSqGSIb3DQEBCwUAA4IBAQDKRjvE4qgo 12 | QRkDdzz7JX4mRO96Qe0vX2bVjmNE6nvIr4aXbdpg5ZxFydD7poWfGDO4yUmJytYA 13 | uI61KIqsA9AD453QCa442iM6+pZ/L9V57HkpRi1JHMj32SHl2KQ01Roxe8GA4H2A 14 | nW69kVCEMjmoquEJe1blmQ08gMNSCTnP0GQfm5ce2CZ1Uv0Gn6JM53bKN227UbGZ 15 | lg/lCjEz1rBZRmCAfosR8q+X+K17ITulBmbsF/yHjsPDuFo2GAMcR3QeQyDlz4sR 16 | 5Y5OT26uFT4BU0La5DeOVrGwoPg6IESMAPHQ91xkGxhfo5NCHTaMlCV5+tiL1TCX 17 | YJ5ehZzYa309 18 | -----END NEW CERTIFICATE REQUEST----- 19 | -------------------------------------------------------------------------------- /jexxa-core/src/test/java/io/jexxa/testapplication/infrastructure/drivenadapter/factory/ValidFactoryMethodServiceImpl.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.testapplication.infrastructure.drivenadapter.factory; 2 | 3 | import io.jexxa.testapplication.domainservice.ValidFactoryMethodService; 4 | 5 | import java.util.Objects; 6 | import java.util.Properties; 7 | 8 | /** 9 | * Simulate a valid driven adapter with static factory method expecting a Properties 10 | */ 11 | @SuppressWarnings("unused") 12 | public final class ValidFactoryMethodServiceImpl implements ValidFactoryMethodService 13 | { 14 | private ValidFactoryMethodServiceImpl() 15 | { 16 | //Empty and private constructor so that static methods must be used in tests 17 | } 18 | 19 | 20 | private ValidFactoryMethodServiceImpl(Properties properties) 21 | { 22 | Objects.requireNonNull(properties); 23 | } 24 | 25 | public static ValidFactoryMethodService create() 26 | { 27 | return new ValidFactoryMethodServiceImpl(); 28 | } 29 | 30 | public static ValidFactoryMethodService create(Properties properties) 31 | { 32 | return new ValidFactoryMethodServiceImpl(properties); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /jexxa-core/src/main/java/io/jexxa/core/BootstrapService.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.core; 2 | 3 | import io.jexxa.common.facade.utils.annotation.CheckReturnValue; 4 | 5 | import java.util.Objects; 6 | import java.util.function.Consumer; 7 | 8 | @SuppressWarnings("unused") 9 | public class BootstrapService 10 | { 11 | private final Class bootstrapServiceClass; 12 | private final JexxaMain jexxaMain; 13 | 14 | BootstrapService(Class bootstrapService, JexxaMain jexxaMain) 15 | { 16 | this.bootstrapServiceClass = Objects.requireNonNull(bootstrapService); 17 | this.jexxaMain = Objects.requireNonNull(jexxaMain); 18 | } 19 | 20 | @CheckReturnValue 21 | public JexxaMain with(Consumer initFunction) 22 | { 23 | jexxaMain.addBootstrapService(bootstrapServiceClass, initFunction); 24 | return jexxaMain; 25 | } 26 | 27 | @CheckReturnValue 28 | public JexxaMain and() 29 | { 30 | jexxaMain.addBootstrapService(bootstrapServiceClass, noInitFunction()); 31 | return jexxaMain; 32 | } 33 | 34 | public static Consumer noInitFunction() 35 | { 36 | return element -> { /* just an empty function */}; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /docs/adr/004-ioc-without-annotations.md: -------------------------------------------------------------------------------- 1 | # 4. IoC without Annotations 2 | 3 | Date: 2021-12-23 4 | 5 | ## Status 6 | 7 | Accepted 8 | 9 | ## Context 10 | Like any other framework, Jexxa takes control of part of your application core. 11 | Especially in Java, this is often done with framework-specific annotations. 12 | The downside is that these annotations tightly couple your application core 13 | to a specific technology stack. 14 | 15 | ## Decision 16 | Jexxa does not use annotations for all IoC aspects used in the application core, such as dependency injection. 17 | Instead, conventions are used. 18 | To remember the essential conventions, they must be few and easy to remember. 19 | Thus, this decision is intended to support compliance with the KISS principle. 20 | 21 | Note: Annotations are allowed between Jexxa and the infrastructure part of an application. For example, you can use 22 | annotations in the infrastructure part to map REST calls to an application service. 23 | 24 | ## Consequences 25 | * IoC concepts are not described by annotations inside the application core. Instead, the conventions must be known to the 26 | developers. To support this, Jexxa validates the conventions and shows appropriate error messages. 27 | * Dependency Injection must not use annotations. Therefore, Jexxa supports only implicit constructor injection. 28 | 29 | -------------------------------------------------------------------------------- /jexxa-core/src/main/java/io/jexxa/core/VersionInfo.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.core; 2 | 3 | @SuppressWarnings("unused") 4 | public record VersionInfo(String version, String repository, String projectName, String buildTimestamp) 5 | { 6 | public static VersionInfoBuilder of() { 7 | return new VersionInfoBuilder(); 8 | } 9 | 10 | public static class VersionInfoBuilder { 11 | private String version; 12 | private String repository; 13 | private String projectName; 14 | private String buildTimestamp; 15 | 16 | public VersionInfoBuilder version(String version) { 17 | this.version = version; 18 | return this; 19 | } 20 | 21 | public VersionInfoBuilder repository(String repository) { 22 | this.repository = repository; 23 | return this; 24 | } 25 | 26 | public VersionInfoBuilder projectName(String projectName) { 27 | this.projectName = projectName; 28 | return this; 29 | } 30 | 31 | public VersionInfoBuilder buildTimestamp(String buildTimestamp) { 32 | this.buildTimestamp = buildTimestamp; 33 | return this; 34 | } 35 | 36 | public VersionInfo create() { 37 | return new VersionInfo(version, repository, projectName, buildTimestamp); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /jexxa-core/src/test/java/io/jexxa/testapplication/domain/model/JexxaEntity.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.testapplication.domain.model; 2 | 3 | import java.util.Objects; 4 | 5 | public final class JexxaEntity 6 | { 7 | private final JexxaValueObject jexxaValueObject; 8 | 9 | private int internalValue; 10 | 11 | public static JexxaEntity create(JexxaValueObject key) 12 | { 13 | return new JexxaEntity(key); 14 | } 15 | 16 | public void setInternalValue(int value) 17 | { 18 | internalValue = value; 19 | } 20 | 21 | public int getInternalValue() 22 | { 23 | return internalValue; 24 | } 25 | 26 | 27 | public JexxaValueObject getKey() 28 | { 29 | return jexxaValueObject; 30 | } 31 | 32 | private JexxaEntity(JexxaValueObject jexxaValueObject) 33 | { 34 | this.jexxaValueObject = jexxaValueObject; 35 | } 36 | 37 | @Override 38 | public boolean equals(Object o) 39 | { 40 | if (this == o) 41 | { 42 | return true; 43 | } 44 | if (o == null || getClass() != o.getClass()) 45 | { 46 | return false; 47 | } 48 | JexxaEntity that = (JexxaEntity) o; 49 | return Objects.equals(getKey(), that.getKey()); // Only compare keys 50 | } 51 | 52 | @Override 53 | public int hashCode() 54 | { 55 | return Objects.hash(jexxaValueObject); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /jexxa-core/src/test/java/io/jexxa/testapplication/infrastructure/drivenadapter/messaging/ValidDomainSenderImpl.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.testapplication.infrastructure.drivenadapter.messaging; 2 | 3 | import io.jexxa.common.drivenadapter.messaging.MessageSender; 4 | import io.jexxa.testapplication.domain.model.JexxaValueObject; 5 | import io.jexxa.testapplication.domainservice.ValidDomainSender; 6 | 7 | import java.util.Properties; 8 | 9 | import static io.jexxa.common.drivenadapter.messaging.MessageSenderFactory.createMessageSender; 10 | 11 | @SuppressWarnings("unused") 12 | public class ValidDomainSenderImpl implements ValidDomainSender 13 | { 14 | public static final String JEXXA_TOPIC = "JexxaTopic"; 15 | public static final String JEXXA_QUEUE = "JexxaQueue"; 16 | 17 | private final MessageSender messageSender; 18 | 19 | public ValidDomainSenderImpl(Properties properties) 20 | { 21 | this.messageSender = createMessageSender(ValidDomainSenderImpl.class, properties); 22 | } 23 | 24 | @Override 25 | public void sendToQueue(JexxaValueObject jexxaValueObject) 26 | { 27 | messageSender.send(jexxaValueObject) 28 | .toQueue(JEXXA_QUEUE) 29 | .asJson(); 30 | } 31 | 32 | @Override 33 | public void sendToTopic(JexxaValueObject jexxaValueObject) 34 | { 35 | messageSender.send(jexxaValueObject) 36 | .toTopic(JEXXA_TOPIC) 37 | .asJson(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /docs/jexxa-restructured.architecture: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /jexxa-core/src/test/java/io/jexxa/testapplication/infrastructure/drivingadapter/generic/ProxyDrivingAdapter.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.testapplication.infrastructure.drivingadapter.generic; 2 | 3 | import io.jexxa.adapterapi.drivingadapter.IDrivingAdapter; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | import java.util.concurrent.atomic.AtomicInteger; 8 | 9 | public class ProxyDrivingAdapter implements IDrivingAdapter 10 | { 11 | private static final AtomicInteger INSTANCE_COUNT = new AtomicInteger(); 12 | 13 | private final List portList = new ArrayList<>(); 14 | 15 | public ProxyDrivingAdapter() 16 | { 17 | incrementInstanceCount(); 18 | } 19 | 20 | @Override 21 | public void register(Object port) 22 | { 23 | portList.add(port); 24 | } 25 | 26 | @Override 27 | public void start() 28 | { 29 | // No operation required 30 | } 31 | 32 | @Override 33 | public void stop() 34 | { 35 | // No operation required 36 | } 37 | 38 | public List getPortList() 39 | { 40 | return portList; 41 | } 42 | 43 | public static int getInstanceCount() 44 | { 45 | return INSTANCE_COUNT.get(); 46 | } 47 | 48 | public static void resetInstanceCount() 49 | { 50 | INSTANCE_COUNT.set(0); 51 | } 52 | 53 | private static void incrementInstanceCount() 54 | { 55 | INSTANCE_COUNT.incrementAndGet(); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /docs/adr/002-visible-flow-of-control.md: -------------------------------------------------------------------------------- 1 | # 2. Visible flow of control 2 | 3 | Date: 2021-12-18 4 | 5 | ## Status 6 | 7 | Accepted 8 | 9 | ## Context 10 | Most today’s frameworks bind technology stacks automatically to your application core. If at all, you have to add a new 11 | dependency and rebuild the application. Unfortunately, you hide the flow of control which makes it harder for beginners 12 | to understand an application which is based on a ports and adapters architecture. This is especially true for the entry 13 | points of your application. 14 | 15 | This might be obvious to incoming synchronous calls (RMI), but can be hard to see for incoming asynchronous messaging. 16 | Most frameworks use annotations here, but the developer must be aware of them. 17 | 18 | 19 | ## Decision 20 | * Jexxa uses explicit binding for driving adapters so that the main method represents the single starting point for 21 | the flow of control. 22 | 23 | * See tutorial [TimeService - Flow of Control](https://github.com/jexxa-projects/JexxaTutorials/blob/main/TimeService/README-FlowOfControl.md) for further information. 24 | 25 | ## Consequences 26 | 27 | * The main method includes some boilerplate code to bind required driving adapters to the application core. 28 | * The constructor of inbound ports only accepts interfaces which are considered as outbound ports. Thus, the 29 | implementation of an inbound port has to instantiate all required business objects by itself. Only outbound ports 30 | are injected. -------------------------------------------------------------------------------- /jexxa-core/src/main/java/io/jexxa/core/FluentMonitor.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.core; 2 | 3 | import io.jexxa.adapterapi.invocation.InvocationManager; 4 | import io.jexxa.adapterapi.invocation.monitor.AfterMonitor; 5 | import io.jexxa.adapterapi.invocation.monitor.AroundMonitor; 6 | import io.jexxa.adapterapi.invocation.monitor.BeforeMonitor; 7 | 8 | import java.util.Objects; 9 | 10 | @SuppressWarnings({"unused"}) 11 | public final class FluentMonitor 12 | { 13 | private final Object targetObject; 14 | private final JexxaMain jexxaMain; 15 | 16 | FluentMonitor(JexxaMain jexxaMain, Object targetObject ) 17 | { 18 | this.targetObject = Objects.requireNonNull( targetObject ); 19 | this.jexxaMain = Objects.requireNonNull( jexxaMain ); 20 | } 21 | 22 | public JexxaMain with(BeforeMonitor monitor) 23 | { 24 | monitor.setObservedObject(targetObject); 25 | InvocationManager.getRootInterceptor(targetObject).registerBefore(monitor); 26 | return jexxaMain.registerHealthCheck(monitor); 27 | } 28 | 29 | public JexxaMain with(AfterMonitor monitor) 30 | { 31 | InvocationManager.getRootInterceptor(targetObject).registerAfter(monitor); 32 | return jexxaMain.registerHealthCheck(monitor); 33 | } 34 | 35 | public JexxaMain with(AroundMonitor monitor) 36 | { 37 | InvocationManager.getRootInterceptor(targetObject).registerAround(monitor); 38 | return jexxaMain.registerHealthCheck(monitor); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /jexxa-test/src/main/java/io/jexxa/jexxatest/integrationtest/rest/BoundedContextHandler.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.jexxatest.integrationtest.rest; 2 | 3 | import io.jexxa.adapterapi.drivingadapter.Diagnostics; 4 | import io.jexxa.core.VersionInfo; 5 | import kong.unirest.GenericType; 6 | 7 | import java.time.Duration; 8 | import java.util.List; 9 | import java.util.Properties; 10 | 11 | public class BoundedContextHandler extends RESTHandler 12 | { 13 | public BoundedContextHandler(Properties properties, Class endpointClazz) 14 | { 15 | super(properties, endpointClazz); 16 | } 17 | 18 | public Duration uptime() 19 | { 20 | return getRequest(Duration.class, "uptime"); 21 | } 22 | 23 | public String contextName() 24 | { 25 | return getRequest(String.class, "contextName"); 26 | } 27 | 28 | public VersionInfo jexxaVersion() 29 | { 30 | return getRequest(VersionInfo.class, "jexxaVersion"); 31 | } 32 | 33 | public VersionInfo contextVersion() 34 | { 35 | return getRequest(VersionInfo.class, "contextVersion"); 36 | } 37 | 38 | public boolean isRunning() 39 | { 40 | return getRequest(Boolean.class, "isRunning"); 41 | } 42 | 43 | public boolean isHealthy() 44 | { 45 | return getRequest(Boolean.class, "isHealthy"); 46 | } 47 | 48 | public List diagnostics() 49 | { 50 | return getRequest(new GenericType<>() {}, "diagnostics"); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /jexxa-test/src/test/java/io/jexxa/jexxatest/architecture/validapplication/applicationservice/TestApplicationService.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.jexxatest.architecture.validapplication.applicationservice; 2 | 3 | import io.jexxa.addend.applicationcore.ApplicationService; 4 | import io.jexxa.jexxatest.architecture.validapplication.domain.valid.ValidAggregate; 5 | import io.jexxa.jexxatest.architecture.validapplication.domain.valid.ValidEnum; 6 | import io.jexxa.jexxatest.architecture.validapplication.domain.valid.ValidRepository; 7 | import io.jexxa.jexxatest.architecture.validapplication.domain.valid.ValidValueObject; 8 | 9 | import java.util.List; 10 | 11 | @SuppressWarnings("unused") 12 | @ApplicationService 13 | public class TestApplicationService 14 | { 15 | private final ValidRepository validRepository; 16 | public TestApplicationService(ValidRepository validRepository) 17 | { 18 | this.validRepository = validRepository; 19 | } 20 | //Using enum in a switch statement creates anonymous class 21 | public ValidEnum useEnum(ValidEnum validEnum) 22 | { 23 | return switch (validEnum) { 24 | case VALUE_B -> ValidEnum.VALUE_B; 25 | case VALUE_C -> ValidEnum.VALUE_C; 26 | default -> ValidEnum.VALUE_A; 27 | }; 28 | } 29 | 30 | public List get() { 31 | return validRepository.get().stream() 32 | .map(ValidAggregate::getValidValueObjectA) 33 | .toList(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /jexxa-core/src/test/java/io/jexxa/testapplication/domain/model/SpecialCasesValueObject.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.testapplication.domain.model; 2 | 3 | import java.util.Objects; 4 | 5 | /** 6 | * This ValueObject include the following special cases: 7 | *
    8 | *
  • private field without public getter
  • 9 | *
  • private field with null
  • 10 | *
11 | */ 12 | @SuppressWarnings("SameParameterValue") 13 | public final class SpecialCasesValueObject 14 | { 15 | 16 | public static final SpecialCasesValueObject SPECIAL_CASES_VALUE_OBJECT = new SpecialCasesValueObject(1); 17 | 18 | private final int valueWithoutGetter; 19 | private final String nullValue = null; 20 | 21 | private SpecialCasesValueObject(int value) 22 | { 23 | this.valueWithoutGetter = value; 24 | } 25 | 26 | @SuppressWarnings({"ConstantConditions", "unused"}) 27 | public String getNullValue() 28 | { 29 | return nullValue; 30 | } 31 | 32 | @Override 33 | public boolean equals(Object o) 34 | { 35 | if (this == o) 36 | { 37 | return true; 38 | } 39 | if (o == null || getClass() != o.getClass()) 40 | { 41 | return false; 42 | } 43 | SpecialCasesValueObject that = (SpecialCasesValueObject) o; 44 | return valueWithoutGetter == that.valueWithoutGetter; 45 | } 46 | 47 | @Override 48 | public int hashCode() 49 | { 50 | return Objects.hash(valueWithoutGetter); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /jexxa-core/src/test/java/io/jexxa/testapplication/domain/model/JexxaAggregate.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.testapplication.domain.model; 2 | 3 | import java.util.Objects; 4 | 5 | public final class JexxaAggregate 6 | { 7 | private final JexxaEntity jexxaEntity; 8 | private final JexxaValueObject jexxaValueObject; 9 | 10 | private JexxaAggregate(JexxaValueObject jexxaValueObject) 11 | { 12 | this.jexxaEntity = JexxaEntity.create(jexxaValueObject); 13 | this.jexxaValueObject = jexxaValueObject; 14 | } 15 | 16 | public void setInternalValue(int value) 17 | { 18 | jexxaEntity.setInternalValue(value); 19 | } 20 | 21 | public int getInternalValue() 22 | { 23 | return jexxaEntity.getInternalValue(); 24 | } 25 | 26 | public JexxaValueObject getKey() 27 | { 28 | return jexxaValueObject; 29 | } 30 | 31 | public static JexxaAggregate create(JexxaValueObject key) 32 | { 33 | return new JexxaAggregate(key); 34 | } 35 | 36 | @Override 37 | public boolean equals(Object o) 38 | { 39 | if (this == o) 40 | { 41 | return true; 42 | } 43 | if (o == null || getClass() != o.getClass()) 44 | { 45 | return false; 46 | } 47 | JexxaAggregate that = (JexxaAggregate) o; 48 | return Objects.equals(getKey(), that.getKey()); // Only compare keys 49 | } 50 | 51 | @Override 52 | public int hashCode() 53 | { 54 | return Objects.hash(jexxaValueObject); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /jexxa-core/src/test/java/io/jexxa/testapplication/domain/model/JexxaValueObject.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.testapplication.domain.model; 2 | 3 | 4 | public class JexxaValueObject 5 | { 6 | private final int value; 7 | private final double valueInPercent; 8 | 9 | public JexxaValueObject(int value) { 10 | this.value = value; 11 | this.valueInPercent = value / 100.0; 12 | } 13 | 14 | public int getValue() 15 | { 16 | return value; 17 | } 18 | 19 | @SuppressWarnings("unused") 20 | public double getValueInPercent() 21 | { 22 | return valueInPercent; 23 | } 24 | 25 | @Override 26 | public boolean equals(Object other) 27 | { 28 | if (this == other) 29 | { 30 | return true; 31 | } 32 | 33 | if (other == null) { 34 | return false; 35 | } 36 | 37 | if (getClass() != other.getClass()) { 38 | return false; 39 | } 40 | JexxaValueObject otherObject = (JexxaValueObject) other; 41 | 42 | 43 | return (this.value == otherObject.getValue() && 44 | this.valueInPercent == getValueInPercent()); 45 | } 46 | 47 | @Override 48 | public String toString() 49 | { 50 | return JexxaValueObject.class.getSimpleName() + " : {" + 51 | " value = " + value + 52 | " valueInPercent = " + valueInPercent + 53 | " } "; 54 | } 55 | 56 | 57 | @Override 58 | public int hashCode() 59 | { 60 | return value; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /jexxa-core/src/main/java/io/jexxa/properties/JexxaCoreProperties.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.properties; 2 | 3 | public final class JexxaCoreProperties 4 | { 5 | /** Define an additional import file that is loaded. The Default value is empty. */ 6 | public static final String JEXXA_CONFIG_IMPORT = "io.jexxa.config.import"; 7 | 8 | /** Define the name of the bounded context. The Default value is the name of the main class of an application */ 9 | public static final String JEXXA_CONTEXT_NAME = "io.jexxa.context.name"; 10 | 11 | /** Defines the version number of the context. This is typically set via maven */ 12 | public static final String JEXXA_CONTEXT_VERSION = "io.jexxa.context.version"; 13 | 14 | /** Defines the repository of the context. This is typically set via maven */ 15 | public static final String JEXXA_CONTEXT_REPOSITORY = "io.jexxa.context.repository"; 16 | 17 | /** Defines the build timestamp of the context. This is typically set via maven */ 18 | public static final String JEXXA_CONTEXT_BUILD_TIMESTAMP = "io.jexxa.context.build.timestamp"; 19 | 20 | /** Configures the global system property `user.timezone` to define the timezone used by the application */ 21 | public static final String JEXXA_USER_TIMEZONE = "io.jexxa.user.timezone"; 22 | 23 | /** Defines the default properties file which is /jexxa-application.properties */ 24 | public static final String JEXXA_APPLICATION_PROPERTIES = "/jexxa-application.properties"; 25 | 26 | private JexxaCoreProperties() 27 | { 28 | //Private constructor 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /jexxa-core/src/test/java/io/jexxa/core/DrivingAdapterTest.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.core; 2 | 3 | import io.jexxa.TestConstants; 4 | import io.jexxa.core.convention.AdapterConventionViolation; 5 | import io.jexxa.core.convention.PortConventionViolation; 6 | import io.jexxa.testapplication.JexxaTestApplication; 7 | import io.jexxa.testapplication.applicationservice.InvalidConstructorApplicationService; 8 | import io.jexxa.testapplication.infrastructure.drivingadapter.generic.InvalidDrivingAdapter; 9 | import io.jexxa.testapplication.infrastructure.drivingadapter.generic.ProxyDrivingAdapter; 10 | import org.junit.jupiter.api.Tag; 11 | import org.junit.jupiter.api.Test; 12 | 13 | import static org.junit.jupiter.api.Assertions.assertThrows; 14 | 15 | @Tag(TestConstants.UNIT_TEST) 16 | class DrivingAdapterTest 17 | { 18 | 19 | @Test 20 | void throwOnInvalidPortConvention() 21 | { 22 | //Arrange 23 | JexxaMain jexxaMain = new JexxaMain(JexxaTestApplication.class); 24 | var drivingAdapter = jexxaMain.bind(ProxyDrivingAdapter.class); 25 | 26 | //Act / Assert 27 | assertThrows(PortConventionViolation.class, () -> drivingAdapter.to(InvalidConstructorApplicationService.class)); 28 | } 29 | 30 | @SuppressWarnings("ResultOfMethodCallIgnored") 31 | @Test 32 | void throwOnInvalidAdapterConvention() 33 | { 34 | //Arrange 35 | JexxaMain jexxaMain = new JexxaMain(DrivingAdapterTest.class); 36 | 37 | //Act / Assert 38 | assertThrows(AdapterConventionViolation.class, () -> jexxaMain.bind(InvalidDrivingAdapter.class)); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /jexxa-core/src/main/java/io/jexxa/core/convention/PortConvention.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.core.convention; 2 | 3 | import java.util.Arrays; 4 | 5 | public final class PortConvention 6 | { 7 | 8 | /** 9 | * Validates if given clazz matches the convention of a port. 10 | * If one of the conventions is not matched, an exception is thrown: 11 | * 12 | *
  • 13 | * Exactly one public constructor. 14 | *
  • 15 | *
  • 16 | * Attributes of the constructor must be interfaces 17 | *
  • 18 | *
    19 | * 20 | * @param clazz that should be used as a Port 21 | * @param generic type of the port 22 | */ 23 | public static void validate(Class clazz) 24 | { 25 | if ( clazz.getConstructors().length == 0) 26 | { 27 | throw new PortConventionViolation("No public constructor available for Port : " + clazz.getName()); 28 | } 29 | 30 | if ( clazz.getConstructors().length > 1) 31 | { 32 | throw new PortConventionViolation("More than one public constructor available for Port : " + clazz.getName()); 33 | } 34 | 35 | var nonInterfaceAttribute = 36 | Arrays.stream(clazz.getConstructors()[0].getParameterTypes()) 37 | .filter(attribute -> !attribute.isInterface()) 38 | .findAny(); 39 | 40 | if (nonInterfaceAttribute.isPresent()) 41 | { 42 | throw new PortConventionViolation(clazz); 43 | } 44 | } 45 | 46 | 47 | private PortConvention() 48 | { 49 | //Private Constructor 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /jexxa-core/src/test/java/io/jexxa/testapplication/infrastructure/drivingadapter/generic/ProxyAdapter.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.testapplication.infrastructure.drivingadapter.generic; 2 | 3 | import io.jexxa.adapterapi.drivingadapter.IDrivingAdapter; 4 | import io.jexxa.adapterapi.invocation.InvocationManager; 5 | import io.jexxa.adapterapi.invocation.function.SerializableConsumer; 6 | import io.jexxa.adapterapi.invocation.function.SerializableFunction; 7 | import io.jexxa.adapterapi.invocation.function.SerializableSupplier; 8 | 9 | public class ProxyAdapter implements IDrivingAdapter 10 | { 11 | private Object port; 12 | 13 | @Override 14 | public void register(Object port) { 15 | this.port = port; 16 | } 17 | 18 | @Override 19 | public void start() { 20 | // No action required 21 | } 22 | 23 | @Override 24 | public void stop() { 25 | // No action required 26 | } 27 | 28 | @SuppressWarnings("unused") 29 | public void invoke(SerializableConsumer consumer, T argument) 30 | { 31 | InvocationManager.getInvocationHandler(port) 32 | .invoke(port, consumer, argument ); 33 | } 34 | 35 | @SuppressWarnings("unused") 36 | public T invoke(SerializableSupplier supplier) 37 | { 38 | return InvocationManager.getInvocationHandler(port) 39 | .invoke(port, supplier ); 40 | } 41 | 42 | @SuppressWarnings("unused") 43 | R invoke(SerializableFunction function, T argument) 44 | { 45 | return InvocationManager.getInvocationHandler(port) 46 | .invoke(port, function, argument ); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /jexxa-core/src/main/java/io/jexxa/core/factory/InvalidAdapterException.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.core.factory; 2 | 3 | import java.io.Serial; 4 | 5 | public class InvalidAdapterException extends RuntimeException 6 | { 7 | private static final String CANNOT_CREATE_ADAPTER = "Cannot create adapter "; 8 | private static final String CHECK_CONVENTIONS = "For further information check the conventions of an adapter (https://jexxa-projects.github.io/Jexxa/jexxa_reference.html#_dependency_injection_di). "; 9 | 10 | @Serial 11 | private static final long serialVersionUID = 1L; 12 | 13 | private final String errorMessage; 14 | 15 | @SuppressWarnings("unused") 16 | public InvalidAdapterException(Class adapter) 17 | { 18 | this.errorMessage = CANNOT_CREATE_ADAPTER + adapter.getSimpleName() + " -> " + CHECK_CONVENTIONS; 19 | } 20 | 21 | public InvalidAdapterException(Class adapter, String message) 22 | { 23 | this.errorMessage = CANNOT_CREATE_ADAPTER + adapter.getSimpleName() + "! "+ message + " -> " + CHECK_CONVENTIONS; 24 | } 25 | 26 | public InvalidAdapterException(Class adapter, Throwable exception) 27 | { 28 | super(exception); 29 | 30 | Throwable rootCause = exception; 31 | 32 | while (rootCause.getCause() != null && !rootCause.getCause().equals(rootCause)) 33 | { 34 | rootCause = rootCause.getCause(); 35 | } 36 | 37 | errorMessage = CANNOT_CREATE_ADAPTER + adapter.getSimpleName() + " because a(n) " + rootCause.getClass().getSimpleName() + " occurred."; 38 | } 39 | 40 | @Override 41 | public String getMessage() 42 | { 43 | return errorMessage; 44 | } 45 | } -------------------------------------------------------------------------------- /.github/workflows/validateJavaSupport.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven 3 | 4 | 5 | name: Java Support Matrix 6 | 7 | on: 8 | push: 9 | workflow_dispatch: 10 | 11 | jobs: 12 | build: 13 | 14 | runs-on: ubuntu-latest 15 | strategy: 16 | matrix: 17 | # test against supported Java version: 18 | java: [ '25'] 19 | 20 | # Service containers to run with `runner-job` 21 | services: 22 | # Label used to access the service container 23 | activemq: 24 | # Docker Hub image 25 | image: quay.io/artemiscloud/activemq-artemis-broker:latest 26 | # 27 | ports: 28 | # Opens tcp port 6379 on the host and service container 29 | - 61616:61616 30 | env: 31 | AMQ_USER: admin 32 | AMQ_PASSWORD: admin 33 | postgres: 34 | # Docker Hub image 35 | image: postgres 36 | # setup default ports 37 | ports: 38 | # Opens tcp port 5432 on the host and service container 39 | - 5432:5432 40 | # setup default user for testing 41 | env: 42 | POSTGRES_PASSWORD: admin 43 | POSTGRES_USER: postgres 44 | 45 | steps: 46 | - uses: actions/checkout@v6 47 | - name: Set up JDK ${{ matrix.java }} 48 | uses: actions/setup-java@v5 49 | with: 50 | distribution: 'temurin' 51 | java-version: ${{ matrix.java }} 52 | cache: maven 53 | 54 | - name: Maven build 55 | run: mvn '-Dmaven.compiler.release=${{ matrix.java }}' -B clean install 56 | -------------------------------------------------------------------------------- /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven 3 | 4 | 5 | name: Java 25 LTS CI 6 | 7 | on: 8 | push: 9 | workflow_dispatch: 10 | 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | strategy: 17 | matrix: 18 | # test against LTS Java version: 19 | java: [ '25' ] 20 | 21 | # Service containers to run with `runner-job` 22 | services: 23 | # Label used to access the service container 24 | activemq: 25 | # Docker Hub image 26 | image: quay.io/artemiscloud/activemq-artemis-broker:latest 27 | ports: 28 | # Opens tcp port 6379 on the host and service container 29 | - 61616:61616 30 | env: 31 | AMQ_USER: admin 32 | AMQ_PASSWORD: admin 33 | postgres: 34 | # Docker Hub image 35 | image: postgres 36 | # setup default ports 37 | ports: 38 | # Opens tcp port 5432 on the host and service container 39 | - 5432:5432 40 | # setup default user for testing 41 | env: 42 | POSTGRES_PASSWORD: admin 43 | POSTGRES_USER: postgres 44 | 45 | steps: 46 | - uses: actions/checkout@v6 47 | - name: Set up JDK ${{ matrix.java }} 48 | uses: actions/setup-java@v5 49 | with: 50 | distribution: 'temurin' 51 | java-version: ${{ matrix.java }} 52 | 53 | - name: Maven build 54 | run: mvn -B clean install 55 | 56 | - name: SonarQube Scan 57 | run: mvn sonar:sonar 58 | env: 59 | SONAR_TOKEN: 60 | ${{ secrets.SONAR_TOKEN }} -------------------------------------------------------------------------------- /docs/adr/005-strategy-pattern-for-driven-adapters.md: -------------------------------------------------------------------------------- 1 | # 5. Record architecture decisions 2 | 3 | Date: 2021-12-23 4 | 5 | ## Status 6 | 7 | Accepted 8 | 9 | ## Context 10 | 11 | Jexxa is considered as a lightweight framework that ensures that junior developers get the time to learn the craftsmanship of software development, methods of software architecture and the domain language step by step from the intermediate and senior developers. 12 | 13 | Because your senior and intermediate developers focus on the application core, your junior developers must focus on the technology stack. Therefore, the binding of the application core to a technology stack should be as simple as possible. 14 | 15 | ## Decision 16 | 17 | Jexxa provides typical implementation of application infrastructure patterns via strategies 18 | so that the implementation of driven adapters is just a simple facade, 19 | which maps between the API of outbound ports to the corresponding API of the strategy. 20 | 21 | ## Consequences 22 | 23 | * Regarding your business domain, your junior developers will learn at least the name of the most important business objects, because Aggregates include the business logic of this domain. 24 | 25 | * From a software engineering point of view, your junior developer gets familiar with the strategy design pattern. 26 | 27 | * From an architectural point of view, your junior developer gets familiar with the principal of dependency inversion. 28 | 29 | * Finally, your developers learn that they can persist data within a database without thinking about the database layout. Using a strategy pattern instead makes the database to a plugin. 30 | 31 | * As soon as your junior developers feel that they are not challenged with implementing driven adapters, give them one of the above points to study. -------------------------------------------------------------------------------- /jexxa-test/src/main/java/io/jexxa/jexxatest/infrastructure/messaging/recording/MessageRecorder.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.jexxatest.infrastructure.messaging.recording; 2 | 3 | import io.jexxa.common.facade.utils.annotation.CheckReturnValue; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | import java.util.Optional; 8 | 9 | /** 10 | * This class is the API for unit test to access and validate recorded messages. 11 | */ 12 | public class MessageRecorder 13 | { 14 | private final List recordedMessageList = new ArrayList<>(); 15 | 16 | @CheckReturnValue 17 | @SuppressWarnings("unused") 18 | public List getMessages() 19 | { 20 | return recordedMessageList; 21 | } 22 | 23 | @CheckReturnValue 24 | public Optional pop() 25 | { 26 | if (recordedMessageList.isEmpty()) 27 | { 28 | return Optional.empty(); 29 | } 30 | 31 | var latestElement = recordedMessageList.getFirst(); 32 | recordedMessageList.removeFirst(); 33 | return Optional.ofNullable(latestElement); 34 | } 35 | 36 | @CheckReturnValue 37 | public boolean isEmpty() 38 | { 39 | return recordedMessageList.isEmpty(); 40 | } 41 | 42 | @CheckReturnValue 43 | public int size() 44 | { 45 | return recordedMessageList.size(); 46 | } 47 | 48 | @CheckReturnValue 49 | public T getMessage(Class classType) 50 | { 51 | return pop().orElseThrow().getMessage(classType); 52 | } 53 | 54 | public void clear() 55 | { 56 | recordedMessageList.clear(); 57 | } 58 | 59 | void put(RecordedMessage recordedMessage) 60 | { 61 | recordedMessageList.add(recordedMessage); 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /jexxa-core/src/test/resources/jexxa-application.properties: -------------------------------------------------------------------------------- 1 | #suppress inspection "UnusedProperty" for whole file 2 | 3 | ########################################## 4 | # Adjust system properties # 5 | ########################################## 6 | # io.jexxa.user.timezone=UTC 7 | 8 | ########################################## 9 | #Settings for JMSAdapter and JMSSender # 10 | ########################################## 11 | #java.naming.factory.initial=org.apache.activemq.jndi.ActiveMQInitialContextFactory 12 | io.jexxa.java.naming.factory.initial=org.apache.activemq.artemis.jndi.ActiveMQInitialContextFactory 13 | io.jexxa.java.naming.provider.url=tcp://localhost:61616 14 | #local jms provider 15 | #io.jexxa.java.naming.provider.url=vm://localhost?broker.persistent=false 16 | io.jexxa.java.naming.user=artemis 17 | io.jexxa.java.naming.password=simetraehcapa 18 | 19 | ########################################## 20 | #Settings for JDBCConnection # 21 | ########################################## 22 | io.jexxa.jdbc.driver=org.postgresql.Driver 23 | io.jexxa.jdbc.url=jdbc:postgresql://localhost:5432/jexxatest 24 | io.jexxa.jdbc.username=postgres 25 | io.jexxa.jdbc.password=admin 26 | # Following setting is only required if you want to auto-create your database, and it is supported via connection URL. In this case you have to define a valid default URL (e.g. for testing purpose) 27 | io.jexxa.jdbc.autocreate.database=jdbc:postgresql://localhost:5432/postgres 28 | # Following setting is only required if you want to auto-create your tables (e.g. for testing purpose) 29 | io.jexxa.jdbc.autocreate.table=true 30 | 31 | #Settings for JDBCConnection to H2 DB 32 | # io.jexxa.jdbc.url=jdbc:h2:mem:hellojexxa;DB_CLOSE_DELAY=-1 33 | # io.jexxa.jdbc.driver=org.h2.Driver 34 | # io.jexxa.jdbc.username=admin 35 | # io.jexxa.jdbc.password=admin 36 | -------------------------------------------------------------------------------- /jexxa-test/src/test/java/io/jexxa/jexxatest/application/DomainEventPublisher.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.jexxatest.application; 2 | 3 | import io.jexxa.addend.applicationcore.Observer; 4 | 5 | import java.util.HashSet; 6 | import java.util.Map; 7 | import java.util.Set; 8 | import java.util.concurrent.ConcurrentHashMap; 9 | import java.util.function.Consumer; 10 | 11 | @Observer 12 | public final class DomainEventPublisher { 13 | private final Map, Set>> subscribers = new ConcurrentHashMap<>(); 14 | private static final DomainEventPublisher DOMAIN_EVENT_PUBLISHER = new DomainEventPublisher(); 15 | 16 | public static DomainEventPublisher instance() 17 | { 18 | return DOMAIN_EVENT_PUBLISHER; 19 | } 20 | 21 | @SuppressWarnings("unchecked") // We check if the given domainEvent is assignable to a listener. Therefore, the unchecked cast is safe 22 | public static synchronized void publish(final T domainEvent) 23 | { 24 | instance() 25 | .subscribers 26 | .entrySet() 27 | .stream() 28 | .filter(element -> element.getKey().isAssignableFrom(domainEvent.getClass())) 29 | .flatMap(element -> element.getValue().stream()) 30 | .forEach(element -> ((Consumer) element).accept(domainEvent)); 31 | } 32 | 33 | public static synchronized void subscribe(Class domainEvent, Consumer subscriber) 34 | { 35 | instance().subscribers.putIfAbsent(domainEvent, new HashSet<>()); 36 | instance().subscribers.get(domainEvent).add(subscriber); 37 | } 38 | 39 | public static synchronized void subscribe(Consumer subscriber) 40 | { 41 | subscribe(Object.class, subscriber); 42 | } 43 | 44 | private DomainEventPublisher() 45 | { 46 | //Private constructor 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /jexxa-core/src/test/resources/simplelogger.properties: -------------------------------------------------------------------------------- 1 | #suppress inspection "UnusedProperty" for whole file 2 | 3 | # SLF4J's SimpleLogger configuration file 4 | # Simple implementation of Logger that sends all enabled log messages, for all defined loggers, to System.err. 5 | # Default logging detail level for all instances of SimpleLogger. 6 | # Must be one of ("trace", "debug", "info", "warn", or "error"). 7 | # If not specified, defaults to "info". 8 | org.slf4j.simpleLogger.defaultLogLevel=info 9 | 10 | # Logging detail level for a SimpleLogger instance named "xxxxx". 11 | # Must be one of ("trace", "debug", "info", "warn", or "error"). 12 | # If not specified, the default logging detail level is used. 13 | #org.slf4j.simpleLogger.log.xxxxx= 14 | org.slf4j.simpleLogger.log.org.eclipse.jetty.server=warn 15 | org.slf4j.simpleLogger.log.io.javalin=warn 16 | 17 | # Set to true if you want the current date and time to be included in output messages. 18 | # Default is false, and will output the number of milliseconds elapsed since startup. 19 | org.slf4j.simpleLogger.showDateTime=true 20 | 21 | # The date and time format to be used in the output messages. 22 | # The pattern describing the date and time format is the same that is used in java.text.SimpleDateFormat. 23 | # If the format is not specified or is invalid, the default format is used. 24 | # The default format is yyyy-MM-dd HH:mm:ss:SSS Z. 25 | org.slf4j.simpleLogger.dateTimeFormat='['yyyy-MM-dd'T'HH:mmXXX']' 26 | 27 | # Set to true if you want to output the current thread name. 28 | # Defaults to true. 29 | org.slf4j.simpleLogger.showThreadName=false 30 | 31 | # Set to true if you want the Logger instance name to be included in output messages. 32 | # Defaults to true. 33 | #org.slf4j.simpleLogger.showLogName=true 34 | 35 | # Set to true if you want the Logger instance short name to be included in output messages. 36 | # Defaults to false. 37 | org.slf4j.simpleLogger.showShortLogName=true 38 | -------------------------------------------------------------------------------- /jexxa-test/src/test/resources/simplelogger.properties: -------------------------------------------------------------------------------- 1 | #suppress inspection "UnusedProperty" for whole file 2 | 3 | # SLF4J's SimpleLogger configuration file 4 | # Simple implementation of Logger that sends all enabled log messages, for all defined loggers, to System.err. 5 | # Default logging detail level for all instances of SimpleLogger. 6 | # Must be one of ("trace", "debug", "info", "warn", or "error"). 7 | # If not specified, defaults to "info". 8 | org.slf4j.simpleLogger.defaultLogLevel=info 9 | 10 | # Logging detail level for a SimpleLogger instance named "xxxxx". 11 | # Must be one of ("trace", "debug", "info", "warn", or "error"). 12 | # If not specified, the default logging detail level is used. 13 | #org.slf4j.simpleLogger.log.xxxxx= 14 | org.slf4j.simpleLogger.log.org.eclipse.jetty.server=warn 15 | org.slf4j.simpleLogger.log.io.javalin=warn 16 | 17 | # Set to true if you want the current date and time to be included in output messages. 18 | # Default is false, and will output the number of milliseconds elapsed since startup. 19 | org.slf4j.simpleLogger.showDateTime=true 20 | 21 | # The date and time format to be used in the output messages. 22 | # The pattern describing the date and time format is the same that is used in java.text.SimpleDateFormat. 23 | # If the format is not specified or is invalid, the default format is used. 24 | # The default format is yyyy-MM-dd HH:mm:ss:SSS Z. 25 | org.slf4j.simpleLogger.dateTimeFormat='['yyyy-MM-dd'T'HH:mmXXX']' 26 | 27 | # Set to true if you want to output the current thread name. 28 | # Defaults to true. 29 | org.slf4j.simpleLogger.showThreadName=false 30 | 31 | # Set to true if you want the Logger instance name to be included in output messages. 32 | # Defaults to true. 33 | #org.slf4j.simpleLogger.showLogName=true 34 | 35 | # Set to true if you want the Logger instance short name to be included in output messages. 36 | # Defaults to false. 37 | org.slf4j.simpleLogger.showShortLogName=true 38 | -------------------------------------------------------------------------------- /jexxa-web/src/test/resources/simplelogger.properties: -------------------------------------------------------------------------------- 1 | #suppress inspection "UnusedProperty" for whole file 2 | 3 | # SLF4J's SimpleLogger configuration file 4 | # Simple implementation of Logger that sends all enabled log messages, for all defined loggers, to System.err. 5 | # Default logging detail level for all instances of SimpleLogger. 6 | # Must be one of ("trace", "debug", "info", "warn", or "error"). 7 | # If not specified, defaults to "info". 8 | org.slf4j.simpleLogger.defaultLogLevel=info 9 | 10 | # Logging detail level for a SimpleLogger instance named "xxxxx". 11 | # Must be one of ("trace", "debug", "info", "warn", or "error"). 12 | # If not specified, the default logging detail level is used. 13 | #org.slf4j.simpleLogger.log.xxxxx= 14 | org.slf4j.simpleLogger.log.org.eclipse.jetty.server=warn 15 | org.slf4j.simpleLogger.log.io.javalin=warn 16 | 17 | # Set to true if you want the current date and time to be included in output messages. 18 | # Default is false, and will output the number of milliseconds elapsed since startup. 19 | org.slf4j.simpleLogger.showDateTime=true 20 | 21 | # The date and time format to be used in the output messages. 22 | # The pattern describing the date and time format is the same that is used in java.text.SimpleDateFormat. 23 | # If the format is not specified or is invalid, the default format is used. 24 | # The default format is yyyy-MM-dd HH:mm:ss:SSS Z. 25 | org.slf4j.simpleLogger.dateTimeFormat='['yyyy-MM-dd'T'HH:mmXXX']' 26 | 27 | # Set to true if you want to output the current thread name. 28 | # Defaults to true. 29 | org.slf4j.simpleLogger.showThreadName=false 30 | 31 | # Set to true if you want the Logger instance name to be included in output messages. 32 | # Defaults to true. 33 | #org.slf4j.simpleLogger.showLogName=true 34 | 35 | # Set to true if you want the Logger instance short name to be included in output messages. 36 | # Defaults to false. 37 | org.slf4j.simpleLogger.showShortLogName=true 38 | -------------------------------------------------------------------------------- /jexxa-core/src/test/java/io/jexxa/core/convention/PortConventionTest.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.core.convention; 2 | 3 | import io.jexxa.testapplication.applicationservice.InvalidConstructorApplicationService; 4 | import io.jexxa.testapplication.domain.model.JexxaEntityRepository; 5 | import org.junit.jupiter.api.Test; 6 | 7 | import java.util.Objects; 8 | import java.util.Properties; 9 | 10 | import static org.junit.jupiter.api.Assertions.assertThrows; 11 | 12 | class PortConventionTest 13 | { 14 | @Test 15 | void invalidPortConstructor() 16 | { 17 | //Arrange - Nothing 18 | 19 | //Act/Assert 20 | assertThrows(PortConventionViolation.class, () -> PortConvention.validate(InvalidConstructorApplicationService.class)); // Violation: No public constructor 21 | assertThrows(PortConventionViolation.class, () -> PortConvention.validate(InvalidApplicationServiceNoInterface.class)); // Violation: Constructor does not take interfaces as argument 22 | assertThrows(PortConventionViolation.class, () -> PortConvention.validate(InvalidApplicationServiceMultipleConstructor.class)); // Violation: multiple constructor available 23 | } 24 | 25 | public static class InvalidApplicationServiceMultipleConstructor 26 | { 27 | @SuppressWarnings("unused") 28 | public InvalidApplicationServiceMultipleConstructor() 29 | { 30 | //Empty constructor for testing purpose 31 | } 32 | 33 | @SuppressWarnings("unused") 34 | public InvalidApplicationServiceMultipleConstructor(JexxaEntityRepository jexxaAggregateRepository) 35 | { 36 | Objects.requireNonNull(jexxaAggregateRepository); 37 | } 38 | } 39 | 40 | public static class InvalidApplicationServiceNoInterface 41 | { 42 | public InvalidApplicationServiceNoInterface(Properties properties) 43 | { 44 | Objects.requireNonNull(properties); 45 | } 46 | 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /jexxa-core/src/main/java/io/jexxa/core/factory/MissingAdapterException.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.core.factory; 2 | 3 | import java.io.Serial; 4 | import java.util.ArrayList; 5 | import java.util.Arrays; 6 | 7 | public class MissingAdapterException extends RuntimeException 8 | { 9 | @Serial 10 | private static final long serialVersionUID = 1L; 11 | 12 | private final String internalMessage; 13 | 14 | MissingAdapterException(Class port, AdapterFactory drivenAdapterFactory) 15 | { 16 | internalMessage = getInternalMessage(port, drivenAdapterFactory); 17 | } 18 | 19 | @Override 20 | public String getMessage() 21 | { 22 | return internalMessage; 23 | } 24 | 25 | 26 | private String getInternalMessage(Class port, AdapterFactory drivenAdapterFactory) 27 | { 28 | var stringBuilder = new StringBuilder(); 29 | stringBuilder.append("Could not create port: ") 30 | .append(port.getName()).append("\n") 31 | .append("Missing DrivenAdapter:\n"); 32 | 33 | 34 | var missingAdapters = new ArrayList>(); 35 | Arrays.asList(port.getConstructors()) 36 | .forEach( element -> 37 | missingAdapters.addAll(drivenAdapterFactory.getMissingAdapter(Arrays.asList(element.getParameterTypes()))) 38 | ); 39 | 40 | if ( missingAdapters.isEmpty() ) 41 | { 42 | stringBuilder.append(" * ").append("\n"); 43 | } else { 44 | missingAdapters.forEach( missingAdapter -> stringBuilder.append(" * ").append(missingAdapter.getName()).append("\n") ); 45 | } 46 | 47 | stringBuilder.append("\n Please check accepted packages. Current accepted packages: \n"); 48 | var acceptedPackages = drivenAdapterFactory.getAcceptPackages(); 49 | acceptedPackages.forEach( element -> stringBuilder.append(" * ").append(element).append("\n") ); 50 | 51 | return stringBuilder.toString(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /jexxa-core/src/test/java/io/jexxa/testapplication/infrastructure/drivenadapter/persistence/GenericRepositoryImpl.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.testapplication.infrastructure.drivenadapter.persistence; 2 | 3 | import io.jexxa.common.drivenadapter.persistence.repository.IRepository; 4 | 5 | import java.util.List; 6 | import java.util.Optional; 7 | import java.util.Properties; 8 | import java.util.function.Function; 9 | 10 | import static io.jexxa.common.drivenadapter.persistence.RepositoryFactory.createRepository; 11 | 12 | /** 13 | * Generic implementation of a Repository including typical methods 14 | * 15 | * @param represents the type of the Aggregate 16 | * @param represents the type of the AggregateID 17 | */ 18 | public class GenericRepositoryImpl 19 | { 20 | 21 | private final Function keyFunction; 22 | private final IRepository repository; 23 | 24 | public GenericRepositoryImpl(Class aggregateClass, 25 | Function keyFunction, 26 | Properties properties) 27 | { 28 | this.keyFunction = keyFunction; 29 | this.repository = createRepository( 30 | aggregateClass, 31 | keyFunction, 32 | properties); 33 | } 34 | 35 | public void add(A jexxaEntity) 36 | { 37 | repository.add(jexxaEntity); 38 | } 39 | 40 | public A get(I aggregateID) 41 | { 42 | return repository.get(aggregateID).orElseThrow(); 43 | } 44 | 45 | @SuppressWarnings("unused") 46 | public Optional find(I aggregateID) 47 | { 48 | return repository.get(aggregateID); 49 | } 50 | 51 | public List get() 52 | { 53 | return repository.get(); 54 | } 55 | 56 | public void update(A aggregate) 57 | { 58 | repository.update(aggregate); 59 | } 60 | 61 | public void remove(A aggregate) 62 | { 63 | repository.remove(keyFunction.apply(aggregate)); 64 | } 65 | 66 | public void removeAll() 67 | { 68 | repository.removeAll(); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /jexxa-web/src/main/java/io/jexxa/drivingadapter/rest/JexxaWebProperties.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.drivingadapter.rest; 2 | 3 | public final class JexxaWebProperties 4 | { 5 | /** 6 | * Defines the listening address used by the driving adapter 7 | */ 8 | public static final String JEXXA_REST_HOST = "io.jexxa.rest.host"; 9 | 10 | /** 11 | * Defines the http-port used by the driving adapter 12 | */ 13 | public static final String JEXXA_REST_PORT = "io.jexxa.rest.port"; 14 | 15 | /** 16 | * Defines the https-port used by the driving adapter 17 | */ 18 | public static final String JEXXA_REST_HTTPS_PORT = "io.jexxa.rest.https.port"; 19 | 20 | /** 21 | * Defines the location of the keystore that includes https-certifcates 22 | */ 23 | public static final String JEXXA_REST_KEYSTORE = "io.jexxa.rest.keystore.location"; 24 | 25 | /** 26 | * Defines the password of the keystore 27 | */ 28 | public static final String JEXXA_REST_KEYSTORE_PASSWORD = "io.jexxa.rest.keystore.password"; 29 | 30 | /** 31 | * Defines a file that includes the password of the keystore 32 | */ 33 | public static final String JEXXA_REST_FILE_KEYSTORE_PASSWORD = "io.jexxa.rest.keystore.file.password"; 34 | 35 | /** 36 | * Defines the path for the autogenerated OpenAPI 37 | */ 38 | public static final String JEXXA_REST_OPEN_API_PATH = "io.jexxa.rest.openapi.path"; 39 | /** 40 | * Defines the path for the autogenerated OpenAPI 41 | */ 42 | public static final String JEXXA_REST_OPEN_API_SERVERS = "io.jexxa.rest.openapi.servers"; 43 | 44 | /** 45 | * Defines the directory path to the static files provided via http(s) included in the jar 46 | */ 47 | public static final String JEXXA_REST_STATIC_FILES_ROOT = "io.jexxa.rest.static.files.root"; 48 | 49 | /** 50 | * Defines the directory path to the static files provided via http(s) outside the jar 51 | */ 52 | public static final String JEXXA_REST_STATIC_FILES_EXTERNAL = "io.jexxa.rest.static.files.external"; 53 | 54 | private JexxaWebProperties() 55 | { 56 | //private constructor 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /jexxa-test/src/main/java/io/jexxa/jexxatest/integrationtest/messaging/JMSBinding.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.jexxatest.integrationtest.messaging; 2 | 3 | 4 | import io.jexxa.common.drivenadapter.messaging.MessageSender; 5 | import io.jexxa.common.drivenadapter.messaging.MessageSenderFactory; 6 | import io.jexxa.common.drivenadapter.messaging.jms.JMSSender; 7 | import io.jexxa.common.drivingadapter.messaging.jms.JMSAdapter; 8 | import io.jexxa.common.drivingadapter.messaging.jms.JMSConfiguration; 9 | import io.jexxa.jexxatest.integrationtest.Listener; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | import java.util.Properties; 14 | 15 | import static io.jexxa.common.drivenadapter.messaging.MessageSenderFactory.createMessageSender; 16 | 17 | public class JMSBinding implements AutoCloseable 18 | { 19 | private final List adapterList = new ArrayList<>(); 20 | private final Properties properties; 21 | private final MessageSender messageSender; 22 | 23 | 24 | public JMSBinding(Class application, Properties properties) 25 | { 26 | MessageSenderFactory.setMessageSender(JMSSender.class, application); 27 | this.properties = properties; 28 | this.messageSender = createMessageSender(application, properties); 29 | } 30 | 31 | public MessageSender getSender() 32 | { 33 | return messageSender; 34 | } 35 | 36 | public Listener getListener(String destination, JMSConfiguration.MessagingType messagingType) 37 | { 38 | var jmsListener = new JMSListener(destination, messagingType); 39 | var jmsAdapter = new JMSAdapter(properties); 40 | jmsAdapter.register(jmsListener); 41 | jmsAdapter.start(); 42 | adapterList.add(jmsAdapter); 43 | 44 | return jmsListener; 45 | } 46 | 47 | public void registerListener(Listener listener) 48 | { 49 | var jmsAdapter = new JMSAdapter(properties); 50 | jmsAdapter.register(listener); 51 | jmsAdapter.start(); 52 | adapterList.add(jmsAdapter); 53 | } 54 | 55 | @Override 56 | public void close() { 57 | adapterList.forEach(JMSAdapter::stop); 58 | adapterList.clear(); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /jexxa-test/src/main/java/io/jexxa/jexxatest/integrationtest/messaging/JMSListener.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.jexxatest.integrationtest.messaging; 2 | 3 | import io.jexxa.common.drivingadapter.messaging.jms.DefaultJMSConfiguration; 4 | import io.jexxa.common.drivingadapter.messaging.jms.JMSConfiguration; 5 | import io.jexxa.common.drivingadapter.messaging.jms.listener.JSONMessageListener; 6 | import io.jexxa.jexxatest.integrationtest.Listener; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | import java.util.concurrent.TimeUnit; 11 | 12 | import static org.awaitility.Awaitility.await; 13 | 14 | public class JMSListener extends JSONMessageListener implements Listener { 15 | private final List messageList = new ArrayList<>(); 16 | private final String topicDestination; 17 | private final JMSConfiguration.MessagingType messagingType; 18 | 19 | public JMSListener(String topicDestination, JMSConfiguration.MessagingType messagingType) 20 | { 21 | this.topicDestination = topicDestination; 22 | this.messagingType = messagingType; 23 | } 24 | 25 | @Override 26 | public void onMessage(String message) 27 | { 28 | messageList.add(message); 29 | } 30 | 31 | @Override 32 | public List getAll() 33 | { 34 | return messageList; 35 | } 36 | 37 | @Override 38 | public void clear() 39 | { 40 | messageList.clear(); 41 | } 42 | @Override 43 | public T pop(Class clazz) 44 | { 45 | if (messageList.isEmpty()) 46 | { 47 | return null; 48 | } 49 | 50 | return fromJson( messageList.removeFirst(), clazz); 51 | } 52 | 53 | @Override 54 | public JMSListener awaitMessage(int timeout, TimeUnit timeUnit) 55 | { 56 | await().atMost(timeout, timeUnit) 57 | .pollDelay(100, TimeUnit.MILLISECONDS) 58 | .until(() -> !getAll().isEmpty()); 59 | 60 | return this; 61 | } 62 | 63 | @SuppressWarnings("unused") // Used by JMSAdapter 64 | public JMSConfiguration getConfiguration() 65 | { 66 | return new DefaultJMSConfiguration(topicDestination, messagingType); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /jexxa-core/src/test/java/io/jexxa/core/convention/AdapterConventionTest.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.core.convention; 2 | 3 | import io.jexxa.testapplication.applicationservice.SimpleApplicationService; 4 | import io.jexxa.testapplication.infrastructure.drivenadapter.factory.ValidDefaultConstructorServiceImpl; 5 | import io.jexxa.testapplication.infrastructure.drivenadapter.factory.ValidFactoryMethodServiceImpl; 6 | import io.jexxa.testapplication.infrastructure.drivenadapter.factory.ValidPropertiesConstructorServiceImpl; 7 | import io.jexxa.testapplication.infrastructure.drivingadapter.generic.InvalidDrivingAdapter; 8 | import io.jexxa.testapplication.infrastructure.drivingadapter.portadapter.PortAdapter; 9 | import io.jexxa.testapplication.infrastructure.drivingadapter.portadapter.PortAdapterWithProperties; 10 | import org.junit.jupiter.api.Test; 11 | 12 | import java.util.List; 13 | 14 | import static org.junit.jupiter.api.Assertions.assertFalse; 15 | import static org.junit.jupiter.api.Assertions.assertThrows; 16 | import static org.junit.jupiter.api.Assertions.assertTrue; 17 | 18 | class AdapterConventionTest 19 | { 20 | @Test 21 | void validateAdapterConvention() 22 | { 23 | //Arrange - Nothing 24 | 25 | //Assert all Adapter conventions 26 | AdapterConvention.validate(ValidDefaultConstructorServiceImpl.class); 27 | 28 | AdapterConvention.validate(ValidFactoryMethodServiceImpl.class); 29 | 30 | AdapterConvention.validate(ValidPropertiesConstructorServiceImpl.class); 31 | 32 | assertThrows(AdapterConventionViolation.class, () -> AdapterConvention.validate(InvalidDrivingAdapter.class)); 33 | } 34 | 35 | @Test 36 | void validatePortAdapterConvention() 37 | { 38 | //Arrange 39 | var infrastructure = List.of("io.jexxa.testapplication.infrastructure.drivingadapter"); 40 | 41 | //Assert all port adapter conventions 42 | assertTrue(AdapterConvention.isPortAdapter(PortAdapter.class, infrastructure)); 43 | assertTrue(AdapterConvention.isPortAdapter(PortAdapterWithProperties.class, infrastructure)); 44 | assertFalse(AdapterConvention.isPortAdapter(SimpleApplicationService.class, infrastructure)); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /jexxa-test/src/main/java/io/jexxa/jexxatest/integrationtest/rest/RESTBinding.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.jexxatest.integrationtest.rest; 2 | 3 | import io.jexxa.common.facade.json.JSONManager; 4 | import io.jexxa.core.BoundedContext; 5 | import kong.unirest.GenericType; 6 | import kong.unirest.ObjectMapper; 7 | import kong.unirest.Unirest; 8 | import kong.unirest.UnirestException; 9 | 10 | import java.util.Properties; 11 | import java.util.concurrent.TimeUnit; 12 | 13 | import static org.awaitility.Awaitility.await; 14 | 15 | public class RESTBinding implements AutoCloseable 16 | { 17 | 18 | private final Properties properties; 19 | 20 | @SuppressWarnings("java:S1172") 21 | public RESTBinding(Class ignoredApplication, Properties properties) 22 | { 23 | Unirest.config().setObjectMapper(new UnirestObjectMapper()); 24 | this.properties = properties; 25 | var boundedContext = new BoundedContextHandler(properties, BoundedContext.class); 26 | await().atMost(10, TimeUnit.SECONDS) 27 | .pollDelay(100, TimeUnit.MILLISECONDS) 28 | .ignoreException(UnirestException.class) 29 | .until(boundedContext::isRunning); 30 | } 31 | 32 | public RESTHandler getRESTHandler(Class endpoint) 33 | { 34 | return new RESTHandler(properties, endpoint); 35 | } 36 | 37 | public BoundedContextHandler getBoundedContext() 38 | { 39 | return new BoundedContextHandler(properties, BoundedContext.class); 40 | } 41 | 42 | @Override 43 | public void close() { 44 | // Nothing to implement here 45 | } 46 | 47 | private static class UnirestObjectMapper implements ObjectMapper 48 | { 49 | @Override 50 | public T readValue(String value, Class valueType) 51 | { 52 | return JSONManager.getJSONConverter().fromJson(value, valueType); 53 | } 54 | 55 | @Override 56 | public T readValue(String value, GenericType genericType) 57 | { 58 | return JSONManager.getJSONConverter().fromJson(value, genericType.getType()); 59 | } 60 | 61 | @Override 62 | public String writeValue(Object value) 63 | { 64 | return JSONManager.getJSONConverter().toJson(value); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /jexxa-core/src/main/java/io/jexxa/core/FluentInterceptor.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.core; 2 | 3 | import io.jexxa.adapterapi.invocation.InvocationContext; 4 | import io.jexxa.adapterapi.invocation.InvocationManager; 5 | 6 | import java.util.ArrayList; 7 | import java.util.Arrays; 8 | import java.util.Collection; 9 | import java.util.function.Consumer; 10 | 11 | @SuppressWarnings("unused") 12 | public final class FluentInterceptor 13 | { 14 | private final Collection targetObjects = new ArrayList<>(); 15 | private final JexxaMain jexxaMain; 16 | 17 | FluentInterceptor(JexxaMain jexxaMain, Object... targetObjects ) 18 | { 19 | this.targetObjects.addAll(Arrays.asList(targetObjects)); 20 | 21 | this.jexxaMain = jexxaMain; 22 | } 23 | 24 | @SuppressWarnings("UnusedReturnValue") 25 | public JexxaMain before(Consumer consumer) 26 | { 27 | targetObjects.stream() 28 | .map(InvocationManager::getRootInterceptor) 29 | .forEach( interceptor -> interceptor.registerBefore(consumer::accept)); 30 | 31 | return jexxaMain; 32 | } 33 | 34 | public FluentInterceptor beforeAnd(Consumer consumer) 35 | { 36 | before(consumer); 37 | return this; 38 | } 39 | 40 | @SuppressWarnings("UnusedReturnValue") 41 | public JexxaMain after(Consumer consumer) 42 | { 43 | targetObjects.stream() 44 | .map(InvocationManager::getRootInterceptor) 45 | .forEach( interceptor -> interceptor.registerAfter(consumer::accept)); 46 | 47 | return jexxaMain; 48 | } 49 | 50 | @SuppressWarnings("UnusedReturnValue") 51 | public FluentInterceptor afterAnd(Consumer consumer) 52 | { 53 | after(consumer); 54 | return this; 55 | } 56 | 57 | @SuppressWarnings("UnusedReturnValue") 58 | public JexxaMain around(Consumer consumer) 59 | { 60 | targetObjects.stream() 61 | .map(InvocationManager::getRootInterceptor) 62 | .forEach( interceptor -> interceptor.registerAround(consumer::accept)); 63 | 64 | return jexxaMain; 65 | } 66 | 67 | @SuppressWarnings("UnusedReturnValue") 68 | public FluentInterceptor aroundAnd(Consumer consumer) 69 | { 70 | around(consumer); 71 | return this; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /jexxa-core/src/main/java/io/jexxa/core/DrivingAdapter.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.core; 2 | 3 | import io.jexxa.adapterapi.drivingadapter.IDrivingAdapter; 4 | import io.jexxa.core.convention.AdapterConvention; 5 | import io.jexxa.core.convention.PortConvention; 6 | 7 | import java.lang.annotation.Annotation; 8 | import java.util.Objects; 9 | import java.util.function.BooleanSupplier; 10 | 11 | public final class DrivingAdapter 12 | { 13 | private final JexxaMain jexxaMain; 14 | private final Class drivingAdapterClass; 15 | private final BooleanSupplier conditionalBind; 16 | 17 | DrivingAdapter(Class drivingAdapterClass, JexxaMain jexxaMain) 18 | { 19 | this(() -> true, drivingAdapterClass, jexxaMain); 20 | } 21 | 22 | DrivingAdapter(BooleanSupplier conditionalBind, Class drivingAdapterClass, JexxaMain jexxaMain) 23 | { 24 | AdapterConvention.validate(drivingAdapterClass); 25 | 26 | this.drivingAdapterClass = Objects.requireNonNull(drivingAdapterClass); 27 | this.jexxaMain = Objects.requireNonNull(jexxaMain); 28 | this.conditionalBind = Objects.requireNonNull(conditionalBind); 29 | } 30 | 31 | public

    JexxaMain to(Class

    port) 32 | { 33 | Objects.requireNonNull(port); 34 | 35 | if ( !conditionalBind.getAsBoolean()) 36 | { 37 | return jexxaMain; 38 | } 39 | 40 | if ( AdapterConvention.isPortAdapter(port, jexxaMain.getInfrastructure())) 41 | { 42 | jexxaMain.bindToPortAdapter(drivingAdapterClass, port); 43 | return jexxaMain; 44 | } 45 | 46 | PortConvention.validate(port); 47 | 48 | jexxaMain.bindToPort(drivingAdapterClass, port); 49 | 50 | return jexxaMain; 51 | } 52 | 53 | public JexxaMain to(Object port) 54 | { 55 | Objects.requireNonNull(port); 56 | 57 | if ( !conditionalBind.getAsBoolean()) 58 | { 59 | return jexxaMain; 60 | } 61 | 62 | return jexxaMain.bindToPort(drivingAdapterClass, port); 63 | } 64 | 65 | public

    JexxaMain toAnnotation(Class

    annotation) 66 | { 67 | Objects.requireNonNull(annotation); 68 | 69 | if ( !conditionalBind.getAsBoolean()) 70 | { 71 | return jexxaMain; 72 | } 73 | 74 | jexxaMain.bindToAnnotatedPorts(drivingAdapterClass, annotation); 75 | return jexxaMain; 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /jexxa-test/src/main/java/io/jexxa/jexxatest/architecture/ProjectContent.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.jexxatest.architecture; 2 | 3 | import com.tngtech.archunit.base.DescribedPredicate; 4 | import com.tngtech.archunit.core.domain.JavaClass; 5 | import com.tngtech.archunit.core.domain.JavaClasses; 6 | import com.tngtech.archunit.core.importer.ClassFileImporter; 7 | import com.tngtech.archunit.core.importer.ImportOption; 8 | 9 | @SuppressWarnings("unused") 10 | public abstract class ProjectContent 11 | { 12 | private final Class project; 13 | private JavaClasses importedClasses; 14 | 15 | protected ProjectContent(Class project, ImportOption importOption) 16 | { 17 | this.project = project; 18 | importedClasses = new ClassFileImporter() 19 | .withImportOption(importOption) 20 | .importPackages( 21 | project.getPackageName()+ ".domain..", 22 | project.getPackageName()+ ".domainservice..", 23 | project.getPackageName()+ ".applicationservice..", 24 | project.getPackageName()+ ".infrastructure.." ); 25 | } 26 | 27 | public ProjectContent ignoreClass(Class clazz) 28 | { 29 | importedClasses = importedClasses.that(isNot(clazz)); 30 | return this; 31 | } 32 | 33 | public ProjectContent ignorePackage(String packageName) 34 | { 35 | importedClasses = importedClasses.that(areNotIn(packageName)); 36 | return this; 37 | } 38 | public abstract void validate(); 39 | 40 | protected JavaClasses importedClasses() 41 | { 42 | return importedClasses; 43 | } 44 | 45 | protected Class project() 46 | { 47 | return project; 48 | } 49 | private static DescribedPredicate isNot(Classclazz) { 50 | return new DescribedPredicate<>("Ignore class " + clazz.getSimpleName()) { 51 | @Override 52 | public boolean test(JavaClass javaClass) { 53 | return !javaClass.isEquivalentTo(clazz); 54 | } 55 | }; 56 | } 57 | 58 | private static DescribedPredicate areNotIn(String packageName) { 59 | return new DescribedPredicate<>("Ignore package " + packageName) { 60 | @Override 61 | public boolean test(JavaClass javaClass) { 62 | return !javaClass.getPackage().getName().contains(packageName); 63 | } 64 | }; 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /jexxa-test/src/main/java/io/jexxa/jexxatest/infrastructure/messaging/recording/MessageRecordingStrategy.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.jexxatest.infrastructure.messaging.recording; 2 | 3 | import io.jexxa.common.facade.utils.annotation.CheckReturnValue; 4 | import io.jexxa.common.drivenadapter.messaging.DestinationType; 5 | import io.jexxa.common.drivenadapter.messaging.MessageBuilder; 6 | import io.jexxa.common.drivenadapter.messaging.MessageSender; 7 | 8 | import java.util.Objects; 9 | import java.util.Properties; 10 | 11 | public class MessageRecordingStrategy extends MessageSender 12 | { 13 | private Object currentMessage; 14 | private MessageRecorder messageRecorder; 15 | 16 | @CheckReturnValue 17 | @Override 18 | public MessageBuilder send(T message) 19 | { 20 | Objects.requireNonNull(message); 21 | 22 | //Get a caller object of this class. Here we assume that it is the implementation of a driven adapter 23 | var walker = StackWalker 24 | .getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE); 25 | Class callerClass = walker.getCallerClass(); 26 | 27 | currentMessage = message; 28 | messageRecorder = MessageRecorderManager.getMessageRecorder(callerClass); 29 | return new RecordableMessageBuilder(message, this); 30 | } 31 | 32 | @Override 33 | protected void sendToQueue(String message, String destination, Properties messageProperties, MessageType messageType) 34 | { 35 | messageRecorder.put(new RecordedMessage( 36 | currentMessage, 37 | message, 38 | DestinationType.QUEUE, 39 | destination, 40 | messageProperties, 41 | messageType) 42 | ); 43 | } 44 | 45 | @Override 46 | protected void sendToTopic(String message, String destination, Properties messageProperties, MessageType messageType) 47 | { 48 | messageRecorder.put(new RecordedMessage( 49 | currentMessage, 50 | message, 51 | DestinationType.TOPIC, 52 | destination, 53 | messageProperties, 54 | messageType) 55 | ); 56 | } 57 | 58 | private static class RecordableMessageBuilder extends MessageBuilder 59 | { 60 | protected RecordableMessageBuilder(T message, MessageRecordingStrategy jmsSender) 61 | { 62 | super(message, jmsSender, MessageType.TEXT_MESSAGE); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /jexxa-test/src/main/java/io/jexxa/jexxatest/JexxaIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.jexxatest; 2 | 3 | import io.jexxa.common.facade.logger.SLF4jLogger; 4 | import io.jexxa.core.JexxaMain; 5 | import kong.unirest.Unirest; 6 | 7 | import java.lang.reflect.Constructor; 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | import java.util.Properties; 11 | 12 | import static io.jexxa.jexxatest.JexxaTest.loadJexxaTestProperties; 13 | 14 | public class JexxaIntegrationTest 15 | { 16 | private final Properties properties; 17 | private final Class application; 18 | private final JexxaMain jexxaMain; 19 | private final Map, AutoCloseable> bindingMap = new HashMap<>(); 20 | 21 | public JexxaIntegrationTest(Class application) 22 | { 23 | this.application = application; 24 | jexxaMain = new JexxaMain(application); 25 | jexxaMain.addProperties( loadJexxaTestProperties() ); 26 | this.properties = jexxaMain.getProperties(); 27 | } 28 | 29 | public T getBinding(Class bindingClazz) 30 | { 31 | bindingMap.putIfAbsent(bindingClazz, createInstance(bindingClazz, application, properties)); 32 | return bindingClazz.cast(bindingMap.get(bindingClazz)); 33 | } 34 | 35 | public Properties getProperties() { 36 | return properties; 37 | } 38 | 39 | public void shutDown() 40 | { 41 | bindingMap.values().forEach(this::close); 42 | bindingMap.clear(); 43 | Unirest.shutDown(); 44 | jexxaMain.stop(); 45 | } 46 | 47 | private void close(AutoCloseable autoCloseable) 48 | { 49 | try{ 50 | autoCloseable.close(); 51 | } catch (Exception e){ 52 | SLF4jLogger.getLogger(JexxaIntegrationTest.class).error("Could not close Binding {}", e.getMessage()); 53 | } 54 | } 55 | 56 | private static T createInstance(Class clazz, Class targetClass, Properties props) { 57 | try { 58 | // Suche den Konstruktor (Class, Properties) 59 | Constructor constructor = clazz.getConstructor(Class.class, Properties.class); 60 | 61 | // Erzeuge Instanz 62 | return constructor.newInstance(targetClass, props); 63 | 64 | } catch (NoSuchMethodException e) { 65 | throw new IllegalArgumentException("Class " + clazz.getName() + 66 | " does not provide a constructor of (Class, Properties).", e); 67 | } catch (Exception e) { 68 | throw new IllegalArgumentException("Given properties can not be used to create " + clazz.getName(), e); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /jexxa-test/src/test/java/io/jexxa/jexxatest/architecture/ArchitectureTest.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.jexxatest.architecture; 2 | 3 | import com.tngtech.archunit.core.importer.ImportOption; 4 | import io.jexxa.jexxatest.architecture.invalidapplication.InvalidApplication; 5 | import io.jexxa.jexxatest.architecture.validapplication.ValidApplication; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import static org.junit.jupiter.api.Assertions.assertThrows; 9 | 10 | class ArchitectureTest { 11 | 12 | @Test 13 | void validateOnionArchitecture() 14 | { 15 | var objectUnderTest = new PortsAndAdapters(ValidApplication.class, ImportOption.Predefined.ONLY_INCLUDE_TESTS) 16 | .addDrivenAdapterPackage("jms") 17 | 18 | .addDrivingAdapterPackage("rest") 19 | .addDrivingAdapterPackage("jms"); 20 | 21 | objectUnderTest.validate(); 22 | } 23 | 24 | @Test 25 | void validatePatternLanguage() 26 | { 27 | var objectUnderTest = new PatternLanguage(ValidApplication.class, ImportOption.Predefined.ONLY_INCLUDE_TESTS); 28 | objectUnderTest.validate(); 29 | } 30 | 31 | @Test 32 | void validateAggregates() 33 | { 34 | var objectUnderTest = new AggregateRules(ValidApplication.class, ImportOption.Predefined.ONLY_INCLUDE_TESTS); 35 | objectUnderTest.validate(); 36 | } 37 | 38 | @Test 39 | void validateInvalidOnionArchitecture() 40 | { 41 | var objectUnderTest = new PortsAndAdapters(InvalidApplication.class, ImportOption.Predefined.ONLY_INCLUDE_TESTS) 42 | .addDrivingAdapterPackage("rest"); 43 | assertThrows(AssertionError.class, objectUnderTest::validate); 44 | } 45 | 46 | @Test 47 | void validateInvalidPatternLanguage() 48 | { 49 | var objectUnderTest = new PatternLanguage(InvalidApplication.class, ImportOption.Predefined.ONLY_INCLUDE_TESTS); 50 | assertThrows(AssertionError.class, objectUnderTest::validate); 51 | } 52 | 53 | @Test 54 | void validateInvalidAggregates() 55 | { 56 | var objectUnderTest = new AggregateRules(InvalidApplication.class, ImportOption.Predefined.ONLY_INCLUDE_TESTS); 57 | 58 | assertThrows(AssertionError.class, objectUnderTest::validateOnlyAggregatesHaveAggregatesAsFields); 59 | assertThrows(AssertionError.class, objectUnderTest::validateOnlyAggregatesAndNestedClassesAreMutable); 60 | assertThrows(AssertionError.class, objectUnderTest::validateOnlyRepositoriesAcceptAggregates); 61 | assertThrows(AssertionError.class, objectUnderTest::validateReturnAggregates); 62 | assertThrows(AssertionError.class, objectUnderTest::validateAggregateID); 63 | assertThrows(AssertionError.class, objectUnderTest::validate); 64 | } 65 | } -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '0 11 * * 4' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'java' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://git.io/codeql-language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v6 42 | 43 | - name: Setup Java 44 | uses: actions/setup-java@v5 45 | with: 46 | distribution: 'temurin' 47 | java-version: '25' 48 | 49 | # Initializes the CodeQL tools for scanning. 50 | - name: Initialize CodeQL 51 | uses: github/codeql-action/init@v4 52 | with: 53 | languages: ${{ matrix.language }} 54 | queries: security-and-quality 55 | # If you wish to specify custom queries, you can do so here or in a config file. 56 | # By default, queries listed here will override any specified in a config file. 57 | # Prefix the list here with "+" to use these queries and those in the config file. 58 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 59 | 60 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 61 | # If this step fails, then you should remove it and run the build manually (see below) 62 | - name: Autobuild 63 | uses: github/codeql-action/autobuild@v4 64 | 65 | # ℹ️ Command-line programs to run using the OS shell. 66 | # 📚 https://git.io/JvXDl 67 | 68 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 69 | # and modify them (or add more) to build your code if your project 70 | # uses a compiled language 71 | 72 | #- run: | 73 | # make bootstrap 74 | # make release 75 | 76 | - name: Perform CodeQL Analysis 77 | uses: github/codeql-action/analyze@v4 78 | -------------------------------------------------------------------------------- /jexxa-core/src/main/java/io/jexxa/core/factory/DependencyScanner.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.core.factory; 2 | 3 | import io.github.classgraph.ClassGraph; 4 | import io.github.classgraph.ScanResult; 5 | 6 | import java.lang.annotation.Annotation; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.RetentionPolicy; 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | import java.util.Objects; 12 | 13 | final class DependencyScanner 14 | { 15 | private final List acceptedPackages = new ArrayList<>(); 16 | private ScanResult scanResult; 17 | 18 | DependencyScanner acceptPackage(String packageName) 19 | { 20 | acceptedPackages.add(packageName); 21 | scanResult = null; //Reset scan result so that it is recreated with new accepted packages 22 | return this; 23 | } 24 | 25 | List getAcceptPackages() 26 | { 27 | return acceptedPackages; 28 | } 29 | 30 | List> getClassesWithAnnotation(final Class annotation) 31 | { 32 | validateRetentionRuntime(annotation); 33 | 34 | return getScanResult() 35 | .getClassesWithAnnotation(annotation.getName()) 36 | .loadClasses(); 37 | } 38 | 39 | 40 | List> getClassesImplementing(final Class interfaceType) 41 | { 42 | Objects.requireNonNull(interfaceType); 43 | return getScanResult() 44 | .getClassesImplementing(interfaceType.getName()) 45 | .loadClasses(); 46 | } 47 | 48 | 49 | 50 | private void validateRetentionRuntime(final Class annotation) { 51 | Objects.requireNonNull(annotation.getAnnotation(Retention.class), "Annotation must be declared with '@Retention(RUNTIME)'" ); 52 | if (!annotation.getAnnotation(Retention.class).value().equals(RetentionPolicy.RUNTIME)) 53 | { 54 | throw new IllegalArgumentException("Annotation must be declared with '@Retention(RUNTIME)"); 55 | } 56 | } 57 | 58 | private ScanResult getScanResult() 59 | { 60 | if ( scanResult == null ) 61 | { 62 | if (acceptedPackages.isEmpty()) 63 | { 64 | scanResult = new ClassGraph() 65 | .enableAnnotationInfo() 66 | .enableClassInfo() 67 | .scan(); 68 | } 69 | else 70 | { 71 | scanResult = new ClassGraph() 72 | .enableAnnotationInfo() 73 | .enableClassInfo() 74 | .acceptPackages(acceptedPackages.toArray(new String[0])) 75 | .scan(); 76 | 77 | } 78 | } 79 | 80 | return scanResult; 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /jexxa-core/src/test/java/io/jexxa/testapplication/domain/model/JexxaObject.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.testapplication.domain.model; 2 | 3 | import java.util.Objects; 4 | 5 | import static java.lang.Math.floor; 6 | import static java.lang.Math.log; 7 | 8 | public final class JexxaObject 9 | { 10 | private final JexxaEntity jexxaEntity; 11 | private final JexxaValueObject jexxaValueObject; 12 | private JexxaValueObject optionalJexxaValue; 13 | private String optionalString; 14 | private final String internalString; 15 | 16 | public void setOptionalValue(JexxaValueObject optionalJexxaValue) 17 | { 18 | this.optionalJexxaValue = optionalJexxaValue; 19 | } 20 | 21 | public void setOptionalString(String optionalString) 22 | { 23 | this.optionalString = optionalString; 24 | } 25 | 26 | public String getOptionalString() 27 | { 28 | return optionalString; 29 | } 30 | 31 | public String getString() 32 | { 33 | return internalString; 34 | } 35 | 36 | public JexxaValueObject getOptionalValue() 37 | { 38 | return optionalJexxaValue; 39 | } 40 | 41 | private JexxaObject(JexxaValueObject jexxaValueObject, String internalString) 42 | { 43 | this.jexxaEntity = JexxaEntity.create(jexxaValueObject); 44 | this.jexxaValueObject = jexxaValueObject; 45 | this.internalString = internalString; 46 | this.optionalString = null; 47 | this.optionalJexxaValue = null; 48 | } 49 | 50 | // Create a sequence of chars 'A' .. 'Z', 'AA', ... 51 | public static String createCharSequence(int n) { 52 | var counter = n; 53 | char[] buf = new char[(int) floor(log(25 * (counter + 1)) / log(26))]; 54 | for (int i = buf.length - 1; i >= 0; i--) 55 | { 56 | counter--; 57 | buf[i] = (char) ('A' + counter % 26); 58 | counter /= 26; 59 | } 60 | return new String(buf); 61 | } 62 | 63 | public void setInternalValue(int value) 64 | { 65 | jexxaEntity.setInternalValue(value); 66 | } 67 | 68 | public int getInternalValue() 69 | { 70 | return jexxaEntity.getInternalValue(); 71 | } 72 | 73 | public JexxaValueObject getKey() 74 | { 75 | return jexxaValueObject; 76 | } 77 | 78 | public static JexxaObject create(JexxaValueObject key) 79 | { 80 | return new JexxaObject(key, createCharSequence(key.getValue())); 81 | } 82 | 83 | @Override 84 | public boolean equals(Object o) 85 | { 86 | if (this == o) 87 | { 88 | return true; 89 | } 90 | if (o == null || getClass() != o.getClass()) 91 | { 92 | return false; 93 | } 94 | JexxaObject that = (JexxaObject) o; 95 | return Objects.equals(getKey(), that.getKey()); // Only compare keys 96 | } 97 | 98 | @Override 99 | public int hashCode() 100 | { 101 | return Objects.hash(jexxaValueObject); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /jexxa-web/src/test/java/io/jexxa/drivingadapter/rest/MultipleRESTClientsIT.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.drivingadapter.rest; 2 | 3 | import io.jexxa.TestConstants; 4 | import io.jexxa.core.JexxaMain; 5 | import io.jexxa.testapplication.JexxaTestApplication; 6 | import io.jexxa.testapplication.applicationservice.IncrementApplicationService; 7 | import kong.unirest.Unirest; 8 | import org.junit.jupiter.api.AfterEach; 9 | import org.junit.jupiter.api.BeforeEach; 10 | import org.junit.jupiter.api.Tag; 11 | import org.junit.jupiter.api.Test; 12 | 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | import java.util.stream.IntStream; 16 | import java.util.stream.Stream; 17 | 18 | import static io.jexxa.common.facade.utils.function.ThrowingConsumer.exceptionCollector; 19 | import static org.junit.jupiter.api.Assertions.assertEquals; 20 | import static org.junit.jupiter.api.Assertions.assertTrue; 21 | 22 | @Tag(TestConstants.INTEGRATION_TEST) 23 | class MultipleRESTClientsIT 24 | { 25 | private static final String METHOD_GET_SIMPLE_VALUE = "increment"; 26 | private static final int MAX_COUNTER = 1000; 27 | private static final int MAX_THREADS = 5; 28 | 29 | private IncrementApplicationService applicationService; 30 | private JexxaMain jexxaMain; 31 | 32 | 33 | @BeforeEach 34 | void setUp() 35 | { 36 | jexxaMain = new JexxaMain(JexxaTestApplication.class); 37 | jexxaMain.disableBanner() 38 | .bind(RESTfulRPCAdapter.class).to(IncrementApplicationService.class) 39 | .start(); 40 | 41 | applicationService = jexxaMain.getInstanceOfPort(IncrementApplicationService.class); 42 | } 43 | 44 | @Test 45 | void synchronizeMultipleClients() 46 | { 47 | //Arrange 48 | applicationService.setMaxCounter(MAX_COUNTER); 49 | List expectedResult = IntStream.rangeClosed(1, MAX_COUNTER) 50 | .boxed() 51 | .toList(); 52 | 53 | var clientPool = Stream.generate(() -> new Thread(this::incrementService)) 54 | .limit(MAX_THREADS) 55 | .toList(); 56 | 57 | var exceptionList = new ArrayList(); 58 | 59 | //Act 60 | clientPool.forEach(Thread::start); 61 | 62 | clientPool.forEach(exceptionCollector(Thread::join, exceptionList)); 63 | 64 | 65 | //Assert 66 | assertEquals(expectedResult, applicationService.getUsedCounter()); 67 | assertTrue(exceptionList.isEmpty()); 68 | } 69 | 70 | void incrementService() 71 | { 72 | while ( applicationService.getCounter() < MAX_COUNTER ) 73 | { 74 | //Act 75 | var restPath = "http://localhost:7500/IncrementApplicationService/"; 76 | var response = Unirest.post(restPath + METHOD_GET_SIMPLE_VALUE) 77 | .header(RESTConstants.CONTENT_TYPE, RESTConstants.APPLICATION_TYPE) 78 | .asJson(); 79 | if (!response.isSuccess()) 80 | { 81 | throw new IllegalArgumentException("HTTP Response Error: " + response.getStatus() + " " + response.getStatusText() ); 82 | } 83 | } 84 | } 85 | 86 | @AfterEach 87 | void tearDown() 88 | { 89 | jexxaMain.stop(); 90 | Unirest.shutDown(); 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /jexxa-test/src/test/java/io/jexxa/jexxatest/integrationtest/messaging/JMSBindingIT.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.jexxatest.integrationtest.messaging; 2 | 3 | import io.jexxa.common.drivingadapter.messaging.jms.JMSConfiguration; 4 | import io.jexxa.testapplication.domain.model.JexxaValueObject; 5 | import io.jexxa.jexxatest.JexxaIntegrationTest; 6 | import io.jexxa.jexxatest.application.JexxaITTestApplication; 7 | import org.junit.jupiter.api.AfterAll; 8 | import org.junit.jupiter.api.BeforeAll; 9 | import org.junit.jupiter.api.Test; 10 | 11 | import java.util.concurrent.TimeUnit; 12 | 13 | import static org.junit.jupiter.api.Assertions.assertEquals; 14 | import static org.junit.jupiter.api.Assertions.assertTrue; 15 | 16 | class JMSBindingIT { 17 | 18 | private static final JexxaIntegrationTest JEXXA_INTEGRATION_TEST = new JexxaIntegrationTest(JexxaITTestApplication.class); 19 | private static JMSBinding objectUnderTest; 20 | 21 | @BeforeAll 22 | static void initBeforeAll() 23 | { 24 | objectUnderTest = JEXXA_INTEGRATION_TEST.getBinding(JMSBinding.class); 25 | } 26 | 27 | @Test 28 | void testMessageListener() 29 | { 30 | //Arrange 31 | var testTopic = "TestTopic"; 32 | var messageSender = objectUnderTest.getSender(); 33 | var messageListener = objectUnderTest.getListener(testTopic, JMSConfiguration.MessagingType.TOPIC); 34 | 35 | //Act 36 | messageSender.send(new JexxaValueObject(42)).toTopic(testTopic).asJson(); 37 | 38 | var result = messageListener 39 | .awaitMessage(5, TimeUnit.SECONDS) 40 | .pop(JexxaValueObject.class); 41 | 42 | //Assert 43 | assertEquals(new JexxaValueObject(42), result); 44 | assertTrue(messageListener.getAll().isEmpty()); 45 | } 46 | 47 | @Test 48 | void testRegisterListener() 49 | { 50 | //Arrange 51 | var testTopic = "TestTopic"; 52 | var messageSender = objectUnderTest.getSender(); 53 | var messageListener = new JMSListener(testTopic, JMSConfiguration.MessagingType.TOPIC); 54 | objectUnderTest.registerListener(messageListener); 55 | 56 | //Act 57 | messageSender.send(new JexxaValueObject(42)).toTopic(testTopic).asJson(); 58 | 59 | var result = messageListener 60 | .awaitMessage(5, TimeUnit.SECONDS) 61 | .pop(JexxaValueObject.class); 62 | 63 | //Assert 64 | assertEquals(new JexxaValueObject(42), result); 65 | assertTrue(messageListener.getAll().isEmpty()); 66 | } 67 | 68 | @Test 69 | void testClearMessageListener() 70 | { 71 | //Arrange 72 | var testTopic = "TestTopic"; 73 | var messageSender = objectUnderTest.getSender(); 74 | var messageListener = objectUnderTest.getListener(testTopic, JMSConfiguration.MessagingType.TOPIC); 75 | 76 | //Act 77 | messageSender.send(new JexxaValueObject(42)).toTopic(testTopic).asJson(); 78 | messageListener 79 | .awaitMessage(5, TimeUnit.SECONDS) 80 | .clear(); 81 | 82 | messageSender.send(new JexxaValueObject(42)).toTopic(testTopic).asJson(); 83 | var result = messageListener 84 | .awaitMessage(5, TimeUnit.SECONDS) 85 | .pop(JexxaValueObject.class); 86 | 87 | //Assert 88 | assertEquals(new JexxaValueObject(42), result); 89 | assertTrue(messageListener.getAll().isEmpty()); 90 | } 91 | 92 | @AfterAll 93 | static void tearDown() 94 | { 95 | JEXXA_INTEGRATION_TEST.shutDown(); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /jexxa-core/src/test/java/io/jexxa/core/FluentMonitorTest.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.core; 2 | 3 | import io.jexxa.adapterapi.invocation.InvocationContext; 4 | import io.jexxa.adapterapi.invocation.monitor.AroundMonitor; 5 | import io.jexxa.testapplication.JexxaTestApplication; 6 | import io.jexxa.testapplication.applicationservice.SimpleApplicationService; 7 | import io.jexxa.testapplication.infrastructure.drivingadapter.generic.ProxyAdapter; 8 | import io.jexxa.testapplication.infrastructure.drivingadapter.generic.ProxyDrivingAdapter; 9 | import io.jexxa.testapplication.infrastructure.drivingadapter.portadapter.ProxyPortAdapter; 10 | import org.junit.jupiter.api.Test; 11 | 12 | import java.time.Duration; 13 | import java.util.concurrent.TimeUnit; 14 | 15 | import static io.jexxa.common.healthcheck.HealthIndicators.timeoutIndicator; 16 | import static org.awaitility.Awaitility.await; 17 | import static org.junit.jupiter.api.Assertions.assertFalse; 18 | import static org.junit.jupiter.api.Assertions.assertThrows; 19 | import static org.junit.jupiter.api.Assertions.assertTrue; 20 | 21 | class FluentMonitorTest { 22 | 23 | @Test 24 | void beforeMonitor() 25 | { 26 | //Arrange 27 | var jexxaMain = new JexxaMain(JexxaTestApplication.class); 28 | var maxTimeout = Duration.ofSeconds(2); 29 | var boundedContext = jexxaMain.getBoundedContext(); 30 | 31 | jexxaMain.bind(ProxyDrivingAdapter.class).to(ProxyPortAdapter.class); 32 | 33 | //Act 34 | jexxaMain.monitor(ProxyPortAdapter.class).with(timeoutIndicator(maxTimeout)); 35 | 36 | var firstResult = boundedContext.isHealthy(); 37 | 38 | await() 39 | .atMost(3, TimeUnit.SECONDS) 40 | .pollInterval(100, TimeUnit.MILLISECONDS) 41 | .until(() -> !boundedContext.isHealthy()); 42 | 43 | // / Assert 44 | assertTrue(firstResult); // After start status should be healthy 45 | assertFalse(boundedContext.isHealthy()); 46 | } 47 | 48 | @Test 49 | void aroundMonitor() 50 | { 51 | //Arrange 52 | var jexxaMain = new JexxaMain(JexxaTestApplication.class); 53 | var boundedContext = jexxaMain.getBoundedContext(); 54 | var objectUnderTest = jexxaMain.getInstanceOfPort(SimpleApplicationService.class); 55 | 56 | var proxyAdapter = new ProxyAdapter(); 57 | proxyAdapter.register(objectUnderTest); 58 | jexxaMain.monitor(SimpleApplicationService.class).with(new ExceptionIndicator()); 59 | 60 | // Act 61 | assertThrows(NullPointerException.class, () -> proxyAdapter.invoke(objectUnderTest::throwNullPointerException)); 62 | 63 | // Assert 64 | assertFalse(boundedContext.isHealthy()); 65 | } 66 | 67 | private static class ExceptionIndicator extends AroundMonitor { 68 | 69 | private RuntimeException occurredException; 70 | 71 | @Override 72 | public boolean healthy() { 73 | return occurredException == null; 74 | } 75 | 76 | @Override 77 | public String getStatusMessage() { 78 | if (occurredException == null) 79 | { 80 | return "All fine!"; 81 | } 82 | return occurredException.getMessage(); 83 | } 84 | 85 | @Override 86 | public void around(InvocationContext invocationContext) { 87 | try { 88 | invocationContext.invoke(); 89 | } catch (RuntimeException e) 90 | { 91 | occurredException = e; 92 | throw e; 93 | } 94 | } 95 | } 96 | } -------------------------------------------------------------------------------- /jexxa-test/src/main/java/io/jexxa/jexxatest/architecture/PortsAndAdapters.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.jexxatest.architecture; 2 | 3 | 4 | import com.tngtech.archunit.core.importer.ImportOption; 5 | import com.tngtech.archunit.library.Architectures; 6 | import io.jexxa.addend.applicationcore.Aggregate; 7 | import io.jexxa.addend.applicationcore.Repository; 8 | 9 | import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; 10 | import static com.tngtech.archunit.library.Architectures.onionArchitecture; 11 | 12 | /** 13 | * These tests validate the access direction of an onion architecture which is as follows: 14 | * 15 | * @startuml 16 | * 17 | * package "DrivingAdapter " #DDDDDD { 18 | * [DrivingAdapter] #Implementation 19 | * } 20 | * package ApplicationCore #DDDDDD { 21 | * [ApplicationService] #lightgreen 22 | * [DomainService] #lightgreen 23 | * [Domain] #lightgreen 24 | * } 25 | * package "DrivenAdapter " #DDDDDD { 26 | * [DrivenAdapter] #Implementation 27 | * } 28 | * [DrivingAdapter] -r--> [ApplicationService] : uses 29 | * [DrivingAdapter] -r--> [DomainService] : uses 30 | * [ApplicationService] -down-> [DomainService] : uses 31 | * [ApplicationService] -down-> [Domain] : uses 32 | * [DomainService] -r-> [Domain] : uses 33 | * [DrivenAdapter] .u..> [Domain] 34 | * [DrivenAdapter] .u..> [DomainService] : implements 35 | * 36 | * @enduml 37 | * .... 38 | */ 39 | public class PortsAndAdapters extends ProjectContent { 40 | 41 | private final Architectures.OnionArchitecture onionArchitecture; 42 | @SuppressWarnings("unused") 43 | PortsAndAdapters(Class project) 44 | { 45 | this(project, ImportOption.Predefined.DO_NOT_INCLUDE_TESTS); 46 | } 47 | 48 | protected PortsAndAdapters(Class project, ImportOption importOption) 49 | { 50 | super(project, importOption); 51 | this.onionArchitecture = onionArchitecture() 52 | .domainModels(project().getPackage().getName() + ".domain..") 53 | .domainServices(project().getPackage().getName() +".domainservice..") 54 | .applicationServices(project().getPackage().getName() + ".applicationservice.."); 55 | 56 | this.onionArchitecture.allowEmptyShould(true); 57 | } 58 | 59 | public PortsAndAdapters addDrivenAdapterPackage(String drivenAdapterPackage) 60 | { 61 | onionArchitecture.adapter("drivenAdapter." + drivenAdapterPackage, project().getPackageName() + "." + "infrastructure.drivenadapter." + drivenAdapterPackage + ".."); 62 | return this; 63 | } 64 | 65 | public PortsAndAdapters addDrivingAdapterPackage(String drivingAdapterPackage) 66 | { 67 | onionArchitecture.adapter("drivingAdapter." + drivingAdapterPackage, project().getPackageName() + ".infrastructure.drivingadapter." + drivingAdapterPackage + ".."); 68 | return this; 69 | } 70 | 71 | @Override 72 | public void validate() 73 | { 74 | validatePortsAndAdapters(); 75 | validateDrivingAdapterAccess(); 76 | } 77 | 78 | private void validatePortsAndAdapters() 79 | { 80 | onionArchitecture.check(importedClasses()); 81 | } 82 | 83 | private void validateDrivingAdapterAccess() 84 | { 85 | //Don't access a repository or aggregate directly from a driving adapter 86 | var drivingAdapter = noClasses().that() 87 | .resideInAnyPackage(project().getPackageName() + ".infrastructure.drivingadapter..").should() 88 | .dependOnClassesThat().areAnnotatedWith(Aggregate.class).orShould() 89 | .dependOnClassesThat().areAnnotatedWith(Repository.class) 90 | .allowEmptyShould(true); 91 | drivingAdapter.check(importedClasses()); 92 | } 93 | } -------------------------------------------------------------------------------- /jexxa-core/src/test/java/io/jexxa/core/factory/DependencyScannerTest.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.core.factory; 2 | 3 | import io.jexxa.TestConstants; 4 | import io.jexxa.adapterapi.drivingadapter.IDrivingAdapter; 5 | import io.jexxa.testapplication.annotation.UnavailableDuringRuntime; 6 | import io.jexxa.testapplication.annotation.ValidApplicationService; 7 | import io.jexxa.testapplication.applicationservice.SimpleApplicationService; 8 | import org.junit.jupiter.api.Tag; 9 | import org.junit.jupiter.api.Test; 10 | import org.junit.jupiter.api.parallel.Execution; 11 | import org.junit.jupiter.api.parallel.ExecutionMode; 12 | 13 | import java.util.List; 14 | 15 | import static io.jexxa.core.factory.PackageConstants.JEXXA_APPLICATION_SERVICE; 16 | import static io.jexxa.core.factory.PackageConstants.JEXXA_DRIVING_ADAPTER; 17 | import static org.junit.jupiter.api.Assertions.assertEquals; 18 | import static org.junit.jupiter.api.Assertions.assertFalse; 19 | import static org.junit.jupiter.api.Assertions.assertThrows; 20 | import static org.junit.jupiter.api.Assertions.assertTrue; 21 | 22 | @Execution(ExecutionMode.CONCURRENT) 23 | @Tag(TestConstants.UNIT_TEST) 24 | class DependencyScannerTest 25 | { 26 | @Test 27 | void findAnnotatedClassesWithinPackage() { 28 | //Arrange 29 | var objectUnderTest = new DependencyScanner(); 30 | 31 | //Act 32 | var applicationServiceList = objectUnderTest. 33 | acceptPackage(JEXXA_APPLICATION_SERVICE). 34 | getClassesWithAnnotation(ValidApplicationService.class); 35 | 36 | //Assert 37 | assertFalse(applicationServiceList.isEmpty()); 38 | assertTrue(applicationServiceList 39 | .stream() 40 | .anyMatch(SimpleApplicationService.class::isAssignableFrom)); 41 | 42 | } 43 | 44 | 45 | @Test 46 | void findAnnotatedClassesFailsWithinPackage() { 47 | //Arrange 48 | var invalidPackageName = "io.invalid.package"; 49 | var objectUnderTest = new DependencyScanner(); 50 | 51 | //Act 52 | var applicationServiceList = objectUnderTest. 53 | acceptPackage(invalidPackageName). 54 | getClassesWithAnnotation(ValidApplicationService.class); 55 | 56 | //Assert 57 | assertTrue(applicationServiceList.isEmpty()); 58 | } 59 | 60 | @Test 61 | void getClassesImplementingInterface() { 62 | //Arrange 63 | var objectUnderTest = new DependencyScanner(); 64 | objectUnderTest.acceptPackage(JEXXA_DRIVING_ADAPTER); 65 | 66 | 67 | //Act 68 | List> drivingAdapters = objectUnderTest.getClassesImplementing(IDrivingAdapter.class); 69 | 70 | //Assert 71 | assertFalse(drivingAdapters.isEmpty()); 72 | } 73 | 74 | 75 | @Test 76 | void getClassesImplementingInterfaceInSpecificPackage() { 77 | //Arrange 78 | var objectUnderTest = new DependencyScanner(); 79 | var packageName = "io.jexxa.common.drivingadapter.messaging"; 80 | 81 | //Act 82 | List> drivingAdapters = objectUnderTest. 83 | acceptPackage(packageName). 84 | getClassesImplementing(IDrivingAdapter.class); 85 | 86 | //Assert 87 | assertFalse(drivingAdapters.isEmpty()); 88 | assertEquals(1, drivingAdapters.size()); 89 | } 90 | 91 | @Test 92 | void handleAnnotationUnavailableDuringRuntime() 93 | { 94 | //Arrange 95 | var objectUnderTest = new DependencyScanner(); 96 | 97 | //Act 98 | assertThrows(IllegalArgumentException.class, () -> objectUnderTest.getClassesWithAnnotation(UnavailableDuringRuntime.class)); 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /jexxa-core/src/test/java/io/jexxa/core/BoundedContextTest.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.core; 2 | 3 | 4 | import io.jexxa.TestConstants; 5 | import io.jexxa.adapterapi.drivingadapter.HealthCheck; 6 | import io.jexxa.testapplication.JexxaTestApplication; 7 | import org.junit.jupiter.api.BeforeEach; 8 | import org.junit.jupiter.api.Tag; 9 | import org.junit.jupiter.api.Test; 10 | import org.junit.jupiter.api.function.Executable; 11 | import org.junit.jupiter.api.parallel.Execution; 12 | import org.junit.jupiter.api.parallel.ExecutionMode; 13 | 14 | import java.time.Duration; 15 | import java.util.concurrent.TimeUnit; 16 | 17 | import static org.awaitility.Awaitility.await; 18 | import static org.junit.jupiter.api.Assertions.assertEquals; 19 | import static org.junit.jupiter.api.Assertions.assertFalse; 20 | import static org.junit.jupiter.api.Assertions.assertTimeout; 21 | import static org.junit.jupiter.api.Assertions.assertTrue; 22 | 23 | @Execution(ExecutionMode.SAME_THREAD) 24 | @Tag(TestConstants.UNIT_TEST) 25 | class BoundedContextTest 26 | { 27 | private JexxaMain jexxaMain; 28 | private BoundedContext objectUnderTest; 29 | 30 | 31 | @BeforeEach 32 | void init() 33 | { 34 | jexxaMain = new JexxaMain(JexxaTestApplication.class); 35 | 36 | objectUnderTest = jexxaMain 37 | .disableBanner() 38 | .getBoundedContext(); 39 | } 40 | 41 | @Test 42 | void shutdown() 43 | { 44 | //Arrange 45 | var thread = new Thread(jexxaMain::run); 46 | thread.start(); 47 | 48 | await().atMost(1, TimeUnit.SECONDS) 49 | .until(() -> (objectUnderTest != null && objectUnderTest.isRunning())); 50 | 51 | //Act 52 | objectUnderTest.stop(); 53 | 54 | //Assert 55 | assertTimeout(Duration.ofSeconds(1), (Executable) thread::join); 56 | } 57 | 58 | @Test 59 | void testIsHealthy() 60 | { 61 | //Arrange 62 | objectUnderTest.registerHealthCheck(new SimpleHealthCheck(true)); 63 | 64 | //Assert 65 | assertTrue(objectUnderTest.isHealthy()); 66 | assertEquals(1, objectUnderTest.diagnostics().size()); 67 | assertTrue(objectUnderTest.diagnostics().getFirst().isHealthy()); 68 | } 69 | 70 | @Test 71 | void testIsUnhealthy() 72 | { 73 | //Arrange 74 | objectUnderTest.registerHealthCheck(new SimpleHealthCheck(true)); 75 | objectUnderTest.registerHealthCheck(new SimpleHealthCheck(false)); 76 | 77 | //Assert 78 | assertFalse(objectUnderTest.isHealthy()); 79 | assertEquals(2, objectUnderTest.diagnostics().size()); 80 | assertTrue(objectUnderTest.diagnostics().get(0).isHealthy()); 81 | assertFalse(objectUnderTest.diagnostics().get(1).isHealthy()); 82 | } 83 | 84 | @Test 85 | void testJexxaVersion() 86 | { 87 | //Arrange -- 88 | 89 | //Act 90 | var jexxaVersion = objectUnderTest.jexxaVersion(); 91 | 92 | //Assert 93 | assertFalse(jexxaVersion.version().isEmpty()); 94 | assertFalse(jexxaVersion.buildTimestamp().isEmpty()); 95 | assertFalse(jexxaVersion.projectName().isEmpty()); 96 | assertFalse(jexxaVersion.repository().isEmpty()); 97 | } 98 | 99 | public static class SimpleHealthCheck extends HealthCheck 100 | { 101 | private final boolean isHealthy; 102 | 103 | public SimpleHealthCheck( boolean isHealthy ) 104 | { 105 | this.isHealthy = isHealthy; 106 | } 107 | 108 | @Override 109 | public boolean healthy() 110 | { 111 | return isHealthy; 112 | } 113 | 114 | @Override 115 | public String getStatusMessage() { 116 | return ""; 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /jexxa-core/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | jexxa 7 | io.jexxa 8 | 9.0.2-SNAPSHOT 9 | 10 | 11 | jexxa-core 12 | 13 | Jexxa-Core 14 | 15 | 16 | The Apache Software License, Version 2.0 17 | https://www.apache.org/licenses/LICENSE-2.0.txt 18 | repo 19 | 20 | 21 | 22 | 23 | 4.2.4-SNAPSHOT 24 | repplix_Jexxa 25 | 26 | 27 | 28 | 29 | 30 | 31 | io.github.classgraph 32 | classgraph 33 | ${classgraph.version} 34 | compile 35 | 36 | 37 | 38 | io.jexxa.common 39 | common-adapters 40 | ${jexxa.common.adapters.version} 41 | 42 | 43 | 44 | 45 | org.junit.platform 46 | junit-platform-launcher 47 | ${junit.platform.launcher.version} 48 | test 49 | 50 | 51 | org.junit.jupiter 52 | junit-jupiter-engine 53 | ${junit.jupiter.engine.version} 54 | test 55 | 56 | 57 | org.junit.jupiter 58 | junit-jupiter-params 59 | ${junit.jupiter.params.version} 60 | test 61 | 62 | 63 | org.slf4j 64 | slf4j-simple 65 | ${slf4j.simple.version} 66 | test 67 | 68 | 69 | org.awaitility 70 | awaitility-groovy 71 | ${awaitility.version} 72 | test 73 | 74 | 75 | 76 | org.postgresql 77 | postgresql 78 | ${postgres.version} 79 | test 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | org.codehaus.mojo 88 | templating-maven-plugin 89 | 90 | 91 | filter-src 92 | 93 | filter-sources 94 | 95 | 96 | 97 | 98 | 99 | 100 | org.apache.maven.plugins 101 | maven-jar-plugin 102 | 103 | 104 | Jar Tests Package 105 | package 106 | 107 | test-jar 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /jexxa-web/src/test/resources/jexxa-application.properties: -------------------------------------------------------------------------------- 1 | #suppress inspection "UnusedProperty" for whole file 2 | 3 | ########################################## 4 | # Adjust system properties # 5 | ########################################## 6 | #io.jexxa.user.timezone=UTC 7 | 8 | ########################################## 9 | #Settings for JMSAdapter and JMSSender # 10 | ########################################## 11 | java.naming.factory.initial=org.apache.activemq.jndi.ActiveMQInitialContextFactory 12 | java.naming.provider.url=tcp://localhost:61616 13 | #java.naming.provider.url=vm://localhost?broker.persistent=false #for local jms provider 14 | #java.naming.client.id="MyClient" # Client ID for JMS connection 15 | java.naming.user=artemis 16 | java.naming.password=simetraehcapa 17 | #java.naming.file.user=/pathTo/jdbcUsername 18 | #java.naming.file.password=/pathTo/jdbcPassword 19 | 20 | ########################################## 21 | #Settings for RESTfulRPCAdapter # 22 | ########################################## 23 | io.jexxa.rest.host=localhost 24 | io.jexxa.rest.port=7500 25 | 26 | # Static files related settings 27 | # Following path relates to Classpath 28 | # io.jexxa.rest.static.files.root=/public 29 | 30 | # You can also configure an external path 31 | # io.jexxa.rest.static.files.root=src/main/resources/public 32 | # io.jexxa.rest.static.files.external=true 33 | 34 | # HTTPS related settings 35 | # See here how to create a keystore including a certificate: 36 | # https://docs.oracle.com/cd/E35976_01/server.740/es_admin/src/tadm_ssl_jetty_keystore.html 37 | #io.jexxa.rest.https.port=8080 38 | #io.jexxa.rest.keystore.location=keystore.jks 39 | #io.jexxa.rest.keystore.password=test123 40 | #io.jexxa.rest.keystore.file.password=/pathTo/fileWithSecret 41 | 42 | # OpenAPI Support. 43 | # Enable OpenAPI support by defining a path. 44 | #io.jexxa.rest.openapi.path=swagger-docs 45 | 46 | ########################################## 47 | #Settings for JDBCConnection # 48 | ########################################## 49 | io.jexxa.jdbc.driver=org.postgresql.Driver 50 | io.jexxa.jdbc.url=jdbc:postgresql://localhost:5432/hellojexxa 51 | io.jexxa.jdbc.username=admin 52 | io.jexxa.jdbc.password=admin 53 | #io.jexxa.jdbc.file.username=/pathTo/usernameFile 54 | #io.jexxa.jdbc.file.password=/pathTo/passwordFile 55 | 56 | # Following setting is only required if you want to auto-create your database, and it is supported via connection URL. In this case you have to define a valid default URL (e.g. for testing purpose) 57 | io.jexxa.jdbc.autocreate.database=jdbc:postgresql://localhost:5432/postgres 58 | 59 | # Following setting is only required if you want to auto-create your tables (e.g. for testing purpose) 60 | io.jexxa.jdbc.autocreate.table=true 61 | 62 | 63 | ########################################################## 64 | # Application specific information: # 65 | ########################################################## 66 | # Import other properties file 67 | #io.jexxa.config.import=path/to/other/config.properties 68 | 69 | ########################################################## 70 | # You can get this information from maven properties. # 71 | # Please note that maven's resource filtering must be # 72 | # enabled. # 73 | # # 74 | # # 75 | # # 76 | # src/main/resources # 77 | # true # 78 | # # 79 | # # 80 | ########################################################## 81 | io.jexxa.context.name=${project.name} 82 | io.jexxa.context.version=${project.name} 83 | io.jexxa.context.repository=${project.scm.developerConnection} 84 | io.jexxa.context.build.timestamp=${build.timestamp} 85 | 86 | -------------------------------------------------------------------------------- /jexxa-web/src/test/java/io/jexxa/drivingadapter/rest/HttpsRESTfulRPCAdapterIT.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.drivingadapter.rest; 2 | 3 | import io.jexxa.testapplication.applicationservice.SimpleApplicationService; 4 | import kong.unirest.Unirest; 5 | import kong.unirest.apache.ApacheClient; 6 | import org.apache.http.conn.ssl.NoopHostnameVerifier; 7 | import org.apache.http.conn.ssl.TrustSelfSignedStrategy; 8 | import org.apache.http.impl.client.CloseableHttpClient; 9 | import org.apache.http.impl.client.HttpClients; 10 | import org.apache.http.ssl.SSLContextBuilder; 11 | import org.junit.jupiter.api.AfterEach; 12 | import org.junit.jupiter.api.BeforeEach; 13 | import org.junit.jupiter.params.ParameterizedTest; 14 | import org.junit.jupiter.params.provider.MethodSource; 15 | 16 | import javax.net.ssl.SSLContext; 17 | import java.security.KeyManagementException; 18 | import java.security.KeyStoreException; 19 | import java.security.NoSuchAlgorithmException; 20 | import java.util.Properties; 21 | import java.util.stream.Stream; 22 | 23 | import static org.junit.jupiter.api.Assertions.assertEquals; 24 | import static org.junit.jupiter.api.Assertions.assertNotNull; 25 | 26 | class HttpsRESTfulRPCAdapterIT 27 | { 28 | private static final String METHOD_GET_SIMPLE_VALUE = "getSimpleValue"; 29 | 30 | private static final int DEFAULT_VALUE = 42; 31 | private final SimpleApplicationService simpleApplicationService = new SimpleApplicationService(); 32 | 33 | @SuppressWarnings("unused") 34 | // Run the tests with Port 0 (random port and port 8090) 35 | static Stream httpsPorts() { 36 | return Stream.of(0,8090); 37 | } 38 | 39 | @BeforeEach 40 | void initTest() throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException 41 | { 42 | SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(new TrustSelfSignedStrategy()).build(); 43 | 44 | CloseableHttpClient customHttpClient = HttpClients.custom().setSSLContext(sslContext) 45 | .setSSLHostnameVerifier(new NoopHostnameVerifier()).build(); 46 | 47 | Unirest.config().httpClient(ApacheClient.builder(customHttpClient)); 48 | 49 | Unirest.config().sslContext(sslContext); 50 | Unirest.config().hostnameVerifier(new NoopHostnameVerifier()); 51 | } 52 | 53 | @ParameterizedTest 54 | @MethodSource("httpsPorts") 55 | void testHTTPSConnectionRandomPort(Integer httpsPort) 56 | { 57 | //Arrange 58 | var properties = new Properties(); 59 | var defaultHost = "0.0.0.0"; 60 | 61 | properties.put(JexxaWebProperties.JEXXA_REST_HOST, defaultHost); 62 | properties.put(JexxaWebProperties.JEXXA_REST_PORT, Integer.toString(0)); 63 | properties.put(JexxaWebProperties.JEXXA_REST_HTTPS_PORT, httpsPort.toString()); 64 | properties.put(JexxaWebProperties.JEXXA_REST_KEYSTORE_PASSWORD, "test123"); 65 | properties.put(JexxaWebProperties.JEXXA_REST_KEYSTORE, "certificate/keystore.jks"); 66 | properties.put(JexxaWebProperties.JEXXA_REST_OPEN_API_PATH, "swagger-docs"); 67 | 68 | var objectUnderTest = RESTfulRPCAdapter.createAdapter(properties); 69 | objectUnderTest.register(simpleApplicationService); 70 | objectUnderTest.start(); 71 | 72 | String restPath = "https://localhost:" + objectUnderTest.getHTTPSPort() + "/SimpleApplicationService/"; 73 | 74 | objectUnderTest.bannerInformation(properties); 75 | 76 | //Act 77 | Integer result = Unirest.get(restPath + METHOD_GET_SIMPLE_VALUE) 78 | .header(RESTConstants.CONTENT_TYPE, RESTConstants.APPLICATION_TYPE) 79 | .asObject(Integer.class).getBody(); 80 | 81 | 82 | //Assert 83 | assertNotNull(result); 84 | assertEquals(DEFAULT_VALUE, simpleApplicationService.getSimpleValue()); 85 | assertEquals(simpleApplicationService.getSimpleValue(), result.intValue() ); 86 | } 87 | 88 | @AfterEach 89 | void tearDown() 90 | { 91 | Unirest.shutDown(); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /jexxa-web/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | jexxa 7 | io.jexxa 8 | 9.0.2-SNAPSHOT 9 | 10 | 11 | jexxa-web 12 | 13 | 14 | The Apache Software License, Version 2.0 15 | https://www.apache.org/licenses/LICENSE-2.0.txt 16 | repo 17 | 18 | 19 | 20 | Jexxa-Web 21 | 22 | 23 | 24 | 25 | io.jexxa 26 | jexxa-core 27 | ${project.version} 28 | compile 29 | 30 | 31 | com.google.code.gson 32 | gson 33 | ${gson.version} 34 | 35 | 36 | 37 | 38 | io.javalin 39 | javalin 40 | ${javalin.version} 41 | compile 42 | 43 | 44 | 45 | io.smallrye.config 46 | smallrye-config-core 47 | ${smallrye.config.core.version} 48 | 49 | 50 | 51 | 52 | io.smallrye 53 | smallrye-open-api-core 54 | ${smallrye.openapi.core.version} 55 | 56 | 57 | 58 | 59 | 60 | 61 | org.junit.platform 62 | junit-platform-launcher 63 | ${junit.platform.launcher.version} 64 | test 65 | 66 | 67 | org.junit.jupiter 68 | junit-jupiter-engine 69 | ${junit.jupiter.engine.version} 70 | test 71 | 72 | 73 | org.junit.jupiter 74 | junit-jupiter-params 75 | ${junit.jupiter.params.version} 76 | test 77 | 78 | 79 | org.slf4j 80 | slf4j-simple 81 | ${slf4j.simple.version} 82 | test 83 | 84 | 85 | org.awaitility 86 | awaitility-groovy 87 | ${awaitility.version} 88 | test 89 | 90 | 91 | io.jexxa 92 | jexxa-core 93 | ${project.version} 94 | test-jar 95 | test 96 | 97 | 98 | com.konghq 99 | unirest-java 100 | ${unirest.java.version} 101 | test 102 | 103 | 104 | 105 | commons-codec 106 | commons-codec 107 | 108 | 109 | 110 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /jexxa-test/src/test/java/io/jexxa/jexxatest/JexxaTestTest.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.jexxatest; 2 | 3 | import io.jexxa.core.factory.InvalidAdapterException; 4 | import io.jexxa.jexxatest.application.DomainEventPublisher; 5 | import io.jexxa.testapplication.JexxaTestApplication; 6 | import io.jexxa.testapplication.applicationservice.ApplicationServiceWithInvalidDrivenAdapters; 7 | import io.jexxa.testapplication.domain.model.JexxaAggregateRepository; 8 | import io.jexxa.testapplication.domain.model.JexxaDomainEvent; 9 | import io.jexxa.testapplication.domain.model.JexxaValueObject; 10 | import io.jexxa.testapplication.domainservice.BootstrapJexxaAggregates; 11 | import io.jexxa.testapplication.domainservice.InvalidConstructorParameterService; 12 | import io.jexxa.testapplication.domainservice.NotImplementedService; 13 | import io.jexxa.testapplication.infrastructure.drivenadapter.persistence.JexxaAggregateRepositoryImpl; 14 | import org.junit.jupiter.api.BeforeEach; 15 | import org.junit.jupiter.api.Test; 16 | 17 | import static io.jexxa.jexxatest.JexxaTest.getJexxaTest; 18 | import static org.junit.jupiter.api.Assertions.assertEquals; 19 | import static org.junit.jupiter.api.Assertions.assertFalse; 20 | import static org.junit.jupiter.api.Assertions.assertThrows; 21 | import static org.junit.jupiter.api.Assertions.assertTrue; 22 | 23 | 24 | class JexxaTestTest 25 | { 26 | private JexxaTest jexxaTest; 27 | 28 | @BeforeEach 29 | void setUp() 30 | { 31 | //Arrange 32 | jexxaTest = getJexxaTest(JexxaTestApplication.class); 33 | } 34 | 35 | @Test 36 | void requestInvalidPort() 37 | { 38 | assertThrows(InvalidAdapterException.class, () -> jexxaTest.getInstanceOfPort(ApplicationServiceWithInvalidDrivenAdapters.class)); 39 | } 40 | 41 | @Test 42 | void validateRepository() 43 | { 44 | //Arrange 45 | var jexxaRepository = jexxaTest.getRepository(JexxaAggregateRepository.class); 46 | 47 | //Act 48 | jexxaTest.getInstanceOfPort(BootstrapJexxaAggregates.class).initDomainData(); 49 | 50 | //Assert 51 | assertFalse(jexxaRepository.get().isEmpty()); 52 | } 53 | 54 | @Test 55 | void validateRepositoryIsInterface() 56 | { 57 | //Act/Assert 58 | assertThrows(IllegalArgumentException.class, () -> jexxaTest.getRepository(JexxaAggregateRepositoryImpl.class)); 59 | } 60 | 61 | 62 | @Test 63 | void recordDomainEvent() 64 | { 65 | //Arrange 66 | var domainEvent = new JexxaDomainEvent(new JexxaValueObject(1)); 67 | var objectUnderTest = jexxaTest.getDomainEventRecorder(JexxaDomainEvent.class, DomainEventPublisher::subscribe); 68 | 69 | //Act 70 | DomainEventPublisher.publish(domainEvent); 71 | 72 | 73 | //Assert MessageRecorder 74 | assertFalse(objectUnderTest.get().isEmpty()); 75 | assertEquals(1, objectUnderTest.get().size()); 76 | assertEquals(domainEvent, objectUnderTest.get().getFirst()); 77 | } 78 | 79 | 80 | @Test 81 | void recordAllDomainEvent() 82 | { 83 | //Arrange 84 | var domainEvent = new JexxaDomainEvent(new JexxaValueObject(1)); 85 | var objectUnderTest = jexxaTest.getDomainEventRecorder(DomainEventPublisher::subscribe); 86 | 87 | //Act 88 | DomainEventPublisher.publish(domainEvent); 89 | 90 | 91 | //Assert MessageRecorder 92 | assertFalse(objectUnderTest.get().isEmpty()); 93 | assertEquals(1, objectUnderTest.get().size()); 94 | assertEquals(domainEvent, objectUnderTest.get().getFirst()); 95 | } 96 | 97 | 98 | 99 | @Test 100 | void repositoryNotAvailable() 101 | { 102 | //Act/Assert 103 | assertThrows( IllegalArgumentException.class, () -> jexxaTest.getRepository(NotImplementedService.class) ); 104 | 105 | //Act/Assert 106 | assertThrows( InvalidAdapterException.class, () -> jexxaTest.getRepository(InvalidConstructorParameterService.class) ); 107 | } 108 | 109 | @Test 110 | void jexxaTestProperties() 111 | { 112 | //Act/Assert 113 | assertTrue( jexxaTest.getProperties().containsKey("io.jexxa.test.project") ); 114 | } 115 | 116 | 117 | } 118 | -------------------------------------------------------------------------------- /jexxa-core/src/main/java/io/jexxa/core/BoundedContext.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.core; 2 | 3 | import io.jexxa.adapterapi.drivingadapter.Diagnostics; 4 | import io.jexxa.adapterapi.drivingadapter.HealthCheck; 5 | import io.jexxa.properties.JexxaCoreProperties; 6 | 7 | import java.time.Clock; 8 | import java.time.Duration; 9 | import java.time.Instant; 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | import java.util.Objects; 13 | 14 | import static io.jexxa.common.facade.logger.SLF4jLogger.getLogger; 15 | 16 | 17 | public final class BoundedContext 18 | { 19 | private boolean isRunning = false; 20 | private boolean isWaiting = false; 21 | private final String contextName; 22 | private final Clock clock = Clock.systemUTC(); 23 | private final Instant startTime; 24 | private final JexxaMain jexxaMain; 25 | private final List healthChecks = new ArrayList<>(); 26 | 27 | BoundedContext(final String contextName, JexxaMain jexxaMain) 28 | { 29 | this.startTime = clock.instant(); 30 | this.contextName = Objects.requireNonNull(contextName); 31 | this.jexxaMain = Objects.requireNonNull(jexxaMain); 32 | } 33 | 34 | public Duration uptime() 35 | { 36 | return Duration.between(startTime, clock.instant()); 37 | } 38 | 39 | public String contextName() 40 | { 41 | return contextName; 42 | } 43 | 44 | public VersionInfo jexxaVersion() 45 | { 46 | return JexxaVersion.getJexxaVersion(); 47 | } 48 | 49 | public VersionInfo contextVersion() 50 | { 51 | var properties = jexxaMain.getProperties(); 52 | 53 | return VersionInfo.of() 54 | .version(properties.getProperty(JexxaCoreProperties.JEXXA_CONTEXT_VERSION, "")) 55 | .repository(properties.getProperty(JexxaCoreProperties.JEXXA_CONTEXT_REPOSITORY, "")) 56 | .buildTimestamp(properties.getProperty(JexxaCoreProperties.JEXXA_CONTEXT_BUILD_TIMESTAMP, "")) 57 | .projectName(properties.getProperty(JexxaCoreProperties.JEXXA_CONTEXT_NAME, "")) 58 | .create(); 59 | } 60 | 61 | 62 | public synchronized boolean isRunning() 63 | { 64 | return isRunning; 65 | } 66 | 67 | /** 68 | * Returns true if all HealthChecks returns true. 69 | * If at least one HealthCheck returns false, this method returns false as well 70 | * 71 | * @return True if all HealthChecks return true, otherwise false. 72 | */ 73 | public boolean isHealthy() 74 | { 75 | var isHealthy = healthChecks.stream() 76 | .map(HealthCheck::healthy) 77 | .filter( element -> !element) 78 | .findFirst(); 79 | 80 | return isHealthy.orElse(true); 81 | } 82 | 83 | public List diagnostics() 84 | { 85 | return healthChecks 86 | .stream() 87 | .map(HealthCheck::getDiagnostics) 88 | .toList(); 89 | } 90 | 91 | void registerHealthCheck( HealthCheck healthCheck) 92 | { 93 | healthChecks.add(healthCheck); 94 | } 95 | 96 | synchronized JexxaMain waitForShutdown() 97 | { 98 | if (!isRunning) 99 | { 100 | return jexxaMain; 101 | } 102 | 103 | setupSignalHandler(); 104 | isWaiting = true; 105 | 106 | try 107 | { 108 | while ( isWaiting ) { 109 | this.wait(); 110 | } 111 | } 112 | catch (InterruptedException e) 113 | { 114 | Thread.currentThread().interrupt(); 115 | throw new IllegalStateException(e); 116 | } 117 | 118 | return jexxaMain; 119 | } 120 | 121 | synchronized void start() 122 | { 123 | isRunning = true; 124 | } 125 | 126 | synchronized void stop() 127 | { 128 | isRunning = false; 129 | internalShutdown(); 130 | } 131 | 132 | private void setupSignalHandler() { 133 | Runtime.getRuntime().addShutdownHook(new Thread(() -> { 134 | getLogger(JexxaMain.class).info("Shutdown signal received ..."); 135 | jexxaMain.stop(); 136 | })); 137 | } 138 | 139 | private synchronized void internalShutdown() 140 | { 141 | if ( isWaiting ) 142 | { 143 | isWaiting = false; 144 | notifyAll(); 145 | } 146 | } 147 | 148 | } 149 | -------------------------------------------------------------------------------- /jexxa-core/src/main/java/io/jexxa/core/convention/AdapterConvention.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.core.convention; 2 | 3 | import java.lang.reflect.Modifier; 4 | import java.util.Arrays; 5 | import java.util.List; 6 | import java.util.Properties; 7 | 8 | public final class AdapterConvention 9 | { 10 | public static void validate(Class clazz) 11 | { 12 | if ( 13 | isDefaultConstructorAvailable(clazz) 14 | || isPropertiesConstructorAvailable(clazz) 15 | || isDefaultFactoryMethodAvailable(clazz) 16 | || isPropertiesFactoryMethodAvailable(clazz) 17 | ) 18 | { 19 | return; 20 | } 21 | 22 | throw new AdapterConventionViolation(clazz); 23 | } 24 | 25 | 26 | public static

    boolean isPortAdapter(Class

    port, List acceptedInfrastructure) 27 | { 28 | 29 | //Check constructor with one single application/domain service 30 | if ( Arrays.stream(port.getConstructors()) 31 | .filter(constructor -> constructor.getParameterTypes().length == 1) 32 | .anyMatch(constructor -> !constructor.getParameterTypes()[0].isInterface()) 33 | && 34 | isInInfrastructurePackage(port, acceptedInfrastructure)) 35 | { 36 | return true; 37 | } 38 | 39 | //Check constructor with one single application/domain service and a properties-parameter 40 | return Arrays.stream(port.getConstructors()) 41 | .filter(constructor -> constructor.getParameterTypes().length == 2) 42 | .anyMatch(constructor -> !constructor.getParameterTypes()[0].isInterface() 43 | && constructor.getParameterTypes()[1].isAssignableFrom(Properties.class) 44 | ) 45 | && 46 | isInInfrastructurePackage(port, acceptedInfrastructure); 47 | } 48 | 49 | private static boolean isDefaultConstructorAvailable(Class clazz) 50 | { 51 | try 52 | { 53 | clazz.getConstructor(); 54 | return true; //Default constructor available 55 | } 56 | catch (NoSuchMethodException | SecurityException _) 57 | { 58 | //If exception is thrown go on to check if another suitable constructor is available 59 | } 60 | 61 | return false; 62 | } 63 | 64 | private static boolean isPropertiesConstructorAvailable(Class clazz) 65 | { 66 | try 67 | { 68 | clazz.getConstructor(Properties.class); 69 | return true; //Constructor with Properties argument available 70 | } 71 | catch (NoSuchMethodException | SecurityException _) 72 | { 73 | //If exception is thrown go on to check if another suitable constructor is available 74 | } 75 | return false; 76 | } 77 | 78 | private static boolean isDefaultFactoryMethodAvailable(Class clazz) 79 | { 80 | var factoryMethods = Arrays 81 | .stream(clazz.getMethods()) 82 | .filter(method -> Modifier.isStatic(method.getModifiers())) 83 | .filter(method -> method.getReturnType().isAssignableFrom(clazz)) 84 | .toList(); 85 | 86 | return factoryMethods.stream().anyMatch(method -> method.getParameterCount() == 0); //Factory method with no arguments available 87 | } 88 | 89 | private static boolean isPropertiesFactoryMethodAvailable(Class clazz) 90 | { 91 | var factoryMethods = Arrays 92 | .stream(clazz.getMethods()) 93 | .filter(method -> Modifier.isStatic(method.getModifiers())) 94 | .filter(method -> method.getReturnType().isAssignableFrom(clazz)) 95 | .toList(); 96 | 97 | return factoryMethods.stream().anyMatch(method -> ( 98 | method.getParameterCount() == 1 && 99 | method.getParameterTypes()[0].isAssignableFrom(Properties.class))); //Factory method with Properties argument available 100 | } 101 | 102 | private static boolean isInInfrastructurePackage( Class clazz, List acceptedInfrastructure) 103 | { 104 | return acceptedInfrastructure 105 | .stream() 106 | .anyMatch( element -> clazz.getPackage().toString().contains( element ) ); 107 | } 108 | 109 | 110 | private AdapterConvention() 111 | { 112 | //Private Constructor 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /jexxa-test/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | jexxa 5 | io.jexxa 6 | 9.0.2-SNAPSHOT 7 | 8 | 4.0.0 9 | 10 | io.jexxa.jexxatest 11 | jexxa-test 12 | Jexxa-Test 13 | 14 | 15 | The Apache Software License, Version 2.0 16 | https://www.apache.org/licenses/LICENSE-2.0.txt 17 | repo 18 | 19 | 20 | 21 | 22 | 23 | io.jexxa 24 | jexxa-core 25 | ${project.version} 26 | compile 27 | 28 | 29 | com.tngtech.archunit 30 | archunit 31 | ${archunit.version} 32 | compile 33 | 34 | 35 | io.jexxa.addend 36 | Addend 37 | ${addend.version} 38 | compile 39 | 40 | 41 | com.konghq 42 | unirest-java 43 | ${unirest.java.version} 44 | 45 | 46 | commons-codec 47 | commons-codec 48 | 49 | 50 | 51 | 52 | org.awaitility 53 | awaitility-groovy 54 | ${awaitility.version} 55 | 56 | 57 | 58 | 59 | 60 | org.slf4j 61 | slf4j-simple 62 | ${slf4j.simple.version} 63 | test 64 | 65 | 66 | org.postgresql 67 | postgresql 68 | ${postgres.version} 69 | test 70 | 71 | 72 | org.junit.platform 73 | junit-platform-launcher 74 | ${junit.platform.launcher.version} 75 | test 76 | 77 | 78 | org.junit.jupiter 79 | junit-jupiter-engine 80 | ${junit.jupiter.engine.version} 81 | test 82 | 83 | 84 | org.junit.jupiter 85 | junit-jupiter-params 86 | ${junit.jupiter.params.version} 87 | test 88 | 89 | 90 | io.jexxa 91 | jexxa-web 92 | ${project.version} 93 | test 94 | 95 | 96 | org.apache.activemq 97 | artemis-jms-client 98 | ${activemq.artemis.client.version} 99 | test 100 | 101 | 102 | 103 | 104 | 105 | io.jexxa 106 | jexxa-core 107 | ${project.version} 108 | test-jar 109 | test 110 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /jexxa-web/src/test/java/io/jexxa/drivingadapter/rest/RESTfulRPCConventionTest.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.drivingadapter.rest; 2 | 3 | import io.jexxa.TestConstants; 4 | import io.jexxa.testapplication.applicationservice.SimpleApplicationService; 5 | import org.junit.jupiter.api.BeforeEach; 6 | import org.junit.jupiter.api.Tag; 7 | import org.junit.jupiter.api.Test; 8 | import org.junit.jupiter.api.parallel.Execution; 9 | import org.junit.jupiter.api.parallel.ExecutionMode; 10 | 11 | import java.lang.reflect.Modifier; 12 | import java.util.ArrayList; 13 | import java.util.Arrays; 14 | 15 | import static org.junit.jupiter.api.Assertions.assertEquals; 16 | import static org.junit.jupiter.api.Assertions.assertFalse; 17 | import static org.junit.jupiter.api.Assertions.assertNotEquals; 18 | import static org.junit.jupiter.api.Assertions.assertNotNull; 19 | import static org.junit.jupiter.api.Assertions.assertThrows; 20 | import static org.junit.jupiter.api.Assertions.assertTrue; 21 | 22 | @Execution(ExecutionMode.CONCURRENT) 23 | @Tag(TestConstants.UNIT_TEST) 24 | class RESTfulRPCConventionTest 25 | { 26 | private RESTfulRPCConvention objectUnderTest; 27 | private SimpleApplicationService simpleApplicationService; 28 | 29 | @BeforeEach 30 | void setupTests() 31 | { 32 | simpleApplicationService = new SimpleApplicationService(); 33 | simpleApplicationService.setSimpleValue(42); 34 | objectUnderTest = new RESTfulRPCConvention(simpleApplicationService); 35 | } 36 | 37 | @Test 38 | void validateGETCommands() 39 | { 40 | //Act 41 | var result = objectUnderTest.getGETCommands(); 42 | 43 | //Assert 44 | //1. Check all conventions as defined in {@link RESTfulRPCModel}. 45 | assertFalse(result.isEmpty()); 46 | 47 | //2. Check that all commands are marked as GET 48 | result.forEach(element -> assertEquals(RESTfulRPCConvention.RESTfulRPCMethod.HTTPCommand.GET, 49 | element.getHTTPCommand())); 50 | 51 | //3. Check URIs 52 | result.forEach(element -> assertEquals("/" + SimpleApplicationService.class.getSimpleName() + "/"+element.method().getName(), 53 | element.resourcePath())); 54 | 55 | //4. Check return types are NOT void 56 | result.forEach(element -> assertNotEquals(void.class, element.method().getReturnType())); 57 | } 58 | 59 | @Test 60 | void validatePOSTCommands() 61 | { 62 | //Act 63 | var result = objectUnderTest.getPOSTCommands(); 64 | 65 | //Assert 66 | //1.Check all conventions as defined in {@link RESTfulRPCGenerator}. 67 | assertFalse(result.isEmpty()); 68 | 69 | //2.Check that all commands are marked as GET 70 | result.forEach(element -> assertEquals(RESTfulRPCConvention.RESTfulRPCMethod.HTTPCommand.POST, 71 | element.getHTTPCommand())); 72 | 73 | //3.Check URIs 74 | result.forEach(element -> assertEquals("/" + SimpleApplicationService.class.getSimpleName() + "/"+element.method().getName(), 75 | element.resourcePath())); 76 | 77 | //4.Check return types are NOT void or Parameter > 0 78 | result.forEach(element -> assertTrue( (void.class.equals(element.method().getReturnType()) 79 | || element.method().getParameterCount() > 0 ))); 80 | 81 | } 82 | 83 | @Test 84 | void noStaticMethods() 85 | { 86 | //Arrange 87 | var staticMethods = Arrays.stream(simpleApplicationService.getClass().getMethods()) 88 | .filter(method -> Modifier.isStatic(method.getModifiers())) 89 | .toList(); 90 | 91 | //Act - get All methods 92 | var methods = new ArrayList(); 93 | methods.addAll(objectUnderTest.getPOSTCommands()); 94 | methods.addAll(objectUnderTest.getGETCommands()); 95 | 96 | //Assert - that we get mbean methods without static methods 97 | assertNotNull(methods); 98 | assertTrue ( staticMethods.stream().allMatch( 99 | staticMethod -> methods.stream() 100 | .noneMatch( method -> method.method().getName().equals(staticMethod.getName())) 101 | ) 102 | ); 103 | } 104 | 105 | @Test 106 | void invalidApplicationService() 107 | { 108 | //Arrange 109 | var unsupportedApplicationService = new UnsupportedApplicationService(); 110 | 111 | //Act / Assert 112 | assertThrows(IllegalArgumentException.class, () -> 113 | new RESTfulRPCConvention(unsupportedApplicationService) 114 | ); 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /jexxa-web/src/main/java/io/jexxa/drivingadapter/rest/RESTfulRPCConvention.java: -------------------------------------------------------------------------------- 1 | package io.jexxa.drivingadapter.rest; 2 | 3 | import java.lang.reflect.Method; 4 | import java.lang.reflect.Modifier; 5 | import java.util.ArrayList; 6 | import java.util.Arrays; 7 | import java.util.HashSet; 8 | import java.util.List; 9 | 10 | 11 | /** 12 | * This class generates uniform IDs (URIs) for resources to be offered via REST 13 | * using a convention over configuration approach: 14 | *
    15 | * Used conventions for URI: 16 | *
    17 | * {@code URI: http://://} 18 | *
    19 | * Example URI: http://localhost:7500/MyApplicationService/myMethod 20 | *
    21 | * This implies the following conventions: 22 | *

      23 | *
    • Simple name of a class must be unique within a single application
    • 24 | *
    • Each class must have unique method names. Any method overloading is not supported
    • 25 | *
    • Methods from base class `Object` are ignored
    • 26 | *
    27 | * GET - mapping: 28 | *
    29 | *
      30 | *
    • If a method returns a type != 'void' and has no arguments then it is mapped to a GET method
    • 31 | *
    32 | *
    33 | * POST - mapping: 34 | *
      35 | *
    • In all other cases
    • 36 | *
    37 | */ 38 | class RESTfulRPCConvention 39 | { 40 | private final Object object; 41 | 42 | RESTfulRPCConvention(Object object) 43 | { 44 | this.object = object; 45 | validateUniqueURI(); 46 | } 47 | 48 | 49 | record RESTfulRPCMethod(RESTfulRPCConvention.RESTfulRPCMethod.HTTPCommand httpCommand, 50 | String resourcePath, 51 | Method method) { 52 | enum HTTPCommand {GET, POST} 53 | 54 | HTTPCommand getHTTPCommand() { 55 | return httpCommand; 56 | } 57 | } 58 | 59 | List getGETCommands() { 60 | 61 | return getPublicMethods(object.getClass()) 62 | .stream() 63 | .filter(element -> !Modifier.isStatic(element.getModifiers())) //Convention for all exposed methods 64 | .filter(element -> !(element.getReturnType().equals(void.class)) && 65 | element.getParameterCount() == 0) // Convention for GET method 66 | .map(element -> 67 | new RESTfulRPCMethod( 68 | RESTfulRPCMethod.HTTPCommand.GET, 69 | generateURI(element), 70 | element)) 71 | .toList(); 72 | } 73 | 74 | 75 | List getPOSTCommands() { 76 | 77 | return getPublicMethods(object.getClass()) 78 | .stream() 79 | .filter(element -> !Modifier.isStatic(element.getModifiers())) //Convention for all exposed methods 80 | .filter(element -> (element.getReturnType().equals(void.class) || 81 | element.getParameterCount() > 0)) // Convention for POST method 82 | .map(element -> 83 | new RESTfulRPCMethod( 84 | RESTfulRPCMethod.HTTPCommand.POST, 85 | generateURI(element), 86 | element)) 87 | .toList(); 88 | } 89 | 90 | 91 | private String generateURI(Method method) { 92 | return "/" + method.getDeclaringClass().getSimpleName() + "/" + method.getName(); 93 | } 94 | 95 | private List getPublicMethods(Class clazz) 96 | { 97 | List result = new ArrayList<>(Arrays.asList(clazz.getMethods())); 98 | result.removeAll(Arrays.asList(Object.class.getMethods())); 99 | 100 | return result; 101 | } 102 | 103 | private void validateUniqueURI() 104 | { 105 | List publicMethods = getPublicMethods(object.getClass()); 106 | List methodNames = new ArrayList<>(); 107 | 108 | publicMethods.forEach(element -> methodNames.add(generateURI(element))); 109 | 110 | // Make a unique list (by converting it into a HashSet) and compare its size with size of publicMethods. 111 | // If it is not equal, URIs are not unique 112 | List uniqueNames = new ArrayList<>( new HashSet<>(methodNames) ); 113 | 114 | if (uniqueNames.size() != methodNames.size() ) { 115 | throw new IllegalArgumentException("Method names are not unique of Object " + object.getClass().getSimpleName()); 116 | } 117 | } 118 | 119 | public static RESTfulRPCConvention createRPCConvention(Object object) 120 | { 121 | return new RESTfulRPCConvention(object); 122 | } 123 | } 124 | --------------------------------------------------------------------------------