OffsetDateTime returns the datetime in wrong format - spring

I use a Spring 4.3.4, a Jackson 2.8.6 and a Tomcat 8.5.11. I've some REST API:
#RestController
#RequestMapping(value = "/netadvice")
public class NetworkAdviceController {
...
#RequestMapping(
value = "/offsetDateTime",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
public OffsetDateTime todayOffsetDateTime() {
return OffsetDateTime.now();
}
}
After call /netadvice/offsetDateTime I would like to receive the datetime in the following format: 2017-02-14T08:46:06.102Z but I receive something like this: 1487062454.957000000.
What did I wrong and how can I fix this issue?
The following code is my custom datetime serializer and deserializer:
#Configuration
public class JsonConfig {
public static final DateTimeFormatter FORMATTER = ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
#Bean
#Primary
public ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addSerializer(OffsetDateTime.class, new OffsetDateTimeSerializer());
javaTimeModule.addDeserializer(OffsetDateTime.class, new OffsetdateTimeDeserializer());
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
objectMapper.registerModule(javaTimeModule);
return objectMapper;
}
public class OffsetDateTimeSerializer extends JsonSerializer<OffsetDateTime> {
#Override
public void serialize(OffsetDateTime value, JsonGenerator gen, SerializerProvider serializers)
throws IOException {
gen.writeString(value.format(FORMATTER));
}
}
public class OffsetdateTimeDeserializer extends JsonDeserializer<OffsetDateTime> {
#Override
public OffsetDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
return OffsetDateTime.parse(p.getValueAsString(), FORMATTER);
}
}
}

Related

PayloadRootSmartSoapEndpointInterceptor Intercepts multiple EndPoints

