Skip to content

Commit 28a452b

Browse files
committed
Merge branch 'hotfix-1.1.31'
2 parents b7b29fb + 0732a9b commit 28a452b

8 files changed

Lines changed: 151 additions & 41 deletions

File tree

pom.xml

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<name>baseCode</name>
66
<groupId>baseCode</groupId>
77
<artifactId>baseCode</artifactId>
8-
<version>1.1.30</version>
8+
<version>1.1.31</version>
99
<inceptionYear>2003</inceptionYear>
1010
<description>
1111
<![CDATA[Data structures, math and statistics tools, and utilities that are often needed across projects.]]>
@@ -92,13 +92,12 @@
9292
<dependency>
9393
<groupId>org.apache.commons</groupId>
9494
<artifactId>commons-configuration2</artifactId>
95-
<version>2.11.0</version>
95+
<version>2.12.0</version>
9696
</dependency>
9797
<dependency>
9898
<groupId>org.apache.commons</groupId>
9999
<artifactId>commons-lang3</artifactId>
100-
<!-- 3.15.0 uses a secure random which is extremely slow, see #1194 in Gemma for details -->
101-
<version>3.14.0</version>
100+
<version>3.17.0</version>
102101
</dependency>
103102
<dependency>
104103
<groupId>commons-io</groupId>
@@ -119,7 +118,7 @@
119118
<dependency>
120119
<groupId>commons-logging</groupId>
121120
<artifactId>commons-logging</artifactId>
122-
<version>1.3.2</version>
121+
<version>1.3.5</version>
123122
<scope>runtime</scope>
124123
</dependency>
125124
<dependency>
@@ -314,13 +313,13 @@
314313
<dependency>
315314
<groupId>org.mockito</groupId>
316315
<artifactId>mockito-core</artifactId>
317-
<version>5.17.0</version>
316+
<version>5.20.0</version>
318317
<scope>test</scope>
319318
</dependency>
320319
<dependency>
321320
<groupId>org.assertj</groupId>
322321
<artifactId>assertj-core</artifactId>
323-
<version>3.27.4</version>
322+
<version>3.27.6</version>
324323
<scope>test</scope>
325324
</dependency>
326325
<dependency>
@@ -708,7 +707,7 @@
708707
<pavlab.server>frink.pavlab.msl.ubc.ca</pavlab.server>
709708
<pavlab.repoDir>/space/maven2</pavlab.repoDir>
710709
<pavlab.siteDir>/space/web/maven-sites</pavlab.siteDir>
711-
<log4j.version>2.25.1</log4j.version>
710+
<log4j.version>2.25.2</log4j.version>
712711
<lucene.version>3.6.2</lucene.version>
713712
<!-- rJava -->
714713
<!-- You may override this in ~/.m2/settings.xml -->

src/ubic/basecode/ontology/jena/AbstractOntologyService.java

Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ public abstract class AbstractOntologyService implements OntologyService {
8888
private boolean searchEnabled = true;
8989
private Set<String> excludedWordsFromStemming = Collections.emptySet();
9090
private Set<String> additionalPropertyUris = DEFAULT_ADDITIONAL_PROPERTIES.stream().map( Property::getURI ).collect( Collectors.toSet() );
91+
@Nullable
92+
private Set<String> allowedUriPrefixes = null;
9193

9294
protected AbstractOntologyService( String ontologyName, String ontologyUrl, boolean ontologyEnabled, @Nullable String cacheName ) {
9395
this.ontologyName = ontologyName;
@@ -126,9 +128,9 @@ public synchronized void startInitializationThread( boolean forceLoad, boolean f
126128
try {
127129
this.initialize( forceLoad, forceIndexing );
128130
} catch ( Exception e ) {
129-
log.error( "Initialization for %s failed.", e );
131+
log.error( "Initialization of {} failed.", this, e );
130132
}
131-
}, getOntologyName() + "_load_thread_" + RandomStringUtils.randomAlphanumeric( 5 ) );
133+
}, getOntologyName() + "_load_thread_" + RandomStringUtils.insecure().nextAlphanumeric( 5 ) );
132134
// To prevent VM from waiting on this thread to shut down (if shutting down).
133135
initializationThread.setDaemon( true );
134136
initializationThread.start();
@@ -235,6 +237,19 @@ public void setProcessImports( boolean processImports ) {
235237
this.processImports = processImports;
236238
}
237239

