Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .idea/gradle.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions .idea/protoeditor.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,10 @@ repositories {
}

dependencies {
implementation project(":messages")
implementation project(":common")
implementation project(":server")
implementation project(":client")

implementation libs.contracts
implementation libs.concurrency
Expand Down
37 changes: 37 additions & 0 deletions client/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
plugins {
id 'java-library'
}

getTasks().withType(JavaCompile.class).configureEach {
getOptions().getRelease().set(9)
}

repositories {
mavenCentral()
mavenLocal()
}

dependencies {
api libs.contracts.api
api libs.concurrency.api
api libs.metalog.api
api project(":common")

implementation libs.contracts
implementation libs.concurrency
implementation libs.metalog
implementation project(":messages")

implementation libs.grpc.protobuf
implementation libs.grpc.stub
implementation libs.grpc.services
runtimeOnly libs.grpc.netty.shaded

testImplementation libs.contracts.test
testImplementation libs.concurrency.test
testImplementation libs.metalog.test
}

test {
useJUnitPlatform()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package io.github.jonloucks.example.client;

import io.github.jonloucks.concurrency.api.Idempotent;
import io.github.jonloucks.concurrency.api.WaitableNotify;
import io.github.jonloucks.contracts.api.AutoOpen;
import io.github.jonloucks.contracts.api.Contract;
import io.github.jonloucks.contracts.api.Contracts;
import io.github.jonloucks.contracts.api.GlobalContracts;

import java.time.Duration;
import java.util.function.Supplier;

public interface Client extends AutoOpen {
Contract<Client> CONTRACT = Contract.create(Client.class);

WaitableNotify<Idempotent> lifeCycleNotify();

String getWeatherReport();

interface Config {
/**
* The default configuration used when creating a new Client instance
*/
Config DEFAULT = new Config() {};

/**
* @return the contracts, some use case have their own Contracts instance.
*/
default Contracts contracts() {
return GlobalContracts.getInstance();
}

/**
* @return if true, reflection might be used to locate the ClientFactory
*/
default boolean useReflection() {
return true;
}

/**
* @return the class name to use if reflection is used to find the ClientFactory
*/
default String reflectionClassName() {
return "io.github.jonloucks.example.client.ClientFactoryImpl";
}

/**
* @return if true, the ServiceLoader might be used to locate the ClientFactory
*/
default boolean useServiceLoader() {
return true;
}

/**
* @return the class name to load from the ServiceLoader to find the ClientFactory
*/
default Class<? extends ClientFactory> serviceLoaderClass() {
return ClientFactory.class;
}

default int port() {
return 50052;
}

default String hostname() {
return "localhost";
}

default Duration shutdownTimeout() {
return Duration.ofSeconds(60);
}

interface Builder extends Config {
Contract<Supplier<Builder>> FACTORY = Contract.create("Client Config Builder Factory");

Builder contracts(Contracts contracts);

Builder port(int port);

Builder hostname(String hostname);

Builder shutdownTimeout(Duration shutdownTimeout);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package io.github.jonloucks.example.client;

import static io.github.jonloucks.contracts.api.Checks.messageCheck;

/**
* Runtime exception thrown for Client related problems.
* For example, when Client fails to initialize.
*/
public class ClientException extends RuntimeException {

private static final long serialVersionUID = 0L;

/**
* Passthrough for {@link RuntimeException#RuntimeException(String)}
*
* @param message the message for this exception
*/
public ClientException(String message) {
this(message, null);
}

/**
* Passthrough for {@link RuntimeException#RuntimeException(String, Throwable)}
*
* @param message the message for this exception
* @param thrown the cause of this exception, null is allowed
*/
public ClientException(String message, Throwable thrown) {
super(messageCheck(message), thrown);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package io.github.jonloucks.example.client;

import io.github.jonloucks.contracts.api.AutoOpen;
import io.github.jonloucks.contracts.api.Contract;
import io.github.jonloucks.contracts.api.Repository;

import java.util.function.Consumer;

/**
* Responsible for creating new instances of Client
*/
public interface ClientFactory {

/**
* Used to promise and claim the ClientFactory implementation
*/
Contract<ClientFactory> CONTRACT = Contract.create(ClientFactory.class);

/**
* Create a new instance of Client
* <p>
* Note: caller is responsible for calling {@link AutoOpen#open()} and calling
* the {@link io.github.jonloucks.contracts.api.AutoClose#close() when done}
* </p>
* @param config the Client configuration for the new instance
* @return the new Client instance
*/
Client create(Client.Config config);

/**
* Create a new instance of Client
*
* @param builderConsumer the config builder consumer callback
* @return the new Client instance
* @throws IllegalArgumentException if builderConsumer is null or when configuration is invalid
*/
Client create(Consumer<Client.Config.Builder> builderConsumer);

/**
* Install all the requirements and promises to the given Client Repository.
* Include Client#CONTRACT which will private a unique
*
* @param config the Client 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(Client.Config config, Repository repository);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package io.github.jonloucks.example.client;

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 ClientFactory for a deployment.
*/
public final class ClientFactoryFinder {
public ClientFactoryFinder(Client.Config config) {
this.config = configCheck(config);
}

public ClientFactory get() {
return find().orElseThrow(this::newNotFoundException);
}

public Optional<ClientFactory> find() {
final Optional<ClientFactory> byReflection = createByReflection();
if (byReflection.isPresent()) {
return byReflection;
}
return createByServiceLoader();
}

private Optional<ClientFactory> createByServiceLoader() {
if (config.useServiceLoader()) {
try {
for (ClientFactory factory : ServiceLoader.load(getServiceFactoryClass())) {
return Optional.of(factory);
}
} catch (Throwable ignored) {
return Optional.empty();
}
}
return Optional.empty();
}

private Class<? extends ClientFactory> getServiceFactoryClass() {
return nullCheck(config.serviceLoaderClass(), "Client Service Loader class must be present.");
}

private Optional<ClientFactory> createByReflection() {
if (config.useReflection()) {
return getReflectionClassName().map(this::createNewInstance);
}
return Optional.empty();
}

private ClientFactory createNewInstance(String className) {
try {
return (ClientFactory)Class.forName(className).getConstructor().newInstance();
} catch (Throwable thrown) {
return null;
}
}

private Optional<String> getReflectionClassName() {
return ofNullable(config.reflectionClassName()).filter(x -> !x.isEmpty());
}

private ClientException newNotFoundException() {
return new ClientException("Unable to find Client factory.");
}

private final Client.Config config;
}
Loading