Spring boot internalization does not work per request - java

I know this question might have an answer but am a newbie to spring boot. I need to do internalization to my rest endpoints and I followed this blog to implement internalization but the problem is one. The language does not change per request it only change only after application launch that is suppose at launch in my post man the language was fr that will work but after I change fr to pt (Portuguese) it does not pick pt it still remains with fr. Here is my code that am working with. I have created 4 different messages.properties that is messages_fr.properties, messages_pt.properties, messages_sw.properties under resources dir
#Configuration
public class MyCustomLocaleResolver extends AcceptHeaderLocaleResolver
implements WebMvcConfigurer {
List<Locale> LOCALES = Arrays.asList(
new Locale("en"),
new Locale("pt"),
new Locale("sw"),
new Locale("fr"));
#Override
public Locale resolveLocale(HttpServletRequest request) {
String headerLang = request.getHeader("Accept-Language");
System.out.println("header:"+headerLang);
return headerLang == null || headerLang.isEmpty()
? Locale.getDefault()
: Locale.lookup(Locale.LanguageRange.parse(headerLang), LOCALES);
}
#Bean
public MessageSource messageSource() {
final ResourceBundleMessageSource rs = new ResourceBundleMessageSource();
rs.setDefaultEncoding(StandardCharsets.ISO_8859_1.name());
rs.setBasename("messages");
rs.setUseCodeAsDefaultMessage(true);
return rs;
}
}
And Translator class
#Component
public class LanguageTranslator {
private static ResourceBundleMessageSource messageSource;
#Autowired
LanguageTranslator(ResourceBundleMessageSource messageSource) {
LanguageTranslator.messageSource = messageSource;
}
public static String translate(String msg) {
Locale locale = LocaleContextHolder.getLocale();
return messageSource.getMessage(msg, null, locale);
}
}
And here is my postman request
and here is messages_fr.properties
and project structure
And here is my controller code
#RestController
#RequestMapping("/home")
public class HomeContoller {
#Autowired
private SystemSettingService settingService;
#GetMapping("/")
public String home() {
return "Home page";
}
#RequestMapping(value = "/demo", method = RequestMethod.POST)
public ResponseEntity all_menu_assignment(HttpServletRequest req) {
return ResponseEntity
.ok().body(new ServerResponse(Common.SUCCESS_CODE,Common.SUCCESS_MESSAGE));
}
}
and here is my Common class
public class Common{
public static String SUCCESS_MESSAGE= LanguageTranslator.translate("SUCCESS_MESSAGE");
}

The LocaleContextHolder holds the locale of the current thread - so referencing this in a static variable will result in the default VM local to be used (because the constant will be initialized during startup).
Something like this should work:
#RestController
#RequestMapping("/home")
public class HomeContoller {
#Autowired
private SystemSettingService settingService;
#GetMapping("/")
public String home() {
return "Home page";
}
#RequestMapping(value = "/demo", method = RequestMethod.POST)
public ResponseEntity all_menu_assignment(HttpServletRequest req) {
return ResponseEntity
.ok().body(new ServerResponse(Common.SUCCESS_CODE, LanguageTranslator.translate("SUCCESS_MESSAGE")));
}
}
Also, it would be better make the translate method in LanguageTranslator non-static and to inject (autowire) the object in the controller.

Translator.toLocale(Common.SUCCESS_MESSAGE) is missing in your controller api method

Related

How to dynamically disable specific API in spring?

