CRUD Repository findById() different return value - java

In my SpringBoot applicatication I'm using CrudRepo.
I have found a problem with return value: Required != Found
GitHub: https://github.com/einhar/WebTaskManager/tree/findById-problem
No matter about changing method return type from Task into Object -> IDE stopped show error, but then it could be problem due to validation of data type later on.
Do you know how to fix it? Any hint?
CrudRepo
public interface TaskRepository extends CrudRepository<Task, Integer> {}
Service
#Service
#Transactional
public class TaskService {
#Autowired
private final TaskRepository taskRepository;
public TaskService(TaskRepository taskRepository) {
this.taskRepository = taskRepository;
}
public List<Task> findAll() {
List<Task> tasks = new ArrayList<>();
for (Task task : taskRepository.findAll()) {
tasks.add(task);
}
return tasks; // Work properly :)
}
/* ... */
public Task findTask(Integer id) {
return taskRepository.findById(id); // Find:Task | Required: java.util.Optional :(
}
}

The findById method is return Optional, So you can get the task by get() method. You can choose the following 3 case
You will get an exception when Task not found:
public Task findTask(Integer id) {
return taskRepository.findById(id).get();
}
You will get null when Task not found:
public Task findTask(Integer id) {
return taskRepository.findById(id).orElse(null);
}
You will get an empty new Task when Task not found:
public Task findTask(Integer id) {
return taskRepository.findById(id).orElse(new Task());
}
Or just return the Optional Object
public Optional<Task> findTask(Integer id) {
return taskRepository.findById(id);
}

in your CrudRepo create a method:
Task getById(Integer id);
and then call this method in your TaskService and you should be ready to go:)

I think no need to create getById(... id) method in Repository bean class because in SimpleJPARepository such method is already implemented. So you can directly call this method.
See Official spring document:-
/*
* (non-Javadoc)
* #see org.springframework.data.repository.CrudRepository#findById(java.io.Serializable)
*/
public Optional<T> findById(ID id) {
Assert.notNull(id, ID_MUST_NOT_BE_NULL);
Class<T> domainType = getDomainClass();
if (metadata == null) {
return Optional.ofNullable(em.find(domainType, id));
}
LockModeType type = metadata.getLockModeType();
Map<String, Object> hints = getQueryHints().withFetchGraphs(em).asMap();
return Optional.ofNullable(type == null ? em.find(domainType, id, hints) : em.find(domainType, id, type, hints));
}

You will get an exception when Task not found, to solve this add an exception to your code
like this:
public Task findTask(Integer id) {
return taskRepository.findById(id).orElseThrow(()-> new RuntimeException(String.format("Account %s not found",id)));
}

Related

Correct class hierarchy

