I am creating a project which will respond to collect multiple bean object, save it to the database and return the status of the transaction. There can be multiple objects that can be sent from the client. For each object, they are having separate database thus separate controller.
So I planned to create a framework that can accept multiple objects from multiple controllers and send only one centralized object. But I am not sure how to use a centralized object as a return type in the controller(currently I returned them as Object). Below is my code:
Controller:
#RestController
#RequestMapping("/stat/player")
public class PlayerController {
#Autowired
private StatService<PlayerValue> statPlayer;
#RequestMapping("/number/{number}")
public Object findByNumber(#PathVariable String number) { // Here returning Object seem odd
return statPlayer.findByNumber(number);
}
}
Service:
#Service
#Transactional(isolation = Isolation.READ_COMMITTED)
public class PlayerServiceImpl implements StatService<PlayerValue> {
#Autowired
private PlayerRepository repository;
#Override
public PlayerValue findByNumber(String number) {
Optional<PlayerEntity> numberValue = repository.findByNumber(number);
return numberValue.map(PlayerEntity::toValue).orElse(null);
}
}
In service I returned the PlayerValue object but I want to wrap this object into a centralized bean ResponseValue. I created an aspect for that
#Aspect
#Component
public class Converter {
private static final Logger LOG = LoggerFactory.getLogger(Converter.class);
#Pointcut("within(#org.springframework.web.bind.annotation.RestController *)")
public void restControllerClassMethod() {}
private <T> ResponseValue<T> convert(List<T> results) {
String message = results.isEmpty() ? "No result found" : ResponseValueStatus.OK.toString();
return new ResponseValue<>(ResponseValueStatus.OK, message, results);
}
#Around("restControllerClassMethod()")
#SuppressWarnings("unchecked")
public <T> ResponseValue<T> convert(ProceedingJoinPoint joinPoint) {
ResponseValue value;
try {
Object findObject = joinPoint.proceed();
List<Object> objects = toList(findObject);
value = convert(objects);
} catch (NullPointerException e) {
throw new StatException(String.format("Exception thrown from %s from %s method with parameter %s", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName(), joinPoint.getArgs()[0].toString()));
//this exception will go in a controller advice and create a response value with this message
} catch (Throwable e) {
LOG.error("Exception occurred while converting the object", e);
throw new StatException(String.format("Exception thrown from %s from %s method with parameter %s with exception message %s", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName(), joinPoint.getArgs()[0].toString(), e.getMessage()));
}
return value;
}
private List<Object> toList(Object findObject) {
List<Object> objects = new ArrayList<>();
if (findObject instanceof List) {
((List) findObject).forEach(item -> objects.add(findObject));
} else {
objects.add(findObject);
}
return objects;
}
}
To sum up, There could be multiple entity similar to PlayerValue. I need a way to return the result in a centralized bean. Above process work, BUT for this I have to give return type as Object in Controller. Does anybody has an idea how can I use return type as List or T in controller. Also I know it can be done by implementing a ValueConverter interface, but this conversion is straightforward. So it would be nice if any other developer don't have to implement the ValueConverter everytime he want to add a different controller.
Also feel free to review the implementation and let me know if anyone has some alternative idea or some comments.
Note: I reduce a lot of code in the question so that it can be easier to understandable without understanding the actual requirement context. Please do let me know if anyone need more info.
After some research I came across to a better design solution for the framework (but of course with flaws) to achieve conversion to a centralized bean for multiple domain objects is to use a marker interface.
Marker interface can provide a centralized type for all the bean. The main rule need to be followed by the client is to implement that marker interface. So the basic solution is
Marker interface:
public interface StateResponseValue<T> {
}
Implement the interface in all the bean.
public class PlayerValue implements StateResponseValue<PlayerValue> {
}
public class ResponseValue<T> implements StateResponseValue<T> {
//fields and their getter and setter
}
Change the return type in service and controller.
public interface StatService<T> {
StateResponseValue<T> findByNumber(String number);
}
Change the return type in controller
#RestController
#RequestMapping("/stat/player")
public class PlayerController {
#Autowired
private StatService<PlayerValue> statPlayer;
#RequestMapping("/number/{number}")
public StateResponseValue<T> findByNumber(#PathVariable String number) { // Here returning Object seem odd
return statPlayer.findByNumber(number);
}
}
Note: The main drawback I feel is that whenever we want to access the field client need to explicitly cast the object to ResponseValue which is still pretty ugly.
What if you create an AbstractStatController which is generic ?
Generic interface StatService:
public interface StatService<T> {
T findByNumber(String number);
}
Generic abstract class AbstractStatController:
public abstract class AbstractStatController<T> {
abstract StatService<T> getStatService();
#RequestMapping("/number/{number}")
public T findByNumber(#PathVariable String number) {
return getStatService().findByNumber(number);
}
}
Concrete class PlayerController:
#RestController
#RequestMapping("/stat/player")
public class PlayerController extends AbstractStatController<Player> {
private final PlayerService playerService;
public PlayerController(PlayerService playerService) {
this.playerService = playerService;
}
#Override
StatService<Player> getStatService() {
return playerService;
}
}
Related
I want implement strategy design pattern in spring boot application. I create BeanPostProcessor for construct strategy resolver:
#Component
public class HandlerInAnnotationBeanPostProcessor implements BeanPostProcessor {
private final UnpHandlersResolver unpHandlersResolver;
public HandlerInAnnotationBeanPostProcessor(UnpHandlersResolver unpHandlersResolver) {
this.unpHandlersResolver = unpHandlersResolver;
}
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
Annotation[] annotations = bean.getClass().getAnnotations();
for (Annotation annotation : annotations) {
if (annotation instanceof HandlerIn) {
if (bean.getClass() != UnpHandler.class)
throw new RuntimeException("Not UnpHandler bean annotated by HandlerIn");
SmevMessageType[] type = ((HandlerIn) annotation).type();
for (SmevMessageType smevMessageType : type) {
unpHandlersResolver.setHandler(smevMessageType, (UnpHandler) bean);
}
}
}
return bean;
}
}
And I create resolver:
#Slf4j
#Component
public class UnpHandlersResolverImpl implements UnpHandlersResolver {
private Map<SmevMessageType, UnpHandler> map = new HashMap<>();
#Override
public void setHandler(SmevMessageType messageType, UnpHandler unpHandler) {
map.put(messageType, unpHandler);
}
#Override
public UnpHandler getUnpHandler(SmevMessageType type) {
UnpHandler sendRequestHandler = map.get(type);
if (sendRequestHandler == null)
throw new IllegalArgumentException("Invalid SendRequestHandler type: " + type);
return sendRequestHandler;
}
}
My BeanPostProcessor scan all beans with annotation HandlerIn and add to resolver's mup. I think it's wrong to do that:
unpHandlersResolver.setHandler(smevMessageType, (UnpHandler) bean);
But I not understand how can I add find beans to resolver. Before this implementation I faind beans in #Postconstruct method of resolver like:
context.getBeansWithAnnotation(HandlerIn.class);
But in this solution I have context in resolver and I think is bad.
Tell me how to properly implement what I want? In short, I want to have a set of classes that implement different behaviors. And the class that controls them. Give the class a parameter so that he chooses the right strategy and gives it to me. Like this:
Handler handler = handlersResolver.getHandler(messageType);
Result result = handler.somthing(param);
I'm going to try to make a simple example.
Interface Greeting {
void sayHello();
String getSupportedLanguage();
}
Then you have X number of implementations and you can loop through them in your "resolver"'s constructor to build the map. (I've seen this called a Proxy or a Decorator in code though, i.e. GreetingProxy or GreetingDecorator)
#Service
public GreetingResolver {
private Map<String, Greeting> languageToGreetingMap = new HashMap<>();
#Autowired
public GreetingResolver(List<Greeting> greetings) {
for (Greeting greeting : greetings) {
languageToGreetingMap.put(greeting.getSupportedLanguage(), greeting);
}
}
public void sayGreetingForLanguage(String language) {
languageToGreetingMap.get(language).sayHello();
}
}
This is a basic example of how one can do the strategy pattern in Spring. Every interface implementation of "Greeting" only knows about itself and what it can support. We then autowire all implementations in a list and loop through to create the map once and then during runtime only the relevant entry from the map in retrieved and used.
Note: this was typed "free hand" directly in the web page so please forgive any typos in the code.
My Spring Boot application contains several #KafkaListeners, and each listener performs the same steps before and after actually processing the payload: Validate the payload, check whether the event has been processed already, check whether it's a tombstone (null) message, decide whether processing should be retried in case of failure, emit metrics, etc.
These steps are currently implemented in a base class, but because the topics passed to #KafkaListener must be constant at runtime, the method annotated with #KafkaListener is defined in the subclass, and does nothing but pass its parameters to a method in the base class.
This works just fine, but I wonder if there's a more elegant solution. I assume my base class would have to create a listener container programmatically, but after a quick look at KafkaListenerAnnotationBeanPostProcessor, it seems to be quite involved.
Does anyone have any recommendadtions?
Having stumbled upon this question while looking to implement something similar, I first started with Artem Bilan's answer. However this did not work because annotations by default are not inherited in child classes unless they are themselves annotated with #Inherited. Despite this there may yet be a way to make an annotation approach work and I will update this answer if and when I get it to work. Thankfully though I have achieved the desired behavour using programtic registration of the Kafka listeners.
My code is something like the following:
Interface:
public interface GenericKafkaListener {
String METHOD = "handleMessage";
void handleMessage(ConsumerRecord<String, String> record);
}
Abstract Class:
public abstract class AbstractGenericKafkaListener implements GenericKafkaListener {
private final String kafkaTopic;
public AbstractGenericKafkaListener(final String kafkaTopic) {
this.kafakTopic = kafkaTopic;
}
#Override
public void handleMessage(final ConsumerRecord<String, String> record) {
//do common logic here
specificLogic(record);
}
protected abstract specificLogic(ConsumerRecord<String, String> record);
public String getKafkaTopic() {
return kafkaTopic;
}
}
We can then programtically register all beans of type AbstractGenericKafkaListener in a KafkaListenerConfigurer:
#Configuration
public class KafkaListenerConfigurataion implements KafkaListenerConfigurer {
#Autowired
private final List<AbstractGenericKafkaListener> listeners;
#Autowired
private final BeanFactory beanFactory;
#Autowired
private final MessageHandlerMethodFactory messageHandlerMethodFactory;
#Autowired
private final KafkaListenerContainerFactory kafkaListenerContainerFactory;
#Value("${your.kafka.consumer.group-id}")
private String consumerGroup;
#Value("${your.application.name}")
private String service;
#Override
public void configureKafkaListeners(
final KafkaListenerEndpointRegistrar registrar) {
final Method listenerMethod = lookUpMethod();
listeners.forEach(listener -> {
registerListenerEndpoint(listener, listenerMethod, registrar);
});
}
private void registerListenerEndpoint(final AbstractGenericKafkaListener listener,
final Method listenerMethod,
final KafkaListenerEndpointRegistrar registrar) {
log.info("Registering {} endpoint on topic {}", listener.getClass(),
listener.getKafkaTopic());
final MethodKafkaListenerEndpoint<String, String> endpoint =
createListenerEndpoint(listener, listenerMethod);
registrar.registerEndpoint(endpoint);
}
private MethodKafkaListenerEndpoint<String, String> createListenerEndpoint(
final AbstractGenericKafkaListener listener, final Method listenerMethod) {
final MethodKafkaListenerEndpoint<String, String> endpoint = new MethodKafkaListenerEndpoint<>();
endpoint.setBeanFactory(beanFactory);
endpoint.setBean(listener);
endpoint.setMethod(listenerMethod);
endpoint.setId(service + "-" + listener.getKafkaTopic());
endpoint.setGroup(consumerGroup);
endpoint.setTopics(listener.getKafkaTopic());
endpoint.setMessageHandlerMethodFactory(messageHandlerMethodFactory);
return endpoint;
}
private Method lookUpMethod() {
return Arrays.stream(GenericKafkaListener.class.getMethods())
.filter(m -> m.getName().equals(GenericKafkaListener.METHOD))
.findAny()
.orElseThrow(() ->
new IllegalStateException("Could not find method " + GenericKafkaListener.METHOD));
}
}
How about this:
public abstract class BaseKafkaProcessingLogic {
#KafkaHandler
public void handle(Object payload) {
}
}
#KafkaListener(topics = "topic1")
public class Topic1Handler extends BaseKafkaProcessingLogic {
}
#KafkaListener(topics = "topic2")
public class Topic2Handler extends BaseKafkaProcessingLogic {
}
?
I needed the same functionality and came up with solution close to Artem Bilan answer. Yes, #KafkaHandler annotation is not inherited by the child classes but defined in interface it is. Here is the solution:
interface AbstractKafkaListener<T> {
default Class<T> getCommandType() {
TypeToken<T> type = new TypeToken<>(getClass()) {};
return (Class<T>) type.getRawType();
}
#KafkaHandler
default void handle(String message) throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
T value = objectMapper.readValue(message, getCommandType());
handle(value);
}
void handle(T message);
}
The class should implement the handle method only:
#Component
#KafkaListener(topics = "my_topic")
public class KafkaListenerForMyCustomMessage implements AbstractKafkaListener<MyCustomMessage> {
#Override
public void handle(MyCustomMessage message) {
System.out.println(message);
}
}
The 2 implemented methods in the interface should be private/protected but because they are in interface this cannot be done. default methods are always public. Actually, all methods defined in interface are always public.
I use this solution to dynamically parse the message from kafka (received in String) to the custom class.
getCommandType method returns the class of the T generic param. TypeToken is from Google Guava package.
I am currently working on a Spring Boot application that allows users to save categories into a database. I can get my code to "work", however, I think it limits the amount of testing I can do, hence my question.
The controller receives a list of categories. The controller iterates over these categories, validates them and depending on whether they are valid, they are saved to a database. The controller finally returns a list of messages, such that the recipient can identify which category has been accepted or rejected, etc.
I have a list of model messages (List), which upon each iteration, the controller instantiates a new model message (new ModelMessage()) and eventually adds it to the List. Is there a way to inject a new ModelMessage upon each iteration or do I need to use the new keyword? If I do use the new keyword, I feel like this is limiting my testability/tightly coupling my controller to the model message.
The controller:
#PostMapping("/category")
public String saveCategoryModelToDatabase(#RequestBody CategoryModelWrapper categoryModelWrapper){
List<CategoryModel> categoryModelList = categoryModelWrapper.getCategoryModelList();
modelMessageList.clear();
for(CategoryModel categoryModel : categoryModelList){
//Resetting model
modelMessage = new ModelMessage(); //This tightly couples my method to the ModelMessage class, which is bad for testing?
//#Autowired modelMessage; <-- something like this? Inject a new ModelMessage with each iteration.
modelMessage.setName(categoryModel.getName());
//Resetting categoryModelErrors
Errors categoryModelErrors = new BeanPropertyBindingResult(categoryModel, "categoryModel");
categoryModelValidator.validate(categoryModel, categoryModelErrors);
if(categoryModelErrors.hasErrors()){
modelMessage.setStatus(ModelMessageStatusEnum.REJECTED);
modelMessage.setReason(MODEL_MESSAGE_0004);
}
if(categoryModelService.save(categoryModel)){
modelMessage.setStatus(ModelMessageStatusEnum.ACCEPTED);
}
else{
modelMessage.setStatus(ModelMessageStatusEnum.REJECTED);
modelMessage.setReason(MODEL_MESSAGE_0005);
}
modelMessageList.add(modelMessage);
}
return gson.toJson(modelMessageList);
}
An example of the response to the recipient:
[{"name":"Arts","status":"ACCEPTED"},{"name":"Business","status":"ACCEPTED"},{"name":"Gaming","status":"ACCEPTED"},{"name":"Deals","status":"REJECTED","reason":"Category rejected because of an unexpected exception, i.e. possibly due to duplicate keys."}]
Thanks for any help :)
You could use the ApplicationContext, assuming you have access to it, as a factory for ModelMessage. But, is that really necessary?
I think you can create new ModelMessages in your Controller, it's only a data object and not a service bean.
A junit can check the result of the method.
But if you really want to use Spring, I would look at the FactoryBean...
Example:
public class ModelMessage {
String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
#Component
public class ModelMessageFactory implements FactoryBean<ModelMessage> {
#Override
public ModelMessage getObject() throws Exception {
return new ModelMessage();
}
#Override
public Class<?> getObjectType() {
return ModelMessage.class;
}
}
#RunWith(SpringRunner.class)
#SpringBootTest
#SpringJUnitConfig
public class ModelMessageFactoryTest {
#Autowired
private ModelMessageFactory messageFactory;
#Test
public void testGetObject() throws Exception {
assertNotNull("Factory is null", messageFactory);
IModelMessage modelMessage1 = messageFactory.getObject();
IModelMessage modelMessage2 = messageFactory.getObject();
assertNotEquals("error object is equal", System.identityHashCode(modelMessage1),
System.identityHashCode(modelMessage2));
}
#Test
public void testGetObjectType() throws Exception {
assertEquals(ModelMessage.class, messageFactory.getObjectType());
}
}
I am using Guice for Dependency injection. I want to use #CheckedProvides as object construction may throw exception and I do not want it to be wrapped up in ProvisionException. I found this useful but I am not able to get the complete picture. I have created an interface which is extending CheckedProvider interface. Now I want to know what the implementing class of this interface will contain?
My interface looks like
public interface TokenEncrypterProvider <T> extends CheckedProvider<T> {
T get() throws ExceptionInInitializerError;
}
i want it to be used as
#Provides
public TokenManager getTokenManager(SomeClass obj) {
return new TokenManager(obj);
}
#CheckedProvides(TokenEncrypterProvider.class)
public SomeClass getEncrypter() {
try {
return new SomeClassFactory()
.buildEncryptionClient(AppConfig.findString("xyz"));
} catch (Exception e) {
throw new ExceptionInInitializerError(e);
}
}
public class TokenManager {
private final SomeClass obj;
#Inject
public TokenManager(SomeClass obj) {
this.obj = obj;
}
...
}
now for binding what should I write
bind(TokenEncrypterProvider.class).toProvider(WHICHCLASS?.class);
or if it is not the way to use CheckedProvides, please suggest a correct one.
I am assuming you are using Guice 3+ or 4+. You don't have to bind your provider if you are using CheckedProvides annotations.
You have to add install(ThrowingProviderBinder.forModule(this)) in the configure() method of your module to be able to use #CheckedProvides methods.
Documentation
I have the following scenario:
public interface ServiceClientAdapter {
SomeData getSomeData()
}
#LegacyServiceClientAdapter
public class MyLegacyServiceClientAdapterImpl implements ServiceClientAdapter {
public SomeData getSomeData() {
// implementation
}
}
#NewServiceClientAdapter
public class MyNewServiceClientAdapterImpl implements ServiceClientAdapter {
public SomeData getSomeData() {
// implementation
}
}
public class BusinessLogic {
#Inject
private ServiceClientAdapter serviceClientAdapter;
}
LegacyServiceClientAdapter and NewServiceClientAdapter are custom annotations.
The implementation for the serviceClientAdapter field will be determined at runtime by whether the user has been migrated from the legacy to the new service or not.
What is the best way to accomplish this dependency injection using Google Guice?
Take into account that different BusinessLogic classes will exist, each with their own (different) ServiceClientAdapter-like interface and corresponding legacy and new implementation classes.
Ideally this should be done with a piece of framework code that can be used across all use cases.
I'm going to assume that the result of your LDAP call can be represented as a string, let's say "legacy" or "new". If not, hopefully you should still be able to adapt this example.
In your module, use a MapBinder:
public class BusinessLogicModule {
#Override
protected void configure() {
// create empty map binder
MapBinder<String, ServiceClientAdapter> mapBinder =
MapBinder.newMapBinder(
binder(), String.class, ServiceClientAdapter.class);
// bind different impls, keyed by descriptive strings
mapBinder.addBinding("legacy")
.to(MyLegacyServiceClientAdapterImpl.class);
mapBinder.addBinding("new")
.to(MyNewServiceClientAdapterImpl.class);
}
}
Now you can inject a map of instances (or a map of providers of instances if you need to keep creating new instances) into your main class and use the string discovered at runtime to control which kind of instance you get.
public class BusinessLogic {
#Inject
private ServiceClientAdapter serviceClientAdapter;
#Inject
private Map<String, ServiceClientAdapter> mapBinder;
public void setupAndUseClientAdapter() {
String userType = getUserTypeFromLdapServer();
serviceClientAdapter = mapBinder.get(userType);
if (serviceClientAdapter == null) {
throw new IllegalArgumentException(
"No service client adapter available for " +
userType + " user type.";
}
doStuffWithServiceClientAdapter();
}
}