I have a flag DISABLE_FLAG and I want to use it to control multiple specific APIs in different controllers.
#RestController
public final class Controller1 {
#RequestMapping(value = "/foo1", method = RequestMethod.POST)
public String foo1()
}
#RestController
public final class Controller2 {
#RequestMapping(value = "/foo2", method = RequestMethod.POST)
public String foo2()
}
I can use an interceptor to handle all the urls. Is there a easy way to do that like annotation?
You could use AOP to do something like that.
Create your own annotation...
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface Maybe { }
and corresponding aspect...
#Aspect
public class MaybeAspect {
#Pointcut("#annotation(com.example.Maybe)")
public void callMeMaybe() {}
#Around("callMeMaybe()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
// do your logic here..
if(DISABLE_FOO) {
// do nothing ? throw exception?
// return null;
throw new IllegalStateException();
} else {
// process the request normally
return joinPoint.proceed();
}
}
}
I don't think there is direct way to disable a constructed request mapping but We can disable API in many ways with some condition.
Here is the 2 ways disabling by spring profile or JVM properties.
public class SampleController {
#Autowired
Environment env;
#RequestMapping(value = "/foo", method = RequestMethod.POST)
public String foo(HttpServletResponse response) {
// Using profile
if (env.acceptsProfiles("staging")) {
response.setStatus(404);
return "";
}
// Using JVM options
if("true".equals(System.getProperty("DISABLE_FOO"))) {
response.setStatus(404);
return "";
}
return "";
}
}
If you are thinking futuristic solution using cloud config is the best approach. https://spring.io/guides/gs/centralized-configuration/
Using Conditional components
This allows to build bean with conditions, if the condition failed on startup, the entire component will never be built. Group all your optional request mapping to new controller and add conditional annotation
#Conditional(ConditionalController.class)
public class SampleController {
#Autowired
Environment env;
#RequestMapping(value = "/foo", method = RequestMethod.POST)
public String foo(HttpServletResponse response) {
return "";
}
public static class ConditionalController implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return context.getEnvironment().acceptsProfiles("staging"); // Or whatever condition
}
}
}
You can solve this with annotations by utilizing spring profiles. You define two profiles one for enabled flag and another profile for the disabled flag. Your example would look like this:
#Profile("DISABLED_FLAG")
#RestController
public final class Controller1 {
#RequestMapping(value = "/foo1", method = RequestMethod.POST)
public String foo1()
}
#Profile("ENABLED_FLAG")
#RestController
public final class Controller2 {
#RequestMapping(value = "/foo2", method = RequestMethod.POST)
public String foo2()
}
Here is the link to the spring framework documentation for this feature: https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/Profile.html
I did it as follows :
#Retention(RUNTIME)
#Target(ElementType.METHOD)
public #interface DisableApiControl {
}
This class is my customization statement. After could use AOP :
for AbstractBaseServiceImpl :
public abstract class AbstractBaseServiceImpl {
private static boolean disableCheck = false;
public void setDisableChecker(boolean checkParameter) {
disableCheck = checkParameter;
}
public boolean getDisableChecker() {
return disableCheck;
}
}
NOTE : The above class has been prepared to provide a dynamic structure.
#Aspect
#Component
public class DisableApiControlAspect extends AbstractBaseServiceImpl {
#Autowired
private HttpServletResponse httpServletResponse;
#Pointcut(" #annotation(disableMe)")
protected void disabledMethods(DisableApiControl disableMe) {
// comment line
}
#Around("disabledMethods(disableMe)")
public Object dontRun(ProceedingJoinPoint joinPoint, DisableApiControl disableMe) throws Throwable {
if (getDisableChecker()) {
httpServletResponse.sendError(HttpStatus.NOT_FOUND.value(), "Not found");
return null;
} else {
return joinPoint.proceed();
}
}
}
checker parameter added global at this point. The rest will be easier when the value is given as true / false when needed.
#GetMapping("/map")
#DisableApiControl
public List<?> stateMachineFindMap() {
return new ArrayList<>;
}

Spring Boot - Mock a POST REST request to an external API