240+
@Override
241+
public void setAllowedUriPrefixes( String... uriPrefixes ) {
242+
if ( uriPrefixes.length == 0 ) {
243+
throw new IllegalArgumentException( "At least one URI prefix must be supplied." );
244+
}
245+
this.allowedUriPrefixes = new HashSet<>( Arrays.asList( uriPrefixes ) );
246+
}
247+
248+
@Override
249+
public void clearAllowedUriPrefixes() {
250+
this.allowedUriPrefixes = null;
251+
}
252+
238253
@Override
239254
public boolean isSearchEnabled() {
240255
return getState().map( state -> state.index != null ).orElse( searchEnabled );
@@ -288,6 +303,7 @@ private synchronized void initialize( @Nullable InputStream stream, boolean forc
288303
boolean processImports = this.processImports;
289304
boolean searchEnabled = this.searchEnabled;
290305
Set<String> excludedWordsFromStemming = this.excludedWordsFromStemming;
306+
Set<String> allowedUriPrefixes = this.allowedUriPrefixes;
291307

292308
// Detect configuration problems.
293309
if ( StringUtils.isBlank( ontologyUrl ) ) {
@@ -379,13 +395,15 @@ private synchronized void initialize( @Nullable InputStream stream, boolean forc
379395
}
380396
}
381397

382-
this.state = new State( model, index, excludedWordsFromStemming, additionalRestrictions, languageLevel, inferenceMode, processImports, additionalProperties.stream().map( Property::getURI ).collect( Collectors.toSet() ), null );
398+
this.state = new State( model, index, excludedWordsFromStemming, additionalRestrictions, languageLevel,
399+
inferenceMode, processImports, additionalProperties.stream().map( Property::getURI ).collect( Collectors.toSet() ),
400+
allowedUriPrefixes, null );
383401
if ( cacheName != null ) {
384402
// now that the terms have been replaced, we can clear old caches
385403
try {
386404
OntologyLoader.deleteOldCache( cacheName );
387405
} catch ( IOException e ) {
388-
log.error( String.format( String.format( "Failed to delete old cache directory for %s.", this ), e ) );
406+
log.error( "Failed to delete old cache directory for {}.", this, e );
389407
}
390408
}
391409

@@ -422,6 +440,7 @@ public Collection<OntologySearchResult<OntologyIndividual>> findIndividuals( Str
422440
return Collections.emptySet();
423441
}
424442
return state.index.searchIndividuals( state.model, search, maxResults ).stream()
443+
.filter( i -> state.isUriAllowed( i.result.getURI() ) )
425444
.map( i -> as( i.result, Individual.class ).map( r -> new OntologySearchResult<>( ( OntologyIndividual ) new OntologyIndividualImpl( r, state.additionalRestrictions ), i.score ) ) )
426445
.filter( Optional::isPresent )
427446
.map( Optional::get )
@@ -443,6 +462,7 @@ public Collection<OntologySearchResult<OntologyResource>> findResources( String
443462
return Collections.emptySet();
444463
}
445464
return state.index.search( state.model, searchString, maxResults ).stream()
465+
.filter( i -> state.isUriAllowed( i.result.getURI() ) )
446466
.filter( ( r -> r.result.canAs( OntClass.class ) || r.result.canAs( Individual.class ) ) )
447467
.map( r -> {
448468
if ( r.result.canAs( OntClass.class ) ) {
@@ -474,6 +494,7 @@ public Collection<OntologySearchResult<OntologyTerm>> findTerm( String search, i
474494
return Collections.emptySet();
475495
}
476496
return state.index.searchClasses( state.model, search, maxResults ).stream()
497+
.filter( i -> state.isUriAllowed( i.result.getURI() ) )
477498
.map( r -> as( r.result, OntClass.class ).map( s -> new OntologySearchResult<>( ( OntologyTerm ) new OntologyTermImpl( s, state.additionalRestrictions ), r.score ) ) )
478499
.filter( Optional::isPresent )
479500
.map( Optional::get )
@@ -514,6 +535,9 @@ public Set<String> getAllURIs() {
514535
@Override
515536
public OntologyResource getResource( String uri ) {
516537
return getState().map( state -> {
538+
if ( !state.isUriAllowed( uri ) ) {
539+
return null;
540+
}
517541
OntologyResource res;
518542
Resource resource = state.model.getResource( uri );
519543
if ( resource.getURI() == null ) {
@@ -536,6 +560,9 @@ public OntologyResource getResource( String uri ) {
536560
@Override
537561
public OntologyTerm getTerm( String uri ) {
538562
return getState().map( state -> {
563+
if ( !state.isUriAllowed( uri ) ) {
564+
return null;
565+
}
539566
OntClass ontCls = state.model.getOntClass( uri );
540567
// null or bnode
541568
if ( ontCls == null || ontCls.getURI() == null ) {
@@ -564,6 +591,7 @@ public Set<OntologyTerm> getParents( Collection<OntologyTerm> terms, boolean dir
564591
return getState().map( state ->
565592
JenaUtils.getParents( state.model, getOntClassesFromTerms( state.model, terms ), direct, includeAdditionalProperties ? state.additionalRestrictions : null )
566593
.stream()
594+
.filter( o -> state.isUriAllowed( o.getURI() ) )
567595
.map( o -> ( OntologyTerm ) new OntologyTermImpl( o, state.additionalRestrictions ) )
568596
.filter( o -> keepObsoletes || !o.isObsolete() )
569597
.collect( Collectors.toSet() ) )
@@ -576,6 +604,7 @@ public Set<OntologyTerm> getChildren( Collection<OntologyTerm> terms, boolean di
576604
return getState().map( state ->
577605
JenaUtils.getChildren( state.model, getOntClassesFromTerms( state.model, terms ), direct, includeAdditionalProperties ? state.additionalRestrictions : null )
578606
.stream()
607+
.filter( o -> state.isUriAllowed( o.getURI() ) )
579608
.map( o -> ( OntologyTerm ) new OntologyTermImpl( o, state.additionalRestrictions ) )
580609
.filter( o -> keepObsoletes || !o.isObsolete() )
581610
.collect( Collectors.toSet() )
@@ -667,7 +696,7 @@ public synchronized void index( boolean force ) {
667696
return;
668697
}
669698
// now we replace the index
670-
this.state = new State( state.model, index, state.excludedWordsFromStemming, state.additionalRestrictions, state.languageLevel, state.inferenceMode, state.processImports, state.additionalPropertyUris, state.alternativeIDs );
699+
this.state = new State( state.model, index, state.excludedWordsFromStemming, state.additionalRestrictions, state.languageLevel, state.inferenceMode, state.processImports, state.additionalPropertyUris, state.allowedUriPrefixes, state.alternativeIDs );
671700
}
672701

673702
/**
@@ -703,7 +732,7 @@ private State initSearchByAlternativeId( State state ) {
703732
alternativeIDs.put( baseOntologyUri + alternativeIdModified, ontologyTerm.getUri() );
704733
}
705734
}
706-
return new State( state.model, state.index, state.excludedWordsFromStemming, state.additionalRestrictions, state.languageLevel, state.inferenceMode, state.processImports, state.additionalPropertyUris, alternativeIDs );
735+
return new State( state.model, state.index, state.excludedWordsFromStemming, state.additionalRestrictions, state.languageLevel, state.inferenceMode, state.processImports, state.additionalPropertyUris, state.allowedUriPrefixes, alternativeIDs );
707736
}
708737

709738
@Override
@@ -715,8 +744,8 @@ public void close() throws Exception {
715744

716745
@Override
717746
public String toString() {
718-
return String.format( "%s [url=%s] [language level=%s] [inference mode=%s] [imports=%b] [search=%b]",
719-
getOntologyName(), getOntologyUrl(), getLanguageLevel(), getInferenceMode(), getProcessImports(), isSearchEnabled() );
747+
return String.format( "%s [url=%s] [allowed prefixes=%s] [language level=%s] [inference mode=%s] [imports=%b] [search=%b]",
748+
getOntologyName(), getOntologyUrl(), allowedUriPrefixes != null ? String.join( ",", allowedUriPrefixes ) : "*", getLanguageLevel(), getInferenceMode(), getProcessImports(), isSearchEnabled() );
720749
}
721750

722751
private Optional<State> getState() {
@@ -747,9 +776,11 @@ private static class State implements AutoCloseable {
747776
private final boolean processImports;
748777
private final Set<String> additionalPropertyUris;
749778
@Nullable
779+
private final Set<String> allowedUriPrefixes;
780+
@Nullable
750781
private final Map<String, String> alternativeIDs;
751782

752-
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+
private State( OntModel model, @Nullable SearchIndex index, Set<String> excludedWordsFromStemming, Set<Restriction> additionalRestrictions, @Nullable LanguageLevel languageLevel, InferenceMode inferenceMode, boolean processImports, Set<String> additionalPropertyUris, @Nullable Set<String> allowedUriPrefixes, @Nullable Map<String, String> alternativeIDs ) {
753784
this.model = model;
754785
this.index = index;
755786
this.excludedWordsFromStemming = excludedWordsFromStemming;
@@ -758,9 +789,20 @@ private State( OntModel model, @Nullable SearchIndex index, Set<String> excluded
758789
this.inferenceMode = inferenceMode;
759790
this.processImports = processImports;
760791
this.additionalPropertyUris = additionalPropertyUris;
792+
this.allowedUriPrefixes = allowedUriPrefixes;
761793
this.alternativeIDs = alternativeIDs;
762794
}
763795

796+
/**
797+
* Check if this particular state allows a given URI from being returned by the service.
798+
*/
799+
public boolean isUriAllowed( @Nullable String uri ) {
800+
if ( allowedUriPrefixes == null ) {
801+
return true;
802+
}
803+
return uri == null || allowedUriPrefixes.stream().anyMatch( uri::startsWith );
804+
}
805+
764806
@Override
765807
public void close() throws Exception {
766808
try {

src/ubic/basecode/ontology/jena/OntologyLoader.java

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
import com.hp.hpl.jena.rdf.model.*;
2626
import com.hp.hpl.jena.shared.CannotCreateException;
2727
import com.hp.hpl.jena.shared.JenaException;
28-
import com.hp.hpl.jena.tdb.TDBFactory;
28+
import com.hp.hpl.jena.sparql.graph.GraphReadOnly;
2929
import org.apache.commons.io.FileUtils;
3030
import org.apache.commons.lang3.StringUtils;
3131
import org.apache.commons.lang3.time.StopWatch;
@@ -85,6 +85,16 @@ static OntModel createMemoryModel( String url, String name, @Nullable String cac
8585
return model;
8686
}
8787

88+
/**
89+
* ModelFactory.createMemModelMaker()
90+
* Get model that is entirely in memory.
91+
*/
92+
private static OntModel getModel( String name, boolean processImports, OntModelSpec spec ) {
93+
ModelMaker maker = ModelFactory.createMemModelMaker();
94+
Model base = maker.createModel( name, false );
95+
return getModel( maker, base, processImports, spec );
96+
}
97+
8898
private static void readModelFromUrl( OntModel model, String url, @Nullable String cacheName ) throws IOException {
8999
boolean attemptToLoadFromDisk = false;
90100
URLConnection urlc = null;
@@ -165,7 +175,7 @@ private static void readModelFromUrl( OntModel model, String url, @Nullable Stri
165175
* contains all the necessary definitions.
166176
* @param spec spec to use to create the ontology model
167177
*/
168-
public static OntModel createTdbModel( Dataset dataset, @Nullable String name, boolean processImports, OntModelSpec spec ) {
178+
public static OntModel createTdbModel( Dataset dataset, @Nullable String name, boolean processImports, OntModelSpec spec, boolean readOnly ) {
169179
ModelMaker maker = ModelFactory.createMemModelMaker();
170180
Model base;
171181
if ( name != null ) {
@@ -177,16 +187,9 @@ public static OntModel createTdbModel( Dataset dataset, @Nullable String name, b
177187
throw new IllegalStateException( String.format( "The %s at %s is empty.",
178188
name != null ? "named model " + name : "default model", dataset ) );
179189
}
180-
return getModel( maker, base, processImports, spec );
181-
}
182-
183-
/**
184-
* ModelFactory.createMemModelMaker()
185-
* Get model that is entirely in memory.
186-
*/
187-
private static OntModel getModel( String name, boolean processImports, OntModelSpec spec ) {
188-
ModelMaker maker = ModelFactory.createMemModelMaker();
189-
Model base = maker.createModel( name, false );
190+
if ( readOnly ) {
191+
base = ModelFactory.createModelForGraph( new GraphReadOnly( base.getGraph() ) );
192+
}
190193
return getModel( maker, base, processImports, spec );
191194
}
192195

src/ubic/basecode/ontology/jena/TdbOntologyService.java

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,18 @@
22

33
import com.hp.hpl.jena.query.Dataset;
44
import com.hp.hpl.jena.tdb.TDBFactory;
5+
import com.hp.hpl.jena.tdb.base.file.Location;
6+
import org.apache.commons.io.file.PathUtils;
57
import ubic.basecode.ontology.model.OntologyModel;
68

79
import javax.annotation.Nullable;
10+
import java.io.IOException;
811
import java.io.InputStream;
12+
import java.nio.file.Files;
913
import java.nio.file.Path;
14+
import java.util.Set;
15+
import java.util.stream.Collectors;
16+
import java.util.stream.Stream;
1017

1118
/**
1219
* An implementation based on Jena TDB.
@@ -16,22 +23,54 @@ public class TdbOntologyService extends AbstractOntologyService {
1623

1724
private final Path tdbDir;
1825
private final String modelName;
26+
private final boolean readOnly;
1927

2028
@Nullable
2129
private Dataset dataset;
2230

23-
public TdbOntologyService( String ontologyName, Path tdbDir, @Nullable String modelName, boolean ontologyEnabled, @Nullable String cacheName ) {
31+
/**
32+
* Temporary d
33+
*/
34+
private Path tempDir;
35+
36+
/**
37+
* @param readOnly open the TDB database in read-only mode, allowing multiple JVMs to share a common TDB. For this
38+
* to work safely, all the TDB files must not be writable. Additionally, a temporary directory with
39+
* symbolic links will be created since Jena will still be using a lock file.
40+
*/
41+
public TdbOntologyService( String ontologyName, Path tdbDir, @Nullable String modelName, boolean ontologyEnabled, @Nullable String cacheName, boolean readOnly ) {
2442
super( ontologyName, tdbDir.toUri().toString(), ontologyEnabled, cacheName );
2543
this.tdbDir = tdbDir;
2644
this.modelName = modelName;
45+
this.readOnly = readOnly;
2746
}
2847

2948
@Override
30-
protected OntologyModel loadModel( boolean processImports, LanguageLevel languageLevel, InferenceMode inferenceMode ) {
49+
protected OntologyModel loadModel( boolean processImports, LanguageLevel languageLevel, InferenceMode inferenceMode ) throws IOException {
3150
if ( dataset == null ) {
32-
dataset = TDBFactory.createDataset( tdbDir.toString() );
51+
if ( readOnly ) {
52+
// lock the location and make a copy
53+
Location loc = Location.create( tdbDir.toString() );
54+
loc.getLock().obtain();
55+
try {
56+
Set<Path> filesToLink;
57+
try ( Stream<Path> z = Files.list( tdbDir ) ) {
58+
filesToLink = z.collect( Collectors.toSet() );
59+
}
60+
tempDir = Files.createTempDirectory( getOntologyName() + ".tdb" );
61+
for ( Path p : filesToLink ) {
62+
Files.copy( p, tempDir.resolve( p.getFileName() ) );
63+
}
64+
} finally {
65+
loc.getLock().release();
66+
}
67+
log.info( "Reading read-only TDB model from {}.", tempDir );
68+
dataset = TDBFactory.createDataset( tempDir.toString() );
69+
} else {
70+
dataset = TDBFactory.createDataset( tdbDir.toString() );
71+
}
3372
}
34-
return new OntologyModelImpl( OntologyLoader.createTdbModel( dataset, modelName, processImports, getSpec( languageLevel, inferenceMode ) ) );
73+
return new OntologyModelImpl( OntologyLoader.createTdbModel( dataset, modelName, processImports, getSpec( languageLevel, inferenceMode ), readOnly ) );
3574
}
3675

3776
@Override
@@ -47,6 +86,9 @@ public void close() throws Exception {
4786
if ( dataset != null ) {
4887
TDBFactory.release( dataset );
4988
}
89+
if ( tempDir != null ) {
90+
PathUtils.delete( tempDir );
91+
}
5092
}
5193
}
5294
}

0 commit comments

Comments
 (0)