I was wondering what is the correct way to organize my class hierarchy in the following situation.
I wanted to build an abstraction around postgresql advisory lock.
Note just for context: An advisory lock is a lock that you can obtain at a session or transaction level. Postgres handle all the complexity for you.
The code that I've written so far is something like
interface DBLockService
interface SessionLockService : DBLockService {
fun acquire(id: Long)
fun unlock(id: Long): Boolean
}
interface TransactionalLockService : DBLockService {
fun txAcquire(id: Long)
}
abstract class BaseDBLockService(protected val entityManager: EntityManager): DBLockService {
protected fun executeAcquire(preparedStatement: String, id: Long) {
executeAcquire<Any>(preparedStatement, id)
}
protected inline fun <reified T> executeAcquire(preparedStatement: String, id: Long) =
entityManager
.createNativeQuery(preparedStatement, T::class.java)
.setParameter("id", id)
.singleResult as T
}
#Component
class LockServiceImpl(
entityManager: EntityManager
) : BaseDBLockService(entityManager),
SessionLockService {
companion object {
const val acquireStatement = "SELECT pg_advisory_lock(:id)"
const val unlockStatement = "SELECT pg_advisory_unlock(:id)"
}
override fun acquire(id: Long) {
executeAcquire(acquireStatement, id)
}
override fun unlock(id: Long) =
executeAcquire<Boolean>(unlockStatement, id)
}
#Component
class TransactionalLockServiceImpl(
entityManager: EntityManager
) : BaseDBLockService(entityManager),
TransactionalLockService {
// very similar implementation
}
Looking at this code there is something that tell's me that there is something wrong:
DBLockService is a bit useless interface, there is no method
Are SessionLockService and TransactionalLockService just an implementation detail? Is it correct that there is a different interface for every "type" of lock?
But at the same time, if I remove the DBLockService seems very odd to me that there are 2 interfaces (SessionLockService and TransactionalLockService) with very similar context that are not related in any way.
Moreover, removing DBLockService, I'll have the 2 implementations (LockServiceImpl and TransactionalLockServiceImpl) that extends from the abstract class BaseDBLockService to implement these 2 interfaces but at the same time the abstract class is not related to them.
What to you think?
Thanks
Update
As requested I'll add an example of a real case scenario
#Service
class SomethingService(private val lockService: TransactionalLockService){
#Transactional
fun aMethod(entityId: Long){
lockService.txAcquire(entityId)
//code to be synchronized or there will be problems
}
}
I would like to inject a class of a generic LockService but I cannot find a way to abstract that because imho a lock that disappear after the transaction ends is a lock different from a lock that disappear after the connection to the db is closed (session lock) that is different from a lock that need to be unlocked automatically.
It's possible that there are a lot of other implementations of lock, for example a TimeoutLock that remove the lock after some time.
But I'm not able to think how to separate these implementation details from the general concept of a Lock.
Okay, thanks for the example. I still find it a bit odd to call what you want to implement a Service. I'd probably call it a Strategy, but that's not really that important. It's just a semantic preference.
Anyway, what I would do is probably something like the following (untested/pseudo code):
interface LockService {
Boolean acquire(Long id);
Boolean unlock(Long id);
}
abstract class BaseLockService
implements LockService {
protected EntityManager entityManager;
BaseLockService(EntityManager entityManager) {
this.entityManager = entityManager;
}
protected Boolean executeAcquire(String preparedStatement, Long id) {
// your generic implementation
}
}
class SessionLockService
extends BaseLockService {
private static class Statements {
static final String acquireStatement = "...";
static final String unlockStatement = "...";
}
SessionLockService(EntityManager entityManager) {
super(entityManager);
}
#Override
Boolean acquire(Long id) {
return executeAcquire(Statements.acquireStatement, id);
}
#Override
Boolean unlock(Long id) {
return executeAcquire(Statements.unlockStatement, id);
}
}
class TransactionalLockService
extends BaseLockService {
private static class Statements {
static final String acquireStatement = "...";
}
TransactionalLockService(EntityManager entityManager) {
super(entityManager);
}
#Override
Boolean acquire(Long id) {
return executeAcquire(Statements.acquireStatement, id);
}
#Override
Boolean unlock(Long id) {
// simply return true
return true;
// or if there's some Postgres or EntityManager mechanism to find out if the transaction is still active:
return !entityManager.isInTransaction(id);
}
}
class SomeService {
private final LockService lockService;
SomeService(LockService lockService) {
this.lockService = lockService;
}
void aMethod(Long entityId) {
if(!lockService.acquire(entityId)) {
throw new SomeException();
}
// do code that needs lock
if(!lockService.unlock(entityId)) {
throw new SomeException();
}
}
}
So basically, I would use a common interface and just make TransactionalLockService.unlock() sort of a no-op function that always returns true or, if achievable, more desirable: return the result of some probe mechanism to find out if the transaction with id has correctly ended.
Another idea would be to have a Lock interface, that a LockService returns (very abbreviated example):
interface Lock {
Boolean unlock();
}
interface LockService {
Lock acquire(Long id);
}
class TransactionalLock
implements Lock {
private Long id;
TransactionalLock(Long id) {
this.id = id;
}
#Override
Boolean unlock() {
// again, either simply return true
return true;
// ...or some result that verifies the transaction has ended
return verifyTransactionHasEnded(id);
}
}
class SomeService {
private final LockService lockService;
SomeService(LockService lockService) {
this.lockService = lockService;
}
void aMethod(Long entityId) {
Lock lock = lockService.acquire(entityId);
if(lock == null) {
throw new SomeException();
}
// do code that needs lock
if(!lock.unlock()) {
throw new SomeException();
}
}
}
...etc., but that could get very complex very fast, because the Lock implementations need their own mechanism to unlock themselves.
This might still not be exactly what you're looking for, but hopefully it gives you some ideas.

Resilience4j context propagator not able to propagte thread local values

