├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── pom.xml └── src ├── main └── java │ └── ioinformarics │ └── oss │ └── jackson │ └── module │ └── jsonld │ ├── BeanJsonldResource.java │ ├── HydraCollection.java │ ├── HydraCollectionBuilder.java │ ├── JsonldContextFactory.java │ ├── JsonldGraph.java │ ├── JsonldGraphBuilder.java │ ├── JsonldModule.java │ ├── JsonldResource.java │ ├── JsonldResourceBuilder.java │ ├── MapJsonldResource.java │ ├── annotation │ ├── JsonldId.java │ ├── JsonldLink.java │ ├── JsonldLinks.java │ ├── JsonldNamespace.java │ ├── JsonldProperty.java │ ├── JsonldResource.java │ ├── JsonldType.java │ └── JsonldTypeFromJavaClass.java │ ├── internal │ ├── AnnotationConstants.java │ ├── JsonldBeanDeserializerModifier.java │ ├── JsonldPropertyNamingStrategy.java │ ├── JsonldResourceSerializer.java │ └── JsonldResourceSerializerModifier.java │ └── util │ ├── AnnotationsUtils.java │ ├── JsonUtils.java │ └── JsonldResourceUtils.java └── test └── java └── ioinformarics └── oss └── jackson └── module └── jsonld ├── JsonldContextFactoryTest.java ├── JsonldModuleTest.java └── testobjects ├── Child.java ├── Parent.java └── internal └── TestObject.java /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | pom.xml.tag 3 | pom.xml.releaseBackup 4 | pom.xml.versionsBackup 5 | pom.xml.next 6 | release.properties 7 | .idea 8 | jackson-jsonld.iml 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - oraclejdk8 4 | script: 5 | - mvn clean test -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 IO Informatics Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jackson-jsonld [![License](http://img.shields.io/badge/license-MIT-blue.svg?style=flat)](http://www.opensource.org/licenses/MIT) [![Build Status](https://travis-ci.org/io-informatics/jackson-jsonld.svg)](https://travis-ci.org/io-informatics/jackson-jsonld) [![Join the chat at https://gitter.im/io-informatics/jackson-jsonld](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/io-informatics/jackson-jsonld?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 2 | 3 | JSON-LD Module for Jackson 4 | 5 | ## Install it 6 | If you use maven, just add the dependency to your pom.xml 7 | ```xml 8 | 9 | com.io-informatics.oss 10 | jackson-jsonld 11 | 0.0.5 12 | 13 | ``` 14 | 15 | ## Use it 16 | The first step is to register the module with jackson. 17 | ```java 18 | // this configure the JsonldModule with an empty default context. 19 | objectMapper.registerModule(new JsonldModule()); 20 | ``` 21 | If you want to provide a default JSON-LD context for your application check the other constructors of [JsonldModule](https://github.com/io-informatics/jackson-jsonld/blob/master/src/main/java/ioinformarics/oss/jackson/module/jsonld/JsonldModule.java#L25) 22 | 23 | 24 | Next, we can have annotated java beans which can be serialized using Jsonld. For instance: 25 | 26 | ```java 27 | @JsonldType("http://schema.org/Person") 28 | public class Person { 29 | @JsonldId 30 | public String id; 31 | @JsonldProperty("http://schema.org/name") 32 | public String name; 33 | @JsonldProperty("http://schema.org/jobTitle") 34 | public String jobtitle; 35 | @JsonldProperty("http://schema.org/url") 36 | public String url; 37 | } 38 | ``` 39 | 40 | Instances of Person can we wrapped inside a JsonldResource or JsonldGraph/HydraCollection. To do this you can use the builders that the library provides: 41 | 42 | ```java 43 | Person alex = new Person(); 44 | alex.id = "mailto:me@alexdeleon.name"; 45 | alex.name = "Alex De Leon"; 46 | alex.jobtitle = "Software Developer"; 47 | alex.url = "http://alexdeleon.name"; 48 | 49 | objectMapper.writer().writeValue(System.out, JsonldResource.Builder.create().build(alex)); 50 | ``` 51 | The above will generate the following JSON-LD representation: 52 | 53 | ```json 54 | { 55 | "@context": { 56 | "name": "http://schema.org/name", 57 | "jobtitle": "http://schema.org/jobTitle", 58 | "url": "http://schema.org/url" 59 | }, 60 | "@type": "http://schema.org/Person", 61 | "name": "Alex De Leon", 62 | "jobtitle": "Software Developer", 63 | "url": "http://alexdeleon.name", 64 | "@id": "mailto:me@alexdeleon.name" 65 | } 66 | ``` 67 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | com.io-informatics.oss 6 | jackson-jsonld 7 | 0.1.2-SNAPSHOT 8 | jackson-jsonld 9 | JSON-LD Module for Jackson 10 | https://github.com/io-informatics/jackson-jsonld 11 | 12 | 13 | scm:git:git@github.com:io-informatics/jackson-jsonld.git 14 | scm:git:git@github.com:io-informatics/jackson-jsonld.git 15 | scm:git:git@github.com:io-informatics/jackson-jsonld.git 16 | HEAD 17 | 18 | 19 | 20 | 21 | Alexander De Leon 22 | adeleon@io-informatics.com 23 | Europe/Madrid 24 | 25 | committer 26 | 27 | 28 | 29 | 30 | 31 | 2.4.4 32 | 2.4.4 33 | 0.5.1 34 | 35 | 36 | 37 | 38 | com.fasterxml.jackson.core 39 | jackson-annotations 40 | ${version.jackson.annotations} 41 | 42 | 43 | com.fasterxml.jackson.core 44 | jackson-core 45 | ${version.jackson.core} 46 | 47 | 48 | com.fasterxml.jackson.core 49 | jackson-databind 50 | ${version.jackson.core} 51 | 52 | 53 | com.github.jsonld-java 54 | jsonld-java 55 | ${version.jsonld.java} 56 | 57 | 58 | org.apache.commons 59 | commons-lang3 60 | 3.3.2 61 | 62 | 63 | io.github.lukehutch 64 | fast-classpath-scanner 65 | 1.99.0 66 | 67 | 68 | junit 69 | junit 70 | 4.11 71 | test 72 | 73 | 74 | org.hamcrest 75 | hamcrest-junit 76 | 2.0.0.0 77 | test 78 | 79 | 80 | 81 | 82 | 83 | 84 | org.apache.maven.plugins 85 | maven-compiler-plugin 86 | 3.1 87 | 88 | 1.8 89 | 1.8 90 | 91 | 92 | 93 | org.sonatype.plugins 94 | nexus-staging-maven-plugin 95 | 1.6.5 96 | true 97 | 98 | sonatype-releases 99 | https://oss.sonatype.org/ 100 | true 101 | 102 | 103 | 104 | org.apache.maven.plugins 105 | maven-release-plugin 106 | 2.4.1 107 | 108 | 109 | org.apache.maven.scm 110 | maven-scm-provider-gitexe 111 | 1.9 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | sonatype-releases 121 | Sonatype Nexus releases repository 122 | https://oss.sonatype.org/content/repositories/releases 123 | 124 | 125 | sonatype 126 | Sonatype Nexus snapshot repository 127 | https://oss.sonatype.org/content/repositories/snapshots 128 | 129 | 130 | 131 | 132 | 133 | sonatype 134 | Sonatype Nexus snapshot repository 135 | https://oss.sonatype.org/content/repositories/snapshots 136 | true 137 | 138 | 139 | sonatype-releases 140 | Sonatype Nexus releases repository 141 | https://oss.sonatype.org/content/repositories/releases 142 | false 143 | 144 | 145 | 146 | 147 | Travis CI 148 | https://travis-ci.org/io-informatics/jackson-jsonld 149 | 150 | 151 | 152 | https://github.com/io-informatics/jackson-jsonld/issues 153 | GitHub Issues 154 | 155 | 156 | 157 | 158 | MIT License 159 | http://www.opensource.org/licenses/mit-license.php 160 | repo 161 | 162 | 163 | 164 | 165 | 166 | release-sign-artifacts 167 | 168 | 169 | performRelease 170 | true 171 | 172 | 173 | 174 | 175 | 176 | org.apache.maven.plugins 177 | maven-gpg-plugin 178 | 179 | 180 | sign-artifacts 181 | verify 182 | 183 | sign 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | -------------------------------------------------------------------------------- /src/main/java/ioinformarics/oss/jackson/module/jsonld/BeanJsonldResource.java: -------------------------------------------------------------------------------- 1 | package ioinformarics.oss.jackson.module.jsonld; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import com.fasterxml.jackson.annotation.JsonPropertyOrder; 6 | import com.fasterxml.jackson.annotation.JsonUnwrapped; 7 | import com.fasterxml.jackson.databind.JsonNode; 8 | 9 | /** 10 | * @author Alexander De Leon 11 | */ 12 | @JsonPropertyOrder({"@context", "@type", "@id"}) 13 | public class BeanJsonldResource implements JsonldResource{ 14 | 15 | @JsonUnwrapped 16 | @JsonInclude(JsonInclude.Include.NON_NULL) 17 | public final Object scopedObj; 18 | 19 | @JsonProperty("@context") 20 | @JsonInclude(JsonInclude.Include.NON_NULL) 21 | public final JsonNode context; 22 | 23 | @JsonProperty("@type") 24 | @JsonInclude(JsonInclude.Include.NON_NULL) 25 | public final String type; 26 | 27 | @JsonProperty("@id") 28 | @JsonInclude(JsonInclude.Include.NON_NULL) 29 | public final String id; 30 | 31 | 32 | BeanJsonldResource(Object scopedObj, JsonNode context, String type, String id) { 33 | this.scopedObj = scopedObj; 34 | this.context = context; 35 | this.type = type; 36 | this.id = id; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/ioinformarics/oss/jackson/module/jsonld/HydraCollection.java: -------------------------------------------------------------------------------- 1 | package ioinformarics.oss.jackson.module.jsonld; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | import com.fasterxml.jackson.databind.JsonNode; 5 | import ioinformarics.oss.jackson.module.jsonld.annotation.JsonldProperty; 6 | 7 | /** 8 | * @author Alexander De Leon 9 | */ 10 | public class HydraCollection extends BeanJsonldResource { 11 | 12 | HydraCollection(Iterable graph, JsonNode context, String type, String id) { 13 | super(new CollectionContainer(graph), context, type, id); 14 | } 15 | 16 | HydraCollection(Iterable graph, 17 | JsonNode context, 18 | String type, 19 | String id, 20 | Long totalItems, 21 | Integer itemsPerPage, 22 | String firstPage, 23 | String nextPage, 24 | String previousPage, 25 | String lastPage) { 26 | super(new CollectionContainer(graph, totalItems, itemsPerPage, firstPage, nextPage, previousPage, lastPage), context, type, id); 27 | } 28 | 29 | static class CollectionContainer { 30 | @JsonldProperty("hydra:member") 31 | @JsonInclude(JsonInclude.Include.NON_NULL) 32 | public final Iterable member; 33 | 34 | @JsonldProperty("hydra:totalItems") 35 | @JsonInclude(JsonInclude.Include.NON_NULL) 36 | public final Long totalItems; 37 | 38 | @JsonldProperty("hydra:itemsPerPage") 39 | @JsonInclude(JsonInclude.Include.NON_NULL) 40 | public final Integer itemsPerPage; 41 | 42 | @JsonldProperty("hydra:firstPage") 43 | @JsonInclude(JsonInclude.Include.NON_NULL) 44 | public final String firstPage; 45 | 46 | @JsonldProperty("hydra:nextPage") 47 | @JsonInclude(JsonInclude.Include.NON_NULL) 48 | public final String nextPage; 49 | 50 | @JsonldProperty("hydra:previousPage") 51 | @JsonInclude(JsonInclude.Include.NON_NULL) 52 | public final String previousPage; 53 | 54 | @JsonldProperty("hydra:lastPage") 55 | @JsonInclude(JsonInclude.Include.NON_NULL) 56 | public final String lastPage; 57 | 58 | CollectionContainer(Iterable member) { 59 | this(member, null,null,null,null,null,null); 60 | } 61 | 62 | public CollectionContainer(Iterable member, Long totalItems, Integer itemsPerPage, String firstPage, String nextPage, String previousPage, String lastPage) { 63 | this.member = member; 64 | this.totalItems = totalItems; 65 | this.itemsPerPage = itemsPerPage; 66 | this.firstPage = firstPage; 67 | this.nextPage = nextPage; 68 | this.previousPage = previousPage; 69 | this.lastPage = lastPage; 70 | } 71 | } 72 | 73 | public interface Builder { 74 | static HydraCollectionBuilder create() { 75 | return new HydraCollectionBuilder(); 76 | } 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/ioinformarics/oss/jackson/module/jsonld/HydraCollectionBuilder.java: -------------------------------------------------------------------------------- 1 | package ioinformarics.oss.jackson.module.jsonld; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | import com.fasterxml.jackson.databind.JsonNode; 5 | import com.fasterxml.jackson.databind.node.JsonNodeFactory; 6 | import com.fasterxml.jackson.databind.node.ObjectNode; 7 | import ioinformarics.oss.jackson.module.jsonld.annotation.JsonldProperty; 8 | 9 | import java.util.Optional; 10 | 11 | /** 12 | * @author Alexander De Leon 13 | */ 14 | public class HydraCollectionBuilder extends JsonldGraphBuilder { 15 | 16 | private Long totalItems; 17 | private Integer itemsPerPage; 18 | private String firstPage; 19 | private String nextPage; 20 | private String previousPage; 21 | private String lastPage; 22 | private boolean isPaged = false; 23 | 24 | 25 | public HydraCollectionBuilder totalItems(Long totalItems) { 26 | this.totalItems = totalItems; 27 | isPaged = true; 28 | return this; 29 | } 30 | 31 | public HydraCollectionBuilder itemsPerPage(Integer itemsPerPage) { 32 | this.itemsPerPage = itemsPerPage; 33 | isPaged = true; 34 | return this; 35 | } 36 | 37 | public HydraCollectionBuilder firstPage(String firstPage) { 38 | this.firstPage = firstPage; 39 | isPaged = true; 40 | return this; 41 | } 42 | 43 | public HydraCollectionBuilder nextPage(String nextPage) { 44 | this.nextPage = nextPage; 45 | isPaged = true; 46 | return this; 47 | } 48 | 49 | public HydraCollectionBuilder previousPage(String previousPage) { 50 | this.previousPage = previousPage; 51 | isPaged = true; 52 | return this; 53 | } 54 | 55 | public HydraCollectionBuilder lastPage(String lastPage) { 56 | this.lastPage = lastPage; 57 | isPaged = true; 58 | return this; 59 | } 60 | 61 | public JsonldResource build(Iterable elements) { 62 | return new HydraCollection(elements, buildContext(elements).orElse(null), 63 | isPaged? "hydra:PagedCollection": "hydra:Collection", graphId, totalItems, itemsPerPage, firstPage, nextPage, previousPage, lastPage); 64 | } 65 | 66 | protected Optional buildContext(Iterable elements) { 67 | Optional hydraContext = JsonldContextFactory.fromAnnotations(HydraCollection.CollectionContainer.class); 68 | Optional mergedContext = hydraContext.map(it -> (ObjectNode)it.setAll(JsonldContextFactory.fromAnnotations(elements).orElse(emptyNode()))); 69 | return JsonldContextFactory.multiContext(Optional.ofNullable(context), mergedContext); 70 | } 71 | 72 | private ObjectNode emptyNode() { 73 | return JsonNodeFactory.withExactBigDecimals(true).objectNode(); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/ioinformarics/oss/jackson/module/jsonld/JsonldContextFactory.java: -------------------------------------------------------------------------------- 1 | package ioinformarics.oss.jackson.module.jsonld; 2 | 3 | import com.fasterxml.jackson.databind.JsonNode; 4 | import com.fasterxml.jackson.databind.node.ArrayNode; 5 | import com.fasterxml.jackson.databind.node.JsonNodeFactory; 6 | import com.fasterxml.jackson.databind.node.ObjectNode; 7 | import com.fasterxml.jackson.databind.node.TextNode; 8 | import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner; 9 | import ioinformarics.oss.jackson.module.jsonld.annotation.*; 10 | import ioinformarics.oss.jackson.module.jsonld.util.AnnotationsUtils; 11 | import ioinformarics.oss.jackson.module.jsonld.util.JsonUtils; 12 | import ioinformarics.oss.jackson.module.jsonld.util.JsonldResourceUtils; 13 | import org.apache.commons.lang3.ClassUtils; 14 | import java.lang.reflect.Field; 15 | import java.lang.reflect.Modifier; 16 | import java.lang.reflect.ParameterizedType; 17 | import java.lang.reflect.Type; 18 | import java.util.*; 19 | import java.util.stream.Stream; 20 | 21 | /** 22 | * @author Alexander De Leon 23 | */ 24 | public class JsonldContextFactory { 25 | 26 | public static ObjectNode fromPackage(String packageName) { 27 | ObjectNode generatedContext = JsonNodeFactory.withExactBigDecimals(true).objectNode(); 28 | FastClasspathScanner scanner = new FastClasspathScanner(packageName); 29 | scanner.matchAllStandardClasses((clazz) -> { 30 | if(!Modifier.isAbstract(clazz.getModifiers()) && AnnotationsUtils.isAnnotationPresent(clazz, JsonldTypeFromJavaClass.class)) { 31 | Optional type = JsonldResourceUtils.dynamicTypeLookup(clazz); 32 | type.ifPresent(t ->generatedContext.set(clazz.getSimpleName(), TextNode.valueOf(t))); 33 | } 34 | if(AnnotationsUtils.isAnnotationPresent(clazz, ioinformarics.oss.jackson.module.jsonld.annotation.JsonldResource.class)) { 35 | Optional resourceContext = fromAnnotations(clazz); 36 | resourceContext.ifPresent((context) -> JsonUtils.merge(generatedContext, context)); 37 | } 38 | }); 39 | scanner.scan(); 40 | return (ObjectNode) JsonNodeFactory.withExactBigDecimals(true).objectNode().set("@context", generatedContext); 41 | } 42 | 43 | public static Optional fromAnnotations(Object instance) { 44 | return fromAnnotations(instance.getClass()); 45 | } 46 | 47 | public static Optional fromAnnotations(Iterable instances) { 48 | ObjectNode mergedContext = JsonNodeFactory.withExactBigDecimals(true).objectNode(); 49 | instances.forEach(e -> fromAnnotations(e).map(mergedContext::setAll)); 50 | return mergedContext.size() != 0 ? Optional.of(mergedContext) : Optional.empty(); 51 | } 52 | 53 | public static Optional fromAnnotations(Class objType) { 54 | ObjectNode generatedContext = JsonNodeFactory.withExactBigDecimals(true).objectNode(); 55 | generateNamespaces(objType).forEach((name, uri) -> generatedContext.set(name, new TextNode(uri))); 56 | //TODO: This is bad...it does not consider other Jackson annotations. Need to use a AnnotationIntrospector? 57 | final Map fieldContexts = generateContextsForFields(objType); 58 | fieldContexts.forEach(generatedContext::set); 59 | //add links 60 | JsonldLink[] links = objType.getAnnotationsByType(JsonldLink.class); 61 | if (links != null) { 62 | for (int i = 0; i < links.length; i++) { 63 | com.fasterxml.jackson.databind.node.ObjectNode linkNode = JsonNodeFactory.withExactBigDecimals(true) 64 | .objectNode(); 65 | linkNode.set("@id", new TextNode(links[i].rel())); 66 | linkNode.set("@type", new TextNode("@id")); 67 | generatedContext.set(links[i].name(), linkNode); 68 | } 69 | } 70 | //Return absent optional if context is empty 71 | return generatedContext.size() != 0 ? Optional.of(generatedContext) : Optional.empty(); 72 | } 73 | 74 | private static Map generateContextsForFields(Class objType) { 75 | return generateContextsForFields(objType, new ArrayList<>()); 76 | } 77 | 78 | private static Map generateContextsForFields(Class objType, List> ignoreTypes) { 79 | final Map contexts = new HashMap<>(); 80 | Class currentClass = objType; 81 | Optional namespace = Optional.ofNullable(currentClass.getAnnotation(JsonldNamespace.class)); 82 | while (currentClass != null && !currentClass.equals(Object.class)) { 83 | final Field[] fields = currentClass.getDeclaredFields(); 84 | for (Field f : fields) { 85 | if(f.isAnnotationPresent(JsonldId.class) || f.getName().equals("this$0")) { 86 | continue; 87 | } 88 | final JsonldProperty jsonldProperty = f.getAnnotation(JsonldProperty.class); 89 | Optional propertyId = Optional.empty(); 90 | // Most concrete field overrides any field with the same name defined higher up the hierarchy 91 | if (jsonldProperty != null && !contexts.containsKey(f.getName())) { 92 | propertyId = Optional.of(jsonldProperty.value()); 93 | } 94 | else if(jsonldProperty == null && namespace.map(JsonldNamespace::applyToProperties).orElse(false)) { 95 | propertyId = Optional.of(namespace.get().name() + ":" + f.getName()); 96 | } 97 | propertyId.ifPresent((id) -> { 98 | if(isRelation(f)) { 99 | ObjectNode node = JsonNodeFactory.withExactBigDecimals(true).objectNode(); 100 | node.set("@id", TextNode.valueOf(id)); 101 | node.set("@type", TextNode.valueOf("@id")); 102 | contexts.put(f.getName(), node); 103 | } 104 | else { 105 | contexts.put(f.getName(), TextNode.valueOf(id)); 106 | } 107 | }); 108 | } 109 | currentClass = currentClass.getSuperclass(); 110 | if(!namespace.isPresent()) { 111 | namespace = Optional.ofNullable(currentClass.getAnnotation(JsonldNamespace.class)); 112 | } 113 | } 114 | return contexts; 115 | } 116 | 117 | private static Class relationType(Field field) { 118 | Class type = field.getType(); 119 | if(Collection.class.isAssignableFrom(type)) { 120 | ParameterizedType parameterizedType = (ParameterizedType) field.getGenericType(); 121 | Type t = parameterizedType.getActualTypeArguments()[0]; 122 | if(Class.class.isAssignableFrom(t.getClass())) { 123 | type = (Class) t; 124 | } 125 | else if(ParameterizedType.class.isAssignableFrom(t.getClass())) { 126 | type = (Class)((ParameterizedType) t).getRawType(); 127 | } 128 | } 129 | if(type.isArray()) { 130 | type = type.getComponentType(); 131 | } 132 | return type; 133 | } 134 | 135 | private static boolean isRelation(Field field) { 136 | Class type = relationType(field); 137 | return Stream.concat(Stream.of(type), 138 | Stream.concat(ClassUtils.getAllSuperclasses(type).stream(), ClassUtils.getAllInterfaces(type).stream())) 139 | .flatMap(currentClass -> Stream.concat( 140 | Stream.of(currentClass.getDeclaredFields()), 141 | Stream.of(currentClass.getDeclaredMethods()) 142 | )) 143 | .anyMatch(p -> p.getAnnotation(JsonldId.class) != null); 144 | } 145 | 146 | private static Map generateNamespaces(Class objType) { 147 | JsonldNamespace[] namespaceAnnotations = objType.getAnnotationsByType(JsonldNamespace.class); 148 | Map namespaces = new HashMap<>(namespaceAnnotations.length); 149 | Arrays.asList(namespaceAnnotations).forEach((ns) -> namespaces.put(ns.name(), ns.uri())); 150 | return namespaces; 151 | } 152 | 153 | 154 | public static Optional multiContext(Optional externalContext, 155 | Optional internalContext) { 156 | if (internalContext.isPresent()) { 157 | return externalContext.isPresent() ? 158 | Optional.of((JsonNode) buildMultiContext(externalContext.get(), internalContext.get())) : 159 | internalContext.map(it -> (JsonNode) it); 160 | } 161 | return externalContext.map(TextNode::valueOf); 162 | } 163 | 164 | private static ArrayNode buildMultiContext(String context, JsonNode generatedContext) { 165 | return JsonNodeFactory.withExactBigDecimals(true).arrayNode().add(context).add(generatedContext); 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/main/java/ioinformarics/oss/jackson/module/jsonld/JsonldGraph.java: -------------------------------------------------------------------------------- 1 | package ioinformarics.oss.jackson.module.jsonld; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import com.fasterxml.jackson.databind.JsonNode; 6 | 7 | /** 8 | * @author Alexander De Leon 9 | */ 10 | public class JsonldGraph extends BeanJsonldResource { 11 | 12 | 13 | JsonldGraph(Iterable graph, JsonNode context, String type, String id) { 14 | super(new JsonldGraphContainer(graph), context, type, id); 15 | } 16 | 17 | 18 | static class JsonldGraphContainer { 19 | @JsonProperty("@graph") 20 | @JsonInclude(JsonInclude.Include.NON_NULL) 21 | public final Iterable graph; 22 | 23 | JsonldGraphContainer(Iterable graph) { 24 | this.graph = graph; 25 | } 26 | } 27 | 28 | public interface Builder { 29 | static JsonldGraphBuilder create() { 30 | return new JsonldGraphBuilder(); 31 | } 32 | } 33 | } 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/main/java/ioinformarics/oss/jackson/module/jsonld/JsonldGraphBuilder.java: -------------------------------------------------------------------------------- 1 | package ioinformarics.oss.jackson.module.jsonld; 2 | 3 | import com.fasterxml.jackson.databind.JsonNode; 4 | import com.fasterxml.jackson.databind.node.TextNode; 5 | 6 | import java.util.ArrayList; 7 | import java.util.Arrays; 8 | import java.util.Optional; 9 | import java.util.function.Function; 10 | 11 | /** 12 | * @author Alexander De Leon 13 | */ 14 | public class JsonldGraphBuilder { 15 | 16 | protected String context; 17 | protected String graphType; 18 | protected String graphId; 19 | protected JsonldResourceBuilder resourceBuilder; 20 | protected Function typeSupplier; 21 | 22 | JsonldGraphBuilder() { 23 | resourceBuilder = new JsonldResourceBuilder(); 24 | } 25 | 26 | public JsonldGraphBuilder context(String context){ 27 | this.context = context; 28 | return this; 29 | } 30 | 31 | public JsonldGraphBuilder type(String type){ 32 | this.graphType = type; 33 | return this; 34 | } 35 | 36 | public JsonldGraphBuilder id(String id){ 37 | this.graphId = id; 38 | return this; 39 | } 40 | 41 | public JsonldGraphBuilder elementId(Function idSupplier){ 42 | this.resourceBuilder.id(idSupplier); 43 | return this; 44 | } 45 | 46 | public JsonldGraphBuilder elementType(Function typeSupplier){ 47 | this.typeSupplier = typeSupplier; 48 | return this; 49 | } 50 | 51 | public JsonldResource build(T ... elements) { 52 | return build(Arrays.asList(elements)); 53 | } 54 | 55 | public JsonldResource build(Iterable elements) { 56 | return new JsonldGraph(elements, Optional.ofNullable(context).map(c -> TextNode.valueOf(c)).orElse(null), graphType, graphId); 57 | 58 | } 59 | 60 | protected String getType(T e) { 61 | return typeSupplier.apply(e); 62 | } 63 | 64 | 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/ioinformarics/oss/jackson/module/jsonld/JsonldModule.java: -------------------------------------------------------------------------------- 1 | package ioinformarics.oss.jackson.module.jsonld; 2 | 3 | import com.fasterxml.jackson.databind.module.SimpleModule; 4 | import ioinformarics.oss.jackson.module.jsonld.internal.JsonldBeanDeserializerModifier; 5 | import ioinformarics.oss.jackson.module.jsonld.internal.JsonldPropertyNamingStrategy; 6 | import ioinformarics.oss.jackson.module.jsonld.internal.JsonldResourceSerializerModifier; 7 | import jdk.nashorn.internal.ir.ObjectNode; 8 | 9 | import java.util.Collections; 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | import java.util.function.Supplier; 13 | 14 | /** 15 | * @author Alexander De Leon 16 | */ 17 | public class JsonldModule extends SimpleModule { 18 | 19 | /** 20 | * Create a JsonldModule configured with a function which supplies the @context structure of your application. 21 | * This constructor is useful if you want to construct your context dynamically. If the context is static is better to use the other constructors of this class. 22 | * 23 | * @param contextSupplier a function from () to Object which supplies the default Jsonld context of your application. 24 | */ 25 | public JsonldModule(Supplier contextSupplier){ 26 | setNamingStrategy(new JsonldPropertyNamingStrategy()); 27 | setDeserializerModifier(new JsonldBeanDeserializerModifier(contextSupplier)); 28 | setSerializerModifier(new JsonldResourceSerializerModifier()); 29 | } 30 | 31 | /** 32 | * Creates a JsonldModule configured with an empty application context. 33 | */ 34 | public JsonldModule(){ 35 | this(() -> Collections.emptyMap()); 36 | } 37 | 38 | /** 39 | * Creates a JsonldModule configured with a Jsonld Context specified in the context argument (Json Object) 40 | * @param context an ObjectNode with the structure of your default @context 41 | */ 42 | public JsonldModule(ObjectNode context){ 43 | this(() -> context); 44 | } 45 | 46 | /** 47 | * Creates a JsonldModule configured with a Jsonld Context specified in the context argument (Map) 48 | * @param context a Map with the structure of your default @context 49 | */ 50 | public JsonldModule(Map context){ 51 | this(() -> context); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/ioinformarics/oss/jackson/module/jsonld/JsonldResource.java: -------------------------------------------------------------------------------- 1 | package ioinformarics.oss.jackson.module.jsonld; 2 | 3 | /** 4 | * @author Alexander De Leon 5 | */ 6 | public interface JsonldResource { 7 | public interface Builder { 8 | static JsonldResourceBuilder create() { 9 | return new JsonldResourceBuilder(); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/ioinformarics/oss/jackson/module/jsonld/JsonldResourceBuilder.java: -------------------------------------------------------------------------------- 1 | package ioinformarics.oss.jackson.module.jsonld; 2 | 3 | import com.fasterxml.jackson.databind.JsonNode; 4 | import ioinformarics.oss.jackson.module.jsonld.annotation.JsonldType; 5 | import ioinformarics.oss.jackson.module.jsonld.annotation.JsonldTypeFromJavaClass; 6 | import ioinformarics.oss.jackson.module.jsonld.internal.AnnotationConstants; 7 | 8 | import java.util.Map; 9 | import java.util.Optional; 10 | import java.util.function.Function; 11 | 12 | /** 13 | * @author Alexander De Leon 14 | */ 15 | public class JsonldResourceBuilder { 16 | 17 | private String _context; 18 | private String _type; 19 | private String _id; 20 | private Function _idSupplier; 21 | 22 | JsonldResourceBuilder(){ 23 | } 24 | 25 | public JsonldResourceBuilder context(String context){ 26 | this._context = context; 27 | return this; 28 | } 29 | 30 | public JsonldResourceBuilder type(String type){ 31 | this._type = type; 32 | return this; 33 | } 34 | 35 | public JsonldResourceBuilder id(String id){ 36 | this._id = id; 37 | return this; 38 | } 39 | 40 | public JsonldResourceBuilder id(Function idSupplier){ 41 | this._idSupplier = idSupplier; 42 | return this; 43 | } 44 | 45 | public JsonldResource build(T scopedObj) { 46 | if(scopedObj == null){ 47 | return null; 48 | } 49 | if(Map.class.isAssignableFrom(scopedObj.getClass())){ 50 | return new MapJsonldResource((Map)scopedObj, getContext(scopedObj).orElse(null), getType(scopedObj), getId(scopedObj)); 51 | } 52 | return new BeanJsonldResource(scopedObj, getContext(scopedObj).orElse(null), getType(scopedObj), getId(scopedObj)); 53 | } 54 | 55 | protected Optional getContext(T scopedObj) { 56 | return JsonldContextFactory.multiContext(Optional.ofNullable(_context), JsonldContextFactory.fromAnnotations(scopedObj)); 57 | } 58 | 59 | protected String getId(T scopedObj){ 60 | return Optional.ofNullable(_id).orElse(Optional.ofNullable(_idSupplier).map(f -> f.apply(scopedObj)).orElse(null)); 61 | } 62 | 63 | protected String getType(T scopedObj) { 64 | return Optional.ofNullable(_type).orElse(dynamicTypeLookup(scopedObj.getClass())); 65 | } 66 | 67 | static String dynamicTypeLookup(Class objType){ 68 | return Optional.ofNullable(objType.getAnnotation(JsonldType.class)) 69 | .map(JsonldType::value) 70 | .orElse(typeFromJavaClass(objType)); 71 | } 72 | 73 | static String typeFromJavaClass(Class objType) { 74 | return Optional.ofNullable(objType.getAnnotation(JsonldTypeFromJavaClass.class)) 75 | .map((t) -> { 76 | String prefix = t.namespace(); 77 | if(prefix.equals(AnnotationConstants.UNASSIGNED)) { 78 | prefix = t.namespacePrefix().equals(AnnotationConstants.UNASSIGNED) ? "" : t.namespacePrefix() + ":"; 79 | } 80 | return prefix + objType.getSimpleName(); 81 | 82 | }) 83 | .orElse(null); 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/ioinformarics/oss/jackson/module/jsonld/MapJsonldResource.java: -------------------------------------------------------------------------------- 1 | package ioinformarics.oss.jackson.module.jsonld; 2 | 3 | import com.fasterxml.jackson.databind.JsonNode; 4 | import com.fasterxml.jackson.databind.node.ArrayNode; 5 | import com.fasterxml.jackson.databind.node.ObjectNode; 6 | 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | /** 11 | * @author Alexander De Leon 12 | */ 13 | public class MapJsonldResource extends HashMap implements JsonldResource { 14 | public MapJsonldResource(Map map) { 15 | super(map); 16 | } 17 | 18 | public MapJsonldResource(Map scopedObj, JsonNode context, String type, String id){ 19 | this(scopedObj); 20 | if (isNotEmpty(context)) { 21 | put("@context", context); 22 | } 23 | if(id != null) { 24 | put("@id", id); 25 | } 26 | if(type != null) { 27 | put("@type", type); 28 | } 29 | } 30 | 31 | private boolean isNotEmpty(JsonNode context) { 32 | if(context == null){ 33 | return false; 34 | } 35 | if(context.isArray()){ 36 | return ((ArrayNode)context).size() != 0; 37 | } 38 | if(context.isObject()){ 39 | return ((ObjectNode)context).size() != 0; 40 | } 41 | return true; 42 | } 43 | 44 | } 45 | 46 | -------------------------------------------------------------------------------- /src/main/java/ioinformarics/oss/jackson/module/jsonld/annotation/JsonldId.java: -------------------------------------------------------------------------------- 1 | package ioinformarics.oss.jackson.module.jsonld.annotation; 2 | 3 | import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | 6 | import java.lang.annotation.ElementType; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.RetentionPolicy; 9 | import java.lang.annotation.Target; 10 | 11 | /** 12 | * @author Alexander De Leon 13 | */ 14 | @Retention(RetentionPolicy.RUNTIME) 15 | @Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER}) 16 | @JacksonAnnotationsInside 17 | @JsonProperty("@id") 18 | public @interface JsonldId { 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/ioinformarics/oss/jackson/module/jsonld/annotation/JsonldLink.java: -------------------------------------------------------------------------------- 1 | package ioinformarics.oss.jackson.module.jsonld.annotation; 2 | 3 | import java.lang.annotation.*; 4 | import java.util.Map; 5 | 6 | /** 7 | * @author Alexander De Leon 8 | */ 9 | @Retention(RetentionPolicy.RUNTIME) 10 | @Target(ElementType.TYPE) 11 | @Inherited 12 | @Repeatable(JsonldLinks.class) 13 | public @interface JsonldLink { 14 | String rel(); 15 | String name(); 16 | String href(); 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/ioinformarics/oss/jackson/module/jsonld/annotation/JsonldLinks.java: -------------------------------------------------------------------------------- 1 | package ioinformarics.oss.jackson.module.jsonld.annotation; 2 | 3 | import java.lang.annotation.*; 4 | 5 | /** 6 | * @author Alexander De Leon 7 | */ 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target(ElementType.TYPE) 10 | @Inherited 11 | public @interface JsonldLinks { 12 | JsonldLink[] value(); 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/ioinformarics/oss/jackson/module/jsonld/annotation/JsonldNamespace.java: -------------------------------------------------------------------------------- 1 | package ioinformarics.oss.jackson.module.jsonld.annotation; 2 | 3 | import java.lang.annotation.*; 4 | 5 | /** 6 | * @author Alexander De Leon (alex.deleon@devialab.com) 7 | */ 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target(ElementType.TYPE) 10 | @Inherited 11 | public @interface JsonldNamespace { 12 | String name(); 13 | String uri(); 14 | boolean applyToProperties() default true; 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/ioinformarics/oss/jackson/module/jsonld/annotation/JsonldProperty.java: -------------------------------------------------------------------------------- 1 | package ioinformarics.oss.jackson.module.jsonld.annotation; 2 | 3 | import java.lang.annotation.*; 4 | 5 | /** 6 | * @author Alexander De Leon 7 | */ 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER}) 10 | @Inherited 11 | public @interface JsonldProperty { 12 | String value(); 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/ioinformarics/oss/jackson/module/jsonld/annotation/JsonldResource.java: -------------------------------------------------------------------------------- 1 | package ioinformarics.oss.jackson.module.jsonld.annotation; 2 | 3 | import java.lang.annotation.*; 4 | 5 | /** 6 | * @author Alexander De Leon (alex.deleon@devialab.com) 7 | */ 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target(ElementType.TYPE) 10 | @Inherited 11 | public @interface JsonldResource { 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/ioinformarics/oss/jackson/module/jsonld/annotation/JsonldType.java: -------------------------------------------------------------------------------- 1 | package ioinformarics.oss.jackson.module.jsonld.annotation; 2 | 3 | 4 | import java.lang.annotation.*; 5 | 6 | /** 7 | * @author Alexander De Leon 8 | */ 9 | @Retention(RetentionPolicy.RUNTIME) 10 | @Target(ElementType.TYPE) 11 | @Inherited 12 | @JsonldResource 13 | public @interface JsonldType { 14 | String value(); 15 | } -------------------------------------------------------------------------------- /src/main/java/ioinformarics/oss/jackson/module/jsonld/annotation/JsonldTypeFromJavaClass.java: -------------------------------------------------------------------------------- 1 | package ioinformarics.oss.jackson.module.jsonld.annotation; 2 | 3 | import ioinformarics.oss.jackson.module.jsonld.internal.AnnotationConstants; 4 | 5 | import java.lang.annotation.*; 6 | 7 | /** 8 | * @author Alexander De Leon (alex.deleon@devialab.com) 9 | */ 10 | @Retention(RetentionPolicy.RUNTIME) 11 | @Target(ElementType.TYPE) 12 | @Inherited 13 | @JsonldResource 14 | public @interface JsonldTypeFromJavaClass { 15 | String namespace() default AnnotationConstants.UNASSIGNED; 16 | String namespacePrefix() default AnnotationConstants.UNASSIGNED; 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/ioinformarics/oss/jackson/module/jsonld/internal/AnnotationConstants.java: -------------------------------------------------------------------------------- 1 | package ioinformarics.oss.jackson.module.jsonld.internal; 2 | 3 | /** 4 | * @author Alexander De Leon (alex.deleon@devialab.com) 5 | */ 6 | public interface AnnotationConstants { 7 | String UNASSIGNED = "[unassigned]"; 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/ioinformarics/oss/jackson/module/jsonld/internal/JsonldBeanDeserializerModifier.java: -------------------------------------------------------------------------------- 1 | package ioinformarics.oss.jackson.module.jsonld.internal; 2 | 3 | import com.fasterxml.jackson.core.JsonGenerationException; 4 | import com.fasterxml.jackson.core.JsonParser; 5 | import com.fasterxml.jackson.core.JsonProcessingException; 6 | import com.fasterxml.jackson.core.JsonToken; 7 | import com.fasterxml.jackson.databind.*; 8 | import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier; 9 | import com.fasterxml.jackson.databind.deser.std.DelegatingDeserializer; 10 | import com.github.jsonldjava.core.JsonLdError; 11 | import com.github.jsonldjava.core.JsonLdOptions; 12 | import com.github.jsonldjava.core.JsonLdProcessor; 13 | import ioinformarics.oss.jackson.module.jsonld.annotation.JsonldType; 14 | 15 | import java.io.IOException; 16 | import java.util.List; 17 | import java.util.Map; 18 | import java.util.function.Supplier; 19 | 20 | /** 21 | * @author Alexander De Leon 22 | */ 23 | public class JsonldBeanDeserializerModifier extends BeanDeserializerModifier { 24 | 25 | private final ObjectMapper mapper = new ObjectMapper(); 26 | private final Supplier contextSupplier; 27 | 28 | public JsonldBeanDeserializerModifier(Supplier contextSupplier){ 29 | this.contextSupplier = contextSupplier; 30 | } 31 | 32 | @Override 33 | public JsonDeserializer modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer deserializer) { 34 | if(beanDesc.getClassInfo().hasAnnotation(JsonldType.class)){ 35 | return new JsonldDelegatingDeserializer(deserializer); 36 | } 37 | return deserializer; 38 | } 39 | 40 | 41 | class JsonldDelegatingDeserializer extends DelegatingDeserializer { 42 | 43 | public JsonldDelegatingDeserializer(JsonDeserializer delegatee) { 44 | super(delegatee); 45 | } 46 | 47 | @Override 48 | public Object deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { 49 | Object input = parseJsonldObject(jp); 50 | if(input == null) { 51 | return super.deserialize(jp, ctxt); 52 | } 53 | try { 54 | JsonLdOptions options = new JsonLdOptions(); 55 | Object context = contextSupplier.get(); 56 | if(context instanceof JsonNode){ 57 | context = parseJsonldObject(initParser(mapper.treeAsTokens((JsonNode)context))); 58 | } 59 | Object obj = JsonLdProcessor.compact(input, context, options); 60 | JsonParser newParser = initParser(mapper.getFactory().createParser(mapper.valueToTree(obj).toString())); 61 | return super.deserialize(newParser, ctxt); 62 | } catch (JsonLdError e) { 63 | throw new JsonGenerationException("Failed to flatten json-ld", e); 64 | } 65 | } 66 | 67 | @Override 68 | protected JsonDeserializer newDelegatingInstance(JsonDeserializer newDelegatee) { 69 | return new JsonldDelegatingDeserializer(newDelegatee); 70 | } 71 | 72 | private Object parseJsonldObject(JsonParser jp) throws IOException { 73 | Object rval = null; 74 | final JsonToken initialToken = jp.getCurrentToken(); 75 | 76 | if (initialToken == JsonToken.START_ARRAY) { 77 | jp.setCodec(mapper); 78 | rval = jp.readValueAs(List.class); 79 | } else if (initialToken == JsonToken.START_OBJECT) { 80 | jp.setCodec(mapper); 81 | rval = jp.readValueAs(Map.class); 82 | } else if (initialToken == JsonToken.VALUE_STRING) { 83 | jp.setCodec(mapper); 84 | rval = jp.readValueAs(String.class); 85 | } else if (initialToken == JsonToken.VALUE_FALSE || initialToken == JsonToken.VALUE_TRUE) { 86 | jp.setCodec(mapper); 87 | rval = jp.readValueAs(Boolean.class); 88 | } else if (initialToken == JsonToken.VALUE_NUMBER_FLOAT 89 | || initialToken == JsonToken.VALUE_NUMBER_INT) { 90 | jp.setCodec(mapper); 91 | rval = jp.readValueAs(Number.class); 92 | } else if (initialToken == JsonToken.VALUE_NULL) { 93 | rval = null; 94 | } 95 | return rval; 96 | } 97 | } 98 | 99 | private static JsonParser initParser(JsonParser jp) throws IOException{ 100 | jp.nextToken(); //put the parser at the start token 101 | return jp; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/main/java/ioinformarics/oss/jackson/module/jsonld/internal/JsonldPropertyNamingStrategy.java: -------------------------------------------------------------------------------- 1 | package ioinformarics.oss.jackson.module.jsonld.internal; 2 | 3 | import com.fasterxml.jackson.databind.DeserializationConfig; 4 | import com.fasterxml.jackson.databind.PropertyNamingStrategy; 5 | import com.fasterxml.jackson.databind.cfg.MapperConfig; 6 | import com.fasterxml.jackson.databind.introspect.AnnotatedField; 7 | import com.fasterxml.jackson.databind.introspect.AnnotatedMember; 8 | import com.fasterxml.jackson.databind.introspect.AnnotatedMethod; 9 | import com.fasterxml.jackson.databind.introspect.AnnotatedParameter; 10 | import ioinformarics.oss.jackson.module.jsonld.annotation.JsonldProperty; 11 | 12 | import java.util.Optional; 13 | 14 | /** 15 | * @author Alexander De Leon 16 | */ 17 | public class JsonldPropertyNamingStrategy extends PropertyNamingStrategy { 18 | 19 | @Override 20 | public String nameForField(MapperConfig config, AnnotatedField field, String defaultName) { 21 | String name = config instanceof DeserializationConfig? jsonldName(field): null; 22 | return Optional.ofNullable(name).orElse(super.nameForField(config, field, defaultName)); 23 | } 24 | 25 | @Override 26 | public String nameForGetterMethod(MapperConfig config, AnnotatedMethod method, String defaultName) { 27 | String name = config instanceof DeserializationConfig? jsonldName(method): null; 28 | return Optional.ofNullable(name).orElse(super.nameForGetterMethod(config, method, defaultName)); 29 | } 30 | 31 | @Override 32 | public String nameForSetterMethod(MapperConfig config, AnnotatedMethod method, String defaultName) { 33 | String name = config instanceof DeserializationConfig? jsonldName(method): null; 34 | return Optional.ofNullable(name).orElse(super.nameForSetterMethod(config, method, defaultName)); 35 | } 36 | 37 | @Override 38 | public String nameForConstructorParameter(MapperConfig config, AnnotatedParameter ctorParam, String defaultName) { 39 | String name = config instanceof DeserializationConfig? jsonldName(ctorParam): null; 40 | return Optional.ofNullable(name).orElse(super.nameForConstructorParameter(config, ctorParam, defaultName)); 41 | } 42 | 43 | private String jsonldName(AnnotatedMember member){ 44 | JsonldProperty jsonldProperty = member.getAnnotation(JsonldProperty.class); 45 | return jsonldProperty != null? jsonldProperty.value() : null; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/ioinformarics/oss/jackson/module/jsonld/internal/JsonldResourceSerializer.java: -------------------------------------------------------------------------------- 1 | package ioinformarics.oss.jackson.module.jsonld.internal; 2 | 3 | import com.fasterxml.jackson.core.JsonGenerationException; 4 | import com.fasterxml.jackson.core.JsonGenerator; 5 | import com.fasterxml.jackson.databind.SerializerProvider; 6 | import com.fasterxml.jackson.databind.node.ObjectNode; 7 | import com.fasterxml.jackson.databind.ser.BeanSerializer; 8 | import com.fasterxml.jackson.databind.ser.std.BeanSerializerBase; 9 | import ioinformarics.oss.jackson.module.jsonld.annotation.JsonldLink; 10 | import ioinformarics.oss.jackson.module.jsonld.util.JsonldResourceUtils; 11 | 12 | import java.io.IOException; 13 | import java.lang.annotation.Annotation; 14 | import java.util.*; 15 | 16 | /** 17 | * @author Alexander De Leon (alex.deleon@devialab.com) 18 | */ 19 | public class JsonldResourceSerializer extends BeanSerializer { 20 | 21 | public JsonldResourceSerializer(BeanSerializerBase src) { 22 | super(src); 23 | } 24 | 25 | @Override 26 | protected void serializeFields(Object bean, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonGenerationException { 27 | Optional type = JsonldResourceUtils.dynamicTypeLookup(bean.getClass()); 28 | if(type.isPresent()) { 29 | jgen.writeStringField("@type", type.get()); 30 | } 31 | Optional context = JsonldResourceUtils.getContext(bean); 32 | if(context.isPresent()) { 33 | jgen.writeObjectField("@context", context.get()); 34 | } 35 | super.serializeFields(bean, jgen, provider); 36 | getLinks(bean).ifPresent(linksMap -> 37 | linksMap.forEach((key, value) -> { 38 | try { 39 | jgen.writeStringField(key, value); 40 | } catch (Exception e) { 41 | throw new RuntimeException(e); 42 | } 43 | })); 44 | } 45 | 46 | protected Optional> getLinks(Object resource) { 47 | Map linksNodes = null; 48 | Class beanType = resource.getClass(); 49 | JsonldLink[] links = beanType.getAnnotationsByType(JsonldLink.class); 50 | if(links != null){ 51 | linksNodes = new HashMap<>(links.length); 52 | for(int i=0; i < links.length; i++){ 53 | linksNodes.put(links[i].name(), links[i].href()); 54 | } 55 | } 56 | return Optional.ofNullable(linksNodes); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/ioinformarics/oss/jackson/module/jsonld/internal/JsonldResourceSerializerModifier.java: -------------------------------------------------------------------------------- 1 | package ioinformarics.oss.jackson.module.jsonld.internal; 2 | 3 | import com.fasterxml.jackson.databind.*; 4 | import com.fasterxml.jackson.databind.ser.BeanSerializerModifier; 5 | import com.fasterxml.jackson.databind.ser.std.BeanSerializerBase; 6 | import ioinformarics.oss.jackson.module.jsonld.annotation.JsonldResource; 7 | import ioinformarics.oss.jackson.module.jsonld.util.AnnotationsUtils; 8 | 9 | /** 10 | * @author Alexander De Leon 11 | */ 12 | public class JsonldResourceSerializerModifier extends BeanSerializerModifier { 13 | 14 | 15 | @Override 16 | public JsonSerializer modifySerializer(SerializationConfig config, BeanDescription beanDesc, JsonSerializer serializer) { 17 | if(AnnotationsUtils.isAnnotationPresent(beanDesc.getBeanClass(), JsonldResource.class) && serializer instanceof BeanSerializerBase){ 18 | return new JsonldResourceSerializer((BeanSerializerBase) serializer); 19 | } 20 | return serializer; 21 | } 22 | 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/ioinformarics/oss/jackson/module/jsonld/util/AnnotationsUtils.java: -------------------------------------------------------------------------------- 1 | package ioinformarics.oss.jackson.module.jsonld.util; 2 | 3 | import java.lang.annotation.Annotation; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | 7 | /** 8 | * @author Alexander De Leon (alex.deleon@devialab.com) 9 | */ 10 | public abstract class AnnotationsUtils { 11 | 12 | public static boolean isAnnotationPresent(Class type, Class annotationType) { 13 | return isAnnotationPresent(type, annotationType, new ArrayList<>()); 14 | } 15 | 16 | protected static boolean isAnnotationPresent(Class type, Class annotationType, List> ignore) { 17 | if(type.isAnnotationPresent(annotationType)) { 18 | return true; 19 | } 20 | if(type.getAnnotations().length == 0) { 21 | return false; 22 | } 23 | for(Annotation a : type.getAnnotations()) { 24 | if(!ignore.contains(a.annotationType())) { 25 | ignore.add(type); 26 | if(isAnnotationPresent(a.annotationType(), annotationType, ignore)) { 27 | return true; 28 | } 29 | } 30 | } 31 | return false; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/ioinformarics/oss/jackson/module/jsonld/util/JsonUtils.java: -------------------------------------------------------------------------------- 1 | package ioinformarics.oss.jackson.module.jsonld.util; 2 | 3 | import com.fasterxml.jackson.databind.JsonNode; 4 | import com.fasterxml.jackson.databind.node.ObjectNode; 5 | 6 | import java.util.Iterator; 7 | 8 | /** 9 | * @author Alexander De Leon (alex.deleon@devialab.com) 10 | */ 11 | public abstract class JsonUtils { 12 | 13 | public static JsonNode merge(JsonNode mainNode, JsonNode updateNode) { 14 | 15 | Iterator fieldNames = updateNode.fieldNames(); 16 | while (fieldNames.hasNext()) { 17 | 18 | String fieldName = fieldNames.next(); 19 | JsonNode jsonNode = mainNode.get(fieldName); 20 | // if field exists and is an embedded object 21 | if (jsonNode != null && jsonNode.isObject()) { 22 | merge(jsonNode, updateNode.get(fieldName)); 23 | } 24 | else { 25 | if (mainNode instanceof ObjectNode) { 26 | // Overwrite field 27 | JsonNode value = updateNode.get(fieldName); 28 | ((ObjectNode) mainNode).set(fieldName, value); 29 | } 30 | } 31 | 32 | } 33 | 34 | return mainNode; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/ioinformarics/oss/jackson/module/jsonld/util/JsonldResourceUtils.java: -------------------------------------------------------------------------------- 1 | package ioinformarics.oss.jackson.module.jsonld.util; 2 | 3 | import com.fasterxml.jackson.databind.node.ObjectNode; 4 | import ioinformarics.oss.jackson.module.jsonld.JsonldContextFactory; 5 | import ioinformarics.oss.jackson.module.jsonld.annotation.JsonldType; 6 | import ioinformarics.oss.jackson.module.jsonld.annotation.JsonldTypeFromJavaClass; 7 | import ioinformarics.oss.jackson.module.jsonld.internal.AnnotationConstants; 8 | 9 | import java.util.Optional; 10 | 11 | /** 12 | * @author Alexander De Leon (alex.deleon@devialab.com) 13 | */ 14 | public abstract class JsonldResourceUtils { 15 | 16 | public static Optional getContext(Object scopedObj) { 17 | return JsonldContextFactory.fromAnnotations(scopedObj); 18 | } 19 | 20 | public static Optional dynamicTypeLookup(Class objType){ 21 | Optional typeFromAnnotation = Optional.ofNullable(objType.getAnnotation(JsonldType.class)) 22 | .map(JsonldType::value); 23 | return typeFromAnnotation.isPresent() ? typeFromAnnotation : typeFromJavaClass(objType); 24 | } 25 | 26 | private static Optional typeFromJavaClass(Class objType) { 27 | return Optional.ofNullable(objType.getAnnotation(JsonldTypeFromJavaClass.class)) 28 | .map((t) -> { 29 | String prefix = t.namespace(); 30 | if(prefix.equals(AnnotationConstants.UNASSIGNED)) { 31 | prefix = t.namespacePrefix().equals(AnnotationConstants.UNASSIGNED) ? "" : t.namespacePrefix() + ":"; 32 | } 33 | return prefix + objType.getSimpleName(); 34 | 35 | }); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/test/java/ioinformarics/oss/jackson/module/jsonld/JsonldContextFactoryTest.java: -------------------------------------------------------------------------------- 1 | package ioinformarics.oss.jackson.module.jsonld; 2 | 3 | import com.fasterxml.jackson.databind.node.ObjectNode; 4 | import com.fasterxml.jackson.databind.node.TextNode; 5 | import ioinformarics.oss.jackson.module.jsonld.testobjects.Child; 6 | import static org.junit.Assert.*; 7 | import static org.hamcrest.Matchers.*; 8 | import org.junit.Test; 9 | 10 | import java.util.*; 11 | 12 | 13 | public class JsonldContextFactoryTest { 14 | 15 | @Test 16 | public void fromAnnotationsGeneratesContextsForAncestorsAsWell() { 17 | final Child child = new Child(); 18 | final Optional result = JsonldContextFactory.fromAnnotations(child); 19 | assertTrue(result.isPresent()); 20 | final Iterator fields = result.get().fieldNames(); 21 | int counter = 0; 22 | final Set fieldNames = new HashSet<>(Arrays.asList("name", "description", "version")); 23 | while (fields.hasNext()) { 24 | counter++; 25 | final String fieldName = fields.next(); 26 | assertTrue(fieldNames.contains(fieldName)); 27 | } 28 | assertEquals(fieldNames.size(), counter); 29 | } 30 | 31 | @Test 32 | public void testContextFromPackage() { 33 | ObjectNode context = JsonldContextFactory.fromPackage("ioinformarics.oss.jackson.module.jsonld.testobjects"); 34 | System.out.println("Context: "+context); 35 | ObjectNode innerContext = (ObjectNode) context.get("@context"); 36 | assertNotNull(innerContext); 37 | // from: ioinformarics.oss.jackson.module.jsonld.testobjects.Parent 38 | assertThat(innerContext.get("name"), is(TextNode.valueOf("http://www.w3.org/2000/01/rdf-schema#label"))); 39 | // from: ioinformarics.oss.jackson.module.jsonld.testobjects.internal.TestObject 40 | assertThat(innerContext.get("url"), is(TextNode.valueOf("http://schema.org/url"))); 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /src/test/java/ioinformarics/oss/jackson/module/jsonld/JsonldModuleTest.java: -------------------------------------------------------------------------------- 1 | package ioinformarics.oss.jackson.module.jsonld; 2 | 3 | import com.fasterxml.jackson.core.JsonGenerationException; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import ioinformarics.oss.jackson.module.jsonld.annotation.JsonldId; 6 | import ioinformarics.oss.jackson.module.jsonld.annotation.JsonldNamespace; 7 | import ioinformarics.oss.jackson.module.jsonld.annotation.JsonldProperty; 8 | import ioinformarics.oss.jackson.module.jsonld.annotation.JsonldType; 9 | import org.junit.Test; 10 | 11 | import java.io.IOException; 12 | 13 | /** 14 | * @author Alexander De Leon 15 | */ 16 | public class JsonldModuleTest { 17 | 18 | 19 | @Test 20 | public void testSerializeBean() throws IOException { 21 | ObjectMapper objectMapper = new ObjectMapper(); 22 | objectMapper.registerModule(new JsonldModule()); 23 | 24 | Person alex = new Person(); 25 | alex.id = "mailto:me@alexdeleon.name"; 26 | alex.name = "Alex De Leon"; 27 | alex.jobtitle = "Software Developer"; 28 | alex.url = "http://alexdeleon.name"; 29 | 30 | objectMapper.writer().writeValue(System.out, alex); 31 | 32 | } 33 | 34 | @Test 35 | public void testSerializeGraph() throws IOException { 36 | ObjectMapper objectMapper = new ObjectMapper(); 37 | objectMapper.registerModule(new JsonldModule()); 38 | 39 | Person alex = new Person(); 40 | alex.id = "mailto:me@alexdeleon.name"; 41 | alex.name = "Alex De Leon"; 42 | alex.jobtitle = "Software Developer"; 43 | alex.url = "http://alexdeleon.name"; 44 | 45 | Object graph = JsonldGraph.Builder.create().id("http://example.graph").build(alex); 46 | 47 | objectMapper.writer().writeValue(System.out, graph); 48 | 49 | } 50 | 51 | 52 | @JsonldNamespace(name = "s", uri = "http://schema.org/") 53 | @JsonldType("s:Person") 54 | public class Person { 55 | @JsonldId 56 | public String id; 57 | @JsonldProperty("http://schema.org/name") 58 | public String name; 59 | @JsonldProperty("http://schema.org/jobTitle") 60 | public String jobtitle; 61 | @JsonldProperty("http://schema.org/url") 62 | public String url; 63 | 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/test/java/ioinformarics/oss/jackson/module/jsonld/testobjects/Child.java: -------------------------------------------------------------------------------- 1 | package ioinformarics.oss.jackson.module.jsonld.testobjects; 2 | 3 | import ioinformarics.oss.jackson.module.jsonld.annotation.JsonldProperty; 4 | import ioinformarics.oss.jackson.module.jsonld.annotation.JsonldType; 5 | 6 | /** 7 | * @author Alexander De Leon (alex.deleon@devialab.com) 8 | */ 9 | @JsonldType("http://example.com/schema#Child") 10 | public class Child extends Parent { 11 | 12 | @JsonldProperty("http://www.w3.org/2000/01/rdf-schema#comment") 13 | String description; 14 | 15 | @JsonldProperty("http://example.com/schema#version") 16 | Long version; 17 | } 18 | -------------------------------------------------------------------------------- /src/test/java/ioinformarics/oss/jackson/module/jsonld/testobjects/Parent.java: -------------------------------------------------------------------------------- 1 | package ioinformarics.oss.jackson.module.jsonld.testobjects; 2 | 3 | import ioinformarics.oss.jackson.module.jsonld.annotation.JsonldId; 4 | import ioinformarics.oss.jackson.module.jsonld.annotation.JsonldProperty; 5 | import ioinformarics.oss.jackson.module.jsonld.annotation.JsonldType; 6 | 7 | /** 8 | * @author Alexander De Leon (alex.deleon@devialab.com) 9 | */ 10 | @JsonldType("http://example.com/schema#Parent") 11 | public class Parent { 12 | 13 | @JsonldId 14 | Integer id; 15 | 16 | @JsonldProperty("http://www.w3.org/2000/01/rdf-schema#label") 17 | String name; 18 | } 19 | -------------------------------------------------------------------------------- /src/test/java/ioinformarics/oss/jackson/module/jsonld/testobjects/internal/TestObject.java: -------------------------------------------------------------------------------- 1 | package ioinformarics.oss.jackson.module.jsonld.testobjects.internal; 2 | 3 | import ioinformarics.oss.jackson.module.jsonld.annotation.JsonldId; 4 | import ioinformarics.oss.jackson.module.jsonld.annotation.JsonldProperty; 5 | import ioinformarics.oss.jackson.module.jsonld.annotation.JsonldResource; 6 | 7 | /** 8 | * @author Alexander De Leon (alex.deleon@devialab.com) 9 | */ 10 | @JsonldResource 11 | public class TestObject { 12 | 13 | @JsonldId 14 | public String id; 15 | 16 | @JsonldProperty("http://schema.org/url") 17 | public String url; 18 | 19 | @JsonldProperty("http://www.w3.org/2000/01/rdf-schema#label") 20 | String name; 21 | } 22 | --------------------------------------------------------------------------------