I'm trying to add a Custom Interceptors to the interceptors List in my EndPoint Config, but i have a problem where PayloadRootSmartSoapEndpointInterceptor intercepts 2 of my Endpoints instead of one, I have Defined 2 SOAP EndPoints using spring-ws.
#EnableWs
#Configuration
#Order(1)
public class Config extends WsConfigurerAdapter {
private String namespaceBti = "http://tarim.bull.ro/BullTarimWS/BTIService";
private String namespaceBtiLst = "http://tarim.bull.ro/BullTarimWS/BTILSTService";
#Bean
public ServletRegistrationBean messageDispatcherServlet(ApplicationContext applicationContext) {
MessageDispatcherServlet servlet = new MessageDispatcherServlet();
servlet.setApplicationContext(applicationContext);
servlet.setTransformWsdlLocations(true);
return new ServletRegistrationBean(servlet, "/public/btiWS/*");
}
//Service 1
#Bean(name = "BTIService")
public DefaultWsdl11Definition defaultWsdl11DefinitionBti(#Qualifier("BTISchema") XsdSchema certificateSchema) {
DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition();
wsdl11Definition.setPortTypeName("BtiPort");
wsdl11Definition.setLocationUri("/public/btiWS"); //<context-path>
wsdl11Definition.setTargetNamespace(namespaceBti);
wsdl11Definition.setRequestSuffix("Input");
wsdl11Definition.setResponseSuffix("Output");
wsdl11Definition.setSchema(certificateSchema);
return wsdl11Definition;
}
#Bean(name="BTISchema")
public XsdSchema certificateSchemaBti() {
return new SimpleXsdSchema(new ClassPathResource("xml-resources/GETBTI.xsd"));
}
// Service 2
#Bean(name = "BTILSTService") //name of the wsdl in the URL
public DefaultWsdl11Definition defaultWsdl11DefinitionBtiLst(#Qualifier("BTILSTSchema") XsdSchema certificateSchema) {
DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition();
wsdl11Definition.setPortTypeName("BtiLstPort");
wsdl11Definition.setLocationUri("/public/btiWS"); //<context-path>
wsdl11Definition.setTargetNamespace(namespaceBtiLst);
wsdl11Definition.setRequestSuffix("Input");
wsdl11Definition.setResponseSuffix("Output");
wsdl11Definition.setSchema(certificateSchema);
return wsdl11Definition;
}
#Bean(name="BTILSTSchema")
public XsdSchema certificateSchemaBtiLst() {
return new SimpleXsdSchema(new ClassPathResource("xml-resources/GETBTILST.xsd"));
}
#Autowired
private WriteBtiDto writeBtiDto;
Adding a Custom Interceptor to the list>
#Override
public void addInterceptors(List<EndpointInterceptor> interceptors) {
interceptors.add(new PayloadRootSmartSoapEndpointInterceptor(
new BtiEndpointInterceptor(), //let Spring Build and Manage The Bean, not me
BtiEndpoint.getNamespaceUri(),
BtiEndpoint.getLocalPart()
));
}
BTI EndPoint
#Endpoint()
public class BtiEndpoint {
private static final String NAMESPACE_URI="http://tarim.bull.ro/BullTarimWS/BTIService";
private static final String LOCAL_PART = "CXMLTYPE-GETBTIInput";
#PayloadRoot(namespace = NAMESPACE_URI, localPart = LOCAL_PART)
#ResponsePayload
public CXMLTYPEGETBTIOutput getBTI(#RequestPayload CXMLTYPEGETBTIInput request){
CXMLTYPEGETBTIOutput response = new CXMLTYPEGETBTIOutput();
return response;
}
// GETTERS AND SETTER FOR NAMESPACE AND LOCAL PART
BTILST EndPoint
#Endpoint()
public class BtiLstEndpoint {
private static final String NAMESPACE_URI="http://tarim.bull.ro/BullTarimWS/BTILSTService";
private static final String LOCAL_PART = "CXMLTYPE-GETBTILSTInput";
#PayloadRoot(namespace = NAMESPACE_URI, localPart = LOCAL_PART)
#ResponsePayload
public CXMLTYPEGETBTILSTOutput getBTI(#RequestPayload CXMLTYPEGETBTILSTInput request){
CXMLTYPEGETBTILSTOutput response = new CXMLTYPEGETBTILSTOutput();
return response;
}
// GETTERS AND SETTER FOR NAMESPACE AND LOCAL PART
EndpointInterceptor
#Component
public class BtiEndpointInterceptor implements EndpointInterceptor {
private static final Log LOG = LogFactory.getLog(BtiEndpointInterceptor.class);
#Override
public boolean handleRequest(MessageContext messageContext, Object o) throws Exception {
LOG.info("1. Global Request Handling");
return true;
}
#Override
public boolean handleResponse(MessageContext messageContext, Object o) throws Exception {
LOG.info("2. Global Response Handling");
return true;
}
#Override
public boolean handleFault(MessageContext messageContext, Object o) throws Exception {
LOG.info("Global Exception Handling");
return true;
}
#Override
public void afterCompletion(MessageContext messageContext, Object endpoint, Exception ex) {
}

InvalidFormatException for Date - fixing without using JsonFormat or modifying original class

Introduction
We are using a custom starter hosted on a nexus repository, that contains spring-cloud-feign clients that make requests to microservices.
One of the microservices returns the dates as "dd-MM-yyyy HH:mm:ssZ" and this works in most of our applications. However, we have one application that is throwing the following error:
Caused by: com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `java.util.Date` from String "2019-10-16 14:23:17": not a valid representation (error: Failed to parse Date value '2019-10-16 14:23:17': Unparseable date: "2019-10-16 14:23:1
7")
Current work-around
My Current work-around, as I don't want to pollute the starter, is to extend the class and create a local feign-client and local pojo with the proper JsonFormat:
public class DocumentMetaDataFix extends DocumentMetaData {
#JsonFormat(
shape = Shape.STRING,
pattern = "yyyy-MM-dd HH:mm:ss"
)
private Date creationDate;
#JsonFormat(
shape = Shape.STRING,
pattern = "yyyy-MM-dd HH:mm:ss"
)
Failed Fixes
I have tried the following in my configuration class, in order to try affecting the de-serialization from another path. However, the DocumentMetaDataSerializer is never called. The ObjectMapper bean IS called.
#Configuration
#EnableSpringDataWebSupport
#RequiredArgsConstructor
public class MyConfig extends WebMvcConfigurerAdapter {
#Bean
public Jackson2ObjectMapperBuilderCustomizer addCustomBigDecimalDeserialization() {
return new Jackson2ObjectMapperBuilderCustomizer() {
#Override
public void customize(Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder) {
jacksonObjectMapperBuilder.deserializerByType(DocumentMetaData.class, new DocumentMetaDataDeserializer());
}
};
}
#Primary
#Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, true);
mapper.setDateFormat(new SimpleDateFormat("dd-MM-yyyy HH:mm:ss"));
//mapper.configure(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS, true);
return mapper;
}
#Bean
public Module dynamoDemoEntityDeserializer() {
SimpleModule module = new SimpleModule();
module.addDeserializer(DocumentMetaData.class, new DocumentMetaDataDeserializer());
return module;
}
public static class DocumentMetaDataDeserializer extends JsonDeserializer<DocumentMetaData> {
#Override
public DocumentMetaData deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
// return DynamoDemoEntity instance;
JsonNode node = jp.getCodec().readTree(jp);
return null;
}
public DocumentMetaData deserializeWithType(JsonParser jp, DeserializationContext ctxt, TypeDeserializer t) throws IOException {
JsonNode node = jp.getCodec().readTree(jp);
return null;
}
}
Full Stacktrace
Caused by: com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `java.util.Date` from String "2019-10-16 14:23:17": not a valid representation (error: Failed to parse Date value '2019-10-16 14:23:17': Unparseable date: "2019-10-16 14:23:1
7")
at [Source: (ByteArrayInputStream); line: 1, column: 580] (through reference chain: eu.europa.ec.nova.documentstore.DocumentMetaData["creationDate"])
at com.fasterxml.jackson.databind.exc.InvalidFormatException.from(InvalidFormatException.java:67)
at com.fasterxml.jackson.databind.DeserializationContext.weirdStringException(DeserializationContext.java:1548)
at com.fasterxml.jackson.databind.DeserializationContext.handleWeirdStringValue(DeserializationContext.java:910)
at com.fasterxml.jackson.databind.deser.std.StdDeserializer._parseDate(StdDeserializer.java:524)
at com.fasterxml.jackson.databind.deser.std.StdDeserializer._parseDate(StdDeserializer.java:467)
at com.fasterxml.jackson.databind.deser.std.DateDeserializers$DateBasedDeserializer._parseDate(DateDeserializers.java:195)
at com.fasterxml.jackson.databind.deser.std.DateDeserializers$DateDeserializer.deserialize(DateDeserializers.java:285)
at com.fasterxml.jackson.databind.deser.std.DateDeserializers$DateDeserializer.deserialize(DateDeserializers.java:268)
at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:127)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:288)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:151)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4013)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3084)
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:237)
... 70 common frames omitted
So, any ideas?
I have searched through the project for references to Jackson in case there is anything else in my project causing this.
I am will try to go inside the ObjectMapper and try to debug the current parameters/fields of the configuration at ObjectMapper.java:3084 from the stacktace:
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3084)
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:237)
... 67 common frames omitted
Update
I added a breakpoint in the objectmapper constructor, and am seeing that it is being initialized from more than one location. This led me to suspect that spring-boot is not using my ObjectMapper. Instead it is using an internal spring one that is called from MappingJackson2HttpMessageConverter .
<init>:480, ObjectMapper
build:606, Jackson2ObjectMapperBuilder
<init>:59, MappingJackson2HttpMessageConverter
<init>:74, AllEncompassingFormHttpMessageConverter
I will therefore try to over-ride this internal spring one, based on results I found from: How to customise the Jackson JSON mapper implicitly used by Spring Boot?
However this also failed.
References
Is it possible to configure Jackson custom deserializers at class level for different data types?
https://docs.spring.io/spring-boot/docs/current/reference/html/howto-spring-mvc.html#howto-customize-the-jackson-objectmapper
https://www.baeldung.com/jackson-deserialization
very useful: https://mostafa-asg.github.io/post/customize-json-xml-spring-mvc-output/
How to customise Jackson in Spring Boot 1.4
Update - final list of tries
It still fails with an error.
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.serializationInclusion(JsonInclude.Include.NON_NULL);
builder.propertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);
builder.serializationInclusion(Include.NON_EMPTY);
builder.indentOutput(true).dateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
converters.add(new MappingJackson2HttpMessageConverter(builder.build()));
converters.add(new MappingJackson2XmlHttpMessageConverter(builder.createXmlMapper(true).build()));
//converters.add(cmsaMessageConverter());
converters.add(new StringHttpMessageConverter());
converters.add(new FormHttpMessageConverter());
converters.add(new MappingJackson2HttpMessageConverter());
}
#Bean
public Jackson2ObjectMapperBuilderCustomizer addCustomBigDecimalDeserialization() {
return new Jackson2ObjectMapperBuilderCustomizer() {
#Override
public void customize(Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder) {
jacksonObjectMapperBuilder.deserializerByType(DocumentMetaData.class, new DocumentMetaDataDeserializer());
}
};
}
#Primary
#Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, true);
mapper.setDateFormat(new SimpleDateFormat("dd-MM-yyyy HH:mm:ss"));
//mapper.configure(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS, true);
return mapper;
}
#Bean
public Module dynamoDemoEntityDeserializer() {
SimpleModule module = new SimpleModule();
module.addDeserializer(DocumentMetaData.class, new DocumentMetaDataDeserializer());
return module;
}
public static class DocumentMetaDataDeserializer extends JsonDeserializer<DocumentMetaData> {
#Override
public DocumentMetaData deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
// return DynamoDemoEntity instance;
JsonNode node = jp.getCodec().readTree(jp);
return null;
}
public DocumentMetaData deserializeWithType(JsonParser jp, DeserializationContext ctxt, TypeDeserializer t) throws IOException {
JsonNode node = jp.getCodec().readTree(jp);
return null;
}
}
It still fails with an error.
Try using LocalDateTime,
this is what I'm doing and working for me
#JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime date;

