Skip to content

Commit 2b5b199

Browse files
committed
Parsing curies in incoming documents
1 parent da0192d commit 2b5b199

File tree

11 files changed

+266
-47
lines changed

11 files changed

+266
-47
lines changed

pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,8 @@
9494
<artifactId>maven-compiler-plugin</artifactId>
9595
<version>3.5.1</version>
9696
<configuration>
97-
<source>1.7</source>
98-
<target>1.7</target>
97+
<source>1.8</source>
98+
<target>1.8</target>
9999
<compilerArgs>
100100
<arg>-Xlint</arg>
101101
</compilerArgs>

src/main/java/io/openapitools/jackson/dataformat/hal/annotation/Curie.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
import java.lang.annotation.Target;
77

88
/**
9-
* Annotation specifying a CURIE for use with links
9+
* Annotation specifying a CURIE for use with links. As defined by W3C in
10+
* <a href="https://www.w3.org/TR/2010/NOTE-curie-20101216/">CURIE Syntax 1.0</a>.
1011
*/
1112
@Target({ElementType.TYPE })
1213
@Retention(RetentionPolicy.RUNTIME)
@@ -23,6 +24,6 @@
2324
* e.g. "mysite"
2425
* @return the name of the curie
2526
*/
26-
String curie() default "";
27+
String prefix() default "";
2728

2829
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package io.openapitools.jackson.dataformat.hal.deser;
2+
3+
import io.openapitools.jackson.dataformat.hal.annotation.Curie;
4+
import java.lang.management.OperatingSystemMXBean;
5+
import java.net.URI;
6+
import java.util.Arrays;
7+
import java.util.Objects;
8+
import java.util.Optional;
9+
import java.util.StringTokenizer;
10+
import java.util.concurrent.ConcurrentHashMap;
11+
12+
/**
13+
* Defining mapping of a curie in the context of HAL. That is this will only substitute
14+
* a curie reference into <code>{rel}</code> of a templated URI.
15+
*
16+
* @see <a href="https://www.w3.org/TR/2010/NOTE-curie-20101216/">CURIE SYntax 1.0</a>
17+
*/
18+
public class CurieMapping {
19+
20+
private final ConcurrentHashMap<String, String> mappings = new ConcurrentHashMap<>();
21+
22+
23+
public CurieMapping(Mapping... mappings) {
24+
Objects.requireNonNull(mappings, "Non-null array must be provided");
25+
Arrays.stream(mappings).forEach(m -> this.mappings.put(m.prefix, m.template));
26+
}
27+
28+
/**
29+
* Resolve the given curie using this mapping. Return empty if no relevant mapping could be found.
30+
*/
31+
public Optional<URI> resolve(String curie) {
32+
StringTokenizer st = new StringTokenizer(curie, ":");
33+
if (st.countTokens() != 2) {
34+
return Optional.empty();
35+
}
36+
37+
String template = mappings.get(st.nextToken());
38+
if (template == null) {
39+
return Optional.empty();
40+
} else {
41+
URI resolvedURI = URI.create(template.replace("{rel}", st.nextToken()));
42+
return Optional.of(resolvedURI);
43+
}
44+
}
45+
46+
public static class Mapping {
47+
private final String prefix;
48+
private final String template;
49+
50+
public Mapping(String prefix, String template) {
51+
this.prefix = prefix;
52+
this.template = template;
53+
}
54+
55+
public Mapping(Curie curie) {
56+
this.prefix = curie.prefix();
57+
this.template = curie.href();
58+
}
59+
}
60+
}

src/main/java/io/openapitools/jackson/dataformat/hal/deser/HALBeanDeserializer.java

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,11 @@
88
import com.fasterxml.jackson.databind.JsonNode;
99
import com.fasterxml.jackson.databind.deser.BeanDeserializerBase;
1010
import com.fasterxml.jackson.databind.deser.std.DelegatingDeserializer;
11+
import com.fasterxml.jackson.databind.node.ArrayNode;
1112
import com.fasterxml.jackson.databind.node.ObjectNode;
1213
import java.io.IOException;
14+
import java.net.URI;
15+
import java.util.ArrayList;
1316
import java.util.Iterator;
1417
import java.util.Map;
1518

