View Javadoc
1   /*
2    * Copyright 2018-2022 Medical Information Systems Research Group (https://medical.zcu.cz),
3    * Department of Computer Science and Engineering, University of West Bohemia.
4    * Address: Univerzitni 8, 306 14 Plzen, Czech Republic.
5    *
6    * Author Petr Vcelak (vcelak@kiv.zcu.cz).
7    *
8    * This file is part of MRECore project.
9    *
10   * MRECore is free software: you can redistribute it and/or modify
11   * it under the terms of the GNU General Public License as published by
12   * the Free Software Foundation, either version 3 of the License.
13   *
14   * MRECore is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17   * GNU General Public License for more details.
18   *
19   * You should have received a copy of the GNU General Public License
20   * along with MRECore. If not, see <http://www.gnu.org/licenses/>.
21   */
22  package cz.zcu.mre.service.data;
23  
24  import cz.zcu.mre.config.ApplicationConfiguration;
25  import cz.zcu.mre.data.rdf.ResourceWrapperComparator;
26  import cz.zcu.mre.data.rdf.VocabularyWrapper;
27  import cz.zcu.mre.vocab.FORM;
28  import cz.zcu.mre.vocab.IBDT;
29  import java.util.ArrayList;
30  import java.util.Collections;
31  import java.util.List;
32  import java.util.Map;
33  import java.util.concurrent.ConcurrentHashMap;
34  import jakarta.servlet.ServletContext;
35  import org.apache.jena.ontology.Individual;
36  import org.apache.jena.ontology.OntClass;
37  import org.apache.jena.ontology.OntResource;
38  import org.apache.jena.rdf.model.RDFNode;
39  import org.apache.jena.rdf.model.Resource;
40  import org.apache.jena.rdf.model.ResourceFactory;
41  import org.apache.jena.util.iterator.ExtendedIterator;
42  import org.slf4j.Logger;
43  import org.slf4j.LoggerFactory;
44  import org.springframework.beans.factory.annotation.Autowired;
45  import org.springframework.context.annotation.Scope;
46  import org.springframework.context.annotation.ScopedProxyMode;
47  import org.springframework.context.event.ContextRefreshedEvent;
48  import org.springframework.context.event.EventListener;
49  
50  /**
51   * Vocabulary service takes care of providing vocabulary items and is available
52   * in servlet context as 'vocab' attribute.
53   *
54   * @author Petr Vcelak (vcelak@kiv.zcu.cz)
55   */
56  //@Service
57  @Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
58  public class VocabularyServiceImpl implements VocabularyService {
59  
60      private static final Logger LOG = LoggerFactory.getLogger(VocabularyServiceImpl.class);
61  
62      private static final String WARN_MSG = "Missing label for property {}";
63  
64      /**
65       * Vocabulary items comparator.
66       */
67      private static final ResourceWrapperComparator resourceWraperComparator = new ResourceWrapperComparator();
68  
69      /**
70       * Maps Resource to List of IndividualWrappers.
71       */
72      private final Map<Resource, List<VocabularyWrapper>> vocabularyMap = new ConcurrentHashMap<>();
73      /**
74       * Maps individual/instance URI to a VocabularyWrapper.
75       */
76      private final Map<String, VocabularyWrapper> individualMap = new ConcurrentHashMap<>();
77  
78      @Autowired
79      private OntologyService ontologyService;
80  
81      @Autowired
82      private ServletContext servletContext;
83  
84      /**
85       * Handle context refreshed event and register (set) attributes in the
86       * servlet context.
87       *
88       * It is possible to use placeholder with the local name of vocabulary
89       * ${application.vocab.label('YesNoVocab')} or property URI like:
90       * ${application.vocab.values('YesNoVocab')}.
91       *
92       * @param e Context refreshed event.
93       */
94      @EventListener
95      protected void handleContextRefresh(ContextRefreshedEvent e) {
96          LOG.info("Add VocabularyService on the event ContextRefreshedEvent.");
97  
98          // set attribute vocab (application.vocab.*())
99          servletContext.setAttribute("vocab", this);
100     }
101 
102     @Override
103     public void afterPropertiesSet() throws Exception {
104         LOG.info("VocabularyService - prepare");
105         loadVocabularies();
106         LOG.info("VocabularyService - prepare - done.");
107     }
108 
109     private void loadVocabularies() {
110 
111         List<OntClass> ontClassList = ontologyService.getVocabularyList();
112         ontClassList.forEach((ontClass) -> {
113             List<VocabularyWrapper> vwList = new ArrayList<>();
114             ExtendedIterator<? extends OntResource> it = ontClass.listInstances();
115             while (it.hasNext()) {
116                 OntResource ontResource = it.next();
117                 LOG.debug("Loaded vocabulary {} instance {}.", ontClass.getURI(), ontResource.getURI());
118                 VocabularyWrapper vw = createVocabularyWrapper(ontClass, ontResource);
119 
120                 // put into a list per vocabulary
121                 vwList.add(vw);
122 
123                 // put to individual map
124                 individualMap.put(ontResource.getURI(), vw);
125             }
126 
127             // check if list is empty
128             if (vwList.isEmpty()) {
129                 // mark vocabulary empty
130                 vocabularyMap.put(ontClass, Collections.EMPTY_LIST);
131             } else {
132 
133                 // list is not empty -- sort values by ResourceWrapperComparator
134                 LOG.debug("Sort vocabulary {} items ({}).", ontClass.getURI(), vwList.size());
135                 vwList.sort(resourceWraperComparator);
136 
137                 // put into vocabulary map
138                 vocabularyMap.put(ontClass, vwList);
139             }
140         });
141     }
142 
143     private VocabularyWrapper createVocabularyWrapper(OntClass ontClazz, OntResource ontResource) {
144 
145         LOG.debug("Create VocabularyWrapper for vocabulary {} and instance {}", ontClazz.getURI(), ontResource.getURI());
146         VocabularyWrapper vw = new VocabularyWrapper(ontResource);
147 
148         // set label
149         String label = ontResource.getLabel(ApplicationConfiguration.DEFAULT_LANGUAGE);
150         if (label == null || label.isEmpty()) {
151             label = ontResource.getLabel(null);
152         }
153         vw.setLabel(label);
154 
155         // set code
156         if (ontResource.hasProperty(FORM.CODE)) {
157             String code = ontResource.getProperty(FORM.CODE).getString();
158             if (code == null || code.isEmpty()) {
159                 code = label;
160             }
161             vw.setItemCode(code);
162         }
163 
164         // set item id for sorting form:vocabularyItemId
165         RDFNode node = ontResource.getPropertyValue(FORM.VOCABULARY_ITEM_ID);
166         if (node != null && node.asLiteral() != null) {
167             int order = node.asLiteral().getInt();
168             vw.setIntOrder(order);
169         } else {
170 
171             // form:vocabularyLineNumber
172             node = ontResource.getPropertyValue(FORM.VOCABULARY_LINE_NUMBER);
173             if (node != null && node.asLiteral() != null) {
174                 int order = node.asLiteral().getInt();
175                 vw.setIntOrder(order);
176             }
177         }
178 
179         return vw;
180     }
181 
182     @Override
183     public List<VocabularyWrapper> itemsList(String rdfClass) {
184 
185         LOG.debug("List items of class {}", rdfClass);
186         return itemsList(ResourceFactory.createResource(rdfClass));
187     }
188 
189     @Override
190     public List<VocabularyWrapper> itemsList(Resource rdfClass) {
191 
192         LOG.debug("List items of {}", rdfClass.getURI());
193 
194         if (vocabularyMap.containsKey(rdfClass)) {
195             LOG.debug("List items of {} - using hash map.", rdfClass.getURI());
196             return vocabularyMap.get(rdfClass);
197         }
198 
199         return Collections.EMPTY_LIST;
200     }
201 
202     @Override
203     public VocabularyWrapper item(String uri) {
204 
205         return individualMap.get(uri);
206     }
207 
208     @Override
209     public String label(Resource resource) {
210 
211         if (resource == null) {
212             return "";
213         }
214 
215         if (individualMap.containsKey(resource.getURI())) {
216             LOG.debug("Get prepared label for individual {}", resource);
217             return individualMap.get(resource.getURI()).getLabel();
218         }
219 
220         LOG.warn(WARN_MSG, resource);
221         return resource.getLocalName();
222     }
223 
224     @Override
225     public String code(Resource resource) {
226 
227         if (resource == null) {
228             return "";
229         }
230 
231         if (individualMap.containsKey(resource.getURI())) {
232             LOG.debug("Get prepared code for resource {}", resource);
233             return individualMap.get(resource.getURI()).getItemCode();
234         }
235 
236         LOG.warn(WARN_MSG, resource);
237         return resource.getLocalName();
238     }
239 
240     @Override
241     public boolean itemExist(Resource rdfClass) {
242 
243         if (vocabularyMap.containsKey(rdfClass)) {
244             return true;
245         }
246 
247         return !itemsList(rdfClass).isEmpty();
248     }
249 
250     @Override
251     public int itemCount(Resource rdfClass) {
252 
253         if (vocabularyMap.containsKey(rdfClass)) {
254             return vocabularyMap.get(rdfClass).size();
255         }
256 
257         return itemsList(rdfClass).size();
258     }
259 
260     @Override
261     public boolean itemByCodeExist(Resource rdfClass, String itemCode) {
262 
263         List<VocabularyWrapper> vocabularyContent = itemsList(rdfClass);
264         // look for the first item
265 
266         return vocabularyContent.stream().map((vw) -> (Individual) vw.getResource()).map((individual) -> individual.getPropertyValue(FORM.CODE)).anyMatch((node) -> (node != null && node.asLiteral().getString().equals(itemCode)));
267     }
268 
269     @Override
270     public VocabularyWrapper itemByCode(Resource rdfClass, String itemCode) {
271 
272         List<VocabularyWrapper> vocabularyContent = itemsList(rdfClass);
273 
274         for (VocabularyWrapper vw : vocabularyContent) {
275 
276             Individual individual = (Individual) vw.getResource();
277 
278             // look for the first item
279             RDFNode node = individual.getPropertyValue(FORM.CODE);
280             if (node != null && node.asLiteral().getString().equals(itemCode)) {
281                 return vw;
282             }
283         }
284         return null;
285     }
286 
287     @Override
288     public VocabularyWrapper itemByNumber(Resource rdfClass, int number) {
289 
290         List<VocabularyWrapper> vocabularyContent = itemsList(rdfClass);
291 
292         for (VocabularyWrapper vw : vocabularyContent) {
293 
294             Individual individual = (Individual) vw.getResource();
295 
296             // look for the first item
297             RDFNode node = individual.getPropertyValue(FORM.CODE);
298             if (node != null && node.asLiteral().getInt() == number) {
299                 return vw;
300             }
301         }
302 
303         return null;
304     }
305 
306     @Override
307     public boolean itemByNumberExist(Resource rdfClass, int number) {
308 
309         List<VocabularyWrapper> vocabularyContent = itemsList(rdfClass);
310         // look for the first item
311 
312         return vocabularyContent.stream().map((vw) -> (Individual) vw.getResource()).map((individual) -> individual.getPropertyValue(FORM.CODE)).anyMatch((node) -> (node != null && node.asLiteral().getInt() == number));
313     }
314 
315     @Override
316     public VocabularyWrapper itemDefault(String rdfClass) {
317 
318         return itemDefault(ResourceFactory.createResource(rdfClass));
319     }
320 
321     @Override
322     public VocabularyWrapper itemDefault(Resource rdfClass) {
323 
324         VocabularyWrapper first = null;
325 
326         List<VocabularyWrapper> vocabularyContent = itemsList(rdfClass);
327         for (VocabularyWrapper vw : vocabularyContent) {
328             Individual individual = (Individual) vw.getResource();
329 
330             // look for the first item
331             RDFNode node = individual.getPropertyValue(FORM.VOCABULARY_ITEM_ID);
332             if (first == null && node != null && node.asLiteral().getInt() == 1) {
333                 first = vw;
334             }
335 
336             node = individual.getPropertyValue(FORM.IS_DEFAULT_VALUE);
337             if (node != null && node.asLiteral().getBoolean()) {
338                 return vw;
339             }
340         }
341 
342         // Return the first item as default, because default value is not specified.
343         return first;
344     }
345 
346     @Override
347     public VocabularyWrapper itemFirst(Resource rdfClass) {
348 
349         List<VocabularyWrapper> vocabularyContent = itemsList(rdfClass);
350         if (vocabularyContent.isEmpty()) {
351             return null;
352         }
353 
354         return vocabularyContent.get(0);
355 
356     }
357 
358     @Override
359     public VocabularyWrapper itemLast(Resource rdfClass) {
360 
361         List<VocabularyWrapper> vocabularyContent = itemsList(rdfClass);
362         if (vocabularyContent.isEmpty()) {
363             return null;
364         }
365 
366         return vocabularyContent.get(vocabularyContent.size() - 1);
367     }
368 
369     public float itemScore(Individual individual) {
370 
371         RDFNode node = individual.getPropertyValue(FORM.SCORE);
372         if (node != null) {
373             return node.asLiteral().getFloat();
374         }
375 
376         return Float.NaN;
377     }
378 
379     public float itemCheckScore(Individual individual) {
380 
381         RDFNode node = individual.getPropertyValue(FORM.SCORE);
382         if (node != null) {
383             return node.asLiteral().getFloat();
384         }
385 
386         return Float.NaN;
387     }
388 
389     @Override
390     public List<VocabularyWrapper> itemsFilteredByAgeSex(Resource rdfClass, int age, String sex) {
391 
392         List<VocabularyWrapper> filteredList = new ArrayList<>();
393         List<VocabularyWrapper> vocabularyContent = itemsList(rdfClass);
394         vocabularyContent.forEach((vw) -> {
395             int ageMin = Integer.MIN_VALUE;
396             int ageMax = Integer.MAX_VALUE;
397             Individual individual = (Individual) vw.getResource();
398             RDFNode node = individual.getPropertyValue(IBDT.COND_SEX);
399             if (!(node != null && node.asLiteral().getString().equals(sex))) {
400                 node = individual.getPropertyValue(IBDT.AGE_MIN);
401                 if (node != null) {
402                     ageMin = node.asLiteral().getInt();
403                 }
404                 node = individual.getPropertyValue(IBDT.AGE_MAX);
405                 if (node != null) {
406                     ageMax = node.asLiteral().getInt();
407                 }
408                 // TODO check and fix to 'less or equal' and 'more or equal'
409                 if (ageMin < age && age < ageMax) {
410                     filteredList.add(vw);
411                 }
412             }
413         });
414 
415         return filteredList;
416     }
417 }