Map Database timestamp column to UTC Calendar (JPA) and pass it as UTC date via WebService (jax-ws)
This sounds like a simple task.
Get UTC timestamp value from DB and pass it as UTC date via Web Service.We have timestamp column DATE_COLUMN and store there time in UTC time zone.
With JPA we get this time with
@Column(name = "DATE_COLUMN")
private java.sql.Timestamp dateValue;
And as we have to pass this time via Web Service in UTC (Jax-ws 2.0) we have getDate and setDate methods.
We are interested in getDate.public Calendar getDate()
{
Calendar calendar = Calendar.getInstance(utcTimeZone);
calendar.setTimeInMillis(dateValue.getTime());
return calendar;
}
This doesn't work as you may think it should.
And this is because application's default time zone is not 'UTC'.Here is an example for clarification.
Value in the DATE_COLUMN equals to "30.11.09 16:34:48,833045000", when I translate it to UTC I get "2009-11-30T14:34:48.833Z". The difference is 2 hours. And this is because my default time zone is "Europe/Helsinki".Same problem if you just want to map 'DATE_COLUMN' to Calendar
@Column(name = "DATE_COLUMN")
@Temporal(TemporalType.TIMESTAMP)
private Calendar dateValue;
public Calendar getDate()
{
calendar.setTimeZone(utcTime开发者_运维技巧Zone);
return calendar;
}
I don't want to change application's time zone because it doesn't look like the solution.
By now we have only two options.
First. Calculate offset between application's time zone and UTC and add it manually after automatic subtraction in the calendar.setTimeZone.
public Calendar getDate()
{
Calendar calendar = Calendar.getInstance(utcTimeZone);
calendar.setTimeInMillis(dateValue.getTime());
int offset = TimeZone.getDefault().getOffset(dateValue.getTime());
calendar.add(Calendar.MILLISECOND, offset);
return calendar;
}
Second. Pass dateValue as Long
via Web Service. Which is not bad except that we lose real type of the field in wsdl.
My imaginary solution is
@Column(name = "DATE_COLUMN")
@Temporal(type = TemporalType.TIMESTAMP, timezone = 'UTC')
private Calendar dateValue;
But I tend to think that there is the real one somewhere. And I hope you can point it out.
We decided to use following solution.
Use Date
for retrieving date from database. It is because Date
is timezoneless type.
@Column(name = "DATE_COLUMN")
@Temporal(TemporalType.TIMESTAMP)
private Date dateValue;
public Date getDate()
{
return dateValue;
}
And to send it via WebService in UTC (jax-ws) we created UtcTimestampAdapter
to change zone from application's default to UTC in the marshaling phase.
public class UtcTimestampAdapter extends XmlAdapter<XMLGregorianCalendar, Date>
{
@Override
public XMLGregorianCalendar marshal(Date date) throws Exception
{
GregorianCalendar calendar = new GregorianCalendar();
calendar.setTime(date);
DatatypeFactory dataTypeFactory = DatatypeFactory.newInstance();
XMLGregorianCalendar xmlCalendar =
dataTypeFactory.newXMLGregorianCalendar(calendar);
//Reset time zone to UTC
xmlCalendar.setTimezone(0);
return xmlCalendar;
}
@Override
public Date unmarshal(XMLGregorianCalendar calendar) throws Exception
{
return calendar.toGregorianCalendar().getTime();
}
}
Then to enable this rule to all Data
s fields in the module we added package specific setting like so.
@XmlJavaTypeAdapter(value = UtcTimestampAdapter.class, type = Date.class)
@XmlSchemaType(name = "dateTime", type = XMLGregorianCalendar.class)
package com.companyname.modulename;
That's it. Now we have generic solution which encapsulate all logic in one place. And if we want to send timezoneless dates as UTC via web service in some other module we will just annotate certain package.
If you need the java process to run at the UTC timezone, the easiest way to do so is by adding the following JVM parameter:
-Duser.timezone=UTC
TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
seems to be affecting the entire JVM.
This will cause other applications fail if they were expecting local time
Another solution is to set the default timezone for the application only in a @StartupBean
:
import java.util.TimeZone;
import javax.annotation.PostConstruct;
import javax.ejb.Singleton;
import javax.ejb.Startup;
@Startup
@Singleton
public class StartupBean {
@PostConstruct
public void initializeTheServer() {
TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
}
}
From then on, all interpretation of Date objects will be based on UTC. This includes XML marshalling.
精彩评论