CONTRACT = Contract.create(ServerFactory.class);
+
+ /**
+ * Create a new instance of Server
+ *
+ * Note: caller is responsible for calling {@link AutoOpen#open()} and calling
+ * the {@link io.github.jonloucks.contracts.api.AutoClose#close() when done}
+ *
+ * @param config the Server configuration for the new instance
+ * @return the new Server instance
+ */
+ Server create(Server.Config config);
+
+ /**
+ * Create a new instance of Server
+ *
+ * @param builderConsumer the config builder consumer callback
+ * @return the new Server instance
+ * @throws IllegalArgumentException if builderConsumer is null or when configuration is invalid
+ */
+ Server create(Consumer builderConsumer);
+
+ /**
+ * Install all the requirements and promises to the given Server Repository.
+ * Include Server#CONTRACT which will private a unique
+ *
+ * @param config the Server config
+ * @param repository the repository to add requirements and promises to
+ * @throws IllegalArgumentException if config is null, config is invalid, or repository is null
+ */
+ void install(Server.Config config, Repository repository);
+}
diff --git a/server/src/main/java/io/github/jonloucks/example/server/ServerFactoryFinder.java b/server/src/main/java/io/github/jonloucks/example/server/ServerFactoryFinder.java
new file mode 100644
index 0000000..b7483f5
--- /dev/null
+++ b/server/src/main/java/io/github/jonloucks/example/server/ServerFactoryFinder.java
@@ -0,0 +1,71 @@
+package io.github.jonloucks.example.server;
+
+import java.util.Optional;
+import java.util.ServiceLoader;
+
+import static io.github.jonloucks.contracts.api.Checks.configCheck;
+import static io.github.jonloucks.contracts.api.Checks.nullCheck;
+import static java.util.Optional.ofNullable;
+
+/**
+ * Responsible for locating and creating the ServerFactory for a deployment.
+ */
+public final class ServerFactoryFinder {
+ public ServerFactoryFinder(Server.Config config) {
+ this.config = configCheck(config);
+ }
+
+ public ServerFactory get() {
+ return find().orElseThrow(this::newNotFoundException);
+ }
+
+ public Optional find() {
+ final Optional byReflection = createByReflection();
+ if (byReflection.isPresent()) {
+ return byReflection;
+ }
+ return createByServiceLoader();
+ }
+
+ private Optional createByServiceLoader() {
+ if (config.useServiceLoader()) {
+ try {
+ for (ServerFactory factory : ServiceLoader.load(getServiceFactoryClass())) {
+ return Optional.of(factory);
+ }
+ } catch (Throwable ignored) {
+ return Optional.empty();
+ }
+ }
+ return Optional.empty();
+ }
+
+ private Class extends ServerFactory> getServiceFactoryClass() {
+ return nullCheck(config.serviceLoaderClass(), "Server Service Loader class must be present.");
+ }
+
+ private Optional createByReflection() {
+ if (config.useReflection()) {
+ return getReflectionClassName().map(this::createNewInstance);
+ }
+ return Optional.empty();
+ }
+
+ private ServerFactory createNewInstance(String className) {
+ try {
+ return (ServerFactory)Class.forName(className).getConstructor().newInstance();
+ } catch (Throwable thrown) {
+ return null;
+ }
+ }
+
+ private Optional getReflectionClassName() {
+ return ofNullable(config.reflectionClassName()).filter(x -> !x.isEmpty());
+ }
+
+ private ServerException newNotFoundException() {
+ return new ServerException("Unable to find Server factory.");
+ }
+
+ private final Server.Config config;
+}
diff --git a/server/src/main/java/io/github/jonloucks/example/server/ServerFactoryImpl.java b/server/src/main/java/io/github/jonloucks/example/server/ServerFactoryImpl.java
new file mode 100644
index 0000000..f90440c
--- /dev/null
+++ b/server/src/main/java/io/github/jonloucks/example/server/ServerFactoryImpl.java
@@ -0,0 +1,103 @@
+package io.github.jonloucks.example.server;
+
+import io.github.jonloucks.concurrency.api.Concurrency;
+import io.github.jonloucks.concurrency.api.ConcurrencyFactory;
+import io.github.jonloucks.concurrency.api.StateMachineFactory;
+import io.github.jonloucks.concurrency.api.WaitableFactory;
+import io.github.jonloucks.contracts.api.Contracts;
+import io.github.jonloucks.contracts.api.Promisor;
+import io.github.jonloucks.contracts.api.Repository;
+import io.github.jonloucks.example.server.Server.Config;
+import io.github.jonloucks.metalog.api.*;
+
+import java.util.Optional;
+import java.util.function.Consumer;
+
+import static io.github.jonloucks.concurrency.api.GlobalConcurrency.findConcurrencyFactory;
+import static io.github.jonloucks.contracts.api.BindStrategy.IF_NOT_BOUND;
+import static io.github.jonloucks.contracts.api.Checks.*;
+import static io.github.jonloucks.contracts.api.GlobalContracts.lifeCycle;
+import static io.github.jonloucks.metalog.api.GlobalMetalog.findMetalogFactory;
+
+public class ServerFactoryImpl implements ServerFactory {
+ @Override
+ public Server create(Config config) {
+ final Server.Config validConfig = enhancedConfigCheck(config);
+ final Repository repository = validConfig.contracts().claim(Repository.FACTORY).get();
+
+ installConcurrency(validConfig, repository);
+ installMetalog(validConfig, repository);
+ installCore(validConfig, repository);
+
+ final Server server = new ServerImpl(validConfig, repository, true);
+ repository.keep(Server.CONTRACT, () -> server);
+ return server;
+ }
+
+ @Override
+ public Server create(Consumer builderConsumer) {
+ final Consumer validBuilderConsumer = builderConsumerCheck(builderConsumer);
+ final ConfigBuilderImpl configBuilder = new ConfigBuilderImpl();
+ validBuilderConsumer.accept(configBuilder);
+ return create(configBuilder);
+ }
+
+ @Override
+ public void install(Config config, Repository repository) {
+ final Server.Config validConfig = enhancedConfigCheck(config);
+ final Repository validRepository = nullCheck(repository, "Repository must be present.");
+
+ installConcurrency(validConfig, validRepository);
+ installMetalog(validConfig, validRepository);
+ installCore(validConfig, validRepository);
+
+ final Promisor serverPromisor = lifeCycle(() -> new ServerImpl(validConfig, validRepository, false));
+
+ validRepository.keep(Server.CONTRACT, serverPromisor, IF_NOT_BOUND);
+ }
+
+ private void installConcurrency(Server.Config config, Repository repository) {
+ final Concurrency.Config concurrencyConfig = new Concurrency.Config() {
+ @Override
+ public Contracts contracts() {
+ return config.contracts();
+ }
+ };
+ //noinspection ResultOfMethodCallIgnored
+ contractsCheck(concurrencyConfig.contracts());
+ final Optional optionalFactory = findConcurrencyFactory(concurrencyConfig);
+
+ optionalFactory.ifPresent(f -> f.install(concurrencyConfig, repository));
+ }
+
+ private void installMetalog(Server.Config config, Repository repository) {
+ final Metalog.Config metalogConfig = new Metalog.Config() {
+ @Override
+ public Contracts contracts() {
+ return config.contracts();
+ }
+ };
+ //noinspection ResultOfMethodCallIgnored
+ contractsCheck(metalogConfig.contracts());
+ final Optional optionalFactory = findMetalogFactory(metalogConfig);
+
+ optionalFactory.ifPresent(f -> f.install(metalogConfig, repository));
+ }
+
+ private Server.Config enhancedConfigCheck(Server.Config config) {
+ final Server.Config candidateConfig = configCheck(config);
+ final Contracts contracts = contractsCheck(candidateConfig.contracts());
+
+ if (contracts.isBound(Server.CONTRACT)) {
+ throw new ServerException("Server is already bound.");
+ }
+
+ return candidateConfig;
+ }
+
+ private void installCore(Server.Config config, Repository repository) {
+ repository.require(Repository.FACTORY);
+ repository.require(WaitableFactory.CONTRACT);
+ repository.require(StateMachineFactory.CONTRACT);
+ }
+}
diff --git a/server/src/main/java/io/github/jonloucks/example/server/ServerImpl.java b/server/src/main/java/io/github/jonloucks/example/server/ServerImpl.java
new file mode 100644
index 0000000..47b497a
--- /dev/null
+++ b/server/src/main/java/io/github/jonloucks/example/server/ServerImpl.java
@@ -0,0 +1,48 @@
+package io.github.jonloucks.example.server;
+
+import io.github.jonloucks.concurrency.api.Idempotent;
+import io.github.jonloucks.concurrency.api.StateMachine;
+import io.github.jonloucks.concurrency.api.WaitableNotify;
+import io.github.jonloucks.contracts.api.AutoClose;
+import io.github.jonloucks.contracts.api.Repository;
+
+import static io.github.jonloucks.concurrency.api.Idempotent.withClose;
+import static io.github.jonloucks.concurrency.api.Idempotent.withOpen;
+import static io.github.jonloucks.contracts.api.Checks.configCheck;
+import static io.github.jonloucks.contracts.api.Checks.nullCheck;
+
+final class ServerImpl implements Server {
+
+ @Override
+ public AutoClose open() {
+ return withOpen(stateMachine, this::realOpen);
+ }
+
+ @Override
+ public WaitableNotify lifeCycleNotify() {
+ return stateMachine;
+ }
+
+ ServerImpl(Server.Config config, Repository repository, boolean openRepository) {
+ this.config = configCheck(config);
+ final Repository validRepository = nullCheck(repository, "Repository must be present.");
+ this.closeRepository = openRepository ? validRepository.open() : AutoClose.NONE;
+ this.stateMachine = Idempotent.createStateMachine(config.contracts());
+ }
+
+ private AutoClose realOpen() {
+ return this::exposedClose;
+ }
+
+ private void exposedClose() {
+ withClose(stateMachine, this::realClose);
+ }
+
+ private void realClose() {
+ closeRepository.close();
+ }
+
+ private final Server.Config config;
+ private final AutoClose closeRepository;
+ private final StateMachine stateMachine;
+}
diff --git a/server/src/main/java/module-info.java b/server/src/main/java/module-info.java
new file mode 100644
index 0000000..4c2f498
--- /dev/null
+++ b/server/src/main/java/module-info.java
@@ -0,0 +1,13 @@
+module io.github.jonloucks.example.server {
+ requires transitive io.github.jonloucks.contracts;
+ requires transitive io.github.jonloucks.concurrency;
+ requires transitive io.github.jonloucks.metalog;
+
+ uses io.github.jonloucks.contracts.api.ContractsFactory;
+ uses io.github.jonloucks.concurrency.api.ConcurrencyFactory;
+ uses io.github.jonloucks.metalog.api.MetalogFactory;
+
+ provides io.github.jonloucks.example.server.ServerFactory with io.github.jonloucks.example.server.ServerFactoryImpl;
+
+ exports io.github.jonloucks.example.server;
+}
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
index e5a05d5..ff37d8e 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,3 +1,4 @@
rootProject.name = 'examples'
-include 'basic-contracts'
-include 'basic-concurrency'
\ No newline at end of file
+include 'common'
+
+include 'server'
\ No newline at end of file
diff --git a/src/main/java/io/github/jonloucks/examples/Main.java b/src/main/java/io/github/jonloucks/examples/Main.java
new file mode 100644
index 0000000..e705ac3
--- /dev/null
+++ b/src/main/java/io/github/jonloucks/examples/Main.java
@@ -0,0 +1,82 @@
+package io.github.jonloucks.examples;
+
+import io.github.jonloucks.contracts.api.AutoClose;
+import io.github.jonloucks.contracts.api.Repository;
+import io.github.jonloucks.example.server.Server;
+import io.github.jonloucks.example.server.ServerFactory;
+import io.github.jonloucks.example.server.ServerFactoryFinder;
+import io.github.jonloucks.examples.common.Common;
+import io.github.jonloucks.examples.common.Program;
+
+import java.util.Arrays;
+
+import static io.github.jonloucks.contracts.api.BindStrategy.ALWAYS;
+import static io.github.jonloucks.contracts.api.GlobalContracts.claimContract;
+import static io.github.jonloucks.examples.common.Common.waitForQuitting;
+import static io.github.jonloucks.examples.common.Constants.*;
+
+public final class Main {
+
+ /**
+ * Main entry point.
+ * Note. Entry points are where final decisions on dependency inversions are made.
+ * Not all decisions must be made in an entry point, but in this example it is exposed
+ * for visibility
+ * @param args the command line arguments
+ */
+ public static void main(String[] args) {
+ final Repository repository = createMyRepository(args);
+ try (AutoClose closeRepository = repository.open()) {
+ final Program program = claimContract(PROGRAM);
+ installCommand(program);
+ program.runCommandLine( c -> {});
+ waitForQuitting();
+ System.exit(0);
+ } catch (Exception thrown) {
+ System.err.println(thrown.getMessage());
+ System.exit(1);
+ }
+ }
+
+ private static void installCommand(Program program) {
+ program.keepCommand(new ServerCommand());
+ }
+
+ /**
+ * For complex projects with many modules and entry points there a many
+ * ways to extend and manage dependency inversions without
+ * have them all defined here.
+ * For example:
+ * each module could have its own Repository.
+ * MyModule.createRepository(contracts)
+ * each module could have a static method to install contracts to a given repository
+ * MyModule.install(repository)
+ * each module could use the ServiceLoader mechanism
+ * I am sure there are more like SpringBoot
+ */
+ private static Repository createMyRepository(String[] args) {
+ final Repository repository = claimContract(Repository.FACTORY).get();
+
+ Common.install(repository);
+
+ installServer(repository);
+
+ // Save the command line for later use
+ repository.keep(PROGRAM_ARGUMENTS, () -> Arrays.asList(args), ALWAYS);
+
+ // Constant string, but could be changed to a localized value without changing uses
+ repository.keep(PROGRAM_NAME, () -> "Examples", ALWAYS);
+
+ return repository;
+ }
+
+ private static void installServer(Repository repository) {
+ final ServerFactoryFinder finder = new ServerFactoryFinder(Server.Config.DEFAULT);
+ final ServerFactory serverFactory = finder.get();
+ serverFactory.install(Server.Config.DEFAULT, repository);
+ }
+
+ private Main() {
+
+ }
+}
diff --git a/src/main/java/io/github/jonloucks/examples/ServerCommand.java b/src/main/java/io/github/jonloucks/examples/ServerCommand.java
new file mode 100644
index 0000000..aa1a6c0
--- /dev/null
+++ b/src/main/java/io/github/jonloucks/examples/ServerCommand.java
@@ -0,0 +1,25 @@
+package io.github.jonloucks.examples;
+
+import io.github.jonloucks.example.server.Server;
+import io.github.jonloucks.examples.common.Command;
+import io.github.jonloucks.examples.common.Common;
+
+import java.util.List;
+
+import static io.github.jonloucks.concurrency.api.Idempotent.CLOSED;
+import static io.github.jonloucks.contracts.api.GlobalContracts.claimContract;
+
+final class ServerCommand implements Command {
+ @Override
+ public String execute(List arguments) {
+ final Server server = claimContract(Server.CONTRACT);
+ //noinspection resource, scope is full life of server
+ server.lifeCycleNotify().notifyIf(s -> s == CLOSED, s -> Common.setQuitting());
+ return "Server started";
+ }
+
+ @Override
+ public String getName() {
+ return "server";
+ }
+}
diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java
new file mode 100644
index 0000000..1f5de32
--- /dev/null
+++ b/src/main/java/module-info.java
@@ -0,0 +1,14 @@
+module io.github.jonloucks.examples {
+ requires transitive io.github.jonloucks.contracts;
+ requires transitive io.github.jonloucks.concurrency;
+ requires transitive io.github.jonloucks.metalog;
+ requires transitive io.github.jonloucks.examples.common;
+ requires transitive io.github.jonloucks.example.server;
+
+ uses io.github.jonloucks.contracts.api.ContractsFactory;
+ uses io.github.jonloucks.concurrency.api.ConcurrencyFactory;
+ uses io.github.jonloucks.metalog.api.MetalogFactory;
+ uses io.github.jonloucks.example.server.ServerFactory;
+
+ exports io.github.jonloucks.examples;
+}
\ No newline at end of file