I am trying to migrate my circuit breaker code from Hystrix to Resilience4j. The communication is between two applications out of which one is an artifact containing all the resilience 4j config in the java code itself and the second application which is a microservice uses it directly.
There's one RequestId which generates in the microservice and propagates to the artifact context where it gets printed in the logs. With Hystrix, it was working perfectly fine but ever since I moved to resilience, I am getting null for the request Id.
Below is my config for bulk head and context propagator :
ThreadPoolBulkheadConfig bulkheadConfig = ThreadPoolBulkheadConfig.custom()
.maxThreadPoolSize(maxThreadPoolSize)
.coreThreadPoolSize(coreThreadPoolSize)
.queueCapacity(queueCapacity)
.contextPropagator(new DummyContextPropagator())
.build();
// Bulk Head Registry
ThreadPoolBulkheadRegistry bulkheadRegistry = ThreadPoolBulkheadRegistry.of(bulkheadConfig);
// Create Bulk Head
ThreadPoolBulkhead bulkhead = bulkheadRegistry.bulkhead(name, bulkheadConfig);
Dummy Context Propagator :
public class DummyContextPropagator implements ContextPropagator {
private static final Logger log = LoggerFactory.getLogger( DummyContextPropagator.class);
#Override
public Supplier<Optional<Object>> retrieve() {
return () -> (Optional<Object>) get();
}
#Override
public Consumer<Optional<Object>> copy() {
return (t) -> t.ifPresent(e -> {
clear();
put(e);
});
}
#Override
public Consumer<Optional<Object>> clear() {
return (t) -> DummyContextHolder.clear();
}
public static class DummyContextHolder {
private static final ThreadLocal threadLocal = new ThreadLocal();
private DummyContextHolder() {
}
public static void put(Object context) {
if (threadLocal.get() != null) {
clear();
}
threadLocal.set(context);
}
public static void clear() {
if (threadLocal.get() != null) {
threadLocal.set(null);
threadLocal.remove();
}
}
public static Optional<Object> get() {
return Optional.ofNullable(threadLocal.get());
}
}
}
However, nothing seems to work so that I can get the RequestId.
Am I doing everything right or is there another way to do that ?
i think you want to get params from threadlocal from parent-thread when you in sub-thread, in hystrix it use command-model to decorate callabletask
in resilience4j i think u can fix it like this:
#Resource
DispatcherServlet dispatcherServlet;
#PostConstruct
public void changeThreadLocalModel() {
dispatcherServlet.setThreadContextInheritable(true);
}
i find my last answer may lead to some problems, when you use "dispatcherServlet.setThreadContextInheritable(true);"
it may pollute your custom thread-pool`s threadlocalmap;
so here is my final resolve, and it only works at resilience4j;
#Resource
Resilience4jBulkheadProvider resilience4jBulkheadProvider;
#PostConstruct
public void concurrentThreadContextStrategy() {
ThreadPoolBulkheadConfig threadPoolBulkheadConfig = ThreadPoolBulkheadConfig.custom().contextPropagator(new CustomInheritContextPropagator()).build();
resilience4jBulkheadProvider.configureDefault(id -> new Resilience4jBulkheadConfigurationBuilder()
.bulkheadConfig(BulkheadConfig.ofDefaults()).threadPoolBulkheadConfig(threadPoolBulkheadConfig)
.build());
}
private static class CustomInheritContextPropagator implements ContextPropagator<RequestAttributes> {
#Override
public Supplier<Optional<RequestAttributes>> retrieve() {
// give requestcontext to reference from threadlocal;
// this method call by web-container thread, such as tomcat, jetty,or undertow, depends on what you used;
return () -> Optional.ofNullable(RequestContextHolder.getRequestAttributes());
}
#Override
public Consumer<Optional<RequestAttributes>> copy() {
// load requestcontex into real-call thread
// this method call by resilience4j bulkhead thread;
return requestAttributes -> requestAttributes.ifPresent(context -> {
RequestContextHolder.resetRequestAttributes();
RequestContextHolder.setRequestAttributes(context);
});
}
#Override
public Consumer<Optional<RequestAttributes>> clear() {
// clean requestcontext finally ;
// this method call by resilience4j bulkhead thread;
return requestAttributes -> RequestContextHolder.resetRequestAttributes();
}
}
i got the same problem with springboot 2.5 et springboot cloud 2020.0.6
and I solved it with an implementation of ContextPropagator
public class SleuthPropagator implements ContextPropagator<TraceContext> {
ThreadLocal<ScopedSpan> scopedSpanThreadLocal = new ThreadLocal<>();
#Override
public Supplier<Optional<TraceContext>> retrieve() {
return this::getCurrentcontext;
}
#Override
public Consumer<Optional<TraceContext>> copy() {
return c -> {
if (!c.isPresent()) {
return;
}
TraceContext traceContext = c.get();
ScopedSpan resilience4jSpan = getTracer()
.map(t -> t.startScopedSpanWithParent("Resilience4j", traceContext))
.orElse(null);
scopedSpanThreadLocal.set(resilience4jSpan);
};
}
#Override
public Consumer<Optional<TraceContext>> clear() {
return t -> {
try {
ScopedSpan resilience4jSpan = scopedSpanThreadLocal.get();
if (resilience4jSpan != null) {
resilience4jSpan.finish();
}
} finally {
scopedSpanThreadLocal.remove();
}
};
}
private static Optional<Tracer> getTracer() {
return Optional.ofNullable(Tracing.current())
.map(Tracing::tracer);
}
private Optional<TraceContext> getCurrentcontext() {
return getTracer()
.map(Tracer::currentSpan)
.map(Span::context);
}
}
And use the propagator in adding this to your application.properties
resilience4j.thread-pool-bulkhead.instances.YOUR_BULKHEAD_CONFIG.context-propagators=com.your.package.SleuthPropagator

Flux into Mono List Object - project reactor

I'm using project reactor and I've the next issue:
I've one method that return Mono<CustomerResponse> that contains a CustomerDto list, each client has attributes, one of theirs attributes is a payment list. But this payment list is null.
I've another method that receive client id and returns a Flux payment Flux<PaymentDto> for that client.
This are the model
public class CustomerResponse {
private List<CustomerDto> customers;
}
public class CustomerDto {
private int id;
private String fullname;
private String documentNumber;
private List<PaymentDto> payments;
}
These are the interfaces
public interface CustomerService {
public Mono<CustomerResponse> customerSearch(CustomerRequest request);
}
public interface PaymentService {
public Flux<PaymentDto> getPayments(int clientId);
}
This is my method
public Mono<CustomerResponse> getCustomer(CustomerRequest request) {
return customerService.customerSearch(request).map(resp -> resp.getCustomers())
.flatMap(customerList -> {
List<CustomerDto> newCustomerList = customerList.parallelStream().map(customer -> {
Flux<PaymentDto> paymentFlux =
paymentService.getPayments(customer.getId());
// Here: java.lang.IllegalStateException: block()/blockFirst()/blockLast()
customer.setPayments(paymentFlux.collectList().block());
return customer;
}).collect(Collectors.toList());
return Mono.just(new CustomerResponse(newCustomerList));
});
}
I've the next exception:
java.lang.IllegalStateException: block()/blockFirst()/blockLast() are blocking, which is not supported in thread reactor-http-nio-4
at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(BlockingSingleSubscriber.java:83) ~[reactor-core-3.3.6.RELEASE.jar:3.3.6.RELEASE]
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
I would like to know if there is a non-blocking or optimal way to do it
You can refactor your code like this to avoid blocking call:
public Mono<CustomerResponse> getCustomer(CustomerRequest request) {
Flux<CustomerDto> customerDtoFluxEnriched = customerService.customerSearch(request)
.map(CustomerResponse::getCustomers).flatMapMany(Flux::fromIterable).flatMap(customerDto -> {
Flux<PaymentDto> paymentFlux = paymentService.getPayments(customerDto.getId());
Mono<List<PaymentDto>> paymentListMono = paymentFlux.collectList();
return paymentListMono.map(paymentList -> {
customerDto.setPayments(paymentList);
return customerDto;
});
});
return customerDtoFluxEnriched.collectList().map(customerList -> {
CustomerResponse customerResponse = new CustomerResponse();
customerResponse.setCustomers(customerList);
return customerResponse;
});
}

How to add instrumentation to GraphQL Java with graphql-spring-boot?

does anybody know how I can add instrumentation to a GraphQL execution when using graphql-spring-boot (https://github.com/graphql-java-kickstart/graphql-spring-boot) ? I know how this is possible with plain-vanilla graphql-java: https://www.graphql-java.com/documentation/v13/instrumentation/
However, I don't know how to do this when graphql-spring-boot is used and takes control over the execution. Due to lack of documentation I tried it simply this way:
#Service
public class GraphQLInstrumentationProvider implements InstrumentationProvider {
#Override
public Instrumentation getInstrumentation() {
return SimpleInstrumentation.INSTANCE;
}
}
But the method getInstrumentation on my InstrumentationProvider bean is (as expected) never called. Any help appreciated.
Answering my own question. In the meantime I managed to do it this way:
final class RequestLoggingInstrumentation extends SimpleInstrumentation {
private static final Logger logger = LoggerFactory.getLogger(RequestLoggingInstrumentation.class);
#Override
public InstrumentationContext<ExecutionResult> beginExecution(InstrumentationExecutionParameters parameters) {
long startMillis = System.currentTimeMillis();
var executionId = parameters.getExecutionInput().getExecutionId();
if (logger.isInfoEnabled()) {
logger.info("GraphQL execution {} started", executionId);
var query = parameters.getQuery();
logger.info("[{}] query: {}", executionId, query);
if (parameters.getVariables() != null && !parameters.getVariables().isEmpty()) {
logger.info("[{}] variables: {}", executionId, parameters.getVariables());
}
}
return new SimpleInstrumentationContext<>() {
#Override
public void onCompleted(ExecutionResult executionResult, Throwable t) {
if (logger.isInfoEnabled()) {
long endMillis = System.currentTimeMillis();
if (t != null) {
logger.info("GraphQL execution {} failed: {}", executionId, t.getMessage(), t);
} else {
var resultMap = executionResult.toSpecification();
var resultJSON = ObjectMapper.pojoToJSON(resultMap).replace("\n", "\\n");
logger.info("[{}] completed in {}ms", executionId, endMillis - startMillis);
logger.info("[{}] result: {}", executionId, resultJSON);
}
}
}
};
}
}
#Service
class InstrumentationService {
private final ContextFactory contextFactory;
InstrumentationService(ContextFactory contextFactory) {
this.contextFactory = contextFactory;
}
/**
* Return all instrumentations as a bean.
* The result will be used in class {#link com.oembedler.moon.graphql.boot.GraphQLWebAutoConfiguration}.
*/
#Bean
List<Instrumentation> instrumentations() {
// Note: Due to a bug in GraphQLWebAutoConfiguration, the returned list has to be modifiable (it will be sorted)
return new ArrayList<>(
List.of(new RequestLoggingInstrumentation()));
}
}
It helped me to have a look into the class GraphQLWebAutoConfiguration. There I found out that the framework expects a bean of type List<Instrumentation>, which contains all the instrumentations that will be added to the GraphQL execution.
There is a simpler way to add instrumentation with spring boot:
#Configuration
public class InstrumentationConfiguration {
#Bean
public Instrumentation someFieldCheckingInstrumentation() {
return new FieldValidationInstrumentation(env -> {
// ...
});
}
}
Spring boot will collect all beans which implement Instrumentation (see GraphQLWebAutoConfiguration).

Spring - Dynamic factory based on enum

I have the following classes:
public enum TaskType {
VERIFY_X_TASK, COMPUTE_Y_TASK, PROCESS_Z_TASK;
}
public interface Task{
void process();
}
#Component
public class VerifyXTask implements Task{
// Similar classes for the other types of tasks
public void process() {
}
}
#Component
public class TaskFactory{
private Map<TaskType, Task> tasks;
public Task getTask(TaskType type){
return tasks.get(type); // return a singleton with all it's fields injected by the application context
}
}
class UseTool{
#Autowired
private TaskFactory taskFactory;
public void run(String taskType){
Task task = taskFactory.getTask(TaskType.valueOf(taskType));
task.process();
}
}
What is the most elegant way of injecting the association between TaskType and Task into the factory?
Consider that there are almost 100 task types and that these may change quite frequently.
--
Further explanations:
I could do in the TaskFactory class smth like:
tasks.put(TaskType.VERIFY_X_TASK, new VerifyTask());
tasks.put(TaskType.COMPUTE_Y_TASK, new ComputeTask());
tasks.put(TaskType.PROCESS_Z_TASK, new ProcessTask());
But this does not inject any properties in the Task object.
I would suggest the following approach:
Define a custom annotation #ImplementsTask that takes a TaskType as a parameter, so that you can write your implementation class like this:
#Component
#ImplementsTask(TaskType.VERIFY_X_TASK)
public class VerifyXTask implements Task {
...
(Or you can meta-annotate #Component to avoid having to use it on all the classes.)
Inject all of the identified Task objects into your factory:
#Autowired
private Set<Task> scannedTasks;
In a #PostConstruct method on the factory, iterate over each of the elements in scannedTasks, reading the annotation value and adding a Map entry (to an EnumMap, of course). You'll need to decide how to deal with duplicate implementations for a given TaskType.
This will require a bit of reflection work in the factory setup, but it means that you can just annotate a Task implementation with the appropriate value and have it scanned in without any additional work by the implementor.
I got into similar kind of problem to solve, what I really did is, It may be helpful.
Define Tasks Enum like.
public enum Tasks {
Task1(SubTasks.values());
Tasks(PagesEnumI[] pages) {
this.pages = pages;
}
PagesEnumI[] pages;
// define setter and getter
}
Defined Subtask like
public interface PagesEnumI {
String getName();
String getUrl();
}
public enum SubTasks implements PagesEnumI {
Home("home_url");
SubTasks(String url) {
this.url = url;
}
private String url;
#Override
public String getUrl() {
return url;
}
#Override
public String getName() {
return this.name();
}
}
Defined Service to call per SubTasks enum like
public interface PageI {
void process();
Sites getTaskName();
PagesEnumI getSubTaskName();
}
#Component
public class Home implements PageI {
// function per SubTask to process
#Override
public void process() {}
// to get the information about Main Task
#Override
public Tasks getTaskName() {
return Tasks.Task1;
}
// to get the information about Sub Task
#Override
public PagesEnumI getSubTaskName() {
return Task1.Home;
}
}
Define a factory like...
#Component
public class PageFactory {
Set<PageI> pages;
// HashMap for keeping objects into
private static HashMap<String, PageI> pagesFactory = new HashMap<>();
#Autowired
public void setPages(Set<PageI> pages) {
this.pages = pages;
}
// construct key by
private static String constructKey(Tasks taks, PagesEnumI page) {
return task.name() + "__" + page.getName();
}
// PostConstruct means after construct class object this method should get run
// iterating over all pages and storing into Map
#PostConstruct
private void postConstruct() {
for (PageI pageI : pages) {
pagesFactory.put(constructKey(pageI.getTaskName(), pageI.getSubTaskName()), pageI);
}
}
// getting object from factory
public PageI getPageObject(Tasks task, PagesEnumI page) {
return pagesFactory.get(constructKey(task, page));
}
}
Till now we have registered our enum(Tasks and SunTasks) and their service(With getter of Tasks and SubTasks), Now defining a factory to call service process method.
#SpringBootApplication
public class Application implements CommandLineRunner {
PageFactory factory;
#Autowired
public void setFactory(PageFactory factory) {
this.factory = factory;
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Override
public void run(String... args) throws Exception {
// for each task we might have different sub task
Arrays.stream(Tasks.values()).forEach(
task -> {
// for each and subtask of a task need to perform process
for (PagesEnumI page : task.getPages()) {
PageI pageI = factory.getPageObject(task, page);
pageI.process();
}
}
);
}
}
This is not exact similar problem, way to solve it may be similar. So I thought this might be helpful to put it here. Please don't by putting name, just trying to understand concept. If anyone have more inputs, please share.
Let Task tell the factory which TaskType it supports.
It can be done using a plain old Java method, no Spring annotations required.
public interface Task {
void process();
TaskType supportedType();
}
#Component
public class VerifyXTask implements Task {
#Override
public void process() {
}
#Override
public TaskType supportedType() {
return TaskType.VERIFY_X_TASK;
}
}
#Component
public class TaskFactory {
private Map<TaskType, Task> tasks;
public TaskFactory(List<Task> tasks) {
this.tasks = tasks.stream()
.collect(Collectors.toMap(Task::supportedType, Function.identity()));
}
public Task getTask(TaskType type) {
return tasks.get(type);
}
}

Categories

Resources