JAXB parseMethod throws exceptions, but generated adapter methods don't - java

I have some dates I have to represent in an XML file in a format different than the one JAXB uses by default.
So, I've written some static methods to print and parse the required format:
public static String toDateTime(XMLGregorianCalendar d) {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
.format(d.toGregorianCalendar().getTime());
}
public static XMLGregorianCalendar parseDateTime(String s)
throws DatatypeConfigurationException, ParseException {
GregorianCalendar cal = new GregorianCalendar();
cal.setTime(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
.parse(s));
return DatatypeFactory.newInstance().newXMLGregorianCalendar(cal);
}
And I've written a .xjb file specifying to use these parse and print methods:
<jxb:bindings
node="//xs:element[#name='date']">
<jxb:javaType name="javax.xml.datatype.XMLGregorianCalendar"
parseMethod="MyClass.parseDateTime"
printMethod="MyClass.toDateTime"
/>
</jxb:bindings>
(The XML schema specifies the element as an xsd:dateTime).
The xjc tool is generating an adapter that calls my methods:
public XMLGregorianCalendar unmarshal(String value) {
return (MyClass.parseDateTime(value));
}
public String marshal(XMLGregorianCalendar value) {
return (MyClass.toDateTime(value));
}
My problem is that the generated adapter won't compile. The unmarshal method is declared not to throw exceptions, but MyClass.parseDateTime does. It seems perfectly reasonable that it should, since the date might be invalid. And indeed, the unmarshal method of java.xml.bind.annotation.adapters.XmlAdapter that the generated adapter overrides declares that it throws Exception.
I couldn't find any way to declare in the xjb file which exceptions the parseMethod throws. It occurred to me that maybe I should create my own adapter class, but I couldn't find any way to declare in the xjb that my custom adapter should be used. I would very much like to be able to generate the classes from the xsd and xjb with xjc and not have to modify them afterwards.
What is the best way to resolve this situation?

You should handle both checked exceptions in your code. For an invalid date-time, you have to decide whether to leave it as null (and maybe emit a warning), or blow up and kill the whole parsing attempt (in that case, throw a RuntimeException wrapping the original ParseException).
In the case of DatatypeConfigurationException, this is one of the JDK APIs that is badly designed, throwing checked exceptions when there's nothing your code can do. Just rethrow it as RuntimeException.

Related

ParseException never thrown

I have a #Service annotated service class method createShiftPlan(int numberOfEmployees, int startingMonth, int year) inside which I am using SimpleDateFormat class's parse() method.
How can I test for parse exceptions with integer inputs here?
Set SimpleDateFormat.setLenient(false) to disable lenient parsing and supply a non-existing date e.g. 35th of April. With lenient disabled this will throw a ParseException instead of silently fixing the date.

Returning partial incomplete results from a method that threw an exception

I'd like some feedback on a situation where:
A method constructs an object, but some of the work done while constructing it might fail. This will lead to an object that is missing some data. I want to give the user of this method the ability to handle the object if complete but also handle the object if incomplete while also being able to handle the exception thrown.
Use Case:
I'm reading a file from disk into a POJO and some of the file attributes like date created can throw an exception while being read from the Operating System. In that case I'm throwing a custom exception but I also want the user to be able to handle that incomplete file representation (POJO).
My solution:
I used a custom exception that wraps the thrown exception and the incomplete object.
My code:
public FileDto getFromFile(File f) throws IncompleteFileDtoException {
FileDto dto = new FileDto();
dto.setName(f.getName());
dto.setPath(f.getAbsolutePath());
dto.setDirectory(f.isDirectory());
dto.setSize(f.length());
dto.setModifiedAt(f.lastModified());
try {
BasicFileAttributes attr = Files.readAttributes(f.toPath(), BasicFileAttributes.class);
dto.setCreatedAt(attr.creationTime().toMillis());
}
catch(Exception e)
{
throw new IncompleteFileDtoException("Unable to transform " +f.getAbsolutePath() + " to DTO.", e, dto );
}
return dto;
}
public static class IncompleteFileDtoException extends Exception
{
private FileDto fileDto;
public IncompleteFileDtoException(String message, Exception e, FileDto fileDto)
{
super(message,e);
this.fileDto = fileDto;
}
public FileDto getFileDto() {
return fileDto;
}
}
What negative effects could this code have ?
Your example only contained one value that might lead to a problem but as soon as you have multiple values you end up with quiet complicated code, because you have to keep the information if such an exception should be thrown.
Personally a better approach might be to just set fitting default values (if not just a null) if the processing failed but it's OK for the initialization of that particular value. And if it's OK that a value can be null you can just the whole exception-throwing. If you need to know if there was a problem during setup, add a flag in that object that gives the information if something failed that can be cheecked. That would also allow you to pass the object around without losing that information in subsequent classes, etc.
In short: Exception should only indicate exceptional situations, i.e. that an object can't be used and not to indicate expected situations
I offer you to use Builder pattern. Do create FileDtoBuilder and put it into exception. When you read file successfully, the do create FileDto instance from existed FileDtoBuilder.
Gang Of Four Design Patterns
https://en.wikipedia.org/wiki/Builder_pattern

Jersey + Gson not deserializing java.util.Date

