Are there any ways to easy format Joda DateTime objects in Freemarker? For example with Java dates, we could use the ?string and other directives.
I know I could call toDate and get a Java Date, but I was hoping there was a better way.
You should be able to call the toString(String pattern) method directly from Freemarker:
${dateTime.toString('MM-dd-yy')}
(not tested)
There's even a simpler way of doing this, if you don't want to splatter toString('MM-dd-yy') all over your templates.
Simply extend Freemarker's DefaultObjectWrapper, so that it understands Joda Time out of the box:
public class JodaAwareObjectWrapper extends DefaultObjectWrapper {
#Override
public TemplateModel wrap(final Object obj) throws TemplateModelException {
if (obj == null) { return super.wrap(obj); }
if (obj instanceof LocalDate) { return new SimpleDate(((LocalDate) obj).toDate(), TemplateDateModel.DATE); }
// add support for all desired types here...
return super.wrap(obj);
}
}
and feed this object wrapper to the FreeMarker config when you fire up your FreeMarker engine
Configuration config = // ...
config.setObjectWrapper(new JodaAwareObjectWrapper());
You can then use FreeMarkers standard date built ins, such as ${dateTime?date} in your templates
I do not believe at this time there is any integration in Freemarker for JodaTime. It is pretty easy to put an object in your model for formatting, i.e.
Write a class "MyCustomJodaFormatterBean", with a format(String pattern, DateTime dateTime) method. Put an instance of this in the root.
root.put("joda", new
MyCustomJodaFormatterBean());
Then in freemarker,
${joda.format("MM-dd-yyy", dateTime)}
During parsing of FTL files freemarker builds its internal model of objects. For example java.util.Date expressions are wrapped into freemarker.template.SimpleDate. If expression value of your model is of type org.joda.time.DateTime - which is unknown for freemarker, it will be wrapped by default into freemarker.ext.beans.StringModel, converting your DateTime to string using toString() method.
For example, assume we have in FTL expression like:
med.expiryDate?date <= today?date
Where "med.expiryDate" is of type DateTime.
"med.expiryDate" will be wrapped into freemarker.ext.beans.StringModel and after this "med.expiryDate?date" will be parsed used freemarker.template.Configuration dateFormat. Which can leed to exception if this dateFormat is different then default format of DateTime.toString().
To fix this you need to make Freemarker understand that DateTime is also a date, not a string. Write your custom object wraper:
/**
* Wrapper to make freemarker identify org.joda.time.DateTime objects as date.
* User: evvo
* Date: 5/26/2016
* Time: 18:21
*/
public class DateTimeAwareObjectWrapper extends DefaultObjectWrapper {
#Override
public TemplateModel wrap(Object obj) throws TemplateModelException {
if (obj instanceof DateTime) {
return new SimpleDate(((DateTime) obj).toDate(), getDefaultDateType());
}
return super.wrap(obj);
}
}
And set it into freemarker configuration
configuration.setObjectWrapper(new DateTimeAwareObjectWrapper());
After such change I belive ?string suffix will also work on DateTime expression.
Related
Is there a clean way to map Optional fields with DynamoDBMapper, for both basic types and arbitrary/custom objects?
I believe one option would be to implement DynamoDBTypeConverter for optionals of basic types (Optional<String>, Optional<Integer>, etc). But, in addition to being a bit dirty, this would not work for custom objects.
I am looking for some sort of "chaining" behaviour of converters, where the default converter is applied first and the result wrapped in case of optional fields.
Is there a way to specify this behaviour?
#DynamoDBTable
public class MyModel {
#DynamoDBAttribute
private Optional<String> someStringField;
#DynamoDBAttribute
private Optional<AnotherModel> someAnotherModelField;
...
}
#DynamoDBDocument
public class AnotherModel {
}
For what you want to do, I believe the custom converters is the proper way.
Create a class for example:
class SomeAnotherModelOptionalConverter implements DynamoDBTypeConverter<String, Optional<AnotherModel>> {
#Override
public String convert(Optional<AnotherModel> optional) {
// your conversion from Optional attribute to String DynamoDB attribute
}
#Override
public Optional<AnotherModel> unconvert(String s) {
// your conversion from String DynamoDB attribute to Optional
}
}
Then on your attribute, you add the following tag:
#DynamoDBAttribute
#DynamoDBTypeConverted(converter = SomeAnotherModelOptionalConverter.class)
private Optional<AnotherModel> someAnotherModelField;
Anyways, I would not use an Optional as a field in a class. Instead I would create a getter that has and Optional as a return.
private AnotherModel someAnotherModelField
...
Optional<AnotherModel> getSomeAnotherModelField(){
return Optional.ofNullable(someAnotherModelField);
}
Let me know if that works for you!
I call a webservice from a Spring Boot App, using jackson-jsr-310 as maven dependency for being able to make use of LocalDateTime:
RestTemplate restTemplate = new RestTemplate();
HttpHeaders httpHeaders = this.createHeaders();
ResponseEntity<String> response;
response = restTemplate.exchange(uri,HttpMethod.GET,new HttpEntity<Object>(httpHeaders),String.class);
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.registerModule(new JavaTimeModule());
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
BusinessPartner test = mapper.readValue(response.getBody(), BusinessPartner.class);
My problem is in the last line, the code produces this error:
java.time.format.DateTimeParseException: Text '/Date(591321600000)/' could not be parsed at index 0
The resulting JSON in response.getBody() looks like this:
{
"d":{
...
"Address":{...},
"FirstName":"asd",
"LastName":"asd",
"BirthDate":"\/Date(591321600000)\/",
}
}
And in my model class, I have the following member:
#JsonProperty("BirthDate")
private LocalDateTime birthDate;
So, after a bit of searching here I found out that this /Date(...)/ seems to be a Microsoft-proprietary Dateformat, which Jackson cannot deserialize into an object per default.
Some questions advise to create a custom SimpleDateFormat and apply it to the opbject mapper, which I tried to do, but then I think I miss the right syntax for mapper.setDateFormat(new SimpleDateFormat("..."));
I tried with e.g. mapper.setDateFormat(new SimpleDateFormat("/Date(S)/"));
or at the end even mapper.setDateFormat(new SimpleDateFormat("SSSSSSSSSSSS)"));
but it seems this does not work, too, so I am out of ideas for now and hope some people here could help me out.
edit 1:
further investigated, it seems one way to go is to write a custom DateDeSerializer for jackson. So I tried this:
#Component
public class JsonDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {
private DateTimeFormatter formatter;
private JsonDateTimeDeserializer() {
this(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
}
public JsonDateTimeDeserializer(DateTimeFormatter formatter) {
this.formatter = formatter;
}
#Override
public LocalDateTime deserialize(JsonParser parser, DeserializationContext context) throws IOException
{
if (parser.hasTokenId(JsonTokenId.ID_STRING)) {
String unixEpochString = parser.getText().trim();
unixEpochString = unixEpochString.replaceAll("[^\\d.]", "");
long unixTime = Long.valueOf(unixEpochString);
if (unixEpochString.length() == 0) {
return null;
}
LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(unixTime), ZoneId.systemDefault());
localDateTime.format(formatter);
return localDateTime;
}
return null;
}
}
which actually returns nearly what I want, annotating my fields in the model using
#JsonDeserialize(using = JsonDateTimeDeserializer.class)
but not exactly:
This code returns a LocalDateTime of value: 1988-09-27T01:00.
But in the thirdparty system, the xmlvalue is 1988-09-27T00:00:00.
As it is obvious, the ZoneId here:
LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(unixTime), ZoneId.systemDefault());
is the Problem, apart from a wrong dateformat.
So could someone here please help me out in how to switch to always use zeros for the time-part and to get my dateformat right? Would be great!
I'm assuming that the number 591321600000 is the epoch milli (number of milliseconds from 1970-01-01T00:00:00Z).
If that's the case, I think that SimpleDateFormat can't help you (at least I couldn't find a way to parse a date from the epoch milli using this class). The pattern S (according to javadoc) is used to format or parse the milliseconds field of a time (so its maximum value is 999) and won't work for your case.
The only way I could make it work is creating a custom deserializer.
First, I created this class:
public class SimpleDateTest {
#JsonProperty("BirthDate")
private LocalDateTime birthDate;
// getter and setter
}
Then I created the custom deserializer and added it to a custom module:
// I'll explain all the details below
public class CustomDateDeserializer extends JsonDeserializer<LocalDateTime> {
#Override
public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
String s = p.getText(); // s is "/Date(591321600000)/"
// assuming the format is always /Date(number)/
long millis = Long.parseLong(s.replaceAll("\\/Date\\((\\d+)\\)\\/", "$1"));
Instant instant = Instant.ofEpochMilli(millis); // 1988-09-27T00:00:00Z
// instant is in UTC (no timezone assigned to it)
// to get the local datetime, you must provide a timezone
// I'm just using system's default, but you must use whatever timezone your system uses
return instant.atZone(ZoneId.systemDefault()).toLocalDateTime();
}
}
public class CustomDateModule extends SimpleModule {
public CustomDateModule() {
addDeserializer(LocalDateTime.class, new CustomDateDeserializer());
}
}
Then I added this module to my mapper and it worked:
// using reduced JSON with only the relevant field
String json = "{ \"BirthDate\": \"\\/Date(591321600000)\\/\" }";
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
// add my custom module
mapper.registerModule(new CustomDateModule());
SimpleDateTest value = mapper.readValue(json, SimpleDateTest.class);
System.out.println(value.getBirthDate()); // 1988-09-26T21:00
Now some comments about the deserializer method.
First I converted the millis 591321600000 to an Instant (a class that represents a UTC instant). 591321600000 in millis is equivalent to 1988-09-27T00:00:00Z.
But that's the UTC date/time. To get the local date and time, you must know in what timezone you are, because in every timezone it's a different date and time (everybody in the world are at the same instant, but their local date/time might be different, depending on where they are).
In my example, I just used ZoneId.systemDefault(), which gets the default timezone of my system. But if you don't want to depend on the default and want to use a specific timezone, use the ZoneId.of("timezone name") method (you can get the list of all available timezones names with ZoneId.getAvailableZoneIds() - this method returns all valid names accepted by the ZoneId.of() method).
As my default timezone is America/Sao_Paulo, this code sets the birthDate to 1988-09-26T21:00.
If you don't want to convert to a specific timezone, you can use the ZoneOffset.UTC. So, in the deserializer method, the last line will be:
return instant.atZone(ZoneOffset.UTC).toLocalDateTime();
Now the local date will be 1988-09-27T00:00 - as we're using UTC offset, there's no timezone conversion and the local date/time is not changed.
PS: if you need to convert the birthDate back to MS's custom format, you can write a custom serializer and add to the custom module as well. To convert a LocalDateTime to that format, you can do:
LocalDateTime birthDate = value.getBirthDate();
// you must know in what zone you are to convert it to epoch milli (using default as an example)
Instant instant = birthDate.atZone(ZoneId.systemDefault()).toInstant();
String msFormat = "/Date(" + instant.toEpochMilli() + ")/";
System.out.println(msFormat); // /Date(591321600000)/
Note that, to convert a LocalDateTime to Instant, you must know in what timezone you are. In this case, I recommend to use the same timezone for serializing and deserializing (in your case, you can use ZoneOffset.UTC instead of ZoneId.systemDefault().
Here's some Groovy code I wrote that also handles the timezone offset: https://gist.github.com/jeffsheets/938733963c03208afd74927fb6130884
class JsonDotNetLocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {
#Override
LocalDateTime deserialize(JsonParser parser, DeserializationContext ctxt) {
convertDotNetDateToJava(parser.text.trim())
}
/**
* Returns a Java LocalDateTime when given a .Net Date String
* /Date(1535491858840-0500)/
*/
static LocalDateTime convertDotNetDateToJava(String dotNetDate) {
// Strip the prefix and suffix to just 1535491858840-0500
String epochAndOffset = dotNetDate[6..-3]
// 1535491858840
String epoch = epochAndOffset[0..-6]
// -0500 Note, keep the negative/positive indicator
String offset = epochAndOffset[-5..-1]
ZoneId zoneId = ZoneId.of("UTC${offset}")
LocalDateTime.ofInstant(Instant.ofEpochMilli(epoch.toLong()), zoneId)
}
}
I just spent a long time trying to work this one out, so I'm posting here in case anyone else makes the same mistake as me.
So, to replicate the situation, I just had a few classes with basic inheritance:
public abstract class Foo
{
public string Name { get; set; }
}
public class Bar : Foo
{
public int SomethingSpecial { get; set; }
}
public class Baz : Foo
{
public string SomethingMundane { get; set; }
}
Now, I want to be able to take a json string, and parse back a concrete implementation of Foo, without knowing beforehand which type the json represents. JSON.Net handles this, by using a $type variable to look up the type of the object:
{
"$type": "MyNamespace.Bar",
"Name": "Resources",
"SomethingSpecial": 42
}
When deserializing the object, you may want to specify the type handling options, and a custom binder, in the JsonSerializerSettings, but that's optional.
The problem I ran into was that when posting the json from a web client, the object was not being deserialized, instead either throwing an exception, or in the case of a POST parameter in ASP.Net, the value was just coming in as null.
The problem here is that Newtonsoft.Json (at least, the version I'm using - 9.0.1) expects the $type element to be the first element of the object, and will ignore it when it is in any other position. So the following json, while technically equivalent to the json in the question, will not work:
{
"Name": "Resources",
"$type": "MyNamespace.Bar",
"SomethingSpecial": 42
}
There is a setting for this, although it may have a performance impact:
new JsonSerializerSettings
{
// $type no longer needs to be first
MetadataPropertyHandling = MetadataPropertyHandling.ReadAhead
}
It's worth noting that if you're using JavaScript's JSON.stringify function, properties are generally written out in the order they were first assigned to the object, so you could also make sure the first thing you do is assign $type.
Is it possible to specify dynamically (at runtime) the indexName for each #Document, for example, via a configuration file? Or is it possible to make #Document Spring environment (dev, prod) dependant?
Thank you!
The #Document annotation does not permit to pass the indexname in parameter directly. However I found a work around.
In my configuration class I created a Bean returning a string. In this string I injected the name of the index with #Value :
#Value("${etrali.indexname}")
private String indexName;
#Bean
public String indexName(){
return indexName;
}
Afterward it is possible to inject the index into the #Documentation annotation like this :
#Document(indexName="#{#indexName}",type = "syslog_watcher")
It works for me, I hope it will help you.
Best regards
The solution from Bruno probably works but the "I created a Bean returning a string" part is a bit confusing.
Here is how I do it :
Have the "index.name" key valued in an application.properties file loaded by "<context:property-placeholder location="classpath:application.properties" />"
Create a bean named ConfigBean annotated with #Named or #Component
#Named
public class ConfigBean {
#Value("${index.name}")
private String indexName;
public String getIndexName() {
return indexName;
}
public void setIndexName(String indexName) {
this.indexName = indexName;
}
}
Inject the value of configBean.getIndexName() into the "#Document" annotation using Spring EL : #Document(indexName = "#{ configBean.indexName }", type = "myType")
P.S. : You may achieve the same result directly using the implicit bean "systemProperties" (something like #{ systemProperties['index.name'] }) but it didn't work for me and it's pretty hard to debug since u can't resolve systemProperties in a programmatic context (https://jira.spring.io/browse/SPR-6651)
The Bruno's solution works but there is no need to create a new Bean in this way. What I do is:
create a bean annotated with #org.springframework.stereotype.Service where the index name is loaded from the database:
#Service
public class ElasticsearchIndexConfigService {
private String elasticsearchIndexName;
// some code to update the elasticsearchIndexName variable
public String getIndexName() {
return elasticsearchIndexName;
}
}
call the getIndexName() method from the bean in the #Document annotation using the SpEL:
#Document(indexName = "#{#elasticsearchIndexConfigService.getIndexName()}", createIndex = false)
public class MyEntity {
}
The crucial part is to use # - #{elasticsearchIndexConfigService.getIndexName()} won't work. I lost some time to figure this out.
I was wondering if it's possible to use an extension method with asp.net webforms and nvelocity. I would like to set some defaults if the string value is null or empty.
Example of .vm file:
Example of my email body...
Billable Status: $billableStatus.Evaluate()
rest of my email body...
Attempted extension method:
public static class Helper
{
public static string Evaluate(this string value)
{
if (String.IsNullOrEmpty(value))
return "Not Provided";
else
return value;
}
}
Or is there an alternative to what I'm tryting to accomplish?
I don't think NVelocity can resolve extension methods with C#/VB.NET syntax sugar. What I do is register an instance of a helper in the velocity context:
var context = VelocityContext();
context.Put("helper", new Helper());
context.Put("billableStatus", "something");
...
and then in your template:
$helper.Evaluate($billableStatus)
You have to make your helper non-static for this to work, of course.
I came across something similar in past and I was looking for something more sophisticated and with more control. I found that NVelocity does provide a way to intercept the method and property calls but for that you will have to implement certain things. In order to make your custom interceptor you will need to implement NVelocity.IDuck. For example
public class MyClass : NVelocity.IDuck
{
public object GetInvoke(string propName)
{
....
}
public object Invoke(string method, params object[] args)
{
....
}
public void SetInvoke(string propName, object value)
{
....
}
}
Now any instance of MyClass will intercept and pass the method and property calls to our these three function implementation and give us a chance to resolve and return the output. You may notice from these three function signatures that in order to implement them we may need some reflection where we can locate respective methods on available extension types and execute them. If needed you can read following blog post for more details about going this way. NVelocity and extension methods