spring test rest template : Could not extract response: no suitable HttpMessageConverter found for response type

I Get this exception
org.springframework.web.client.RestClientException: Could not extract
response: no suitable HttpMessageConverter found for response type [AnalyticsResponse] and content type [application/json;charset=UTF-8]
in my junit test cases only(Rest endpoints work fine) if my DTO contains a map with a user defined class as a key
#Data
public class AnalyticsResponse {
private List<Committer> commitersList; //OK
private Map<Committer , Long> comittersCommitsMap; // Problem
private Map<Date, List<CommitItem>> commitItemsTimeLineMap; //OK
}
If comittersCommitsMap field is removed, every thing goes fine
my test case code snippet:
ResponseEntity<AnalyticsResponse> analyticsResponse = testRestTemplate.getForEntity(ANALYSIS_CONTROLLER_BASE_URL+"analytics?repo-full-name=" + searchResponse.getBody().get(0).getFull_name() ,
AnalyticsResponse.class);
---update : Committer class
#Data
#AllArgsConstructor
#NoArgsConstructor
public class Committer {
private String name;
private String email;
}
By default, the ObjectMapper cannot determine serialization and deserialization for a Map in which a key is not a String, you have to provide your custom implementation.
Implement KeyDeserializer and JsonSerializer for Committer and Date(as key for Map)
Configure ObjectMapper - register module with KeyDeserializer and KeySerializer(JsonSerializer)
#SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
// KeyDeserializer for Committer (simple without 'null' check)
#RequiredArgsConstructor
public static class CommitterKeyDeserializer extends KeyDeserializer {
private final ObjectMapper mapper;
#Override
public Object deserializeKey(final String key,
final DeserializationContext ctxt)
throws IOException, JsonProcessingException {
return mapper.readValue(key, Committer.class);
}
}
// KeyDeserializer for Date (simple without 'null' check)
#RequiredArgsConstructor
public static class DateKeyDeserializer extends KeyDeserializer {
private final ObjectMapper mapper;
#Override
public Object deserializeKey(final String key,
final DeserializationContext ctxt)
throws IOException, JsonProcessingException {
return mapper.readValue(key, Date.class);
}
}
// JsonSerializer for Committer (simple without 'null' check)
#RequiredArgsConstructor
public static class CommitterJsonSerializer extends JsonSerializer<Committer> {
private final ObjectMapper mapper;
#Override
public void serialize(Committer committer,
JsonGenerator jgen,
SerializerProvider provider)
throws IOException, JsonProcessingException {
jgen.writeFieldName(mapper.writeValueAsString(committer));
}
}
// JsonSerializer for Date (simple without 'null' check)
public static class DateJsonSerializer extends JsonSerializer<Date> {
#Override
public void serialize(Date date,
JsonGenerator jgen,
SerializerProvider provider)
throws IOException, JsonProcessingException {
jgen.writeFieldName(String.valueOf(date.getTime()));
}
}
// ObjectMapper configuration
#Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
// register module with custom serializers and deserializers
mapper.registerModule(new SimpleModule()
.addKeyDeserializer(
Committer.class,
new CommitterKeyDeserializer(mapper))
.addKeyDeserializer(
Date.class,
new DateKeyDeserializer(mapper))
.addKeySerializer(
Committer.class,
new CommitterJsonSerializer(mapper))
.addKeySerializer(
Date.class,
new DateJsonSerializer()));
return mapper;
}
// RestTemplate configuration
#Bean
public RestTemplate restTemplate(List<HttpMessageConverter<?>> converters) {
RestTemplate restTemplate = new RestTemplate();
// add spring's predefined converters
restTemplate.setMessageConverters(converters);
return restTemplate;
}
}
Note that in this simple implementation the key Committer in comittersCommitsMap represented as a String in JSON response (RestTemplate with this implementation works as well):
{
"commitersList": [
{
"name": "name",
"email": "email"
}
],
"comittersCommitsMap": {
"{\"name\":\"name\",\"email\":\"email\"}": 1
},
"commitItemsTimeLineMap": {
"1570929503854": [
{
"data": "data"
}
]
}
}

