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
Related
I have a service defined as follows.
public String getData(#QueryParam("date") Date date)
I'm trying to pass a java.util.Date to it from my client (which is jaxrs:client of CXF, not a generic HTTP client or browser).
My service receives the date as Thu Mar 01 22:33:10 IST 2012 in the HTTP URL. Since CXF won't be able to create a Date object using this String, my client receives a 404 error.
I tried using a ParameterHandler on the service side, but I still can't parse it successfully because I'm not expecting the date in any specific format.
As per this post, passing a Date is supposed to work out of the box, but I can't seem to get the basic case working. Am I required to do anything in order to successfully pass a Date object from my client to service? Appreciate any help.
Thanks
The problem is that JAX-RS dictates that parameter unbundling be done in one of two ways:
The parameter bean has a public constructor that accepts a String
The parameter bean has a static valueOf(String) method.
In your case, the Date is being unbundled via its Date(String) constructor, which cannot handle the input format your client is sending. You have a couple options available to remedy this:
Option 1
Get your client to change the format of the date before they send it. This is the ideal, but probably the hardest to accomplish!
Option 2
Handle the crazy date format. The options for this are:
Change your method signature to accept a string. Attempt to construct a Date object out of that and if that fails, use your own custom SimpleDateFormat class to parse it.
static final DateFormat CRAZY_FORMAT = new SimpleDateFormat("");
public String getData(#QueryParam("date") String dateString) {
final Date date;
try {
date = new Date(dateString); // yes, I know this is a deprecated method
} catch(Exception e) {
date = CRAZY_FORMAT.parse(dateString);
}
}
Define your own parameter class that does the logic mentioned above. Give it a string constructor or static valueOf(String) method that invokes the logic. And an additional method to get the Date when all is said and done.
public class DateParameter implements Serializable {
public static DateParameter valueOf(String dateString) {
try {
date = new Date(dateString); // yes, I know this is a deprecated method
} catch(Exception e) {
date = CRAZY_FORMAT.parse(dateString);
}
}
private Date date;
// Constructor, Getters, Setters
}
public String getData(#QueryParam("date") DateParameter dateParam) {
final Date date = dateParam.getDate();
}
Or finally, you can register a parameter handler for dates. Where its logic is simply the same as mentioned for the other options above. Note that you need to be using at least CXF 2.5.3 in order to have your parameter handler evaluated before it tries the default unbundling logic.
public class DateHandler implements ParameterHandler<Date> {
public Map fromString(String s) {
final Date date;
try {
date = new Date(dateString); // yes, I know this is a deprecated method
} catch(Exception e) {
date = CRAZY_FORMAT.parse(dateString);
}
}
}
Percepiton's answer was very useful, but ParameterHandler has been deprecated in Apache-cxf 3.0, see the Apache-cxf 3.0 Migration Guide:
CXF JAX-RS ParameterHandler has been dropped, please use JAX-RS 2.0 ParamConverterProvider.
So I add an example with the ParamConverterProvider :
public class DateParameterConverterProvider implements ParamConverterProvider {
#Override
public <T> ParamConverter<T> getConverter(Class<T> type, Type type1, Annotation[] antns) {
if (Date.class.equals(type)) {
#SuppressWarnings("unchecked")
ParamConverter<T> paramConverter = (ParamConverter<T>) new DateParameterConverter();
return paramConverter;
}
return null;
}
}
public class DateParameterConverter implements ParamConverter<Date> {
public static final String format = "yyyy-MM-dd"; // set the format to whatever you need
#Override
public Date fromString(String string) {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(format);
try {
return simpleDateFormat.parse(string);
} catch (ParseException ex) {
throw new WebApplicationException(ex);
}
}
#Override
public String toString(Date t) {
return new SimpleDateFormat(format).format(t);
}
}
The #SuppressWarnings is required to suppress an "unchecked or unsafe operations" warning during compilation. See How do I address unchecked cast warnings for more details.
The ParamConverterProvider can be registred as provider. Here is how I did it:
<jaxrs:server id="myService" address="/rest">
<jaxrs:serviceBeans>
...
</jaxrs:serviceBeans>
<jaxrs:providers>
<ref bean="dateParameterConverterProvider" />
</jaxrs:providers>
</jaxrs:server>
<bean id="dateParameterConverterProvider" class="myPackage.DateParameterConverterProvider"/>
See Apache-cxf JAX-RS : Services Configuration for more information.
Using a custom DateParam class seems the safest option. You can then base your method signatures on that and implement the ugly conversion logic inside the valueOf() method or the class constructor. It is also more self-documenting than using plain strings
As #Perception suggests in option two, you can handle the date. But you should use following:
private Date getDateFromString(String dateString) {
try {
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
Date date = df.parse(dateString);
return date;
} catch (ParseException e) {
//WebApplicationException ...("Date format should be yyyy-MM-dd'T'HH:mm:ss", Status.BAD_REQUEST);
}
}
You call it from within the resource as
Date date = getDateFromString(dateString);//dateString is query param.
I am working on an application which sends an object to a server for processing. The object is sent in JSON format using Spring.
My issue is that all the fields are passed correctly - EXCEPT for the Date variables. They show up as a completely different value, and I am stumped as to why.
Here is an abbreviated version of the object that is being passed:
public class TransactionParameters {
public Date startDate;
public Date endDate;
public List<String> transactionCodes;
public Date getStartDate() {
return startDate;
}
public void setStartDate(Date startDate) {
this.startDate = startDate;
}
public Date getEndDate() {
return endDate;
}
public void setEndDate(Date endDate) {
this.endDate = endDate;
}
public List<String> getTransactionCodes() {
return transactionCodes;
}
public void setTransactionCodes(List<String> transactionCodes) {
this.transactionCodes = transactionCodes;
}
}
Here is the JSON created:
{"transactionCodes":["195"],"startDate":1524456000000,"endDate":1524456000000}
Here is the client code:
String responseString =
restTemplate.postForObject("http://localhost:9080/app/transaction"
+ "testUser123", transactionParameters, String.class);
Here is the server code:
#ApiOperation(value="Get Transactions for Customer")
#POST
#Produces({ MediaType.APPLICATION_JSON })
#Consumes(MediaType.APPLICATION_JSON)
#Path("/customerAccountTransactions/{customerCode: [a-zA-Z0-9]+}")
#RequestMapping(value ="/transaction/{customerCode: [a-zA-Z0-9]+}", method=RequestMethod.POST, produces=MediaType.APPLICATION_JSON, consumes=MediaType.APPLICATION_JSON)
#ApiImplicitParams(#ApiImplicitParam(name = AUTHORIZATION, value = AUTHORIZATION, required = true, dataType = STRING, paramType = HEADER))
public Response getAccountTransactionsForCustomer(#PathVariable(CUSTOMER_CODE) #PathParam(CUSTOMER_CODE) final String customerCode, TransactionParameters transactionParameters) throws IntegrationException {
LOGGER.info("getAccountTransactionsForCustomer()");
Response response = null;
try {
final AccountTransactionsBean atb = getTransactions(customerCode, transactionParameters)
response = ResponseBuilder.buildSuccessResponse(atb);
} catch (final NotAuthorizedException nae) {
response = ResponseBuilder.buildNotAuthorizedResponse();
}
return response;
}
But here's my issue - When I put a breakpoint at where the client calls the endpoint, the date is correct.
However, the date is wildly incorrect as it enters the server's endpoint.
All the the other variables in the TransactionParameters bean are correct. I have also replicated this call using SOAP UI, to rule out any issues with the client, and the issue still persists.
Can anyone offer any suggestions?
Thanks in advance for any help.
The reason for this issue is that Date and String are two different data types. When you are converting your Object to JSON, it is directly converting the date to String and in that process losing its essence.
In order to solve this, you need to tell the code that those particular fields are dates and thus, need to be retained as it is. You can do that by using annotations in your POJO:
Example:
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss.SSSZ")
private Date changeTimestamp;
You can use the above syntax and then change the pattern as per your need.
Disclaimer admittedly I don't know much about Spring REST so I can only give you general pointers, but this really does seem like a de-serialization issue.
Some general things to consider:
Make sure the server and client have the same settings for serializing/de-serializing.
Make sure they are running the same versions of Spring REST and Jackson.
Set the JVM arg -Djavax.net.debug=all and run again to look at what is really being sent/recieved.
Being Spring REST this uses Jackson under the hood right?
Try explicitly annotating your dates and see if that helps:
public class TransactionParameters {
#JsonFormat(pattern="yyyy-MM-dd HH:mm:ss")
public Date startDate;
#JsonFormat(pattern="yyyy-MM-dd HH:mm:ss")
public Date endDate;
// ...
}
You probably have to either add or remove the milliseconds to get the conversion to work correctly. 000
I am trying to read a java.sql.Date field and parse it to a java bean using Univocity with the following code:
public class Example {
public static void main(final String[] args) throws FileNotFoundException {
final BeanListProcessor<Person> rowProcessor = new BeanListProcessor<Person>(Person.class);
final CsvParserSettings parserSettings = new CsvParserSettings();
parserSettings.setProcessor(rowProcessor);
parserSettings.setHeaderExtractionEnabled(false);
parserSettings.getFormat().setDelimiter('|');
final String line = "0|John|12-04-1986";
final CsvParser parser = new CsvParser(parserSettings);
parser.parseLine(line);
final List<Person> beans = rowProcessor.getBeans();
for (final Person person : beans) {
// Expected print: Birthday: 12-04-1986
System.out.println("Birthday: " + person.getBirthDate());
}
}
}
but I am getting the following exception Caused by: java.lang.ClassCastException: java.util.Date cannot be cast to java.lang.String with additional information of com.univocity.parsers.common.DataProcessingException: Error converting value 'Sat Apr 12 00:00:00 CEST 1986' using conversion com.univocity.parsers.conversions.TrimConversion when parsing the line with parser.parseLine(line);
I am trying to represent the Date to how it is in the line e.g. "12-04-1986" and I tried providing the conversion "dd-MM-yyyy", unfortunately to no avail.
What am I missing in my code to get the expected syso of "Birthday: 12-04-1986" ?
EDIT: using java.util.Date
The person class:
// using the correct Date object!
import java.util.Date;
import com.univocity.parsers.annotations.Format;
import com.univocity.parsers.annotations.Parsed;
public class Person {
#Parsed(index=0)
private Integer id;
#Parsed(index=1)
private String name;
#Parsed(index=2)
#Format(formats = "dd-MM-yyyy")
private Date birthDate;
//getters and setters ommited
}
When changing the Date object to a java.util.Date and applying the correct date format on the java.util.Date object the print is correctly displaying the expected result.
The first issue is the conversion sequence you defined:
Conversions.toDate("dd-MM-yyyy"), Conversions.trim()
This generates a Date object, and then applies a String.trim() operation on top of a date instead of a String, which causes the error you just got.
If you change the order this should work:
Conversions.trim(), Conversions.toDate("dd-MM-yyyy")
However, your Person class must have its birthDate field annotated with #Parsed otherwise you'll get a null.
It should be easier to just add the #Format annotation on your birthDate field instead of applying that sequence of conversions. You can just declare your class like this:
public class Person{
#Parsed
String id;
#Parsed
String name;
#Parsed
#Format(formats = "dd-MM-yyyy") //notice that multiple formats are supported
Date birthDate;
}
Finally, notice that the parser trims values by default so you don't need to use the trim conversion or the #Trim annotation unless you disable trimming in the settings.
Hope this helps.
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.
I've got a dynamodb table with a timestamp ("creationDate") as a range. The model is using a joda DateTime to make it easy to use (compatibility with the rest of the code). To be able to make between queries on this range, I used a numeric type for the attribute in the table, and planned to store it as a java timestamp (milliseconds since epoch). Then, I added a marshaller to convert a joda DateTime to a String representing a long and vice-versa.
The table structure (creation):
void CreateTable()
{
CreateTableRequest createTableRequest = new CreateTableRequest().withTableName(LinkManager.TABLE_NAME);
ProvisionedThroughput pt = new ProvisionedThroughput()
.withReadCapacityUnits(LinkManager.READ_CAPACITY_UNITS)
.withWriteCapacityUnits(LinkManager.WRITE_CAPACITY_UNITS);
createTableRequest.setProvisionedThroughput(pt);
ArrayList<AttributeDefinition> ad = new ArrayList<AttributeDefinition>();
ad.add(new AttributeDefinition().withAttributeName("creationDate").withAttributeType(ScalarAttributeType.N));
ad.add(new AttributeDefinition().withAttributeName("contentHash").withAttributeType(ScalarAttributeType.S));
createTableRequest.setAttributeDefinitions(ad);
ArrayList<KeySchemaElement> ks = new ArrayList<KeySchemaElement>();
ks.add(new KeySchemaElement().withAttributeName("contentHash").withKeyType(KeyType.HASH));
ks.add(new KeySchemaElement().withAttributeName("creationDate").withKeyType(KeyType.RANGE));
createTableRequest.setKeySchema(ks);
this.kernel.DDB.createTable(createTableRequest);
}
The model:
#DynamoDBTable(tableName="Link")
public class Link {
private String ContentHash;
private DateTime CreationDate;
#DynamoDBHashKey(attributeName = "contentHash")
public String getContentHash() {
return ContentHash;
}
public void setContentHash(String contentHash) {
ContentHash = contentHash;
}
#DynamoDBRangeKey(attributeName = "creationDate")
#DynamoDBMarshalling(marshallerClass = DateTimeMarshaller.class)
public DateTime getCreationDate() {
return CreationDate;
}
public void setCreationDate(DateTime creationDate) {
CreationDate = creationDate;
}
}
The marshaller:
public class DateTimeMarshaller extends JsonMarshaller<DateTime>
{
public String marshall(DateTime dt)
{
return String.valueOf(dt.getMillis());
}
public DateTime unmarshall(String dt)
{
long ldt = Long.parseLong(dt);
return new DateTime(ldt);
}
}
I get the following error:
Exception in thread "main" com.amazonaws.AmazonServiceException: Type of specified attribute inconsistent with type in table (Service: AmazonDynamoDBv2; Status Code: 400; Error Code: ValidationException; Request ID: 8aabb703-cb44-4e93-ab47-c527a5aa7d52)
I guess this is because the marshaller returns a String, and dynamoDB wants a numeric type, as the attribute type is N. I don't know what people do in this case, I searched for a solution but couldn't find it. I only tested this on a local dynamodb instance, which I don't think makes any difference (this is a validation check failing, there's no request even made).
The obvious workaround is to use long type for the dates in the model and add special getters and setters to work with DateTime. Still, is there a cleaner way ? I am sure I'm not the only one using DatTime range in a model.
What I would do is re-create the table with the Range key as String itself.
Even if it's going to be populated with long numbers, making it type: S will ensure compatibility with the Marshaller