View Javadoc

1   package eu.ehri.project.exporters.cvoc;
2   
3   import com.google.common.collect.ImmutableMap;
4   import com.google.common.collect.Lists;
5   import com.google.common.collect.Sets;
6   import com.tinkerpop.blueprints.Direction;
7   import com.tinkerpop.frames.Adjacency;
8   import com.tinkerpop.frames.Property;
9   import eu.ehri.project.models.EntityClass;
10  import eu.ehri.project.models.annotations.InverseOf;
11  import eu.ehri.project.models.annotations.Mandatory;
12  import eu.ehri.project.models.utils.ClassUtils;
13  import org.apache.jena.datatypes.xsd.XSDDatatype;
14  import org.apache.jena.rdf.model.Model;
15  import org.apache.jena.rdf.model.ModelFactory;
16  import org.apache.jena.rdf.model.RDFNode;
17  import org.apache.jena.rdf.model.Resource;
18  import org.apache.jena.vocabulary.OWL;
19  import org.apache.jena.vocabulary.RDF;
20  import org.apache.jena.vocabulary.RDFS;
21  import org.apache.jena.vocabulary.XSD;
22  
23  import java.io.OutputStream;
24  import java.io.UnsupportedEncodingException;
25  import java.lang.reflect.Method;
26  import java.lang.reflect.ParameterizedType;
27  import java.util.Collections;
28  import java.util.List;
29  import java.util.Map;
30  import java.util.Set;
31  
32  /**
33   * Dump an RDF schema + OWL representation of the model schema.
34   */
35  public class SchemaExporter {
36  
37      private final String rdfFormat;
38  
39      public SchemaExporter(String rdfFormat) {
40          this.rdfFormat = rdfFormat;
41      }
42  
43      public final static String DEFAULT_BASE_URI = "http://data.ehri-project.eu/ontology/";
44      public final static Map<String, String> NAMESPACES = ImmutableMap.<String, String>builder()
45              .put("owl", OWL.getURI())
46              .put("xsd", XSD.getURI())
47              .put("rdfs", RDFS.getURI())
48              .put("ehri", DEFAULT_BASE_URI)
49              .build();
50  
51      public void dumpSchema(OutputStream outputStream, String baseUri) throws UnsupportedEncodingException {
52  
53          Model model = ModelFactory.createDefaultModel();
54          model.setNsPrefixes(NAMESPACES);
55  
56          Set<Class<?>> baseClasses = Sets.newHashSet();
57  
58          for (EntityClass entityClass : EntityClass.values()) {
59              Collections.addAll(baseClasses, entityClass.getJavaClass().getInterfaces());
60          }
61  
62          for (Class<?> sup : baseClasses) {
63              dumpEntityClass(model, sup);
64          }
65  
66          for (EntityClass entityClass : EntityClass.values()) {
67              Class<?> cls = entityClass.getJavaClass();
68              Resource shape = dumpEntityClass(model, cls);
69  
70              for (Class<?> sup : cls.getInterfaces()) {
71                  model.add(shape, RDFS.subClassOf,
72                          model.createResource(DEFAULT_BASE_URI + sup.getSimpleName()));
73              }
74          }
75  
76          model.getWriter(rdfFormat).write(model, outputStream, baseUri);
77      }
78  
79      // Helpers
80      private XSDDatatype typeOf(Class<?> cls) {
81          if (Number.class.isAssignableFrom(cls)) {
82              return XSDDatatype.XSDinteger;
83          } else if (Boolean.class.isAssignableFrom(cls)) {
84              return XSDDatatype.XSDboolean;
85          } else {
86              return XSDDatatype.XSDstring;
87          }
88      }
89  
90      private Resource dumpEntityClass(Model model, Class<?> cls) {
91          String name = cls.getSimpleName();
92  
93          // Create the rdfClass...
94          Resource rdfClass = model.createResource(DEFAULT_BASE_URI + name);
95          model.add(rdfClass, RDF.type, OWL.Class);
96          model.add(rdfClass, RDFS.label, name);
97  
98          for (Method method : cls.getDeclaredMethods()) {
99              Property property = method.getAnnotation(Property.class);
100             Class<?> returnType = method.getReturnType();
101 
102             if (property != null) {
103                 String propertyName = property.value();
104                 boolean mandatory = method.getAnnotation(Mandatory.class) != null;
105                 addDatatypeProperty(model, rdfClass, propertyName, returnType, mandatory);
106             } else {
107                 Adjacency adjacency = method.getAnnotation(Adjacency.class);
108                 InverseOf inverseOf = method.getAnnotation(InverseOf.class);
109                 if (adjacency != null
110                         && method.getName().startsWith(ClassUtils.FETCH_METHOD_PREFIX)
111                         && (adjacency.direction().equals(Direction.OUT) || inverseOf != null)) {
112                     boolean mandatory = method.getAnnotation(Mandatory.class) != null;
113                     String label = inverseOf != null ? inverseOf.value() : adjacency.label();
114                     addObjectProperty(model, rdfClass, method, returnType, label, mandatory);
115                 }
116             }
117         }
118 
119         return rdfClass;
120     }
121 
122     private void addObjectProperty(Model model, Resource rdfClass, Method method,
123             Class<?> returnType, String name, boolean mandatory) {
124         boolean collection = Iterable.class.isAssignableFrom(returnType);
125         if (collection) {
126             // This is (probably) an iterable frame rel
127             ParameterizedType type = (ParameterizedType) method.getGenericReturnType();
128             returnType = (Class<?>) type.getActualTypeArguments()[0];
129             mandatory = false;
130         }
131 
132         Resource propResource = model.createResource(DEFAULT_BASE_URI + name);
133         model.add(propResource, RDF.type, OWL.ObjectProperty);
134         model.add(propResource, RDFS.domain, rdfClass);
135         model.add(propResource, RDFS.range,
136                 model.createResource(DEFAULT_BASE_URI + returnType.getSimpleName()));
137 
138         Resource restriction = model.createResource();
139         model.add(restriction, RDF.type, OWL.Restriction);
140         model.add(restriction, OWL.onProperty, propResource);
141         model.add(restriction, OWL.allValuesFrom,
142                 model.createResource(DEFAULT_BASE_URI + returnType.getSimpleName()));
143         if (mandatory) {
144             model.add(restriction, OWL.cardinality,
145                     model.createTypedLiteral(1, XSDDatatype.XSDnonNegativeInteger));
146         }
147         model.add(rdfClass, RDFS.subClassOf, restriction);
148     }
149 
150     private void addDatatypeProperty(Model model, Resource rdfClass,
151             String propertyName, Class<?> type, boolean mandatory) {
152         Resource propResource = model.createResource(DEFAULT_BASE_URI + propertyName);
153         model.add(propResource, RDF.type, OWL.DatatypeProperty);
154 
155         model.add(propResource, RDFS.domain, rdfClass);
156         model.add(propResource, RDFS.range,
157                 model.createResource(typeOf(type).getURI()));
158 
159         Resource restriction = model.createResource();
160         model.add(restriction, RDF.type, OWL.Restriction);
161         model.add(restriction, OWL.onProperty, propResource);
162         model.add(restriction, mandatory ? OWL.cardinality : OWL.maxCardinality,
163                 model.createTypedLiteral(1, XSDDatatype.XSDnonNegativeInteger));
164         model.add(rdfClass, RDFS.subClassOf, restriction);
165 
166         if (type.isEnum()) {
167             List<RDFNode> inValues = Lists.newArrayList();
168             for (Object constValue : type.getEnumConstants()) {
169                 inValues.add(model.createLiteral(constValue.toString()));
170             }
171             model.add(propResource, OWL.oneOf, model.createList(inValues.iterator()));
172         }
173     }
174 }