Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve serialization #2

Merged
merged 9 commits into from
Mar 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .github/workflows/publish-snapshots.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: Publish snapshots

on:
push:
branches:
- 'develop'

jobs:
gradle:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions/setup-java@v1
with:
java-version: 11
- uses: eskatos/gradle-command-action@v1
with:
arguments: build -x test publish
env:
GPR_USERNAME: benfortuna
GPR_TOKEN: ${{ secrets.GITHUB_TOKEN }}
MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }}
MAVEN_PASSWORD: ${{ secrets.MAVEN_TOKEN }}
50 changes: 48 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ The purpose of this library is to provide custom marshalling between iCal4j obje
The following is a non-exhaustive list of known JSON calendar formats:

* [jCal](https://tools.ietf.org/html/rfc7265) - The JSON Format for iCalendar
* [jscalendar](https://tools.ietf.org/html/draft-ietf-calext-jscalendar-32) - A JSON representation of calendar data (currently a draft specification)
* [JSCalendar](https://tools.ietf.org/html/draft-ietf-calext-jscalendar-32) - A JSON representation of calendar data (currently a draft specification)

## Implementation

Expand All @@ -20,4 +20,50 @@ dependencies includes:

## Usage

TBD.
### Serialization

#### jCal JSON format:

```java
Calendar calendar = ...;

SimpleModule module = new SimpleModule();
module.addSerializer(Calendar.class, new JCalSerializer());
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(module);

String serialized = mapper.writeValueAsString(calendar);
```

#### JSCalendar JSON format:

```java
Calendar calendar = ...;

SimpleModule module = new SimpleModule();
module.addSerializer(Calendar.class, new JSCalendarSerializer());
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(module);

String serialized = mapper.writeValueAsString(calendar);
```

### Deserialization

```java
String json = ...;

SimpleModule module = new SimpleModule();
module.addDeserializer(Calendar.class, new JCalMapper())
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(module);

Calendar calendar = mapper.readValue(json, Calendar.class);
```

## References

* [RFC5545](https://tools.ietf.org/html/rfc5545) (iCalendar)
* [RFC7265](https://tools.ietf.org/html/rfc7265) (jCal)
* [JSCalendar Draft](https://tools.ietf.org/html/draft-ietf-calext-jscalendar-32)
* [JSCalendar to iCalendar Draft](https://datatracker.ietf.org/doc/html/draft-ietf-calext-jscalendar-icalendar-04)
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
ical4jVersion = 4.0.0-alpha8
ical4jVersion = 3.0.22
jacksonVersion = 2.12.1
groovyVersion = 3.0.7
slf4jVersion = 1.7.30
32 changes: 25 additions & 7 deletions src/main/java/org/mnode/ical4j/json/JCalMapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@
import net.fortuna.ical4j.model.*;
import net.fortuna.ical4j.model.component.CalendarComponent;
import net.fortuna.ical4j.model.component.VEvent;
import net.fortuna.ical4j.model.parameter.Value;
import net.fortuna.ical4j.model.property.ProdId;
import net.fortuna.ical4j.model.property.Uid;
import net.fortuna.ical4j.model.property.Version;

import java.io.IOException;
import java.net.URISyntaxException;
import java.text.ParseException;
import java.util.Arrays;
import java.util.List;

Expand All @@ -39,24 +41,24 @@ public Calendar deserialize(JsonParser p, DeserializationContext ctxt) throws IO
assertNextToken(p, JsonToken.START_ARRAY);
while (!JsonToken.END_ARRAY.equals(p.nextToken())) {
try {
calendar.add(parseProperty(p));
} catch (URISyntaxException e) {
calendar.getProperties().add(parseProperty(p));
} catch (URISyntaxException | ParseException e) {
throw new IllegalArgumentException(e);
}
}
// calendar components..
assertNextToken(p, JsonToken.START_ARRAY);
while (!JsonToken.END_ARRAY.equals(p.nextToken())) {
try {
calendar.add((CalendarComponent) parseComponent(p));
} catch (URISyntaxException e) {
calendar.getComponents().add((CalendarComponent) parseComponent(p));
} catch (URISyntaxException | ParseException e) {
throw new IllegalArgumentException(e);
}
}
return calendar;
}

private Component parseComponent(JsonParser p) throws IOException, URISyntaxException {
private Component parseComponent(JsonParser p) throws IOException, URISyntaxException, ParseException {
assertCurrentToken(p, JsonToken.START_ARRAY);
ComponentBuilder<?> componentBuilder = new ComponentBuilder<>().factories(componentFactories);
componentBuilder.name(p.nextTextValue());
Expand All @@ -73,7 +75,7 @@ private Component parseComponent(JsonParser p) throws IOException, URISyntaxExce
return componentBuilder.build();
}

private Property parseProperty(JsonParser p) throws IOException, URISyntaxException {
private Property parseProperty(JsonParser p) throws IOException, URISyntaxException, ParseException {
assertCurrentToken(p, JsonToken.START_ARRAY);
PropertyBuilder propertyBuilder = new PropertyBuilder().factories(propertyFactories);
propertyBuilder.name(p.nextTextValue());
Expand All @@ -88,8 +90,24 @@ private Property parseProperty(JsonParser p) throws IOException, URISyntaxExcept
throw new IllegalArgumentException(e);
}
}

// propertyType
p.nextTextValue();
String propertyType = p.nextTextValue();
switch (propertyType) {
case "binary":
propertyBuilder.parameter(Value.BINARY);
case "duration":
propertyBuilder.parameter(Value.DURATION);
case "date":
propertyBuilder.parameter(Value.DATE);
case "date-time":
propertyBuilder.parameter(Value.DATE_TIME);
case "period":
propertyBuilder.parameter(Value.PERIOD);
case "uri":
propertyBuilder.parameter(Value.URI);
}

propertyBuilder.value(p.nextTextValue());
assertNextToken(p, JsonToken.END_ARRAY);

Expand Down
37 changes: 21 additions & 16 deletions src/main/java/org/mnode/ical4j/json/JCalSerializer.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,13 @@
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import net.fortuna.ical4j.model.Calendar;
import net.fortuna.ical4j.model.Component;
import net.fortuna.ical4j.model.Parameter;
import net.fortuna.ical4j.model.Property;
import net.fortuna.ical4j.model.*;
import net.fortuna.ical4j.model.component.VEvent;
import net.fortuna.ical4j.model.component.VTimeZone;
import net.fortuna.ical4j.model.component.VToDo;
import net.fortuna.ical4j.model.parameter.Value;

import java.io.IOException;
import java.util.List;

public class JCalSerializer extends StdSerializer<Calendar> {

Expand All @@ -36,13 +33,13 @@ private JsonNode buildVCalendar(Calendar calendar) {
vcalendar.add("vcalendar");

ArrayNode vcalprops = mapper.createArrayNode();
for (Property p : calendar.getProperties().getAll()) {
for (Property p : calendar.getProperties()) {
vcalprops.add(buildPropertyArray(p));
}
vcalendar.add(vcalprops);

ArrayNode vcalcomponents = mapper.createArrayNode();
for (Component c : calendar.getComponents().getAll()) {
for (Component c : calendar.getComponents()) {
vcalcomponents.add(buildComponentArray(c));
}
vcalendar.add(vcalcomponents);
Expand All @@ -57,22 +54,22 @@ private JsonNode buildComponentArray(Component component) {
cArray.add(component.getName().toLowerCase());

ArrayNode componentprops = mapper.createArrayNode();
for (Property p : component.getProperties().getAll()) {
for (Property p : component.getProperties()) {
componentprops.add(buildPropertyArray(p));
}
cArray.add(componentprops);

ArrayNode subcomponents = mapper.createArrayNode();
if (component instanceof VEvent) {
for (Component c : ((VEvent) component).getAlarms().getAll()) {
for (Component c : ((VEvent) component).getAlarms()) {
subcomponents.add(buildComponentArray(c));
}
} else if (component instanceof VToDo) {
for (Component c : ((VToDo) component).getAlarms().getAll()) {
for (Component c : ((VToDo) component).getAlarms()) {
subcomponents.add(buildComponentArray(c));
}
} else if (component instanceof VTimeZone) {
for (Component c : ((VTimeZone) component).getObservances().getAll()) {
for (Component c : ((VTimeZone) component).getObservances()) {
subcomponents.add(buildComponentArray(c));
}
}
Expand All @@ -86,14 +83,20 @@ private JsonNode buildPropertyArray(Property property) {

ArrayNode pArray = mapper.createArrayNode();
pArray.add(property.getName().toLowerCase());
pArray.add(buildParamsObject(property.getParameters().getAll()));
pArray.add(buildParamsObject(property.getParameters()));
pArray.add(getPropertyType(property));
pArray.add(property.getValue());

return pArray;
}

private String getPropertyType(Property property) {
// handle property type overrides via VALUE param..
Value value = property.getParameter(Parameter.VALUE);
if (value != null) {
return value.getValue().toLowerCase();
}

switch (property.getName()) {
case "CALSCALE":
case "METHOD":
Expand All @@ -115,9 +118,8 @@ private String getPropertyType(Property property) {
case "UID":
case "ACTION":
case "REQUEST-STATUS":
case "NAME":
return "text";
case "ATTACH":
return "binary";
case "GEO":
return "float";
case "PERCENT-COMPLETE":
Expand Down Expand Up @@ -145,6 +147,9 @@ private String getPropertyType(Property property) {
return "utc-offset";
case "TZURL":
case "URL":
case "ATTACH":
case "IMAGE":
case "SOURCE":
return "uri";
case "ATTENDEE":
case "ORGANIZER":
Expand All @@ -155,12 +160,12 @@ private String getPropertyType(Property property) {
throw new IllegalArgumentException("Unknown property type");
}

private JsonNode buildParamsObject(List<Parameter> parameterList) {
private JsonNode buildParamsObject(ParameterList parameterList) {
ObjectMapper mapper = new ObjectMapper();

ObjectNode params = mapper.createObjectNode();
for (Parameter p : parameterList) {
params.put(p.getName().toLowerCase(), p.getValue());
params.put(p.getName().toLowerCase(), p.getValue().toLowerCase());
}
return params;
}
Expand Down
14 changes: 7 additions & 7 deletions src/main/java/org/mnode/ical4j/json/JSCalendarSerializer.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,23 @@ public JSCalendarSerializer(Class<Calendar> t) {
@Override
public void serialize(Calendar value, JsonGenerator gen, SerializerProvider provider) throws IOException {
// For calendar objects with a UID we assume a JSGroup is used to represent in jscalendar..
if (value.getProperties().getFirst(Property.UID).isPresent()) {
if (value.getProperty(Property.UID) != null) {
try {
gen.writeTree(buildJSGroup(value));
} catch (ConstraintViolationException e) {
throw new RuntimeException(e);
}
}
// For calendar objects with one or more VEVENTs assume a JSEvent representation..
else if (value.getComponents().getFirst(Component.VEVENT).isPresent()) {
else if (value.getComponent(Component.VEVENT) != null) {
try {
gen.writeTree(buildJSEvent(value));
} catch (ConstraintViolationException e) {
throw new RuntimeException(e);
}
}
// For calendar objects with one or more VTODOs assume a JSTask representation..
else if (value.getComponents().getFirst(Component.VTODO).isPresent()) {
else if (value.getComponent(Component.VTODO) != null) {
try {
gen.writeTree(buildJSTask(value));
} catch (ConstraintViolationException e) {
Expand All @@ -49,20 +49,20 @@ else if (value.getComponents().getFirst(Component.VTODO).isPresent()) {

private JsonNode buildJSGroup(Calendar calendar) throws ConstraintViolationException {
JSGroupBuilder builder = new JSGroupBuilder()
.uid(calendar.getProperties().getRequired(Property.UID).getValue());
.uid(calendar.getProperty(Property.UID).getValue());
return builder.build();
}

private JsonNode buildJSEvent(Calendar calendar) throws ConstraintViolationException {
JSEventBuilder builder = new JSEventBuilder()
.uid(calendar.getComponents().getRequired(Component.VEVENT)
.getProperties().getRequired(Property.UID).getValue());
.uid(calendar.getComponent(Component.VEVENT)
.getProperty(Property.UID).getValue());
return builder.build();
}

private JsonNode buildJSTask(Calendar calendar) throws ConstraintViolationException {
JSTaskBuilder builder = new JSTaskBuilder()
.uid(calendar.getProperties().getRequired(Property.UID).getValue());
.uid(calendar.getProperty(Property.UID).getValue());
return builder.build();
}
}
2 changes: 2 additions & 0 deletions src/main/java/org/mnode/ical4j/json/LocationBuilder.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.mnode.ical4j.json;

import net.fortuna.ical4j.model.LocationType;

import java.net.URL;
import java.time.ZoneId;
import java.util.Map;
Expand Down
29 changes: 0 additions & 29 deletions src/main/java/org/mnode/ical4j/json/LocationType.java

This file was deleted.

4 changes: 2 additions & 2 deletions src/test/groovy/org/mnode/ical4j/json/JCalMapperTest.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class JCalMapperTest extends Specification {

def 'test calendar deserialization'() {
given: 'a json string'
String json = '["vcalendar",[["prodid",{},"string","-//Ben Fortuna//iCal4j 1.0//EN"],["version",{},"string","2.0"],["uid",{},"string","123"]],[]]'
String json = '["vcalendar",[["prodid",{},"string","-//Ben Fortuna//iCal4j 1.0//EN"],["version",{},"string","2.0"],["uid",{},"string","123"],["source",{},"uri","https://www.abc.net.au/news/feed/51120/rss.xml"]],[]]'

and: 'an object mapper'
SimpleModule module = []
Expand All @@ -21,6 +21,6 @@ class JCalMapperTest extends Specification {
Calendar calendar = mapper.readValue(json, Calendar)

then: 'calendar matches expected result'
calendar as String == 'BEGIN:VCALENDAR\r\nPRODID:-//Ben Fortuna//iCal4j 1.0//EN\r\nVERSION:2.0\r\nUID:123\r\nEND:VCALENDAR\r\n'
calendar as String == 'BEGIN:VCALENDAR\r\nPRODID:-//Ben Fortuna//iCal4j 1.0//EN\r\nVERSION:2.0\r\nUID:123\r\nSOURCE;VALUE=URI:https://www.abc.net.au/news/feed/51120/rss.xml\r\nEND:VCALENDAR\r\n'
}
}
Loading