@@ -30,11 +33,19 @@ public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOEx
3033
for (ReservedProperty rp : ReservedProperty.values()) {
3134
ObjectNode on = (ObjectNode) tn.get(rp.getPropertyName());
3235
if (on != null) {
33-
removeCuries(rp, on);
36+
ArrayList<CurieMapping.Mapping> mappings = new ArrayList<>();
37+
if (ReservedProperty.LINKS.equals(rp) && on.has("curies")) {
38+
ArrayNode curies = (ArrayNode) on.get("curies");
39+
curies.elements().forEachRemaining(node -> mappings.add(createMapping((ObjectNode) node)));
40+
on.remove("curies");
41+
}
42+
CurieMapping curieMapping = new CurieMapping(mappings.toArray(new CurieMapping.Mapping[0]));
43+
3444
Iterator<Map.Entry<String,JsonNode>> it = on.fields();
3545
while (it.hasNext()) {
3646
Map.Entry<String,JsonNode> jn = it.next();
37-
root.set(rp.alternateName(jn.getKey()), jn.getValue());
47+
String propertyName = curieMapping.resolve(jn.getKey()).map(URI::toString).orElse(jn.getKey());
48+
root.set(rp.alternateName(propertyName), jn.getValue());
3849
}
3950
root.remove(rp.getPropertyName());
4051
}
@@ -47,6 +58,10 @@ public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOEx
4758
return _delegatee.deserialize(modifiedParser, ctxt);
4859
}
4960

