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 com.google.common.collect.ImmutableMap;
26  import org.apache.jena.ontology.OntClass;
27  import org.apache.jena.ontology.OntModel;
28  import org.apache.jena.ontology.OntProperty;
29  import org.apache.jena.rdf.model.Model;
30  import org.apache.jena.rdf.model.Resource;
31  import org.apache.jena.vocabulary.*;
32  import org.slf4j.Logger;
33  import org.slf4j.LoggerFactory;
34  
35  import javax.persistence.Id;
36  import javax.persistence.ManyToOne;
37  import java.io.ByteArrayOutputStream;
38  import java.io.IOException;
39  import java.lang.annotation.Annotation;
40  import java.lang.reflect.Field;
41  import java.lang.reflect.Method;
42  import java.lang.reflect.ParameterizedType;
43  import java.lang.reflect.Type;
44  import java.sql.Timestamp;
45  import java.text.SimpleDateFormat;
46  import java.time.Instant;
47  import java.time.LocalDate;
48  import java.time.LocalDateTime;
49  import java.time.ZoneId;
50  import java.time.format.DateTimeFormatter;
51  import java.util.*;
52  import java.util.stream.Collectors;
53  import java.util.stream.Stream;
54  
55  public abstract class OwlUtils {
56  
57      private static Logger log = LoggerFactory.getLogger(OwlUtils.class);
58  
59      public static String ADAGIO_PREFIX = "http://www.e-is.pro/2019/03/adagio/";
60      public static ZoneId ZONE_ID = ZoneId.systemDefault();
61      public static DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.S");
62      public static SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.S");
63  
64      public static Method getterOfField(Class t, String field) {
65          try {
66              Method res = t.getMethod("get" + field.substring(0, 1).toUpperCase() + field.substring(1));
67              return res;
68          } catch (NoSuchMethodException e) {
69              log.error("error in the declaration of allowed ManyToOne " + e.getMessage());
70          }
71          return null;
72      }
73  
74      public static Optional<Method> setterOfField(Resource schema, Class t, String field, RdfImportContext context) {
75          try {
76              Optional<Field> f = fieldOf(schema, t, field, context);
77              if (f.isPresent()) {
78                  String setterName = "set" + f.get().getName().substring(0, 1).toUpperCase() + f.get().getName().substring(1);
79                  //log.info("setterName " + setterName);
80                  Method met = t.getMethod(setterName, f.get().getType());
81                  return Optional.of(met);
82              }
83  
84          } catch (NoSuchMethodException e) {
85              log.warn("NoSuchMethodException setterOfField " + field);
86          } catch (NullPointerException e) {
87              log.warn("NullPointerException setterOfField " + field);
88          }
89          return Optional.empty();
90      }
91  
92      public static Map<Class, Resource> Class2Resources = ImmutableMap.<Class, Resource>builder()
93              .put(Date.class, XSD.date)
94              .put(LocalDateTime.class, XSD.dateTime)
95              .put(Timestamp.class, XSD.dateTimeStamp)
96              .put(Integer.class, XSD.integer)
97              .put(Short.class, XSD.xshort)
98              .put(Long.class, XSD.xlong)
99              .put(Double.class, XSD.xdouble)
100             .put(Float.class, XSD.xfloat)
101             .put(Boolean.class, XSD.xboolean)
102             .put(long.class, XSD.xlong)
103             .put(int.class, XSD.integer)
104             .put(float.class, XSD.xfloat)
105             .put(double.class, XSD.xdouble)
106             .put(short.class, XSD.xshort)
107             .put(boolean.class, XSD.xboolean)
108             .put(String.class, XSD.xstring)
109             .put(void.class, RDFS.Literal)
110             .build();
111 
112     public static Map<Resource, Class> Resource2Classes = Class2Resources.entrySet().stream().collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey, (x, y) -> x));
113 
114     public static List<Class> ACCEPTED_LIST_CLASS = Arrays.asList(List.class, ArrayList.class, Set.class);
115 
116 
117     public static Optional<Field> fieldOf(Resource schema, Class t, String name, RdfImportContext context) {
118         try {
119 
120             Class ret = context.URI_2_CLASS.get(t.getSimpleName());
121             if (ret == null) {
122                 log.error("error fieldOf " + classToURI(schema, t) + " " + name);
123                 return Optional.empty();
124             } else {
125                 return Optional.of(ret.getDeclaredField(name));
126 
127             }
128         } catch (NoSuchFieldException e) {
129             log.error("error fieldOf " + t.getSimpleName() + " " + name + " - " + e.getMessage());
130         }
131         return null;
132     }
133 
134 
135     public static String classToURI(Resource ont, Class c) {
136         String uri = ont + c.getSimpleName();
137         if (uri.substring(1).contains("<")) {
138             uri = uri.substring(0, uri.indexOf("<"));
139         }
140 //        if (uri.endsWith("<java.lang.Integer, java.util.Date>")) {
141 //            uri = uri.replace("<java.lang.Integer, java.util.Date>", "");
142 //        }
143 
144         if (uri.contains("$")) {
145             log.error("Inner classes not handled " + uri);
146         }
147 
148         return uri;
149     }
150 
151 
152     public static boolean isJavaType(Type type) {
153         return Class2Resources.keySet().stream().anyMatch(type::equals);
154     }
155 
156     public static boolean isJavaType(Method getter) {
157         return isJavaType(getter.getGenericReturnType());
158     }
159 
160     public static boolean isJavaType(Field field) {
161         return isJavaType(field.getType());
162     }
163 
164 
165     /**
166      * check the getter and its corresponding field's annotations
167      *
168      * @param met the getter method to test
169      * @return true if it is a technical id to exclude from the model
170      */
171     public static boolean isId(Method met) {
172         return "getId".equals(met.getName())
173                 && Stream.concat(annotsOfField(getFieldOfGetter(met)), Stream.of(met.getAnnotations()))
174                 .anyMatch(annot -> annot instanceof Id || annot instanceof org.springframework.data.annotation.Id);
175     }
176 
177     public static boolean isManyToOne(Method met) {
178         return annotsOfField(getFieldOfGetter(met)).anyMatch(annot -> annot instanceof ManyToOne) // check the corresponding field's annotations
179                 ||
180                 Stream.of(met.getAnnotations()).anyMatch(annot -> annot instanceof ManyToOne)  // check the method's annotations
181                 ;
182     }
183 
184     public static Stream<Annotation> annotsOfField(Optional<Field> field) {
185         return field.map(field1 -> Stream.of(field1.getAnnotations())).orElseGet(Stream::empty);
186     }
187 
188     public static boolean isGetter(Method met) {
189         return met.getName().startsWith("get") // only getters
190                 && !"getBytes".equals(met.getName()) // ignore ugly
191                 && met.getParameterCount() == 0 // ignore getters that are not getters
192                 && getFieldOfGetter(met).isPresent()
193                 ;
194     }
195 
196 
197     public static boolean isSetter(Method met) {
198         return met.getName().startsWith("set");
199     }
200 
201     public static Field getFieldOfGetteR(Method getter) {
202         String fieldName = getter.getName().substring(3, 4).toLowerCase() + getter.getName().substring(4);
203         try {
204             return getter.getDeclaringClass().getDeclaredField(fieldName);
205         } catch (NoSuchFieldException e) {
206             return null; // this is never going to happen right ?
207         }
208     }
209 
210 
211     public static Optional<Field> getFieldOfGetter(Method getter) {
212 
213         String fieldName = getter.getName().substring(3, 4).toLowerCase() + getter.getName().substring(4);
214         //log.info("searching field : " + fieldName);
215         try {
216             return Optional.of(getter.getDeclaringClass().getDeclaredField(fieldName));
217         } catch (Exception e) {
218             //log.error("field not found : " + fieldName + " for class " + getter.getDeclaringClass() + "  " + e.getMessage());
219             return Optional.empty();
220         }
221     }
222 
223     public static Resource getStdType(Field f) {
224         return Class2Resources.getOrDefault(f.getType(), RDFS.Literal);
225 //        return Class2Resources.entrySet().stream()
226 //                .filter((entry) -> entry.getKey().getTypeName().equals(f.getStdType().getSimpleName()))
227 //                .map(Map.Entry::getValue)
228 //                .findAny()
229 //                .orElse(RDFS.Literal);
230     }
231 
232     public static Resource getStdType(Type type) {
233         return Class2Resources.getOrDefault(type, RDFS.Literal);
234 //        return Class2Resources.entrySet().stream()
235 //                .filter((entry) -> entry.getKey().getTypeName().equals(type.getTypeName()))
236 //                .map(Map.Entry::getValue)
237 //                .findAny()
238 //                .orElse(RDFS.Literal);
239     }
240 
241 
242     // =============== List handling ===============
243 
244 
245     public static boolean isListType(Type type) {
246         if (type instanceof ParameterizedType) {
247             ParameterizedType parameterized = (ParameterizedType) type;// This would be Class<List>, say
248             Type raw = parameterized.getRawType();
249 
250             return (ACCEPTED_LIST_CLASS.stream() // add set LinkedList... if you wish
251                     .anyMatch(x -> x.getCanonicalName().equals(raw.getTypeName())));
252         }
253 
254         return false;
255 
256     }
257 
258     public static Type getListType(Type type) {
259         if (type instanceof ParameterizedType) {
260             ParameterizedType parameterized = (ParameterizedType) type;// This would be Class<List>, say
261             Type raw = parameterized.getRawType();
262             Type own = parameterized.getOwnerType();
263             Type[] typeArgs = parameterized.getActualTypeArguments();
264 
265             if (ACCEPTED_LIST_CLASS.stream()
266                     .anyMatch(x -> x.getCanonicalName().equals(raw.getTypeName()))) {
267                 return typeArgs[0];
268             }
269         }
270         return null;
271     }
272 
273 
274     // =============== Define relation  ===============
275 
276     public static void createOneToMany(OntModel ontoModel, OntClass ontoClass, OntProperty prop, Resource resource) {
277         OntClass minCardinalityRestriction = ontoModel.createMinCardinalityRestriction(null, prop, 1);
278         ontoClass.addSuperClass(minCardinalityRestriction);
279     }
280 
281     public static void createZeroToMany(OntModel ontoModel, OntClass ontoClass, OntProperty prop, Resource resource) {
282         OntClass minCardinalityRestriction = ontoModel.createMinCardinalityRestriction(null, prop, 0);
283         ontoClass.addSuperClass(minCardinalityRestriction);
284     }
285 
286     public static void createZeroToOne(OntModel ontoModel, OntClass ontoClass1, OntProperty prop, OntClass ontoClass2) {
287         OntClass maxCardinalityRestriction = ontoModel.createMaxCardinalityRestriction(null, prop, 1);
288         ontoClass1.addSuperClass(maxCardinalityRestriction);
289     }
290 
291     public static void createOneToOne(OntModel ontoModel, OntClass ontoClass1, OntProperty prop, OntClass ontoClass2) {
292         OntClass maxCardinalityRestriction = ontoModel.createMaxCardinalityRestriction(null, prop, 1);
293         ontoClass1.addSuperClass(maxCardinalityRestriction);
294     }
295 
296 
297     /**
298      * Serialize model in requested format
299      *
300      * @param model  input model
301      * @param format output format if null then output to RDF/XML
302      * @return a string representation of the model
303      */
304     public static String toString(Model model, String format) {
305 
306         try (final ByteArrayOutputStream os = new ByteArrayOutputStream()) {
307             if (format == null) {
308                 model.write(os);
309             } else {
310                 model.write(os, format);
311             }
312             os.flush();
313             os.close();
314             return new String(os.toByteArray(), "UTF8");
315         } catch (IOException e) {
316             log.error("doWrite ", e);
317         }
318         return "there was an error writing the model ";
319     }
320 
321 
322 
323     public static LocalDate convertToLocalDateViaMilisecond(Date dateToConvert) {
324         return Instant.ofEpochMilli(dateToConvert.getTime())
325                 .atZone(ZONE_ID)
326                 .toLocalDate();
327     }
328 
329     public static LocalDate convertToLocalDateViaInstant(Date dateToConvert) {
330         return dateToConvert.toInstant()
331                 .atZone(ZONE_ID)
332                 .toLocalDate();
333     }
334 
335     public static Date convertToDateViaInstant(LocalDateTime dateToConvert) {
336         return java.util.Date
337                 .from(dateToConvert.atZone(ZONE_ID)
338                         .toInstant());
339     }
340 
341 
342 }