I have a Spring-Boot 1.5.21 application that serves as a REST gateway between an Angular UI and an external API that provides the data (long story - acts as auth between UI and datasource). A request comes to the Spring-Boot application, it calls the data source API with the request payload.
I am new to Unit Testing for Spring-Boot and am trying to write a test for the POST REST method in the Gateway application that creates a new record (create). I've read a couple of tutorials and other websites detailing how to unit test Spring-Boot APIs but nothing that helps me in my situation.
I want to:
Unit test the REST Controller method and check that the #RequestBody is valid
I do not want a record created in the datasource
Controller Method:
#PostMapping(value = "/" + Constants.API_CHANGE_REQUEST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public String submitChangeRequest(#RequestBody ChangeRequestWrapper changeRequestWrapper) {
logger.info("API Request: Posting Change Request: " + changeRequestWrapper.toString());
return restService.makeApiPost(sharedDataService.buildApiUrlPath(Constants.API_CHANGE_REQUEST), changeRequestWrapper);
}
AppConfig:
#PropertySource({"classpath:application.properties"})
#Configuration
public class AppConfig {
#Resource
private Environment env;
#Bean
public RestTemplate restTemplate() {
RestTemplateBuilder builder = new RestTemplateBuilder();
return builder
.setConnectTimeout(Constants.API_TIMEOUT_CONNECT)
.setReadTimeout(Constants.API_TIMEOUT_READ)
.basicAuthorization(env.getProperty("bpm.user"), env.getProperty("bpm.password"))
.build();
}
}
RestServiceImpl:
#Service
public class RestServiceImpl implements RestService {
private static final Logger logger = LoggerFactory.getLogger(RestServiceImpl.class);
#Autowired
private RestTemplate myRestTemplate;
#Value("${bpm.url}")
private String restUrl;
public String getApiUri() {
return restUrl;
}
public String makeApiCall(String payload) /*throws GradeAdminException */{
logger.info("Implementing API call.");
logger.debug("userApi: " + payload);
return myRestTemplate.getForObject(payload, String.class);
}
public String makeApiPost(String endpoint, Object object) {
logger.info("Implementing API post submission");
logger.debug("userApi endpoint: " + endpoint);
return myRestTemplate.postForObject(endpoint, object, String.class);
}
}
SharedDataServiceImpl:
#Service
public class SharedDataServiceImpl implements SharedDataService {
#Autowired
private RestService restService;
#Override
public String buildApiUrlPath(String request) {
return buildApiUrlPath(request, null);
}
#Override
public String buildApiUrlPath(String request, Object parameter) {
String path;
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(restService.getApiUri());
if (parameter != null) {
builder = builder.path(getApiPath(request) + "/{object}");
UriComponents buildPath = builder.buildAndExpand(parameter);
path = buildPath.toUriString();
} else {
builder = builder.path(getApiPath(request));
path = builder.build().toUriString();
}
return path;
}
}
What I've done for the GET methods:
#RunWith(SpringRunner.class)
#WebMvcTest(ClientDataRequestController.class)
#ContextConfiguration(classes = { TestConfig.class }, loader = AnnotationConfigWebContextLoader.class)
public class ClientDataRequestControllerTest {
#Autowired
private MockMvc mvc;
#Before
public void setUp() {
}
#Test
public void test_no_endpoint() throws Exception {
this.mvc.perform(get("/")).andExpect(status().isNotFound()).andReturn();
}
#Test
public void test_controller_no_endpoint() throws Exception {
this.mvc.perform(get("/api/")).andExpect(status().isOk()).andReturn();
}
#Test
public void test_getStudent_valid_parameters() throws Exception {
this.mvc.perform(get("/api/students/?pidm=272746")).andExpect(status().isOk()).andReturn();
}
}
I would greatly appreciate some assistance with this.
Solution:
I've since found this SO answer that has solved my problem.
You could mock the RestServiceImpl. Add a dependency in your test and annotate it with MockBean:
#MockBean
private RemoteService remoteService;
Now you can go ahead and mock the methods:
import org.mockito.BDDMockito;
BDDMockito.given(this.remoteService.makeApiPost()).willReturn("whatever is needed for your test");

Retrieve locale based on the Accept-Language in Spring Boot

