View Javadoc
1   /*
2    * #%L
3    * SUMARiS
4    * %%
5    * Copyright (C) 2019 SUMARiS Consortium
6    * %%
7    * This program is free software: you can redistribute it and/or modify
8    * it under the terms of the GNU General Public License as
9    * published by the Free Software Foundation, either version 3 of the
10   * License, or (at your option) any later version.
11   *
12   * This program is distributed in the hope that it will be useful,
13   * but WITHOUT ANY WARRANTY; without even the implied warranty of
14   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15   * GNU General Public License for more details.
16   *
17   * You should have received a copy of the GNU General Public
18   * License along with this program.  If not, see
19   * <http://www.gnu.org/licenses/gpl-3.0.html>.
20   * #L%
21   */
22  
23  package net.sumaris.rdf.util;
24  
25  import org.apache.jena.ontology.OntClass;
26  import org.apache.jena.ontology.OntModel;
27  import org.apache.jena.ontology.OntProperty;
28  import org.apache.jena.rdf.model.*;
29  import org.apache.jena.vocabulary.OWL;
30  import org.apache.jena.vocabulary.RDF;
31  import org.apache.jena.vocabulary.RDFS;
32  import org.slf4j.Logger;
33  import org.slf4j.LoggerFactory;
34  
35  import javax.persistence.EntityManager;
36  import javax.persistence.Id;
37  import java.lang.annotation.Annotation;
38  import java.lang.reflect.Field;
39  import java.lang.reflect.Method;
40  import java.lang.reflect.ParameterizedType;
41  import java.lang.reflect.Type;
42  import java.util.*;
43  import java.util.stream.Stream;
44  import static net.sumaris.rdf.util.OwlUtils.*;
45  
46  public class Bean2Owl {
47  
48      /**
49       * Logger.
50       */
51      private static Logger LOG = LoggerFactory.getLogger(Bean2Owl.class);
52  
53      private String modelPrefix;
54  
55      public Bean2Owl(String modelPrefix) {
56          this.modelPrefix = modelPrefix;
57      }
58  
59      protected String getModelPrefix() {
60          return modelPrefix;
61      }
62  
63      /**
64       * Returns and adds OntClass to the model
65       *
66       * @param ontology
67       * @param clazz
68       * @return
69       */
70      public OntClass classToOwl(OntModel ontology, Class clazz, Map<OntClass, List<OntClass>> mutuallyDisjoint, boolean addInterface, boolean addMethods) {
71  
72          Resource schema = ontology.listSubjectsWithProperty(RDF.type, OWL.Ontology).nextResource();
73  
74          try {
75              //OntResource r = m.createOntResource(SCHEMA_URL+ ent.getName().replaceAll("\\.", "/"));
76              //OntProperty pred = m.createOntProperty(SCHEMA_URL+ ent.getName().replaceAll("\\.", "/"));
77              //String classBase = ent.getName().replaceAll("\\.", "/");
78  //                        LOG.info("Building Ont of " + classBase);
79  
80              OntClass aClass = ontology.createClass(classToURI(schema, clazz));
81              aClass.setIsDefinedBy(schema);
82  
83              //aClass.addSameAs(ontology.createResource(classToURI(schema, ent)));
84  
85              String label = clazz.getName().substring(clazz.getName().lastIndexOf(".") + 1);
86              //aClass.addLabel(label + "e", "fr");
87              aClass.addLabel(label, "en");
88  
89              String entityName = clazz.getName();
90              if (!"".equals(entityName))
91                  aClass.addComment(entityName, "en");
92  
93  
94              if (mutuallyDisjoint != null && addInterface) {
95                  Stream.of(clazz.getGenericInterfaces())
96                          .filter(interfaze -> !ParameterizedType.class.equals(interfaze))
97                          .forEach(interfaze -> {
98                              // LOG.info(interfaze+" "+Class.class + " " +  interfaze.getClass() );
99                              OntClass r = typeToUri(schema, interfaze);
100                             if (!mutuallyDisjoint.containsKey(r)) {
101                                 mutuallyDisjoint.put(r, new ArrayList<>());
102                             }
103                             mutuallyDisjoint.get(r).add(aClass);
104 
105                             aClass.addSuperClass(r);
106                         });
107             }
108 
109 
110             Stream.of(clazz.getGenericSuperclass())
111                     .filter(c -> c != null && !Object.class.equals(c))
112                     .forEach(i -> {
113                                 OntClass ont = typeToUri(schema, i);
114                                 aClass.addSuperClass(ont);
115                             }
116                     );
117 
118 
119             if (addMethods) {
120                 Stream.of(clazz.getMethods())
121                         .filter(m -> !isSetter(m))
122                         .filter(m -> !isGetter(m))
123                         .filter(m -> Stream.of("getBytes", "hashCode", "getClass", "toString", "equals", "wait", "notify", "notifyAll").noneMatch(s -> s.equals(m.getName())))
124 
125                         .forEach(met -> {
126 
127                             String name = classToURI(schema, clazz) + "#" + met.getName();
128                             OntProperty function = ontology.createObjectProperty(name, true);
129                             function.addDomain(aClass.asResource());
130                             if (isJavaType(met.getReturnType())) {
131                                 function.addRange(getStdType(met.getReturnType()));
132                             } else {
133                                 OntClass o = typeToUri(schema, met.getReturnType());
134                                 if (o.getURI().endsWith("void"))
135                                     function.addRange(RDFS.Literal);
136                                 else
137                                     function.addRange(o);
138                             }
139                             function.setIsDefinedBy(schema);
140                             function.addLabel(met.getName(), "en");
141                         });
142             }
143 
144 
145             Stream.of(clazz.getMethods())
146                     .filter(OwlUtils::isGetter)
147                     .map(OwlUtils::getFieldOfGetteR)
148                     .forEach(field -> {
149                         //LOG.info("processing Field : " + field + " - type?" + isJavaType(field) + " - list?" + isListType(field.getGenericType()));
150                         String fieldName = classToURI(schema, clazz) + "#" + field.getName();
151 
152                         if (isJavaType(field)) {
153                             Resource type = getStdType(field);
154 
155                             OntProperty stdType = ontology.createDatatypeProperty(fieldName, true);
156 
157                             stdType.setDomain(aClass.asResource());
158                             stdType.setRange(type);
159                             stdType.setIsDefinedBy(schema);
160 
161                             //link.addRDFType(ontology.createResource(type));
162                             stdType.addLabel(field.getName(), "en");
163                             //LOG.info("Simple property of type " + type + " for " + fieldName + "\n" + link);*
164 
165                         } else if (field.getDeclaringClass().isArray()) {
166 
167                         } else if (isListType(field.getGenericType())) {
168                             Type contained = getListType(field.getGenericType());
169                             OntProperty list = null;
170                             Resource resou = null;
171                             //LOG.info("List property " + fieldName + " " + contained.getTypeName());
172 
173                             if (isJavaType(contained)) {
174                                 list = ontology.createDatatypeProperty(fieldName, true);
175                                 resou = getStdType(contained);
176                             } else {
177                                 list = ontology.createObjectProperty(fieldName, false);
178                                 resou = typeToUri(schema, contained);
179                             }
180 
181                             list.addRange(resou);
182                             list.addDomain(aClass.asResource());
183                             list.setIsDefinedBy(schema);
184                             list.addLabel("list" + field.getName(), "en");
185 
186                             createZeroToMany(ontology, aClass, list, resou);
187 
188                         } else {
189 
190                             // var type = ontology.createObjectProperty();
191                             OntProperty bean = ontology.createObjectProperty(fieldName, true);
192                             bean.addDomain(aClass.asResource());
193                             bean.addRange(typeToUri(schema, field.getType()));
194                             bean.setIsDefinedBy(schema);
195                             bean.addLabel(field.getName(), "en");
196                             // LOG.info("Default Object property x " + link);
197 
198                         }
199                     });
200             return aClass;
201         } catch (Exception e) {
202             LOG.error(e.getMessage(), e);
203         }
204         return null;
205     }
206 
207     protected OntClass typeToUri(Resource schema, Type t) {
208 
209         OntModel model = ((OntModel) schema.getModel());
210 
211         String uri = schema + t.getTypeName();
212         if (t instanceof ParameterizedType) {
213             uri = uri.substring(0, uri.indexOf("<"));
214         }
215 
216         uri = uri.substring(uri.lastIndexOf(".") + 1);
217 
218         OntClass ont = model.getOntClass(uri);
219 
220         if (ont == null) {
221 
222             String name = t.getTypeName();
223             name = name.substring(name.lastIndexOf(".") + 1);
224 
225             ont = model.createClass(schema + name);
226         }
227 
228         ont.setIsDefinedBy(schema);
229         ont.addComment(t.getTypeName(), "en");
230         //ont.addLabel(t.getTypeName(), "en");
231 
232         return ont;
233 
234     }
235 
236     protected OntClass interfaceToOwl(OntModel model, Type type) {
237 
238         String name = type.getTypeName();
239         name = name.substring(name.lastIndexOf(".") + 1);
240 
241         return model.createClass(getModelPrefix() + name);
242     }
243 
244     public Resource bean2Owl(OntModel model, Object obj, int depth,
245                              List<Method> includes,
246                              List<Method> excludes) {
247         Resource schema = model.listSubjectsWithProperty(RDF.type, OWL.Ontology).nextResource();
248 
249         if (obj == null) {
250             LOG.error("bean2Owl received a null object as parameter");
251             return null;
252         }
253         String classURI = classToURI(schema, obj.getClass());
254         OntClass ontClazz = model.getOntClass(classURI);
255         if (ontClazz == null) {
256             LOG.warn("ontClazz " + ontClazz + ", not found in model, mak" +
257                     "ing one at " + classURI);
258             ontClazz = classToOwl(model, obj.getClass(), null, true, true);
259         }
260 
261         // try using the ID field if exists to represent the node
262         String individualURI;
263         try {
264             Method m = findGetterAnnotatedID(obj.getClass());
265             individualURI = classURI + "#" + m.invoke(obj);
266             //LOG.info("Created objectIdentifier " + individualURI);
267         } catch (Exception e) {
268             individualURI = "";
269             LOG.error(e.getClass().getName() + " bean2Owl " + classURI + " - ");
270         }
271         Resource individual = ontClazz.createIndividual(individualURI); // model.createResource(node);
272         if (depth < 0) {
273             LOG.error("Max depth reached " + depth);
274             return individual;
275         } else {
276             // LOG.warn("depth reached " + depth);
277 
278         }
279 
280 
281         // Handle Methods
282         Stream.of(obj.getClass().getMethods())
283                 .filter(OwlUtils::isGetter)
284                 .filter(met -> excludes.stream().noneMatch(x -> x.equals(met)))
285                 .filter(met -> {
286                     //LOG.info(" filtering on " + met +"  " +  WHITELIST.contains(met) + " "+ !isManyToOne(met) ) ;
287 
288                     // LOG.info(" -- " + WHITELIST.size() + "  " + BLACKLIST.size() + "  " + URI_2_CLASS.size());
289 
290                     return (!isManyToOne(met) || includes.contains(met));
291                 })
292                 .forEach(met -> {
293 
294                     //LOG.info("processing method " + met.getDeclaringClass().getSimpleName()+"."+ met.getName()+" "+met.getGenericReturnType());
295                     try {
296                         Object invoked = met.invoke(obj);
297                         if (invoked == null) {
298                             //LOG.warn("invoked function null "+ met.getName() + " skipping... " );
299                             return;
300                         }
301                         Property pred = model.createProperty(classURI, "#" + met.getName().replace("get", ""));
302 
303                         if (isId(met)) {
304                             individual.addProperty(pred, invoked + "");
305                         } else if ("getClass".equals(met.getName())) {
306                             individual.addProperty(RDF.type, pred);
307                         } else if (invoked.getClass().getCanonicalName().contains("$")) {
308                             //skip inner classes. mostly handles generated code issues
309                         } else if (!isJavaType(met)) {
310                             //LOG.warn("recurse for " + met.getName());
311                             //LOG.info("not java generic, recurse on node..." + invoked);
312                             Resource recurse = bean2Owl(model, invoked, (depth - 1), includes, excludes);
313                             if (recurse != null)
314                                 individual.addProperty(pred, recurse);
315                         } else if (met.getGenericReturnType() instanceof ParameterizedType) {
316 
317                             Resource anonId = model.createResource(new AnonId("params" + new Random().nextInt(1000000)));
318                             //individual.addProperty( pred, anonId);
319 
320                             Optional<Resource> listNode = fillDataList(model, met.getGenericReturnType(), invoked, pred, anonId, depth - 1,
321                                     includes,
322                                     excludes);
323                             if (listNode.isPresent()) {
324                                 LOG.info(" --and res  : " + listNode.get().getURI());
325                                 individual.addProperty(pred, listNode.get());
326                             }
327                             //
328                         } else {
329                             if (met.getName().toLowerCase().contains("date")) {
330                                 //String d = DATE_TIME_FORMATTER.format(((Date) invoked).toInstant());
331                                 individual.addProperty(pred, SIMPLE_DATE_FORMAT.format((Date) invoked));
332 
333                             } else {
334                                 individual.addProperty(pred, invoked + "");
335                             }
336                         }
337 
338                     } catch (Exception e) {
339                         LOG.error(e.getMessage(), e);
340                     }
341 
342                 });
343 
344         return individual;
345     }
346 
347 
348     protected Method findGetterAnnotatedID(Class clazz) {
349         for (Field f : clazz.getDeclaredFields())
350             for (Annotation an : f.getDeclaredAnnotations())
351                 if (an instanceof Id)
352                     return getterOfField(clazz, f.getName());
353 
354         return null;
355     }
356 
357     protected Optional<Resource> fillDataList(OntModel model, Type type, Object listObject, Property prop, Resource fieldId, int depth,
358                                             List<Method> includes,
359                                             List<Method> excludes) {
360 
361         if (isListType(type)) {
362 
363 // Create a list containing the subjects of the role assignments in one go
364 
365             List<RDFNode> nodes = new ArrayList<>();
366             List<? extends Object> asList = castListSafe((List<? extends Object>) listObject, Object.class);
367 
368             if (asList.isEmpty()) {
369                 LOG.warn(" - empty list, ignoring ");
370                 return Optional.empty();
371             }
372             for (Object x : asList) {
373                 Resource listItem = bean2Owl(model, x, (depth - 1), includes, excludes);
374                 nodes.add(listItem);
375 
376             }
377 
378             RDFList list = model.createList(nodes.toArray(new RDFNode[nodes.size()]));
379 
380             LOG.info("  - rdflist " + list.size() + " : " + list);
381 //var tmp = model.createProperty("sdfsdfsdf"+new Random().nextInt(10000000));
382             fieldId.addProperty(prop, list);
383             //              fieldId.addProperty(tmp ,model.createList(list));
384             return Optional.of(list);
385 
386         }
387 
388 //        }
389         return Optional.empty();
390     }
391 
392     /**
393      * Performs a forced cast.
394      * Returns null if the collection type does not match the items in the list.
395      *
396      * @param data     The list to cast.
397      * @param listType The type of list to cast to.
398      */
399     protected <T> List<? super T> castListSafe(List<?> data, Class<T> listType) {
400         List<T> retval = null;
401         //This test could be skipped if you trust the callers, but it wouldn't be safe then.
402         if (data != null && !data.isEmpty() && listType.isInstance(data.iterator().next().getClass())) {
403             LOG.info("  - castListSafe passed check ");
404 
405             @SuppressWarnings("unchecked")//It's OK, we know List<T> contains the expected type.
406                     List<T> foo = (List<T>) data;
407             return foo;
408         }
409 
410         LOG.info("  - castListSafe failed check  forcing it though");
411         return (List<T>) data;
412     }
413 
414 
415 }