We are using JAXB Annotations on our model objects that we return from our Web API and we want the data to be localized and other values formatted based on user preferences (i.e. metric vs. statute). We do this by adding custom adapters to the Marshaller.
Marshaller marshaller = jc.createMarshaller();
marshaller.setAdapter(NumberPersonalizedXmlAdapter.class,
new NumberPersonalizedXmlAdapter(locale);
marshaller.marshal(expected, writer);
Attempting the simplest approach I will just get the Locale from the HTTP Request Headers and provide it to the Marshaller in one of the MessageBodyWriter classes.
I looked at extending the default registered providers like XMLRootElementProvider, but realized they were written as mostly final so I abandoned that approach. There would have been at least 10 classes I needed to extend anyway so that wasn't ideal.
Does anyone know how best to get the marshaller in the MessageBodyWriter set up with customer Adapters for each request? I'm pretty sure it has something to do with ContextResolver.
Writing a ContextResolver for the Marshalling results in a much cleaner and more appropriate solution than writing a MessageBodyWriter. All of the JAXB classes use the Providers.getContextResolver method to obtain a marshaller. I provide my custom ContextResolver and I have i18n responses.
#Provider
#Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public class JaxbPersonalizerContextResolver implements ContextResolver<Marshaller> {
private HttpHeaders requestHeaders;
public JaxbPersonalizerContextResolver(#Context HttpHeaders requestHeaders) {
this.requestHeaders = requestHeaders;
}
#Override
public Marshaller getContext(Class<?> type) {
Locale locale = If.first(this.requestHeaders.
getAcceptableLanguages(), Locale.US);
NumberFormat formatter = NumberFormat.getNumberInstance(locale);
formatter.setMaximumFractionDigits(1);
Marshaller marshaller;
try {
JAXBContext jc = JAXBContext.newInstance(type);
marshaller = jc.createMarshaller();
} catch (JAXBException e) {
throw new RuntimeException(e);
}
marshaller.setAdapter(QuantityXmlAdapter.class,
new QuantityXmlAdapter.Builder().locale(locale).build());
marshaller.setAdapter(NumberPersonalizedXmlAdapter.class,
new NumberPersonalizedXmlAdapter.Builder().
formatter(formatter).build());
return marshaller;
}
}
The JSON wasn't being localized and after some investigation I realized the Jackson JSON libraries were being used instead of JAXBJSONElementProvider distributed with the Jersey libraries. I removed the POJOMappingFeature configuration in web.xml and I have localized JSON, however, it isn't as nice as the Jackson JSON.
A very clean solution which makes me think that the JAX-RS and Jersey implementations are done very well.
I solved the problem writing my own MessageBodyWriter which receives the HttpHeaders injected into the constructor which I use later when writing the response. I will include the entire class since it's not that large.
#Produces(MediaType.APPLICATION_XML)
#Provider
public class JaxbPersonalizationProvider implements MessageBodyWriter<Object> {
private HttpHeaders requestHeaders;
private Providers providers;
public JaxbPersonalizationProvider(#Context HttpHeaders requestHeaders, #Context Providers providers) {
this.requestHeaders = requestHeaders;
this.providers = providers;
}
#Override
public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return type.getAnnotation(XmlRootElement.class) != null && mediaType.equals(MediaType.APPLICATION_XML_TYPE);
}
#Override
public long getSize(Object t, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return -1;
}
#Override
public void writeTo(Object t, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream) throws IOException,
WebApplicationException {
Locale locale = If.first(this.requestHeaders.getAcceptableLanguages(), Locale.US);
NumberFormat formatter = NumberFormat.getNumberInstance(locale);
formatter.setMaximumFractionDigits(1);
Marshaller marshaller;
try {
JAXBContext jc = JAXBContext.newInstance(TrackInfo.class);
marshaller = jc.createMarshaller();
marshaller.setAdapter(QuantityXmlAdapter.class, new QuantityXmlAdapter.Builder().locale(locale).build());
marshaller.setAdapter(NumberPersonalizedXmlAdapter.class, new NumberPersonalizedXmlAdapter.Builder()
.formatter(formatter).build());
marshaller.marshal(t, entityStream);
} catch (JAXBException e) {
throw new RuntimeException(e);
}
}
}
Which produces this xml excerpt with the default locale of en-US:
<display lang="en_US">
<value>3,286.1</value>
</display>
and this xml excerpt when the locale of fr-FR is sent in the headers:
<display lang="fr_FR">
<value>3 286,1</value>
</display>
This approach is still not ideal as I now will need to write a similar MessageBodyWriter for JSON or add JSON support to this MessageBodyWriter. Additionally I assume the default JAXB Providers are doing some tweaking that I'm not taking advantage of.
A MessageBodyWriter is the right approach for this use case. I would recommend adding the following field to your MessageBodyWriter:
#javax.ws.rs.core.Context
protected Providers providers;
And then using it to access the JAXBContext to create the Marshaller
public void writeTo(DataObject dataObject, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> multivaluedMap, OutputStream outputStream) throws IOException, WebApplicationException {
JAXBContext jaxbContext = null;
ContextResolver<JAXBContext> resolver = providers.getContextResolver(JAXBContext.class, arg3);
if(null != resolver) {
jaxbContext = resolver.getContext(type);
}
if(null == jaxbContext) {
jaxbContext = JAXBContext.newInstance(type);
}
Marshaller marshaller = jaxbContext.createMarshaller();
}
Related Example
Validate JAXBElement in JPA/JAX-RS Web Service
Related
My project requirement is to consume a SOAP service and I am trying to use Quarkus for this purpose. What are the quarkus dependecies hwould I use to acheive this? Is there any sample application I can refer to?
In Spring we can use org.springframework.ws.client.core.support.WebServiceGatewaySupport is there anything similiar in Quarkus.
There is no SOAP client extension at the moment in Quarkus.
There is some discussion to include a CXF extension here : https://github.com/quarkusio/quarkus/issues/4005, you can join the discussion.
A PR is open (not yet finished) for SOAP WS support via CXF but not for SOAP client: https://github.com/quarkusio/quarkus/pull/5538
If you didn't plan to deploy to GraalVM (Quarkus can be deployed both in standard JVM mode and on GraalVM/SubstrateVM as a native application) you can still use any Java library with Quarkus but you will not have any integration with Quarkus itself. So using the CXF Client should works fine in JVM mode : https://cxf.apache.org/docs/how-do-i-develop-a-client.html
we have a new version on https://github.com/quarkiverse/quarkiverse-cxf that you can used for native. It is in beta and can be reference with maven central.
It can be done like #loicmathieu said.
In our realization we have Controller :
#Slf4j
#Path("/xxx")
public class EKWReactiveResource {
#Inject
RequestObject2WsdlRequestObjectConverter converter;
#POST
#Path("/xxxx")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_XML)
public Uni<Response<XsdResponseObject>> wyszukajKsiege(RequestObject request) {
return Uni.createFrom().item(request)
.onItem()
.invoke( req -> log.info(req.toString()))
.map(converter::convert)
.onItem()
.apply(ServiceClient::send);
}
}
ServiceClient :
#Slf4j
public final class ServiceClient {
private final static String ENDPOINT_HTTP = "XXXX";
private final static QName SERVICE_QNAME = new QName("XXXX", "XXXX");
private final static QName SERVICE_QNAME2 = new QName("XXXX", "XXXX");
private static XXXPortType portType;
static {
try {
URL endpointUrl = new URL(ENDPOINT_HTTP);
XXXService service = new XXXService(endpointUrl ,SERVICE_QNAME);
portType = service.getPort(SERVICE_QNAME2, XXXPortType.class);
} catch (MalformedURLException e) {
log.error(e.getMessage(), e);
}
}
public static Response<XsdResponseObject> send(RequestObject requestType) {
return portType.EndpointAsync(requestType);
}
}
And after this we must define ResponseMessageBodyWriter for AsyncResponseImpl> because for some reason it is unknown.
MessageBodyWriter example - you should better write isWriteable method i just dont do this perfectly because this is example only :
#Slf4j
#Provider
#Produces(MediaType.APPLICATION_XML)
public class XXXMessageBodyWriter implements MessageBodyWriter<AsyncResponseImpl<XsdResponseObject>> {
#Override
public boolean isWriteable(Class<?> aClass, Type type, Annotation[] annotations, MediaType mediaType) {
return AsyncResponseImpl.class.isAssignableFrom(aClass);
}
#Override
public void writeTo(AsyncResponseImpl<XsdResponseObject> asyncResponse, Class<?> aClass, Type type, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> multivaluedMap, OutputStream outputStream) throws IOException, WebApplicationException {
try {
XsdResponseObject responseObject = asyncResponse.get();
String marshalled = JAXBUtils.marshallToSOAP(responseObject);
log.info(String.format("Response : %s",marshalled));
outputStream.write(marshalled.getBytes());
} catch (InterruptedException | ExecutionException | JAXBException | ParserConfigurationException | SOAPException e) {
log.error(e.getMessage(),e);
}
}
}
I have tried the solutions to above that I found on stackoverflow. But they didn't do what I wanted the controller to do. I don't want to change getter/setter method in every class to convert the null values to empty values. I am looking for a solution which will allow me to do this at global level using object mapper, by configuration perhaps.
Add this bean in your configuration class:
#Bean
public Jackson2ObjectMapperBuilder configureObjectMapper() {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
ObjectMapper objectMapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(new NullSerializer());
objectMapper.registerModule(module);
builder.configure(objectMapper);
return builder;
}
NullSerializer class:
public class NullSerializer extends JsonSerializer<Object> {
#Override
public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
// any JSON value you want...
gen.writeString("");
}
}
Thanks and this solution does not work...I have mentioned that I have tried the stack overflow solutions and they dont work. its not printing anything anymore even the values that are not null.
I'm attempting to use the Jackson serialization feature of spring-data-redis. I am building a ObjectMapper and using the GenericJackson2JsonRedisSerializer as the serializer for the redisTemplate:
#Configuration
public class SampleModule {
#Bean
public ObjectMapper objectMapper() {
return Jackson2ObjectMapperBuilder.json()
.serializationInclusion(JsonInclude.Include.NON_NULL) // Don’t include null values
.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) //ISODate
.build();
}
#Bean
public RedisTemplate getRedisTemplate(ObjectMapper objectMapper, RedisConnectionFactory redisConnectionFactory){
RedisTemplate redisTemplate = new RedisTemplate();
redisTemplate.setDefaultSerializer(new GenericJackson2JsonRedisSerializer(objectMapper));
redisTemplate.setConnectionFactory(redisConnectionFactory);
return redisTemplate;
}
}
I have a SampleBean I am attempting to save:
#RedisHash("sampleBean")
public class SampleBean {
#Id
String id;
String value;
Date date;
public SampleBean(String value, Date date) {
this.value = value;
this.date = date;
}
}
And a repository for that bean:
public interface SampleBeanRepository extends CrudRepository {
}
I am then trying to write the bean to Redis:
ConfigurableApplicationContext context = SpringApplication.run(SampleRedisApplication.class, args);
SampleBean helloSampleBean = new SampleBean("hello", new Date());
ObjectMapper objectMapper = context.getBean(ObjectMapper.class);
logger.info("Expecting date to be written as: " + objectMapper.writeValueAsString(helloSampleBean.date));
SampleBeanRepository repository = context.getBean(SampleBeanRepository.class);
repository.save(helloSampleBean);
context.close();
I expect the redisTemplate to use the Serializer to write the Date inside of the SampleBean as a Timestamp, however it is written as a long.
The relevant spring-data-redis reference: http://docs.spring.io/spring-data/data-redis/docs/current/reference/html/#redis:serializer
Full code sample: https://github.com/bandyguy/spring-redis-jackson-sample-broken
The serializer/mapper used by the template does not affect the one used by the repository since the repository directly operates upon the byte[] using Converter implementations for reading/writing data based on domain type metadata.
Please refer to the Object to Hash Mapping section of the reference manual for guidance how to write and register a custom Converter.
Have you tried disable serialization feature SerializationFeature.WRITE_DATES_AS_TIMESTAMPS?
In a spring based project I am working on, there's a layer of functionality for calling web service. For each web service operation, a method is created with almost same code but with some different, operation specific, information(e.g. service name, operation name, namespaces, etc).
I am replacing this layer with interfaces and annotated methods. For example, below code is provided for operation "fetchBar" of web service("foo").
package a.b.c.webservices;
#WebService(service="foo", namespace="...")
public interface FooWebService {
#WebServiceOperation(operation="fetchBar")
BarRespons fetchBar(BarRequest request) throws WebServiceException;
}
Now I want, with some mechanism, spring allow me to create dynamic proxy beans from some specified package(s) and I can use following code to call web service.
package a.b.c.business;
import a.b.c.webservices.FooWebService;
public class FooBusiness {
#Autowired
FooWebService fooWebService;
public Bar getBar() {
Bar bar = null;
BarRequest request;
//create request
BarResponse response = fooWebService.fetchBar(request);
//extrac bar from response
return bar;
}
}
To achieve this I have created dynamic beans instances using java.lang.reflect.Proxy.newProxyInstance by providing it implementation of InvocationHandler. But Autowiring doesn't work in provided implementation of invocationHandler and in its further dependencies.
I tried following ways to achieve this.
Implemented BeanFactoryPostProcessor.postProcessBeanFactory and registered beans using ConfigurableListableBeanFactory.registerSingleton method.
Implemented ImportBeanDefinitionRegistrar.registerBeanDefinitions and tried to use BeanDefinitionRegistry.registerBeanDefinition but I am confused how to provide correct Bean definition that supports Autowiring.
Can any one tell me what is missing? Please guide me if I am not going in right direction.
Here's how I implemented all the functionality that creates beans of 'WebService' annotated interfaces and also supports Autowiring inside proxy implementation. (package declaration and import statements are omitted in below code)
First of all I created WebService and WebServiceOperation annotation.
WebService Annotation
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
public #interface WebService {
String service();
String namespace();
}
WebService Operation Annotation
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface WebServiceOperation {
String operation();
}
Next step is to scan all WebService annotated interfaces from specified packages. Spring provides ClassPathScanningCandidateComponentProvider for package scanning but it does not detect interfaces. Please see this question and it's answer for more details. So I extended ClassPathScanningCandidateComponentProvider and overrode isCandidateComponent method.
ClassPathScanner
public class ClassPathScanner extends ClassPathScanningCandidateComponentProvider {
public ClassPathScanner(final boolean useDefaultFilters) {
super(useDefaultFilters);
}
#Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return beanDefinition.getMetadata().isIndependent();
}
}
At this point I created EnableWebServices annotation to enable web services and to provide web service packages that contain WebService annotated interfaces.
EnableWebServices Annotation
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
#Import({
WebServiceProxyConfig.class,
WebServiceProxyBeansRegistrar.class
})
public #interface EnableWebServices {
#AliasFor("basePackages")
String[] value() default {};
#AliasFor("value")
String[] basePackages() default {};
}
This annotation can be applied to some Configuration annotated class with packages to scan interfaces, as below.
#EnableWebServices({
"a.b.c.webservices",
"x.y.z.webservices"
})
It's time to think about dynamic proxy creation that will invoke actual web service from information given in WebService and WebServiceOperation annotations. Java provides a mechanism to create dynamic proxy which requires to provide implementation of InvocationHandler interface and provide logic in its invoke method. I named this implementaiton as WebServiceProxy
Suppose a bean of type 'TheWebServiceCaller' contains all nasty logic to call a web service. I just have inject it and to invoke it's call method with a TheWebServiceInfo (extracted from WebService and WebServiceOperation annotations) and request object.
TheWebServiceInfo(Suppose all fields have getters and setters)
public class TheWebServiceInfo {
private String service;
private String namespace;
private String operation;
}
WebServiceProxy
public class WebServiceProxy implements InvocationHandler {
#Autowired
private TheWebServiceCaller caller;
#Override
public Object invoke(Object target, Method method, Object[] args) throws Exception {
Object request = (null != args && args.length > 0) ? args[0] : null;
WebService webService = method.getDeclaringClass().getAnnotation(WebService.class);
WebServiceOperation webServiceOperation = method.getAnnotation(WebServiceOperation.class);
TheWebServiceInfo theInfo = createTheWebServiceInfo(webService, webServiceOperation);
return caller.call(theInfo, request);
}
private TheWebServiceInfo createTheWebServiceInfo(WebService webService, WebServiceOperation webServiceOperation) {
TheWebServiceInfo theInfo = new TheWebServiceInfo();
theInfo.setService(webService.service());
theInfo.setNamespace(webService.namespace());
theInfo.setOperation(webServiceOperation.operation());
return theInfo;
}
}
Implementaion of InvocationHandler is passed to Proxy.newProxyInstance (along with some other information) to create proxy objects. I need separat proxy objectes for each WebService annotated interface. I will now create a factory to proxy instances creation and name is as 'WebServiceProxyBeanFactory'. Instances created by this factory will become beans for corresponding WebService annotated interfaces.
A bit later, I will expose 'WebServiceProxy' and WebServiceProxyBeanFactory as beans. In 'WebServiceProxyBeanFactory', I will inject WebServiceProxy and used it. Please note that createWebServiceProxyBean uses generics. This is important.
WebServiceProxyBeanFactory
public class WebServiceProxyBeanFactory {
#Autowired
WebServiceProxy webServiceProxy;
#SuppressWarnings("unchecked")
public <WS> WS createWebServiceProxyBean(ClassLoader classLoader, Class<WS> clazz) {
return (WS) Proxy.newProxyInstance(classLoader, new Class[] {clazz}, webServiceProxy);
}
}
If you remember, earlier I have imported WebServiceProxyConfig in EnableWebServices annotations. WebServiceProxyConfig is used to expose WebServiceProxy and WebServiceProxyBeanFactory as beans.
WebServiceProxyConfig
#Configuration
public class WebServiceProxyConfig {
#Bean
public WebServiceProxy webServiceProxy() {
return new WebServiceProxy();
}
#Bean(name = "webServiceProxyBeanFactory")
public WebServiceProxyBeanFactory webServiceProxyBeanFactory() {
return new WebServiceProxyBeanFactory();
}
}
Now everything is in place. it's time to write a hook to start scanning Web service packages and register dynamic proxies as beans. I will provide implementation of ImportBeanDefinitionRegistrar.
WebServiceProxyBeansRegistrar
#Configuration
public class WebServiceProxyBeansRegistrar implements ImportBeanDefinitionRegistrar, BeanClassLoaderAware {
private ClassPathScanner classpathScanner;
private ClassLoader classLoader;
public WebServiceProxyBeansRegistrar() {
classpathScanner = new ClassPathScanner(false);
classpathScanner.addIncludeFilter(new AnnotationTypeFilter(WebService.class));
}
#Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
#Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
String[] basePackages = getBasePackages(importingClassMetadata);
if (ArrayUtils.isNotEmpty(basePackages)) {
for (String basePackage : basePackages) {
createWebServicProxies(basePackage, registry);
}
}
}
private String[] getBasePackages(AnnotationMetadata importingClassMetadata) {
String[] basePackages = null;
MultiValueMap<String, Object> allAnnotationAttributes =
importingClassMetadata.getAllAnnotationAttributes(EnableWebServices.class.getName());
if (MapUtils.isNotEmpty(allAnnotationAttributes)) {
basePackages = (String[]) allAnnotationAttributes.getFirst("basePackages");
}
return basePackages;
}
private void createWebServicProxies(String basePackage, BeanDefinitionRegistry registry) {
try {
for (BeanDefinition beanDefinition : classpathScanner.findCandidateComponents(basePackage)) {
Class<?> clazz = Class.forName(beanDefinition.getBeanClassName());
WebService webService = clazz.getAnnotation(WebService.class);
String beanName = StringUtils.isNotEmpty(webService.bean())
? webService.bean() : ClassUtils.getShortNameAsProperty(clazz);
GenericBeanDefinition proxyBeanDefinition = new GenericBeanDefinition();
proxyBeanDefinition.setBeanClass(clazz);
ConstructorArgumentValues args = new ConstructorArgumentValues();
args.addGenericArgumentValue(classLoader);
args.addGenericArgumentValue(clazz);
proxyBeanDefinition.setConstructorArgumentValues(args);
proxyBeanDefinition.setFactoryBeanName("webServiceProxyBeanFactory");
proxyBeanDefinition.setFactoryMethodName("createWebServiceProxyBean");
registry.registerBeanDefinition(beanName, proxyBeanDefinition);
}
} catch (Exception e) {
System.out.println("Exception while createing proxy");
e.printStackTrace();
}
}
}
In this class, I extracted all packages provided in EnableWebServices annotation. for each extracted package, I used ClassPathScanner to scan. (Here logic can be refined to filter only WebService annotated interfaces). For each detected interface, I have registered a bean definitions. Please note I have used webServiceProxyBeanFactory and called its createWebServiceProxyBean with classLoader and type of interface. This factory method, when invoked by spring later, will return bean of same type as that of interface, so bean with correct type is registered. This bean can be injected anywhere with interface type. Moreover, WebServiceProxy can inject and use any other bean. So autowiring will also work as expected.
Is your InvocationHandler a bean? You should create it as a bean, not just a simple object to get Autowired working
I was thinking about the same problem but in a slightly more lightweight context. I don't need to load dynamicaly all the webservice clients. So instead I used a FactoryBean and within this factory bean I constructed the dynamic proxy. Here is one example where Autowiring of the service works:
public class CurrencyServiceWithDynamicProxy extends AbstractFactoryBean<CurrencyService> {
ServiceClientConfiguration clientConfiguration;
Object proxy;
#Autowired
public CurrencySyncFactoryDynamicProxy(ServiceClientConfigurationProvider serviceClientConfigurationProvider) {
this.clientConfiguration = serviceClientConfigurationProvider.createClientConfig("currency");
proxy = Proxy.newProxyInstance(getClass().getClassLoader(), new Class<?>[] { getObjectType() }, new MyInvocationHandler());
}
#Override
public Class<CurrencySync> getObjectType() {
// TODO Auto-generated method stub
return CurrencyService.class;
}
#Override
public CurrencySync createInstance() throws Exception {
// do some creational logic
return (CurrencySync)proxy;
}
public CurrencySync createService() {
JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();
factory.setServiceClass(getObjectType());
factory.getFeatures().add(som features);
return getObjectType().cast(factory.create());
}
}
With respect of the accepted answer this factory example can easily be extended into a more dynamic version.
after solving this Do we have to have to post json object with exactly same fields as in pojo object in controller? im getting 415 error while posting from AJAX ,I m using spring rest . And yes i have seen other similar questions but non of them solved my problem
controller:
#RequestMapping(value = "/createTest", method = RequestMethod.POST )
public #ResponseBody String createTest(#RequestBody TestJsonDTO testJson)
throws JsonProcessingException, IOException {
TestSet test = new TestSet();
//................
AJAX:
function createTest() {
$.ajax({
type : 'POST',
url : "http://localhost:8085/annotationBased/admin/createTest",
dataType : "json",
accept:"application/json",
contentType : "application/json",
data : testToJSON(),
success : function() {
alert("success")
},
complete : function(){
findAllTEst()
alert("OK")
},
});
function testToJSON() {
listOfQuestionForTest = questionToAdd;
return JSON.stringify({
"testSet" : {name : $('#testname').val(),
fullmark : parseInt($('#fullmark').val()),
passmark : parseInt($('#passmark').val())},
"questionsInTest" : listOfQuestionForTest
// "testDate":$('#testDate').value()
})
}
and i have added those class u suggested.
You're getting a 415 status code because the server is sending html in the response, while your client expects json.
This might indicate that a server-side exception occured. In such a case, application servers send back a html response.
You have to either make the server respond with json, even if an exception has occured, or let the client handle not only json responses, but also html ones.
I recommend you take the first approach:
#ControllerAdvice
public class ExceptionControllerAdvice {
#ExceptionHandler(Exception.class)
public ErrorResponse handleException(Exception ex) {
ErrorResponse err = new ErrorResponse();
err.setStatusCode(/* 4XX or 500, depending on exception type */);
err.setERrorMessage(ex.getMessage());
return err;
}
}
public class ErrorResponse {
private int statusCode;
private String errorMessage;
// getters and setters or make the fields public
}
A #ControllerAdvice is like a Spring controller, except that it works for every request. #ExceptionHandler tells Spring to intercept exceptions of the specified type and run the code within the annotated method.
Depending on the type of the exception, you should set the right status code in the ErrorResponse object you'll be returning. This is a very basic example, you can also extend from default Spring exception resolvers and overwrite the default behavior. Please refer to this article for further details.
EDIT:
Another thing you could try is to force response's Content-Type to be always application/json, no matter the http stastus returned. You can do this by adding an interceptor in the class where you configure message converters and JSON serialization/deserialization properties:
#Configuration
public class ServiceContext
extends WebMvcConfigurationSupport {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
MappingJackson2HttpMessageConverter converter = this.getMappingJackson2HttpMessageConverter();
converters.add(converter);
}
#Bean
public MappingJackson2HttpMessageConverter getMappingJackson2HttpMessageConverter() {
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
ObjectMapper objectMapper = this.getObjectMapper();
mappingJackson2HttpMessageConverter.setObjectMapper(objectMapper);
return mappingJackson2HttpMessageConverter;
}
#Bean
public ObjectMapper getObjectMapper() {
JsonFactory jsonFactory = new JsonFactory();
ObjectMapper objectMapper = new ObjectMapper(jsonFactory);
objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); // this is what you need
objectMapper.setSerializationInclusion(Include.NON_NULL); // this is to not serialize unset properties
return objectMapper;
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
ResponseHeadersInterceptor headersInterceptor = this.getResponseHeadersInterceptor();
registry.addInterceptor(headersInterceptor).addPathPatterns("/**");
}
#Bean
public ResponseHeadersInterceptor getResponseHeadersInterceptor() {
return new ResponseHeadersInterceptor();
}
}
With ResponseHeadersInterceptor being as follows:
public class ResponseHeadersInterceptor
extends HandlerInterceptorAdapter {
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
response.setContentType(MediaType.APPLICATION_JSON_VALUE + "; charset=UTF-8");
}
}
This way, the server always responds JSON. If you still get 404 or 415, no doubt it's due to some error in the client side.