I have a Spring Boot (2.1.3.RELEASE) application that uses Jersey to define the (RESTful) endpoints. I'm trying to read and propagate some messages based on the locale being sent by the user-agents.
I've configured these beans:
#Bean
public LocaleResolver localeResolver() {
final AcceptHeaderLocaleResolver resolver = new AcceptHeaderLocaleResolver();
resolver.setSupportedLocales(Arrays.asList(Locale.GERMANY, Locale.US));
resolver.setDefaultLocale(Locale.ENGLISH);
return resolver;
}
#Bean
public MessageSource messageSource() { // Not sure if this is needed
final ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setDefaultEncoding(StandardCharsets.UTF_8.name());
messageSource.setBasenames("classpath:/messages");
messageSource.setUseCodeAsDefaultMessage(true);
messageSource.setCacheSeconds(5);
return messageSource;
}
...and also the bundles (inside ../src/main/resources/) like: messages.properties (fallback), messages_en_US.properties, messages_de_DE.properties, etc.
Now, the challenge is that I'm not sure how to "read" the locale sent by the user-agents in order to read the messages from the bundles appropriately. I'm injecting a MessageSource ms, and programmatically reading messages like:
final Locale locale = ???
ms.getMessage("message.duplicate-token", null, locale);
Any clues?
I've tried LocaleContextHolder.getLocale() but it's always en_US. If I hardcode the corresponding locale for the getMessage call, I'm able to retrieve the correct message(s). So I know the setup/configuration works for the most part.
Clients are sending the locale using the Accept-Language header — and values like: de-DE, en-US, etc.
You need add an LocaleChangeInterceptor and configure the bean as follow:
Refer Spring Boot internationalization for more
#Bean
public LocaleChangeInterceptor localeChangeInterceptor() {
    LocaleChangeInterceptor lci = new LocaleChangeInterceptor();
    lci.setParamName("lang");
    return lci;
}
If you want to use "Accept-Language" header only, then you can extend AcceptHeaderLocaleResolver and can customize:
package com.deb.demo.config;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import javax.servlet.http.HttpServletRequest;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver;
public class CustomLocaleResolver extends AcceptHeaderLocaleResolver {
List<Locale> LOCALES = Arrays.asList(new Locale("en"),new Locale("es"),new Locale("fr"));
#Override
public Locale resolveLocale(HttpServletRequest request) {
if (StringUtils.isEmpty(request.getHeader("Accept-Language"))) {
return Locale.getDefault();
}
List<Locale.LanguageRange> list = Locale.LanguageRange.parse(request.getHeader("Accept-Language"));
Locale locale = Locale.lookup(list,LOCALES);
return locale;
}
}
I m using a bean
#Bean
public LocaleResolver localeResolver() {
AcceptHeaderLocaleResolver slr = new AcceptHeaderLocaleResolver();
slr.setDefaultLocale(Locale.UK);
return slr;
}
then another one
#Bean
public LanguageUtil languageUtil() {
return new LanguageUtil();
}
with
private Locale getLocale() {
return LocaleContextHolder.getLocale();
}
public String getLocalizedMessage(String messageKey) {
return messageSource.getMessage(messageKey, null, getLocale());
}
The header is saved into the LocaleContextHolder, and you can use it when you need it.
Create a custom AcceptHeaderLocaleResolver
public class AcceptHeaderResolver extends AcceptHeaderLocaleResolver {
List<Locale> LOCALES = Arrays.asList(new Locale("en"), new Locale("ar"));
#Override
public Locale resolveLocale(HttpServletRequest request) {
String headerLang = request.getHeader("Accept-Language");
return headerLang == null || headerLang.isEmpty()
? Locale.getDefault()
: Locale.lookup(Locale.LanguageRange.parse(headerLang), LOCALES);
}
}
And Don't forgot to use it in #Configuration file
#Bean
public LocaleResolver sessionLocaleResolver() {
AcceptHeaderResolver localeResolver = new AcceptHeaderResolver();
return localeResolver;
}
If you want to get this on a REST controller level, you can directly get the locale instance in the REST methods. Spring does this magic
#GetMapping("/status")
public ResponseEntitiy<String> getStatus(final Locale locale) {
}
The LocaleResolver bean that you create only gets used in Spring MVC and not in the Jersey container. It is the Spring's DispatcherServlet that uses the LocaleResolver.
So LocaleContextHolder.getLocale(); will return different local depending on if call in a Jersey controller or in a Spring MVC controller.
There is no need to extend AcceptHeaderLocaleResolver.
Create a bean definition such as:
#Bean
public LocalResolver localeResolver() {
AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
localeResolver.setSupportedLocales(Arrays.asList(new Locale("fa"), new Locale("en")));
localeResolver.setDefaultLocale(new Locale("fa"));
return localeResolver;
}
I also implemented same scenario and it's works for me. For this, need to override the resolveLocale method in AcceptHeaderLocaleResolver.
Create component LanguageResolver for the custom implementation. Use Locale.forLanguageTag(language) to create locale from accept-header value. This will create a local with language and country code.
#Component
public class LanguageResolver extends AcceptHeaderLocaleResolver {
#Override
public Locale resolveLocale(HttpServletRequest request) {
String language = request.getHeader("Accept-Language");
List<Locale> supportedLocales = getSupportedLocales();
Locale defaultLocale = getDefaultLocale();
Locale requestLocale = Locale.forLanguageTag(language);
if (StringUtils.isEmpty(language)) {
return defaultLocale;
} else if (supportedLocales.contains(requestLocale)) {
return requestLocale;
} else {
return defaultLocale;
}
}
}
In the configuration class create bean using custom LanguageResolver class.
#Configuration
public class Internationalization extends WebMvcConfigurerAdapter {
#Bean
public AcceptHeaderLocaleResolver localeResolver() {
final LanguageResolver resolver = new LanguageResolver();
resolver.setSupportedLocales(Arrays.asList(Locale.GERMANY, Locale.US,Locale.UK));
resolver.setDefaultLocale(Locale.US);
return resolver;
}
#Bean
public ResourceBundleMessageSource messageSource() {
final ResourceBundleMessageSource source = new ResourceBundleMessageSource();
source.setBasename("language/messages");
source.setDefaultEncoding("UTF-8");
return source;
}
}
Here LocaleContextHolder.getLocale() will invoke the override method in LanguageResolver class.
#Service
public class LocaleService {
#Autowired
ResourceBundleMessageSource messageSource;
public String getMessage(String code) {
return messageSource.getMessage(code, null, LocaleContextHolder.getLocale());
}
}
And property files are in path of resources -> language
messages_en_US.properties
messages_en_GB.properties
messages_de_DE.properties
Content of the file in the format of below
test.hello=Hello GERMANY
Tested method using below.
#RestController
public class TestController {
private LocaleService localeService;
#Autowired
public TestController(LocaleService localeService) {
this.localeService = localeService;
}
#GetMapping("/local")
public String getMessageForLocal() {
return localeService.getMessage("test.hello");
}
}
You have to add Accept-Language header in your api endpoint to get desired locale output. Then you have to add following configuration to parse and set the Accept-Language header value from incoming request.
#Configuration
public class I18NConfiguration {
#Value("${i18n.locale.default:en-US}")
private String defaultLocale;
#Value("#{'${i18n.locale.supported: }'.split(',\\s*')}")
private List<String> supportedLocales;
#Bean
public LocaleResolver localeResolver() {
AcceptHeaderLocaleResolver acceptHeaderLocaleResolver = new AcceptHeaderLocaleResolver();
acceptHeaderLocaleResolver.setDefaultLocale(Locale.forLanguageTag(defaultLocale));
if (supportedLocales != null && !supportedLocales.isEmpty()) {
List<Locale> localeList = supportedLocales.stream().map(Locale::forLanguageTag).collect(Collectors.toUnmodifiableList());
acceptHeaderLocaleResolver.setSupportedLocales(localeList);
}
return acceptHeaderLocaleResolver;
}
}