61+
private CurieMapping.Mapping createMapping(ObjectNode node) {
62+
return new CurieMapping.Mapping(node.get("name").textValue(), node.get("href").textValue());
63+
}
64+
5065
private void removeCuries(ReservedProperty rp, ObjectNode on) {
5166
// Check for curies in the _links object. If they exist, remove them
5267
// as we have nothing in the bean to deserialize into. Curies only exist

src/main/java/io/openapitools/jackson/dataformat/hal/deser/HALBeanDeserializerModifier.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public List<BeanPropertyDefinition> updateProperties(DeserializationConfig confi
4141
while (defIt.hasNext()) {
4242
BeanPropertyDefinition pbd = defIt.next();
4343
for (ReservedProperty rp : ReservedProperty.values()) {
44-
String alternateName = rp.alternateName(pbd, pbd.getName());
44+
String alternateName = rp.alternateName(beanDesc, pbd, pbd.getName());
4545
if (!pbd.getName().equals(alternateName)) {
4646
modified.add(pbd.withName(new PropertyName(alternateName)));
4747
defIt.remove();

src/main/java/io/openapitools/jackson/dataformat/hal/deser/ReservedProperty.java

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
package io.openapitools.jackson.dataformat.hal.deser;
22

3+
import com.fasterxml.jackson.databind.BeanDescription;
34
import com.fasterxml.jackson.databind.introspect.AnnotatedMember;
45
import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition;
6+
import io.openapitools.jackson.dataformat.hal.annotation.Curie;
57
import io.openapitools.jackson.dataformat.hal.annotation.Curies;
68
import io.openapitools.jackson.dataformat.hal.annotation.EmbeddedResource;
79
import io.openapitools.jackson.dataformat.hal.annotation.Link;
8-
910
import java.lang.annotation.Annotation;
1011
import java.lang.reflect.InvocationTargetException;
1112
import java.lang.reflect.Method;
13+
import java.net.URI;
14+
import java.util.ArrayList;
15+
import java.util.Arrays;
16+
import java.util.Optional;
1217
import java.util.UUID;
1318

1419
/**
@@ -23,7 +28,7 @@ public enum ReservedProperty {
2328
private final Class<? extends Annotation> annotation;
2429
private final Method valueMethod;
2530

26-
private ReservedProperty(String name, Class<? extends Annotation> annotation) {
31+
ReservedProperty(String name, Class<? extends Annotation> annotation) {
2732
this.name = name;
2833
this.annotation = annotation;
2934
try {
@@ -37,29 +42,48 @@ public String getPropertyName() {
3742
return name;
3843
}
3944

40-
public String alternateName(BeanPropertyDefinition bpd, String originalName) {
45+
public String alternateName(BeanDescription beanDescription, BeanPropertyDefinition bpd, String originalName) {
4146
AnnotatedMember annotatedMember = firstNonNull(bpd.getField(), bpd.getSetter(), bpd.getGetter());
4247
if (annotatedMember == null) {
4348
return originalName;
4449
}
4550

4651
Annotation o = annotatedMember.getAnnotation(annotation);
47-
if (o != null) {
48-
// Handle the special case for curies first...
49-
if (o.annotationType() == Link.class) {
50-
String curie = ((Link) o).curie();
51-
if (curie != null && !curie.isEmpty()) {
52-
return alternateName(curie + ":" + originalName);
52+
if (o == null) {
53+
return originalName;
54+
}
55+
56+
String name;
57+
58+
try {
59+
String alternateName = (String) valueMethod.invoke(o);
60+
name = alternateName.isEmpty() ? originalName : alternateName;
61+
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
62+
throw new RuntimeException(e);
63+
}
64+
65+
if (o.annotationType() == Link.class) {
66+
String curiePrefix = ((Link) o).curie();
67+
if (!curiePrefix.isEmpty()) {
68+
ArrayList<CurieMapping.Mapping> mappings = new ArrayList<>();
69+
70+
Curie sc = beanDescription.getClassAnnotations().get(Curie.class);
71+
if (sc != null) {
72+
mappings.add(new CurieMapping.Mapping(sc));
5373
}
54-
}
55-
try {
56-
String alternateName = (String) valueMethod.invoke(o);
57-
return alternateName(alternateName.isEmpty() ? originalName : alternateName);
58-
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
59-
throw new RuntimeException(e);
74+
75+
Curies cs = beanDescription.getClassAnnotations().get(Curies.class);
76+
if (cs != null) {
77+
Arrays.stream(cs.value()).forEach(c -> mappings.add(new CurieMapping.Mapping(c)));
78+
}
79+
80+
CurieMapping cm = new CurieMapping(mappings.toArray(new CurieMapping.Mapping[0]));
81+
Optional<URI> resolved = cm.resolve(curiePrefix + ":" + name);
82+
return alternateName(resolved.map(URI::toString).orElse(originalName));
6083
}
6184
}
62-
return originalName;
85+
86+
return alternateName(name);
6387
}
6488

6589
public String alternateName(String originalName) {

src/main/java/io/openapitools/jackson/dataformat/hal/ser/HALBeanSerializer.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -152,10 +152,10 @@ private void populateCurieMap(BeanDescription beanDescription) {
152152
}
153153

154154
for (Curie curie : curieAnnotations) {
155-
if (curieMap.containsKey(curie.curie())) {
156-
LOG.warn("Curie annotation already exists [{}]", curie.curie());
155+
if (curieMap.containsKey(curie.prefix())) {
156+
LOG.warn("Curie annotation already exists [{}]", curie.prefix());
157157
}
158-
curieMap.put(curie.curie(), curie.href());
158+
curieMap.put(curie.prefix(), curie.href());
159159
}
160160
}
161161

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package io.openapitoools.jackson.dataformat.hal.deser;
2+
3+
import io.openapitools.jackson.dataformat.hal.deser.CurieMapping;
4+
import java.net.URI;
5+
import org.junit.Test;
6+
7+
import static org.junit.Assert.assertEquals;
8+
9+
public class CurieMappingTest {
10+
11+
@Test
12+
public void testBasicSubstitution() {
13+
CurieMapping cm = new CurieMapping(new CurieMapping.Mapping("prefix", "https://www.example.com/doc/{rel}"));
14+
URI uri = cm.resolve("prefix:reference").get();
15+
assertEquals(URI.create("https://www.example.com/doc/reference"), uri);
16+
}
17+
18+
}

0 commit comments

Comments
 (0)