Skip to content

Commit

Permalink
[closes #433] Replace Service interface with an abstract class
Browse files Browse the repository at this point in the history
  • Loading branch information
avano authored and mmuzikar committed Apr 25, 2023
1 parent 8981471 commit 59a809c
Show file tree
Hide file tree
Showing 133 changed files with 576 additions and 759 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ using JUnit's `@AfterAll` method).

## Example service

Each service implements a marker interface [Service](./system-x/common/src/main/java/software/tnb/common/service/Service.java).
Each service extends an abstract [Service](./system-x/common/src/main/java/software/tnb/common/service/Service.java) class that provides
the `account`,
`client` and `validation` fields and methods.

### Remote service

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ default Properties toProperties() {
for (Field field : getAllFields(new ArrayList<>(), this.getClass())) {
try {
field.setAccessible(true);
properties.put(StringUtils.replaceUnderscoreWithCamelCase(field.getName()), field.get(this));
// Null values can't be stored in properties
if (field.get(this) != null) {
properties.put(StringUtils.replaceUnderscoreWithCamelCase(field.getName()), field.get(this));
}
} catch (IllegalAccessException e) {
throw new RuntimeException("Unable to get field " + field.getName() + " value: ", e);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package software.tnb.common.account;

import java.util.Properties;

public class NoAccount implements Account {
@Override
public Properties toProperties() {
throw new RuntimeException("This account shouldn't be used, as the service doesn't require an account");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package software.tnb.common.client;

public class NoClient {
}
Original file line number Diff line number Diff line change
@@ -1,38 +1,31 @@
package software.tnb.common.service;

import software.tnb.common.account.Account;
import software.tnb.common.service.configuration.ServiceConfiguration;
import software.tnb.common.util.ReflectionUtil;
import software.tnb.common.validation.Validation;

import org.junit.platform.commons.util.ReflectionUtils;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

/**
* For services that can have more configurations for deployment
* <p>
* WARNING: It is not recommended to use ConfigurableService with ReusableOpenshiftDeployable!
* because ConfigurableService service can have more deployment configuration in the same test run,
* e.g. TestClass1 -> ServiceA.config1 , ... TestClass4 -> ServiceA.config2 , ... TestClass6 -> ServiceA.config1
* and in that case, the ServiceA.config1 will not be undeployed before ServiceA.config2 which would cause that deploy ServiceA.config2 would fail.
*
* @param <C> Service configuration class which extends ServiceConfiguration
*/
public abstract class ConfigurableService<C extends ServiceConfiguration> implements Service {
private final C configuration;
public abstract class ConfigurableService<A extends Account, C, V extends Validation, S extends ServiceConfiguration> extends Service<A, C, V> {
private final S configuration;

public ConfigurableService() {
Class<?> current = this.getClass();
while (true) {
Type superClass = current.getGenericSuperclass();
if (superClass instanceof ParameterizedType && ((ParameterizedType) superClass).getRawType().equals(ConfigurableService.class)) {
break;
} else {
current = current.getSuperclass();
}
}
configuration = ReflectionUtils.newInstance((Class<C>) ((ParameterizedType) current.getGenericSuperclass()).getActualTypeArguments()[0]);
Class<S> serviceConfigurationClass = (Class<S>) ReflectionUtil.getGenericTypesOf(ConfigurableService.class, this.getClass())[3];
configuration = ReflectionUtils.newInstance(serviceConfigurationClass);
}

public C getConfiguration() {
public S getConfiguration() {
return configuration;
}

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,7 +1,31 @@
package software.tnb.common.service;

import software.tnb.common.account.Account;
import software.tnb.common.account.AccountFactory;
import software.tnb.common.util.ReflectionUtil;
import software.tnb.common.validation.Validation;

import org.junit.jupiter.api.extension.AfterAllCallback;
import org.junit.jupiter.api.extension.BeforeAllCallback;

public interface Service extends BeforeAllCallback, AfterAllCallback {
public abstract class Service<A extends Account, C, V extends Validation> implements BeforeAllCallback, AfterAllCallback {
protected A account;
protected C client;
protected V validation;

public A account() {
if (account == null) {
Class<A> accountClass = (Class<A>) ReflectionUtil.getGenericTypesOf(Service.class, this.getClass())[0];
account = AccountFactory.create(accountClass);
}
return account;
}

protected C client() {
return client;
}

public V validation() {
return validation;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,15 @@ private ServiceFactory() {
* @param clazz class to create
* @param <S> type
*/
public static <S extends Service> S create(Class<S> clazz) {
public static <S extends Service<?, ?, ?>> S create(Class<S> clazz) {
S service = loadService(clazz);
if (service instanceof ConfigurableService<?>) {
((ConfigurableService<?>) service).defaultConfiguration();
if (service instanceof ConfigurableService<?, ?, ?, ?>) {
((ConfigurableService<?, ?, ?, ?>) service).defaultConfiguration();
}
return service;
}

private static <S extends Service> S loadService(Class<S> clazz) {
private static <S extends Service<?, ?, ?>> S loadService(Class<S> clazz) {
if (ReflectionUtils.isAbstract(clazz) || clazz.isInterface()) {
final ServiceLoader<S> loader = ServiceLoader.load(clazz);
if (loader.stream().findAny().isEmpty()) {
Expand Down Expand Up @@ -74,22 +74,22 @@ private static <S extends Service> S loadService(Class<S> clazz) {
}
}

public static <C extends ServiceConfiguration, S extends ConfigurableService<C>> S create(Class<S> clazz, Consumer<C> config) {
public static <C extends ServiceConfiguration, S extends ConfigurableService<?, ?, ?, C>> S create(Class<S> clazz, Consumer<C> config) {
S service = create(clazz);
config.accept(service.getConfiguration());
return service;
}

public static <S extends Service> void withService(Class<S> clazz, Consumer<S> code) {
public static <S extends Service<?, ?, ?>> void withService(Class<S> clazz, Consumer<S> code) {
withService(create(clazz), code);
}

public static <C extends ServiceConfiguration, S extends ConfigurableService<C>> void withService(Class<S> clazz, Consumer<C> config,
public static <C extends ServiceConfiguration, S extends ConfigurableService<?, ?, ?, C>> void withService(Class<S> clazz, Consumer<C> config,
Consumer<S> code) {
withService(create(clazz, config), code);
}

private static <S extends Service> void withService(S service, Consumer<S> code) {
private static <S extends Service<?, ?, ?>> void withService(S service, Consumer<S> code) {
try {
service.beforeAll(null);
} catch (Exception e) {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package software.tnb.common.util;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

public final class ReflectionUtil {
private ReflectionUtil() {
}

/**
* This method walks though parent classes until the parent class is equal to the first argument of the method. Then it returns an array of
* generic types of the class.
*
* @param parent parent class that is generic
* @param clazz current class
* @return an array of generic types of the parent class
*/
public static Type[] getGenericTypesOf(Class<?> parent, Class<?> clazz) {
Class<?> current = clazz;
while (true) {
Type superClass = current.getGenericSuperclass();
if (superClass instanceof ParameterizedType && ((ParameterizedType) superClass).getRawType().equals(parent)) {
break;
} else {
current = current.getSuperclass();
}
}

return ((ParameterizedType) (current.getGenericSuperclass())).getActualTypeArguments();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package software.tnb.common.validation;

public class NoValidation implements Validation {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package software.tnb.common.validation;

public interface Validation {
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@ public class Cloudwatch extends AWSService<AWSAccount, CloudWatchClient, Cloudwa
public void beforeAll(ExtensionContext extensionContext) throws Exception {
super.beforeAll(extensionContext);
LOG.debug("Creating new Cloudwatch validation");
validation = new CloudwatchValidation(client(CloudWatchClient.class));
validation = new CloudwatchValidation(client());
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package software.tnb.aws.cloudwatch.validation;

import software.tnb.aws.cloudwatch.validation.model.MetricsRequest;
import software.tnb.common.service.Validation;
import software.tnb.common.validation.Validation;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
import software.tnb.common.account.AccountFactory;
import software.tnb.common.service.ConfigurableService;
import software.tnb.common.service.ServiceFactory;
import software.tnb.common.service.Validation;
import software.tnb.common.util.ReflectionUtil;
import software.tnb.common.validation.Validation;

import org.junit.jupiter.api.extension.ExtensionContext;

Expand All @@ -15,38 +16,34 @@

import software.amazon.awssdk.core.SdkClient;

public abstract class AWSService<A extends AWSAccount, C extends SdkClient, V extends Validation> extends ConfigurableService<AWSConfiguration> {
public abstract class AWSService<A extends AWSAccount, C extends SdkClient, V extends Validation>
extends ConfigurableService<A, C, V, AWSConfiguration> {
protected LocalStack localStack;

protected static final Logger LOG = LoggerFactory.getLogger(AWSService.class);

protected A account;
protected C client;
protected V validation;

@Override
protected void defaultConfiguration() {
getConfiguration().useLocalstack(false);
}

@Override
public A account() {
if (account == null) {
account = (A) AccountFactory.create(AWSAccount.class);
Class<A> accountClass = (Class<A>) ReflectionUtil.getGenericTypesOf(AWSService.class, this.getClass())[0];
account = AccountFactory.create(accountClass);
}
return account;
}

protected C client(Class<C> clazz) {
protected C client() {
if (client == null) {
client = AWSClient.createDefaultClient(account(), clazz, getConfiguration().isLocalstack() ? localStack.clientUrl() : null);
Class<C> clientClass = (Class<C>) ReflectionUtil.getGenericTypesOf(AWSService.class, this.getClass())[1];
client = AWSClient.createDefaultClient(account(), clientClass, getConfiguration().isLocalstack() ? localStack.clientUrl() : null);
}
return client;
}

public V validation() {
return validation;
}

@Override
public void beforeAll(ExtensionContext extensionContext) throws Exception {
if (getConfiguration().isLocalstack()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package software.tnb.aws.common.service;

import software.tnb.aws.common.account.AWSAccount;
import software.tnb.common.deployment.WithDockerImage;
import software.tnb.common.service.Service;
import software.tnb.common.validation.Validation;

public abstract class LocalStack implements Service, WithDockerImage {
import software.amazon.awssdk.core.SdkClient;

public abstract class LocalStack extends Service<AWSAccount, SdkClient, Validation> implements WithDockerImage {
protected static final int PORT = 4566;

public abstract String serviceUrl();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public void beforeAll(ExtensionContext extensionContext) throws Exception {
LOG.debug("Creating new DynamoDB validation");
streamsClient = AWSClient.createDefaultClient(account(), DynamoDbStreamsClient.class,
getConfiguration().isLocalstack() ? localStack.clientUrl() : null);
validation = new DynamoDBValidation(client(DynamoDbClient.class), streamsClient);
validation = new DynamoDBValidation(client(), streamsClient);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package software.tnb.aws.dynamodb.validation;

import software.tnb.common.service.Validation;
import software.tnb.common.utils.WaitUtils;
import software.tnb.common.validation.Validation;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
package software.tnb.aws.iam.service;

import software.tnb.aws.iam.validation.IAMValidation;

import software.tnb.aws.common.account.AWSAccount;
import software.tnb.aws.common.service.AWSService;
import software.tnb.aws.iam.validation.IAMValidation;

import org.junit.jupiter.api.extension.ExtensionContext;

Expand All @@ -16,7 +15,7 @@
@AutoService(IAM.class)
public class IAM extends AWSService<AWSAccount, IamClient, IAMValidation> {
@Override
protected IamClient client(Class<IamClient> clazz) {
protected IamClient client() {
// IAM client doesn't have the "create" method as other clients, probably because it's not tied to any region
if (client == null) {
client = IamClient.builder()
Expand All @@ -30,6 +29,6 @@ protected IamClient client(Class<IamClient> clazz) {
@Override
public void beforeAll(ExtensionContext context) throws Exception {
LOG.debug("Creating new IAM validation");
validation = new IAMValidation(client(IamClient.class));
validation = new IAMValidation(client());
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package software.tnb.aws.iam.validation;

import software.tnb.common.service.Validation;
import software.tnb.common.validation.Validation;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public void beforeAll(ExtensionContext extensionContext) throws Exception {
super.beforeAll(extensionContext);
iam.beforeAll(extensionContext);
LOG.debug("Creating new Kinesis validation");
validation = new KinesisFirehoseValidation(client(FirehoseClient.class), iam);
validation = new KinesisFirehoseValidation(client(), iam);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

import static org.junit.jupiter.api.Assertions.fail;

import software.tnb.common.service.Validation;
import software.tnb.common.utils.WaitUtils;
import software.tnb.aws.iam.service.IAM;
import software.tnb.common.utils.WaitUtils;
import software.tnb.common.validation.Validation;

import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@ public class Kinesis extends AWSService<AWSAccount, KinesisClient, KinesisValida
public void beforeAll(ExtensionContext extensionContext) throws Exception {
super.beforeAll(extensionContext);
LOG.debug("Creating new Kinesis validation");
validation = new KinesisValidation(client(KinesisClient.class));
validation = new KinesisValidation(client());
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package software.tnb.aws.kinesis.validation;

import software.tnb.common.service.Validation;
import software.tnb.common.validation.Validation;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down
Loading

0 comments on commit 59a809c

Please sign in to comment.