Java, Spring internationalization: how to use values from .properties in simple String?

I use the next code
#Value("${app.user.root}")
private String userRoot;
to get constant value from my application.properties file.
In my GetMapping method I need to redirect to the error page and to pass a String as parameter.
#GetMapping("/user/activate")
public String activate(String activation) {
Users u = usersService.activate(activation);
if (u != null) {
usersService.autoLogin(u);
return "redirect:/";
}
return "redirect:/error?message=Could not activate with this activation code, please contact support";
But I need to have different String values with different languages. So, I am using Spring i18n, but how can I get the value I need at runtime? I need something like this:
return "redirect:/error?message=${errorMessage}";
Thank you, hope you will help me.
First you have to create multiple properties file for multiple languages
messages_en.properties
messages_fr.properties
The configuration of i18n should be following
#Configuration
public class LanguageConfig extends WebMvcConfigurerAdapter {
#Bean
public ReloadableResourceBundleMessageSource messageSource(){
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasename("classpath:messages");
messageSource.setDefaultEncoding("UTF-8");
return messageSource;
}
#Bean
public LocaleResolver localeResolver() {
SmartLocaleResolver slr = new SmartLocaleResolver();
Locale locale = new Locale("en", "us");
slr.setDefaultLocale(locale); // Set default Locale as en_cos
return slr;
}
#Bean
public LocaleChangeInterceptor localeInterceptor() {
LocaleChangeInterceptor interceptor = new LocaleChangeInterceptor();
interceptor.setParamName("lang");
return interceptor;
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(localeInterceptor());
}
class SmartLocaleResolver extends CookieLocaleResolver {
#Override
public Locale resolveLocale(HttpServletRequest request) {
String acceptLanguage = request.getHeader("Accept-Language");
if (acceptLanguage == null || acceptLanguage.trim().isEmpty()) {
return super.determineDefaultLocale(request);
}
return request.getLocale();
}
}
}
Now update your controller code and autowire org.springframework.context.MessageSource and then use it to get localized message.
#Autowired
private MessageSource messageSource;
Then you can get the localized message using following code.
String errorMessage = messageSource.getMessage("project.errorMessage", new Object[]{"John Doe"}, LocaleContextHolder.getLocale());
You can also use Locale object from controller method parameter instead of LocaleContextHolder.getLocale(), but it works just fine.

How do I design a generic Response builder / RESTful Web Service using Spring MVC?

Trying to build a RESTful web service using Spring MVC.
The controller should return specific Java types, but the response body must be a generic envelope. How can this be done?
The following sections of code are what I have so far:
Controller method:
#Controller
#RequestMapping(value = "/mycontroller")
public class MyController {
public ServiceDetails getServiceDetails() {
return new ServiceDetails("MyService");
}
}
Response envelope:
public class Response<T> {
private String message;
private T responseBody;
}
ServiceDetails code:
public class ServiceDetails {
private String serviceName;
public ServiceDetails(String serviceName) {
this.serviceName = serviceName;
}
}
Intended final response to clients should appear as:
{
"message" : "Operation OK"
"responseBody" : {
"serviceName" : "MyService"
}
}
What you can do is having a MyRestController just wrapping the result in a Response like this:
#Controller
#RequestMapping(value = "/mycontroller")
public class MyRestController {
#Autowired
private MyController myController;
#RequestMapping(value = "/details")
public #ResponseBody Response<ServiceDetails> getServiceDetails() {
return new Response(myController.getServiceDetails(),"Operation OK");
}
}
This solution keep your original MyController independant from your REST code. It seems you need to include Jackson in your classpath so that Spring will auto-magically serialize to JSON (see this for details)
EDIT
It seems you need something more generic... so here is a suggestion.
#Controller
#RequestMapping(value = "/mycontroller")
public class MyGenericRestController {
#Autowired
private MyController myController;
//this will match all "/myController/*"
#RequestMapping(value = "/{operation}")
public #ResponseBody Response getGenericOperation(String #PathVariable operation) {
Method operationToInvoke = findMethodWithRequestMapping(operation);
Object responseBody = null;
try{
responseBody = operationToInvoke.invoke(myController);
}catch(Exception e){
e.printStackTrace();
return new Response(null,"operation failed");
}
return new Response(responseBody ,"Operation OK");
}
private Method findMethodWithRequestMapping(String operation){
//TODO
//This method will use reflection to find a method annotated
//#RequestMapping(value=<operation>)
//in myController
return ...
}
}
And keep your original "myController" almost as it was:
#Controller
public class MyController {
//this method is not expected to be called directly by spring MVC
#RequestMapping(value = "/details")
public ServiceDetails getServiceDetails() {
return new ServiceDetails("MyService");
}
}
Major issue with this : the #RequestMapping in MyController need probably to be replaced by some custom annotation (and adapt findMethodWithRequestMapping to perform introspection on this custom annotation).
By default, Spring MVC uses org.springframework.http.converter.json.MappingJacksonHttpMessageConverter to serialize/deserialize JSON through Jackson.
I'm not sure if it's a great idea, but one way of solving your problem is to extend this class, and override the writeInternal method:
public class CustomMappingJacksonHttpMessageConverter extends MappingJacksonHttpMessageConverter {
#Override
protected void writeInternal(Object object, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
super.writeInternal(new Response(object, "Operation OK"), outputMessage);
}
}
If you're using XML configuration, you could enable the custom converter like this:
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="path.to.CustomMappingJacksonHttpMessageConverter">
</mvc:message-converters>
</mvc:annotation-driven>
Try the below solution.
Create a separate class such ResponseEnvelop. It must implement ResponseBodyAdvice interface.
Annotate the above class with #ControllerAdvice
Autowire HttpServletRequest
Override methods according to your requirement. Take reference from below.
#Override
public boolean supports(
MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
if (httpServletRequest.getRequestURI().startsWith("/api")) {
return true;
}
return false;
}
#Override
public Object beforeBodyWrite(
Object body,
MethodParameter returnType,
MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> converterType,
ServerHttpRequest request,
ServerHttpResponse response) {
if (((ServletServerHttpResponse) response).getServletResponse().getStatus()
== HttpStatus.OK.value()
|| ((ServletServerHttpResponse) response).getServletResponse().getStatus()
== HttpStatus.CREATED.value()) {
return new EntityResponse(Constants.SUCCESS, body);
}
return body;
}

Categories

Resources