extractMapTypes(Field field) {
+ ParameterizedType pt = (ParameterizedType) field.getGenericType();
+ String typeName = pt.getTypeName();
+ typeName = typeName.substring(typeName.indexOf(KehioUtils.START_CLASS_TOKEN)+1, typeName.indexOf(KehioUtils.END_CLASS_TOKEN));
+ String[] mapTypes = typeName.split(", ");
+ return Map.entry(mapTypes[0], mapTypes[1]);
+ }
+
+ protected static Class> extractMapValueType(Field field){
+ Class> clazz = null;
+ try {
+ String mapType = KehioUtils.extractMapTypes(field).getValue();
+ clazz = Class.forName(mapType);
+ }catch(Exception e) {
+ throw new IllegalArgumentException(e.toString());
+ }
+ return clazz;
+ }
+
+ // other
+
+ public static boolean isURI(String url){
+ Boolean isURL = false;
+ try {
+ URI uri = new URI(url);
+ isURL = uri.isAbsolute() ;//contains(":"); // true && (true is implicit as far as no exception is thrown)
+ } catch (Exception exception) {
+ isURL = false;
+ }
+ return isURL;
+ }
+
+ public static boolean isValidResource(String url) {
+ Boolean isURL = false;
+ try {
+ ResourceFactory.createResource(url);
+ isURL = true;
+ }catch(Exception e) {
+ isURL = false;
+ }
+ return isURL;
+ }
+
+ // -- Instantiation methods
+
+ /**
+ * This method receives a field ({@link Collection}, {@link Set}, {@link List}, {@link Queue}, {@link Deque}, or {@link Sortedset}) belonging to an object, and also, a generic collection containing its instantiating values.
+ * As a result, this method instantiates the field of the object with the provided collection.
+ * @param field the field ({@link Collection}, {@link Set}, {@link List}, {@link Queue}, {@link Deque}, or {@link Sortedset})
+ * @param instantiatedCollection the instantiated collection
+ * @param object the generic object that has the provided field and which will be instantiated with the provided collection
+ * @throws IllegalArgumentException in case that the field is not one of {@link Collection}, {@link Set}, {@link List}, {@link Queue}, {@link Deque}, or {@link Sortedset}
+ * @throws IllegalAccessException in case the field does not belongs to the provided object
+ */
+ protected static void instantiateCollection(Field field, Collection instantiatedCollection, Object object) throws IllegalArgumentException, IllegalAccessException {
+ Type fieldType = field.getType();
+ Collection collection = null;
+ if(fieldType.equals(Collection.class) || fieldType.equals(List.class)) {
+ collection = instantiatedCollection;
+ }else if(fieldType.equals(Set.class)) {
+ collection = new HashSet<>(instantiatedCollection);
+ }else if(fieldType.equals(Queue.class) || fieldType.equals(Deque.class)) {
+ collection = new LinkedList<>(instantiatedCollection);
+ }else if(fieldType.equals(SortedSet.class)) {
+ collection = new TreeSet<>(instantiatedCollection);
+ }else{
+ String illegalMessage = KehioUtils.concatStrings("@RdfDatatype and/or @RdfObject can be used with Collections, List, Set, SortedSet, Queue, Dequeue. Please create an issue to add more java data structures. Please review annotation for attribute ",field.getName());
+ throw new IllegalArgumentException(illegalMessage);
+ }
+ if(collection!=null && !collection.isEmpty())
+ field.set(object, collection);
+ }
+
+ protected static void instantiateField(Field field, Object object, Object fieldValue) throws IllegalArgumentException, IllegalAccessException {
+ if(fieldValue!=null)
+ field.set(object, fieldValue);
+ }
+
+ protected static Object instantiatedField(Field field, Object object) throws IllegalArgumentException, IllegalAccessException {
+ return field.get(object);
+ }
+
+
+
+
+}
diff --git a/src/main/java/kehio/mapper/RdfContainerMapper.java b/src/main/java/kehio/mapper/RdfContainerMapper.java
new file mode 100644
index 0000000..7263867
--- /dev/null
+++ b/src/main/java/kehio/mapper/RdfContainerMapper.java
@@ -0,0 +1,324 @@
+package kehio.mapper;
+
+import java.lang.reflect.Field;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.apache.jena.rdf.model.Model;
+import org.apache.jena.rdf.model.Property;
+import org.apache.jena.rdf.model.RDFNode;
+import org.apache.jena.rdf.model.Resource;
+import org.apache.jena.rdf.model.ResourceFactory;
+import org.apache.jena.rdf.model.Statement;
+import org.apache.jena.rdf.model.StmtIterator;
+
+import kehio.annotations.RdfContainer;
+import kehio.annotations.RdfUrlMap;
+
+
+class RdfContainerMapper implements RdfMapper{
+
+ protected Set forbidenRdfProperties;
+
+ public void setPropertiesNotToContain(Set processedProperties) {
+ this.forbidenRdfProperties = processedProperties;
+ }
+
+ @Override
+ public boolean hasProcesableAnnotation(Field field) {
+ return field.isAnnotationPresent(RdfContainer.class);
+ }
+
+ @Override
+ public Property fromRdfToObject(Field field, Object object, Model model, Resource subject) throws IllegalArgumentException, IllegalAccessException, URISyntaxException {
+ field.setAccessible(true);
+
+ PropertiesContainerAnotation annotation = new PropertiesContainerAnotation(field);
+ Map collectionOfTriples = populateMapFromRDF(annotation, model, subject, true);
+
+ field.set(object, collectionOfTriples);
+
+ return null;
+ }
+
+
+
+ private Map populateMapFromRDF(PropertiesContainerAnotation annotation, Model model, Resource subject, boolean firstCall) {
+ Set ignore = annotation.getPropertiesToIgnore();
+ Map collectionOfTriples = new HashMap<>();
+ StmtIterator stIterator = model.listStatements(subject, null, (RDFNode) null);
+ while(stIterator.hasNext()) {
+ Statement st = stIterator.next();
+
+ List inverseAliases = annotation.inverseAlias(st.getPredicate());
+ if( (!inverseAliases.isEmpty() && !firstCall) || (firstCall && !forbidenRdfProperties.contains(st.getPredicate()) && !ignore.contains(st.getPredicate()))){
+ if(inverseAliases.isEmpty()) // if empty add current property, otherwise replaces current predicate with its shorten forms, i.e., inverse alias
+ inverseAliases.add(st.getPredicate().toString());
+ for(int index=0; index < inverseAliases.size(); index++) {
+ String shortenPredicate = inverseAliases.get(index);
+ shortenPredicate = shortenURI(model, shortenPredicate);
+ RDFNode object = st.getObject();
+ if(object.isResource()) {
+ updateCollectionOfTriplesWithResources(annotation, model, collectionOfTriples, shortenPredicate, object.asResource());
+ }else {
+ updateCollectionOfTriplesWithLiterals(collectionOfTriples, shortenPredicate, object.asLiteral().getValue());
+ }
+ }
+ }
+ }
+ // add suject as property if specified in annotation
+ if(annotation.getIdentifiers()!=null && !annotation.getIdentifiers().isEmpty())
+ annotation.getIdentifiers().stream().forEach(id -> collectionOfTriples.put(id, subject.toString()));
+
+ return collectionOfTriples;
+ }
+
+
+ private void updateCollectionOfTriplesWithResources(PropertiesContainerAnotation annotation, Model model, Map collectionOfTriples, String predicate, Resource object){
+
+ if(!model.contains(object.asResource(), null, (RDFNode) null)) {
+ // DONE: Si el objecto no existe en el modelo como sujeto : guardarlo como
+ String shortenObject = shortenURI(model, object.toString());
+ collectionOfTriples.put(predicate, shortenObject);
+ }else {
+ // DONE: Si el objecto es tambien sujeto en el modelo -> entonces se traduce como un Map) : llamada recurisva
+ Map nestedResource = populateMapFromRDF(annotation, model, object, false);
+ // DONE: Si el objecto es tambien sujeto en el modelo y ya existe en el mapa acutal : entonces traducirlos como una collection de Map
+ updateCollectionOfTriplesWithLiterals(collectionOfTriples, predicate, nestedResource);
+
+ }
+ }
+
+ /*
+ * Since objects here are literals we already know that the range of the property are always literals, and thus, as object it will be collection of strings or string
+ */
+ @SuppressWarnings("unchecked")
+ private void updateCollectionOfTriplesWithLiterals(Map collectionOfTriples, String predicate, Object object) {
+ Object oldValue = collectionOfTriples.remove(predicate);
+ if(oldValue!=null) { // already existed
+ Collection newObjects = new ArrayList<>();
+ if(oldValue instanceof Collection) {
+ newObjects.addAll((Collection) oldValue);
+ }else {
+ newObjects.add(oldValue);
+ }
+ newObjects.add(object);
+ collectionOfTriples.put(predicate, newObjects);
+ }else {
+ // new element
+ collectionOfTriples.put(predicate, object);
+ }
+ }
+
+ private String shortenURI(Model model, String uri) {
+ String shortenUri = model.shortForm(uri);
+ if(shortenUri==null)
+ shortenUri = uri;
+ return shortenUri;
+ }
+
+ // --
+
+ @Override
+ public void fromObjectToRdf(Field field, Object object, Model model, Resource subject) throws IllegalArgumentException, IllegalAccessException, URISyntaxException {
+ field.setAccessible(true);
+
+ PropertiesContainerAnotation annotation = new PropertiesContainerAnotation(field);
+ @SuppressWarnings("unchecked")
+ Map values = (Map) annotation.getField().get(object);
+ populateModel(annotation, model, subject, values.entrySet());
+ }
+
+ @SuppressWarnings("unchecked")
+ private void populateModel(PropertiesContainerAnotation annotation, Model model, Resource subject, Set> entries) {
+
+ for (Entry entry : entries) {
+ Object value = entry.getValue();
+ Property property = computeFullProperty(entry.getKey(), model.getNsPrefixMap());
+ if (isCollection(value)) {
+ Collection newProperties = (Collection) value;
+ nestedElements(annotation, new ArrayList<>(newProperties), model, subject, property);
+ } else if (isMap(value)) {
+ resourceObjectToRDF(annotation, model, subject, property, value);
+ } else if (!isCollection(value) && !isMap(value)) {
+ keyValueToRDF(annotation, model, subject, property, value);
+ }
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private void resourceObjectToRDF(PropertiesContainerAnotation annotation, Model model, Resource subject, Property property, Object value) {
+ Resource newSubject = model.createResource();
+ Map newEntries = (Map) value;
+ Optional propertyIdentifier = newEntries.keySet().stream().filter(keyProperty -> annotation.isPropertyIdentifier(keyProperty)).findFirst();
+ if(propertyIdentifier.isPresent()) { // if a property in the json/object was set as identifier create a subject with such
+ String newSubjectStr = newEntries.remove(propertyIdentifier.get()).toString();
+ newSubject = model.createResource(newSubjectStr);
+ }
+ model.add(subject, property, newSubject);
+ populateModel(annotation, model, newSubject, newEntries.entrySet());
+ }
+
+ private static Boolean isCollection(Object value) {
+ return value instanceof Collection;
+ }
+
+ private static Boolean isMap(Object value) {
+ return value instanceof Map;
+ }
+
+ @SuppressWarnings("unchecked")
+ private void nestedElements(PropertiesContainerAnotation annotation, List elements, Model model, Resource subject, Property property ) {
+ for(int index=0; index < elements.size(); index++) {
+ Object value = elements.get(index);
+ if(isCollection(value)) {
+ Collection newProperties = (Collection) value;
+ nestedElements(annotation, new ArrayList<>(newProperties), model, subject, property );
+ }else if(isMap(value)) {
+ resourceObjectToRDF(annotation, model, subject, property, value);
+ }else if( !isMap(value) && !isCollection(value)) {
+ keyValueToRDF( annotation,model, subject, property, value);
+ }
+ }
+ }
+
+ private static void keyValueToRDF(PropertiesContainerAnotation annotation, Model model, Resource subject, Property property, Object value) {
+ String url = model.expandPrefix(value.toString());
+
+ if(!url.equals(value) && KehioUtils.isURI(url) ) {
+ Property alias = annotation.getAlias(property.getURI());
+ model.add(subject, alias, ResourceFactory.createResource(url));
+ }else if(KehioUtils.isURI(value.toString())) {
+ Property alias = annotation.getAlias(property.getURI());
+ model.add(subject, alias, ResourceFactory.createResource(value.toString()));
+ }else {
+ model.add(subject, property, ResourceFactory.createTypedLiteral(value));
+ }
+ }
+
+
+ private Property computeFullProperty(String property, Map prefixes) {
+ Property computedProperty = ResourceFactory.createProperty(property);
+ int index = property.indexOf(':');
+ if(index>-1) {
+ String prefix = property.substring(0,index);
+ String value = property.substring(index+1,property.length());
+ String url = prefixes.get(prefix);
+ if(url!=null)
+ computedProperty = ResourceFactory.createProperty(url+value);
+ }
+ return computedProperty;
+ }
+
+
+
+
+
+
+ class PropertiesContainerAnotation{
+
+ private Field field;
+
+ public PropertiesContainerAnotation(Field field) {
+ this.field = field;
+ checkAnnotation();
+ }
+
+
+
+ public Property getAlias(String predicate) {
+ Map aliases = this.getAliases();
+ Property aliasProperty = aliases.get(predicate);
+ if(aliasProperty==null)
+ aliasProperty = ResourceFactory.createProperty(predicate);
+ return aliasProperty;
+ }
+
+ private void checkAnnotation() {
+ if(!field.getType().equals(Map.class))
+ throw new IllegalArgumentException(KehioUtils.concatStrings("@RdfBlankContainer must be used only with a Map attribute. Please review annotation in field ", field.getName()));
+ Entry mapTypes = KehioUtils.extractMapTypes(field);
+ if(!mapTypes.getKey().contains(KehioUtils.STRING_CLASS))
+ throw new IllegalArgumentException(KehioUtils.concatStrings("@RdfBlankContainer must be used only with a Map that has as key type a String. Please review annotation in field ", field.getName()));
+
+ //if(!KehioUtils.extractMapValueType(field).toString().equals(Object.class.toString()))
+ // throw new IllegalArgumentException(KehioUtils.concatStrings("@RdfContainer must be used only with a Map that has as value a java generic Object. Please review annotation in field ", field.getName()));
+
+ /*
+ if (onlyDatatypes() && onlyObjects())
+ throw new IllegalArgumentException(KehioUtils.concatStrings("@RdfContainer can have only one flag marked as true, either 'onlyDatatypes' or 'onlyObjects'. Please review annotation for field ", field.getName()));
+
+ if(onlyDatatypes() && (!mapTypes.getValue().equals(KehioUtils.STRING_CLASS) || )
+ throw new IllegalArgumentException(KehioUtils.concatStrings("@RdfContainer must be used only with a Map attribute. If 'onlyDatatypes' is true, then the values of the map must be String or a Collection of Strings. Please review annotation in field ", field.getName()));
+ */
+ }
+
+
+ /* public boolean onlyDatatypes() {
+ return field.getAnnotation(RdfBlankContainer.class).onlyDatatypes();
+ }
+
+ public boolean onlyObjects() {
+ return field.getAnnotation(RdfBlankContainer.class).onlyObjects();
+ }*/
+
+ public Field getField() {
+ return field;
+ }
+
+ public Set getPropertiesToIgnore() {
+ Set ignore = new HashSet<>();
+ String[] ignorable = field.getAnnotation(RdfContainer.class).ignore();
+ for(int index=0; index < ignorable.length; index++) {
+ ignore.add(ResourceFactory.createProperty(ignorable[index]));
+ }
+ return ignore;
+ }
+
+ public Set getIdentifiers(){
+ Set identifiers = new HashSet<>();
+ String[] rawIdentifiers = field.getAnnotation(RdfContainer.class).identifiers();
+ for(int index=0; index < rawIdentifiers.length; index++) {
+ identifiers.add(rawIdentifiers[index]);
+ }
+ return identifiers;
+ }
+
+ public Boolean isPropertyIdentifier(String property){
+ return getIdentifiers().contains(property);
+ }
+
+ public Map getAliases(){
+ Map aliases = new HashMap<>();
+ RdfUrlMap[] rawAliases = field.getAnnotation(RdfContainer.class).aliases();
+ for(int index=0; index < rawAliases.length; index++) {
+ aliases.put(rawAliases[index].key(), ResourceFactory.createProperty(rawAliases[index].value()));
+ }
+ return aliases;
+ }
+
+ public List inverseAlias(Property predicate) {
+ List inverseAlias = new ArrayList<>();
+ inverseAlias.addAll( getAliases().entrySet().stream().filter(entry -> entry.getValue().equals(predicate)).map(Entry::getKey).collect(Collectors.toList()));
+ return inverseAlias;
+ }
+
+ }
+
+
+
+
+
+
+
+
+}
diff --git a/src/main/java/kehio/mapper/RdfDatatypeGroupMapper.java b/src/main/java/kehio/mapper/RdfDatatypeGroupMapper.java
new file mode 100644
index 0000000..248b60a
--- /dev/null
+++ b/src/main/java/kehio/mapper/RdfDatatypeGroupMapper.java
@@ -0,0 +1,140 @@
+package kehio.mapper;
+
+import java.lang.reflect.Field;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.apache.jena.datatypes.RDFDatatype;
+import org.apache.jena.datatypes.TypeMapper;
+import org.apache.jena.rdf.model.Literal;
+import org.apache.jena.rdf.model.Model;
+import org.apache.jena.rdf.model.Property;
+import org.apache.jena.rdf.model.RDFNode;
+import org.apache.jena.rdf.model.Resource;
+import org.apache.jena.rdf.model.ResourceFactory;
+
+import kehio.annotations.RdfDatatypeGroup;
+
+class RdfDatatypeGroupMapper implements RdfMapper {
+
+
+ @Override
+ public boolean hasProcesableAnnotation(Field field) {
+ return field.isAnnotationPresent(RdfDatatypeGroup.class);
+ }
+
+ @Override
+ public Property fromRdfToObject(Field field, Object object, Model model, Resource subject) throws IllegalAccessException {
+ field.setAccessible(true);
+
+ DgroupAnotation anotation = new DgroupAnotation(field);
+ List rdfNodes = KehioUtils.objectNodes(model, subject, anotation.getAsProperty());
+
+ Map map = instantiateMap(rdfNodes, anotation);
+ if(!map.isEmpty())
+ KehioUtils.instantiateField(field, object,map);
+ return anotation.getAsProperty();
+ }
+
+ private Map instantiateMap(List rdfNodes, DgroupAnotation anotation ){
+ Map map = new HashMap<>();
+ for(int index=0; index < rdfNodes.size(); index++) {
+ RDFNode node = rdfNodes.get(index);
+ if(node.isLiteral()) {
+ Literal literal = node.asLiteral();
+ if(anotation.isByLang() && !literal.getLanguage().isEmpty()) {
+ map.put(literal.getLanguage(), literal.getValue().toString());
+ }else if(anotation.isByDatatype()) {
+ map.put(literal.getDatatypeURI(), literal.getValue().toString());
+ }
+ }
+ }
+ return map;
+ }
+
+
+
+
+ // -- from Object to RDF
+
+ @Override
+ public void fromObjectToRdf(Field field, Object object, Model model, Resource subject) throws IllegalAccessException {
+ field.setAccessible(true);
+
+ DgroupAnotation anotation = new DgroupAnotation(field);
+
+ @SuppressWarnings("unchecked")
+ Map values = (Map) field.get(object);
+ if(values!=null) {
+ for(Entry entry : values.entrySet()) {
+ Literal nodeLiteral = buildLiteral(anotation, entry);
+ if(nodeLiteral!=null)
+ model.add(subject, anotation.getAsProperty(), nodeLiteral);
+ }
+ }
+ }
+
+ private Literal buildLiteral(DgroupAnotation anotation, Entry entry) {
+ Literal literal = null;
+ if(anotation.isByLang())
+ literal = ResourceFactory.createLangLiteral(entry.getValue(), entry.getKey());
+ if(anotation.isByDatatype()) {
+ RDFDatatype dt = TypeMapper.getInstance().getTypeByName(entry.getKey());
+ literal = ResourceFactory.createTypedLiteral(entry.getValue(), dt);
+ }
+ return literal;
+ }
+
+
+
+}
+
+class DgroupAnotation{
+
+ private Field field;
+
+ public DgroupAnotation(Field field) {
+ this.field = field;
+ checkAnnotation();
+ }
+
+ private void checkAnnotation() {
+ if (getValue()==null || getValue().isEmpty())
+ throw new IllegalArgumentException(KehioUtils.concatStrings("@RdfDatatypeGroup must be initialised with a value, please review annotation for field ", field.getName()));
+ if(!field.getType().equals(Map.class))
+ throw new IllegalArgumentException(KehioUtils.concatStrings("@RdfDatatypeGroup must be used only with a Map wich keys are always a String and its values ajava wrapping primitive object. Please review annotation in field ", field.getName()));
+ if(isByLang() && isByDatatype())
+ throw new IllegalArgumentException(KehioUtils.concatStrings("@RdfDatatypeGroup must have as true only one flag, either 'byLang' or 'byDatatype'. Please review annotation in field ", field.getName()));
+ Entry mapTypes = KehioUtils.extractMapTypes(field);
+ if(!mapTypes.getKey().equals(KehioUtils.STRING_CLASS))
+ throw new IllegalArgumentException(KehioUtils.concatStrings("@RdfDatatypeGroup must be used with a Map wich keys are always a String. Please review annotation in field ", field.getName()));
+ if(!mapTypes.getValue().equals(KehioUtils.STRING_CLASS))
+ throw new IllegalArgumentException(KehioUtils.concatStrings("@RdfDatatypeGroup must be used with a Map wich values are always a String. Please review annotation in field ", field.getName()));
+ }
+
+ public boolean isByLang() {
+ return field.getAnnotation(RdfDatatypeGroup.class).byLang();
+ }
+
+ public boolean isByDatatype() {
+ return field.getAnnotation(RdfDatatypeGroup.class).byDatatype();
+ }
+
+
+ public Field getField() {
+ return field;
+ }
+
+
+ public String getValue() {
+ return field.getAnnotation(RdfDatatypeGroup.class).value();
+ }
+
+ public Property getAsProperty() {
+ return ResourceFactory.createProperty(field.getAnnotation(RdfDatatypeGroup.class).value());
+ }
+
+
+}
diff --git a/src/main/java/kehio/mapper/RdfDatatypeMapper.java b/src/main/java/kehio/mapper/RdfDatatypeMapper.java
new file mode 100644
index 0000000..21b7a15
--- /dev/null
+++ b/src/main/java/kehio/mapper/RdfDatatypeMapper.java
@@ -0,0 +1,284 @@
+package kehio.mapper;
+
+import java.lang.reflect.Field;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import org.apache.jena.datatypes.RDFDatatype;
+import org.apache.jena.datatypes.TypeMapper;
+import org.apache.jena.rdf.model.Literal;
+import org.apache.jena.rdf.model.Model;
+import org.apache.jena.rdf.model.Property;
+import org.apache.jena.rdf.model.RDFNode;
+import org.apache.jena.rdf.model.Resource;
+import org.apache.jena.rdf.model.ResourceFactory;
+import org.apache.jena.vocabulary.XSD;
+
+import kehio.annotations.RdfDatatype;
+
+/**
+ * Implements all Collection interfaces from https://www.javatpoint.com/collections-in-java
+ * @author cimmino
+ *
+ */
+class RdfDatatypeMapper implements RdfMapper {
+
+ // -- Compatibility method
+
+ @Override
+ public boolean hasProcesableAnnotation(Field field) {
+ return field.isAnnotationPresent(RdfDatatype.class);
+ }
+
+ // -- from RDF to Object methods
+
+ @Override
+ public Property fromRdfToObject(Field field, Object object, Model model, Resource subject) throws IllegalAccessException {
+ field.setAccessible(true);
+
+ DatatypeAnotation annotation = new DatatypeAnotation(field);
+ List fieldValues = serialiseToWrappingPrimitiveCollection(annotation, model, subject);
+ if(KehioUtils.isWrappingPrimitive(field)) {
+ if(!fieldValues.isEmpty())
+ KehioUtils.instantiateField(field, object, fieldValues.get(0));
+ }else if(KehioUtils.isCollectionOfWrappingPrimitive(field)) {
+ KehioUtils.instantiateCollection(field, fieldValues, object);
+ }else {
+ throw new IllegalArgumentException(KehioUtils.concatStrings("@RdfDatatype must be used with wrapping primitive types: String, Integer, Boolean, [...]; or a Collection, a List, a Queue, or a Set containing one of these wrapping primitive types. Please review annotation for attribute ",field.getName()));
+ }
+ return annotation.getProperty();
+ }
+
+ private List serialiseToWrappingPrimitiveCollection(DatatypeAnotation annotation, Model model, Resource subject) {
+ List fieldValues = new ArrayList<>();
+ Field field = annotation.getField();
+ List literals = KehioUtils.objectNodes(model, subject, annotation.getProperty());
+ for(int index=0; index < literals.size(); index++) {
+ Object fieldValue = null;
+ RDFNode node = literals.get(index);
+ if(node.isLiteral()) {
+ Literal literal = node.asLiteral();
+ if(!KehioUtils.isFieldString(field) && (literal.getValue().getClass().equals(field.getType()) || (literal.getValue() instanceof Number && field.getType().equals(Number.class)))) {
+ // Non string attribute
+ fieldValue = literal.getValue();
+ }else {
+ fieldValue = serialiseToString(annotation, literal);
+ }
+ }
+ if(fieldValue!=null)
+ fieldValues.add(fieldValue);
+ }
+ return fieldValues;
+ }
+
+ private String serialiseToString(DatatypeAnotation annotation, Literal literal) {
+ String instantiatedValue = null;
+ String literalValue = literal.getValue().toString();
+ if(annotation.isSinkLang()) {
+ instantiatedValue = KehioUtils.concatStrings(literalValue,"@",literal.getLanguage());
+ }else if(annotation.isSinkDatatype()) {
+ instantiatedValue = KehioUtils.concatStrings(literalValue,"^^",literal.getDatatypeURI());
+ }else if(!annotation.getLang().isEmpty() && !literal.getLanguage().isEmpty() && literal.getLanguage().equals(annotation.getLang())) {
+ instantiatedValue = literalValue;
+ }else if(!annotation.getDatatype().isEmpty() && literal.getDatatypeURI().equals(annotation.getDatatype())) {
+ instantiatedValue = literal.getValue().toString();
+ }else if(annotation.getDatatype().isEmpty() && literal.getDatatypeURI().equals(XSD.xstring.getURI())){
+ instantiatedValue = literal.getValue().toString();
+ }
+ return instantiatedValue;
+ }
+
+
+
+ /*private Collection serialiseToCollection(Object object, DatatypeAnotation annotation, Model model, Resource subject) throws IllegalArgumentException, IllegalAccessException {
+ Collection collection = new ArrayList<>();
+ String collectionType = KehioUtils.extractCollectionType(annotation.getField());
+
+ List literals = KehioUtils.objectNodes(model, subject, annotation.getProperty());
+ for(int index=0; index < literals.size(); index++) {
+ RDFNode node = literals.get(index);
+ if(node.isLiteral()) {
+
+ }
+ if(fieldValue!=null)
+ break;
+ }
+ }
+
+
+ try {
+
+ if(collectionType.equals(KehioUtils.STRING_CLASS)) {
+ instantiateNonStringCollection(object, dtypeAnnotation, model, subject, collectionClazz);
+ }else {
+ instantiateStringCollection(object, dtypeAnnotation, model, subject);
+ }
+ } catch(Exception e) {
+ throw new IllegalArgumentException(e.toString());
+ }
+
+ return collection;
+ }
+
+ private void instantiateNonStringCollection(Object object, DatatypeAnotation annotation, Model model, Resource subject, Class> collectionClazz) throws IllegalAccessException {
+ List nodes = KehioUtils.objectNodes(model, subject, annotation.get);
+
+ // Collection is not string
+ String datatype = annotation.getDatatype();
+ String expectedDatatype = TypeMapper.getInstance().getTypeByClass(collectionClazz).getURI();
+ if(!datatype.isEmpty() && !datatype.equals(expectedDatatype)) {
+ String illegalMessage = KehioUtils.concatStrings("@RdfDatatype error, for field ",annotation.getField().getName(), " it was annotated with the datatype ",datatype," which is incompatible for the Collection<",collectionClazz.getName(),">. Please check the annotation for this field, the datatype should be ",expectedDatatype," or similar");
+ throw new IllegalArgumentException(illegalMessage);
+ }
+ Collection instantiatedCollection = nodes.stream().filter(node -> node.asLiteral().getDatatypeURI().equals(expectedDatatype)).collect(Collectors.toList());
+ KehioUtils.instantiateCollection(dtypeAnnotation.getField(), instantiatedCollection, object);
+ }
+
+ private void instantiateStringCollection(Object object, DatatypeAnotation dtypeAnnotation, Model model, Resource subject) throws IllegalAccessException {
+ List literals = KehioUtils.retrieveFromRdfPath(model, subject, dtypeAnnotation.getValue(), dtypeAnnotation.isPath());
+ Collection collection = new ArrayList<>();
+ for(int index=0; index < literals.size(); index++) {
+ Literal literal = literals.get(index).asLiteral();
+ String value = extractValueFromLiteral(dtypeAnnotation, literal);
+ collection.add(value);
+ }
+ KehioUtils.instantiateCollection(dtypeAnnotation.getField(), collection, object);
+ }*/
+
+
+ // -- from Object to RDF methods
+
+
+ @Override
+ public void fromObjectToRdf(Field field, Object object, Model model, Resource subject) throws IllegalAccessException, URISyntaxException {
+ field.setAccessible(true);
+
+ DatatypeAnotation dtypeAnnotation = new DatatypeAnotation(field);
+ Object attributeValue = field.get(object);
+
+ if(KehioUtils.isWrappingPrimitive(field)) {
+ buildWrappingPrimitiveRdf(dtypeAnnotation, attributeValue, model, subject);
+ }else if(KehioUtils.isCollectionOfWrappingPrimitive(field)) {
+ buildCollectionOfWrappingPrimitiveRdf(dtypeAnnotation, attributeValue, model, subject);
+ }else {
+ throw new IllegalArgumentException(KehioUtils.concatStrings("@RdfDatatype must be used with wrapping primitive types: String, Integer, Boolean, [...]; or a Collection, a List, a Queue, or a Set containing one of these wrapping primitive types. Please review annotation for attribute ",field.getName()));
+ }
+
+ }
+
+ @SuppressWarnings("unchecked")
+ private void buildCollectionOfWrappingPrimitiveRdf(DatatypeAnotation dtypeAnnotation, Object attributeValue, Model model, Resource subject) {
+ Collection values = (Collection) attributeValue;
+ Iterator valuesIterator = values.iterator();
+ while(valuesIterator.hasNext()) {
+ Object innerValue = valuesIterator.next();
+ buildWrappingPrimitiveRdf(dtypeAnnotation, innerValue, model, subject);
+ }
+ }
+
+ private void buildWrappingPrimitiveRdf(DatatypeAnotation dtypeAnnotation, Object attributeValue, Model model, Resource subject) {
+ if(attributeValue!=null) {
+ Property property = ResourceFactory.createProperty(dtypeAnnotation.getValue());
+ Literal literal = ResourceFactory.createTypedLiteral(attributeValue);
+ if(!dtypeAnnotation.getDatatype().isEmpty()) {
+ RDFDatatype dt = TypeMapper.getInstance().getTypeByName(dtypeAnnotation.getDatatype());
+ literal = ResourceFactory.createTypedLiteral(attributeValue.toString(), dt);
+ }else if(!dtypeAnnotation.getLang().isEmpty()) {
+ literal = ResourceFactory.createLangLiteral(attributeValue.toString(), dtypeAnnotation.getLang());
+ }else if(dtypeAnnotation.isSinkDatatype()) {
+ literal = buildSinkDatatypeLiteral(attributeValue);
+ }else if(dtypeAnnotation.isSinkLang()) {
+ literal = buildSinkLangLiteral(attributeValue);
+ }
+ model.add(subject, property, literal);
+ }
+ }
+
+
+ private Literal buildSinkDatatypeLiteral(Object attributeValue) {
+ String value = attributeValue.toString();
+ int splitIndex = value.lastIndexOf("^^")+2;
+ String dtype = value.substring(splitIndex, value.length());
+ dtype = dtype.substring(1, dtype.length()-1);
+ value = value.substring(0,splitIndex-2);
+ RDFDatatype dt = TypeMapper.getInstance().getTypeByName(dtype);
+ return ResourceFactory.createTypedLiteral(value, dt);
+ }
+
+ private Literal buildSinkLangLiteral(Object attributeValue) {
+ String value = attributeValue.toString();
+ int splitIndex = value.lastIndexOf('@')+1;
+ String lang = value.substring(splitIndex, value.length());
+ value = value.substring(0,splitIndex-1);
+ return ResourceFactory.createLangLiteral(value, lang);
+ }
+
+
+
+ class DatatypeAnotation {
+
+ private Field field;
+
+ public DatatypeAnotation(Field field){
+ this.field = field;
+ checkAnnotationConfiguration();
+ }
+
+ private static final String ERROR = "@RdfDatatype error, for field ";
+ private void checkAnnotationConfiguration() {
+ if (getValue() == null || getValue().isEmpty())
+ throw new IllegalArgumentException(KehioUtils.concatStrings("@RdfDatatype must be initialised with a value, please review annotation for attribute ",field.getName()));
+ if(!getDatatype().isEmpty() && isSinkLang())
+ throw new IllegalArgumentException(KehioUtils.concatStrings(ERROR,field.getName(), " datatype and flag 'sinkLang' were provided. Please review annotation for attribute ",field.getName()," and keep only one."));
+ if(!getLang().isEmpty() && isSinkDatatype())
+ throw new IllegalArgumentException(KehioUtils.concatStrings(ERROR,field.getName(), " lang and flag 'sinkDatatype' were provided. Please review annotation for attribute ",field.getName()," and keep only one."));
+ if(isSinkLang() && isSinkDatatype())
+ throw new IllegalArgumentException(KehioUtils.concatStrings(ERROR,field.getName(), " both flags 'sinkDatatype' and 'sinkLang' were set to True. Please review annotation for attribute ",field.getName()," and keep only one of these flags as True."));
+ if(!getDatatype().isEmpty() && !getLang().isEmpty())
+ throw new IllegalArgumentException(KehioUtils.concatStrings(ERROR,field.getName(), " a datatype and a lang were provided. Please review annotation for attribute ",field.getName()," and keep only one of these options."));
+ if (!getDatatype().isEmpty() && isSinkDatatype())
+ throw new IllegalArgumentException(KehioUtils.concatStrings(ERROR ,field.getName(), " a datatype was provided and also 'sinkDatatype' was set to True. Please review annotation for attribute ",field.getName()," and keep only the datatype or the sinkDatatype flag."));
+ if (!getLang().isEmpty() && isSinkLang())
+ throw new IllegalArgumentException(KehioUtils.concatStrings(ERROR ,field.getName(), " a lang was provided and also 'sinkLang' was set to True. Please review annotation for attribute ",field.getName()," and keep only the lang or the sinkLang flag."));
+ if((isSinkLang() || isSinkDatatype()) && !KehioUtils.isCollectionOfWrappingPrimitive(field) && !field.getType().equals(String.class))
+ throw new IllegalArgumentException(KehioUtils.concatStrings("@RdfDatatype error, 'sinkLang' and 'sinkDatatype' are only applicable for String attributes, please review the annotation for attribute ", field.getName()));
+ //if(isSinkLang() || isSinkDatatype() || !getLang().isEmpty())
+ // throw new IllegalArgumentException( KehioUtils.concatStrings(ERROR, field.getName(), " it was used with a Collection containing a wrapping primitive that is not String, in this case flags 'sinkLang' and 'sinkDatatype', and also, 'lang' can not be used."));
+
+ // TODO: datatype solo se puede usar con un field string y lang tambien
+ }
+
+ // -- Getters & Setters
+
+ public String getValue() {
+ return field.getAnnotation(RdfDatatype.class).value().trim();
+ }
+
+ public Property getProperty() {
+ return ResourceFactory.createProperty(field.getAnnotation(RdfDatatype.class).value().trim());
+ }
+
+ public String getDatatype() {
+ return field.getAnnotation(RdfDatatype.class).datatype().trim();
+ }
+
+ public String getLang() {
+ return field.getAnnotation(RdfDatatype.class).lang().trim();
+ }
+
+ public boolean isSinkLang() {
+ return field.getAnnotation(RdfDatatype.class).sinkLang();
+ }
+
+ public boolean isSinkDatatype() {
+ return field.getAnnotation(RdfDatatype.class).sinkDatatype();
+ }
+
+ public Field getField() {
+ return field;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/kehio/mapper/RdfIdMapper.java b/src/main/java/kehio/mapper/RdfIdMapper.java
new file mode 100644
index 0000000..c83c2e5
--- /dev/null
+++ b/src/main/java/kehio/mapper/RdfIdMapper.java
@@ -0,0 +1,76 @@
+package kehio.mapper;
+
+import java.lang.reflect.Field;
+import java.net.URI;
+import java.net.URISyntaxException;
+import org.apache.jena.rdf.model.Model;
+import org.apache.jena.rdf.model.Property;
+import org.apache.jena.rdf.model.Resource;
+import org.apache.jena.vocabulary.RDF;
+
+import kehio.annotations.RdfId;
+
+class RdfIdMapper implements RdfMapper{
+
+
+ @Override
+ public boolean hasProcesableAnnotation(Field field) {
+ return field.isAnnotationPresent(RdfId.class);
+ }
+
+
+ @Override
+ public Property fromRdfToObject(Field field, Object object, Model model, Resource subject) throws IllegalAccessException, URISyntaxException {
+ field.setAccessible(true);
+ if(field.getType().equals(String.class)) {
+ instantiateStringField(field, subject, object, model);
+ }else if(field.getType().equals(URI.class)) {
+ instantiateURIField(field, subject, object, model);
+ }else {
+ throw new IllegalArgumentException(KehioUtils.concatStrings("@RdfId can be only used with String or java.net.URI types. Please review annotation for attribute ", field.getName()));
+ }
+ return null;
+ }
+
+ private void instantiateStringField(Field field, Resource objectProperty, Object object, Model model) throws IllegalArgumentException, IllegalAccessException {
+ if(objectProperty.asNode().isBlank()){
+ field.set(object, objectProperty.asNode().getBlankNodeLabel());
+ }else if( objectProperty.asNode().isURI()) {
+ field.set(object, objectProperty.toString());
+ }else {
+ // Nothing happens
+ }
+ }
+
+ private void instantiateURIField(Field field, Resource objectProperty, Object object, Model model) throws IllegalArgumentException, IllegalAccessException, URISyntaxException {
+ if(objectProperty.asNode().isBlank()){
+ field.set(object, new URI(objectProperty.asNode().getBlankNodeLabel()));
+ }else if( objectProperty.asNode().isURI()) {
+ field.set(object, new URI(objectProperty.toString()));
+ }else {
+ // Nothing happens
+ }
+ }
+
+ // --
+
+ @Override
+ public void fromObjectToRdf(Field field, Object object, Model model, Resource nullSubject) throws IllegalArgumentException, IllegalAccessException, URISyntaxException {
+ field.setAccessible(true);
+ checkFieldCompatibility(field);
+ Object id = field.get(object);
+ Resource subject = null;
+ if(id!=null && !id.toString().trim().isEmpty()) {
+ subject = model.createResource(id.toString().trim());
+ model.add(subject, RDF.type, Kehio.KEHIO_TYPE);
+ }
+ }
+
+ private void checkFieldCompatibility(Field field) {
+ if(!field.getType().equals(String.class) && !field.getType().equals(URI.class)) {
+ throw new IllegalArgumentException(KehioUtils.concatStrings("@RdfId can be only used with String or java.net.URI types. Please review annotation for attribute ", field.getName()));
+ }
+ }
+
+
+}
diff --git a/src/main/java/kehio/mapper/RdfMapper.java b/src/main/java/kehio/mapper/RdfMapper.java
new file mode 100644
index 0000000..aa8ab2f
--- /dev/null
+++ b/src/main/java/kehio/mapper/RdfMapper.java
@@ -0,0 +1,17 @@
+package kehio.mapper;
+
+import java.lang.reflect.Field;
+import java.net.URISyntaxException;
+
+import org.apache.jena.rdf.model.Model;
+import org.apache.jena.rdf.model.Property;
+import org.apache.jena.rdf.model.Resource;
+
+public interface RdfMapper {
+
+
+ public boolean hasProcesableAnnotation(Field field);
+ public Property fromRdfToObject(Field field, Object object, Model model, Resource subject) throws IllegalArgumentException, IllegalAccessException, URISyntaxException;
+ public void fromObjectToRdf(Field field, Object object, Model model, Resource subject) throws IllegalArgumentException, IllegalAccessException, URISyntaxException;
+
+}
diff --git a/src/main/java/kehio/mapper/RdfObjectGroupMapper.java b/src/main/java/kehio/mapper/RdfObjectGroupMapper.java
new file mode 100644
index 0000000..ee775c5
--- /dev/null
+++ b/src/main/java/kehio/mapper/RdfObjectGroupMapper.java
@@ -0,0 +1,191 @@
+package kehio.mapper;
+
+import java.lang.reflect.Field;
+import java.net.URISyntaxException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.apache.jena.rdf.model.Model;
+import org.apache.jena.rdf.model.ModelFactory;
+import org.apache.jena.rdf.model.Property;
+import org.apache.jena.rdf.model.RDFNode;
+import org.apache.jena.rdf.model.Resource;
+import org.apache.jena.rdf.model.ResourceFactory;
+
+import kehio.annotations.RdfObjectGroup;
+import kehio.annotations.RdfUrlMap;
+
+class RdfObjectGroupMapper implements RdfMapper {
+
+
+ @Override
+ public boolean hasProcesableAnnotation(Field field) {
+ return field.isAnnotationPresent(RdfObjectGroup.class);
+ }
+
+ @Override
+ public Property fromRdfToObject(Field field, Object object, Model model, Resource subject) throws IllegalAccessException {
+ field.setAccessible(true);
+
+ ObjectGroupAnnotation annotation = new ObjectGroupAnnotation(field);
+ List objectProperties = KehioUtils.objectNodes(model, subject, annotation.getValue());
+
+ Map instantiation = new HashMap<>();
+ for(int index=0; index < objectProperties.size(); index++) {
+ try {
+ RDFNode node = objectProperties.get(index);
+ Object nestedObject = instantiateClassField(field, node.asResource() , model);
+ String key = instantiateKeyProperty(model, annotation.getGroupKey(), node.asResource(), field);
+ if(key!=null && nestedObject!=null) {
+ // Replace agrupation key for mapped value if exists
+ key = annotation.getGroupKeyInstantiated(key);
+ // Instantiate object
+ instantiation.put(key, nestedObject);
+ }
+ }catch(Exception e) {
+ throw new IllegalArgumentException(e.toString());
+ }
+ }
+ if(!instantiation.isEmpty())
+ field.set(object, instantiation);
+ return annotation.getProperty();
+ }
+
+ private String instantiateKeyProperty(Model model, String keyProperty, Resource objectProperty, Field field) {
+ String key = null;
+ List objectProperties = KehioUtils.objectNodes(model, objectProperty, keyProperty);
+ if(objectProperties.size()>1) {
+ throw new IllegalArgumentException(KehioUtils.concatStrings("@RdfObjectGroup must point to a unique literal through they 'key' property, more than one were retrieved. please review annotation for attribute ", field.getName()));
+ }else if(objectProperties.isEmpty()) {
+ throw new IllegalArgumentException(KehioUtils.concatStrings("@RdfObjectGroup must point to a unique literal through they 'key' property, zero were retrieved. please review annotation for attribute ", field.getName()));
+ }else {
+ RDFNode node = objectProperties.get(0);
+ if(node.isLiteral()) {
+ key = node.asLiteral().getString();
+ }else {
+ throw new IllegalArgumentException(KehioUtils.concatStrings("@RdfObjectGroup must point to a unique literal through they 'key' property, a non-literal was retrieved. please review annotation for attribute ", field.getName()));
+ }
+ }
+ return key;
+ }
+
+ private Object instantiateClassField(Field field, Resource objectProperty, Model model) {
+ Object nestedObject = null;
+ try {
+ Class> clazzFull = KehioUtils.extractMapValueType(field);
+ nestedObject = Kehio.serializeClass(clazzFull, model, objectProperty.asResource());
+ }catch(Exception e) {
+ throw new IllegalArgumentException(e.toString());
+ }
+ return nestedObject;
+ }
+
+
+ // ---
+
+
+ @Override
+ public void fromObjectToRdf(Field field, Object object, Model model, Resource subject) throws IllegalAccessException, URISyntaxException {
+ field.setAccessible(true);
+
+ ObjectGroupAnnotation annotation = new ObjectGroupAnnotation(field);
+
+ @SuppressWarnings("unchecked")
+ Map values = (Map) field.get(object);
+
+ Model nestedModel = buildObjectRdf(annotation, values, subject, model.getNsPrefixMap());
+ model.add(nestedModel);
+ }
+
+
+ private Model buildObjectRdf(ObjectGroupAnnotation annotation, Map values, Resource subject, Map prefixes) throws IllegalAccessException {
+ Model nestedModel = ModelFactory.createDefaultModel();
+
+ for(Entry entry : values.entrySet()) {
+ try {
+ Entry nestedObjects = Kehio.deserializeClassExtended(entry.getValue(), prefixes);
+ nestedModel.add(subject, ResourceFactory.createProperty(annotation.getValue()), nestedObjects.getKey());
+ nestedModel.add(nestedObjects.getValue());
+ if(annotation.addKey()) {
+ // Replace agrgupation key for mapped value if exists
+ String key = annotation.getGroupKeyInstantiated(entry.getKey());
+ // inject into RDF
+ String groupKey = annotation.getGroupKey();
+ nestedModel.add(nestedObjects.getKey(), ResourceFactory.createProperty(groupKey), key);
+ }
+ }catch(Exception e) {
+ throw new IllegalArgumentException(e.toString());
+ }
+ }
+ return nestedModel;
+ }
+
+
+ // ---
+
+
+ private class ObjectGroupAnnotation{
+ private Field field;
+
+ public ObjectGroupAnnotation(Field field) {
+ this.field = field;
+ checkAnnotationRestrictions();
+ }
+
+ private void checkAnnotationRestrictions() {
+ String annotationValue = getValue();
+ String anotationKey = getGroupKey();
+ if (annotationValue==null || annotationValue.isEmpty())
+ throw new IllegalArgumentException(KehioUtils.concatStrings("@RdfObjectGroup must be initialised with a value. Please review annotation for attribute ", field.getName()));
+ if(anotationKey==null || anotationKey.isEmpty())
+ throw new IllegalArgumentException(KehioUtils.concatStrings("@RdfObjectGroup must be initialised with a key that must be a datatype property. Please review annotation for attribute ", field.getName()));
+ Entry mapTypes = KehioUtils.extractMapTypes(field);
+ if(!mapTypes.getKey().equals(KehioUtils.STRING_CLASS))
+ throw new IllegalArgumentException(KehioUtils.concatStrings("@RdfObjectGroup must be used with a Map which keys are Strings. Please review annotation for attribute ", field.getName()));
+ if(KehioUtils.isWrappingPrimitive(mapTypes.getValue()))
+ throw new IllegalArgumentException(KehioUtils.concatStrings("@RdfObjectGroup must be used with a Map which values are non-wrapping primitive objects. Please review annotation for attribute ", field.getName()));
+
+ }
+
+ private String getValue() {
+ return field.getAnnotation(RdfObjectGroup.class).value();
+ }
+
+ private Property getProperty() {
+ return ResourceFactory.createProperty(field.getAnnotation(RdfObjectGroup.class).value());
+ }
+
+ private String getGroupKey() {
+ return field.getAnnotation(RdfObjectGroup.class).key();
+ }
+
+ private Boolean addKey() {
+ return field.getAnnotation(RdfObjectGroup.class).includeKey();
+ }
+
+
+ private Map getMaps(){
+ Map processedMap = new HashMap<>();
+ RdfUrlMap[] maps = field.getAnnotation(RdfObjectGroup.class).aliases();
+ if(maps!=null) {
+ for(int index=0; index < maps.length; index++) {
+ RdfUrlMap map = maps[index];
+ processedMap.put(map.key(), map.value());
+ }
+ }
+ return processedMap;
+ }
+
+ private String getGroupKeyInstantiated(String key) {
+ Map maps = getMaps();
+ if(maps.containsKey(key))
+ key = maps.get(key);
+ return key;
+ }
+
+ }
+
+}
+
diff --git a/src/main/java/kehio/mapper/RdfObjectMapper.java b/src/main/java/kehio/mapper/RdfObjectMapper.java
new file mode 100644
index 0000000..e6d7016
--- /dev/null
+++ b/src/main/java/kehio/mapper/RdfObjectMapper.java
@@ -0,0 +1,305 @@
+package kehio.mapper;
+
+import java.lang.reflect.Field;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.apache.jena.rdf.model.Model;
+import org.apache.jena.rdf.model.Property;
+import org.apache.jena.rdf.model.RDFNode;
+import org.apache.jena.rdf.model.Resource;
+import org.apache.jena.rdf.model.ResourceFactory;
+
+import kehio.annotations.RdfObject;
+import kehio.annotations.RdfUrlMap;
+
+class RdfObjectMapper implements RdfMapper {
+
+
+ @Override
+ public boolean hasProcesableAnnotation(Field field) {
+ return field.isAnnotationPresent(RdfObject.class);
+ }
+
+
+ @Override
+ public Property fromRdfToObject(Field field, Object object, Model model, Resource subject) throws IllegalAccessException, URISyntaxException {
+ field.setAccessible(true);
+
+ ObjectAnnotation annotation = new ObjectAnnotation(field);
+
+ if(KehioUtils.isCollection(field)) {
+ // Collection case
+ Collection instantiatedCollection = initialiseObjectCollection(annotation, subject, model);
+ KehioUtils.instantiateCollection(field, instantiatedCollection, object);
+ } else {
+ // Class, String, or URI case
+ Object instantiatedField = serialiseUnitary(annotation, subject, model);
+ KehioUtils.instantiateField(field, object, instantiatedField);
+ }
+ return annotation.getProperty();
+ }
+
+
+ private Object serialiseUnitary(ObjectAnnotation annotation, Resource subject, Model model) {
+ Object instantiatedObject = null;
+ List objectProperties = KehioUtils.objectNodes(model, subject, annotation.getProperty());
+ if(objectProperties.size()==1) {
+ RDFNode objectProperty = objectProperties.get(0);
+ if(isResource(objectProperty)) {
+ Field field = annotation.getField();
+ if(KehioUtils.isFieldString(field)) {
+ instantiatedObject = serialiseStringField(objectProperty, annotation);
+ }else if(KehioUtils.isFieldURI(field)) {
+ instantiatedObject = serialiseURIField(objectProperty);
+ } else {
+ instantiatedObject = serialiseClassField(field.getType().getCanonicalName(), objectProperty, model);
+ }
+ }
+ }
+
+ return instantiatedObject;
+ }
+
+ private String serialiseStringField(RDFNode objectProperty, ObjectAnnotation annotation) {
+ String instantiatedObject = null;
+ String base = annotation.getBase();
+ if(objectProperty.asNode().isBlank()){
+ instantiatedObject = objectProperty.asNode().getBlankNodeLabel().replace(base, "");
+ }else if( objectProperty.asNode().isURI()) {
+ instantiatedObject = objectProperty.toString();
+ instantiatedObject = instantiatedObject.replace(base, "");
+ }
+ // Change URL for alias value
+ if(instantiatedObject!=null) {
+ String instantiatedObjectAlias = annotation.getAliasKeyInstantiated(instantiatedObject);
+ if(instantiatedObjectAlias!=null)
+ instantiatedObject = instantiatedObjectAlias;
+ if(annotation.getStrict() && instantiatedObjectAlias==null)
+ instantiatedObject=null;
+ }
+
+ return instantiatedObject;
+ }
+
+ private URI serialiseURIField(RDFNode objectProperty) {
+ URI instantiatedObject = null;
+ try {
+ if(objectProperty.asNode().isBlank()){
+ instantiatedObject = new URI(objectProperty.asNode().getBlankNodeLabel());
+ }else if( objectProperty.asNode().isURI()) {
+ instantiatedObject = new URI(objectProperty.toString());
+ }
+ }catch(Exception e) {
+ throw new IllegalArgumentException(e.toString());
+ }
+ return instantiatedObject;
+ }
+
+ private Object serialiseClassField(String className, RDFNode objectProperty, Model model) {
+ Object instantiatedObject = null;
+ try {
+ Class> clazzFull = Class.forName(className);
+ instantiatedObject = Kehio.serializeClass(clazzFull, model, objectProperty.asResource());
+ }catch(Exception e) {
+ throw new IllegalArgumentException(e.toString());
+ }
+ return instantiatedObject;
+ }
+
+ private boolean isResource(RDFNode objectProperty) {
+ return objectProperty.asNode().isBlank() || objectProperty.asNode().isURI();
+ }
+
+ private Collection initialiseObjectCollection(ObjectAnnotation annotation, Resource subject, Model model) throws IllegalAccessException {
+ Collection collection = new ArrayList<>();
+ List objectProperties = KehioUtils.objectNodes(model, subject, annotation.getProperty());
+ for(int index=0; index < objectProperties.size(); index++) {
+ RDFNode node = objectProperties.get(index);
+ if(node.isResource()) {
+ try {
+ String subtype = KehioUtils.extractCollectionType(annotation.getField());
+ Object instantiatedObject = null;
+ if(subtype.equals(KehioUtils.STRING_CLASS)) {
+ instantiatedObject = serialiseStringField(node, annotation) ;
+ }else if( subtype.equals(KehioUtils.URI_CLASS)) {
+ instantiatedObject = serialiseURIField( node) ;
+ }else {
+ instantiatedObject= serialiseClassField( subtype, node, model);
+ }
+ if(instantiatedObject!=null)
+ collection.add(instantiatedObject);
+ }catch(Exception e) {
+ throw new IllegalArgumentException(e.toString());
+ }
+ }else {
+ String illegalMessage = KehioUtils.concatStrings("@RdfObject must be used for properties which range in the RDF are a blank node or a URI (String or java URI) and/or a Class");
+ throw new IllegalArgumentException(illegalMessage);
+ }
+ }
+ return collection;
+ }
+
+ // -- From object to to RDF methods
+
+ @Override
+ public void fromObjectToRdf(Field field, Object object, Model model, Resource subject) throws IllegalAccessException, URISyntaxException {
+ field.setAccessible(true);
+
+ ObjectAnnotation annotation = new ObjectAnnotation(field);
+
+ Object instantiatedField = KehioUtils.instantiatedField(field, object);
+ if(instantiatedField!=null) {
+ if(KehioUtils.isFieldClass(field) && !KehioUtils.isFieldURI(field)) {
+ // Class case
+ deserialiseClass(instantiatedField, annotation, model, subject);
+ }else if(KehioUtils.isCollection(field) ) {
+ // Collection case
+ deserialiseClassCollection(instantiatedField, annotation, model, subject);
+ }else if(KehioUtils.isFieldString(field) || KehioUtils.isFieldURI(field)) {
+ // String or URI case
+ deserialiseStringOrURI(instantiatedField.toString(), annotation, model, subject);
+ }else {
+ throw new IllegalArgumentException(KehioUtils.concatStrings("@RdfObject must be used with a non-wrapping primitive or a Collection of non-wrapping primitives, except with String or URI which are also allowed. Please review annotation for attribute ",field.getName()));
+ }
+ }
+ }
+
+ private void deserialiseStringOrURI(String instantiatedField, ObjectAnnotation annotation, Model model, Resource subject) throws IllegalArgumentException, IllegalAccessException {
+ String objectProperty = annotation.getBase()+annotation.getGroupValueInstantiated(instantiatedField);
+
+ if(KehioUtils.isValidResource(objectProperty)) {
+ model.add(subject, annotation.getProperty(), ResourceFactory.createResource(objectProperty));
+ }else {
+ throw new IllegalArgumentException(KehioUtils.concatStrings("@RdfObject used with a String or a URI was resolved incorrectly as ", objectProperty,". Please review annotation for attribute ",annotation.getField().getName()));
+ }
+ }
+
+
+ private void deserialiseClass(Object instantiatedField, ObjectAnnotation annotation, Model model, Resource subject) {
+ try {
+ Entry nested = Kehio.deserializeClassExtended(instantiatedField, model.getNsPrefixMap());
+ Resource nestedSubject = nested.getKey();
+ if(nestedSubject!=null && !nested.getValue().isEmpty()) {
+ model.add(nested.getValue());
+ model.add(subject, annotation.getProperty(), nestedSubject);
+ }//else {
+ // throw new IllegalArgumentException(KehioUtils.concatStrings("An exception occured while processing annotation @RdfObject with value, ",annotation.getProperty().toString(),". Please review attribute ",annotation.getField().getName()));
+ //}
+ }catch(Exception e) {
+ throw new IllegalArgumentException(e.toString());
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private void deserialiseClassCollection(Object instantiatedField, ObjectAnnotation annotation, Model model, Resource subject) throws IllegalArgumentException, IllegalAccessException {
+ Collection nestedObjects = (Collection) instantiatedField;
+ Iterator nestedObjectsIterator = nestedObjects.iterator();
+ while(nestedObjectsIterator.hasNext()) {
+ Object nestedObject = nestedObjectsIterator.next();
+ if(nestedObject instanceof String || nestedObject instanceof URI) {
+ deserialiseStringOrURI(nestedObject.toString(), annotation, model, subject);
+ }else if(!isNotProcessable(nestedObject)) {
+ deserialiseClass(nestedObject, annotation, model, subject);
+ }else {
+ throw new IllegalArgumentException("@RdfObject error, the annotation was used with a collection of wrapping primitive that is not String. Instead, use a collection of String, or URI, or a Class. Please review field");
+ }
+ }
+ }
+
+ private boolean isNotProcessable(Object nestedObject) {
+ return nestedObject instanceof Long || nestedObject instanceof Float || nestedObject instanceof Double || nestedObject instanceof Character || nestedObject instanceof Boolean || nestedObject instanceof Integer || nestedObject instanceof Short || nestedObject instanceof Byte;
+ }
+
+ // -- Annotation values methods
+
+
+ class ObjectAnnotation{
+
+ private Field field;
+
+ public ObjectAnnotation(Field field) {
+ this.field = field;
+ checkout();
+ }
+
+ private void checkout() {
+ if (getValue()==null || getValue().isEmpty())
+ throw new IllegalArgumentException(KehioUtils.concatStrings("@RdfObject must be initialised with a value. Please review annotation for attribute ", field.getName()));
+ String value = getValue();
+ if(!KehioUtils.isURI(value))
+ throw new IllegalArgumentException(KehioUtils.concatStrings("@RdfObject must be initialised with a value that is a valid URI. Please review annotation for attribute ", field.getName()));
+ if(!KehioUtils.isFieldClass(field) & !KehioUtils.isFieldURI(field) & !KehioUtils.isFieldString(field) && !(KehioUtils.isCollection(field) && isCollectionCompatible()))
+ throw new IllegalArgumentException(KehioUtils.concatStrings("@RdfObject must be used with attributes that are a URI, a String, a Class or a Collection containing one of the previous. Please review attribute ", field.getName()));
+ //if(!getBase().isEmpty() && !getMaps().isEmpty())
+ // throw new IllegalArgumentException(KehioUtils.concatStrings("@RdfObject can not be used with 'map' and 'base' at the same time. Please review attribute ", field.getName()));
+
+ }
+
+ private boolean isCollectionCompatible() {
+ String collectionType = KehioUtils.extractCollectionType(field);
+ return collectionType.equals(KehioUtils.STRING_CLASS) || !KehioUtils.isWrappingPrimitive(collectionType) ;
+ }
+
+ private String getValue() {
+ return field.getAnnotation(RdfObject.class).value();
+ }
+
+ private Property getProperty() {
+ return ResourceFactory.createProperty(field.getAnnotation(RdfObject.class).value());
+ }
+
+ private String getBase() {
+ return field.getAnnotation(RdfObject.class).base();
+ }
+
+ private boolean getStrict() {
+ return field.getAnnotation(RdfObject.class).strict();
+ }
+
+
+ private Field getField() {
+ return field;
+ }
+
+ private Map getAliases(){
+ Map processedMap = new HashMap<>();
+ RdfUrlMap[] maps = field.getAnnotation(RdfObject.class).aliases();
+ if(maps!=null) {
+ for(int index=0; index < maps.length; index++) {
+ RdfUrlMap map = maps[index];
+ processedMap.put(map.key(), map.value());
+ }
+ }
+ return processedMap;
+ }
+
+ private String getAliasKeyInstantiated(String key) {
+ return getAliases().get(key);
+ }
+
+ private String getGroupValueInstantiated(String simplifiedvalue) {
+ Map maps = getAliases();
+ Iterator> iterator = maps.entrySet().iterator();
+ while(iterator.hasNext()) {
+ Entry entry = iterator.next();
+ if(simplifiedvalue.equals(entry.getValue())) {
+ simplifiedvalue = entry.getKey();
+ }
+ }
+ return simplifiedvalue;
+ }
+
+
+
+
+
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/kehio/mapper/RdfPropertiesContainerMapper.java b/src/main/java/kehio/mapper/RdfPropertiesContainerMapper.java
new file mode 100644
index 0000000..3c8fd2a
--- /dev/null
+++ b/src/main/java/kehio/mapper/RdfPropertiesContainerMapper.java
@@ -0,0 +1,175 @@
+package kehio.mapper;
+
+import java.lang.reflect.Field;
+import java.net.URISyntaxException;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import org.apache.jena.rdf.model.Model;
+import org.apache.jena.rdf.model.Property;
+import org.apache.jena.rdf.model.RDFNode;
+import org.apache.jena.rdf.model.Resource;
+import org.apache.jena.rdf.model.ResourceFactory;
+import org.apache.jena.rdf.model.Statement;
+import org.apache.jena.rdf.model.StmtIterator;
+
+import kehio.annotations.RdfPropertiesContainer;
+import kehio.annotations.RdfUrlMap;
+
+
+class RdfPropertiesContainerMapper implements RdfMapper{
+
+ protected Set forbidenRdfProperties;
+
+ @Override
+ public boolean hasProcesableAnnotation(Field field) {
+ return field.isAnnotationPresent(RdfPropertiesContainer.class);
+ }
+
+ public void setPropertiesNotToContain(Set forbidenRdfProperties) {
+ this.forbidenRdfProperties = forbidenRdfProperties;
+ }
+
+ @Override
+ public Property fromRdfToObject(Field field, Object object, Model model, Resource subject) throws IllegalArgumentException, IllegalAccessException, URISyntaxException {
+ field.setAccessible(true);
+
+ PropertiesContainerAnotation annotation = new PropertiesContainerAnotation(field);
+ Map collectionOfTriples = populateMapFromRDF(annotation, model, subject);
+
+ field.set(object, collectionOfTriples);
+
+ return null;
+ }
+
+
+
+ private Map populateMapFromRDF(PropertiesContainerAnotation annotation , Model model, Resource subject) {
+ Set ignore = annotation.getPropertiesToIgnore();
+ Map collectionOfTriples = new HashMap<>();
+ StmtIterator stIterator = model.listStatements(subject, null, (RDFNode) null);
+ while(stIterator.hasNext()) {
+ Statement st = stIterator.next();
+ if(!forbidenRdfProperties.contains(st.getPredicate()) && !ignore.contains(st.getPredicate())){
+ String shortenPredicate = shortenURI(model, st.getPredicate().toString());
+ String shortenObject = st.getObject().toString();
+ if(st.getObject().isResource())
+ shortenObject = shortenURI(model, shortenObject);
+ collectionOfTriples.put(shortenPredicate, shortenObject);
+ }
+ }
+ return collectionOfTriples;
+ }
+
+ private String shortenURI(Model model, String uri) {
+ String shortenUri = model.shortForm(uri);
+ if(shortenUri==null)
+ shortenUri = uri;
+ return shortenUri;
+ }
+
+ // --
+
+ @Override
+ public void fromObjectToRdf(Field field, Object object, Model model, Resource subject) throws IllegalArgumentException, IllegalAccessException, URISyntaxException {
+ field.setAccessible(true);
+
+ PropertiesContainerAnotation annotation = new PropertiesContainerAnotation(field);
+ @SuppressWarnings("unchecked")
+ Map values = (Map) annotation.getField().get(object);
+ for(Entry entry : values.entrySet()) {
+ try {
+ Property rdfProperty = computeFullProperty(entry.getKey(), model.getNsPrefixMap());
+ populateModel(model, subject, rdfProperty, entry.getValue());
+ }catch(Exception e) {
+ throw new IllegalArgumentException(e.toString());
+ }
+ }
+ }
+
+ private Property computeFullProperty(String property, Map prefixes) {
+ Property computedProperty = ResourceFactory.createProperty(property);
+ int index = property.indexOf(':');
+ if(index>-1) {
+ String prefix = property.substring(0,index);
+ String value = property.substring(index+1,property.length());
+ String url = prefixes.get(prefix);
+ if(url!=null)
+ computedProperty = ResourceFactory.createProperty(url+value);
+ }
+ return computedProperty;
+ }
+
+ private void populateModel(Model model, Resource subject, Property rdfProperty, String objectValue) {
+
+ try {
+ RDFNode objectNode = model.createLiteral(objectValue);
+ model.add(subject, rdfProperty, objectNode);
+ }catch (Exception e) {
+ throw new IllegalArgumentException(e.toString());
+ }
+
+ }
+
+
+
+
+ class PropertiesContainerAnotation{
+
+ private Field field;
+
+ public PropertiesContainerAnotation(Field field) {
+ this.field = field;
+ checkAnnotation();
+ }
+
+ private void checkAnnotation() {
+ if(!field.getType().equals(Map.class))
+ throw new IllegalArgumentException(KehioUtils.concatStrings("@RdfContainer must be used only with a Map attribute. Please review annotation in field ", field.getName()));
+ Entry mapTypes = KehioUtils.extractMapTypes(field);
+ if(!mapTypes.getKey().contains(KehioUtils.STRING_CLASS))
+ throw new IllegalArgumentException(KehioUtils.concatStrings("@RdfContainer must be used only with a Map that has as key type a String. Please review annotation in field ", field.getName()));
+
+ //if(!KehioUtils.extractMapValueType(field).toString().equals(Object.class.toString()))
+ // throw new IllegalArgumentException(KehioUtils.concatStrings("@RdfContainer must be used only with a Map that has as value a java generic Object. Please review annotation in field ", field.getName()));
+
+ /*
+ if (onlyDatatypes() && onlyObjects())
+ throw new IllegalArgumentException(KehioUtils.concatStrings("@RdfContainer can have only one flag marked as true, either 'onlyDatatypes' or 'onlyObjects'. Please review annotation for field ", field.getName()));
+
+ if(onlyDatatypes() && (!mapTypes.getValue().equals(KehioUtils.STRING_CLASS) || )
+ throw new IllegalArgumentException(KehioUtils.concatStrings("@RdfContainer must be used only with a Map attribute. If 'onlyDatatypes' is true, then the values of the map must be String or a Collection of Strings. Please review annotation in field ", field.getName()));
+ */
+ }
+
+
+ /* public boolean onlyDatatypes() {
+ return field.getAnnotation(RdfPropertiesContainer.class).onlyDatatypes();
+ }
+
+ public boolean onlyObjects() {
+ return field.getAnnotation(RdfPropertiesContainer.class).onlyObjects();
+ }*/
+
+ public Field getField() {
+ return field;
+ }
+
+ public Set getPropertiesToIgnore() {
+ Set ignore = new HashSet<>();
+ String[] ignorable = field.getAnnotation(RdfPropertiesContainer.class).ignore();
+ for(int index=0; index < ignorable.length; index++) {
+ ignore.add(ResourceFactory.createProperty(ignorable[index]));
+ }
+ return ignore;
+ }
+
+
+
+ }
+
+}
diff --git a/src/main/java/wot/jtd/JTD.java b/src/main/java/wot/jtd/JTD.java
index 3df54b7..1486775 100755
--- a/src/main/java/wot/jtd/JTD.java
+++ b/src/main/java/wot/jtd/JTD.java
@@ -1,40 +1,56 @@
package wot.jtd;
import java.io.IOException;
-import java.util.HashSet;
+import java.net.URISyntaxException;
import java.util.List;
-import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.regex.Pattern;
import org.apache.jena.rdf.model.Model;
-import com.apicatalog.jsonld.JsonLdError;
+import org.apache.jena.rdf.model.ResourceFactory;
+import org.apache.jena.shacl.ShaclValidator;
+import org.apache.jena.shacl.Shapes;
+import org.apache.jena.shacl.ValidationReport;
import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.DeserializationFeature;
-import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
-import com.google.gson.JsonArray;
-import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import wot.jtd.exception.SchemaValidationException;
import wot.jtd.model.Thing;
-import wot.jtd.vocabulary.Vocabulary;
public class JTD {
private static Boolean defaultValues = false;
- private static Boolean showExternalValuesWarnings = false;
- private static final ObjectMapper MAPPER = new ObjectMapper();
- private static List relevantKeys = new CopyOnWriteArrayList<>();
+ private static Boolean removeNestedURNIds = false;
+ private static Boolean removeNestedUUIds = true;
+ private static Boolean includeDefaultLanguageInRDF = false;
+ private static List arrayCompactKeys = new CopyOnWriteArrayList<>();
+ public static final boolean NORMALIZE_SECURITY_DEFINITIONS_SCHEMES = true;
+ public static final Pattern SECURITY_DEFINITION_SCHEMES_PATTERN = Pattern.compile("^[a-z0-9]+\\_sc$");
static {
- relevantKeys.add(Vocabulary.SCOPES);
- relevantKeys.add(Vocabulary.OP);
- relevantKeys.add(Vocabulary.JSONLD_TYPE);
- relevantKeys.add(Vocabulary.JSONLD_CONTEXT);
- relevantKeys.add(Vocabulary.ITEMS);
- relevantKeys.add(Vocabulary.SECURITY);
+ arrayCompactKeys.add(Vocabulary.SCOPES);
+ arrayCompactKeys.add(Vocabulary.OP);
+ arrayCompactKeys.add(Vocabulary.JSONLD_TYPE);
+
+ arrayCompactKeys.add(Vocabulary.JSONLD_CONTEXT);
+ arrayCompactKeys.add(Vocabulary.ITEMS);
+ arrayCompactKeys.add(Vocabulary.SECURITY);
+ }
+
+ private JTD() {
+ super();
+ }
+
+ // Getters & Setters
+
+ public static List getArrayCompactKeys() {
+ return arrayCompactKeys;
}
+ public static void setArrayCompactKey(String key) {
+ arrayCompactKeys.add(key);
+ }
+
// -- getters and setters
public static Boolean getDefaultValues() {
@@ -44,120 +60,76 @@ public static Boolean getDefaultValues() {
public static void setDefaultValues(Boolean defaultValues) {
JTD.defaultValues = defaultValues;
}
-
-
+
+ public static Boolean getRemoveNestedURNIds() {
+ return removeNestedURNIds;
+ }
- // -- methods
-
- public static Object instantiateFromJson(JsonObject json, Class> clazz) throws IOException {
- JsonObject newJson = json.deepCopy();
- expandArrays(newJson);
- return MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).readValue(newJson.toString().getBytes(), clazz);
+ public static void setRemoveNestedURNIds(Boolean removeNestedURNIds) {
+ JTD.removeNestedURNIds = removeNestedURNIds;
}
-
- public static JsonObject toJson(Object object) throws JsonProcessingException {
- ObjectMapper mapper = new ObjectMapper();
- String jsonStr = mapper.writeValueAsString(object);
- JsonObject jsonObject = parseJson(jsonStr);
- compactArrays(jsonObject);
- return jsonObject;
- }
-
- public static void compactArrays(JsonObject json) {
- Set keys = new HashSet<>(json.keySet());
- // Find keys in the JSON that have an Array as value and which size is exactly 1, iterate over its value
- keys.stream().filter(key -> relevantKeys.contains(key) && json.get(key).isJsonArray() && json.getAsJsonArray(key).size()==1)
- .forEach(key -> compactArray(json, key)); // iterate and compact
-
- // Find keys in the JSON that have another JSON as value, and recursively call this function
- keys.stream().filter(key -> json.get(key).isJsonObject()).forEach(key -> compactArrays(json.getAsJsonObject(key)));
-
+
+ public static Boolean getRemoveNestedUUIds() {
+ return removeNestedUUIds;
+ }
+
+ public static void setRemoveNestedUUIds(Boolean removeNestedUUIds) {
+ JTD.removeNestedUUIds = removeNestedUUIds;
}
- private static void compactArray(JsonObject json, String key) {
- if(json.has(key) && json.getAsJsonArray(key).size()==1) {
- JsonElement element = json.getAsJsonArray(key).get(0);
- if( element instanceof JsonObject) {
- json.add(key, element);
- }else {
- json.addProperty(key, element.getAsString());
- }
- }
+ public static boolean getIncludeDefaultLanguageInRDF() {
+ return includeDefaultLanguageInRDF;
}
-
- public static void expandArrays(JsonObject json) {
- // Find keys in the JSON that have a 'primitive' value and that are expandable, then expand them
- Set keys = new HashSet<>(json.keySet());
-
- keys.stream().filter(key -> relevantKeys.contains(key)).forEach(key -> expandArray(json,key));
-
- // Find keys in the JSON that have another JSON as value, and recursively call this function
- keys.stream().filter(key -> json.get(key).isJsonObject()).forEach(key -> expandArrays(json.getAsJsonObject(key)));
- /*for(String key:keys) {
- if(key.contains("actions"))
- System.out.println("here");
- if(json.get(key).isJsonObject()) {
- expandArrays(json.getAsJsonObject(key));
- }
- }*/
-
- // Find keys in the JSON that have an Array as value, iterate over its value
- keys.stream().filter(key -> json.get(key).isJsonArray())
- .forEach(key -> callExpand(json.getAsJsonArray(key))); // iterate and filter elements that are objects
- /*for(String key:keys) {
- if(json.get(key).isJsonArray()) {
- callExpand(json.getAsJsonArray(key));
- }
- }*/
- }
-
- private static void callExpand(JsonArray array) {
- for(int index=0; index < array.size(); index++) {
- JsonElement elem = array.get(index);
-
- if(elem.isJsonObject()) {
- JsonObject json = elem.getAsJsonObject();
- expandArrays(json);
- array.set(index, json);
- }else if(elem.isJsonArray()) {
- callExpand( elem.getAsJsonArray());
- }
- }
- }
-
- private static void expandArray(JsonObject json, String key) {
- if(json.has(key) && !json.get(key).isJsonArray()) {
- JsonElement element = json.get(key);
- json.remove(key);
- JsonArray array = new JsonArray();
- array.add(element);
- json.add(key, array);
- }
+ public static void setIncludeDefaultLanguageInRDF(Boolean includeDefaultLanguageInRDF) {
+ JTD.includeDefaultLanguageInRDF = includeDefaultLanguageInRDF;
}
+
+ // -- JSON methods
public static JsonObject parseJson(String jsonStr) {
Gson gson = new Gson();
return gson.fromJson(jsonStr, JsonObject.class);
}
-
- public static Boolean getShowExternalValuesWarnings() {
- return showExternalValuesWarnings;
+
+ public static Object instantiateFromJson(JsonObject json, Class> clazz) throws IOException {
+ return JsonHandler.instantiateFromJson(json, clazz);
+ }
+
+ public static JsonObject toJson(Object object) throws JsonProcessingException {
+ return JsonHandler.toJson(object);
}
+
- public static void setShowExternalValuesWarnings(Boolean showExternalValues) {
- JTD.showExternalValuesWarnings = showExternalValues;
+ // -- RDF methods
+
+ public static Model toRDF(JsonObject td) throws IllegalAccessException, ClassNotFoundException, URISyntaxException, IOException, SchemaValidationException {
+ Thing thing = (Thing) JTD.instantiateFromJson(td, Thing.class);
+ return RdfHandler.toRDF(thing);
+ }
+
+ public static Model toRDF(Thing thing) throws IllegalAccessException, ClassNotFoundException, URISyntaxException, IOException, SchemaValidationException {
+ return RdfHandler.toRDF(thing);
}
- public static Model toRDF(JsonObject td) throws JsonLdError {
- RDFHandler handler = new RDFHandler();
- return handler.toRDF(td);
+ public static Thing fromRDF(Model model, String thingId) {
+ return RdfHandler.fromRDF(model, ResourceFactory.createResource(thingId));
+ }
+
+ public static List fromRDF(Model model) throws SchemaValidationException {
+ return RdfHandler.fromRDF(model);
}
+ // -- Validation methods
- public static List fromRDF(Model model) throws JsonLdError, IOException, SchemaValidationException {
- RDFHandler handler = new RDFHandler();
- return handler.fromRDF(model);
+ public static ValidationReport validateWithShape(Thing thing, Model shape) throws IllegalAccessException, ClassNotFoundException, URISyntaxException, IOException, SchemaValidationException {
+ Shapes shapes = Shapes.parse(shape.getGraph());
+ Model thingModel = JTD.toRDF(thing);
+ return ShaclValidator.get().validate(shapes, thingModel.getGraph());
}
+
+
+
+
}
diff --git a/src/main/java/wot/jtd/JsonHandler.java b/src/main/java/wot/jtd/JsonHandler.java
new file mode 100644
index 0000000..7ba7871
--- /dev/null
+++ b/src/main/java/wot/jtd/JsonHandler.java
@@ -0,0 +1,222 @@
+package wot.jtd;
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.UUID;
+import java.util.regex.Pattern;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonPrimitive;
+
+/**
+ * This class
+ * @author Andrea Cimmino
+ *
+ */
+public class JsonHandler {
+
+ private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
+
+ private JsonHandler() {
+ super();
+ }
+
+ // -- Protected Methods
+
+ protected static Object instantiateFromJson(JsonObject json, Class> clazz) throws IOException {
+ JsonObject newJson = json.deepCopy();
+ expandArrays(newJson);
+ if(!JTD.getRemoveNestedURNIds())
+ removeNestedIds(newJson, true);
+
+ return OBJECT_MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).readValue(newJson.toString().getBytes(), clazz);
+ }
+
+ protected static JsonObject toJson(Object object) throws JsonProcessingException {
+ ObjectMapper mapper = new ObjectMapper();
+ String jsonStr = mapper.writeValueAsString(object);
+ JsonObject jsonObject = JTD.parseJson(jsonStr);
+ compactArrays(jsonObject);
+ if(!JTD.getRemoveNestedURNIds())
+ removeNestedIds(jsonObject, false);
+ return jsonObject;
+ }
+
+ // -- Methods
+
+ public static void compactArrays(JsonObject json) {
+ Set keys = new HashSet<>(json.keySet());
+ // Find keys in the JSON that have an Array as value and which size is exactly 1, iterate over its value
+ keys.stream().filter(key -> JTD.getArrayCompactKeys().contains(key) && json.get(key).isJsonArray() && json.getAsJsonArray(key).size()==1)
+ .forEach(key -> compactArray(json, key)); // iterate and compact
+
+ // Find keys in the JSON that have another JSON as value, and recursively call this function
+ keys.stream().filter(key -> json.get(key).isJsonObject()).forEach(key -> compactArrays(json.getAsJsonObject(key)));
+
+ //Navigate over arrays
+ keys.stream().filter(key -> json.get(key).isJsonArray()).forEach(key -> json.get(key).getAsJsonArray().forEach(JsonHandler::compactArraysElem));
+ }
+
+ private static void compactArraysElem(JsonElement elem) {
+ if(elem.isJsonObject())
+ compactArrays(elem.getAsJsonObject());
+ }
+
+
+ private static void removeNestedIds(JsonObject json, boolean first) {
+ Set keys = new HashSet<>(json.keySet());
+ for(String key:keys) {
+ JsonElement element = json.get(key);
+ if(element.isJsonArray()) {
+ JsonArray array = element.getAsJsonArray();
+ for(int index=0; index < array.size(); index++) {
+ JsonElement nestedElement = array.get(index);
+ if(nestedElement.isJsonObject())
+ removeNestedIds(nestedElement.getAsJsonObject(), false);
+ }
+ }else if(element.isJsonObject()) {
+ removeNestedIds(element.getAsJsonObject(), false);
+ }else if(element.isJsonPrimitive() && (key.equals("id") || key.equals("@id")) && !first) {
+ removeIdentifier(json, element, key);
+ }
+ }
+ }
+
+ private static void removeIdentifier(JsonObject json, JsonElement element, String key) {
+ Boolean isURN = URN_PATTERN.matcher(element.getAsString()).matches();
+ Boolean isUUID = isUUID(element.getAsString());
+ if(isURN && JTD.getRemoveNestedURNIds()) {
+ json.remove(key);
+ }else if(isUUID && JTD.getRemoveNestedUUIds()) {
+ json.remove(key);
+ }else if(!isURN && !isUUID){
+ json.remove(key);
+ }
+ }
+
+
+ private static final Pattern URN_PATTERN = Pattern.compile(
+ "^urn:[a-z0-9][a-z0-9-]{0,31}:([a-z0-9()+,\\-.:=@;$_!*']|%[0-9a-f]{2})+/?.*$",
+ Pattern.CASE_INSENSITIVE);
+
+ private static boolean isUUID(String uri) {
+ Boolean isUUID = false;
+ try {
+ UUID.fromString(uri);
+ isUUID = true;
+ }catch(Exception e) {
+ e.printStackTrace();
+ }
+ return isUUID;
+ }
+
+ private static void compactArray(JsonObject json, String key) {
+ if(json.has(key) && json.getAsJsonArray(key).size()==1) {
+ JsonElement element = json.getAsJsonArray(key).get(0);
+ if( element instanceof JsonObject) {
+ json.add(key, element);
+ }else {
+ json.addProperty(key, element.getAsString());
+ }
+ }
+ }
+
+ public static void expandArrays(JsonObject json) {
+ // Find keys in the JSON that have a 'primitive' value and that are expandable, then expand them
+ Set keys = new HashSet<>(json.keySet());
+
+ keys.stream().filter(key -> JTD.getArrayCompactKeys().contains(key)).forEach(key -> expandArray(json,key));
+
+ // Find keys in the JSON that have another JSON as value, and recursively call this function
+ keys.stream().filter(key -> json.get(key).isJsonObject()).forEach(key -> expandArrays(json.getAsJsonObject(key)));
+
+ // Find keys in the JSON that have an Array as value, iterate over its value
+ keys.stream().filter(key -> json.get(key).isJsonArray())
+ .forEach(key -> callExpand(json.getAsJsonArray(key))); // iterate and filter elements that are objects
+
+ }
+
+ private static void callExpand(JsonArray array) {
+ for(int index=0; index < array.size(); index++) {
+ JsonElement elem = array.get(index);
+
+ if(elem.isJsonObject()) {
+ JsonObject json = elem.getAsJsonObject();
+ expandArrays(json);
+ array.set(index, json);
+ }else if(elem.isJsonArray()) {
+ callExpand( elem.getAsJsonArray());
+ }
+ }
+ }
+
+ private static void expandArray(JsonObject json, String key) {
+ if(json.has(key) && !json.get(key).isJsonArray()) {
+ JsonElement element = json.get(key);
+ json.remove(key);
+ JsonArray array = new JsonArray();
+ array.add(element);
+ json.add(key, array);
+ }
+ }
+
+ // JSON comparison
+
+ public static boolean compareJson(JsonElement element1, JsonElement element2) {
+ Boolean sameJson = true;
+ if (element1 instanceof JsonObject && element2 instanceof JsonObject) {
+ SortedSet keys1 = new TreeSet<>(element1.getAsJsonObject().keySet());
+ SortedSet keys2 = new TreeSet<>(element2.getAsJsonObject().keySet());
+ sameJson = keys1.containsAll(keys2) && keys2.containsAll(keys1);
+ for (String key : keys1) {
+ JsonElement value1 = element1.getAsJsonObject().get(key);
+ JsonElement value2 = element2.getAsJsonObject().get(key);
+ sameJson = compareJson(value1, value2);
+
+ }
+
+ } else if (element1 instanceof JsonArray && element2 instanceof JsonArray) {
+ JsonArray array1 = element1.getAsJsonArray();
+ JsonArray array2 = element2.getAsJsonArray();
+ sameJson = array1.size() == array2.size();
+ for (int index_1 = 0; index_1 < array1.size(); index_1++) {
+ JsonElement nestedElement1 = array1.get(index_1);
+ Boolean existTheSame = false;
+ int indexToRemove = 0;
+ for (int index_2 = 0; index_2 < array2.size(); index_2++) {
+ JsonElement nestedElement2 = array2.get(index_2);
+ existTheSame = compareJson(nestedElement1, nestedElement2);
+ indexToRemove = index_2;
+ if (existTheSame)
+ break;
+ }
+ if (existTheSame) {
+ sameJson &= existTheSame;
+ array2.remove(indexToRemove);
+ } else {
+ sameJson = false;
+ break;
+ }
+ }
+ } else if (element1 instanceof JsonPrimitive && element2 instanceof JsonPrimitive) {
+ String primitive1 = element1.getAsJsonPrimitive().getAsString();
+ String primitive2 = element2.getAsJsonPrimitive().getAsString();
+ sameJson = primitive1.equals(primitive2);
+ } else {
+ sameJson = false;
+ }
+
+ return sameJson;
+ }
+
+
+
+}
diff --git a/src/main/java/wot/jtd/Main.java b/src/main/java/wot/jtd/Main.java
deleted file mode 100755
index 7c3427a..0000000
--- a/src/main/java/wot/jtd/Main.java
+++ /dev/null
@@ -1,85 +0,0 @@
-package wot.jtd;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.util.Scanner;
-
-import com.google.gson.JsonArray;
-import com.google.gson.JsonObject;
-
-public class Main {
-
- public static void main(String[] args) throws IOException {
- File file = new File("./src/test/resources/linksmart/tds.json");
- String directoryTDs = readFile(file);
- JsonObject directoryJson = JTD.parseJson(directoryTDs);
- JsonArray tds = directoryJson.get("items").getAsJsonArray();
- FileWriter fw = new FileWriter("/Users/cimmino/Desktop/td-frame.csv");
-
-
- for(int index=0; index < tds.size(); index++) {
- JsonObject td = tds.get(index).getAsJsonObject();
- try {
-
- if(td.has("properties")) {
- JsonObject elements = td.getAsJsonObject("properties");
- elements.keySet().forEach(key -> {
- try {
- fw.write("properties_______"+elements.get(key)+"\n");
- } catch (IOException e) {
- e.printStackTrace();
- }
- });
-
- }if(td.has("actions")) {
- JsonObject elements = td.getAsJsonObject("actions");
- elements.keySet().forEach(key -> {
- try {
- fw.write("actions_______"+elements.get(key)+"\n");
- } catch (IOException e) {
- e.printStackTrace();
- }
- });
-
- }if(td.has("events")) {
- JsonObject elements = td.getAsJsonObject("events");
- elements.keySet().forEach(key -> {
- try {
- fw.write("events_______"+elements.get(key)+"\n");
- } catch (IOException e) {
- e.printStackTrace();
- }
- });
- }
-
- }catch(Exception e) {
- e.printStackTrace();
- break;
- }
- }
-
-
- fw.close();
-
- }
-
-
- private static String readFile(File myObj) {
- StringBuilder builder = new StringBuilder();
- try {
- Scanner myReader = new Scanner(myObj);
- while (myReader.hasNextLine()) {
- String data = myReader.nextLine();
- builder.append(data);
- }
- myReader.close();
- } catch (FileNotFoundException e) {
- System.out.println("An error occurred.");
- e.printStackTrace();
- }
- return builder.toString();
- }
-
-}
diff --git a/src/main/java/wot/jtd/MainWot.java b/src/main/java/wot/jtd/MainWot.java
deleted file mode 100755
index 8821ecb..0000000
--- a/src/main/java/wot/jtd/MainWot.java
+++ /dev/null
@@ -1,76 +0,0 @@
-package wot.jtd;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.util.Scanner;
-
-import com.google.gson.JsonArray;
-import com.google.gson.JsonObject;
-
-public class MainWot {
-
- public static void main(String[] args) throws IOException {
- File file = new File("/Users/cimmino/Desktop/tds");
- File[] tds = file.listFiles();
- int counter=0;
- JsonArray interactions = new JsonArray();
- for(File td:tds) {
- String parsedTD = readFile(td);
- try {
- JsonObject tdJson = JTD.parseJson(parsedTD);
- if(tdJson.has("interactions")) {
- counter++;
- interactions.addAll(tdJson.getAsJsonArray("interactions"));
- }else if(tdJson.has("interaction")) {
- counter++;
- interactions.addAll(tdJson.getAsJsonArray("interaction"));
- }else {
- System.out.println("WARNING: "+td.getName());
- }
- }catch(Exception e) {
- System.out.println("ERROR WITH: "+td.getName());
- }
- }
- System.out.println(counter+" out of "+tds.length);
- FileWriter fw = new FileWriter("/Users/cimmino/Desktop/td-iot-frame.csv");
-
- for(int index=0; index < interactions.size(); index++) {
- JsonObject interaction = interactions.get(index).getAsJsonObject();
- if(interaction.has("@type")) {
- JsonArray types = interaction.getAsJsonArray("@type");
- for(int count=0; count context = new HashMap<>();
+ static {
+ context.put("htv","http://www.w3.org/2011/http#");
+ context.put("rdfs","http://www.w3.org/2000/01/rdf-schema#");
+ context.put("xsd","http://www.w3.org/2001/XMLSchema#");
+ context.put("td","https://www.w3.org/2019/wot/td#");
+ context.put("jsonschema","https://www.w3.org/2019/wot/json-schema#");
+ context.put("wotsec","https://www.w3.org/2019/wot/security#");
+ context.put("hctl","https://www.w3.org/2019/wot/hypermedia#");
+ context.put("dct","http://purl.org/dc/terms/");
+ context.put("schema","http://schema.org/");
+ context.put("rdf","http://www.w3.org/1999/02/22-rdf-syntax-ns#");
+ }
+ // -- Deserialisation methods
+
+ public static Model toRDF(Thing thing) throws IllegalArgumentException, IllegalAccessException, ClassNotFoundException, URISyntaxException, IOException, SchemaValidationException {
+ // Extract context
+ JsonObject thingJson = JTD.toJson(thing);
+ JsonElement contextAbstractElement = thingJson.get(CONTEXT_TAG);
+ Map prefixes = new HashMap<>();
+ // Improve context from external specified sources
+ enhanceContext(contextAbstractElement, prefixes);
+ // Load default WoT Context
+ prefixes.putAll(context);
+ // Check if there is a default language
+ String defaultLanguage = prefixes.get(CONTEXT_LANGUAGE_TAG);
+ prefixes.remove(CONTEXT_LANGUAGE_TAG);
+ // Serialise and add default language if necessary
+ Model model = Kehio.deserializeClass(thing, prefixes);
+ if(JTD.getIncludeDefaultLanguageInRDF() && defaultLanguage!=null)
+ addLanguageToModel(model, defaultLanguage);
+
+ return model;
+ }
- public static void serialize() {
- VersionInfo version = new VersionInfo();
-
- for (Field field: version.getClass().getSuperclass().getDeclaredFields()) {
- System.out.println(field.getName());
- if (field.isAnnotationPresent(RdfDatatypeProperty.class)) {
- System.out.print(" -- > "+getSerializedKey(field));
- }
+ private static void enhanceContext(JsonElement contextAbstractElement, Map prefixes) throws JsonProcessingException {
+ if(contextAbstractElement.isJsonArray()) {
+ JsonArray contextElements = contextAbstractElement.getAsJsonArray();
+ for(int index=0; index < contextElements.size(); index++) {
+ JsonElement contextElement = contextElements.get(index);
+ if(contextElement.isJsonObject()) {
+ JsonObject prefixOject = contextElement.getAsJsonObject();
+ prefixOject.entrySet().forEach( entry -> prefixes.put(entry.getKey(), entry.getValue().getAsString()));
+ }
+ }
+ }else if (contextAbstractElement.isJsonObject()) {
+ JsonObject prefixOject = contextAbstractElement.getAsJsonObject();
+ prefixOject.entrySet().stream().filter(json -> json.getValue().isJsonPrimitive()).forEach( entry -> prefixes.put(entry.getKey(), entry.getValue().getAsString()));
}
- for (Field field: version.getClass().getDeclaredFields()) {
- System.out.println(field.getName());
- if (field.isAnnotationPresent(RdfDatatypeProperty.class)) {
- System.out.print(" -- > "+getSerializedKey(field));
- }
+ }
+
+ private static void addLanguageToModel(Model model, String defaultLanguage) {
+ List statements = model.listStatements(null, null, (RDFNode) null).toList();
+ for (int index = 0; index < statements.size(); index++) {
+ Statement statement = statements.get(index);
+ RDFNode objectNode = statement.getObject();
+ if (objectNode.isLiteral()) {
+ Literal literal = objectNode.asLiteral();
+ boolean coditionTypes = literal.getDatatypeURI().equals(XSD.normalizedString.toString()) || literal.getDatatypeURI().equals(XSD.xstring.toString()) || literal.getDatatypeURI().isEmpty();
+ if( coditionTypes && literal.getLanguage().isEmpty()) {
+ model.remove(statement);
+ Literal newLiteral = model.createLiteral(literal.getValue().toString(), defaultLanguage);
+ model.add(statement.getSubject(), statement.getPredicate(), newLiteral);
+ }
+ }
}
-
- }
- private static String getSerializedKey(Field field) {
- String annotationValue = field.getAnnotation(RdfDatatypeProperty.class).value();
-
- if (annotationValue.isEmpty()) {
- return field.getName();
- }
- else {
- return annotationValue;
- }
- }
+ }
- public List fromRDF(Model model) throws JsonLdError, IOException, SchemaValidationException {
+ // -- Serialise methods
+
+ public static List fromRDF(Model model) throws SchemaValidationException {
List things = new ArrayList<>();
- // 1. Write model as JSON-LD 1.0
- Writer writer = new StringWriter();
- model.write(writer, "Turtle");
- String jsonLDModel = writer.toString();
- System.out.println(jsonLDModel);
- writer.close();
- // 1. Find any resource that is a Thing
- List thingResources = findThingResource(model);
- if(thingResources.isEmpty())
- throw new SchemaValidationException("RDF provided has no td:Thing instances, or they lack of mandatory properties such as dc:title ir td:securityDefinitions ");
- for(int index=0; index < thingResources.size(); index++) {
- Resource thing = thingResources.get(index);
- // 1.1 Extract subgraph of this thing
- GraphExtract graphExtractor = new GraphExtract( TripleBoundary.stopNowhere );
- Graph thingGraph = graphExtractor.extract(thing.asNode(), model.getGraph());
- try {
- Thing newThing = buildThing(thing, thingGraph);
- things.add(newThing);
- }catch(Exception e) { // define more accurate exceptions
- e.printStackTrace();
+
+ List thingSubjectsWithSecurity = model.listSubjectsWithProperty(ResourceFactory.createProperty("https://www.w3.org/2019/wot/td#hasSecurityConfiguration")).toList();
+ List thingSubjects = model.listSubjectsWithProperty(ResourceFactory.createProperty("https://www.w3.org/2019/wot/td#securityDefinitions")).toList();
+ thingSubjects.retainAll(thingSubjectsWithSecurity);
+
+ if(thingSubjects.isEmpty()) {
+ throw new SchemaValidationException("Provided RDF data lacks URIs that are Things, subjects with mandatory properties and/or are missing");
+ }else {
+ for(int index=0; index < thingSubjects.size(); index++) {
+ Thing tmpThing = fromRDF(model, thingSubjects.get(index));
+ if(tmpThing!=null)
+ things.add(tmpThing);
}
}
+
return things;
}
- private List findThingResource(Model model) {
- List resources = model.listSubjectsWithProperty(RDF.type, THING).toList();
- if(resources.isEmpty()) {
- resources = model.listSubjectsWithProperty(DC_11.title).toList();
- resources.retainAll(model.listSubjectsWithProperty(SECURITY_DEFINITIONS).toList());
- }
- return resources;
+ private static JsonNode tdContext;
+ static {
+ ObjectMapper objectMapper = new ObjectMapper();
+ try {
+ tdContext = objectMapper.readTree("{\"value\" :\"https://www.w3.org/2019/wot/td/v1\"}").get("value");
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
}
- private Thing buildThing(Resource resource, Graph thingGraph) throws URISyntaxException {
- Model thingModel = ModelFactory.createModelForGraph(thingGraph);
- // minimum thing to buid
- Thing thing = new Thing();
- thing.setId(new URI(resource.toString()));
+ private static List prepareContext(Model model){
+ List context = new ArrayList<>();
+ context.add(tdContext);
+ ObjectMapper objectMapper = new ObjectMapper();
- //setTitle(Thing thing, Resource resource, Model thingModel, Property property)
-
- return thing;
- }
- /*
- private void setLiteralElements(Thing thing, Resource resource, Model thingModel, Property property) {
- Map langElements = getLangElements(resource, thingModel, property);
- if(!langElements.isEmpty()) {
- String title = null;
- if(titles.containsKey("en")) {
- title = titles.get("en");
- }else {
- title = (String) titles.values().toArray()[0];
+ Set> prefixes = model.getNsPrefixMap().entrySet();
+ for(Entry prefix : prefixes) {
+ try {
+ JsonObject contextElement = new JsonObject();
+ contextElement.addProperty(prefix.getKey(), prefix.getValue());
+ context.add(objectMapper.readTree(contextElement.toString()));
+ }catch(Exception e) {
+ e.printStackTrace();
}
- thing.setTitle(title);
- }else {
-
}
- }*/
-
- //
-
- public static void fromRDF() {
- VersionInfo thing = new VersionInfo();
- Method[] annotations = VersionInfo.class.getMethods();
- for(int index=0; index < annotations.length; index++) {
- Method annotation = annotations[index];
- System.out.println(annotation.getName());
- List annon = Arrays.asList(annotation.getAnnotations());
- annon.forEach(elem -> System.out.println(elem));
- }
- /*PropertyDescriptor[] properties = PropertyUtils.getPropertyDescriptors(thing);
- for(int index=0;index < properties.length; index++) {
- PropertyDescriptor property = properties[index];
- System.out.println(property.getDisplayName());
- System.out.println(property.getName());
- Method method = property.getPropertyType()
- if (method.isAnnotationPresent(Init.class)) {
- method.setAccessible(true);
- method.invoke(object);
- }
- for(Annotation anon: .getAnnotations()) {
- System.out.println(anon);
- }
- }*/
+ return context;
}
- protected Literal extractUnitaryObjectLiteral(Model model, Resource subject, Property property) throws RDFValidationException {
- Literal literal = null;
- RDFNode object = extractUnitaryObject(model, subject, property);
- if (object == null)
- throw new RDFValidationException(concatStrings("The porperty ", property.toString(),
- " must point to an existing literal, currently is not been used"));
- if (object.isLiteral()) {
- literal = object.asLiteral();
- } else {
- throw new RDFValidationException(concatStrings("The porperty ", property.toString(),
- " must point to a literal, currently is pointing to a non-literal object"));
- }
- return literal;
- }
-
- protected RDFNode extractUnitaryObject(Model model, Resource subject, Property property) throws RDFValidationException {
- RDFNode node = null;
- List objects = model.listObjectsOfProperty(property).toList();
- if(objects.size()>1) {
- throw new RDFValidationException(concatStrings("The porperty ", property.toString()," is unitary but it was used more than once with the subject ", subject.toString()));
- }else {
- node = objects.get(0);
- }
- return node;
- }
- protected String concatStrings(String ...message) {
- StringBuilder builder = new StringBuilder();
- for(int index =0; index < message.length; index++)
- builder.append(message[index]);
- return builder.toString();
- }
+ public static Thing fromRDF(Model model, Resource thingSubject) {
+ Thing thing = null;
+ List context = prepareContext(model);
+ String defaultLanguage = analyseDefaultLanguage(model);
+ JsonNode lang = setLanguageContext(defaultLanguage);
+ if(lang!=null)
+ context.add(lang);
+ try {
+ Thing tmpThing = (Thing) Kehio.serializeClass(Thing.class, model, thingSubject);
+
+ tmpThing.setContextValues(context);
+ if(tmpThing.getTitles()!=null && !tmpThing.getTitles().isEmpty() && tmpThing.getTitle()==null) {
+ if(tmpThing.getTitles().containsKey("en")) {
+ tmpThing.setTitle(tmpThing.getTitles().get("en"));
+ }else {
+ tmpThing.setTitle(tmpThing.getTitles().values().iterator().next());
+ }
+ }
+ if(tmpThing!=null) {
+ thing = Thing.fromJson(tmpThing.toJson());
+ }
+
+ }catch(Exception e) {
+ e.printStackTrace();
+ }
+
-
-
-
-
- // -- to RDF methods
-
- public Model toRDF(JsonObject td) throws JsonLdError {
- Model model = ModelFactory.createDefaultModel();
- InputStream tdStream = new ByteArrayInputStream(td.toString().getBytes());
- Document documentTD = JsonDocument.of(tdStream);
- RdfDataset dataset = JsonLd.toRdf(documentTD).get();
- Map existingBlankNodes = new HashMap<>();
- dataset.toList().forEach(triple -> populateModel(triple, model, existingBlankNodes));
- return model;
+ return thing;
}
- private void populateModel(RdfNQuad triple, Model model, Map existingBlankNodes) {
- Resource subject = instantiateResource(triple.getSubject(), model, existingBlankNodes);
- Property predicate = ResourceFactory.createProperty(triple.getPredicate().getValue());
-
- RdfValue object = triple.getObject();
- if(object.isIRI()) {
- Resource objectJena = ResourceFactory.createResource(object.getValue());
- model.add(subject, predicate, objectJena);
- }else if(object.isBlankNode()){
- Resource objectJena = instantiateBlankNode(object.getValue(), model, existingBlankNodes);
- model.add(subject, predicate, objectJena);
- }else if(object.isLiteral()) {
- RdfLiteral literal = object.asLiteral();
- Optional lang = literal.getLanguage();
- String datatype = literal.getDatatype();
- Literal jenaLiteral = ResourceFactory.createPlainLiteral(literal.getValue());
- if(lang.isPresent()) {
- jenaLiteral = ResourceFactory.createLangLiteral(literal.getValue(), lang.get());
- }else if(datatype!=null) {
- RDFDatatype dt = NodeFactory.getType(datatype);
- jenaLiteral = ResourceFactory.createTypedLiteral(literal.getValue(), dt);
+ private static JsonNode setLanguageContext(String defaultLanguage) {
+ JsonNode lang = null;
+ ObjectMapper objectMapper = new ObjectMapper();
+ if(defaultLanguage!=null) {
+ try {
+ JsonObject contextElement = new JsonObject();
+ contextElement.addProperty("@language", defaultLanguage);
+ lang = objectMapper.readTree(contextElement.toString());
+ }catch(Exception e) {
+ throw new IllegalArgumentException(e.toString());
}
- model.add(subject, predicate, jenaLiteral);
}
+ return lang;
}
-
- private Resource instantiateResource(RdfResource resource, Model model, Map existingBlankNodes) {
- Resource instantiatedResource = null;
- if(resource.isIRI()) {
- instantiatedResource = ResourceFactory.createResource(resource.getValue());
- }else {
- instantiatedResource = instantiateBlankNode(resource.getValue(), model, existingBlankNodes);
+
+ /**
+ * Returns the default language of the dataset if exists, otherwise returns null
+ * @param model
+ * @return
+ */
+ private static String analyseDefaultLanguage(Model model) {
+ List statements = model.listStatements(null, null, (RDFNode) null).toList();
+ String defaultLanguage = null;
+ boolean notExistDefaultLanguage = false;
+ Model auxiliarModel = ModelFactory.createDefaultModel();
+ List toRemove = new ArrayList<>();
+ for (int index = 0; index < statements.size(); index++) {
+ Statement statement = statements.get(index);
+ RDFNode objectNode = statement.getObject();
+ if (objectNode.isLiteral()) {
+ Literal literal = objectNode.asLiteral();
+ boolean coditionTypes = literal.getDatatypeURI().equals(XSD.normalizedString.toString()) || literal.getDatatypeURI().equals(XSD.xstring.toString()) || literal.getDatatypeURI().isEmpty();
+ if( !literal.getLanguage().isEmpty()) {
+ String language = literal.getLanguage();
+ if(defaultLanguage==null)
+ defaultLanguage = language;
+ notExistDefaultLanguage = !language.equals(defaultLanguage);
+ if(notExistDefaultLanguage)
+ break;
+ auxiliarModel.add(statement.getSubject(), statement.getPredicate(), model.createTypedLiteral(literal.getValue()));
+ toRemove.add(statement);
+ }else if( coditionTypes && literal.getLanguage().isEmpty()) {
+ // Coming here means there is a literal xsd: string or normalised string that has no language
+ notExistDefaultLanguage = true;
+ }
+ }
}
- return instantiatedResource;
- }
-
- private Resource instantiateBlankNode(String blankNode, Model model, Map existingBlankNodes) {
- Resource instantiatedResource = null;
- if(!existingBlankNodes.containsKey(blankNode)) {
- instantiatedResource = model.createResource();
- existingBlankNodes.put(blankNode, instantiatedResource);
+
+ if(!notExistDefaultLanguage) {
+ model.remove(toRemove);
+ model.add(auxiliarModel);
}else {
- instantiatedResource = existingBlankNodes.get(blankNode);
+ defaultLanguage = null;
}
- return instantiatedResource;
+ return defaultLanguage;
+
}
-
}
diff --git a/src/main/java/wot/jtd/ToRemove.java b/src/main/java/wot/jtd/ToRemove.java
deleted file mode 100755
index 1c4bc5d..0000000
--- a/src/main/java/wot/jtd/ToRemove.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package wot.jtd;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.util.Scanner;
-
-import com.google.gson.JsonArray;
-import com.google.gson.JsonObject;
-
-import wot.jtd.exception.SchemaValidationException;
-
-public class ToRemove {
-
- public static void main(String[] args) throws IOException, SchemaValidationException {
- File file = new File("./src/test/resources/linksmart/original/tds.json");
- String directoryTDs = readFile(file);
- JsonObject directoryJson = JTD.parseJson(directoryTDs);
- JsonArray tds = directoryJson.get("items").getAsJsonArray();
-
-
-
- for(int index=0; index < tds.size(); index++) {
- JsonObject td = tds.get(index).getAsJsonObject();
- FileWriter fw = new FileWriter("./src/test/resources/linksmart/td-"+index+".json");
- try {
- fw.write(td.toString());
- }catch(Exception e) {
- e.printStackTrace();
- break;
- }
- fw.close();
- }
-
-
-
-
- }
-
-
- private static String readFile(File myObj) {
- StringBuilder builder = new StringBuilder();
- try {
- Scanner myReader = new Scanner(myObj);
- while (myReader.hasNextLine()) {
- String data = myReader.nextLine();
- builder.append(data);
- }
- myReader.close();
- } catch (FileNotFoundException e) {
- System.out.println("An error occurred.");
- e.printStackTrace();
- }
- return builder.toString();
- }
-
-}
diff --git a/src/main/java/wot/jtd/Validation.java b/src/main/java/wot/jtd/Validation.java
new file mode 100644
index 0000000..1617d91
--- /dev/null
+++ b/src/main/java/wot/jtd/Validation.java
@@ -0,0 +1,283 @@
+package wot.jtd;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+import javax.validation.ConstraintViolation;
+import javax.validation.Validator;
+import javax.validation.ValidatorFactory;
+
+import wot.jtd.exception.ActionAffordanceValidationException;
+import wot.jtd.exception.DataSchemaValidationException;
+import wot.jtd.exception.EventAffordanceValidationException;
+import wot.jtd.exception.ExpectedResponseValidationException;
+import wot.jtd.exception.FormValidationException;
+import wot.jtd.exception.InteractionAffordanceValidationException;
+import wot.jtd.exception.LinkValidationException;
+import wot.jtd.exception.PropertyAffordanceValidationException;
+import wot.jtd.exception.SchemaValidationException;
+import wot.jtd.exception.SecuritySchemeValidationException;
+import wot.jtd.exception.ThingValidationException;
+import wot.jtd.exception.VersionInfoValidationException;
+import wot.jtd.model.ExpectedResponse;
+import wot.jtd.model.Form;
+import wot.jtd.model.Link;
+import wot.jtd.model.Thing;
+import wot.jtd.model.VersionInfo;
+import wot.jtd.model.interactions.ActionAffordance;
+import wot.jtd.model.interactions.EventAffordance;
+import wot.jtd.model.interactions.InteractionAffordance;
+import wot.jtd.model.interactions.PropertyAffordance;
+import wot.jtd.model.schemas.data.DataSchema;
+import wot.jtd.model.schemas.security.SecurityScheme;
+
+public class Validation {
+
+
+// /**
+// * This method validates an instance a {@link Thing} object.
+// * @param thing an instance of {@link Thing}
+// * @throws SchemaValidationException this exception is thrown when the syntax of the Thing Description as ORM is incorrect
+// */
+// public static void validate(Thing thing) throws SchemaValidationException {
+// ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
+// Validator validator = factory.getValidator();
+// Set> violations = validator.validate(thing);
+// StringBuilder builder = new StringBuilder();
+// if(!violations.isEmpty()) {
+// violations.forEach(msg -> builder.append(msg.getMessage()).append("\n"));
+// throw new ThingValidationException(builder.toString());
+// }
+// // TODO: throw nested expcetions
+//
+// // TODO: validate form restrictions
+// // if PropertyAffordance readproperty and writeproperty
+// // if ActionAffordance invokeaction
+// // if EventAffordance subscribeevent
+// }
+//
+//
+// /**
+// * This method validates an instance a {@link SecurityScheme} object.
+// * @param securityScheme an instance of {@link SecurityScheme}
+// * @throws SchemaValidationException this exception is thrown when the syntax of the Thing Description as ORM is incorrect
+// */
+// public static void validate(SecurityScheme securityScheme) throws SchemaValidationException {
+// ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
+// Validator validator = factory.getValidator();
+// Set> violations = validator.validate(securityScheme);
+// StringBuilder builder = new StringBuilder();
+// if(!violations.isEmpty()) {
+// violations.forEach(msg -> builder.append(msg.getMessage()).append("\n"));
+// throw new SecuritySchemeValidationException(builder.toString());
+// }
+//
+// }
+//
+// /**
+// * This method validates an instance of {@link PropertyAffordance}.
+// * @param propertyAffordance an instance of {@link PropertyAffordance}
+// * @throws SchemaValidationException this exception is thrown when the syntax of the Thing Description as ORM is incorrect
+// */
+// public static void validate(PropertyAffordance propertyAffordance) throws SchemaValidationException {
+// ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
+// Validator validator = factory.getValidator();
+// Set> violations = validator.validate(propertyAffordance);
+// StringBuilder builder = new StringBuilder();
+// if(!violations.isEmpty()) {
+// violations.forEach(msg -> builder.append(msg.getMessage()).append("\n"));
+// throw new PropertyAffordanceValidationException(builder.toString());
+// }
+// if(propertyAffordance.getForms()!=null) {
+// for(Form form:propertyAffordance.getForms()) {
+// validate(form);
+// }
+// }
+// if(propertyAffordance.getUriVariables()!=null) {
+// List schemas = new ArrayList<>(propertyAffordance.getUriVariables().values());
+// for(int index=0; index < schemas.size(); index++) {
+// DataSchema.validate(schemas.get(index));
+// }
+// }
+// }
+//
+// /**
+// * This method validates an instance of {@link InteractionAffordance}.
+// * @param interactionAffordance an instance of {@link InteractionAffordance}
+// * @throws SchemaValidationException this exception is thrown when the syntax of the Thing Description as ORM is incorrect
+// */
+// public static void validate(InteractionAffordance interactionAffordance) throws SchemaValidationException {
+// ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
+// Validator validator = factory.getValidator();
+// Set> violations = validator.validate(interactionAffordance);
+// StringBuilder builder = new StringBuilder();
+// if(!violations.isEmpty()) {
+// violations.forEach(msg -> builder.append(msg.getMessage()).append("\n"));
+// throw new InteractionAffordanceValidationException(builder.toString());
+// }
+// if(interactionAffordance.getForms()!=null) {
+// for(Form form: interactionAffordance.getForms()) {
+// validate(form);
+// }
+// }
+// if(interactionAffordance.getUriVariables()!=null) {
+// List schemas = new ArrayList<>(interactionAffordance.getUriVariables().values());
+// for(int index=0; index < schemas.size(); index++) {
+// DataSchema.validate(schemas.get(index));
+// }
+// }
+// }
+//
+// /**
+// * This method validates an instance of {@link EventAffordance}.
+// * @param eventAffordance an instance of {@link EventAffordance}
+// * @throws SchemaValidationException this exception is thrown when the syntax of the Thing Description as ORM is incorrect
+// */
+// public static void validate(EventAffordance eventAffordance) throws SchemaValidationException {
+// ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
+// Validator validator = factory.getValidator();
+// Set> violations = validator.validate(eventAffordance);
+// StringBuilder builder = new StringBuilder();
+// if(!violations.isEmpty()) {
+// violations.forEach(msg -> builder.append(msg.getMessage()).append("\n"));
+// throw new EventAffordanceValidationException(builder.toString());
+// }
+// if(eventAffordance.getForms()!=null) {
+// for(Form form:eventAffordance.getForms()) {
+// validate(form);
+// }
+// }
+// if(eventAffordance.getUriVariables()!=null) {
+// List schemas = new ArrayList<>(eventAffordance.getUriVariables().values());
+// for(int index=0; index < schemas.size(); index++) {
+// DataSchema.validate(schemas.get(index));
+// }
+// }
+// if(eventAffordance.getSubscription()!=null)
+// DataSchema.validate(eventAffordance.getSubscription());
+// if(eventAffordance.getData()!=null)
+// DataSchema.validate(eventAffordance.getData());
+// if(eventAffordance.getCancellation()!=null)
+// DataSchema.validate(eventAffordance.getCancellation());
+//
+// }
+//
+// /**
+// * This method validates an instance of {@link ActionAffordance}.
+// * @param actionAffordance an instance of {@link ActionAffordance}
+// * @throws SchemaValidationException this exception is thrown when the syntax of the Thing Description as ORM is incorrect
+// */
+// public static void validate(ActionAffordance actionAffordance) throws SchemaValidationException {
+// ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
+// Validator validator = factory.getValidator();
+// Set> violations = validator.validate(actionAffordance);
+// StringBuilder builder = new StringBuilder();
+// if(!violations.isEmpty()) {
+// violations.forEach(msg -> builder.append(msg.getMessage()).append("\n"));
+// throw new ActionAffordanceValidationException(builder.toString());
+// }
+// if(actionAffordance.getForms()!=null) {
+// for(Form form: actionAffordance.getForms()) {
+// validate(form);
+// }
+// }
+// if(actionAffordance.getUriVariables()!=null) {
+// List schemas = new ArrayList<>(actionAffordance.getUriVariables().values());
+// for(int index=0; index < schemas.size(); index++) {
+// DataSchema.validate(schemas.get(index));
+// }
+// }
+// }
+//
+// // -- Validation methods
+//
+// /**
+// * This method validates an instance of {@link VersionInfo}.
+// * @param versionInfo an instance of {@link VersionInfo}
+// * @throws SchemaValidationException this exception is thrown when the syntax of the Thing Description as ORM is incorrect
+// */
+// public static void validate(VersionInfo versionInfo) throws SchemaValidationException {
+// ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
+// Validator validator = factory.getValidator();
+// Set> violations = validator.validate(versionInfo);
+// if(!violations.isEmpty()) {
+// StringBuilder builder = new StringBuilder();
+// violations.forEach(msg -> builder.append(msg.getMessage()).append("\n"));
+// throw new VersionInfoValidationException(builder.toString());
+// }
+// }
+//
+// /**
+// * This method validates an instance of {@link Link}.
+// * @param link an instance of {@link Link}
+// * @throws SchemaValidationException this exception is thrown when the syntax of the Thing Description as ORM is incorrect
+// */
+// public static void validate(Link link) throws SchemaValidationException {
+// ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
+// Validator validator = factory.getValidator();
+// Set> violations = validator.validate(link);
+// if(!violations.isEmpty()) {
+// StringBuilder builder = new StringBuilder();
+// violations.forEach(msg -> builder.append(msg.getMessage()).append("\n"));
+// throw new LinkValidationException(builder.toString());
+// }
+// }
+//
+// /**
+// * This method validates an instance a {@link Form} object.
+// * @param form an instance of {@link Form}
+// * @throws SchemaValidationException this exception is thrown when the syntax of the Thing Description as ORM is incorrect
+// */
+// public static void validate(Form form) throws SchemaValidationException {
+// ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
+// Validator validator = factory.getValidator();
+// Set> violations = validator.validate(form);
+// StringBuilder builder = new StringBuilder();
+// if(!violations.isEmpty()) {
+// violations.forEach(msg -> builder.append(msg.getMessage()).append("\n"));
+// throw new FormValidationException(builder.toString());
+// }
+// // TODO: check that op is one of: readproperty, writeproperty, observeproperty, unobserveproperty, invokeaction, subscribeevent, unsubscribeevent, readallproperties, writeallproperties, readmultipleproperties, or writemultiplepropertie
+// // TODO: add to the documentation that OP can be only one of these values ==> transform op in enum?
+// // TODO: check that methodName is one REST operation, check if there are more
+// if(form.getResponse()!=null)
+// validate(form.getResponse());
+// }
+//
+//
+// /**
+// * This method validates an instance of {@link ExpectedResponse}.
+// * @param expectedResponse an instance of {@link ExpectedResponse}
+// * @throws SchemaValidationException this exception is thrown when the syntax of the Thing Description as ORM is incorrect
+// */
+// public static void validate(ExpectedResponse expectedResponse) throws SchemaValidationException {
+// ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
+// Validator validator = factory.getValidator();
+// Set> violations = validator.validate(expectedResponse);
+// if(!violations.isEmpty()) {
+// StringBuilder builder = new StringBuilder();
+// violations.forEach(msg -> builder.append(msg.getMessage()).append("\n"));
+// throw new ExpectedResponseValidationException(builder.toString());
+// }
+// }
+//
+// /**
+// * This method validates an instance of {@link DataSchema}.
+// * @param dataSchema an instance of {@link DataSchema}
+// * @throws SchemaValidationException this exception is thrown when the syntax of the Thing Description as ORM is incorrect
+// */
+// public static void validate(DataSchema dataSchema) throws SchemaValidationException {
+// ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
+// Validator validator = factory.getValidator();
+// Set> violations = validator.validate(dataSchema);
+// StringBuilder builder = new StringBuilder();
+// if(!violations.isEmpty()) {
+// violations.forEach(msg -> builder.append(msg.getMessage()).append("\n"));
+// throw new DataSchemaValidationException(builder.toString());
+// }
+// // TODO: validate nested objects
+// // TODO: validate other restrictions
+// }
+//
+}
diff --git a/src/main/java/wot/jtd/vocabulary/Vocabulary.java b/src/main/java/wot/jtd/Vocabulary.java
similarity index 51%
rename from src/main/java/wot/jtd/vocabulary/Vocabulary.java
rename to src/main/java/wot/jtd/Vocabulary.java
index 1782642..8299055 100755
--- a/src/main/java/wot/jtd/vocabulary/Vocabulary.java
+++ b/src/main/java/wot/jtd/Vocabulary.java
@@ -1,4 +1,4 @@
-package wot.jtd.vocabulary;
+package wot.jtd;
public class Vocabulary {
@@ -8,7 +8,8 @@ private Vocabulary() {
public static final String JSONLD_CONTEXT = "@context";
public static final String JSONLD_TYPE = "@type";
- public static final String TYPE = "type";
+ public static final String JSONLD_ID = "@id";
+ public static final String JSONLD_TYPE_ALIAS = "type";
public static final String STATUS_CODE_NUMBER = "htv:statusCodeNumber";
public static final String STATUS_CODE_VALUE = "htv:statusCodeValue";
@@ -17,7 +18,7 @@ private Vocabulary() {
public static final String SCOPES = "scopes";
public static final String OP = "op";
public static final String ITEMS = "items";
- public static final String CONS = "cons";
+ public static final String CONS = "const";
public static final String SECURITY = "security";
public static final String HEADER = "header";
@@ -25,5 +26,18 @@ private Vocabulary() {
// Default values
public static final String MIME_JSON = "application/json";
+ public static final String JSON_SCHEMA_URI = "https://www.w3.org/2019/wot/json-schema#";
+
+
+ // Security schemes
+
+ public static final String NO_SECURITY_SCHEME = "nosec";
+ public static final String BASIC_SECURITY_SCHEME = "basic";
+ public static final String DIGEST_SECURITY_SCHEME = "digest";
+ public static final String API_KEY_SECURITY_SCHEME = "apikey";
+ public static final String BEARER_SECURITY_SCHEME = "bearer";
+ public static final String PSK_SECURITY_SCHEME = "psk";
+ public static final String OAUTH2_SECURITY_SCHEME = "oauth2";
+
}
diff --git a/src/main/java/wot/jtd/exception/SchemaValidationException.java b/src/main/java/wot/jtd/exception/SchemaValidationException.java
index 3392ec7..8688c2a 100755
--- a/src/main/java/wot/jtd/exception/SchemaValidationException.java
+++ b/src/main/java/wot/jtd/exception/SchemaValidationException.java
@@ -1,5 +1,10 @@
package wot.jtd.exception;
+/**
+ * This exception is thrown when the syntax of the Thing Description is incorrect
+ * @author Andrea Cimmino
+ *
+ */
public class SchemaValidationException extends Exception{
private static final long serialVersionUID = 1L;
diff --git a/src/main/java/wot/jtd/model/AbstractJTDObject.java b/src/main/java/wot/jtd/model/AbstractJTDObject.java
deleted file mode 100755
index d12dc78..0000000
--- a/src/main/java/wot/jtd/model/AbstractJTDObject.java
+++ /dev/null
@@ -1,120 +0,0 @@
-package wot.jtd.model;
-
-import java.net.URI;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-import org.apache.jena.rdf.model.Model;
-import com.apicatalog.jsonld.JsonLdError;
-import com.fasterxml.jackson.annotation.JsonAnyGetter;
-import com.fasterxml.jackson.annotation.JsonAnySetter;
-import com.fasterxml.jackson.annotation.JsonIgnore;
-import com.fasterxml.jackson.annotation.JsonProperty;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.google.gson.JsonObject;
-import wot.jtd.JTD;
-import wot.jtd.RDFHandler;
-import wot.jtd.annotations.RdfDatatypeProperty;
-import wot.jtd.vocabulary.Vocabulary;
-
-
-public abstract class AbstractJTDObject {
-
- protected static final Logger LOGGER = Logger.getLogger(AbstractJTDObject.class.getName());
- static {
- LOGGER.setLevel(Level.WARNING);
- }
-
- // Shared common attributes
- @JsonProperty(Vocabulary.JSONLD_TYPE)
- @RdfDatatypeProperty(value="https://www.w3.org/2019/wot/td#type")
- protected Collection type;
- protected URI id;
-
-
- public Collection getType() {
- return type;
- }
- public void setType(Collection type) {
- this.type = type;
- }
-
- public URI getId() {
- return id;
- }
- public void setId(URI id) {
- this.id = id;
- }
-
-
-
- // Any other property outside the standard
- protected Map unknownProperties = new HashMap<>();
-
- @JsonIgnore
- protected Boolean hasExternalProperties = false;
-
- @JsonAnySetter
- public void setExternal(String name, Object value) {
- unknownProperties.put(name, value);
- hasExternalProperties = true;
- if(JTD.getShowExternalValuesWarnings())
- LOGGER.log( Level.WARNING, getWariningMessage());
- }
-
- private String getWariningMessage() {
- StringBuilder message = new StringBuilder();
- try {
- String clazzName = this.getClass().getSimpleName();
- ObjectMapper mapper = new ObjectMapper();
- String jsonUnknownProperties = mapper.writeValueAsString(unknownProperties);
- message.append("Json elements outside the standard found in '").append(clazzName).append("' : ").append(jsonUnknownProperties);
- }catch(Exception e) {
- e.printStackTrace();
- }
- return message.toString();
- }
-
- @JsonAnyGetter
- public Map getExternal() {
- return unknownProperties;
- }
-
- public Model toRDF(JsonObject td) throws JsonLdError {
- RDFHandler handler = new RDFHandler();
- return handler.toRDF(td);
- }
-
-
-
- // -- Hash code and equals
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + ((unknownProperties == null) ? 0 : unknownProperties.hashCode());
- return result;
- }
- @Override
- public boolean equals(Object obj) {
- if (this == obj)
- return true;
- if (obj == null)
- return false;
- if (getClass() != obj.getClass())
- return false;
- AbstractJTDObject other = (AbstractJTDObject) obj;
- if (unknownProperties == null) {
- if (other.unknownProperties != null)
- return false;
- } else if (!unknownProperties.equals(other.unknownProperties))
- return false;
- return true;
- }
-
-
-}
diff --git a/src/main/java/wot/jtd/model/AbstractRdfObject.java b/src/main/java/wot/jtd/model/AbstractRdfObject.java
new file mode 100755
index 0000000..011285f
--- /dev/null
+++ b/src/main/java/wot/jtd/model/AbstractRdfObject.java
@@ -0,0 +1,115 @@
+package wot.jtd.model;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import com.fasterxml.jackson.annotation.JsonAlias;
+import com.fasterxml.jackson.annotation.JsonAnyGetter;
+import com.fasterxml.jackson.annotation.JsonAnySetter;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import kehio.annotations.RdfContainer;
+import kehio.annotations.RdfId;
+import kehio.annotations.RdfObject;
+import kehio.annotations.RdfUrlMap;
+import wot.jtd.Vocabulary;
+
+
+public class AbstractRdfObject {
+
+
+ @JsonProperty(Vocabulary.JSONLD_TYPE)
+ @RdfObject(value="http://www.w3.org/1999/02/22-rdf-syntax-ns#type", base="https://www.w3.org/2019/wot/td#")
+ protected Collection type;
+
+ @RdfId
+ @JsonProperty("id")
+ @JsonAlias(Vocabulary.JSONLD_ID)
+ protected String id;
+
+ @RdfContainer(ignore= {"https://www.w3.org/2019/wot/td#name", "https://www.w3.org/2019/wot/json-schema#propertyName"},
+ prefixes= {@RdfUrlMap(key="htv", value="http://www.w3.org/2011/http#")},
+ aliases={ //@RdfUrlMap(key="type", value="http://www.w3.org/1999/02/22-rdf-syntax-ns#type"),
+ @RdfUrlMap(key="@type", value="http://www.w3.org/1999/02/22-rdf-syntax-ns#type")
+ },
+ identifiers={ "id"}
+ )
+ @JsonIgnore
+ protected Map externalProperties = new HashMap<>();
+
+ // -- Getters & Setters
+
+ public Collection getType() {
+ return type;
+ }
+ public void setType(Collection type) {
+ this.type = type;
+ }
+
+ public String getId() {
+ return id;
+ }
+ public void setId(String id) {
+ this.id = id;
+ }
+
+
+ // ---
+
+ @JsonAnySetter
+ public void setExternalProperties(String name, Object value) {
+ externalProperties.put(name, value);
+ }
+
+ @JsonAnyGetter
+ public Map getExternalProperties() {
+ return externalProperties;
+ }
+
+ // -- Hash code & Equals
+
+ // -- hash code sub-methods
+
+ protected int superHashcode(int subResult, int prime) {
+ int result = subResult;
+ result = prime * result + ((id == null) ? 0 : id.hashCode());
+ result = prime * result + ((type == null) ? 0 : type.hashCode());
+ result = prime * result + ((externalProperties == null) ? 0 : externalProperties.hashCode());
+ return result;
+ }
+
+ // -- equals sub-methods
+
+ protected boolean sameSuperAttributes(AbstractRdfObject abstractRdfObject) {
+ Boolean sameAttributes = sameCollections(abstractRdfObject.getType(), abstractRdfObject.getType());
+ sameAttributes &= sameAttribute(this.id, abstractRdfObject.getId());
+ sameAttributes &= sameMap(this.externalProperties, abstractRdfObject.getExternalProperties());
+ return sameAttributes;
+ }
+
+ protected boolean sameCollections(Collection> collection1, Collection> collection2) {
+ Boolean sameCollections = collection1!=null && collection2!=null;
+ if(sameCollections)
+ sameCollections &= collection1.containsAll(collection2) && collection2.containsAll(collection1);
+ sameCollections |= collection1==null && collection2==null;
+ return sameCollections;
+ }
+
+ protected boolean sameMap(Map,?> map1, Map,?> map2) {
+ Boolean sameMaps = map1!=null && map2!=null && map1.size() == map2.size() ;
+ if(sameMaps) {
+ Set> entries1 = map1.entrySet();
+ Set> entries2 = map2.entrySet();
+ sameMaps = sameCollections(entries1, entries2);
+ }
+ sameMaps |= map1==null && map2==null;
+ return sameMaps;
+ }
+
+ protected boolean sameAttribute(Object attribute1, Object attribute2) {
+ return (attribute1!=null && attribute2!=null && attribute1.equals(attribute2)) || (attribute1==null && attribute2==null);
+ }
+
+
+}
diff --git a/src/main/java/wot/jtd/model/AbstractRdfObject_Prev.java b/src/main/java/wot/jtd/model/AbstractRdfObject_Prev.java
new file mode 100644
index 0000000..7fb7dc9
--- /dev/null
+++ b/src/main/java/wot/jtd/model/AbstractRdfObject_Prev.java
@@ -0,0 +1,110 @@
+package wot.jtd.model;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import com.fasterxml.jackson.annotation.JsonAlias;
+import com.fasterxml.jackson.annotation.JsonAnyGetter;
+import com.fasterxml.jackson.annotation.JsonAnySetter;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import kehio.annotations.RdfPropertiesContainer;
+import kehio.annotations.RdfUrlMap;
+import kehio.annotations.RdfId;
+import kehio.annotations.RdfObject;
+import wot.jtd.Vocabulary;
+
+
+public class AbstractRdfObject_Prev {
+
+
+ @JsonProperty(Vocabulary.JSONLD_TYPE)
+ @RdfObject(value="http://www.w3.org/1999/02/22-rdf-syntax-ns#type", base="https://www.w3.org/2019/wot/td#")
+ protected Collection type;
+
+ @RdfId
+ @JsonProperty("id")
+ @JsonAlias(Vocabulary.JSONLD_ID)
+ protected String id;
+
+ // Any other property outside the standard
+ @RdfPropertiesContainer(ignore= {"https://www.w3.org/2019/wot/td#name", "https://www.w3.org/2019/wot/json-schema#propertyName"},
+ prefixes= {
+ @RdfUrlMap(key="htv", value="http://www.w3.org/2011/http#"),
+ })
+ protected Map unknownProperties = new HashMap<>();
+
+ // -- Getters & Setters
+
+ public Collection getType() {
+ return type;
+ }
+ public void setType(Collection type) {
+ this.type = type;
+ }
+
+ public String getId() {
+ return id;
+ }
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ @JsonAnySetter
+ public void setExternal(String name, Object value) {
+ unknownProperties.put(name, value.toString());
+ }
+
+ @JsonAnyGetter
+ public Map getExternal() {
+ return unknownProperties;
+ }
+
+
+ // -- Hash code & Equals
+
+ // -- hash code sub-methods
+
+ protected int superHashcode(int subResult, int prime) {
+ int result = subResult;
+ result = prime * result + ((id == null) ? 0 : id.hashCode());
+ result = prime * result + ((type == null) ? 0 : type.hashCode());
+ result = prime * result + ((unknownProperties == null) ? 0 : unknownProperties.hashCode());
+ return result;
+ }
+
+ // -- equals sub-methods
+
+ protected boolean sameSuperAttributes(AbstractRdfObject_Prev abstractRdfObject) {
+ Boolean sameAttributes = sameCollections(abstractRdfObject.getType(), abstractRdfObject.getType());
+ sameAttributes &= sameAttribute(this.id, abstractRdfObject.getId());
+ sameAttributes &= sameMap(this.unknownProperties, abstractRdfObject.getExternal());
+ return sameAttributes;
+ }
+
+ protected boolean sameCollections(Collection> collection1, Collection> collection2) {
+ Boolean sameCollections = collection1!=null && collection2!=null;
+ if(sameCollections)
+ sameCollections &= collection1.containsAll(collection2) && collection2.containsAll(collection1);
+ sameCollections |= collection1==null && collection2==null;
+ return sameCollections;
+ }
+
+ protected boolean sameMap(Map,?> map1, Map,?> map2) {
+ Boolean sameMaps = map1!=null && map2!=null && map1.size() == map2.size() ;
+ if(sameMaps) {
+ Set> entries1 = map1.entrySet();
+ Set> entries2 = map2.entrySet();
+ sameMaps = sameCollections(entries1, entries2);
+ }
+ sameMaps |= map1==null && map2==null;
+ return sameMaps;
+ }
+
+ protected boolean sameAttribute(Object attribute1, Object attribute2) {
+ return (attribute1!=null && attribute2!=null && attribute1.equals(attribute2)) || (attribute1==null && attribute2==null);
+ }
+
+
+}
diff --git a/src/main/java/wot/jtd/model/ActionAffordance.java b/src/main/java/wot/jtd/model/ActionAffordance.java
deleted file mode 100755
index db2bd9e..0000000
--- a/src/main/java/wot/jtd/model/ActionAffordance.java
+++ /dev/null
@@ -1,308 +0,0 @@
-package wot.jtd.model;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import javax.validation.ConstraintViolation;
-import javax.validation.Validation;
-import javax.validation.Validator;
-import javax.validation.ValidatorFactory;
-import javax.validation.constraints.NotEmpty;
-import com.fasterxml.jackson.annotation.JsonInclude;
-import com.fasterxml.jackson.annotation.JsonProperty;
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.google.gson.JsonObject;
-import wot.jtd.JTD;
-import wot.jtd.exception.ActionAffordanceValidationException;
-import wot.jtd.exception.SchemaValidationException;
-import wot.jtd.vocabulary.Vocabulary;
-
-/**
- * This class implements the object ActionAffordance from a Thing Description as specified in the Web of Things (WoT) documentation.
- * @see ActionAffordance WoT documentation
- * @author Andrea Cimmino
- */
-@JsonInclude(JsonInclude.Include.NON_NULL)
-public class ActionAffordance extends AbstractJTDObject{
-
- // -- attributes
-
- @NotEmpty(message="'forms' in any InteractionAffordance (PropertyAffordance, ActionAffordance, or EventAffordance) must not be empty")
- private Collection