1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package ubic.basecode.ontology.jena;
21
22 import com.hp.hpl.jena.ontology.*;
23 import com.hp.hpl.jena.rdf.model.ModelFactory;
24 import com.hp.hpl.jena.rdf.model.NodeIterator;
25 import com.hp.hpl.jena.rdf.model.Property;
26 import com.hp.hpl.jena.rdf.model.Resource;
27 import com.hp.hpl.jena.rdfxml.xmlinput.ARPErrorNumbers;
28 import com.hp.hpl.jena.rdfxml.xmlinput.ParseException;
29 import com.hp.hpl.jena.reasoner.ReasonerFactory;
30 import com.hp.hpl.jena.reasoner.rulesys.OWLFBRuleReasonerFactory;
31 import com.hp.hpl.jena.reasoner.rulesys.OWLMicroReasonerFactory;
32 import com.hp.hpl.jena.reasoner.rulesys.OWLMiniReasonerFactory;
33 import com.hp.hpl.jena.reasoner.transitiveReasoner.TransitiveReasonerFactory;
34 import com.hp.hpl.jena.util.iterator.ExtendedIterator;
35 import com.hp.hpl.jena.vocabulary.DC_11;
36 import org.apache.commons.lang3.RandomStringUtils;
37 import org.apache.commons.lang3.StringUtils;
38 import org.apache.commons.lang3.time.StopWatch;
39 import org.jspecify.annotations.Nullable;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
42 import ubic.basecode.ontology.model.OntologyIndividual;
43 import ubic.basecode.ontology.model.OntologyModel;
44 import ubic.basecode.ontology.model.OntologyResource;
45 import ubic.basecode.ontology.model.OntologyTerm;
46 import ubic.basecode.ontology.providers.OntologyService;
47 import ubic.basecode.ontology.search.OntologySearchException;
48 import ubic.basecode.ontology.search.OntologySearchResult;
49
50 import java.io.IOException;
51 import java.io.InputStream;
52 import java.io.InterruptedIOException;
53 import java.nio.channels.ClosedByInterruptException;
54 import java.util.*;
55 import java.util.function.Predicate;
56 import java.util.stream.Collectors;
57
58 import static ubic.basecode.ontology.jena.JenaUtils.as;
59
60 public abstract class AbstractOntologyService implements OntologyService {
61
62
63
64
65 private static final Set<Property> DEFAULT_ADDITIONAL_PROPERTIES;
66 protected static Logger log = LoggerFactory.getLogger( AbstractOntologyService.class );
67
68 static {
69 DEFAULT_ADDITIONAL_PROPERTIES = new HashSet<>();
70 DEFAULT_ADDITIONAL_PROPERTIES.add( RO.partOf );
71
72 DEFAULT_ADDITIONAL_PROPERTIES.add( RO.activeIngredientIn );
73 DEFAULT_ADDITIONAL_PROPERTIES.add( RO.boundingLayerOf );
74 DEFAULT_ADDITIONAL_PROPERTIES.add( RO.branchingPartOf );
75 DEFAULT_ADDITIONAL_PROPERTIES.add( RO.determinedBy );
76 DEFAULT_ADDITIONAL_PROPERTIES.add( RO.ends );
77 DEFAULT_ADDITIONAL_PROPERTIES.add( RO.isSubsequenceOf );
78 DEFAULT_ADDITIONAL_PROPERTIES.add( RO.isEndSequenceOf );
79 DEFAULT_ADDITIONAL_PROPERTIES.add( RO.isStartSequenceOf );
80 DEFAULT_ADDITIONAL_PROPERTIES.add( RO.lumenOf );
81 DEFAULT_ADDITIONAL_PROPERTIES.add( RO.luminalSpaceOf );
82 DEFAULT_ADDITIONAL_PROPERTIES.add( RO.mainStemOf );
83 DEFAULT_ADDITIONAL_PROPERTIES.add( RO.memberOf );
84 DEFAULT_ADDITIONAL_PROPERTIES.add( RO.occurrentPartOf );
85 DEFAULT_ADDITIONAL_PROPERTIES.add( RO.skeletonOf );
86 DEFAULT_ADDITIONAL_PROPERTIES.add( RO.starts );
87 DEFAULT_ADDITIONAL_PROPERTIES.add( RO.subclusterOf );
88
89
90 DEFAULT_ADDITIONAL_PROPERTIES.add( RO.properPartOf );
91 }
92
93 private final String ontologyName;
94 private final String ontologyUrl;
95 private final boolean ontologyEnabled;
96 @Nullable
97 private final String cacheName;
98
99
100
101
102 @Nullable
103 private State state = null;
104
105 private LanguageLevel languageLevel = LanguageLevel.FULL;
106 private InferenceMode inferenceMode = InferenceMode.TRANSITIVE;
107 private boolean processImports = true;
108 private boolean searchEnabled = true;
109 private Set<String> excludedWordsFromStemming = Collections.emptySet();
110 private Set<String> additionalPropertyUris = DEFAULT_ADDITIONAL_PROPERTIES.stream().map( Property::getURI ).collect( Collectors.toSet() );
111
112 protected AbstractOntologyService( String ontologyName, String ontologyUrl, boolean ontologyEnabled, @Nullable String cacheName ) {
113 this.ontologyName = ontologyName;
114 this.ontologyUrl = ontologyUrl;
115 this.ontologyEnabled = ontologyEnabled;
116 this.cacheName = cacheName;
117 }
118
119 protected String getOntologyName() {
120 return ontologyName;
121 }
122
123 protected String getOntologyUrl() {
124 return ontologyUrl;
125 }
126
127 protected boolean isOntologyEnabled() {
128 return ontologyEnabled;
129 }
130
131 @Nullable
132 protected String getCacheName() {
133 return cacheName;
134 }
135
136 @Nullable
137 private volatile Thread initializationThread = null;
138
139 @Override
140 public synchronized void startInitializationThread( boolean forceLoad, boolean forceIndexing ) {
141 Thread initializationThread = this.initializationThread;
142 if ( initializationThread != null && initializationThread.isAlive() ) {
143 log.warn( " Initialization thread for {} is currently running, not restarting.", this );
144 return;
145 }
146
147 initializationThread = new Thread( () -> {
148 try {
149 this.initialize( forceLoad, forceIndexing );
150 } catch ( Exception e ) {
151 log.error( "Initialization for %s failed.", e );
152 }
153 }, getOntologyName() + "_load_thread_" + RandomStringUtils.insecure().nextAlphanumeric( 5 ) );
154
155 initializationThread.setDaemon( true );
156 initializationThread.start();
157 this.initializationThread = initializationThread;
158 }
159
160 @Override
161 public boolean isInitializationThreadAlive() {
162 Thread initializationThread = this.initializationThread;
163 return initializationThread != null && initializationThread.isAlive();
164 }
165
166 @Override
167 public boolean isInitializationThreadCancelled() {
168 Thread initializationThread = this.initializationThread;
169 return initializationThread != null && initializationThread.isInterrupted();
170 }
171
172
173
174
175 @Override
176 public void cancelInitializationThread() {
177 Thread initializationThread = this.initializationThread;
178 if ( initializationThread == null ) {
179 throw new IllegalStateException( "The initialization thread has not started. Invoke startInitializationThread() first." );
180 }
181 initializationThread.interrupt();
182 }
183
184 @Override
185 public void waitForInitializationThread() throws InterruptedException {
186 Thread initializationThread = this.initializationThread;
187 if ( initializationThread == null ) {
188 throw new IllegalStateException( "The initialization thread has not started. Invoke startInitializationThread() first." );
189 }
190 initializationThread.join();
191 }
192
193 @Override
194 public void loadTermsInNameSpace( InputStream is, boolean forceIndex ) {
195
196 Thread initializationThread = this.initializationThread;
197 if ( initializationThread != null && initializationThread.isAlive() ) {
198 log.warn( "{} initialization is already running, trying to cancel ...", this );
199 initializationThread.interrupt();
200
201 int maxWait = 10;
202 int wait = 0;
203 while ( initializationThread.isAlive() ) {
204 try {
205 initializationThread.join( 5000 );
206 } catch ( InterruptedException e ) {
207 Thread.currentThread().interrupt();
208 return;
209 }
210 log.warn( "Waiting for auto-initialization to stop so manual initialization can begin ..." );
211 ++wait;
212 if ( wait >= maxWait && !initializationThread.isAlive() ) {
213 throw new RuntimeException( String.format( "Got tired of waiting for %s's initialization thread.", this ) );
214 }
215 }
216 }
217 initialize( is, forceIndex );
218 }
219
220 @Nullable
221 @Override
222 public String getName() {
223 return getState().map( state -> {
224 NodeIterator it = state.model.listObjectsOfProperty( DC_11.title );
225 return it.hasNext() ? it.next().asLiteral().getString() : null;
226 } ).orElse( null );
227 }
228
229 @Nullable
230 @Override
231 public String getDescription() {
232 return getState().map( state -> {
233 NodeIterator it = state.model.listObjectsOfProperty( DC_11.description );
234 return it.hasNext() ? it.next().asLiteral().getString() : null;
235 } ).orElse( null );
236 }
237
238 @Override
239 public LanguageLevel getLanguageLevel() {
240 return getState().map( state -> state.languageLevel ).orElse( languageLevel );
241 }
242
243 @Override
244 public void setLanguageLevel( LanguageLevel languageLevel ) {
245 this.languageLevel = languageLevel;
246 }
247
248 @Override
249 public InferenceMode getInferenceMode() {
250 return getState().map( state -> state.inferenceMode ).orElse( inferenceMode );
251 }
252
253 @Override
254 public void setInferenceMode( InferenceMode inferenceMode ) {
255 this.inferenceMode = inferenceMode;
256 }
257
258 @Override
259 public boolean getProcessImports() {
260 return getState().map( state -> state.processImports ).orElse( processImports );
261 }
262
263 @Override
264 public void setProcessImports( boolean processImports ) {
265 this.processImports = processImports;
266 }
267
268 @Override
269 public boolean isSearchEnabled() {
270 return getState().map( state -> state.index != null ).orElse( searchEnabled );
271 }
272
273 @Override
274 public void setSearchEnabled( boolean searchEnabled ) {
275 this.searchEnabled = searchEnabled;
276 }
277
278 @Override
279 public Set<String> getExcludedWordsFromStemming() {
280 return getState().map( state -> state.excludedWordsFromStemming ).orElse( excludedWordsFromStemming );
281 }
282
283 @Override
284 public void setExcludedWordsFromStemming( Set<String> excludedWordsFromStemming ) {
285 this.excludedWordsFromStemming = excludedWordsFromStemming;
286 }
287
288 @Override
289 public Set<String> getAdditionalPropertyUris() {
290 return getState().map( state -> state.additionalPropertyUris ).orElse( additionalPropertyUris );
291 }
292
293 @Override
294 public void setAdditionalPropertyUris( Set<String> additionalPropertyUris ) {
295 this.additionalPropertyUris = additionalPropertyUris;
296 }
297
298 public void initialize( boolean forceLoad, boolean forceIndexing ) {
299 initialize( null, forceLoad, forceIndexing );
300 }
301
302 public void initialize( InputStream stream, boolean forceIndexing ) {
303 initialize( stream, true, forceIndexing );
304 }
305
306 private synchronized void initialize( @Nullable InputStream stream, boolean forceLoad, boolean forceIndexing ) {
307 if ( !forceLoad && state != null ) {
308 log.warn( "{} is already loaded, and force=false, not restarting", this );
309 return;
310 }
311
312
313 String ontologyUrl = getOntologyUrl();
314 String ontologyName = getOntologyName();
315 String cacheName = getCacheName();
316 LanguageLevel languageLevel = this.languageLevel;
317 InferenceMode inferenceMode = this.inferenceMode;
318 boolean processImports = this.processImports;
319 boolean searchEnabled = this.searchEnabled;
320 Set<String> excludedWordsFromStemming = this.excludedWordsFromStemming;
321
322
323 if ( StringUtils.isBlank( ontologyUrl ) ) {
324 throw new IllegalStateException( "URL not defined for %s: ontology cannot be loaded. (" + this + ")" );
325 }
326
327 if ( cacheName == null && forceIndexing ) {
328 throw new IllegalArgumentException( String.format( "No cache directory is set for %s, cannot force indexing.", this ) );
329 }
330
331 boolean loadOntology = isEnabled();
332
333
334 if ( !forceLoad && !loadOntology ) {
335 log.debug( "Loading {} is disabled (force=false, Configuration load.{}=false)",
336 this, ontologyName );
337 return;
338 }
339
340 log.info( "Loading ontology: {}...", this );
341 StopWatch loadTime = StopWatch.createStarted();
342
343
344 OntModel model;
345 SearchIndex index;
346
347
348 if ( Thread.currentThread().isInterrupted() )
349 return;
350
351 try {
352 OntologyModel m = stream != null ? loadModelFromStream( stream, processImports, languageLevel, inferenceMode ) : loadModel( processImports, languageLevel, inferenceMode );
353 model = m.unwrap( OntModel.class );
354 } catch ( Exception e ) {
355 if ( isCausedByInterrupt( e ) ) {
356
357 Thread.currentThread().interrupt();
358 return;
359 } else {
360 throw new RuntimeException( String.format( "Failed to load ontology model for %s.", this ), e );
361 }
362 }
363
364
365 if ( Thread.currentThread().isInterrupted() )
366 return;
367
368
369 Set<Property> additionalProperties = additionalPropertyUris.stream().map( model::getProperty ).collect( Collectors.toSet() );
370 Set<Restriction> additionalRestrictions = JenaUtils.listRestrictionsOnProperties( model, additionalProperties, true ).toSet();
371
372
373
374 if ( Thread.currentThread().isInterrupted() )
375 return;
376
377 if ( searchEnabled && cacheName != null ) {
378
379 boolean changed = OntologyLoader.hasChanged( cacheName );
380 boolean indexExists = OntologyIndexer.getSubjectIndex( cacheName, excludedWordsFromStemming ) != null;
381 boolean forceReindexing = forceLoad && forceIndexing;
382
383 try {
384 index = OntologyIndexer.indexOntology( cacheName, model, excludedWordsFromStemming, forceReindexing || changed || !indexExists );
385 } catch ( Exception e ) {
386 if ( isCausedByInterrupt( e ) ) {
387 return;
388 } else {
389 throw new RuntimeException( String.format( "Failed to generate index for %s.", this ), e );
390 }
391 }
392 } else {
393 index = null;
394 }
395
396
397 if ( Thread.currentThread().isInterrupted() )
398 return;
399
400 if ( this.state != null ) {
401 try {
402 this.state.close();
403 } catch ( Exception e ) {
404 log.error( "Failed to close current state.", e );
405 }
406 }
407
408 this.state = new State( model, index, excludedWordsFromStemming, additionalRestrictions, languageLevel, inferenceMode, processImports, additionalProperties.stream().map( Property::getURI ).collect( Collectors.toSet() ), null );
409 if ( cacheName != null ) {
410
411 try {
412 OntologyLoader.deleteOldCache( cacheName );
413 } catch ( IOException e ) {
414 log.error( String.format( String.format( "Failed to delete old cache directory for %s.", this ), e ) );
415 }
416 }
417
418 loadTime.stop();
419
420 log.info( "Finished loading {} in {}s", this, String.format( "%.2f", loadTime.getTime() / 1000.0 ) );
421 }
422
423 private boolean isCausedByInterrupt( Exception e ) {
424 return hasCauseMatching( e, cause -> ( ( cause instanceof ParseException ) && ( ( ParseException ) cause ).getErrorNumber() == ARPErrorNumbers.ERR_INTERRUPTED ) ) ||
425 hasCause( e, InterruptedException.class ) ||
426 hasCause( e, InterruptedIOException.class ) ||
427 hasCause( e, ClosedByInterruptException.class );
428 }
429
430 private boolean hasCause( Throwable t, Class<? extends Throwable> clazz ) {
431 return hasCauseMatching( t, clazz::isInstance );
432 }
433
434 private boolean hasCauseMatching( Throwable t, Predicate<Throwable> predicate ) {
435 return predicate.test( t ) || ( t.getCause() != null && hasCauseMatching( t.getCause(), predicate ) );
436 }
437
438 @Override
439 public Collection<OntologySearchResult<OntologyIndividual>> findIndividuals( String search, int maxResults, boolean keepObsoletes ) throws
440 OntologySearchException {
441 State state = this.state;
442 if ( state == null ) {
443 log.warn( "Ontology {} is not ready, no individuals will be returned.", this );
444 return Collections.emptySet();
445 }
446 if ( state.index == null ) {
447 log.warn( "Attempt to search {} when index is null, no results will be returned.", this );
448 return Collections.emptySet();
449 }
450 return state.index.searchIndividuals( state.model, search, maxResults ).stream()
451 .map( i -> as( i.result, Individual.class ).map( r -> new OntologySearchResult<>( ( OntologyIndividual ) new OntologyIndividualImpl( r, state.additionalRestrictions ), i.score ) ) )
452 .filter( Optional::isPresent )
453 .map( Optional::get )
454 .filter( ontologyTerm -> keepObsoletes || !ontologyTerm.getResult().isObsolete() )
455 .sorted( Comparator.comparing( OntologySearchResult::getScore, Comparator.reverseOrder() ) )
456 .collect( Collectors.toCollection( LinkedHashSet::new ) );
457 }
458
459 @Override
460 public Collection<OntologySearchResult<OntologyResource>> findResources( String searchString, int maxResults, boolean keepObsoletes ) throws
461 OntologySearchException {
462 State state = this.state;
463 if ( state == null ) {
464 log.warn( "Ontology {} is not ready, no resources will be returned.", this );
465 return Collections.emptySet();
466 }
467 if ( state.index == null ) {
468 log.warn( "Attempt to search {} when index is null, no results will be returned.", this );
469 return Collections.emptySet();
470 }
471 return state.index.search( state.model, searchString, maxResults ).stream()
472 .filter( ( r -> r.result.canAs( OntClass.class ) || r.result.canAs( Individual.class ) ) )
473 .map( r -> {
474 if ( r.result.canAs( OntClass.class ) ) {
475 return as( r.result, OntClass.class )
476 .map( r2 -> new OntologySearchResult<>( ( OntologyResource ) new OntologyTermImpl( r2, state.additionalRestrictions ), r.score ) );
477 } else if ( r.result.canAs( Individual.class ) ) {
478 return as( r.result, Individual.class )
479 .map( r2 -> new OntologySearchResult<>( ( OntologyResource ) new OntologyIndividualImpl( r2, state.additionalRestrictions ), r.score ) );
480 } else {
481 return Optional.<OntologySearchResult<OntologyResource>>empty();
482 }
483 } )
484 .filter( Optional::isPresent )
485 .map( Optional::get )
486 .filter( ontologyTerm -> keepObsoletes || !ontologyTerm.getResult().isObsolete() )
487 .sorted( Comparator.comparing( OntologySearchResult::getScore, Comparator.reverseOrder() ) )
488 .collect( Collectors.toCollection( LinkedHashSet::new ) );
489 }
490
491 @Override
492 public Collection<OntologySearchResult<OntologyTerm>> findTerm( String search, int maxResults, boolean keepObsoletes ) throws OntologySearchException {
493 State state = this.state;
494 if ( state == null ) {
495 log.warn( "Ontology {} is not ready, no terms will be returned.", this );
496 return Collections.emptySet();
497 }
498 if ( state.index == null ) {
499 log.warn( "Attempt to search {} when index is null, no results will be returned.", this );
500 return Collections.emptySet();
501 }
502 return state.index.searchClasses( state.model, search, maxResults ).stream()
503 .map( r -> as( r.result, OntClass.class ).map( s -> new OntologySearchResult<>( ( OntologyTerm ) new OntologyTermImpl( s, state.additionalRestrictions ), r.score ) ) )
504 .filter( Optional::isPresent )
505 .map( Optional::get )
506 .filter( ontologyTerm -> keepObsoletes || !ontologyTerm.getResult().isObsolete() )
507 .sorted( Comparator.comparing( OntologySearchResult::getScore, Comparator.reverseOrder() ) )
508 .collect( Collectors.toCollection( LinkedHashSet::new ) );
509 }
510
511 @Nullable
512 @Override
513 public OntologyTerm findUsingAlternativeId( String alternativeId ) {
514 State state = this.state;
515 if ( state == null ) {
516 log.warn( "Ontology {} is not ready, null will be returned for alternative ID match.", this );
517 return null;
518 }
519 if ( state.alternativeIDs == null ) {
520 log.info( "init search by alternativeID" );
521 this.state = initSearchByAlternativeId( state );
522 }
523 assert state.alternativeIDs != null;
524 String termUri = state.alternativeIDs.get( alternativeId );
525 return termUri != null ? getTerm( termUri ) : null;
526 }
527
528 @Override
529 public Set<String> getAllURIs() {
530 return getState().map( state -> {
531 Set<String> allUris = new HashSet<>();
532 allUris.addAll( state.model.listClasses().mapWith( OntClass::getURI ).toSet() );
533 allUris.addAll( state.model.listIndividuals().mapWith( Individual::getURI ).toSet() );
534 return allUris;
535 } ).orElseGet( () -> {
536 log.warn( "Ontology {} is not ready, no term URIs will be returned.", this );
537 return Collections.emptySet();
538 } );
539 }
540
541 @Nullable
542 @Override
543 public OntologyResource getResource( String uri ) {
544 return getState().map( state -> {
545 OntologyResource res;
546 Resource resource = state.model.getResource( uri );
547 if ( resource.getURI() == null ) {
548 return null;
549 }
550 if ( resource instanceof OntClass ) {
551
552 res = new OntologyTermImpl( ( OntClass ) resource, state.additionalRestrictions );
553 } else if ( resource instanceof Individual ) {
554 res = new OntologyIndividualImpl( ( Individual ) resource, state.additionalRestrictions );
555 } else if ( resource instanceof OntProperty ) {
556 res = PropertyFactory.asProperty( ( ObjectProperty ) resource, state.additionalRestrictions );
557 } else {
558 res = null;
559 }
560 return res;
561 } ).orElse( null );
562 }
563
564 @Nullable
565 @Override
566 public OntologyTerm getTerm( String uri ) {
567 return getState().map( state -> {
568 OntClass ontCls = state.model.getOntClass( uri );
569
570 if ( ontCls == null || ontCls.getURI() == null ) {
571 return null;
572 }
573 return new OntologyTermImpl( ontCls, state.additionalRestrictions );
574 } ).orElse( null );
575 }
576
577 @Override
578 public Collection<OntologyIndividual> getTermIndividuals( String uri ) {
579 OntologyTerm term = getTerm( uri );
580 if ( term == null ) {
581
582
583
584 log.warn( "No term for URI={} in {}; make sure ontology is loaded and uri is valid", uri, this );
585 return Collections.emptySet();
586 }
587 return term.getIndividuals( true );
588 }
589
590 @Override
591 public Set<OntologyTerm> getParents( Collection<OntologyTerm> terms, boolean direct,
592 boolean includeAdditionalProperties, boolean keepObsoletes ) {
593 return getState().map( state ->
594 JenaUtils.getParents( state.model, getOntClassesFromTerms( state.model, terms ), direct, includeAdditionalProperties ? state.additionalRestrictions : null )
595 .stream()
596 .map( o -> ( OntologyTerm ) new OntologyTermImpl( o, state.additionalRestrictions ) )
597 .filter( o -> keepObsoletes || !o.isObsolete() )
598 .collect( Collectors.toSet() ) )
599 .orElse( Collections.emptySet() );
600 }
601
602 @Override
603 public Set<OntologyTerm> getChildren( Collection<OntologyTerm> terms, boolean direct,
604 boolean includeAdditionalProperties, boolean keepObsoletes ) {
605 return getState().map( state ->
606 JenaUtils.getChildren( state.model, getOntClassesFromTerms( state.model, terms ), direct, includeAdditionalProperties ? state.additionalRestrictions : null )
607 .stream()
608 .map( o -> ( OntologyTerm ) new OntologyTermImpl( o, state.additionalRestrictions ) )
609 .filter( o -> keepObsoletes || !o.isObsolete() )
610 .collect( Collectors.toSet() )
611 ).orElse( Collections.emptySet() );
612 }
613
614 @Override
615 public boolean isEnabled() {
616
617 return isOntologyEnabled() || isOntologyLoaded() || isInitializationThreadAlive();
618 }
619
620 @Override
621 public boolean isOntologyLoaded() {
622
623 return state != null;
624 }
625
626
627
628
629
630 protected abstract OntologyModel loadModel( boolean processImports, LanguageLevel languageLevel, InferenceMode inferenceMode ) throws IOException;
631
632
633
634
635 protected abstract OntologyModel loadModelFromStream( InputStream is, boolean processImports, LanguageLevel languageLevel, InferenceMode inferenceMode ) throws IOException;
636
637 protected OntModelSpec getSpec( LanguageLevel languageLevel, InferenceMode inferenceMode ) {
638 String profile;
639 switch ( languageLevel ) {
640 case FULL:
641 profile = ProfileRegistry.OWL_LANG;
642 break;
643 case DL:
644 profile = ProfileRegistry.OWL_DL_LANG;
645 break;
646 case LITE:
647 profile = ProfileRegistry.OWL_LITE_LANG;
648 break;
649 default:
650 throw new UnsupportedOperationException( String.format( "Unsupported OWL language level %s.", languageLevel ) );
651 }
652 ReasonerFactory reasonerFactory;
653 switch ( inferenceMode ) {
654 case FULL:
655 reasonerFactory = OWLFBRuleReasonerFactory.theInstance();
656 break;
657 case MINI:
658 reasonerFactory = OWLMiniReasonerFactory.theInstance();
659 break;
660 case MICRO:
661 reasonerFactory = OWLMicroReasonerFactory.theInstance();
662 break;
663 case TRANSITIVE:
664 reasonerFactory = TransitiveReasonerFactory.theInstance();
665 break;
666 case NONE:
667 reasonerFactory = null;
668 break;
669 default:
670 throw new UnsupportedOperationException( String.format( "Unsupported inference level %s.", inferenceMode ) );
671 }
672 return new OntModelSpec( ModelFactory.createMemModelMaker(), null, reasonerFactory, profile );
673 }
674
675 @Override
676 public synchronized void index( boolean force ) {
677 String cacheName = getCacheName();
678 if ( cacheName == null ) {
679 log.warn( "This ontology does not support indexing; assign a cache name to be used." );
680 return;
681 }
682 if ( !searchEnabled ) {
683 log.warn( "Search is not enabled for this ontology." );
684 return;
685 }
686 State state = this.state;
687 if ( state == null ) {
688 log.warn( "Ontology {} is not initialized, cannot index it.", this );
689 return;
690 }
691 SearchIndex index;
692 try {
693 index = OntologyIndexer.indexOntology( cacheName, state.model, state.excludedWordsFromStemming, force );
694 } catch ( IOException e ) {
695 log.error( "Failed to generate index for {}.", this, e );
696 return;
697 }
698
699 this.state = new State( state.model, index, state.excludedWordsFromStemming, state.additionalRestrictions, state.languageLevel, state.inferenceMode, state.processImports, state.additionalPropertyUris, state.alternativeIDs );
700 }
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716 private State initSearchByAlternativeId( State state ) {
717 Map<String, String> alternativeIDs = new HashMap<>();
718
719 ExtendedIterator<OntClass> iterator = state.model.listClasses();
720 while ( iterator.hasNext() ) {
721 OntologyTerm ontologyTerm = new OntologyTermImpl( iterator.next(), state.additionalRestrictions );
722 if ( ontologyTerm.getUri() == null ) {
723 continue;
724 }
725
726 String baseOntologyUri = ontologyTerm.getUri().substring( 0, ontologyTerm.getUri().lastIndexOf( "/" ) + 1 );
727 for ( String alternativeId : ontologyTerm.getAlternativeIds() ) {
728
729 alternativeIDs.put( alternativeId, ontologyTerm.getUri() );
730
731 String alternativeIdModified = alternativeId.replace( ':', '_' );
732 alternativeIDs.put( baseOntologyUri + alternativeIdModified, ontologyTerm.getUri() );
733 }
734 }
735 return new State( state.model, state.index, state.excludedWordsFromStemming, state.additionalRestrictions, state.languageLevel, state.inferenceMode, state.processImports, state.additionalPropertyUris, alternativeIDs );
736 }
737
738 @Override
739 public void close() throws Exception {
740 if ( state != null ) {
741 state.close();
742 }
743 }
744
745 @Override
746 public String toString() {
747 return String.format( "%s [url=%s] [language level=%s] [inference mode=%s] [imports=%b] [search=%b]",
748 getOntologyName(), getOntologyUrl(), getLanguageLevel(), getInferenceMode(), getProcessImports(), isSearchEnabled() );
749 }
750
751 private Optional<State> getState() {
752 return Optional.ofNullable( state );
753 }
754
755 private Set<OntClass> getOntClassesFromTerms( OntModel model, Collection<OntologyTerm> terms ) {
756 return terms.stream()
757 .map( o -> {
758 if ( o instanceof OntologyTermImpl ) {
759 return ( ( OntologyTermImpl ) o ).getOntClass();
760 } else {
761 return o.getUri() != null ? model.getOntClass( o.getUri() ) : null;
762 }
763 } )
764 .filter( Objects::nonNull )
765 .collect( Collectors.toSet() );
766 }
767
768 private static class State implements AutoCloseable {
769 private final OntModel model;
770 @Nullable
771 private final SearchIndex index;
772 private final Set<String> excludedWordsFromStemming;
773 private final Set<Restriction> additionalRestrictions;
774 @Nullable
775 private final LanguageLevel languageLevel;
776 private final InferenceMode inferenceMode;
777 private final boolean processImports;
778 private final Set<String> additionalPropertyUris;
779 @Nullable
780 private final Map<String, String> alternativeIDs;
781
782 private State( OntModel model, @Nullable SearchIndex index, Set<String> excludedWordsFromStemming, Set<Restriction> additionalRestrictions, @Nullable LanguageLevel languageLevel, InferenceMode inferenceMode, boolean processImports, Set<String> additionalPropertyUris, @Nullable Map<String, String> alternativeIDs ) {
783 this.model = model;
784 this.index = index;
785 this.excludedWordsFromStemming = excludedWordsFromStemming;
786 this.additionalRestrictions = additionalRestrictions;
787 this.languageLevel = languageLevel;
788 this.inferenceMode = inferenceMode;
789 this.processImports = processImports;
790 this.additionalPropertyUris = additionalPropertyUris;
791 this.alternativeIDs = alternativeIDs;
792 }
793
794 @Override
795 public void close() throws Exception {
796 try {
797 model.close();
798 } finally {
799 if ( index != null ) {
800 index.close();
801 }
802 }
803 }
804 }
805 }