I'm having a strange issue with a little servlet which uses Jersey and Gson for the JSON serialization/deserialization. I actually copy-pasted the basic Gson provider written for Jersey, like this one: http://eclipsesource.com/blogs/2012/11/02/integrating-gson-into-a-jax-rs-based-application/ and everything seemed to work fine, until I tried to deserialize a Date (in the standard ISO 8601 format), which always gets mapped into my POJO as null.
My first try was to register a deserializer type adapter before returning the gsonBuilder instance, like that:
import java.util.Date;
...
gsonBuilder.registerTypeAdapter(Date.class,
new JsonDeserializer<Date>() {
#Override
public Date deserialize(JsonElement json, Type type,
JsonDeserializationContext arg2) throws JsonParseException {
try {
System.out.println(json);
return (new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXX")).parse(json.getAsString());
} catch (ParseException e) {
return null;
}
}
});
This didn't work, and nothing is printed out when I send the POST request. I tried to use the setDateFormat method on the gsonBuilder instance before returning it, but this didn't change anything.
I thought there were some others classes implementing the MessageBodyWriter and MessageBodyReader overriding my own implementation, so I tried to delete my own implementation and Jersey complained that it wasn't able to deserialize the JSON (so there are no other providers, i guess).
I tried to set breakpoints in the readFrom method in my MessageBodyReader but the request is actually deserialized without suspending the execution.
I should mention that my class contains different fields too, some strings and one date: the string are always deserialized correctly.
I tried sending different dates, starting with 2016-06-23T00:00:00.000+0200 (which should be formatted with the date format string I used in the code above), and getting to the simple 2016-06-17 by removing one part at the time, and it never worked.
I cleaned my maven project, recompiled it and it didn't work.
I thought it could have been Jetty not loading the correct classes, so i deployed the same code into a Tomcat 8 server, and the result was the same.
My last try was to write another parallel MessageBodyReader but instead of making it generic for the Object type, I made a specific java.util.Date deserializer, and still the readFrom method seems not to be called.
I seriously don't know what I could try now, do you have any idea?
Thanks in advance.
The reason of the error is here...
try {
System.out.println(json);
return (new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXX")).parse(json.getAsString());
} catch (ParseException e) {
return null;
}
to be more specific here:
"yyyy-MM-dd'T'HH:mm:ss.SSSXX"
java Date and SimpleParser can hold at the most only 3 places for the milliseconds, and there is no wildcard .SSSXX in the string used for the SimpleDateFormat, so your parser is throwing an Exception, that you are catching but returning a Date referenced to null,
Ergo:
Gson is handling a null referenced object,

Unmarshalling multiple XML elements to one field using JAXB

I have an XML file in format described below. I'm currently using JAXB to unmarshall the XML elements (event) to Java objects (Event). All properties work fine, except one...
As you can see in the XML, the date element stores only the date and the time stores only the time of the event. I would like to combine these two XML elements into one LocalDataTime field named time, with appropriate getters and setters.
XML:
...
<event>
...
<date>2014-02-19</date>
<time>2000-01-01T14:17:00Z</time>
...
</event>
...
Desired Java Object:
public class Event {
...
// Returns a combination of the date and time stored in the XML
public LocalDateTime getDateTime() {
return dateTime;
}
...
}
My first thought was to use a XMLAdapter but it seems that this only allows me to map 1 XML element to 1 Java object.
After that I tried to implement this by overriding the setters setTime and setDate. These setters would each just change the time or date of the stored time.. But I wasn't able to get it working and it also seemed a quite ugly way to do this.
I already read the following JAXB convert multiple attributes to one type while unmarshalling. But for this project, I would rather not use an external library like MOXy.
Does anyone know how to do this using solely JAXB in a clean way?
You could define in your Event object lifecycle methods:
void afterUnmarshal(Unmarshaller unm, Object parent) {
localDateTime = ....
};
boolean beforeMarshal(Marshaller mar) {
date = localDateTime.toDate();
....
};
to construct the LocalDateTime property after unmarshalling (using the date and time values), and prepare date/time fields before marshalling using the current LocaLDateTime value.
You would still need the time/date fields to match the xml and the localDateTime field must be XmlTransient. So it is not so different from the set/getter approach but probably more "clean".

JAXB Date Marshalling Issue

I have a large web service that builds a response object, the relies on JAXB for marshalling it out to the final XML to be returned. In that object I have a number of Date fields. Some of the date fields must not have time zone information and some do (based on bizarre business rules).
In the code, which has not changed for years, for that dates that must not have TZ information we have a custom type adapter to do what we need. For all the other fields we let JAXB take care of it for us: to marshal and unmarshal the Date to include the TZ information.
For every release of new builds of the service we have a custom regression system to ensure not one byte of the response changes from known good responses. In our most recent build we have an issue that cropping up periodically where those dates where the TZ is expected, it's missing:
Old Value:2014-05-14T08:24:20.283-04:00
New Value:2014-05-14T08:24:20.283Z
And, the frustrating part, is when we run the test again for the failed request, the second time the test passes: meaning the TZ is back. Very hard to debug when the bug is not reproducible on demand.
Nowhere in the code that I can find, and in all the dependencies is the system ever changing the JVM TimeZone, or the default format (Locale.setDefault) that I can find.
Has anybody every seen this type JAXB inconsistency with a Date?
We could write another custom adapter that forces the to/from marshalling to have the TZ... But I just don't understand why now, after years of use, suddenly decided to change on us.
UPDATE
Here is the new adapter. Nothing extravigant. But, Like I said, this issue happened before the addition of this adapter. After it's addition, I've no idea if it's resolved yet... No time to test yet.
public class CustomDateTimeTZAdapter extends XmlAdapter<String, Date>{
private static DateFormat getFormatter() {
return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX"); // <---------------- NOTE: The "XXX" for Timezone is only JAVA 7+ supported!
}
#Override
public Date unmarshal(String v) throws Exception {
try {
return getFormatter().parse(v);
} catch (ParseException e) {
return null;
}
}
#Override
public String marshal(Date v) throws Exception {
if (v != null) {
return getFormatter().format(v);
} else {
return null;
}
}
}

Categories