I've implemented a class for a Java Web App I'm working on. The class has a LocalDateTime property 'created'. However, when I try to set that property (once), its setter is somehow called twice in succession - first setting the value I want, then setting it to null on a second call that should not even happen.
I've traced through the following method and everything looks well up to the third line.
public static ICEDocument mapDocumentFromSOLR(SolrDocument document) {
ICEDocument result = new ICEDocument();
Date uploaded = (Date) document.getFieldValue("CREATED");
LocalDateTime uploadDate = LocalDateUtils.convertUtcDateToLocalDateTime(uploaded); // custom class
result.setCreated(uploadDate); // **faulty line**
}
Here's the class, shortened for clarity:
import java.time.LocalDateTime;
import org.springframework.data.annotation.Transient;
[...]
#JsonIgnoreProperties(ignoreUnknown=true)
public class ICEDocument implements java.io.Serializable {
[...]
#Transient
private LocalDateTime created;
[...]
#JsonDeserialize(using=LocalDateTimeJsonDeserializer.class)
public void setCreated(LocalDateTime created) {
System.out.println("Setting creation date " + created); // added for debugging
this.created = created;
}
}
Steps I've taken trying to resolve this
Removing the #Transient. The data is filled in via Hibernate (ver5.1), and I originally annotated the property since the field itself is not in the corresponding database table. I thought that might be the problem (see Object Serialization and Java Transient Variables), but removing it didn't change anything.
Changing the third line. I switched it with what was basically inside the static LocalDateUtils method. This didn't resolve the issue.
LocalDateTime uploadDate = uploaded.toInstant().atZone(ZoneId.of("UTC")).toLocalDateTime();
Removing the JSON Deserializer. I don't think the JsonDeserializer is at fault since it isn't supposed to (and doesn't accd. to Debug) do anything at this point, but I'll add it here for completeness sake. Could be I'm just grasping at straws at this point.
public class LocalDateTimeJsonDeserializer extends JsonDeserializer<LocalDateTime> {
private static final String DATE_TIME = "yyyy-MM-dd' 'HH:mm:ss";
#Override
public LocalDateTime deserialize(JsonParser parser, DeserializationContext context)
throws IOException, JsonProcessingException {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_TIME);
LocalDateTime deserializedDate = LocalDateTime.parse(parser.getText(), formatter);
return deserializedDate;
}
}
Thank you for reading to the end of my rather long post.
After debugging the code I found a line further down that set the property to null. So it was in fact a second call to the setter and a lot of bad luck, I suppose.
But it might help to know that there wasn't anything wrong with the other factors, so I"ll just leave this here. Thanks again.
Related
I have a Java class having an Instant type of member variable:
public class SomeRecord {
private String someId;
private Instant someInstant;
// getters and setters
}
I am using MongoTemplate to update the someInstant field in database:
public SomeRecord updateSomeRecordBySomeId(final String someId, Object someInstant) {
Query query = new Query();
query.addCriteria(Criteria.where("someId").is(someId));
Update update = new Update();
update.set("someInstant", someInstant);
return operations.findAndModify(query, update, new FindAndModifyOptions().returnNew(true), SomeRecord.class);
}
This works great if I am calling the method as:
updateSomeRecordBySomeId("SOME-ID", Instant.now());
persisting the field in DB as a Date type:
"someInstant" : ISODate("2017-07-11T07:26:44.269Z")
Now the method may also be called as:
updateSomeRecordBySomeId("SOME-ID", "2017-07-11T07:26:44.269Z");
In this case I get an exception as:
org.springframework.core.convert.ConverterNotFoundException: No
converter found capable of converting from type [java.lang.String] to
type [java.time.Instant]
which makes complete sense. (It updates the field in the DB as String though. "someInstant" : "2017-07-11T07:26:44.269Z")
So I added a converter as follows:
MongoConfig.java:
#Configuration
#ComponentScan(basePackages = {"dao package path here"})
public class MongoConfig {
#Autowired
private MongoDbFactory mongoDbFactory;
#Bean
public MongoTemplate mongoTemplate() {
MappingMongoConverter converter = new MappingMongoConverter(new DefaultDbRefResolver(mongoDbFactory),
new MongoMappingContext());
converter.setCustomConversions(new CustomConversions(Collections.singletonList(new StringToInstantConverter())));
return new MongoTemplate(mongoDbFactory, converter);
}
}
StringToInstantConverter.java:
public class StringToInstantConverter implements Converter<String, Instant> {
#Override
public Instant convert(String utcString) {
// TODO: Make it generic for any time-zone
return Instant.parse(utcString);
}
}
After adding the above converter I am not getting ConverterNotFoundException any longer, but the field someInstant is being persisted as plain string: "someInstant" : "2017-07-11T07:26:44.269Z"
And that's what my question is. I know that the converter is being identified that is the reason I am not getting the exception anymore. But why the converter is not converting the String to Instant? Why the field is being persisted as plain String? Is the converter supplied incorrect? How to write converter for this case?
Note:
I have simplified the code to focus on the actual problem. In actual the method does not receive the someInstant field as parameter. So writing overloaded method is not going to be applicable here. Also any kind of instanceOf check inside the method won't work for the actual scenario. So the focus is on the question why the conversion not happening?
The actual data-store for us is DocumentDB, but we use DocumentDB with MongoDB API(as Spring Data does not support DocumentDB) for our database operations.
Your update logic is written in type agnostic way: you can pass any object type (Integer, Long, Boolean, String, Date, etc.) and it will be persisted in DB by overriding the existing value/type with new value and new type. Note: document oriented databased like MongoDB have no fixed schema, so stored data can arbitrary change data types.
The issue you had before you introduced converter with ConverterNotFoundException was not during update action, but during retrieval of updated object and setting it into your Java bean model: Java class defined someInstant property to be of an Instant / Date type, but database supplied a String value.
After you introduced a converter the issue of reading was solved, but only for String and Date types. If you update the someInstant property with some boolean value, you'll get back to the issue to read the object and map it to you Java bean.
I have encountered an issue where I save a list of entities all at once, sometimes some rows have values being written to wrong columns.
Basically, I have a Movie entity, which extends Show (annotated with #MappedSuperclass), which extends TraceableEntity that is also annotated with #MappedSuperclass as shown below:
#MappedSuperclass
#EntityListeners(TraceableEntity.TraceableEntityListener.class)
public abstract class TraceableEntity {
#Column
private Date createdOn;
#Column
private Date dateUpdated;
public Date getCreatedOn() {
return createdOn;
}
public void setCreatedOn(Date createdOn) {
this.createdOn = createdOn;
}
public Date getDateUpdated() {
return dateUpdated;
}
public void setDateUpdated(Date dateUpdated) {
this.dateUpdated = dateUpdated;
}
public static class TraceableEntityListener {
#PrePersist
public void beforeInsert(TraceableEntity entity) {
if (entity.getCreatedOn() == null) {
entity.setCreatedOn(new Date());
}
}
#PreUpdate
public void beforeUpdate(TraceableEntity entity) {
entity.setDateUpdated(new Date());
}
}
}
Now, on some occasions, the value of createdOn ends up in dateUpdated, as shown in this screenshot.
In a nutshell, my application is a scraper that retrieves data from an API. I'm using RestTemplate in CompletableFuture to download data concurrently, and then save everything in one go. The method in which .save(...) is invoked is annotated with #Transactional. When the size of the list is under approximately 1500 the saving is fine, it seems that things go wrong when the size exceeds 1500 for some reason. I'd really appreciate your time and help in this matter!
Does it always happen at the same place? Are you missing some rows for example? perhaps the text of some of the stuff you're scraping has special characters that were not escaped properly? You may want to turn on your logging to see exactly what is being sent to the server.
The reason that if you scrape just the "problematic" movies and it turns out fine is probably because the actual problem occurred before the movies in question.
I have a Java web application which uses Jackson 2.x to deserialize JSON requests. I'm currently interfacing with an external application which is sending JSON data with improperly formatted dates in one property of one of the classes. This is causing exceptions with that client's calls (as well it should). However, for business reasons we need a temporary workaround to accept these requests.
Until that client fixes the data it sends to my application (which may be a while), I want to treat any bad dates in that property as null. I do not want to change the actual class itself, as it is a public API which exposed to other clients, and I don't want this temporary workaround included in that class.
Is there an easy way to configure Jackson to treat invalid dates as null? I'm seeing similar functionality in DeserializationFeature, but nothing specifically for this.
A solution that would require you overriding the setter of the original class in a subclass:
public class InvalidDateDeserializer extends JsonDeserializer<Date>
{
#Override
public Date deserialize(JsonParser jsonParser,
DeserializationContext deserializationcontext) throws IOException, JsonProcessingException {
SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy'T'HH:mm:ss");
String date = jsonParser.getText();
try {
return sdf.parse(date);
} catch (ParseException e) {
return null;
}
}
}
Afterwards, you would just need to annotate the overriden setter in your class with
#JsonDeserialize(using = InvalidDateDeserializer.class)
So the new DTO class would look like this:
public MyModel extends TheirModel {
#Override
#JsonDeserialize(using = InvalidDateDeserializer.class)
public void setProblematicDate() {
super.setProblematicDate();
}
}
I understand this is no magic flag solution, but it should work in your case without changes to the original DTO class.
I was experimenting with Jackson 2.0 mixins to serialize a class with no annotations.
Simplified source code below. Note that I'm not using getters/setters, but it seemed like I should still be able to use mixins according to the documentation.
public class NoAnnotation {
private Date created;
private String name;
// make one with some data in it for the test
static NoAnnotation make() {
NoAnnotation na= new NoAnnotation();
na.created = new Date();
na.name = "FooBear";
return na;
}
// my Mixin "class"
static class JacksonMixIn {
JacksonMixIn(#JsonProperty("created") Date created,
#JsonProperty("name") String name)
{ /* do nothing */ }
}
// test code
public static void main(String[] args) throws Exception {
NoAnnotation na = NoAnnotation.make();
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.addMixInAnnotations(NoAnnotation.class, JacksonMixIn.class);
String jsonText = objectMapper.writeValueAsString(na);
System.out.println(jsonText);
}
}
When I run main I get
Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: No serializer found for class com.flyingspaniel.so.NoAnnotation and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationConfig.SerializationFeature.FAIL_ON_EMPTY_BEANS) )
at com.fasterxml.jackson.databind.ser.impl.UnknownSerializer.failForEmpty(UnknownSerializer.java:51)
at com.fasterxml.jackson.databind.ser.impl.UnknownSerializer.serialize(UnknownSerializer.java:25)
at com.fasterxml.jackson.databind.ser.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:108)
at com.fasterxml.jackson.databind.ObjectMapper._configAndWriteValue(ObjectMapper.java:2407)
at com.fasterxml.jackson.databind.ObjectMapper.writeValueAsString(ObjectMapper.java:1983)
at com.flyingspaniel.so.NoAnnotation.main(NoAnnotation.java:49)
When I follow the instructions in the Exception and add a line
objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
I no longer get an exception, but the result is an empty JSON object, {}.
If I make the fields public it works, but that is not something I want to do, as it's not a reasonable object design.
I'm guessing that I am leaving out a basic "setThis" step somewhere, but don't know what. How can I get mixins to work in this situation?
I figured it out. If you want to access private fields, you need to play with the Visibility by adding the following line:
objectMapper.setVisibilityChecker(VisibilityChecker.Std.defaultInstance()
.withFieldVisibility(Visibility.ANY));
For protected fields, you could also use Visibility.PROTECTED_AND_PUBLIC.
Full example
// test code
public static void main(String[] args) throws Exception {
NoAnnotation na = NoAnnotation.make();
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.addMixInAnnotations(NoAnnotation.class, JacksonMixIn.class);
objectMapper.setVisibilityChecker(VisibilityChecker.Std.defaultInstance()
.withFieldVisibility(Visibility.ANY));
String jsonText = objectMapper.writeValueAsString(na);
System.out.println(jsonText);
}
If you want use the annotation mixin the correct way to declare it is:
static class JacksonMixIn {
#JsonProperty Date created;
#JsonProperty String name;
}
When done in this way you can control the fields to serialize simply including/excluding them from the mix in.
As mentioned in your self-answer, changing the field visibility checker will resolve this situation. As an alternative to modifying the ObjectMapper, this can be done with a purely annotation-based solution by using the #JsonAutoDetect annotation:
#JsonAutoDetect(fieldVisibility = Visibility.ANY)
static class JacksonMixIn {
JacksonMixIn(#JsonProperty("created") Date created,
#JsonProperty("id") int id)
{ /* do nothing */ }
}
I am having some problem mapping my Java Data Type to standard Schema Date data type.
I have a simple class that I annotated like this. The period instance variable is of Java Date object type.
#XmlAccessorType(value = XmlAccessType.NONE)
public class Chart {
#XmlElement
private double amount;
#XmlElement
private double amountDue;
#XmlElement
private Date period;
//constructor getters and setters
}
Here is my Web Service
#WebService
public class ChartFacade {
#WebMethod
public Chart getChart() throws ParseException {
SimpleDateFormat df = new SimpleDateFormat("yyyy-mm-dd");
Chart chart = new Chart(20.0,20.5, df.parse("2001-01-01"));
return chart;
}
}
My problem is it returns the date data in a format not according to what I am expecting.
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Body>
<ns2:getChartResponse xmlns:ns2="http://ss.ugbu.oracle.com/">
<return>
<amount>20.0</amount>
<amountDue>20.5</amountDue>
**<period>2001-01-01T00:01:00+08:00</period>**
</return>
</ns2:getChartResponse>
</S:Body>
</S:Envelope>
I wanted the period element to be returned like this
<period>2001-01-01</period>
Is there any way I can achieve this?
You can do the following to control the schema type:
#XmlElement
#XmlSchemaType(name="date")
private Date period;
For More Information:
http://bdoughan.blogspot.com/2011/01/jaxb-and-datetime-properties.html
Use #XmlJavaTypeAdapter annotation and you can marshal/unmarshal your fields any way you want.
Cannot tell though if it's the simplest way.
And note also that it may harm interoperability with any code that would try to use your WSDL. The programmers for that other code would see xsd:string as the field type, and therefore will have to do formatting and parsing manually (just like you do, yes), introducing who knows how many bugs. So please consider if the xsd:date a bad choice really.
Stolen from here:
#XmlJavaTypeAdapter(value=DateAdapter.class, type=Date.class)
Date someDate;
...
public class DateAdapter extends XmlAdapter<String, Date> {
// the desired format
private String pattern = "MM/dd/yyyy";
public String marshal(Date date) throws Exception {
return new SimpleDateFormat(pattern).format(date);
}
public Date unmarshal(String dateString) throws Exception {
return new SimpleDateFormat(pattern).parse(dateString);
}
}
UPDATE: as was mentioned by #Blaise Doughan, a much shorter way is to annotate the date with
#XmlSchemaType("date")
Date someDate;
Despite it is still not clear why timezone information is not generated for the date, this code works in practice and requires much less typing.
Your Chart constructor seems to be parsing the formatted date string back into a Date, which is then being serialized using the default format to the XML response.
I guess using private String period; (and fixing the constructors) should work