Jersey 2.13 + Bean Validation

I'm using gradle and the following libs:
ext.library['jersey'] = "org.glassfish.jersey:project:2.13"
ext.library['jersey_jettison'] = "org.glassfish.jersey.media:jersey-media-json-jettison:2.13"
ext.library['jersey_jackson'] = "org.glassfish.jersey.media:jersey-media-json-jackson1:2.13"
ext.library['jersey_spring'] = "org.glassfish.jersey.ext:jersey-spring3:2.13"
ext.library['jersey_bean_validation'] = "org.glassfish.jersey.ext:jersey-bean-validation:2.13"
I created the bean validation structure, but its not validating at all. No error messages, nothing. This is the structure I've created:
The DTO
public class MergeSchedulesDto {
#NotNull(message = "validation.invalid.mergeFrom")
private Long mergeFrom;
#NotNull(message = "validation.invalid.mergeTo")
#NotEmpty(message = "validation.invalid.mergeTo")
private List<Long> mergeTo;
The Service
#Path("merge")
#POST
#Consumes({ MediaType.APPLICATION_JSON })
public Response merge(#Valid MergeSchedulesDto dto, #QueryParam("units") List<Long> units) {
The config
public class ApplicationJAXRS extends Application {
public ApplicationJAXRS() {
}
#Override
public Map<String, Object> getProperties() {
Map<String, Object> properties = new HashMap<>();
properties
.put("jersey.config.server.provider.packages",
"com.sifionsolution.sig.academic.resource.service,com.sifionsolution.sig.integration.resource.filter,com.sifionsolution.sig.academic.param.converter,com.sifionsolution.sig.datatables.resource.service,com.sifionsolution.sig.datatables.converter");
return properties;
}
#Override
public Set<Object> getSingletons() {
Set<Object> singletons = new HashSet<>();
singletons.add(new Jackson1Feature());
singletons.add(new ValidationExceptionMapper());
return singletons;
}
}
EDIT I forgot the provider:
#Provider
public class ValidationExceptionMapper implements ExceptionMapper<ConstraintViolationException> {
#Override
public Response toResponse(ConstraintViolationException exception) {
return Response.status(BAD_REQUEST).entity(exception.getMessage()).build();
}
}
EDIT 2: I removed the JUnit test because I didnt test using Jersey Test Framework.
The problem here is that the ValidationExceptionMapper is not beeing called.
Put "#Valid" in your, like this:
#POST
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public Response insert(#Valid T obj) throws Exception{
...
}
This works here.

How to fix xml-less autowiring of service

When I call a service directly in my main() I can query the database and things work fine. When a jersey request comes in and maps the JSON to NewJobRequest I can't use my service because the #Autowire failed.
My app:
public class Main {
public static final URI BASE_URI = getBaseURI();
private static URI getBaseURI() {
return UriBuilder.fromUri("http://localhost/").port(9998).build();
}
protected static HttpServer startServer() throws IOException {
ResourceConfig rc = new PackagesResourceConfig("com.production.api.resources");
rc.getFeatures()
.put(JSONConfiguration.FEATURE_POJO_MAPPING, true);
return GrizzlyServerFactory.createHttpServer(BASE_URI, rc);
}
public static void main(String[] args) throws IOException {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Config.class);
//if this is uncommented, it'll successfully query the database
//VendorService vendorService = (VendorService)ctx.getBean("vendorService");
//Vendor vendor = vendorService.findByUUID("asdf");
HttpServer httpServer = startServer();
System.out.println(String.format("Jersey app started with WADL available at " + "%sapplication.wadl\nTry out %shelloworld\nHit enter to stop it...", BASE_URI, BASE_URI));
System.in.read();
httpServer.stop();
}
}
My Resource (controller):
#Component
#Path("/job")
public class JobResource extends GenericResource {
#Path("/new")
#POST
public String New(NewJobRequest request) {
return "done";
}
}
Jersey is mapping the JSON post to:
#Component
public class NewJobRequest {
#Autowired
private VendorService vendorService;
#JsonCreator
public NewJobRequest(Map<String, Object> request) {
//uh oh, can't do anything here because #Autowired failed and vendorService is null
}
}
VendorService:
#Service
public class VendorService extends GenericService<VendorDao> {
public Vendor findByUUID(String uuid) {
Vendor entity = null;
try {
return (Vendor)em.createNamedQuery("Vendor.findByUUID")
.setParameter("UUID", uuid)
.getSingleResult();
} catch (Exception ex) {
return null;
}
}
}
-
#Service
public class GenericService<T extends GenericDao> {
private static Logger logger = Logger.getLogger(Logger.class.getName());
#PersistenceContext(unitName = "unit")
public EntityManager em;
protected T dao;
#Transactional
public void save(T entity) {
dao.save(entity);
}
}
My service config:
#Configuration
public class Config {
#Bean
public VendorService vendorService() {
return new VendorService();
}
}
My config
#Configuration
#ComponentScan(basePackages = {
"com.production.api",
"com.production.api.dao",
"com.production.api.models",
"com.production.api.requests",
"com.production.api.requests.job",
"com.production.api.resources",
"com.production.api.services"
})
#Import({
com.production.api.services.Config.class,
com.production.api.dao.Config.class,
com.production.api.requests.Config.class
})
#PropertySource(value= "classpath:/META-INF/application.properties")
#EnableTransactionManagement
public class Config {
private static final String PROPERTY_NAME_DATABASE_URL = "db.url";
private static final String PROPERTY_NAME_DATABASE_USER = "db.user";
private static final String PROPERTY_NAME_DATABASE_PASSWORD = "db.password";
private static final String PROPERTY_NAME_HIBERNATE_DIALECT = "hibernate.dialect";
private static final String PROPERTY_NAME_HIBERNATE_FORMAT_SQL = "hibernate.format_sql";
private static final String PROPERTY_NAME_HIBERNATE_SHOW_SQL = "hibernate.show_sql";
private static final String PROPERTY_NAME_ENTITYMANAGER_PACKAGES_TO_SCAN = "entitymanager.packages.to.scan";
#Resource
Environment environment;
#Bean
public DataSource dataSource() {
MysqlDataSource dataSource = new MysqlDataSource();
dataSource.setUrl(environment.getRequiredProperty(PROPERTY_NAME_DATABASE_URL));
dataSource.setUser(environment.getRequiredProperty(PROPERTY_NAME_DATABASE_USER));
dataSource.setPassword(environment.getRequiredProperty(PROPERTY_NAME_DATABASE_PASSWORD));
return dataSource;
}
#Bean
public JpaTransactionManager transactionManager() throws ClassNotFoundException {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactoryBean().getObject());
return transactionManager;
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean() throws ClassNotFoundException {
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
entityManagerFactoryBean.setDataSource(dataSource());
entityManagerFactoryBean.setPersistenceUnitName("unit");
entityManagerFactoryBean.setPackagesToScan(environment.getRequiredProperty(PROPERTY_NAME_ENTITYMANAGER_PACKAGES_TO_SCAN));
entityManagerFactoryBean.setPersistenceProviderClass(HibernatePersistence.class);
Properties jpaProperties = new Properties();
jpaProperties.put(PROPERTY_NAME_HIBERNATE_DIALECT, environment.getRequiredProperty(PROPERTY_NAME_HIBERNATE_DIALECT));
jpaProperties.put(PROPERTY_NAME_HIBERNATE_FORMAT_SQL, environment.getRequiredProperty(PROPERTY_NAME_HIBERNATE_FORMAT_SQL));
jpaProperties.put(PROPERTY_NAME_HIBERNATE_SHOW_SQL, environment.getRequiredProperty(PROPERTY_NAME_HIBERNATE_SHOW_SQL));
entityManagerFactoryBean.setJpaProperties(jpaProperties);
return entityManagerFactoryBean;
}
}
The #Path and #POST annotations are JAX-RS, not Spring. So the container is instantiating your endpoints on its own, without any knowledge of Spring beans. You are most likely not getting any Spring logging because Spring is not being used at all.
I've figured out the issue and blogged about it here: http://blog.benkuhl.com/2013/02/how-to-access-a-service-layer-on-a-jersey-json-object/
In the mean time, I'm also going to post the solution here:
I need to tap into the bean that Spring already created so I used Spring's ApplicationContextAware
public class ApplicationContextProvider implements ApplicationContextAware {
private static ApplicationContext applicationContext;
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
public void setApplicationContext (ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
}
And then used that static context reference within my object to be mapped to so I can perform lookups in the service:
public class NewJobRequest {
private VendorService vendorService;
public NewJobRequest() {
vendorService = (VendorService) ApplicationContextProvider.getApplicationContext().getBean("vendorService");
}
#JsonCreator
public NewJobRequest(Map<String, Object> request) {
setVendor(vendorService.findById(request.get("vendorId")); //vendorService is null
}
....
}

Resources