diff --git a/.editorconfig b/.editorconfig index a951459e28..76c98d911f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,11 +1,24 @@ root = true +[Jenkinsfile] +indent_style=space +indent_size=4 + +[init-indices.sql] +indent_size=2 + [/pom.xml] indent_style=tab [/gemma-web/pom.xml] indent_style=tab +[BioMaterial.hbm.xml] +indent_size=3 + +[BioAssay.hbm.xml] +indent_size=3 + [Investigation.hbm.xml] indent_style=tab @@ -25,4 +38,7 @@ indent_size=3 indent_style=tab [gemma-web/**/applicationContext-security.xml] -indent_style=tab \ No newline at end of file +indent_style=tab + +[dwrServices.js] +indent_size=4 \ No newline at end of file diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index 7217dba5bd..5b1866a662 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -1,5 +1,10 @@ + + \ No newline at end of file + diff --git a/.jenkins/Jenkinsfile b/.jenkins/Jenkinsfile index 706c5a1cf4..5bfb9915bf 100644 --- a/.jenkins/Jenkinsfile +++ b/.jenkins/Jenkinsfile @@ -165,8 +165,8 @@ pipeline { } steps { sh 'mvn -B site-deploy' - sh "ln -sf ${siteDir}/gemma/gemma-${gemmaVersion} ${dataDir}/gemma-devsite" - sh "ln -sf ${siteDir}/baseCode/baseCode-${baseCodeVersion} ${dataDir}/basecode-site" + sh "ln -Tsf ${siteDir}/gemma/gemma-${gemmaVersion} ${dataDir}/gemma-devsite" + sh "ln -Tsf ${siteDir}/baseCode/baseCode-${baseCodeVersion} ${dataDir}/baseCode-site" } } stage ('Deploy Gemma Web') { @@ -192,7 +192,7 @@ pipeline { } } steps { - sh "rsync -adv gemma-cli/target/appassembler ${cliDir}" + sh "rsync -av --delete gemma-cli/target/appassembler/ ${cliDir}/" } } } diff --git a/.jenkins/README.md b/.jenkins/README.md new file mode 100644 index 0000000000..7aefdf90df --- /dev/null +++ b/.jenkins/README.md @@ -0,0 +1,8 @@ +This folder contains a configuration for Jenkins and a script to validate the +Jenkinsfile. + +Run the following before pushing any changes to the CI configuration: + +```bash +.jenkins/validate-jenkinsfile +``` diff --git a/.jenkins/validate-jenkinsfile b/.jenkins/validate-jenkinsfile new file mode 100755 index 0000000000..72758e7bb4 --- /dev/null +++ b/.jenkins/validate-jenkinsfile @@ -0,0 +1,4 @@ +#!/bin/bash +JENKINS_URL=https://jenkins.pavlab.msl.ubc.ca +JENKINS_CRUMB=`curl "$JENKINS_URL/crumbIssuer/api/xml?xpath=concat(//crumbRequestField,\":\",//crumb)"` +curl -X POST -H $JENKINS_CRUMB -F "jenkinsfile=<$(dirname "${BASH_SOURCE[0]}")/Jenkinsfile" $JENKINS_URL/pipeline-model-converter/validate diff --git a/gemma-cli/pom.xml b/gemma-cli/pom.xml index ffb66474a0..9b1ae48b8b 100644 --- a/gemma-cli/pom.xml +++ b/gemma-cli/pom.xml @@ -3,7 +3,7 @@ gemma gemma - 1.30.6 + 1.31.0 4.0.0 gemma-cli diff --git a/gemma-cli/src/main/java/ubic/gemma/core/apps/AffyDataFromCelCli.java b/gemma-cli/src/main/java/ubic/gemma/core/apps/AffyDataFromCelCli.java index 35d2c3f2c3..ed67bb5245 100644 --- a/gemma-cli/src/main/java/ubic/gemma/core/apps/AffyDataFromCelCli.java +++ b/gemma-cli/src/main/java/ubic/gemma/core/apps/AffyDataFromCelCli.java @@ -22,6 +22,7 @@ import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; import org.apache.commons.lang3.StringUtils; import ubic.gemma.core.loader.expression.DataUpdater; import ubic.gemma.core.loader.expression.geo.model.GeoPlatform; @@ -182,7 +183,7 @@ protected void doWork() throws Exception { } @Override - protected void processOptions( CommandLine commandLine ) { + protected void processOptions( CommandLine commandLine ) throws ParseException { super.processOptions( commandLine ); if ( commandLine.hasOption( AffyDataFromCelCli.APT_FILE_OPT ) ) { this.aptFile = commandLine.getOptionValue( AffyDataFromCelCli.APT_FILE_OPT ); diff --git a/gemma-cli/src/main/java/ubic/gemma/core/apps/AffyProbeCollapseCli.java b/gemma-cli/src/main/java/ubic/gemma/core/apps/AffyProbeCollapseCli.java index b79c3348f9..6a39f592a3 100644 --- a/gemma-cli/src/main/java/ubic/gemma/core/apps/AffyProbeCollapseCli.java +++ b/gemma-cli/src/main/java/ubic/gemma/core/apps/AffyProbeCollapseCli.java @@ -22,6 +22,7 @@ import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; import ubic.gemma.core.analysis.sequence.SequenceManipulation; import ubic.gemma.core.loader.expression.arrayDesign.AffyProbeReader; import ubic.gemma.model.expression.designElement.CompositeSequence; @@ -69,7 +70,7 @@ protected void buildOptions( Options options ) { } @Override - protected void processOptions( CommandLine commandLine ) { + protected void processOptions( CommandLine commandLine ) throws ParseException { super.processOptions( commandLine ); affyProbeFileName = commandLine.getOptionValue( "affyProbeFile" ); } diff --git a/gemma-cli/src/main/java/ubic/gemma/core/apps/ArrayDesignAlternativePopulateCli.java b/gemma-cli/src/main/java/ubic/gemma/core/apps/ArrayDesignAlternativePopulateCli.java index 4e951aaf48..85f0d06157 100644 --- a/gemma-cli/src/main/java/ubic/gemma/core/apps/ArrayDesignAlternativePopulateCli.java +++ b/gemma-cli/src/main/java/ubic/gemma/core/apps/ArrayDesignAlternativePopulateCli.java @@ -24,7 +24,7 @@ import org.apache.commons.lang3.StringUtils; import org.springframework.core.io.ClassPathResource; import ubic.gemma.core.apps.GemmaCLI.CommandGroup; -import ubic.gemma.core.util.AbstractCLIContextCLI; +import ubic.gemma.core.util.AbstractAuthenticatedCLI; import ubic.gemma.model.expression.arrayDesign.ArrayDesign; import ubic.gemma.persistence.service.expression.arrayDesign.ArrayDesignService; @@ -38,7 +38,7 @@ * * @author paul */ -public class ArrayDesignAlternativePopulateCli extends AbstractCLIContextCLI { +public class ArrayDesignAlternativePopulateCli extends AbstractAuthenticatedCLI { @Override public CommandGroup getCommandGroup() { @@ -61,7 +61,7 @@ protected boolean requireLogin() { } @Override - protected void processOptions( CommandLine commandLine ) throws Exception { + protected void processOptions( CommandLine commandLine ) { } diff --git a/gemma-cli/src/main/java/ubic/gemma/core/apps/ArrayDesignAnnotationFileCli.java b/gemma-cli/src/main/java/ubic/gemma/core/apps/ArrayDesignAnnotationFileCli.java index 2096753140..3df2b10a93 100644 --- a/gemma-cli/src/main/java/ubic/gemma/core/apps/ArrayDesignAnnotationFileCli.java +++ b/gemma-cli/src/main/java/ubic/gemma/core/apps/ArrayDesignAnnotationFileCli.java @@ -18,21 +18,11 @@ */ package ubic.gemma.core.apps; -import java.io.BufferedReader; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashSet; - import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; import org.apache.commons.lang3.StringUtils; - import ubic.gemma.core.analysis.service.ArrayDesignAnnotationService; import ubic.gemma.core.apps.GemmaCLI.CommandGroup; import ubic.gemma.core.genome.gene.service.GeneService; @@ -45,6 +35,11 @@ import ubic.gemma.model.genome.Taxon; import ubic.gemma.persistence.service.genome.taxon.TaxonService; +import java.io.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; + /** * Given an array design creates a Gene Ontology Annotation file Given a batch file creates all the Annotation files for * the AD's specified in the batch file Given nothing creates annotation files for every AD that isn't subsumed or @@ -107,9 +102,9 @@ protected void buildOptions( Options options ) { } @Override - protected void processOptions( CommandLine commandLine ) { + protected void processOptions( CommandLine commandLine ) throws ParseException { - if ( autoSeek ) { + if ( isAutoSeek() ) { throw new IllegalArgumentException( "This CLI doesn't support the auto option" ); } @@ -333,7 +328,7 @@ private void processFromListInFile() throws IOException { continue; } - ArrayDesign arrayDesign = this.locateArrayDesign( accession, getArrayDesignService() ); + ArrayDesign arrayDesign = this.locateArrayDesign( accession ); try { this.processAD( arrayDesign ); diff --git a/gemma-cli/src/main/java/ubic/gemma/core/apps/ArrayDesignBioSequenceDetachCli.java b/gemma-cli/src/main/java/ubic/gemma/core/apps/ArrayDesignBioSequenceDetachCli.java index d221f02728..30d4873da9 100644 --- a/gemma-cli/src/main/java/ubic/gemma/core/apps/ArrayDesignBioSequenceDetachCli.java +++ b/gemma-cli/src/main/java/ubic/gemma/core/apps/ArrayDesignBioSequenceDetachCli.java @@ -21,8 +21,8 @@ import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; import ubic.gemma.model.common.auditAndSecurity.eventType.ArrayDesignSequenceRemoveEvent; -import ubic.gemma.model.common.auditAndSecurity.eventType.AuditEventType; import ubic.gemma.model.expression.arrayDesign.ArrayDesign; import ubic.gemma.model.expression.designElement.CompositeSequence; import ubic.gemma.model.genome.biosequence.BioSequence; @@ -61,7 +61,7 @@ protected void buildOptions( Options options ) { } @Override - protected void processOptions( CommandLine commandLine ) { + protected void processOptions( CommandLine commandLine ) throws ParseException { super.processOptions( commandLine ); this.delete = commandLine.hasOption( "delete" ); } diff --git a/gemma-cli/src/main/java/ubic/gemma/core/apps/ArrayDesignBlatCli.java b/gemma-cli/src/main/java/ubic/gemma/core/apps/ArrayDesignBlatCli.java index 571e3edb5a..c0c6075aa2 100644 --- a/gemma-cli/src/main/java/ubic/gemma/core/apps/ArrayDesignBlatCli.java +++ b/gemma-cli/src/main/java/ubic/gemma/core/apps/ArrayDesignBlatCli.java @@ -21,6 +21,7 @@ import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; import ubic.gemma.core.apps.GemmaCLI.CommandGroup; import ubic.gemma.core.loader.expression.arrayDesign.ArrayDesignSequenceAlignmentService; import ubic.gemma.core.loader.genome.BlatResultParser; @@ -36,7 +37,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Date; -import java.util.concurrent.*; +import java.util.concurrent.Callable; /** * Command line interface to run blat on the sequences for a microarray; the results are persisted in the DB. You must @@ -72,6 +73,7 @@ protected void buildOptions( Options options ) { .desc( "Threshold (0-1.0) for acceptance of BLAT alignments [Default = " + this.blatScoreThreshold + "]" ) .longOpt( "scoreThresh" ) + .type( Double.class ) .build(); options.addOption( Option.builder( "sensitive" ).desc( "Run on more sensitive server, if available" ).build() ); @@ -89,7 +91,7 @@ protected void buildOptions( Options options ) { } @Override - protected void processOptions( CommandLine commandLine ) { + protected void processOptions( CommandLine commandLine ) throws ParseException { super.processOptions( commandLine ); if ( commandLine.hasOption( "sensitive" ) ) { @@ -105,7 +107,7 @@ protected void processOptions( CommandLine commandLine ) { // } if ( commandLine.hasOption( 's' ) ) { - this.blatScoreThreshold = this.getDoubleOptionValue( commandLine, 's' ); + this.blatScoreThreshold = ( Double ) commandLine.getParsedOptionValue( 's' ); } TaxonService taxonService = this.getBean( TaxonService.class ); diff --git a/gemma-cli/src/main/java/ubic/gemma/core/apps/ArrayDesignMergeCli.java b/gemma-cli/src/main/java/ubic/gemma/core/apps/ArrayDesignMergeCli.java index 2314ba6af9..829a4d05fa 100644 --- a/gemma-cli/src/main/java/ubic/gemma/core/apps/ArrayDesignMergeCli.java +++ b/gemma-cli/src/main/java/ubic/gemma/core/apps/ArrayDesignMergeCli.java @@ -21,6 +21,7 @@ import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; import org.apache.commons.lang3.StringUtils; import ubic.gemma.core.loader.expression.arrayDesign.ArrayDesignMergeService; import ubic.gemma.model.expression.arrayDesign.ArrayDesign; @@ -95,14 +96,14 @@ protected void buildOptions( Options options ) { } @Override - protected void processOptions( CommandLine commandLine ) { + protected void processOptions( CommandLine commandLine ) throws ParseException { super.processOptions( commandLine ); if ( commandLine.hasOption( 'o' ) ) {// required String otherArrayDesignName = commandLine.getOptionValue( 'o' ); String[] names = StringUtils.split( otherArrayDesignName, ',' ); this.otherArrayDesigns = new HashSet<>(); for ( String string : names ) { - ArrayDesign o = this.locateArrayDesign( string, getArrayDesignService() ); + ArrayDesign o = this.locateArrayDesign( string ); if ( o == null ) { throw new IllegalArgumentException( "Array design " + string + " not found" ); } diff --git a/gemma-cli/src/main/java/ubic/gemma/core/apps/ArrayDesignProbeCleanupCLI.java b/gemma-cli/src/main/java/ubic/gemma/core/apps/ArrayDesignProbeCleanupCLI.java index a06a5c8b0d..19bade4309 100644 --- a/gemma-cli/src/main/java/ubic/gemma/core/apps/ArrayDesignProbeCleanupCLI.java +++ b/gemma-cli/src/main/java/ubic/gemma/core/apps/ArrayDesignProbeCleanupCLI.java @@ -21,9 +21,9 @@ import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; import ubic.gemma.core.util.AbstractCLI; import ubic.gemma.model.expression.arrayDesign.ArrayDesign; import ubic.gemma.model.expression.designElement.CompositeSequence; @@ -38,7 +38,6 @@ * * @author Paul */ -@Component public class ArrayDesignProbeCleanupCLI extends ArrayDesignSequenceManipulatingCli { @Autowired @@ -59,7 +58,7 @@ protected void buildOptions( Options options ) { } @Override - protected void processOptions( CommandLine commandLine ) { + protected void processOptions( CommandLine commandLine ) throws ParseException { super.processOptions( commandLine ); if ( commandLine.hasOption( 'f' ) ) { file = commandLine.getOptionValue( 'f' ); diff --git a/gemma-cli/src/main/java/ubic/gemma/core/apps/ArrayDesignProbeMapperCli.java b/gemma-cli/src/main/java/ubic/gemma/core/apps/ArrayDesignProbeMapperCli.java index b020dd810a..7f0ba64765 100644 --- a/gemma-cli/src/main/java/ubic/gemma/core/apps/ArrayDesignProbeMapperCli.java +++ b/gemma-cli/src/main/java/ubic/gemma/core/apps/ArrayDesignProbeMapperCli.java @@ -3,6 +3,8 @@ import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; +import org.springframework.beans.factory.annotation.Autowired; import ubic.gemma.core.analysis.sequence.ProbeMapperConfig; import ubic.gemma.core.loader.expression.arrayDesign.ArrayDesignProbeMapperService; import ubic.gemma.core.util.AbstractCLI; @@ -14,6 +16,7 @@ import ubic.gemma.model.expression.designElement.CompositeSequence; import ubic.gemma.model.genome.Taxon; import ubic.gemma.model.genome.sequenceAnalysis.BlatAssociation; +import ubic.gemma.persistence.service.common.auditAndSecurity.AuditTrailService; import ubic.gemma.persistence.service.common.description.ExternalDatabaseService; import ubic.gemma.persistence.service.expression.designElement.CompositeSequenceService; import ubic.gemma.persistence.service.genome.taxon.TaxonService; @@ -54,6 +57,9 @@ public class ArrayDesignProbeMapperCli extends ArrayDesignSequenceManipulatingCl private final static String OPTION_MRNA = "m"; private final static String OPTION_REFSEQ = "r"; + @Autowired + private AuditTrailService auditTrailService; + private String[] probeNames = null; private ProbeMapperConfig config; private ArrayDesignProbeMapperService arrayDesignProbeMapperService; @@ -80,17 +86,19 @@ public GemmaCLI.CommandGroup getCommandGroup() { protected void buildOptions( Options options ) { super.buildOptions( options ); - options.addOption( Option.builder( "i" ).hasArg().argName( "value" ).desc( - "Sequence identity threshold, default = " + ProbeMapperConfig.DEFAULT_IDENTITY_THRESHOLD ) + options.addOption( Option.builder( "i" ).hasArg().argName( "value" ) + .type( Double.class ) + .desc( "Sequence identity threshold, default = " + ProbeMapperConfig.DEFAULT_IDENTITY_THRESHOLD ) .longOpt( "identityThreshold" ).build() ); options.addOption( Option.builder( "s" ).hasArg().argName( "value" ) + .type( Double.class ) .desc( "Blat score threshold, default = " + ProbeMapperConfig.DEFAULT_SCORE_THRESHOLD ) .longOpt( "scoreThreshold" ).build() ); - options.addOption( Option.builder( "o" ).hasArg().argName( "value" ).desc( - "Minimum fraction of probe overlap with exons, default = " - + ProbeMapperConfig.DEFAULT_MINIMUM_EXON_OVERLAP_FRACTION ) + options.addOption( Option.builder( "o" ).hasArg().argName( "value" ) + .type( Double.class ) + .desc( "Minimum fraction of probe overlap with exons, default = " + ProbeMapperConfig.DEFAULT_MINIMUM_EXON_OVERLAP_FRACTION ) .longOpt( "overlapThreshold" ) .build() ); @@ -178,15 +186,11 @@ protected boolean requireLogin() { * @see AbstractCLI#processOptions(CommandLine) */ @Override - protected void processOptions( CommandLine commandLine ) { + protected void processOptions( CommandLine commandLine ) throws ParseException { super.processOptions( commandLine ); arrayDesignProbeMapperService = this.getBean( ArrayDesignProbeMapperService.class ); taxonService = this.getBean( TaxonService.class ); - if ( commandLine.hasOption( AbstractCLI.THREADS_OPTION ) ) { - this.numThreads = this.getIntegerOptionValue( commandLine, "threads" ); - } - if ( commandLine.hasOption( "import" ) ) { if ( !commandLine.hasOption( 't' ) ) { throw new IllegalArgumentException( "You must provide the taxon when using the import option" ); @@ -208,7 +212,7 @@ protected void processOptions( CommandLine commandLine ) { } } if ( commandLine.hasOption( 't' ) ) { - this.taxon = this.setTaxonByName( commandLine, taxonService ); + this.taxon = this.getTaxonByName( commandLine ); } if ( commandLine.hasOption( "nodb" ) ) { @@ -230,7 +234,7 @@ protected void processOptions( CommandLine commandLine ) { } if ( commandLine.hasOption( 's' ) ) { - blatScoreThreshold = getDoubleOptionValue( commandLine, 's' ); + blatScoreThreshold = ( Double ) commandLine.getParsedOptionValue( 's' ); if ( blatScoreThreshold < 0 || blatScoreThreshold > 1 ) { throw new IllegalArgumentException( "BLAT score threshold must be between 0 and 1" ); } @@ -245,14 +249,14 @@ protected void processOptions( CommandLine commandLine ) { this.mirnaOnlyModeOption = commandLine.hasOption( ArrayDesignProbeMapperCli.MIRNA_ONLY_MODE_OPTION ); if ( commandLine.hasOption( 'i' ) ) { - identityThreshold = this.getDoubleOptionValue( commandLine, 'i' ); + identityThreshold = ( Double ) commandLine.getParsedOptionValue( 'i' ); if ( identityThreshold < 0 || identityThreshold > 1 ) { throw new IllegalArgumentException( "Identity threshold must be between 0 and 1" ); } } if ( commandLine.hasOption( 'o' ) ) { - overlapThreshold = this.getDoubleOptionValue( commandLine, 'o' ); + overlapThreshold = ( Double ) commandLine.getParsedOptionValue( 'o' ); if ( overlapThreshold < 0 || overlapThreshold > 1 ) { throw new IllegalArgumentException( "Overlap threshold must be between 0 and 1" ); } @@ -476,7 +480,7 @@ protected void doWork() throws Exception { } } - } else if ( taxon != null || skipIfLastRunLaterThan != null || autoSeek ) { + } else if ( taxon != null || skipIfLastRunLaterThan != null || isAutoSeek() ) { if ( directAnnotationInputFileName != null ) { throw new IllegalStateException( @@ -700,4 +704,13 @@ public Void call() { return null; } } + + protected Taxon getTaxonByName( CommandLine commandLine ) { + String taxonName = commandLine.getOptionValue( 't' ); + ubic.gemma.model.genome.Taxon taxon = taxonService.findByCommonName( taxonName ); + if ( taxon == null ) { + AbstractCLI.log.error( "ERROR: Cannot find taxon " + taxonName ); + } + return taxon; + } } diff --git a/gemma-cli/src/main/java/ubic/gemma/core/apps/ArrayDesignProbeRenamerCli.java b/gemma-cli/src/main/java/ubic/gemma/core/apps/ArrayDesignProbeRenamerCli.java index 52f051fec8..987871cda6 100755 --- a/gemma-cli/src/main/java/ubic/gemma/core/apps/ArrayDesignProbeRenamerCli.java +++ b/gemma-cli/src/main/java/ubic/gemma/core/apps/ArrayDesignProbeRenamerCli.java @@ -21,6 +21,7 @@ import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; import org.apache.commons.lang3.StringUtils; import ubic.gemma.core.apps.GemmaCLI.CommandGroup; import ubic.gemma.core.util.AbstractCLI; @@ -66,7 +67,7 @@ protected void buildOptions( Options options ) { } @Override - protected void processOptions( CommandLine commandLine ) { + protected void processOptions( CommandLine commandLine ) throws ParseException { super.processOptions( commandLine ); this.fileName = commandLine.getOptionValue( FILE_OPT ); } diff --git a/gemma-cli/src/main/java/ubic/gemma/core/apps/ArrayDesignRepeatScanCli.java b/gemma-cli/src/main/java/ubic/gemma/core/apps/ArrayDesignRepeatScanCli.java index 3eeb709e42..34a81b9ea9 100644 --- a/gemma-cli/src/main/java/ubic/gemma/core/apps/ArrayDesignRepeatScanCli.java +++ b/gemma-cli/src/main/java/ubic/gemma/core/apps/ArrayDesignRepeatScanCli.java @@ -21,6 +21,7 @@ import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; import ubic.gemma.core.analysis.sequence.RepeatScan; import ubic.gemma.core.loader.expression.arrayDesign.ArrayDesignSequenceAlignmentServiceImpl; import ubic.gemma.core.util.AbstractCLI; @@ -57,7 +58,7 @@ protected void buildOptions( Options options ) { } @Override - protected void processOptions( CommandLine commandLine ) { + protected void processOptions( CommandLine commandLine ) throws ParseException { super.processOptions( commandLine ); if ( commandLine.hasOption( 'f' ) ) { this.inputFileName = commandLine.getOptionValue( 'f' ); diff --git a/gemma-cli/src/main/java/ubic/gemma/core/apps/ArrayDesignSequenceAssociationCli.java b/gemma-cli/src/main/java/ubic/gemma/core/apps/ArrayDesignSequenceAssociationCli.java index 04be313214..72db4e1a8e 100644 --- a/gemma-cli/src/main/java/ubic/gemma/core/apps/ArrayDesignSequenceAssociationCli.java +++ b/gemma-cli/src/main/java/ubic/gemma/core/apps/ArrayDesignSequenceAssociationCli.java @@ -21,6 +21,7 @@ import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; import org.apache.commons.lang3.StringUtils; import ubic.basecode.util.FileTools; import ubic.gemma.core.loader.expression.arrayDesign.ArrayDesignSequenceProcessingService; @@ -214,7 +215,7 @@ protected void buildOptions( Options options ) { } @Override - protected void processOptions( CommandLine commandLine ) { + protected void processOptions( CommandLine commandLine ) throws ParseException { super.processOptions( commandLine ); arrayDesignSequenceProcessingService = this.getBean( ArrayDesignSequenceProcessingService.class ); this.taxonService = this.getBean( TaxonService.class ); diff --git a/gemma-cli/src/main/java/ubic/gemma/core/apps/ArrayDesignSequenceManipulatingCli.java b/gemma-cli/src/main/java/ubic/gemma/core/apps/ArrayDesignSequenceManipulatingCli.java index 1557662d49..7143bfd8df 100644 --- a/gemma-cli/src/main/java/ubic/gemma/core/apps/ArrayDesignSequenceManipulatingCli.java +++ b/gemma-cli/src/main/java/ubic/gemma/core/apps/ArrayDesignSequenceManipulatingCli.java @@ -21,16 +21,20 @@ import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; import ubic.gemma.core.analysis.report.ArrayDesignReportService; import ubic.gemma.core.apps.GemmaCLI.CommandGroup; +import ubic.gemma.core.util.AbstractAuthenticatedCLI; import ubic.gemma.core.util.AbstractCLI; -import ubic.gemma.core.util.AbstractCLIContextCLI; +import ubic.gemma.core.util.FileUtils; import ubic.gemma.model.common.auditAndSecurity.AuditEvent; import ubic.gemma.model.common.auditAndSecurity.eventType.ArrayDesignAnalysisEvent; import ubic.gemma.model.common.auditAndSecurity.eventType.AuditEventType; import ubic.gemma.model.expression.arrayDesign.ArrayDesign; import ubic.gemma.model.expression.arrayDesign.TechnologyType; +import ubic.gemma.persistence.service.common.auditAndSecurity.AuditTrailService; import ubic.gemma.persistence.service.expression.arrayDesign.ArrayDesignService; import java.io.IOException; @@ -42,10 +46,15 @@ * * @author pavlidis */ -public abstract class ArrayDesignSequenceManipulatingCli extends AbstractCLIContextCLI { +public abstract class ArrayDesignSequenceManipulatingCli extends AbstractAuthenticatedCLI { + @Autowired private ArrayDesignReportService arrayDesignReportService; + @Autowired private ArrayDesignService arrayDesignService; + @Autowired + protected AuditTrailService auditTrailService; + private Collection arrayDesignsToProcess = new HashSet<>(); @Override @@ -72,15 +81,10 @@ protected void buildOptions( Options options ) { this.addDateOption( options ); this.addAutoOption( options ); - } @Override - protected void processOptions( CommandLine commandLine ) { - - arrayDesignReportService = this.getBean( ArrayDesignReportService.class ); - arrayDesignService = this.getBean( ArrayDesignService.class ); - + protected void processOptions( CommandLine commandLine ) throws ParseException { if ( commandLine.hasOption( 'a' ) ) { this.arraysFromCliList( commandLine ); } else if ( commandLine.hasOption( 'f' ) ) { @@ -92,20 +96,6 @@ protected void processOptions( CommandLine commandLine ) { throw new RuntimeException( e ); } } - - if ( commandLine.hasOption( "mdate" ) ) { - super.mDate = commandLine.getOptionValue( "mdate" ); - if ( commandLine.hasOption( AbstractCLI.AUTO_OPTION_NAME ) ) { - throw new IllegalArgumentException( "Please only select one of 'mdate' OR 'auto'" ); - } - } - - if ( commandLine.hasOption( AbstractCLI.AUTO_OPTION_NAME ) ) { - this.autoSeek = true; - if ( commandLine.hasOption( "mdate" ) ) { - throw new IllegalArgumentException( "Please only select one of 'mdate' OR 'auto'" ); - } - } } protected ArrayDesignReportService getArrayDesignReportService() { @@ -191,7 +181,7 @@ Collection getRelatedDesigns( ArrayDesign design ) { boolean needToRun( Date skipIfLastRunLaterThan, ArrayDesign arrayDesign, Class eventClass ) { - if ( autoSeek ) { + if ( isAutoSeek() ) { return this.needToAutoRun( arrayDesign, eventClass ); } @@ -215,7 +205,7 @@ private void arraysFromCliList( CommandLine commandLine ) { for ( String shortName : shortNames ) { if ( StringUtils.isBlank( shortName ) ) continue; - ArrayDesign ad = this.locateArrayDesign( shortName, arrayDesignService ); + ArrayDesign ad = this.locateArrayDesign( shortName ); if ( ad == null ) { AbstractCLI.log.warn( shortName + " not found" ); continue; @@ -260,7 +250,7 @@ private List getEvents( ArrayDesign arrayDesign, * @return whether the array design needs updating based on the criteria outlined above. */ private boolean needToAutoRun( ArrayDesign arrayDesign, Class eventClass ) { - if ( !autoSeek ) { + if ( !isAutoSeek() ) { return false; } @@ -315,7 +305,7 @@ private boolean needToAutoRun( ArrayDesign arrayDesign, Class readListFile( String fileName ) throws IOException { Set ees = new HashSet<>(); - for ( String eeName : AbstractCLIContextCLI.readListFileToStrings( fileName ) ) { + for ( String eeName : FileUtils.readListFileToStrings( fileName ) ) { ArrayDesign ee = arrayDesignService.findByShortName( eeName ); if ( ee == null ) { @@ -336,4 +326,29 @@ private Set readListFile( String fileName ) throws IOException { } return ees; } + + /** + * @param name of the array design to find. + * @return an array design, if found. Bails otherwise with an error exit code + */ + protected ArrayDesign locateArrayDesign( String name ) { + + ArrayDesign arrayDesign = null; + + Collection byname = arrayDesignService.findByName( name.trim().toUpperCase() ); + if ( byname.size() > 1 ) { + throw new IllegalArgumentException( "Ambiguous name: " + name ); + } else if ( byname.size() == 1 ) { + arrayDesign = byname.iterator().next(); + } + + if ( arrayDesign == null ) { + arrayDesign = arrayDesignService.findByShortName( name ); + } + + if ( arrayDesign == null ) { + AbstractCLI.log.error( "No arrayDesign " + name + " found" ); + } + return arrayDesign; + } } diff --git a/gemma-cli/src/main/java/ubic/gemma/core/apps/ArrayDesignSubsumptionTesterCli.java b/gemma-cli/src/main/java/ubic/gemma/core/apps/ArrayDesignSubsumptionTesterCli.java index 9b703f72d8..3d2f6335f5 100644 --- a/gemma-cli/src/main/java/ubic/gemma/core/apps/ArrayDesignSubsumptionTesterCli.java +++ b/gemma-cli/src/main/java/ubic/gemma/core/apps/ArrayDesignSubsumptionTesterCli.java @@ -21,6 +21,7 @@ import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; import org.apache.commons.lang3.StringUtils; import ubic.gemma.model.common.auditAndSecurity.eventType.ArrayDesignSubsumeCheckEvent; import ubic.gemma.model.expression.arrayDesign.ArrayDesign; @@ -61,7 +62,7 @@ protected void doWork() throws Exception { allToCompare.add( arrayDesign ); for ( String otherArrayDesignName : otherArrayDesignNames ) { - ArrayDesign otherArrayDesign = this.locateArrayDesign( otherArrayDesignName, getArrayDesignService() ); + ArrayDesign otherArrayDesign = this.locateArrayDesign( otherArrayDesignName ); if ( otherArrayDesign == null ) { throw new Exception( "No arrayDesign " + otherArrayDesignName + " found" ); @@ -138,7 +139,7 @@ protected void buildOptions( Options options ) { } @Override - protected void processOptions( CommandLine commandLine ) { + protected void processOptions( CommandLine commandLine ) throws ParseException { super.processOptions( commandLine ); if ( commandLine.hasOption( 'o' ) ) { String otherArrayDesignName = commandLine.getOptionValue( 'o' ); diff --git a/gemma-cli/src/main/java/ubic/gemma/core/apps/BatchEffectPopulationCli.java b/gemma-cli/src/main/java/ubic/gemma/core/apps/BatchEffectPopulationCli.java index c45fe79738..0cadd0fd0d 100755 --- a/gemma-cli/src/main/java/ubic/gemma/core/apps/BatchEffectPopulationCli.java +++ b/gemma-cli/src/main/java/ubic/gemma/core/apps/BatchEffectPopulationCli.java @@ -16,8 +16,6 @@ import org.apache.commons.cli.Options; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; -import ubic.gemma.core.analysis.preprocess.batcheffects.BatchInfoPopulationException; import ubic.gemma.core.analysis.preprocess.batcheffects.BatchInfoPopulationService; import ubic.gemma.core.apps.GemmaCLI.CommandGroup; import ubic.gemma.core.util.AbstractCLI; @@ -30,7 +28,6 @@ * * @author paul */ -@Component public class BatchEffectPopulationCli extends ExpressionExperimentManipulatingCLI { @Autowired diff --git a/gemma-cli/src/main/java/ubic/gemma/core/apps/BibRefUpdaterCli.java b/gemma-cli/src/main/java/ubic/gemma/core/apps/BibRefUpdaterCli.java index 45310de6fb..a88f181f66 100644 --- a/gemma-cli/src/main/java/ubic/gemma/core/apps/BibRefUpdaterCli.java +++ b/gemma-cli/src/main/java/ubic/gemma/core/apps/BibRefUpdaterCli.java @@ -25,7 +25,7 @@ import org.apache.commons.lang3.RandomUtils; import ubic.gemma.core.annotation.reference.BibliographicReferenceService; import ubic.gemma.core.apps.GemmaCLI.CommandGroup; -import ubic.gemma.core.util.AbstractCLIContextCLI; +import ubic.gemma.core.util.AbstractAuthenticatedCLI; import ubic.gemma.model.common.description.BibliographicReference; import java.util.ArrayList; @@ -36,7 +36,7 @@ * * @author Paul */ -public class BibRefUpdaterCli extends AbstractCLIContextCLI { +public class BibRefUpdaterCli extends AbstractAuthenticatedCLI { private String[] pmids; @@ -66,7 +66,7 @@ protected boolean requireLogin() { } @Override - protected void processOptions( CommandLine commandLine ) throws Exception { + protected void processOptions( CommandLine commandLine ) { } @Override diff --git a/gemma-cli/src/main/java/ubic/gemma/core/apps/BioSequenceCleanupCli.java b/gemma-cli/src/main/java/ubic/gemma/core/apps/BioSequenceCleanupCli.java index 228aef5cd2..77f58d23a6 100755 --- a/gemma-cli/src/main/java/ubic/gemma/core/apps/BioSequenceCleanupCli.java +++ b/gemma-cli/src/main/java/ubic/gemma/core/apps/BioSequenceCleanupCli.java @@ -21,6 +21,7 @@ import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; import org.apache.commons.lang3.StringUtils; import ubic.gemma.model.expression.arrayDesign.ArrayDesign; import ubic.gemma.model.expression.designElement.CompositeSequence; @@ -189,7 +190,7 @@ protected void doWork() { } @Override - protected void processOptions( CommandLine commandLine ) { + protected void processOptions( CommandLine commandLine ) throws ParseException { super.processOptions( commandLine ); if ( commandLine.hasOption( "dryrun" ) ) { this.justTesting = true; diff --git a/gemma-cli/src/main/java/ubic/gemma/core/apps/BlacklistCli.java b/gemma-cli/src/main/java/ubic/gemma/core/apps/BlacklistCli.java index 13d7fd79f5..60ed2013b7 100644 --- a/gemma-cli/src/main/java/ubic/gemma/core/apps/BlacklistCli.java +++ b/gemma-cli/src/main/java/ubic/gemma/core/apps/BlacklistCli.java @@ -26,8 +26,8 @@ import ubic.gemma.core.apps.GemmaCLI.CommandGroup; import ubic.gemma.core.loader.expression.geo.model.GeoRecord; import ubic.gemma.core.loader.expression.geo.service.GeoBrowser; +import ubic.gemma.core.util.AbstractAuthenticatedCLI; import ubic.gemma.core.util.AbstractCLI; -import ubic.gemma.core.util.AbstractCLIContextCLI; import ubic.gemma.model.common.description.DatabaseEntry; import ubic.gemma.model.common.description.ExternalDatabase; import ubic.gemma.model.expression.BlacklistedEntity; @@ -51,7 +51,7 @@ * * @author paul */ -public class BlacklistCli extends AbstractCLIContextCLI { +public class BlacklistCli extends AbstractAuthenticatedCLI { private static final int MAX_RETRIES = 3; String fileName = null; diff --git a/gemma-cli/src/main/java/ubic/gemma/core/apps/CountObsoleteTermsCli.java b/gemma-cli/src/main/java/ubic/gemma/core/apps/CountObsoleteTermsCli.java deleted file mode 100644 index 6e23bcdf91..0000000000 --- a/gemma-cli/src/main/java/ubic/gemma/core/apps/CountObsoleteTermsCli.java +++ /dev/null @@ -1,94 +0,0 @@ -package ubic.gemma.core.apps; - -import org.apache.commons.cli.CommandLine; -import org.apache.commons.cli.Option; -import org.apache.commons.cli.Options; -import ubic.gemma.core.ontology.OntologyService; -import ubic.gemma.core.util.AbstractCLI; -import ubic.gemma.core.util.AbstractCLIContextCLI; -import ubic.gemma.model.genome.gene.phenotype.valueObject.CharacteristicValueObject; - -import java.util.Map; - -public class CountObsoleteTermsCli extends AbstractCLIContextCLI { - private OntologyService ontologyService; - - private String startArg; - private String stepArg = "100000"; - private String stopArg; - - @Override - public GemmaCLI.CommandGroup getCommandGroup() { - return GemmaCLI.CommandGroup.EXPERIMENT; - } - - @Override - public String getShortDesc() { - return "Check for characteristics using obsolete terms in the given ID range."; - } - - @Override - protected void processOptions( CommandLine commandLine ) { - ontologyService = this.getBean( OntologyService.class ); - - if ( commandLine.hasOption( "start" ) ) { - this.startArg = commandLine.getOptionValue( "start" ); - } - - if ( commandLine.hasOption( "step" ) ) { - this.stepArg = commandLine.getOptionValue( "step" ); - } - - if ( commandLine.hasOption( "stop" ) ) { - this.stopArg = commandLine.getOptionValue( "stop" ); - } - } - - @Override - public String getCommandName() { - return "countObsoleteTerms"; - } - - @SuppressWarnings("AccessStaticViaInstance") - @Override - protected void buildOptions( Options options ) { - - Option startOption = Option.builder( "start" ) - .desc( "The ID of the first characteristic to check, i.e the end of the ID range." ).hasArg().required() - .build(); - options.addOption( startOption ); - - Option stepOption = Option.builder( "step" ) - .desc( "The amount of characteristics to load in one batch. Default value is 100000." ).hasArg() - .build(); - options.addOption( stepOption ); - - Option stopOption = Option.builder( "stop" ) - .desc( "The ID of the last characteristic to check, i.e the end of the ID range." ).hasArg().required() - .build(); - options.addOption( stopOption ); - } - - @Override - protected void doWork() throws Exception { - int start = Integer.parseInt( startArg ) + 1; - int step = Integer.parseInt( stepArg ) + 1; - int stop = Integer.parseInt( stopArg ) + 1; - - Map vos = ontologyService.countObsoleteOccurrences( start, stop, step ); - - // Output results - - AbstractCLI.log.info( "======================================================" ); - AbstractCLI.log.info( "Obsolete term check finished." ); - AbstractCLI.log.info( "Below is a tab-separated output of all found terms:" ); - AbstractCLI.log.info( "======================================================" ); - AbstractCLI.log.info( "======================================================" ); - AbstractCLI.log.info( "Value\tValueUri\tCount" ); - for ( CharacteristicValueObject vo : vos.values() ) { - AbstractCLI.log.info( vo.getValue() + "\t" + vo.getValueUri() + "\t" + vo.getNumTimesUsed() ); - } - AbstractCLI.log.info( "======================================================" ); - AbstractCLI.log.info( "======================================================" ); - } -} diff --git a/gemma-cli/src/main/java/ubic/gemma/core/apps/DatabaseViewGeneratorCLI.java b/gemma-cli/src/main/java/ubic/gemma/core/apps/DatabaseViewGeneratorCLI.java index 7787f0cbfa..aaadc49289 100644 --- a/gemma-cli/src/main/java/ubic/gemma/core/apps/DatabaseViewGeneratorCLI.java +++ b/gemma-cli/src/main/java/ubic/gemma/core/apps/DatabaseViewGeneratorCLI.java @@ -23,8 +23,8 @@ import org.apache.commons.cli.Options; import ubic.gemma.core.analysis.report.DatabaseViewGenerator; import ubic.gemma.core.apps.GemmaCLI.CommandGroup; +import ubic.gemma.core.util.AbstractAuthenticatedCLI; import ubic.gemma.core.util.AbstractCLI; -import ubic.gemma.core.util.AbstractCLIContextCLI; /** * Simple driver of DatabaseViewGenerator. Developed to support NIF and other external data consumers. @@ -32,7 +32,7 @@ * @author paul */ @SuppressWarnings({ "FieldCanBeLocal", "unused" }) // Possible external use -public class DatabaseViewGeneratorCLI extends AbstractCLIContextCLI { +public class DatabaseViewGeneratorCLI extends AbstractAuthenticatedCLI { private boolean generateDatasetSummary = false; private boolean generateDiffExpressionSummary = false; diff --git a/gemma-cli/src/main/java/ubic/gemma/core/apps/DeleteExperimentsCli.java b/gemma-cli/src/main/java/ubic/gemma/core/apps/DeleteExperimentsCli.java index 07ca29d56a..7cdaf37467 100644 --- a/gemma-cli/src/main/java/ubic/gemma/core/apps/DeleteExperimentsCli.java +++ b/gemma-cli/src/main/java/ubic/gemma/core/apps/DeleteExperimentsCli.java @@ -17,6 +17,7 @@ import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; import org.apache.commons.lang3.StringUtils; import ubic.gemma.model.expression.arrayDesign.ArrayDesign; import ubic.gemma.model.expression.experiment.BioAssaySet; @@ -50,7 +51,7 @@ protected void buildOptions( Options options ) { } @Override - protected void processOptions( CommandLine commandLine ) { + protected void processOptions( CommandLine commandLine ) throws ParseException { this.force = true; // we delete troubled / unusuable items, has to be set prior to processOptions. super.processOptions( commandLine ); if ( commandLine.hasOption( 'a' ) ) { diff --git a/gemma-cli/src/main/java/ubic/gemma/core/apps/DifferentialExpressionAnalysisCli.java b/gemma-cli/src/main/java/ubic/gemma/core/apps/DifferentialExpressionAnalysisCli.java index a43d90409b..edeeaabf31 100644 --- a/gemma-cli/src/main/java/ubic/gemma/core/apps/DifferentialExpressionAnalysisCli.java +++ b/gemma-cli/src/main/java/ubic/gemma/core/apps/DifferentialExpressionAnalysisCli.java @@ -22,6 +22,7 @@ import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.exception.ExceptionUtils; import ubic.gemma.core.analysis.expression.diff.DifferentialExpressionAnalysisConfig; @@ -128,8 +129,7 @@ protected void buildOptions( Options options ) { /* Supports: running on all data sets that have not been run since a given date. */ super.addDateOption( options ); - super.addAutoOption( options ); - this.autoSeekEventType = DifferentialExpressionAnalysisEvent.class; + super.addAutoOption( options, DifferentialExpressionAnalysisEvent.class ); super.addForceOption( options ); Option factors = Option.builder( "factors" ).hasArg().desc( @@ -172,7 +172,7 @@ protected void buildOptions( Options options ) { } @Override - protected void processOptions( CommandLine commandLine ) { + protected void processOptions( CommandLine commandLine ) throws ParseException { super.processOptions( commandLine ); differentialExpressionAnalyzerService = this.getBean( DifferentialExpressionAnalyzerService.class ); differentialExpressionAnalysisService = this.getBean( DifferentialExpressionAnalysisService.class ); diff --git a/gemma-cli/src/main/java/ubic/gemma/core/apps/ExperimentalDesignImportCli.java b/gemma-cli/src/main/java/ubic/gemma/core/apps/ExperimentalDesignImportCli.java index 1144eb60c0..3ee80f2310 100644 --- a/gemma-cli/src/main/java/ubic/gemma/core/apps/ExperimentalDesignImportCli.java +++ b/gemma-cli/src/main/java/ubic/gemma/core/apps/ExperimentalDesignImportCli.java @@ -25,7 +25,7 @@ import ubic.gemma.core.apps.GemmaCLI.CommandGroup; import ubic.gemma.core.loader.expression.simple.ExperimentalDesignImporter; import ubic.gemma.core.ontology.OntologyUtils; -import ubic.gemma.core.util.AbstractCLIContextCLI; +import ubic.gemma.core.util.AbstractAuthenticatedCLI; import ubic.gemma.model.expression.experiment.ExpressionExperiment; import ubic.gemma.persistence.service.expression.experiment.ExpressionExperimentService; @@ -35,7 +35,7 @@ * @author Paul * @see ExperimentalDesignImporter */ -public class ExperimentalDesignImportCli extends AbstractCLIContextCLI { +public class ExperimentalDesignImportCli extends AbstractAuthenticatedCLI { private ExpressionExperiment expressionExperiment; private InputStream inputStream; diff --git a/gemma-cli/src/main/java/ubic/gemma/core/apps/ExperimentalDesignViewCli.java b/gemma-cli/src/main/java/ubic/gemma/core/apps/ExperimentalDesignViewCli.java index b8fabd85dc..ae647468dc 100644 --- a/gemma-cli/src/main/java/ubic/gemma/core/apps/ExperimentalDesignViewCli.java +++ b/gemma-cli/src/main/java/ubic/gemma/core/apps/ExperimentalDesignViewCli.java @@ -2,7 +2,7 @@ import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.Options; -import ubic.gemma.core.util.AbstractCLIContextCLI; +import ubic.gemma.core.util.AbstractAuthenticatedCLI; import ubic.gemma.model.common.description.Characteristic; import ubic.gemma.model.expression.experiment.*; import ubic.gemma.persistence.service.expression.experiment.ExperimentalDesignService; @@ -14,7 +14,7 @@ /** * @author paul */ -public class ExperimentalDesignViewCli extends AbstractCLIContextCLI { +public class ExperimentalDesignViewCli extends AbstractAuthenticatedCLI { @Override public GemmaCLI.CommandGroup getCommandGroup() { @@ -36,7 +36,7 @@ protected void buildOptions( Options options ) { } @Override - protected void processOptions( CommandLine commandLine ) throws Exception { + protected void processOptions( CommandLine commandLine ) { } diff --git a/gemma-cli/src/main/java/ubic/gemma/core/apps/ExperimentalDesignWriterCLI.java b/gemma-cli/src/main/java/ubic/gemma/core/apps/ExperimentalDesignWriterCLI.java index 4fbb57f8be..f2d99c76f0 100755 --- a/gemma-cli/src/main/java/ubic/gemma/core/apps/ExperimentalDesignWriterCLI.java +++ b/gemma-cli/src/main/java/ubic/gemma/core/apps/ExperimentalDesignWriterCLI.java @@ -21,6 +21,7 @@ import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; import ubic.basecode.util.FileTools; import ubic.gemma.core.datastructure.matrix.ExperimentalDesignWriter; import ubic.gemma.model.expression.experiment.BioAssaySet; @@ -81,7 +82,7 @@ protected void buildOptions( Options options ) { } @Override - protected void processOptions( CommandLine commandLine ) { + protected void processOptions( CommandLine commandLine ) throws ParseException { super.processOptions( commandLine ); outFileName = commandLine.getOptionValue( 'o' ); } diff --git a/gemma-cli/src/main/java/ubic/gemma/core/apps/ExpressionDataMatrixWriterCLI.java b/gemma-cli/src/main/java/ubic/gemma/core/apps/ExpressionDataMatrixWriterCLI.java index 69af8d26d3..52fd8eb606 100644 --- a/gemma-cli/src/main/java/ubic/gemma/core/apps/ExpressionDataMatrixWriterCLI.java +++ b/gemma-cli/src/main/java/ubic/gemma/core/apps/ExpressionDataMatrixWriterCLI.java @@ -21,6 +21,7 @@ import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; import org.apache.commons.lang3.StringUtils; import ubic.basecode.util.FileTools; import ubic.gemma.core.analysis.service.ExpressionDataFileService; @@ -53,7 +54,7 @@ protected void buildOptions( Options options ) { } @Override - protected void processOptions( CommandLine commandLine ) { + protected void processOptions( CommandLine commandLine ) throws ParseException { super.processOptions( commandLine ); outFileName = commandLine.getOptionValue( 'o' ); if ( commandLine.hasOption( "filter" ) ) { diff --git a/gemma-cli/src/main/java/ubic/gemma/core/apps/ExpressionExperimentDataFileGeneratorCli.java b/gemma-cli/src/main/java/ubic/gemma/core/apps/ExpressionExperimentDataFileGeneratorCli.java index 0c7d361546..7a21bbd8e5 100644 --- a/gemma-cli/src/main/java/ubic/gemma/core/apps/ExpressionExperimentDataFileGeneratorCli.java +++ b/gemma-cli/src/main/java/ubic/gemma/core/apps/ExpressionExperimentDataFileGeneratorCli.java @@ -22,11 +22,10 @@ import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextHolder; import ubic.gemma.core.analysis.service.ExpressionDataFileService; import ubic.gemma.core.util.AbstractCLI; -import ubic.gemma.model.common.auditAndSecurity.eventType.AuditEventType; import ubic.gemma.model.common.auditAndSecurity.eventType.CommentedEvent; import ubic.gemma.model.expression.experiment.BioAssaySet; import ubic.gemma.model.expression.experiment.ExpressionExperiment; @@ -96,13 +95,9 @@ protected void buildOptions( Options options ) { } @Override - protected void processOptions( CommandLine commandLine ) { + protected void processOptions( CommandLine commandLine ) throws ParseException { super.processOptions( commandLine ); - if ( commandLine.hasOption( AbstractCLI.THREADS_OPTION ) ) { - this.numThreads = this.getIntegerOptionValue( commandLine, "threads" ); - } - if ( commandLine.hasOption( 'w' ) ) { this.force_write = true; } diff --git a/gemma-cli/src/main/java/ubic/gemma/core/apps/ExpressionExperimentManipulatingCLI.java b/gemma-cli/src/main/java/ubic/gemma/core/apps/ExpressionExperimentManipulatingCLI.java index 1b4d1219c9..fe4428cfdf 100644 --- a/gemma-cli/src/main/java/ubic/gemma/core/apps/ExpressionExperimentManipulatingCLI.java +++ b/gemma-cli/src/main/java/ubic/gemma/core/apps/ExpressionExperimentManipulatingCLI.java @@ -21,6 +21,7 @@ import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import ubic.gemma.core.apps.GemmaCLI.CommandGroup; @@ -28,15 +29,22 @@ import ubic.gemma.core.search.SearchException; import ubic.gemma.core.search.SearchResult; import ubic.gemma.core.search.SearchService; +import ubic.gemma.core.util.AbstractAuthenticatedCLI; import ubic.gemma.core.util.AbstractCLI; -import ubic.gemma.core.util.AbstractCLIContextCLI; +import ubic.gemma.core.util.FileUtils; import ubic.gemma.model.analysis.expression.ExpressionExperimentSet; +import ubic.gemma.model.common.Auditable; +import ubic.gemma.model.common.auditAndSecurity.AuditEvent; +import ubic.gemma.model.common.auditAndSecurity.curation.Curatable; +import ubic.gemma.model.common.auditAndSecurity.eventType.AuditEventType; import ubic.gemma.model.common.search.SearchSettings; import ubic.gemma.model.expression.arrayDesign.ArrayDesign; import ubic.gemma.model.expression.experiment.BioAssaySet; import ubic.gemma.model.expression.experiment.ExpressionExperiment; import ubic.gemma.model.genome.Gene; import ubic.gemma.model.genome.Taxon; +import ubic.gemma.persistence.service.common.auditAndSecurity.AuditEventService; +import ubic.gemma.persistence.service.common.auditAndSecurity.AuditTrailService; import ubic.gemma.persistence.service.expression.arrayDesign.ArrayDesignService; import ubic.gemma.persistence.service.expression.experiment.ExpressionExperimentService; import ubic.gemma.persistence.service.expression.experiment.ExpressionExperimentSetService; @@ -67,7 +75,7 @@ * * @author Paul */ -public abstract class ExpressionExperimentManipulatingCLI extends AbstractCLIContextCLI { +public abstract class ExpressionExperimentManipulatingCLI extends AbstractAuthenticatedCLI { @Autowired protected ExpressionExperimentService eeService; @@ -81,6 +89,10 @@ public abstract class ExpressionExperimentManipulatingCLI extends AbstractCLICon private SearchService searchService; @Autowired private ArrayDesignService arrayDesignService; + @Autowired + protected AuditTrailService auditTrailService; + @Autowired + protected AuditEventService auditEventService; protected final Set expressionExperiments = new HashSet<>(); /** @@ -89,9 +101,6 @@ public abstract class ExpressionExperimentManipulatingCLI extends AbstractCLICon private Taxon taxon = null; protected boolean force = false; - protected ExpressionExperimentManipulatingCLI() { - } - @Override public CommandGroup getCommandGroup() { return CommandGroup.EXPERIMENT; @@ -146,9 +155,9 @@ protected Gene findGeneByOfficialSymbol( String symbol, Taxon t ) { } @Override - protected void processOptions( CommandLine commandLine ) { + protected void processOptions( CommandLine commandLine ) throws ParseException { if ( commandLine.hasOption( 't' ) ) { - this.taxon = this.setTaxonByName( commandLine, taxonService ); + this.taxon = this.getTaxonByName( commandLine ); } if ( commandLine.hasOption( "force" ) ) { @@ -187,14 +196,13 @@ protected void processOptions( CommandLine commandLine ) { if ( !force && !expressionExperiments.isEmpty() ) { - if ( commandLine.hasOption( AbstractCLI.AUTO_OPTION_NAME ) ) { - this.autoSeek = true; - if ( this.autoSeekEventType == null ) { + if ( isAutoSeek() ) { + if ( this.getAutoSeekEventType() == null ) { throw new IllegalStateException( "Programming error: there is no 'autoSeekEventType' set" ); } - AbstractCLI.log.info( "Filtering for experiments lacking a " + this.autoSeekEventType.getSimpleName() + AbstractCLI.log.info( "Filtering for experiments lacking a " + this.getAutoSeekEventType().getSimpleName() + " event" ); - auditEventService.retainLackingEvent( this.expressionExperiments, this.autoSeekEventType ); + auditEventService.retainLackingEvent( this.expressionExperiments, this.getAutoSeekEventType() ); } Set troubledExpressionExperiments = this.getTroubledExpressionExperiments(); @@ -337,7 +345,7 @@ private ExpressionExperiment locateExpressionExperiment( String name ) { */ private Set readExpressionExperimentListFile( String fileName ) throws IOException { Set ees = new HashSet<>(); - List idlist = AbstractCLIContextCLI.readListFileToStrings( fileName ); + List idlist = FileUtils.readListFileToStrings( fileName ); log.info( "Found " + idlist.size() + " experiment identifiers in file " + fileName ); int count = 0; for ( String eeName : idlist ) { @@ -385,4 +393,72 @@ private Set getTroubledExpressionExperiments() { .filter( ee -> ee.getCurationDetails().getTroubled() ) .collect( Collectors.toSet() ); } + + /** + * @param auditable auditable + * @param eventClass can be null + * @return boolean + */ + protected boolean noNeedToRun( Auditable auditable, Class eventClass ) { + boolean needToRun = true; + Date skipIfLastRunLaterThan = this.getLimitingDate(); + List events = this.auditEventService.getEvents( auditable ); + + boolean okToRun = true; // assume okay unless indicated otherwise + + // figure out if we need to run it by date; or if there is no event of the given class; "Fail" type events don't + // count. + for ( int j = events.size() - 1; j >= 0; j-- ) { + AuditEvent event = events.get( j ); + if ( event == null ) { + continue; // legacy of ordered-list which could end up with gaps; should not be needed any more + } + AuditEventType eventType = event.getEventType(); + if ( eventType != null && eventClass != null && eventClass.isAssignableFrom( eventType.getClass() ) + && !eventType.getClass().getSimpleName().startsWith( "Fail" ) ) { + if ( skipIfLastRunLaterThan != null ) { + if ( event.getDate().after( skipIfLastRunLaterThan ) ) { + AbstractCLI.log.info( auditable + ": " + " run more recently than " + skipIfLastRunLaterThan ); + addErrorObject( auditable, "Run more recently than " + skipIfLastRunLaterThan ); + needToRun = false; + } + } else { + needToRun = false; // it has been run already at some point + } + } + } + + /* + * Always skip if the object is curatable and troubled + */ + if ( auditable instanceof Curatable ) { + Curatable curatable = ( Curatable ) auditable; + okToRun = !curatable.getCurationDetails().getTroubled(); //not ok if troubled + + // special case for expression experiments - check associated ADs. + if ( okToRun && curatable instanceof ExpressionExperiment ) { + for ( ArrayDesign ad : eeService.getArrayDesignsUsed( ( ExpressionExperiment ) auditable ) ) { + if ( ad.getCurationDetails().getTroubled() ) { + okToRun = false; // not ok if even one parent AD is troubled, no need to check the remaining ones. + break; + } + } + } + + if ( !okToRun ) { + addErrorObject( auditable, "Has an active 'trouble' flag" ); + } + } + + return !needToRun || !okToRun; + } + + protected Taxon getTaxonByName( CommandLine commandLine ) { + String taxonName = commandLine.getOptionValue( 't' ); + ubic.gemma.model.genome.Taxon taxon = taxonService.findByCommonName( taxonName ); + if ( taxon == null ) { + AbstractCLI.log.error( "ERROR: Cannot find taxon " + taxonName ); + } + return taxon; + } } diff --git a/gemma-cli/src/main/java/ubic/gemma/core/apps/ExpressionExperimentPlatformSwitchCli.java b/gemma-cli/src/main/java/ubic/gemma/core/apps/ExpressionExperimentPlatformSwitchCli.java index cf494e9853..21a6e7a572 100644 --- a/gemma-cli/src/main/java/ubic/gemma/core/apps/ExpressionExperimentPlatformSwitchCli.java +++ b/gemma-cli/src/main/java/ubic/gemma/core/apps/ExpressionExperimentPlatformSwitchCli.java @@ -21,8 +21,9 @@ import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; import ubic.gemma.core.loader.expression.ExpressionExperimentPlatformSwitchService; -import ubic.gemma.model.common.auditAndSecurity.eventType.AuditEventType; +import ubic.gemma.core.util.AbstractCLI; import ubic.gemma.model.common.auditAndSecurity.eventType.ExpressionExperimentPlatformSwitchEvent; import ubic.gemma.model.expression.arrayDesign.ArrayDesign; import ubic.gemma.model.expression.experiment.BioAssaySet; @@ -30,6 +31,8 @@ import ubic.gemma.persistence.service.common.auditAndSecurity.AuditTrailService; import ubic.gemma.persistence.service.expression.arrayDesign.ArrayDesignService; +import java.util.Collection; + /** * Switch the array design used to the merged one. * @@ -78,7 +81,7 @@ protected void buildOptions( Options options ) { } @Override - protected void processOptions( CommandLine commandLine ) { + protected void processOptions( CommandLine commandLine ) throws ParseException { super.processOptions( commandLine ); if ( commandLine.hasOption( 'a' ) ) { this.arrayDesignName = commandLine.getOptionValue( 'a' ); @@ -93,7 +96,7 @@ private void processExperiment( ExpressionExperiment ee ) { AuditTrailService ats = this.getBean( AuditTrailService.class ); if ( this.arrayDesignName != null ) { - ArrayDesign ad = this.locateArrayDesign( this.arrayDesignName, arrayDesignService ); + ArrayDesign ad = this.locateArrayDesign( this.arrayDesignName ); if ( ad == null ) { throw new RuntimeException( "Unknown array design" ); } @@ -113,4 +116,30 @@ private void processExperiment( ExpressionExperiment ee ) { addErrorObject( ee, e ); } } + + /** + * @param name of the array design to find. + * @param arrayDesignService the arrayDesignService to use for the AD retrieval + * @return an array design, if found. Bails otherwise with an error exit code + */ + private ArrayDesign locateArrayDesign( String name ) { + + ArrayDesign arrayDesign = null; + + Collection byname = arrayDesignService.findByName( name.trim().toUpperCase() ); + if ( byname.size() > 1 ) { + throw new IllegalArgumentException( "Ambiguous name: " + name ); + } else if ( byname.size() == 1 ) { + arrayDesign = byname.iterator().next(); + } + + if ( arrayDesign == null ) { + arrayDesign = arrayDesignService.findByShortName( name ); + } + + if ( arrayDesign == null ) { + AbstractCLI.log.error( "No arrayDesign " + name + " found" ); + } + return arrayDesign; + } } diff --git a/gemma-cli/src/main/java/ubic/gemma/core/apps/ExpressionExperimentPrimaryPubCli.java b/gemma-cli/src/main/java/ubic/gemma/core/apps/ExpressionExperimentPrimaryPubCli.java index ef3436de09..657b568c0b 100644 --- a/gemma-cli/src/main/java/ubic/gemma/core/apps/ExpressionExperimentPrimaryPubCli.java +++ b/gemma-cli/src/main/java/ubic/gemma/core/apps/ExpressionExperimentPrimaryPubCli.java @@ -23,12 +23,14 @@ import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; +import org.springframework.beans.factory.annotation.Autowired; import ubic.gemma.core.loader.entrez.pubmed.ExpressionExperimentBibRefFinder; import ubic.gemma.core.loader.entrez.pubmed.PubMedXMLFetcher; import ubic.gemma.model.common.description.BibliographicReference; import ubic.gemma.model.expression.experiment.BioAssaySet; import ubic.gemma.model.expression.experiment.ExpressionExperiment; -import ubic.gemma.persistence.persister.Persister; +import ubic.gemma.persistence.persister.PersisterHelper; import ubic.gemma.persistence.service.expression.experiment.ExpressionExperimentService; import java.io.BufferedReader; @@ -46,6 +48,9 @@ */ public class ExpressionExperimentPrimaryPubCli extends ExpressionExperimentManipulatingCLI { + @Autowired + private PersisterHelper persisterHelper; + private String pubmedIdFilename; private Map pubmedIds = new HashMap<>(); @@ -78,7 +83,6 @@ protected void buildOptions( Options options ) { protected void doWork() throws Exception { ExpressionExperimentService ees = this.getBean( ExpressionExperimentService.class ); - Persister ph = this.getPersisterHelper(); PubMedXMLFetcher fetcher = new PubMedXMLFetcher(); // collect some statistics @@ -131,7 +135,7 @@ protected void doWork() throws Exception { try { log.info( "Found pubAccession " + ref.getPubAccession().getAccession() + " for " + experiment ); - ref = ( BibliographicReference ) ph.persist( ref ); + ref = ( BibliographicReference ) persisterHelper.persist( ref ); experiment.setPrimaryPublication( ref ); ees.update( experiment ); } catch ( Exception e ) { @@ -155,7 +159,7 @@ protected void doWork() throws Exception { } @Override - protected void processOptions( CommandLine commandLine ) { + protected void processOptions( CommandLine commandLine ) throws ParseException { super.processOptions( commandLine ); if ( commandLine.hasOption( "pmidFile" ) ) { this.pubmedIdFilename = commandLine.getOptionValue( "pmidFile" ); diff --git a/gemma-cli/src/main/java/ubic/gemma/core/apps/ExternalDatabaseAdderCli.java b/gemma-cli/src/main/java/ubic/gemma/core/apps/ExternalDatabaseAdderCli.java index a6de5af4d3..fc5a45a0e9 100644 --- a/gemma-cli/src/main/java/ubic/gemma/core/apps/ExternalDatabaseAdderCli.java +++ b/gemma-cli/src/main/java/ubic/gemma/core/apps/ExternalDatabaseAdderCli.java @@ -23,8 +23,8 @@ import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import org.springframework.beans.factory.annotation.Autowired; -import ubic.gemma.core.util.AbstractCLIContextCLI; import ubic.gemma.model.common.description.DatabaseType; +import ubic.gemma.core.util.AbstractAuthenticatedCLI; import ubic.gemma.model.common.description.ExternalDatabase; import ubic.gemma.persistence.service.common.description.ExternalDatabaseService; @@ -33,7 +33,7 @@ * * @author paul */ -public class ExternalDatabaseAdderCli extends AbstractCLIContextCLI { +public class ExternalDatabaseAdderCli extends AbstractAuthenticatedCLI { @Autowired private ExternalDatabaseService externalDatabaseService; @@ -63,7 +63,7 @@ protected void buildOptions( Options options ) { } @Override - protected void processOptions( CommandLine commandLine ) throws Exception { + protected void processOptions( CommandLine commandLine ) { this.name = commandLine.getOptionValue( "n" ); this.type = DatabaseType.valueOf( commandLine.getOptionValue( "t" ).toUpperCase() ); } diff --git a/gemma-cli/src/main/java/ubic/gemma/core/apps/ExternalDatabaseOverviewCli.java b/gemma-cli/src/main/java/ubic/gemma/core/apps/ExternalDatabaseOverviewCli.java index 4082e709dd..a7fafd9a1d 100644 --- a/gemma-cli/src/main/java/ubic/gemma/core/apps/ExternalDatabaseOverviewCli.java +++ b/gemma-cli/src/main/java/ubic/gemma/core/apps/ExternalDatabaseOverviewCli.java @@ -3,7 +3,7 @@ import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.Options; import org.springframework.beans.factory.annotation.Autowired; -import ubic.gemma.core.util.AbstractSpringAwareCLI; +import ubic.gemma.core.util.AbstractAuthenticatedCLI; import ubic.gemma.model.common.auditAndSecurity.AuditEvent; import ubic.gemma.model.common.auditAndSecurity.AuditTrail; import ubic.gemma.model.common.description.ExternalDatabase; @@ -13,7 +13,7 @@ import java.util.Comparator; import java.util.stream.Collectors; -public class ExternalDatabaseOverviewCli extends AbstractSpringAwareCLI { +public class ExternalDatabaseOverviewCli extends AbstractAuthenticatedCLI { @Autowired private ExternalDatabaseService externalDatabaseService; @@ -41,7 +41,7 @@ protected void buildOptions( Options options ) { } @Override - protected void processOptions( CommandLine commandLine ) throws Exception { + protected void processOptions( CommandLine commandLine ) { } diff --git a/gemma-cli/src/main/java/ubic/gemma/core/apps/ExternalDatabaseUpdaterCli.java b/gemma-cli/src/main/java/ubic/gemma/core/apps/ExternalDatabaseUpdaterCli.java index 10f0b7f882..baee165a47 100644 --- a/gemma-cli/src/main/java/ubic/gemma/core/apps/ExternalDatabaseUpdaterCli.java +++ b/gemma-cli/src/main/java/ubic/gemma/core/apps/ExternalDatabaseUpdaterCli.java @@ -3,10 +3,10 @@ import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; import ubic.gemma.core.util.AbstractCLI; -import ubic.gemma.core.util.AbstractSpringAwareCLI; +import ubic.gemma.core.util.AbstractAuthenticatedCLI; import ubic.gemma.model.common.description.ExternalDatabase; import ubic.gemma.persistence.service.common.description.ExternalDatabaseService; @@ -15,8 +15,7 @@ import static java.util.Objects.requireNonNull; -@Component -public class ExternalDatabaseUpdaterCli extends AbstractSpringAwareCLI { +public class ExternalDatabaseUpdaterCli extends AbstractAuthenticatedCLI { private static final String NAME_OPTION = "n", DESCRIPTION_OPTION = "d", @@ -87,7 +86,7 @@ protected void buildOptions( Options options ) { } @Override - protected void processOptions( CommandLine commandLine ) throws Exception { + protected void processOptions( CommandLine commandLine ) throws ParseException { name = commandLine.getOptionValue( NAME_OPTION ); description = commandLine.getOptionValue( DESCRIPTION_OPTION ); release = commandLine.hasOption( RELEASE_OPTION ); diff --git a/gemma-cli/src/main/java/ubic/gemma/core/apps/ExternalFileGeneLoaderCLI.java b/gemma-cli/src/main/java/ubic/gemma/core/apps/ExternalFileGeneLoaderCLI.java index 6f626a219e..bafffb9da1 100755 --- a/gemma-cli/src/main/java/ubic/gemma/core/apps/ExternalFileGeneLoaderCLI.java +++ b/gemma-cli/src/main/java/ubic/gemma/core/apps/ExternalFileGeneLoaderCLI.java @@ -23,7 +23,7 @@ import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import ubic.gemma.core.loader.genome.gene.ExternalFileGeneLoaderService; -import ubic.gemma.core.util.AbstractCLIContextCLI; +import ubic.gemma.core.util.AbstractAuthenticatedCLI; import java.io.IOException; @@ -33,7 +33,7 @@ * * @author ldonnison */ -public class ExternalFileGeneLoaderCLI extends AbstractCLIContextCLI { +public class ExternalFileGeneLoaderCLI extends AbstractAuthenticatedCLI { private String directGeneInputFileName = null; private String taxonName; diff --git a/gemma-cli/src/main/java/ubic/gemma/core/apps/FactorValueMigratorCLI.java b/gemma-cli/src/main/java/ubic/gemma/core/apps/FactorValueMigratorCLI.java new file mode 100644 index 0000000000..ec1069b4b6 --- /dev/null +++ b/gemma-cli/src/main/java/ubic/gemma/core/apps/FactorValueMigratorCLI.java @@ -0,0 +1,276 @@ +package ubic.gemma.core.apps; + +import lombok.Value; +import org.apache.commons.cli.*; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVParser; +import org.apache.commons.csv.CSVRecord; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.springframework.beans.factory.annotation.Autowired; +import ubic.gemma.core.util.AbstractAuthenticatedCLI; +import ubic.gemma.persistence.service.expression.experiment.FactorValueMigratorService; + +import javax.annotation.CheckReturnValue; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.apache.commons.lang.StringUtils.stripToNull; + +/** + * Performs the migration of old-style characteristics to statements. + *

+ * The input file describes transition from a bag of {@link ubic.gemma.model.common.description.Characteristic} to more + * structured {@link ubic.gemma.model.expression.experiment.Statement} which are allowed to have up to two related + * objects. + *

+ * All unmentioned characteristics will be migrated to subject-only statements. + * @deprecated this will be removed as soon as all the old-style characteristics are migrated + * @author poirigui + */ +@Deprecated +public class FactorValueMigratorCLI extends AbstractAuthenticatedCLI { + + private static final String + MIGRATION_FILE_OPTION = "migrationFile", + MIGRATE_REMAINING_CHARACTERISTICS_OPTION = "migrateRemainingCharacteristics", + MIGRATE_REMAINING_FACTOR_VALUES_OPTION = "migrateRemainingFactorValues", + MIGRATE_NON_TRIVIAL_CASES_OPTION = "migrateNonTrivialCases", + NOOP_OPTION = "noop"; + + @Autowired + private FactorValueMigratorService factorValueMigratorService; + + /** + * A list of migrations to perform. + */ + private List migrations; + private boolean migrateRemainingCharacteristics; + private boolean migrateRemainingFactorValues; + private boolean migrateNonTrivialCases; + private boolean noop; + + @Override + public String getCommandName() { + return "migrateFactorValues"; + } + + @Override + public String getShortDesc() { + return "Perform the migration of old-style characteristics to statements"; + } + + @Override + public GemmaCLI.CommandGroup getCommandGroup() { + return GemmaCLI.CommandGroup.MISC; + } + + @Override + protected void buildOptions( Options options ) { + options.addOption( Option.builder( MIGRATION_FILE_OPTION ).hasArg().type( File.class ).desc( "File containing factor value migrations" ).build() ); + options.addOption( MIGRATE_REMAINING_CHARACTERISTICS_OPTION, false, "Migrate remaining characteristics of factor values that were mentioned in the migration file. The affected factor values will be marked as needs attention." ); + options.addOption( MIGRATE_REMAINING_FACTOR_VALUES_OPTION, false, "Migrate remaining factor values that weren't mentioned in the migration file." ); + options.addOption( MIGRATE_NON_TRIVIAL_CASES_OPTION, false, "Migrate non-trivial cases (i.e. 2 or more old-style characteristics) to subject-only statements. The affected factor values will be marked as needs attention." ); + options.addOption( NOOP_OPTION, false, "Only validate migrations; no statements will be saved" ); + } + + @Value + static class MigrationWithLineNumber { + long lineNumber; + FactorValueMigratorService.Migration migration; + + @Override + public String toString() { + return String.format( "[%d] %s", lineNumber, migration ); + } + } + + @Override + protected void processOptions( CommandLine commandLine ) throws ParseException { + if ( !commandLine.hasOption( MIGRATION_FILE_OPTION ) && !commandLine.hasOption( MIGRATE_REMAINING_FACTOR_VALUES_OPTION ) ) { + throw new MissingOptionException( String.format( "At least one of -%s or -%s must be specified.", + MIGRATION_FILE_OPTION, MIGRATE_REMAINING_FACTOR_VALUES_OPTION ) ); + } + if ( !commandLine.hasOption( MIGRATION_FILE_OPTION ) && commandLine.hasOption( MIGRATE_REMAINING_CHARACTERISTICS_OPTION ) ) { + throw new MissingOptionException( String.format( "-%s must be specified if -%s is used.", + MIGRATION_FILE_OPTION, MIGRATE_REMAINING_CHARACTERISTICS_OPTION ) ); + } + if ( !commandLine.hasOption( MIGRATE_REMAINING_FACTOR_VALUES_OPTION ) && commandLine.hasOption( MIGRATE_NON_TRIVIAL_CASES_OPTION ) ) { + throw new MissingOptionException( String.format( "-%s must be specified if -%s is used.", + MIGRATE_REMAINING_FACTOR_VALUES_OPTION, MIGRATE_NON_TRIVIAL_CASES_OPTION ) ); + } + migrations = new ArrayList<>(); + if ( commandLine.hasOption( MIGRATION_FILE_OPTION ) ) { + File migrationFile = ( File ) commandLine.getParsedOptionValue( MIGRATION_FILE_OPTION ); + try ( CSVParser parser = CSVParser.parse( migrationFile, StandardCharsets.UTF_8, CSVFormat.TDF.withFirstRecordAsHeader() ) ) { + boolean hasSecondObjectColumns = CollectionUtils.containsAny( parser.getHeaderNames(), + "SecondObjectID", "SecondObjectURI", "SecondObject" ); + boolean hasThirdObjectColumns = CollectionUtils.containsAny( parser.getHeaderNames(), + "ThirdObjectID", "ThirdObjectURI", "ThirdObject" ); + for ( CSVRecord row : parser ) { + FactorValueMigratorService.Migration migration; + try { + FactorValueMigratorService.Migration.MigrationBuilder migrationBuilder = FactorValueMigratorService.Migration.builder() + .factorValueId( parseLongIfNonBlank( row.get( "FactorValueID" ) ) ) + .category( stripToNull( row.get( "Category" ) ) ).categoryUri( stripToNull( row.get( "CategoryURI" ) ) ) + .oldStyleCharacteristicIdUsedAsSubject( parseLongIfNonBlank( row.get( "SubjectID" ) ) ) + .subject( stripToNull( row.get( "Subject" ) ) ).subjectUri( stripToNull( row.get( "SubjectURI" ) ) ) + .predicate( stripToNull( row.get( "Predicate" ) ) ).predicateUri( stripToNull( row.get( "PredicateURI" ) ) ) + .oldStyleCharacteristicIdUsedAsObject( parseLongIfNonBlank( row.get( "ObjectID" ) ) ) + .object( stripToNull( row.get( "Object" ) ) ).objectUri( stripToNull( row.get( "ObjectURI" ) ) ); + if ( hasSecondObjectColumns ) { + // optionally... + migrationBuilder + .secondPredicate( stripToNull( row.get( "SecondPredicate" ) ) ).secondPredicateUri( stripToNull( row.get( "SecondPredicateURI" ) ) ) + .oldStyleCharacteristicIdUsedAsSecondObject( parseLongIfNonBlank( row.get( "SecondObjectID" ) ) ) + .secondObject( stripToNull( row.get( "SecondObject" ) ) ).secondObjectUri( stripToNull( row.get( "SecondObjectURI" ) ) ); + } + if ( hasThirdObjectColumns && ( StringUtils.isNotBlank( row.get( "ThirdObjectID" ) ) || StringUtils.isNotBlank( row.get( "ThirdObjectURI" ) ) || StringUtils.isNotBlank( row.get( "ThirdObject" ) ) ) ) { + throw new IllegalArgumentException( "Statements do not support a third object, make sure that any of the columns related to a third object are left blank." ); + } + migration = migrationBuilder.build(); + } catch ( Exception e ) { + throw new RuntimeException( String.format( "The following migration is invalid:\n\t[%d] %s\n\t%s", + row.getRecordNumber(), Arrays.toString( row.values() ), ExceptionUtils.getRootCauseMessage( e ) ), e ); + } + migrations.add( new MigrationWithLineNumber( row.getRecordNumber(), migration ) ); + } + } catch ( IOException e ) { + throw new RuntimeException( e ); + } + if ( migrations.isEmpty() ) { + log.warn( String.format( "The migration file %s is empty.", migrationFile.getAbsolutePath() ) ); + } + } + migrateRemainingCharacteristics = commandLine.hasOption( MIGRATE_REMAINING_CHARACTERISTICS_OPTION ); + migrateRemainingFactorValues = commandLine.hasOption( MIGRATE_REMAINING_FACTOR_VALUES_OPTION ); + migrateNonTrivialCases = commandLine.hasOption( MIGRATE_NON_TRIVIAL_CASES_OPTION ); + noop = commandLine.hasOption( NOOP_OPTION ); + } + + private Long parseLongIfNonBlank( String s ) { + return StringUtils.isBlank( s ) ? null : Long.parseLong( StringUtils.strip( s ) ); + } + + @Override + protected void doWork() throws Exception { + if ( noop ) { + log.info( "Noop mode enabled, no statements will be saved." ); + } + if ( migrateRemainingFactorValues ) { + promptConfirmationOrAbort( "Migrating remaining factor values will create a lot of statements." ); + } + if ( migrateNonTrivialCases ) { + promptConfirmationOrAbort( "Migrating non-trivial cases will create a lot of statements and needs attention events on the affected datasets." ); + } + Map> migrationsByFactorValue = migrations.stream() + .collect( Collectors.groupingBy( migration -> migration.getMigration().getFactorValueId(), Collectors.toList() ) ); + for ( Map.Entry> entry : migrationsByFactorValue.entrySet() ) { + Long fvId = entry.getKey(); + List ms = entry.getValue(); + // all the mentioned old-style characteristics will be marked for removal and the remaining ones will be + // ported to simple subject-only statements + Set mentionedOldStyleCharacteristicIds = ms.stream() + .map( MigrationWithLineNumber::getMigration ) + .flatMap( m -> Stream.of( m.getOldStyleCharacteristicIdUsedAsSubject(), m.getOldStyleCharacteristicIdUsedAsObject(), m.getOldStyleCharacteristicIdUsedAsSecondObject() ) ) + .filter( Objects::nonNull ) + .collect( Collectors.toSet() ); + if ( ms.size() == 1 ) { + MigrationWithLineNumber m = ms.iterator().next(); + try { + List results = new ArrayList<>(); + results.add( factorValueMigratorService.performMigration( m.getMigration(), noop ) ); + if ( migrateRemainingCharacteristics ) { + results.addAll( factorValueMigratorService.performMigrationOfRemainingOldStyleCharacteristics( fvId, mentionedOldStyleCharacteristicIds, noop ) ); + } + addSuccessObject( "FactorValue #" + fvId, summarizeMigrationResults( results ) ); + } catch ( IllegalArgumentException e ) { + String summary = String.format( "[%d] %s", m.getLineNumber(), m.getMigration() ); + addErrorObject( "FactorValue #" + fvId, + "The following migration failed:\n\t" + summary + "\n\t" + ExceptionUtils.getRootCauseMessage( e ) ); + } catch ( Exception e ) { + throw interruptMigrationProcess( m, e ); + } + } else { + try { + List fvm = ms.stream() + .map( MigrationWithLineNumber::getMigration ) + .collect( Collectors.toList() ); + List results = new ArrayList<>( fvm.size() ); + results.addAll( factorValueMigratorService.performMultipleMigrations( fvm, noop ) ); + if ( migrateRemainingCharacteristics ) { + results.addAll( factorValueMigratorService.performMigrationOfRemainingOldStyleCharacteristics( fvId, mentionedOldStyleCharacteristicIds, noop ) ); + } + addSuccessObject( "FactorValue #" + fvId, summarizeMigrationResults( results ) ); + } catch ( FactorValueMigratorService.MigrationFailedException e ) { + // skip the migrations that succeeded from appearing in the error message + int successfulMigrationsToSkip = 0; + for ( MigrationWithLineNumber m : ms ) { + if ( m.getMigration() == e.getMigration() ) { + break; + } + successfulMigrationsToSkip++; + } + // match it with the migration with line number + String summary = ms.stream() + .skip( successfulMigrationsToSkip ) + .map( m -> String.format( "[%d] %s", m.getLineNumber(), m.getMigration() ) ) + .collect( Collectors.joining( "\n\t" ) ); + if ( e.getCause() instanceof IllegalArgumentException ) { + addErrorObject( "FactorValue #" + fvId, + "One or more of the following migrations failed:\n\t" + summary + "\n\t" + ExceptionUtils.getRootCauseMessage( e ) ); + } else { + throw interruptMigrationProcess( ms.get( successfulMigrationsToSkip ), e ); + } + } + } + } + if ( migrateRemainingFactorValues ) { + try { + factorValueMigratorService.performMigrationOfRemainingFactorValues( migrationsByFactorValue.keySet(), migrateNonTrivialCases, noop ) + .forEach( ( fvId, stmts ) -> { + addSuccessObject( "FactorValue #" + fvId, + summarizeMigrationResults( stmts ) ); + } ); + } catch ( IllegalArgumentException e ) { + addErrorObject( "Remaining FactorValues", "Failed to migrate the remaining factor values.", e ); + } + } + } + + private String summarizeMigrationResults( List results ) { + if ( results.isEmpty() ) { + return "No statements were created or updated."; + } else if ( results.size() <= 5 ) { + return results.stream() + .map( r -> String.format( "%s %s", r.isCreated() ? "Created" : "Updated", r.getStatement() ) ) + .collect( Collectors.joining( "\n" ) ); + } else { + long created = results.stream() + .filter( FactorValueMigratorService.MigrationResult::isCreated ) + .count(); + long updated = ( results.size() - created ); + if ( created > 0 && updated > 0 ) { + return "Created " + created + " and updated " + updated + " statements"; + } else if ( created > 0 ) { + return "Created " + created + " statements"; + } else if ( updated > 0 ) { + return "Updated " + updated + " statements"; + } else { + return "No statements were created or updated."; + } + } + } + + @CheckReturnValue + private Exception interruptMigrationProcess( MigrationWithLineNumber m, Exception cause ) { + log.fatal( "A " + cause.getClass().getName() + " exception occurred, the migration process will not continue." ); + String summary = String.format( "[%d] %s", m.getLineNumber(), m.getMigration() ); + return new Exception( "The following migration failed:\n\t" + summary, cause ); + } +} diff --git a/gemma-cli/src/main/java/ubic/gemma/core/apps/FindObsoleteTermsCli.java b/gemma-cli/src/main/java/ubic/gemma/core/apps/FindObsoleteTermsCli.java new file mode 100644 index 0000000000..effbe261f3 --- /dev/null +++ b/gemma-cli/src/main/java/ubic/gemma/core/apps/FindObsoleteTermsCli.java @@ -0,0 +1,103 @@ +package ubic.gemma.core.apps; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.Options; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.task.AsyncTaskExecutor; +import ubic.gemma.core.ontology.OntologyService; +import ubic.gemma.core.util.AbstractCLI; +import ubic.gemma.model.common.description.CharacteristicValueObject; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletionService; +import java.util.concurrent.ExecutorCompletionService; +import java.util.concurrent.Future; +import java.util.stream.Collectors; + +public class FindObsoleteTermsCli extends AbstractCLI { + + @Autowired + private OntologyService ontologyService; + + @Autowired + @Qualifier("ontologyTaskExecutor") + private AsyncTaskExecutor ontologyTaskExecutor; + + @Value("${load.ontologies}") + private boolean autoLoadOntologies; + + @Autowired + private List ontologies; + + @Override + public GemmaCLI.CommandGroup getCommandGroup() { + return GemmaCLI.CommandGroup.METADATA; + } + + @Override + public String getShortDesc() { + return "Check for characteristics using obsolete terms as values (excluding GO), prints to sdout"; + } + + @Override + protected void processOptions( CommandLine commandLine ) { + // no extra options. + } + + @Override + public String getCommandName() { + return "findObsoleteTerms"; + } + + @Override + protected void buildOptions( Options options ) { + } + + @Override + protected void doWork() throws Exception { + if ( autoLoadOntologies ) { + throw new IllegalArgumentException( "Auto-loading of ontologies is enabled, disable it by setting load.ontologies=false in Gemma.properties." ); + } + + log.info( String.format( "Warming up %d ontologies ...", ontologies.size() ) ); + CompletionService completionService = new ExecutorCompletionService<>( ontologyTaskExecutor ); + Map> futures = new LinkedHashMap<>(); + for ( ubic.basecode.ontology.providers.OntologyService ontology : ontologies ) { + futures.put( ontology, completionService.submit( () -> { + // we don't need all those features for detecting obsolete terms + ontology.setSearchEnabled( false ); + ontology.setInferenceMode( ubic.basecode.ontology.providers.OntologyService.InferenceMode.NONE ); + ontology.initialize( true, false ); + return ontology; + } ) ); + } + + for ( int i = 0; i < ontologies.size(); i++ ) { + ubic.basecode.ontology.providers.OntologyService os = completionService.take().get(); + log.info( String.format( " === Ontology (%d/%d) warmed up: %s", i + 1, ontologies.size(), os ) ); + int remainingToLoad = ontologies.size() - ( i + 1 ); + if ( remainingToLoad > 0 && remainingToLoad <= 5 ) { + log.info( "Still loading:\n\t" + futures.entrySet().stream().filter( e -> !e.getValue().isDone() ) + .map( Map.Entry::getKey ) + .map( ubic.basecode.ontology.providers.OntologyService::toString ) + .collect( Collectors.joining( "\n\t" ) ) ); + } + } + + log.info( "Ontologies warmed up, starting check..." ); + + Map vos = ontologyService.findObsoleteTermUsage(); + + AbstractCLI.log.info( "Obsolete term check finished, printing ..." ); + + System.out.println( "Value\tValueUri\tCount" ); + for ( CharacteristicValueObject vo : vos.values() ) { + System.out.println( vo.getValue() + "\t" + vo.getValueUri() + "\t" + vo.getNumTimesUsed() ); + } + + } +} diff --git a/gemma-cli/src/main/java/ubic/gemma/core/apps/GeeqCli.java b/gemma-cli/src/main/java/ubic/gemma/core/apps/GeeqCli.java index a8053839b0..d17c00f293 100644 --- a/gemma-cli/src/main/java/ubic/gemma/core/apps/GeeqCli.java +++ b/gemma-cli/src/main/java/ubic/gemma/core/apps/GeeqCli.java @@ -21,8 +21,8 @@ import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; import ubic.gemma.core.util.AbstractCLI; import ubic.gemma.model.common.auditAndSecurity.eventType.GeeqEvent; import ubic.gemma.model.expression.experiment.BioAssaySet; @@ -34,7 +34,6 @@ * * @author tesar */ -@Component public class GeeqCli extends ExpressionExperimentManipulatingCLI { @Autowired @@ -48,7 +47,7 @@ public String getShortDesc() { } @Override - protected void processOptions( CommandLine commandLine ) { + protected void processOptions( CommandLine commandLine ) throws ParseException { super.processOptions( commandLine ); if ( commandLine.hasOption( 'm' ) ) { this.mode = GeeqService.ScoreMode.valueOf( commandLine.getOptionValue( 'm' ) ); @@ -64,9 +63,8 @@ public String getCommandName() { protected void buildOptions( Options options ) { super.buildOptions( options ); - super.addAutoOption( options ); + super.addAutoOption( options, GeeqEvent.class ); super.addDateOption( options ); - this.autoSeekEventType = GeeqEvent.class; super.addForceOption( options ); Option modeOption = Option.builder( "m" ).longOpt( "mode" ) diff --git a/gemma-cli/src/main/java/ubic/gemma/core/apps/GemmaCLI.java b/gemma-cli/src/main/java/ubic/gemma/core/apps/GemmaCLI.java index 2ba7511c78..4a2b428c98 100644 --- a/gemma-cli/src/main/java/ubic/gemma/core/apps/GemmaCLI.java +++ b/gemma-cli/src/main/java/ubic/gemma/core/apps/GemmaCLI.java @@ -29,7 +29,6 @@ import ubic.gemma.core.util.AbstractCLI; import ubic.gemma.core.util.BuildInfo; import ubic.gemma.core.util.CLI; -import ubic.gemma.persistence.util.Settings; import ubic.gemma.persistence.util.SpringContextUtil; import ubic.gemma.persistence.util.SpringProfiles; @@ -93,14 +92,14 @@ public static void main( String[] args ) { // quick help without loading the context if ( commandLine.hasOption( HELP_OPTION ) ) { - GemmaCLI.printHelp( options, null ); + GemmaCLI.printHelp( options, null, new PrintWriter( System.out, true ) ); System.exit( 0 ); return; } if ( commandLine.hasOption( VERSION_OPTION ) ) { BuildInfo buildInfo = BuildInfo.fromSettings(); - System.err.printf( "Gemma version %s%n", buildInfo ); + System.out.printf( "Gemma version %s%n", buildInfo ); System.exit( 0 ); return; } @@ -111,6 +110,7 @@ public static void main( String[] args ) { } catch ( ParseException | IllegalArgumentException e ) { System.err.printf( "Failed to parse the %s option: %s.%n", VERBOSITY_OPTION, ExceptionUtils.getRootCauseMessage( e ) ); + GemmaCLI.printHelp( options, null, new PrintWriter( System.err, true ) ); System.exit( 1 ); return; } @@ -127,9 +127,10 @@ public static void main( String[] args ) { try { loggingConfigurer.configureLogger( loggerName, Integer.parseInt( vals[1] ) ); } catch ( IllegalArgumentException e ) { - System.err.printf( "Failed to parse the %s option for %s: %s.%n", VERBOSITY_OPTION, + System.err.printf( "Failed to parse the %s option for %s: %s.%n", LOGGER_OPTION, loggerName, ExceptionUtils.getRootCauseMessage( e ) ); + GemmaCLI.printHelp( options, null, new PrintWriter( System.err, true ) ); System.exit( 1 ); return; } @@ -151,8 +152,6 @@ public static void main( String[] args ) { profiles.add( SpringProfiles.TEST ); } - lintConfiguration(); - ApplicationContext ctx = SpringContextUtil.getApplicationContext( profiles.toArray( new String[0] ) ); /* @@ -178,7 +177,7 @@ public static void main( String[] args ) { // no command is passed if ( commandLine.getArgList().isEmpty() ) { System.err.println( "No command was supplied." ); - GemmaCLI.printHelp( options, commandGroups ); + GemmaCLI.printHelp( options, commandGroups, new PrintWriter( System.err, true ) ); System.exit( 1 ); } @@ -190,7 +189,7 @@ public static void main( String[] args ) { int statusCode; if ( !commandsByName.containsKey( commandRequested ) ) { System.err.println( "Unrecognized command: " + commandRequested ); - GemmaCLI.printHelp( options, commandGroups ); + GemmaCLI.printHelp( options, commandGroups, new PrintWriter( System.err, true ) ); statusCode = 1; } else { try { @@ -221,23 +220,8 @@ static String getOptStringForLogging( Object[] argsToPass ) { return matcher.replaceAll( "$1 XXXXXX" ); } - private static void lintConfiguration() { - // check some common settings that might affect initialization time - if ( Settings.getBoolean( "load.ontologies" ) ) { - log.warn( "Auto-loading of ontologies is enabled, this is not recommended for the CLI. Disable it by setting load.ontologies=false in Gemma.properties." ); - } - - if ( Settings.getBoolean( "load.homologene" ) ) { - log.warn( "Homologene is enabled, this is not recommended for the CLI. Disable it by setting load.homologene=false in Gemma.properties." ); - } - - if ( Settings.getString( "gemma.hibernate.hbm2ddl.auto" ).equals( "validate" ) ) { - log.warn( "Hibernate is configured to validate the database schema, this is not recommended for the CLI. Disable it by setting gemma.hibernate.hbm2ddl.auto= in Gemma.properties." ); - } - } - - private static void printHelp( Options options, @Nullable SortedMap> commands ) { - System.err.println( "============ Gemma CLI tools ============" ); + private static void printHelp( Options options, @Nullable SortedMap> commands, PrintWriter writer ) { + writer.println( "============ Gemma CLI tools ============" ); StringBuilder footer = new StringBuilder(); if ( commands != null ) { @@ -259,7 +243,7 @@ private static void printHelp( Options options, @Nullable SortedMap [options]", + new HelpFormatter().printHelp( writer, 150, "gemma-cli [options]", AbstractCLI.HEADER, options, HelpFormatter.DEFAULT_LEFT_PAD, HelpFormatter.DEFAULT_DESC_PAD, footer.toString() ); } diff --git a/gemma-cli/src/main/java/ubic/gemma/core/apps/GenericGenelistDesignGenerator.java b/gemma-cli/src/main/java/ubic/gemma/core/apps/GenericGenelistDesignGenerator.java index 50fc65f0be..f92fffda37 100644 --- a/gemma-cli/src/main/java/ubic/gemma/core/apps/GenericGenelistDesignGenerator.java +++ b/gemma-cli/src/main/java/ubic/gemma/core/apps/GenericGenelistDesignGenerator.java @@ -17,18 +17,16 @@ import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; -import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; import ubic.gemma.core.analysis.report.ArrayDesignReportService; import ubic.gemma.core.analysis.service.ArrayDesignAnnotationService; import ubic.gemma.core.apps.GemmaCLI.CommandGroup; import ubic.gemma.core.genome.gene.service.GeneService; +import ubic.gemma.core.util.AbstractAuthenticatedCLI; import ubic.gemma.core.util.AbstractCLI; -import ubic.gemma.core.util.AbstractCLIContextCLI; +import ubic.gemma.core.util.FileUtils; import ubic.gemma.model.common.auditAndSecurity.eventType.AnnotationBasedGeneMappingEvent; -import ubic.gemma.model.common.description.DatabaseEntry; -import ubic.gemma.model.common.description.ExternalDatabase; import ubic.gemma.model.expression.arrayDesign.ArrayDesign; -import ubic.gemma.model.expression.arrayDesign.TechnologyType; import ubic.gemma.model.expression.designElement.CompositeSequence; import ubic.gemma.model.genome.Gene; import ubic.gemma.model.genome.Taxon; @@ -37,40 +35,54 @@ import ubic.gemma.model.genome.biosequence.SequenceType; import ubic.gemma.model.genome.gene.GeneProduct; import ubic.gemma.model.genome.sequenceAnalysis.AnnotationAssociation; -import ubic.gemma.persistence.service.common.description.ExternalDatabaseService; +import ubic.gemma.persistence.service.common.auditAndSecurity.AuditTrailService; import ubic.gemma.persistence.service.expression.arrayDesign.ArrayDesignService; import ubic.gemma.persistence.service.expression.designElement.CompositeSequenceService; import ubic.gemma.persistence.service.genome.biosequence.BioSequenceService; +import ubic.gemma.persistence.service.genome.gene.GeneProductService; import ubic.gemma.persistence.service.genome.sequenceAnalysis.AnnotationAssociationService; import ubic.gemma.persistence.service.genome.taxon.TaxonService; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; +import java.io.File; +import java.util.*; /** - * Create (or update) an array design based on the current set of transcripts for a taxon. - * This is used to create a 'platform' for linking non-array based data to the system, or data for which we have only - * gene or transcript-level information. - * See also: To generate annotation files for all genes in a taxon, this can also accomplished by - * ArrayDesignAnnotationFileCli. The difference here is that an array design is actually created. + * Create (or update) an array design based on a list of NCBI gene IDs desired to be on the platform. * * @author paul */ -public class GenericGenelistDesignGenerator extends AbstractCLIContextCLI { +public class GenericGenelistDesignGenerator extends AbstractAuthenticatedCLI { + @Autowired private AnnotationAssociationService annotationAssociationService; + @Autowired private ArrayDesignAnnotationService arrayDesignAnnotationService; + @Autowired private ArrayDesignReportService arrayDesignReportService; + @Autowired private ArrayDesignService arrayDesignService; + @Autowired private BioSequenceService bioSequenceService; + @Autowired private CompositeSequenceService compositeSequenceService; - private ExternalDatabaseService externalDatabaseService; + @Autowired private GeneService geneService; + @Autowired + private GeneProductService geneProductService; + + @Autowired + private TaxonService taxonService; + + private String platformShortName = null; + private String geneListFileName = null; private Taxon taxon = null; - private boolean useEnsemblIds = false; - private boolean useNCBIIds = false; + + private boolean noDB = false; + + + @Autowired + private AuditTrailService auditTrailService; @Override public CommandGroup getCommandGroup() { @@ -82,377 +94,267 @@ public String getCommandName() { return "genericPlatform"; } - @SuppressWarnings("static-access") @Override protected void buildOptions( Options options ) { - options.addOption( Option.builder( "t" ).longOpt( "taxon" ).desc( "Taxon of the genes" ).argName( "taxon" ).hasArg().build() ); - options.addOption( "ncbiids", "use NCBI numeric IDs as the identifiers instead of gene symbols" ); - options.addOption( "ensembl", "use Ensembl identifiers instead of gene symbols" ); - } - - @Override - protected void doWork() throws Exception { - ExternalDatabase genbank = externalDatabaseService.findByName( "Genbank" ); - ExternalDatabase ensembl = externalDatabaseService.findByName( "Ensembl" ); - if ( genbank == null || ensembl == null ) { - throw new IllegalStateException( "A record for Genbank and/or Ensembl couldn't be found" ); - } + options.addOption( Option.builder( "t" ).longOpt( "taxon" ).desc( "Taxon of the genes" ).argName( "taxon" ).required().hasArg().build() ); - /* - * Create the stub array design for the organism. The name and etc. are generated automatically. If the design - * exists, we update it. - */ + Option arrayDesignOption = Option.builder( "a" ).hasArg().argName( "shortName" ) + .desc( "Platform short name (existing or new to add)" ).required().longOpt( "platform" ).build(); + options.addOption( arrayDesignOption ); - String shortName = this.generateShortName(); + Option geneListOption = Option.builder( "f" ).hasArg().argName( "file" ).desc( + "File with list of NCBI IDs of genes to add to platform (one per line)" ) + .longOpt( "geneListFile" ).required().build(); + options.addOption( geneListOption ); - ArrayDesign arrayDesign = ArrayDesign.Factory.newInstance(); - arrayDesign.setShortName( shortName ); + options.addOption( Option.builder( "nodb" ).desc( "Dry run: Do not update the database nor delete any flat files" ).build() ); + } - // common name - arrayDesign.setPrimaryTaxon( taxon ); - String nameExt = useNCBIIds ? ", indexed by NCBI IDs" : useEnsemblIds ? ", indexed by Ensembl IDs" : ""; - arrayDesign.setName( "Generic platform for " + taxon.getScientificName() + nameExt ); - arrayDesign.setDescription( "Created by Gemma" ); - arrayDesign.setTechnologyType( TechnologyType.GENELIST ); // this is key + @Override + protected void doWork() throws Exception { - if ( arrayDesignService.find( arrayDesign ) != null ) { - AbstractCLI.log.info( "Platform for " + taxon + " already exists, will update" ); - arrayDesign = arrayDesignService.findOrFail( arrayDesign ); - arrayDesignService.deleteGeneProductAnnotationAssociations( arrayDesign ); - arrayDesign = arrayDesignService.loadOrFail( arrayDesign.getId() ); + ArrayDesign platform = arrayDesignService.findByShortName( this.platformShortName ); + platform = arrayDesignService.thaw( platform ); - } else { - AbstractCLI.log.info( "Creating new 'generic' platform" ); - arrayDesign = arrayDesignService.create( arrayDesign ); + // test whether the geneListFileName file exists and is readable + File geneListFile = new File( this.geneListFileName ); + if ( !geneListFile.exists() || !geneListFile.canRead() ) { + throw new IllegalArgumentException( "File " + this.geneListFileName + " does not exist or cannot be read" ); } - arrayDesign = arrayDesignService.thaw( arrayDesign ); - - // temporary: making sure we set it, as it is new. - arrayDesign.setTechnologyType( TechnologyType.GENELIST ); - /* - * Load up the genes for the organism. - */ - Collection knownGenes = geneService.loadAll( taxon ); - AbstractCLI.log.info( "Taxon has " + knownGenes.size() + " genes" ); - - // this would be good for cases where the identifier we are using has changed. - Map existingGeneMap = new HashMap<>(); - - if ( !useNCBIIds && !useEnsemblIds ) { - // only using this for symbol changes. - existingGeneMap = this.getExistingGeneMap( arrayDesign ); - } + Set ncbiIds = new HashSet( FileUtils.readListFileToStrings( this.geneListFileName ) ); + AbstractCLI.log.info( "File had " + ncbiIds.size() + " gene ids" ); - Map existingSymbolMap = this.getExistingProbeNameMap( arrayDesign ); + Map existingSymbolMap = this.nameMap( platform ); int count = 0; int numWithNoTranscript = 0; - // int hasGeneAlready = 0; + int hasGeneAlready = 0; // int numNewGenes = 0; + int geneNotFound = 0; int numNewElements = 0; int numUpdatedElements = 0; - for ( Gene gene : knownGenes ) { - gene = geneService.thaw( gene ); + int needsDummyElement = 0; + for ( String ncbiId : ncbiIds ) { - Collection products = gene.getProducts(); - - log.debug( "> Processing: " + gene.getOfficialSymbol() ); - - if ( products.isEmpty() ) { - numWithNoTranscript++; - AbstractCLI.log.info( "No transcript for " + gene ); - continue; - } - - count++; + log.debug( "> Processing element for NCBI ID = " + ncbiId ); CompositeSequence csForGene = null; - - if ( useNCBIIds ) { - if ( gene.getNcbiGeneId() == null ) { - AbstractCLI.log.debug( "No NCBI ID for " + gene + ", skipping" ); - continue; - } - if ( existingSymbolMap.containsKey( gene.getNcbiGeneId().toString() ) ) { - csForGene = existingSymbolMap.get( gene.getNcbiGeneId().toString() ); - log.debug( " ... gene exists on platform, will update if necessary [" + csForGene + "]" ); - } - } else if ( useEnsemblIds ) { - if ( gene.getEnsemblId() == null ) { - AbstractCLI.log.debug( "No Ensembl ID for " + gene + ", skipping" ); + if ( existingSymbolMap.containsKey( ncbiId ) ) { + /* + Work out if the existing association is to a dummy sequence or not; if not, we have to make a new one. + */ + csForGene = existingSymbolMap.get( ncbiId ); + + if ( csForGene.getBiologicalCharacteristic().getType() == null ) { +// log.info( "Gene NCBI ID=" + ncbiId + " already has an element [" + csForGene + "], but sequence type of " + csForGene.getBiologicalCharacteristic() +// + " is null, will replace with dummy" ); // rare case + needsDummyElement++; + } else if ( csForGene.getBiologicalCharacteristic().getType().equals( SequenceType.DUMMY ) ) { + log.debug( "Gene NCBI ID=" + ncbiId + " already has a usable element, nothing to be done" ); // FOR NOW. This could be a dangling sequence if the gene didn't exist. continue; - } - if ( existingSymbolMap.containsKey( gene.getEnsemblId() ) ) { - csForGene = existingSymbolMap.get( gene.getEnsemblId() ); - log.debug( " ... gene exists on platform , will update if necessary [" + csForGene + "]" ); + } else { + needsDummyElement++; + // AbstractCLI.log.info( "Gene NCBI ID=" + ncbiId + " already has an element [" + csForGene + "], but it is not a dummy, will update" ); } } else { - // the "by symbols" platform. + AbstractCLI.log.info( "Gene NCBI ID=" + ncbiId + " not on platform, may add" ); + } - /* - * detect when the symbol has changed - */ - if ( existingSymbolMap.containsKey( gene.getOfficialSymbol() ) ) { - csForGene = existingSymbolMap.get( gene.getOfficialSymbol() ); - } else if ( existingGeneMap.containsKey( gene ) ) { - csForGene = existingGeneMap.get( gene ); - AbstractCLI.log - .debug( "Gene symbol has changed for: " + gene + "? Current element has name=" + csForGene - .getName() ); - csForGene.setName( gene.getOfficialSymbol() ); - } + Gene gene = null; + try { + gene = geneService.findByNCBIId( Integer.parseInt( ncbiId ) ); + } catch ( NumberFormatException e ) { + // shouldn't happen but just in case + AbstractCLI.log.error( "Could not parse NCBI ID = " + ncbiId + " as an integer" ); } - assert csForGene == null || csForGene.getId() != null : "Null id for " + csForGene; + boolean geneExists = gene != null; + if ( !geneExists ) { + AbstractCLI.log.warn( "No gene for NCBI ID = " + ncbiId + " but adding dummy sequence anyway (no gene product association wil be made)" ); + geneNotFound++; + // continue; + // but still make sure there is an element on the platform for it. + } else { + gene = geneService.thawLite( gene ); + } + if ( gene != null && gene.getProducts().isEmpty() ) { + numWithNoTranscript++; + AbstractCLI.log.info( "No transcripts for " + gene + ", adding element anyway" ); + } + + AnnotationAssociation aa = null; + Collection associationsToRemove = new HashSet<>(); /* - * We arbitrarily link the "probe" to one of the gene's RNA transcripts. We could consider other strategies - * to pick the representative, but it generally doesn't matter. + This block is to try to re-use existing usable dummy elements for the gene, but for the first time run it mostly just finds one that we want to remove. + Such re-use makes sense if we have multiple "generations" of the same platform but if we have just one, this really isn't necessary (and it's going to be slow because of the thaws) */ - for ( GeneProduct geneProduct : products ) { - - /* - * Name is usually the genbank or ensembl accession - */ - String name = geneProduct.getName(); - BioSequence bioSequence = BioSequence.Factory.newInstance(); - Collection accessions = geneProduct.getAccessions(); - bioSequence.setName( name ); - bioSequence.setTaxon( taxon ); - bioSequence.setPolymerType( PolymerType.RNA ); - bioSequence.setType( SequenceType.mRNA ); - BioSequence existing = null; - - if ( accessions.isEmpty() ) { - // this should not be hit. - AbstractCLI.log.warn( "No accession for " + name ); - DatabaseEntry de = DatabaseEntry.Factory.newInstance(); - de.setAccession( name ); - if ( name.startsWith( "ENS" ) && name.length() > 10 ) { - de.setExternalDatabase( ensembl ); - } else { - if ( name.matches( "^[A-Z]{1,2}(_?)[0-9]+(\\.[0-9]+)?$" ) ) { - de.setExternalDatabase( genbank ); - } else { - AbstractCLI.log.info( "Name doesn't look like genbank or ensembl, skipping: " + name ); - continue; + if ( gene != null && !gene.getProducts().isEmpty() ) { + Collection aas = annotationAssociationService.find( gene ); // making fetching eager would help avoid thaws below, but not a big deal. + for ( AnnotationAssociation aae : aas ) { + GeneProduct gp = geneProductService.thaw( aae.getGeneProduct() ); + BioSequence bp = bioSequenceService.thaw( aae.getBioSequence() ); + if ( gp == null || bp == null ) { + log.warn( "Invalid association of gp=" + gp + " and bp=" + bp + " for " + gene + ", marking for removal" ); + associationsToRemove.add( aae ); + } else if ( bp.getType() != null && bp.getType().equals( SequenceType.DUMMY ) && gp.isDummy() ) { + if ( aa != null ) { // this is a sanity check, if we are sure this isn't an issue we can just break here. + throw new IllegalStateException( "More than one dummy annotation association for " + gene ); } + log.info( "Re-using dummy association for " + gene ); + aa = aae; + } else { + // otherwise, we're going to want to delete these old AnnotationAssociations assuming they aren't used for anything. + associationsToRemove.add( aae ); } - bioSequence.setSequenceDatabaseEntry( de ); - } else { - // FIXME It is possible that this sequence will have been aligned to the genome, which is a bit - // confusing. So it will map to a gene. Worse case: it maps to more than one gene ... - existing = bioSequenceService.findByAccession( accessions.iterator().next() ); - if ( existing == null ) { - // create a copy, each biosequence must own their database entry - DatabaseEntry databaseEntry = accessions.iterator().next(); - DatabaseEntry clone = DatabaseEntry.Factory.newInstance(); - clone.setAccession( databaseEntry.getAccession() ); - clone.setAccessionVersion( databaseEntry.getAccessionVersion() ); - clone.setUri( databaseEntry.getUri() ); - clone.setExternalDatabase( databaseEntry.getExternalDatabase() ); - bioSequence.setSequenceDatabaseEntry( clone ); - } - } - - if ( existing == null ) { - bioSequence = ( BioSequence ) this.getPersisterHelper().persist( bioSequence ); - } else { - bioSequence = existing; } + } - assert bioSequence != null && bioSequence.getId() != null; + BioSequence bioSequence = null; + if ( aa == null ) { + /* create a dummy gene Product and sequence for the gene. */ - if ( bioSequence.getSequenceDatabaseEntry() == null ) { - AbstractCLI.log.info( "No DB entry for " + bioSequence + "(" + gene - + "), will look for a better sequence to use ..." ); - continue; - } + bioSequence = csForGene == null ? null : csForGene.getBiologicalCharacteristic(); + if ( bioSequence != null && bioSequence.getType() != null && bioSequence.getType().equals( SequenceType.DUMMY ) ) { + log.debug( "Existing dummy sequence for element, will reuse" ); + } else { + bioSequence = BioSequence.Factory.newInstance(); - if ( csForGene == null ) { // i.e. it is new - log.info( "New element " + " with sequence used:" + bioSequence.getName() + " for " + gene.getOfficialSymbol() ); - csForGene = CompositeSequence.Factory.newInstance(); - if ( useNCBIIds ) { - csForGene.setName( gene.getNcbiGeneId().toString() ); - } else if ( useEnsemblIds ) { - csForGene.setName( gene.getEnsemblId() ); + if ( gene == null ) { + bioSequence.setName( "NCBI ID=" + ncbiId + " generic sequence placeholder" ); } else { - csForGene.setName( gene.getOfficialSymbol() ); - } - - csForGene.setArrayDesign( arrayDesign ); - csForGene.setBiologicalCharacteristic( bioSequence ); - csForGene.setDescription( "Generic expression element for " + gene ); - csForGene = compositeSequenceService.create( csForGene ); - assert csForGene.getId() != null : "No id for " + csForGene + " for " + gene; - arrayDesign.getCompositeSequences().add( csForGene ); - numNewElements++; - } else { // i.e. it is already in the system; just updating - boolean changed = false; - assert csForGene.getId() != null : "No id for " + csForGene + " for " + gene; - - if ( !csForGene.getArrayDesign().equals( arrayDesign ) ) { - // does this happen? - log.debug( "Platform changed? " + csForGene + " on " + csForGene.getArrayDesign() ); - csForGene.setArrayDesign( arrayDesign ); - csForGene.setDescription( "Generic expression element for " + gene ); - changed = true; + bioSequence.setName( gene.getOfficialSymbol() + " [NCBI ID=" + gene.getNcbiGeneId() + "] generic sequence placeholder" ); } + bioSequence.setTaxon( this.taxon ); + bioSequence.setPolymerType( PolymerType.RNA ); + bioSequence.setType( SequenceType.DUMMY ); + if ( !noDB ) bioSequence = bioSequenceService.create( bioSequence ); + } - if ( csForGene.getBiologicalCharacteristic() == null ) { - log.warn( csForGene + " had no sequence, setting to " + bioSequence ); - csForGene.setBiologicalCharacteristic( bioSequence ); - changed = true; - } + if ( geneExists ) { // we only create the Biosequence side if the gene doesn't exist. But we make this dummy gene product even if the gene has no transcripts in Gemma. + GeneProduct geneProduct = GeneProduct.Factory.newInstance(); + geneProduct.setGene( gene ); + geneProduct.setDummy( true ); + geneProduct.setName( gene.getOfficialSymbol() + " [NCBI ID=" + gene.getNcbiGeneId() + "] generic element placeholder" ); + if ( !noDB ) geneProduct = geneProductService.create( geneProduct ); - if ( !csForGene.getBiologicalCharacteristic().equals( bioSequence ) ) { - // does this happen? - csForGene.setBiologicalCharacteristic( bioSequence ); - changed = true; - } + aa = AnnotationAssociation.Factory.newInstance(); + aa.setGeneProduct( geneProduct ); + aa.setBioSequence( bioSequence ); + if ( !noDB ) aa = annotationAssociationService.create( aa ); - if ( changed ) { - compositeSequenceService.update( csForGene ); - } - - // making sure ... - csForGene = compositeSequenceService.loadOrFail( csForGene.getId() ); - assert csForGene.getId() != null; - arrayDesign.getCompositeSequences().add( csForGene ); - - if ( changed ) { - if ( AbstractCLI.log.isDebugEnabled() ) - AbstractCLI.log - .debug( "Updating existing element: " + csForGene + " with " + bioSequence + " for " - + gene ); - numUpdatedElements++; - } + assert noDB || bioSequence.getId() != null; + } else { + log.info( "Gene does not exist, will not create dummy gene product or annotation association" ); } + } - assert bioSequence.getId() != null; - assert geneProduct.getId() != null; - assert csForGene.getBiologicalCharacteristic() != null - && csForGene.getBiologicalCharacteristic().getId() != null; + if ( csForGene == null ) { + if ( gene == null ) { + log.info( "New platform element for NCBI=" + ncbiId + " (" + this.taxon.getCommonName() + ") - but no corresponding gene exists in Gemma" ); + } else { + log.info( "New platform element for " + gene.getOfficialSymbol() + " NCBI=" + gene.getNcbiGeneId() + " (" + gene.getTaxon().getCommonName() + ")" ); + } + csForGene = CompositeSequence.Factory.newInstance(); + csForGene.setName( ncbiId ); // IMPORTANT that this be just the NCBI ID. + csForGene.setArrayDesign( platform ); + csForGene.setBiologicalCharacteristic( bioSequence ); + if ( gene == null ) { + csForGene.setDescription( "Generic expression element for NCBI ID = " + ncbiId ); + } else { + csForGene.setDescription( "Generic expression element for " + gene ); + } + if ( !noDB ) csForGene = compositeSequenceService.create( csForGene ); - AnnotationAssociation aa = AnnotationAssociation.Factory.newInstance(); - aa.setGeneProduct( geneProduct ); - aa.setBioSequence( bioSequence ); - annotationAssociationService.create( aa ); + platform.getCompositeSequences().add( csForGene ); + numNewElements++; + } else { + if ( gene == null ) { // this shouldn't happen. + log.info( "Updating element to use dummy for NCBI=" + ncbiId + " (" + taxon.getCommonName() + ")" ); + } else { + log.info( "Updating element to use dummy for " + gene.getOfficialSymbol() + ": NCBI=" + gene.getNcbiGeneId() + " (" + gene.getTaxon().getCommonName() + ")" ); + } + csForGene.setBiologicalCharacteristic( aa.getBioSequence() ); + if ( !noDB ) compositeSequenceService.update( csForGene ); + numUpdatedElements++; + } - break; + if ( !associationsToRemove.isEmpty() ) { + log.info( associationsToRemove.size() + " old 'non-dummy' annotation associations to remove" ); + if ( !noDB ) { + try { + annotationAssociationService.remove( associationsToRemove ); // may fail if there are other associations. + } catch ( Exception e ) { + log.warn( "Could not delete old annotation associations for " + gene + ": " + e.getMessage() ); + } + } } - if ( count % 100 == 0 ) - AbstractCLI.log - .info( count + " genes processed; " + numNewElements + " new elements; " + numUpdatedElements - + " updated elements; " + numWithNoTranscript - + " genes had no transcript and were skipped." ); + assert noDB || ( csForGene.getBiologicalCharacteristic() != null + && csForGene.getBiologicalCharacteristic().getId() != null ); + + count++; + if ( count % 200 == 0 ) + log.info( " >>>>>>>>> " + count + " genes processed; " + numNewElements + " new elements; " + numUpdatedElements + + " updated elements; " + numWithNoTranscript + + " genes had no transcript." ); } - AbstractCLI.log.info( "Platform has " + arrayDesignService.numCompositeSequenceWithGenes( arrayDesign ) + AbstractCLI.log.info( "Platform has " + arrayDesignService.numCompositeSequenceWithGenes( platform ) + " 'elements' associated with genes." ); - arrayDesignReportService.generateArrayDesignReport( arrayDesign.getId() ); + if ( !noDB ) arrayDesignReportService.generateArrayDesignReport( platform.getId() ); + + String auditMessage = count + " genes processed; " + numNewElements + " new elements; " + numUpdatedElements + + " updated elements; " + numWithNoTranscript + " genes had no transcript; " + geneNotFound + " genes from the file could not be found"; + log.info( auditMessage ); - log.info( count + " genes processed; " + numNewElements + " new elements; " + numUpdatedElements - + " updated elements; " + numWithNoTranscript + " genes had no transcript and were skipped." ); + if ( !noDB ) auditTrailService.addUpdateEvent( platform, AnnotationBasedGeneMappingEvent.class, auditMessage ); - auditTrailService.addUpdateEvent( arrayDesign, AnnotationBasedGeneMappingEvent.class, - count + " genes processed; " + numNewElements + " new elements; " + numUpdatedElements - + " updated elements; " + numWithNoTranscript + " genes had no transcript and were skipped." ); - arrayDesignAnnotationService.deleteExistingFiles( arrayDesign ); + AbstractCLI.log.info( "Don't forget to update the annotation files, any old ones will be deleted (unless dry run)" ); + if ( !noDB ) arrayDesignAnnotationService.deleteExistingFiles( platform ); - AbstractCLI.log.info( "Don't forget to update the annotation files" ); + /* + Delete elements for the platform that are not on the input list. This should probably not be kept here; we'll do it offline. + */ +// for ( String geneID : existingSymbolMap.keySet() ) { +// if ( !ncbiIds.contains( geneID ) ) { +// log.info( "Gene " + geneID + " is not in the input list, will remove element from platform if possible (" + existingSymbolMap.get( geneID ) + ")" ); +// if ( !noDB ) { +// try { +// // compositeSequenceService.remove( existingSymbolMap.get( geneID ) ); +// } catch ( Exception e ) { +// // if there is data associated with it, this will fail. We would need to delete DEA results at least. +// log.warn( "Could not remove unneeded platform element for geneID=" + geneID + ": " + existingSymbolMap.get( geneID ) + ": " + e.getMessage() ); +// } +// } +// } +// } } @Override public String getShortDesc() { - return "Update or create a 'platform' based on the genes for the organism"; + return "Update a 'platform' based on a list of NCBI IDs"; } @Override protected void processOptions( CommandLine commandLine ) { - geneService = this.getBean( GeneService.class ); - arrayDesignAnnotationService = this.getBean( ArrayDesignAnnotationService.class ); - TaxonService taxonService = this.getBean( TaxonService.class ); - bioSequenceService = this.getBean( BioSequenceService.class ); - arrayDesignService = this.getBean( ArrayDesignService.class ); - compositeSequenceService = this.getBean( CompositeSequenceService.class ); - annotationAssociationService = this.getBean( AnnotationAssociationService.class ); - externalDatabaseService = this.getBean( ExternalDatabaseService.class ); - arrayDesignReportService = this.getBean( ArrayDesignReportService.class ); - - if ( commandLine.hasOption( 't' ) ) { - this.taxon = this.setTaxonByName( commandLine, taxonService ); - } - if ( commandLine.hasOption( "ncbiids" ) ) { - this.useNCBIIds = true; - } else if ( commandLine.hasOption( "ensembl" ) ) { - this.useEnsemblIds = true; - } + this.platformShortName = commandLine.getOptionValue( "a" ); + this.taxon = this.taxonService.findByCommonName( commandLine.getOptionValue( "t" ) ); + this.geneListFileName = commandLine.getOptionValue( "f" ); - if ( useNCBIIds && useEnsemblIds ) { - throw new IllegalArgumentException( "Choose one of ensembl or ncbi ids or gene symbols" ); - } - } + this.noDB = commandLine.hasOption( "nodb" ); - private String generateShortName() { - String ncbiIdSuffix = useNCBIIds ? "_ncbiIds" : ""; - String ensemblIdSuffix = useEnsemblIds ? "_ensemblIds" : ""; - String shortName; - if ( StringUtils.isBlank( taxon.getCommonName() ) ) { - shortName = "Generic_" + StringUtils.strip( taxon.getScientificName() ).replaceAll( " ", "_" ) + ncbiIdSuffix; - } else { - shortName = "Generic_" + StringUtils.strip( taxon.getCommonName() ).replaceAll( " ", "_" ) + ncbiIdSuffix - + ensemblIdSuffix; + if ( noDB ) { + log.warn( "***** DRY RUN - no changes will be saved (you may still see relevant logging messages) *****" ); } - return shortName; - } - - /** - * For gene symbols. - */ - private Map getExistingGeneMap( ArrayDesign arrayDesign ) { - - Map existingElements = new HashMap<>(); - - if ( arrayDesign.getCompositeSequences().isEmpty() ) - return existingElements; - AbstractCLI.log.info( "Loading genes for existing platform ..." ); - Map> geneMap = compositeSequenceService - .getGenes( arrayDesign.getCompositeSequences() ); - - AbstractCLI.log - .info( "Platform has genes already for " + geneMap.size() + "/" + arrayDesign.getCompositeSequences() - .size() + " elements." ); - - for ( CompositeSequence cs : geneMap.keySet() ) { - Collection genes = geneMap.get( cs ); - - /* - * Two genes with the same symbol, but might be a mistake from an earlier run. - */ - Gene g = null; - if ( genes.size() > 1 ) { - AbstractCLI.log.warn( "More than one gene for: " + cs + ": " + StringUtils.join( genes, ";" ) ); - for ( Gene cg : genes ) { - if ( cg.getOfficialSymbol().equals( cs.getName() ) ) { - g = cg; - } - } - } else { - g = genes.iterator().next(); - } - existingElements.put( g, cs ); - } - - return existingElements; } - private Map getExistingProbeNameMap( ArrayDesign arrayDesign ) { + + private Map nameMap( ArrayDesign arrayDesign ) { Map existingElements = new HashMap<>(); diff --git a/gemma-cli/src/main/java/ubic/gemma/core/apps/GeoGrabberCli.java b/gemma-cli/src/main/java/ubic/gemma/core/apps/GeoGrabberCli.java index aaa9999f92..8b76bf2399 100755 --- a/gemma-cli/src/main/java/ubic/gemma/core/apps/GeoGrabberCli.java +++ b/gemma-cli/src/main/java/ubic/gemma/core/apps/GeoGrabberCli.java @@ -22,8 +22,8 @@ import ubic.gemma.core.apps.GemmaCLI.CommandGroup; import ubic.gemma.core.loader.expression.geo.model.GeoRecord; import ubic.gemma.core.loader.expression.geo.service.GeoBrowser; +import ubic.gemma.core.util.AbstractAuthenticatedCLI; import ubic.gemma.core.util.AbstractCLI; -import ubic.gemma.core.util.AbstractCLIContextCLI; import ubic.gemma.model.expression.arrayDesign.ArrayDesign; import ubic.gemma.model.genome.Taxon; import ubic.gemma.persistence.service.expression.arrayDesign.ArrayDesignService; @@ -45,10 +45,10 @@ * * @author paul */ -public class GeoGrabberCli extends AbstractCLIContextCLI { +public class GeoGrabberCli extends AbstractAuthenticatedCLI { private static final int NCBI_CHUNK_SIZE = 100; - private static final int MAX_RETRIES = 3; // on failures + private static final int MAX_RETRIES = 5; // on failures private static final int MAX_EMPTY_CHUNKS_IN_A_ROW = 20; // stop condition when we stop seeing useful records private Date dateLimit; private String gseLimit; @@ -98,7 +98,7 @@ protected void buildOptions( Options options ) { } @Override - protected void processOptions( CommandLine commandLine ) throws Exception { + protected void processOptions( CommandLine commandLine ) { if ( !commandLine.hasOption( "output" ) ) { throw new IllegalArgumentException( "You must provide an output file name" ); @@ -253,7 +253,7 @@ protected void doWork() throws Exception { retries++; if ( retries <= MAX_RETRIES ) { log.warn( "Failure while fetching records, retrying " + e.getMessage() ); - Thread.sleep( 500 ); + Thread.sleep( 500 * retries ); continue; } throw new IOException( "Too many failures: " + e.getMessage() ); diff --git a/gemma-cli/src/main/java/ubic/gemma/core/apps/IndexGemmaCLI.java b/gemma-cli/src/main/java/ubic/gemma/core/apps/IndexGemmaCLI.java index fb921d1e0d..94a9e68c4c 100644 --- a/gemma-cli/src/main/java/ubic/gemma/core/apps/IndexGemmaCLI.java +++ b/gemma-cli/src/main/java/ubic/gemma/core/apps/IndexGemmaCLI.java @@ -4,7 +4,6 @@ import org.apache.commons.cli.Options; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; import ubic.gemma.core.search.IndexerService; import ubic.gemma.core.util.AbstractCLI; import ubic.gemma.model.analysis.expression.ExpressionExperimentSet; @@ -22,9 +21,10 @@ import java.util.Set; import java.util.stream.Collectors; -@Component public class IndexGemmaCLI extends AbstractCLI { + private static final String THREADS_OPTION = "threads"; + /** * A list of all searchable entities this CLI supports. */ @@ -53,6 +53,7 @@ private static class IndexableEntity { private File searchDir; private final Set> classesToIndex = new HashSet<>(); + private int numThreads; @Override public String getCommandName() { @@ -78,7 +79,7 @@ protected void buildOptions( Options options ) { } @Override - protected void processOptions( CommandLine commandLine ) throws Exception { + protected void processOptions( CommandLine commandLine ) { for ( IndexableEntity ie : indexableEntities ) { if ( commandLine.hasOption( ie.option ) ) { classesToIndex.add( ie.clazz ); @@ -90,12 +91,12 @@ protected void processOptions( CommandLine commandLine ) throws Exception { protected void doWork() throws Exception { if ( classesToIndex.isEmpty() ) { log.info( String.format( "All entities will be indexed under %s.", searchDir.getAbsolutePath() ) ); - indexerService.index( numThreads ); + indexerService.index( getNumThreads() ); } else { log.info( String.format( "The following entities will be indexed under %s:\n\t%s", searchDir.getAbsolutePath(), classesToIndex.stream().map( Class::getName ).collect( Collectors.joining( "\n\t" ) ) ) ); - indexerService.index( classesToIndex, numThreads ); + indexerService.index( classesToIndex, getNumThreads() ); } } } diff --git a/gemma-cli/src/main/java/ubic/gemma/core/apps/LinkAnalysisCli.java b/gemma-cli/src/main/java/ubic/gemma/core/apps/LinkAnalysisCli.java index a183b58b9c..7f965191b3 100644 --- a/gemma-cli/src/main/java/ubic/gemma/core/apps/LinkAnalysisCli.java +++ b/gemma-cli/src/main/java/ubic/gemma/core/apps/LinkAnalysisCli.java @@ -21,9 +21,9 @@ import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; import org.apache.commons.lang3.time.StopWatch; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; import ubic.basecode.dataStructure.matrix.DoubleMatrix; import ubic.basecode.io.ByteArrayConverter; import ubic.basecode.util.FileTools; @@ -64,7 +64,6 @@ * @author paul (refactoring) * @author vaneet */ -@Component public class LinkAnalysisCli extends ExpressionExperimentManipulatingCLI { @Autowired @@ -292,6 +291,8 @@ protected void buildOptions( Options options ) { .build(); options.addOption( chooseCutOption ); + options.addOption( Option.builder( "probeDegreeLim" ).hasArg().type( Integer.class ).build() ); + // finer-grained control is possible, of course. Option skipQC = Option.builder( "noqc" ) .desc( "Skip strict QC for outliers, batch effects and correlation distribution" ) @@ -304,12 +305,11 @@ protected void buildOptions( Options options ) { options.addOption( deleteOption ); this.addForceOption( options ); - this.addAutoOption( options ); + this.addAutoOption( options, LinkAnalysisEvent.class ); } @Override - protected void processOptions( CommandLine commandLine ) { - this.autoSeekEventType = LinkAnalysisEvent.class; + protected void processOptions( CommandLine commandLine ) throws ParseException { super.processOptions( commandLine ); if ( commandLine.hasOption( "delete" ) ) { @@ -427,7 +427,7 @@ protected void processOptions( CommandLine commandLine ) { } if ( commandLine.hasOption( "probeDegreeLim" ) ) { - this.linkAnalysisConfig.setProbeDegreeThreshold( this.getIntegerOptionValue( commandLine, "probeDegreeLim" ) ); + this.linkAnalysisConfig.setProbeDegreeThreshold( ( Integer ) commandLine.getParsedOptionValue( "probeDegreeLim" ) ); } } diff --git a/gemma-cli/src/main/java/ubic/gemma/core/apps/LoadExpressionDataCli.java b/gemma-cli/src/main/java/ubic/gemma/core/apps/LoadExpressionDataCli.java index 771fd50945..1a52693022 100644 --- a/gemma-cli/src/main/java/ubic/gemma/core/apps/LoadExpressionDataCli.java +++ b/gemma-cli/src/main/java/ubic/gemma/core/apps/LoadExpressionDataCli.java @@ -29,9 +29,8 @@ import ubic.gemma.core.apps.GemmaCLI.CommandGroup; import ubic.gemma.core.loader.expression.geo.GeoDomainObjectGenerator; import ubic.gemma.core.loader.expression.geo.service.GeoService; +import ubic.gemma.core.util.AbstractAuthenticatedCLI; import ubic.gemma.core.util.AbstractCLI; -import ubic.gemma.core.util.AbstractCLIContextCLI; -import ubic.gemma.model.common.Describable; import ubic.gemma.model.common.description.DatabaseEntry; import ubic.gemma.model.common.description.ExternalDatabase; import ubic.gemma.model.expression.arrayDesign.ArrayDesign; @@ -51,7 +50,7 @@ * * @author pavlidis */ -public class LoadExpressionDataCli extends AbstractCLIContextCLI { +public class LoadExpressionDataCli extends AbstractAuthenticatedCLI { // Command line Options private String accessionFile = null; @@ -228,6 +227,7 @@ protected void processOptions( CommandLine commandLine ) { private void processAccession( GeoService geoService, String accession ) { try { + log.info(" ***** Starting processing of " + accession + " *****"); if ( updateOnly ) { geoService.updateFromGEO( accession ); addSuccessObject( accession, "Updated" ); diff --git a/gemma-cli/src/main/java/ubic/gemma/core/apps/LoadSimpleExpressionDataCli.java b/gemma-cli/src/main/java/ubic/gemma/core/apps/LoadSimpleExpressionDataCli.java index eb6e6e6451..153572ded0 100644 --- a/gemma-cli/src/main/java/ubic/gemma/core/apps/LoadSimpleExpressionDataCli.java +++ b/gemma-cli/src/main/java/ubic/gemma/core/apps/LoadSimpleExpressionDataCli.java @@ -26,8 +26,8 @@ import ubic.gemma.core.apps.GemmaCLI.CommandGroup; import ubic.gemma.core.loader.expression.simple.SimpleExpressionDataLoaderService; import ubic.gemma.core.loader.expression.simple.model.SimpleExpressionExperimentMetaData; +import ubic.gemma.core.util.AbstractAuthenticatedCLI; import ubic.gemma.core.util.AbstractCLI; -import ubic.gemma.core.util.AbstractCLIContextCLI; import ubic.gemma.model.common.quantitationtype.GeneralType; import ubic.gemma.model.common.quantitationtype.ScaleType; import ubic.gemma.model.common.quantitationtype.StandardQuantitationType; @@ -48,7 +48,7 @@ * * @author xiangwan */ -public class LoadSimpleExpressionDataCli extends AbstractCLIContextCLI { +public class LoadSimpleExpressionDataCli extends AbstractAuthenticatedCLI { private final static String SPLIT_CHAR = "\t"; private final static int NAME_I = 0; private final static int SHORT_NAME_I = LoadSimpleExpressionDataCli.NAME_I + 1; diff --git a/gemma-cli/src/main/java/ubic/gemma/core/apps/MakeExperimentPrivateCli.java b/gemma-cli/src/main/java/ubic/gemma/core/apps/MakeExperimentPrivateCli.java index 8b1de43530..aa68546aa7 100644 --- a/gemma-cli/src/main/java/ubic/gemma/core/apps/MakeExperimentPrivateCli.java +++ b/gemma-cli/src/main/java/ubic/gemma/core/apps/MakeExperimentPrivateCli.java @@ -2,11 +2,9 @@ import gemma.gsec.SecurityService; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; import ubic.gemma.model.common.auditAndSecurity.eventType.MakePrivateEvent; import ubic.gemma.model.expression.experiment.BioAssaySet; -@Component public class MakeExperimentPrivateCli extends ExpressionExperimentManipulatingCLI { @Autowired diff --git a/gemma-cli/src/main/java/ubic/gemma/core/apps/MultifunctionalityCli.java b/gemma-cli/src/main/java/ubic/gemma/core/apps/MultifunctionalityCli.java index e28c85d3f0..3b53c46cda 100644 --- a/gemma-cli/src/main/java/ubic/gemma/core/apps/MultifunctionalityCli.java +++ b/gemma-cli/src/main/java/ubic/gemma/core/apps/MultifunctionalityCli.java @@ -18,15 +18,15 @@ import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import ubic.gemma.core.analysis.service.GeneMultifunctionalityPopulationService; +import ubic.gemma.core.util.AbstractAuthenticatedCLI; import ubic.gemma.core.util.AbstractCLI; -import ubic.gemma.core.util.AbstractCLIContextCLI; import ubic.gemma.model.genome.Taxon; import ubic.gemma.persistence.service.genome.taxon.TaxonService; /** * @author paul */ -public class MultifunctionalityCli extends AbstractCLIContextCLI { +public class MultifunctionalityCli extends AbstractAuthenticatedCLI { private Taxon taxon; diff --git a/gemma-cli/src/main/java/ubic/gemma/core/apps/NCBIGene2GOAssociationLoaderCLI.java b/gemma-cli/src/main/java/ubic/gemma/core/apps/NCBIGene2GOAssociationLoaderCLI.java index 553c09305f..62fa71004f 100755 --- a/gemma-cli/src/main/java/ubic/gemma/core/apps/NCBIGene2GOAssociationLoaderCLI.java +++ b/gemma-cli/src/main/java/ubic/gemma/core/apps/NCBIGene2GOAssociationLoaderCLI.java @@ -22,13 +22,12 @@ import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; import ubic.gemma.core.apps.GemmaCLI.CommandGroup; import ubic.gemma.core.loader.association.NCBIGene2GOAssociationLoader; import ubic.gemma.core.loader.association.NCBIGene2GOAssociationParser; import ubic.gemma.core.loader.util.fetcher.HttpFetcher; +import ubic.gemma.core.util.AbstractAuthenticatedCLI; import ubic.gemma.core.util.AbstractCLI; -import ubic.gemma.core.util.AbstractCLIContextCLI; import ubic.gemma.model.common.description.ExternalDatabase; import ubic.gemma.model.common.description.ExternalDatabases; import ubic.gemma.model.common.description.LocalFile; @@ -49,8 +48,7 @@ * * @author pavlidis */ -@Component -public class NCBIGene2GOAssociationLoaderCLI extends AbstractCLIContextCLI { +public class NCBIGene2GOAssociationLoaderCLI extends AbstractAuthenticatedCLI { private static final String GENE2GO_FILE = "gene2go.gz"; diff --git a/gemma-cli/src/main/java/ubic/gemma/core/apps/NcbiGeneLoaderCLI.java b/gemma-cli/src/main/java/ubic/gemma/core/apps/NcbiGeneLoaderCLI.java index 3e16fff744..aac0a0274f 100755 --- a/gemma-cli/src/main/java/ubic/gemma/core/apps/NcbiGeneLoaderCLI.java +++ b/gemma-cli/src/main/java/ubic/gemma/core/apps/NcbiGeneLoaderCLI.java @@ -23,11 +23,10 @@ import org.apache.commons.cli.Options; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; import ubic.gemma.core.apps.GemmaCLI.CommandGroup; import ubic.gemma.core.loader.genome.gene.ncbi.NcbiGeneLoader; +import ubic.gemma.core.util.AbstractAuthenticatedCLI; import ubic.gemma.core.util.AbstractCLI; -import ubic.gemma.core.util.AbstractCLIContextCLI; import ubic.gemma.model.common.description.ExternalDatabase; import ubic.gemma.model.common.description.ExternalDatabases; import ubic.gemma.model.genome.Taxon; @@ -43,8 +42,7 @@ * * @author joseph */ -@Component -public class NcbiGeneLoaderCLI extends AbstractCLIContextCLI { +public class NcbiGeneLoaderCLI extends AbstractAuthenticatedCLI { private static final String GENE_INFO_FILE = "gene_info.gz"; private static final String GENE2ACCESSION_FILE = "gene2accession.gz"; private static final String GENE_HISTORY_FILE = "gene_history.gz"; diff --git a/gemma-cli/src/main/java/ubic/gemma/core/apps/ProcessedDataComputeCLI.java b/gemma-cli/src/main/java/ubic/gemma/core/apps/ProcessedDataComputeCLI.java index 5a4705b50d..5d918c2efc 100644 --- a/gemma-cli/src/main/java/ubic/gemma/core/apps/ProcessedDataComputeCLI.java +++ b/gemma-cli/src/main/java/ubic/gemma/core/apps/ProcessedDataComputeCLI.java @@ -20,8 +20,8 @@ import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; import ubic.gemma.core.analysis.preprocess.PreprocessorService; import ubic.gemma.core.analysis.preprocess.QuantitationMismatchPreprocessingException; import ubic.gemma.core.datastructure.matrix.SuspiciousValuesForQuantitationException; @@ -41,7 +41,6 @@ * @author xwan, paul * @see ProcessedExpressionDataVectorServiceImpl */ -@Component public class ProcessedDataComputeCLI extends ExpressionExperimentManipulatingCLI { private static final String @@ -79,7 +78,7 @@ protected void buildOptions( Options options ) { } @Override - protected void processOptions( CommandLine commandLine ) { + protected void processOptions( CommandLine commandLine ) throws ParseException { super.processOptions( commandLine ); this.updateDiagnostics = commandLine.hasOption( UPDATE_DIAGNOSTICS_OPTION ); this.updateRanks = commandLine.hasOption( UPDATE_RANKS_OPTION ); diff --git a/gemma-cli/src/main/java/ubic/gemma/core/apps/PubMedLoaderCli.java b/gemma-cli/src/main/java/ubic/gemma/core/apps/PubMedLoaderCli.java index 0b67b1f6fc..a3db2363d4 100644 --- a/gemma-cli/src/main/java/ubic/gemma/core/apps/PubMedLoaderCli.java +++ b/gemma-cli/src/main/java/ubic/gemma/core/apps/PubMedLoaderCli.java @@ -23,7 +23,7 @@ import org.apache.commons.cli.Options; import ubic.gemma.core.apps.GemmaCLI.CommandGroup; import ubic.gemma.core.loader.entrez.pubmed.PubMedService; -import ubic.gemma.core.util.AbstractCLIContextCLI; +import ubic.gemma.core.util.AbstractAuthenticatedCLI; import java.io.File; @@ -32,7 +32,7 @@ * * @author pavlidis */ -public class PubMedLoaderCli extends AbstractCLIContextCLI { +public class PubMedLoaderCli extends AbstractAuthenticatedCLI { private String directory; diff --git a/gemma-cli/src/main/java/ubic/gemma/core/apps/RNASeqBatchInfoCli.java b/gemma-cli/src/main/java/ubic/gemma/core/apps/RNASeqBatchInfoCli.java index e391c056a5..fa4ad23b41 100644 --- a/gemma-cli/src/main/java/ubic/gemma/core/apps/RNASeqBatchInfoCli.java +++ b/gemma-cli/src/main/java/ubic/gemma/core/apps/RNASeqBatchInfoCli.java @@ -16,9 +16,8 @@ import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; -import ubic.gemma.core.analysis.preprocess.batcheffects.BatchInfoPopulationException; import ubic.gemma.core.analysis.preprocess.batcheffects.BatchInfoPopulationService; import ubic.gemma.model.expression.experiment.BioAssaySet; import ubic.gemma.model.expression.experiment.ExpressionExperiment; @@ -30,7 +29,6 @@ * @author tesar * @deprecated this should not be necessary and the regular batch population tool can be used instead. */ -@Component public class RNASeqBatchInfoCli extends ExpressionExperimentManipulatingCLI { @Autowired @@ -50,7 +48,7 @@ protected void buildOptions( Options options ) { } @Override - protected void processOptions( CommandLine commandLine ) { + protected void processOptions( CommandLine commandLine ) throws ParseException { super.processOptions( commandLine ); } diff --git a/gemma-cli/src/main/java/ubic/gemma/core/apps/RNASeqDataAddCli.java b/gemma-cli/src/main/java/ubic/gemma/core/apps/RNASeqDataAddCli.java index ac8104a9e0..74c0d17699 100644 --- a/gemma-cli/src/main/java/ubic/gemma/core/apps/RNASeqDataAddCli.java +++ b/gemma-cli/src/main/java/ubic/gemma/core/apps/RNASeqDataAddCli.java @@ -17,9 +17,9 @@ import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; import org.apache.commons.io.FileUtils; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; import ubic.basecode.dataStructure.matrix.DoubleMatrix; import ubic.basecode.io.reader.DoubleMatrixReader; import ubic.gemma.core.analysis.service.ExpressionDataFileService; @@ -45,7 +45,6 @@ * * @author Paul */ -@Component public class RNASeqDataAddCli extends ExpressionExperimentManipulatingCLI { private static final String ALLOW_MISSING = "allowMissing"; @@ -93,7 +92,7 @@ protected void buildOptions( Options options ) { } @Override - protected void processOptions( CommandLine commandLine ) { + protected void processOptions( CommandLine commandLine ) throws ParseException { super.processOptions( commandLine ); if ( commandLine.hasOption( "log2cpm" ) ) { diff --git a/gemma-cli/src/main/java/ubic/gemma/core/apps/ReplaceDataCli.java b/gemma-cli/src/main/java/ubic/gemma/core/apps/ReplaceDataCli.java index e325a708f2..346278bc8b 100644 --- a/gemma-cli/src/main/java/ubic/gemma/core/apps/ReplaceDataCli.java +++ b/gemma-cli/src/main/java/ubic/gemma/core/apps/ReplaceDataCli.java @@ -17,6 +17,7 @@ import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; import ubic.basecode.dataStructure.matrix.DoubleMatrix; import ubic.basecode.io.reader.DoubleMatrixReader; import ubic.gemma.core.apps.GemmaCLI.CommandGroup; @@ -55,7 +56,7 @@ protected void buildOptions( Options options ) { } @Override - protected void processOptions( CommandLine commandLine ) { + protected void processOptions( CommandLine commandLine ) throws ParseException { super.processOptions( commandLine ); this.file = commandLine.getOptionValue( "file" ); diff --git a/gemma-cli/src/main/java/ubic/gemma/core/apps/SplitExperimentCli.java b/gemma-cli/src/main/java/ubic/gemma/core/apps/SplitExperimentCli.java index 3cf06ac2b0..e08c318ad1 100644 --- a/gemma-cli/src/main/java/ubic/gemma/core/apps/SplitExperimentCli.java +++ b/gemma-cli/src/main/java/ubic/gemma/core/apps/SplitExperimentCli.java @@ -25,6 +25,7 @@ import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; import ubic.gemma.core.analysis.preprocess.SplitExperimentService; import ubic.gemma.core.analysis.preprocess.batcheffects.BatchInfoPopulationServiceImpl; import ubic.gemma.core.apps.GemmaCLI.CommandGroup; @@ -107,7 +108,7 @@ protected void doWork() throws Exception { } @Override - protected void processOptions( CommandLine commandLine ) { + protected void processOptions( CommandLine commandLine ) throws ParseException { super.processOptions( commandLine ); if ( !commandLine.hasOption( FACTOR_OPTION ) ) { throw new IllegalArgumentException( "Please specify the factor" ); diff --git a/gemma-cli/src/main/java/ubic/gemma/core/apps/TaxonLoaderCli.java b/gemma-cli/src/main/java/ubic/gemma/core/apps/TaxonLoaderCli.java index 3b91248d0c..6710645dfc 100644 --- a/gemma-cli/src/main/java/ubic/gemma/core/apps/TaxonLoaderCli.java +++ b/gemma-cli/src/main/java/ubic/gemma/core/apps/TaxonLoaderCli.java @@ -23,7 +23,7 @@ import ubic.gemma.core.apps.GemmaCLI.CommandGroup; import ubic.gemma.core.loader.genome.taxon.TaxonFetcher; import ubic.gemma.core.loader.genome.taxon.TaxonLoader; -import ubic.gemma.core.util.AbstractCLIContextCLI; +import ubic.gemma.core.util.AbstractAuthenticatedCLI; import ubic.gemma.model.common.description.LocalFile; import ubic.gemma.persistence.persister.PersisterHelper; @@ -32,7 +32,7 @@ /** * @author pavlidis */ -public class TaxonLoaderCli extends AbstractCLIContextCLI { +public class TaxonLoaderCli extends AbstractAuthenticatedCLI { @Override public String getCommandName() { @@ -55,7 +55,7 @@ protected void buildOptions( Options options ) { } @Override - protected void processOptions( CommandLine commandLine ) throws Exception { + protected void processOptions( CommandLine commandLine ) { } diff --git a/gemma-cli/src/main/java/ubic/gemma/core/apps/UpdatePubMedCli.java b/gemma-cli/src/main/java/ubic/gemma/core/apps/UpdatePubMedCli.java index f5998acf67..176fe7630a 100644 --- a/gemma-cli/src/main/java/ubic/gemma/core/apps/UpdatePubMedCli.java +++ b/gemma-cli/src/main/java/ubic/gemma/core/apps/UpdatePubMedCli.java @@ -17,16 +17,17 @@ import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.Options; import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; import ubic.gemma.core.annotation.reference.BibliographicReferenceService; import ubic.gemma.core.loader.entrez.pubmed.PubMedSearch; import ubic.gemma.core.loader.expression.geo.model.GeoRecord; import ubic.gemma.core.loader.expression.geo.service.GeoBrowser; -import ubic.gemma.core.util.AbstractCLIContextCLI; +import ubic.gemma.core.util.AbstractAuthenticatedCLI; import ubic.gemma.model.common.description.BibliographicReference; import ubic.gemma.model.common.description.DatabaseEntry; import ubic.gemma.model.common.description.ExternalDatabase; import ubic.gemma.model.expression.experiment.ExpressionExperiment; -import ubic.gemma.persistence.persister.Persister; +import ubic.gemma.persistence.persister.PersisterHelper; import ubic.gemma.persistence.service.expression.experiment.ExpressionExperimentService; import java.io.IOException; @@ -40,7 +41,11 @@ * Fetch their GEO records and check for pubmed IDs * Add the publications where we find them. */ -public class UpdatePubMedCli extends AbstractCLIContextCLI { +public class UpdatePubMedCli extends AbstractAuthenticatedCLI { + + @Autowired + private PersisterHelper persisterHelper; + @Override public String getCommandName() { return "findDatasetPubs"; @@ -124,7 +129,7 @@ protected void doWork() throws Exception { } @Override - protected void processOptions( CommandLine commandLine ) throws Exception { + protected void processOptions( CommandLine commandLine ) { } @@ -142,7 +147,6 @@ public GemmaCLI.CommandGroup getCommandGroup() { private BibliographicReference getBibliographicReference( String pubmedId ) { // check if it already in the system BibliographicReferenceService bibliographicReferenceService = this.getBean( BibliographicReferenceService.class ); - Persister persisterHelper = this.getPersisterHelper(); BibliographicReference publication = bibliographicReferenceService.findByExternalId( pubmedId ); if ( publication == null ) { PubMedSearch pms = new PubMedSearch(); diff --git a/gemma-cli/src/main/java/ubic/gemma/core/loader/association/phenotype/CtdDatabaseImporterCli.java b/gemma-cli/src/main/java/ubic/gemma/core/loader/association/phenotype/CtdDatabaseImporterCli.java index 0b23b66989..28c017f5fe 100644 --- a/gemma-cli/src/main/java/ubic/gemma/core/loader/association/phenotype/CtdDatabaseImporterCli.java +++ b/gemma-cli/src/main/java/ubic/gemma/core/loader/association/phenotype/CtdDatabaseImporterCli.java @@ -36,11 +36,6 @@ public class CtdDatabaseImporterCli extends ExternalDatabaseEvidenceImporterAbst // location of the ctd file private String ctdFile = ""; - @SuppressWarnings({ "unused", "WeakerAccess" }) // Possible external use - public CtdDatabaseImporterCli() throws Exception { - super(); - } - @Override public String getCommandName() { return "ctdDownload"; diff --git a/gemma-cli/src/main/java/ubic/gemma/core/loader/association/phenotype/DeleteEvidenceCLI.java b/gemma-cli/src/main/java/ubic/gemma/core/loader/association/phenotype/DeleteEvidenceCLI.java index 7a50984b04..8d8e89d91f 100644 --- a/gemma-cli/src/main/java/ubic/gemma/core/loader/association/phenotype/DeleteEvidenceCLI.java +++ b/gemma-cli/src/main/java/ubic/gemma/core/loader/association/phenotype/DeleteEvidenceCLI.java @@ -19,8 +19,8 @@ import org.apache.commons.cli.Options; import ubic.gemma.core.apps.GemmaCLI.CommandGroup; import ubic.gemma.core.association.phenotype.PhenotypeAssociationManagerService; +import ubic.gemma.core.util.AbstractAuthenticatedCLI; import ubic.gemma.core.util.AbstractCLI; -import ubic.gemma.core.util.AbstractCLIContextCLI; import ubic.gemma.model.association.phenotype.PhenotypeAssociation; import ubic.gemma.model.genome.gene.phenotype.valueObject.EvidenceValueObject; @@ -31,7 +31,7 @@ * * @author nicolas */ -public class DeleteEvidenceCLI extends AbstractCLIContextCLI { +public class DeleteEvidenceCLI extends AbstractAuthenticatedCLI { private String externalDatabaseName = ""; private PhenotypeAssociationManagerService phenotypeAssociationService = null; diff --git a/gemma-cli/src/main/java/ubic/gemma/core/loader/association/phenotype/EvidenceImporterAbstractCLI.java b/gemma-cli/src/main/java/ubic/gemma/core/loader/association/phenotype/EvidenceImporterAbstractCLI.java index ba75c82e58..ee135291e5 100755 --- a/gemma-cli/src/main/java/ubic/gemma/core/loader/association/phenotype/EvidenceImporterAbstractCLI.java +++ b/gemma-cli/src/main/java/ubic/gemma/core/loader/association/phenotype/EvidenceImporterAbstractCLI.java @@ -23,8 +23,8 @@ import ubic.gemma.core.association.phenotype.PhenotypeAssociationManagerService; import ubic.gemma.core.genome.gene.service.GeneService; import ubic.gemma.core.ontology.providers.MondoOntologyService; +import ubic.gemma.core.util.AbstractAuthenticatedCLI; import ubic.gemma.core.util.AbstractCLI; -import ubic.gemma.core.util.AbstractCLIContextCLI; import ubic.gemma.model.common.description.ExternalDatabaseValueObject; import ubic.gemma.model.genome.gene.phenotype.valueObject.EvidenceSourceValueObject; import ubic.gemma.persistence.service.genome.taxon.TaxonService; @@ -37,7 +37,7 @@ import java.util.*; @Deprecated -public abstract class EvidenceImporterAbstractCLI extends AbstractCLIContextCLI { +public abstract class EvidenceImporterAbstractCLI extends AbstractAuthenticatedCLI { static final String WRITE_FOLDER = Settings.getString( "gemma.appdata.home" ) + File.separator + "EvidenceImporterNeurocarta"; diff --git a/gemma-cli/src/main/java/ubic/gemma/core/loader/association/phenotype/EvidenceImporterCLI.java b/gemma-cli/src/main/java/ubic/gemma/core/loader/association/phenotype/EvidenceImporterCLI.java index 480ff1b63a..7b49d5303d 100755 --- a/gemma-cli/src/main/java/ubic/gemma/core/loader/association/phenotype/EvidenceImporterCLI.java +++ b/gemma-cli/src/main/java/ubic/gemma/core/loader/association/phenotype/EvidenceImporterCLI.java @@ -21,6 +21,7 @@ import ubic.gemma.core.apps.GemmaCLI.CommandGroup; import ubic.gemma.core.association.phenotype.EntityNotFoundException; import ubic.gemma.core.util.AbstractCLI; +import ubic.gemma.model.common.description.CharacteristicValueObject; import ubic.gemma.model.common.description.CitationValueObject; import ubic.gemma.model.genome.Gene; import ubic.gemma.model.genome.gene.phenotype.valueObject.*; @@ -38,7 +39,7 @@ * * @author nicolas */ -@SuppressWarnings({"unused", "WeakerAccess"}) // Possible external use +@SuppressWarnings({ "unused", "WeakerAccess" }) // Possible external use public class EvidenceImporterCLI extends EvidenceImporterAbstractCLI { @Override @@ -254,7 +255,7 @@ private void createImportLog( EvidenceValueObject evidenceValueObject ) { } private Set experimentTags2Ontology( Set values, String category, - String categoryUri, OntologyService ontologyUsed ) throws OntologySearchException { + String categoryUri, OntologyService ontologyUsed ) throws OntologySearchException { Set experimentTags = new HashSet<>(); @@ -271,7 +272,7 @@ private Set experimentTags2Ontology( Set valu } } - CharacteristicValueObject c = new CharacteristicValueObject( -1L, term, category, valueUri, categoryUri ); + CharacteristicValueObject c = new CharacteristicValueObject( term, valueUri, category, categoryUri ); experimentTags.add( c ); } return experimentTags; @@ -472,7 +473,7 @@ private void populateCommonFields( EvidenceValueObject evidence, String[] tok */ @SuppressWarnings("StatementWithEmptyBody") // Better readability private void setScoreDependingOnExternalSource( String externalDatabaseName, EvidenceValueObject evidence, - String evidenceTaxon ) { + String evidenceTaxon ) { // OMIM got special character in description to find score if ( externalDatabaseName.equalsIgnoreCase( "OMIM" ) ) { @@ -552,7 +553,7 @@ private SortedSet toValuesUri( Set phenotypes String valueUri = this.phenotype2Ontology( phenotype ); if ( valueUri != null ) { - CharacteristicValueObject c = new CharacteristicValueObject( -1L, valueUri ); + CharacteristicValueObject c = new CharacteristicValueObject( "", valueUri ); characteristicPhenotypes.add( c ); } } diff --git a/gemma-cli/src/main/java/ubic/gemma/core/loader/association/phenotype/ExternalDatabaseEvidenceImporterAbstractCLI.java b/gemma-cli/src/main/java/ubic/gemma/core/loader/association/phenotype/ExternalDatabaseEvidenceImporterAbstractCLI.java index 07a0ab78c0..b9d1b52075 100644 --- a/gemma-cli/src/main/java/ubic/gemma/core/loader/association/phenotype/ExternalDatabaseEvidenceImporterAbstractCLI.java +++ b/gemma-cli/src/main/java/ubic/gemma/core/loader/association/phenotype/ExternalDatabaseEvidenceImporterAbstractCLI.java @@ -14,20 +14,19 @@ */ package ubic.gemma.core.loader.association.phenotype; -import ubic.basecode.ontology.providers.DiseaseOntologyService; import ubic.basecode.ontology.providers.HumanPhenotypeOntologyService; import ubic.basecode.ontology.providers.MedicOntologyService; import ubic.gemma.core.apps.GemmaCLI.CommandGroup; import ubic.gemma.core.genome.gene.service.GeneService; import ubic.gemma.core.ontology.providers.MondoOntologyService; -import ubic.gemma.core.util.AbstractCLIContextCLI; +import ubic.gemma.core.util.AbstractAuthenticatedCLI; import ubic.gemma.persistence.service.genome.taxon.TaxonService; /** * @author nicolas */ @Deprecated -public abstract class ExternalDatabaseEvidenceImporterAbstractCLI extends AbstractCLIContextCLI { +public abstract class ExternalDatabaseEvidenceImporterAbstractCLI extends AbstractAuthenticatedCLI { protected String writeFolder = null; diff --git a/gemma-cli/src/main/java/ubic/gemma/core/loader/association/phenotype/LoadEvidenceForClassifier.java b/gemma-cli/src/main/java/ubic/gemma/core/loader/association/phenotype/LoadEvidenceForClassifier.java index c146e70fd7..f1bc663678 100644 --- a/gemma-cli/src/main/java/ubic/gemma/core/loader/association/phenotype/LoadEvidenceForClassifier.java +++ b/gemma-cli/src/main/java/ubic/gemma/core/loader/association/phenotype/LoadEvidenceForClassifier.java @@ -19,7 +19,7 @@ import org.springframework.core.io.ClassPathResource; import ubic.gemma.core.annotation.reference.BibliographicReferenceService; import ubic.gemma.core.apps.GemmaCLI.CommandGroup; -import ubic.gemma.core.util.AbstractCLIContextCLI; +import ubic.gemma.core.util.AbstractAuthenticatedCLI; import ubic.gemma.model.common.description.BibliographicReference; import ubic.gemma.model.common.description.DatabaseEntry; import ubic.gemma.model.common.description.MedicalSubjectHeading; @@ -34,7 +34,7 @@ * * @author nicolas */ -public class LoadEvidenceForClassifier extends AbstractCLIContextCLI { +public class LoadEvidenceForClassifier extends AbstractAuthenticatedCLI { // a monthly dump of all evidence, takes too long to all, use files auto-generated private final String evidenceDumpPath = @@ -64,7 +64,7 @@ protected void buildOptions( Options options ) { } @Override - protected void processOptions( CommandLine commandLine ) throws Exception { + protected void processOptions( CommandLine commandLine ) { } diff --git a/gemma-cli/src/main/java/ubic/gemma/core/loader/association/phenotype/OmimDatabaseImporterCli.java b/gemma-cli/src/main/java/ubic/gemma/core/loader/association/phenotype/OmimDatabaseImporterCli.java index f2d72581cf..847bc4c1c1 100644 --- a/gemma-cli/src/main/java/ubic/gemma/core/loader/association/phenotype/OmimDatabaseImporterCli.java +++ b/gemma-cli/src/main/java/ubic/gemma/core/loader/association/phenotype/OmimDatabaseImporterCli.java @@ -70,7 +70,7 @@ protected void buildOptions( Options options ) { } @Override - protected void processOptions( CommandLine commandLine ) throws Exception { + protected void processOptions( CommandLine commandLine ) { } diff --git a/gemma-cli/src/main/java/ubic/gemma/core/loader/entrez/pubmed/PubMedSearcher.java b/gemma-cli/src/main/java/ubic/gemma/core/loader/entrez/pubmed/PubMedSearcher.java index 0a5bab4af1..c1c67df11f 100644 --- a/gemma-cli/src/main/java/ubic/gemma/core/loader/entrez/pubmed/PubMedSearcher.java +++ b/gemma-cli/src/main/java/ubic/gemma/core/loader/entrez/pubmed/PubMedSearcher.java @@ -20,9 +20,11 @@ import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.Options; +import org.springframework.beans.factory.annotation.Autowired; import ubic.gemma.core.apps.GemmaCLI.CommandGroup; -import ubic.gemma.core.util.AbstractCLIContextCLI; +import ubic.gemma.core.util.AbstractAuthenticatedCLI; import ubic.gemma.model.common.description.BibliographicReference; +import ubic.gemma.persistence.persister.PersisterHelper; import java.util.Collection; @@ -31,8 +33,10 @@ * * @author pavlidis */ -public class PubMedSearcher extends AbstractCLIContextCLI { +public class PubMedSearcher extends AbstractAuthenticatedCLI { private static final PubMedSearch pms = new PubMedSearch(); + @Autowired + private PersisterHelper persisterHelper; private Collection args; private boolean persist = false; @@ -70,7 +74,7 @@ protected void doWork() throws Exception { System.out.println( refs.size() + " references found" ); if ( this.persist ) { - this.getPersisterHelper().persist( refs ); + persisterHelper.persist( refs ); } } diff --git a/gemma-cli/src/main/java/ubic/gemma/core/util/AbstractSpringAwareCLI.java b/gemma-cli/src/main/java/ubic/gemma/core/util/AbstractAuthenticatedCLI.java similarity index 57% rename from gemma-cli/src/main/java/ubic/gemma/core/util/AbstractSpringAwareCLI.java rename to gemma-cli/src/main/java/ubic/gemma/core/util/AbstractAuthenticatedCLI.java index 54c0439fa4..2caf470887 100644 --- a/gemma-cli/src/main/java/ubic/gemma/core/util/AbstractSpringAwareCLI.java +++ b/gemma-cli/src/main/java/ubic/gemma/core/util/AbstractAuthenticatedCLI.java @@ -20,41 +20,33 @@ import gemma.gsec.authentication.ManualAuthenticationService; import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.ParseException; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; -import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.concurrent.DelegatingSecurityContextCallable; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; -import ubic.gemma.model.common.Auditable; -import ubic.gemma.model.common.auditAndSecurity.AuditEvent; -import ubic.gemma.model.common.auditAndSecurity.curation.Curatable; -import ubic.gemma.model.common.auditAndSecurity.eventType.AuditEventType; -import ubic.gemma.model.expression.arrayDesign.ArrayDesign; -import ubic.gemma.model.expression.experiment.ExpressionExperiment; -import ubic.gemma.persistence.persister.Persister; -import ubic.gemma.persistence.service.common.auditAndSecurity.AuditEventService; -import ubic.gemma.persistence.service.common.auditAndSecurity.AuditTrailService; -import ubic.gemma.persistence.service.expression.experiment.ExpressionExperimentService; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.util.Collection; -import java.util.Date; import java.util.List; import java.util.concurrent.Callable; import java.util.stream.Collectors; /** - * Subclass this to create command line interface (CLI) tools that need a Spring context. A standard set of CLI options - * are provided to manage authentication. - * + * Subclass this to create command line interface (CLI) tools that need authentication. + *

+ * Credentials may be supplied via the environment using the {@code $GEMMMA_USERNAME} and {@code $GEMMA_PASSWORD} + * variables. A more secure {@code $GEMMA_PASSWORD_CMD} variable can be used to specify a command that produces the + * password. If no environment variables are supplied, they will be prompted if the standard input is attached to a + * console (i.e tty). * @author pavlidis */ -public abstract class AbstractSpringAwareCLI extends AbstractCLI { +public abstract class AbstractAuthenticatedCLI extends AbstractCLI { /** * Environment variable used to store the username (if not passed directly to the CLI). @@ -71,24 +63,8 @@ public abstract class AbstractSpringAwareCLI extends AbstractCLI { */ private static final String PASSWORD_CMD_ENV = "GEMMA_PASSWORD_CMD"; - @Autowired - private BeanFactory ctx; @Autowired private ManualAuthenticationService manAuthentication; - @Autowired - protected AuditTrailService auditTrailService; - @Autowired - protected AuditEventService auditEventService; - @Autowired - private ExpressionExperimentService ees; - @Autowired - private Persister persisterHelper; - - @SuppressWarnings({ "unused", "WeakerAccess" }) // Possible external use - @Autowired - public AbstractSpringAwareCLI() { - super(); - } @Override public String getShortDesc() { @@ -97,7 +73,7 @@ public String getShortDesc() { /** * Indicate if the command requires authentication. - * + *

* Override this to return true to make authentication required. * * @return true if login is required, otherwise false @@ -110,99 +86,11 @@ protected boolean requireLogin() { * You must override this method to process any options you added. */ @Override - protected void processStandardOptions( CommandLine commandLine ) { + protected void processStandardOptions( CommandLine commandLine ) throws ParseException { super.processStandardOptions( commandLine ); this.authenticate(); } - /** - * Convenience method to obtain instance of any bean by name. - * - * @deprecated Use {@link Autowired} to specify your dependencies, this is just a wrapper around the current - * {@link BeanFactory}. - * - * @param the bean class type - * @param clz class - * @param name name - * @return bean - */ - @SuppressWarnings("SameParameterValue") // Better for general use - @Deprecated - protected T getBean( String name, Class clz ) { - assert ctx != null : "Spring context was not initialized"; - return ctx.getBean( name, clz ); - } - - @Deprecated - protected T getBean( Class clz ) { - assert ctx != null : "Spring context was not initialized"; - return ctx.getBean( clz ); - } - - @Deprecated - protected Persister getPersisterHelper() { - return persisterHelper; - } - - /** - * @param auditable auditable - * @param eventClass can be null - * @return boolean - */ - protected boolean noNeedToRun( Auditable auditable, Class eventClass ) { - boolean needToRun = true; - Date skipIfLastRunLaterThan = this.getLimitingDate(); - List events = this.auditEventService.getEvents( auditable ); - - boolean okToRun = true; // assume okay unless indicated otherwise - - // figure out if we need to run it by date; or if there is no event of the given class; "Fail" type events don't - // count. - for ( int j = events.size() - 1; j >= 0; j-- ) { - AuditEvent event = events.get( j ); - if ( event == null ) { - continue; // legacy of ordered-list which could end up with gaps; should not be needed any more - } - AuditEventType eventType = event.getEventType(); - if ( eventType != null && eventClass != null && eventClass.isAssignableFrom( eventType.getClass() ) - && !eventType.getClass().getSimpleName().startsWith( "Fail" ) ) { - if ( skipIfLastRunLaterThan != null ) { - if ( event.getDate().after( skipIfLastRunLaterThan ) ) { - AbstractCLI.log.info( auditable + ": " + " run more recently than " + skipIfLastRunLaterThan ); - addErrorObject( auditable, "Run more recently than " + skipIfLastRunLaterThan ); - needToRun = false; - } - } else { - needToRun = false; // it has been run already at some point - } - } - } - - /* - * Always skip if the object is curatable and troubled - */ - if ( auditable instanceof Curatable ) { - Curatable curatable = ( Curatable ) auditable; - okToRun = !curatable.getCurationDetails().getTroubled(); //not ok if troubled - - // special case for expression experiments - check associated ADs. - if ( okToRun && curatable instanceof ExpressionExperiment ) { - for ( ArrayDesign ad : ees.getArrayDesignsUsed( ( ExpressionExperiment ) auditable ) ) { - if ( ad.getCurationDetails().getTroubled() ) { - okToRun = false; // not ok if even one parent AD is troubled, no need to check the remaining ones. - break; - } - } - } - - if ( !okToRun ) { - addErrorObject( auditable, "Has an active 'trouble' flag" ); - } - } - - return !needToRun || !okToRun; - } - /** * check username and password. */ diff --git a/gemma-cli/src/main/java/ubic/gemma/core/util/AbstractCLI.java b/gemma-cli/src/main/java/ubic/gemma/core/util/AbstractCLI.java index f7b5899139..c503b22d2d 100644 --- a/gemma-cli/src/main/java/ubic/gemma/core/util/AbstractCLI.java +++ b/gemma-cli/src/main/java/ubic/gemma/core/util/AbstractCLI.java @@ -20,298 +20,345 @@ import lombok.Value; import org.apache.commons.cli.*; +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVPrinter; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.commons.lang3.time.StopWatch; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.annotation.Autowired; import ubic.basecode.util.DateUtil; import ubic.gemma.model.common.auditAndSecurity.eventType.AuditEventType; import javax.annotation.Nullable; -import java.io.File; -import java.io.PrintWriter; +import java.io.*; +import java.nio.file.Files; import java.util.*; import java.util.concurrent.*; +import java.util.stream.Collectors; /** - * Base Command Line Interface. Provides some default functionality. + * Basic implementation of the {@link CLI} interface. *

- * To use this, in your concrete subclass, implement a main method. You must implement buildOptions and processOptions - * to handle any application-specific options (they can be no-ops). + * To use this, in your concrete subclass, implement {@link #buildOptions} and {@link #processOptions} to handle any + * application-specific options (they can be no-ops) and {@link #doWork()} to perform the actual work of the CLI. *

- * To facilitate testing of your subclass, your main method must call a non-static 'doWork' method, that will be exposed - * for testing. In that method call processCommandline. You should return any non-null return value from - * processCommandLine. - * + * Use {@link AbstractAuthenticatedCLI} if you need to authenticate the user. * @author pavlidis */ -@SuppressWarnings({ "unused", "WeakerAccess" }) // Possible external use public abstract class AbstractCLI implements CLI { + protected static final Log log = LogFactory.getLog( AbstractCLI.class ); + /** - * Exit code used for a successful doWork execution. + * Exit code used for a successful {@link #doWork} execution. */ - public static final int SUCCESS = 0; + protected static final int SUCCESS = 0; /** - * Exit code used for a failed doWork execution. + * Exit code used for a failed {@link #doWork} execution. */ - public static final int FAILURE = 1; + protected static final int FAILURE = 1; /** * Exit code used for a successful doWork execution that resulted in failed error objects. */ - public static final int FAILURE_FROM_ERROR_OBJECTS = 1; + protected static final int FAILURE_FROM_ERROR_OBJECTS = 1; + /** + * Exit code used when a {@link #doWork()} is aborted. + */ + protected static final int ABORTED = 2; - public static final String FOOTER = "The Gemma project, Copyright (c) 2007-2023 University of British Columbia."; - protected static final String AUTO_OPTION_NAME = "auto"; - protected static final String THREADS_OPTION = "threads"; - protected static final Log log = LogFactory.getLog( AbstractCLI.class ); - private static final int DEFAULT_PORT = 3306; public static final String HEADER = "Options:"; - private static final String HOST_OPTION = "H"; - private static final String PORT_OPTION = "P"; + public static final String FOOTER = "The Gemma project, Copyright (c) 2007-2021 University of British Columbia."; + + private static final String AUTO_OPTION_NAME = "auto"; + private static final String THREADS_OPTION = "threads"; private static final String HELP_OPTION = "h"; private static final String TESTING_OPTION = "testing"; private static final String DATE_OPTION = "mdate"; + private static final String BATCH_FORMAT_OPTION = "batchFormat"; + private static final String BATCH_OUTPUT_FILE_OPTION = "batchOutputFile"; + + @Autowired + private BeanFactory ctx; + + /** + * Indicate if this CLI allows positional arguments. + */ + private boolean allowPositionalArguments = false; /* support for convenience options */ - private final String DEFAULT_HOST = "localhost"; /** * Automatically identify which entities to run the tool on. To enable call addAutoOption. */ - protected boolean autoSeek = false; + private boolean autoSeek; /** * The event type to look for the lack of, when using auto-seek. */ - protected Class autoSeekEventType = null; + private Class autoSeekEventType; /** * Date used to identify which entities to run the tool on (e.g., those which were run less recently than mDate). To * enable call addDateOption. */ - protected String mDate = null; - protected int numThreads = 1; - protected String host = DEFAULT_HOST; - protected int port = AbstractCLI.DEFAULT_PORT; + private String mDate; + /** + * Number of threads to use for batch processing. + */ + private int numThreads; + /** + * Format to use to summarize batch processing. + */ + private BatchFormat batchFormat; + /** + * Destination for batch processing summary. + */ + @Nullable + private File batchOutputFile; private ExecutorService executorService; - // hold the results of the command execution - // needs to be concurrently modifiable and kept in-order - private final List errorObjects = Collections.synchronizedList( new ArrayList<>() ); - private final List successObjects = Collections.synchronizedList( new ArrayList<>() ); + /** + * Hold the results of the command execution + * needs to be concurrently modifiable and kept in-order + */ + private final List batchProcessingResults = Collections.synchronizedList( new ArrayList<>() ); /** - * Run the command. - *

- * Parse and process CLI arguments, invoke the command doWork implementation, and print basic statistics about time - * usage. + * Convenience method to obtain instance of any bean by name. * - * @param args Arguments to pass to {@link #processCommandLine(Options, String[])} - * @return Exit code intended to be used with {@link System#exit(int)} to indicate a success or failure to the - * end-user. Any exception raised by doWork results in a value of {@link #FAILURE}, and any error set in the - * internal error objects will result in a value of {@link #FAILURE_FROM_ERROR_OBJECTS}. + * @deprecated Use {@link Autowired} to specify your dependencies, this is just a wrapper around the current + * {@link BeanFactory}. + * + * @param the bean class type + * @param clz class + * @param name name + * @return bean + */ + @SuppressWarnings("SameParameterValue") // Better for general use + @Deprecated + protected T getBean( String name, Class clz ) { + assert ctx != null : "Spring context was not initialized"; + return ctx.getBean( name, clz ); + } + + @Deprecated + protected T getBean( Class clz ) { + assert ctx != null : "Spring context was not initialized"; + return ctx.getBean( clz ); + } + + /** + * {@inheritDoc} + *

+ * Parse and process CLI arguments, invoke the command {@link #doWork()} implementation, and print basic statistics + * about time usage. + *

+ * Any exception raised by doWork results in a value of {@link #FAILURE}, and any error set in the internal error + * objects will result in a value of {@link #FAILURE_FROM_ERROR_OBJECTS}. */ @Override - public int executeCommand( String[] args ) { - StopWatch watch = new StopWatch(); - watch.start(); - if ( args == null ) { - System.err.println( "No arguments" ); - return FAILURE; - } + public int executeCommand( String... args ) { + Options options = new Options(); + buildStandardOptions( options ); + buildOptions( options ); try { - Options options = new Options(); - buildStandardOptions( options ); - buildOptions( options ); DefaultParser parser = new DefaultParser(); - CommandLine commandLine; - try { - commandLine = parser.parse( options, args ); - } catch ( ParseException e ) { - if ( e instanceof MissingOptionException ) { - // check if -h/--help was passed alongside a required argument - if ( ArrayUtils.contains( args, "-h" ) || ArrayUtils.contains( args, "--help" ) ) { - printHelp( options ); - return SUCCESS; - } - System.err.println( "Required option(s) were not supplied: " + e.getMessage() ); - } else if ( e instanceof AlreadySelectedException ) { - System.err.println( "The option(s) " + e.getMessage() + " were already selected" ); - } else if ( e instanceof MissingArgumentException ) { - System.err.println( "Missing argument: " + e.getMessage() ); - } else if ( e instanceof UnrecognizedOptionException ) { - System.err.println( "Unrecognized option: " + e.getMessage() ); - } - printHelp( options ); - return FAILURE; - } + CommandLine commandLine = parser.parse( options, args ); // check if -h/--help is provided before pursuing option processing if ( commandLine.hasOption( HELP_OPTION ) ) { - printHelp( options ); + printHelp( options, new PrintWriter( System.out, true ) ); return SUCCESS; } if ( commandLine.hasOption( TESTING_OPTION ) ) { - AbstractCLI.log.error( String.format( "The -testing/--testing option must be passed before the %s command.", getCommandName() ) ); - return FAILURE; + throw new UnrecognizedOptionException( String.format( "The -testing/--testing option must be passed before the %s command.", getCommandName() ) ); + } + if ( !allowPositionalArguments && !commandLine.getArgList().isEmpty() ) { + throw new UnrecognizedOptionException( "The command line does not allow positional arguments." ); } processStandardOptions( commandLine ); processOptions( commandLine ); + } catch ( ParseException e ) { + if ( e instanceof MissingOptionException ) { + // check if -h/--help was passed alongside a required argument + if ( ArrayUtils.contains( args, "-h" ) || ArrayUtils.contains( args, "--help" ) ) { + printHelp( options, new PrintWriter( System.out, true ) ); + return SUCCESS; + } + System.err.println( "Required option(s) were not supplied: " + e.getMessage() ); + } else if ( e instanceof AlreadySelectedException ) { + System.err.println( "The option(s) " + e.getMessage() + " were already selected" ); + } else if ( e instanceof MissingArgumentException ) { + System.err.println( "Missing argument: " + e.getMessage() ); + } else if ( e instanceof UnrecognizedOptionException ) { + System.err.println( "Unrecognized option: " + e.getMessage() ); + } else { + System.err.println( e.getMessage() ); + } + printHelp( options, new PrintWriter( System.err, true ) ); + return FAILURE; + } + StopWatch watch = StopWatch.createStarted(); + try { doWork(); - return errorObjects.isEmpty() ? SUCCESS : FAILURE_FROM_ERROR_OBJECTS; + return batchProcessingResults.stream().noneMatch( BatchProcessingResult::isError ) ? SUCCESS : FAILURE_FROM_ERROR_OBJECTS; + } catch ( WorkAbortedException e ) { + log.warn( "Operation was aborted by the current user." ); + return ABORTED; } catch ( Exception e ) { log.error( String.format( "%s failed: %s", getCommandName(), ExceptionUtils.getRootCauseMessage( e ) ), e ); return FAILURE; } finally { // always summarize processing, even if an error is thrown - summarizeProcessing(); + summarizeBatchProcessing(); log.info( String.format( "Elapsed time: %d seconds.", watch.getTime( TimeUnit.SECONDS ) ) ); } } + private void printHelp( Options options, PrintWriter writer ) { + new HelpFormatter().printHelp( writer, 150, + this.getCommandName() + " [options]", + this.getShortDesc() + "\n" + AbstractCLI.HEADER, + options, HelpFormatter.DEFAULT_LEFT_PAD, HelpFormatter.DEFAULT_DESC_PAD, AbstractCLI.FOOTER ); + } + /** - * You must implement the handling for this option. + * Add the {@code -auto} option. + *

+ * The auto option value can be retrieved with {@link #isAutoSeek()}. */ protected void addAutoOption( Options options ) { - Option autoSeekOption = Option.builder( AUTO_OPTION_NAME ) + options.addOption( Option.builder( AUTO_OPTION_NAME ) .desc( "Attempt to process entities that need processing based on workflow criteria." ) - .build(); - - options.addOption( autoSeekOption ); + .build() ); } - protected void addDateOption( Options options ) { - Option dateOption = Option.builder( DATE_OPTION ).hasArg().desc( - "Constrain to run only on entities with analyses older than the given date. " - + "For example, to run only on entities that have not been analyzed in the last 10 days, use '-10d'. " - + "If there is no record of when the analysis was last run, it will be run." ) - .build(); - - options.addOption( dateOption ); + /** + * Add the {@code -auto} option for a specific {@link AuditEventType}. + *

+ * The event type can be retrieved with {@link #getAutoSeekEventType()}. + */ + protected void addAutoOption( Options options, Class autoSeekEventType ) { + addAutoOption( options ); + this.autoSeekEventType = autoSeekEventType; } /** - * Convenience method to add a standard pair of options to intake a host name and port number. * - * - * @param hostRequired Whether the host name is required - * @param portRequired Whether the port is required + * Add the {@code -mdate} option. + *

+ * The limiting date can be retrieved with {@link #getLimitingDate()}. */ - protected void addHostAndPortOptions( Options options, boolean hostRequired, boolean portRequired ) { - Option hostOpt = Option.builder( HOST_OPTION ).argName( "host name" ).longOpt( "host" ).hasArg() - .desc( "Hostname to use (Default = " + DEFAULT_HOST + ")" ) - .build(); - - hostOpt.setRequired( hostRequired ); - - Option portOpt = Option.builder( PORT_OPTION ).argName( "port" ).longOpt( "port" ).hasArg() - .desc( "Port to use on host (Default = " + AbstractCLI.DEFAULT_PORT + ")" ) - .build(); - - portOpt.setRequired( portRequired ); - - options.addOption( hostOpt ); - options.addOption( portOpt ); + protected void addDateOption( Options options ) { + options.addOption( Option.builder( DATE_OPTION ).hasArg() + .desc( "Constrain to run only on entities with analyses older than the given date. " + + "For example, to run only on entities that have not been analyzed in the last 10 days, use '-10d'. " + + "If there is no record of when the analysis was last run, it will be run." ) + .build() ); } /** - * Convenience method to add an option for parallel processing option. + * Add the {@code -threads} option. + *

+ * This is used to configure the internal batch processing thread pool which can be used with + * {@link #executeBatchTasks(Collection)}. You may also use {@link #getNumThreads()} to retrieve the number of + * threads to use. */ protected void addThreadsOption( Options options ) { - Option threadsOpt = Option.builder( THREADS_OPTION ).argName( "numThreads" ).hasArg() + options.addOption( Option.builder( THREADS_OPTION ).argName( "numThreads" ).hasArg() .desc( "Number of threads to use for batch processing." ) - .build(); - options.addOption( threadsOpt ); + .type( Integer.class ) + .build() ); } /** - * Build option implementation. + * Allow positional arguments to be specified. The default is false and an error will be produced if positional + * arguments are supplied by the user. *

- * Implement this method to add options to your command line, using the OptionBuilder. - *

- * This is called right after {@link #buildStandardOptions(Options)} so the options will be added after standard options. + * Those arguments can be retrieved in {@link #processOptions(CommandLine)} by using {@link CommandLine#getArgList()}. */ - protected abstract void buildOptions( Options options ); + @SuppressWarnings("unused") + protected void setAllowPositionalArguments( boolean allowPositionalArguments ) { + this.allowPositionalArguments = allowPositionalArguments; + } + + protected boolean isAutoSeek() { + return autoSeek; + } + + protected Class getAutoSeekEventType() { + return autoSeekEventType; + } + + protected int getNumThreads() { + return numThreads; + } + + protected Date getLimitingDate() { + Date skipIfLastRunLaterThan = null; + if ( StringUtils.isNotBlank( mDate ) ) { + skipIfLastRunLaterThan = DateUtil.getRelativeDate( new Date(), mDate ); + AbstractCLI.log.info( "Analyses will be run only if last was older than " + skipIfLastRunLaterThan ); + } + return skipIfLastRunLaterThan; + } protected void buildStandardOptions( Options options ) { AbstractCLI.log.debug( "Creating standard options" ); options.addOption( HELP_OPTION, "help", false, "Print this message" ); options.addOption( TESTING_OPTION, "testing", false, "Use the test environment. This option must be passed before the command." ); + options.addOption( BATCH_FORMAT_OPTION, true, "Format to use to the batch summary" ); + options.addOption( Option.builder( BATCH_OUTPUT_FILE_OPTION ).hasArg().type( File.class ).desc( "Output file to use for the batch summary (default is standard output)" ).build() ); } /** - * Command line implementation. + * Build option implementation. *

- * This is called after {@link #buildOptions(Options)} and {@link #processOptions(CommandLine)}, so the implementation can assume that - * all its arguments have already been initialized. - * - * @throws Exception in case of unrecoverable failure, an exception is thrown and will result in a {@link #FAILURE} - * exit code, otherwise use {@link #addErrorObject} + * Implement this method to add options to your command line, using the OptionBuilder. + *

+ * This is called right after {@link #buildStandardOptions(Options)} so the options will be added after standard options. */ - protected abstract void doWork() throws Exception; - - protected final double getDoubleOptionValue( CommandLine commandLine, char option ) { - try { - return Double.parseDouble( commandLine.getOptionValue( option ) ); - } catch ( NumberFormatException e ) { - throw new RuntimeException( this.invalidOptionString( commandLine, String.valueOf( option ) ) + ", not a valid double", e ); - } - } + protected abstract void buildOptions( Options options ); - protected final double getDoubleOptionValue( CommandLine commandLine, String option ) { - try { - return Double.parseDouble( commandLine.getOptionValue( option ) ); - } catch ( NumberFormatException e ) { - throw new RuntimeException( this.invalidOptionString( commandLine, option ) + ", not a valid double", e ); + /** + * Somewhat annoying: This causes subclasses to be unable to safely use 'h', 'p', 'u' and 'P' etc. for their own + * purposes. + */ + protected void processStandardOptions( CommandLine commandLine ) throws ParseException { + if ( commandLine.hasOption( DATE_OPTION ) ^ commandLine.hasOption( AbstractCLI.AUTO_OPTION_NAME ) ) { + throw new IllegalArgumentException( String.format( "Please only select one of -%s or -%s", DATE_OPTION, AUTO_OPTION_NAME ) ); } - } - protected final String getFileNameOptionValue( CommandLine commandLine, char c ) { - String fileName = commandLine.getOptionValue( c ); - File f = new File( fileName ); - if ( !f.canRead() ) { - throw new RuntimeException( this.invalidOptionString( commandLine, String.valueOf( c ) ) + ", cannot read from file" ); + if ( commandLine.hasOption( DATE_OPTION ) ) { + this.mDate = commandLine.getOptionValue( DATE_OPTION ); } - return fileName; - } - protected final String getFileNameOptionValue( CommandLine commandLine, String c ) { - String fileName = commandLine.getOptionValue( c ); - File f = new File( fileName ); - if ( !f.canRead() ) { - throw new RuntimeException( this.invalidOptionString( commandLine, c ) + ", cannot read from file" ); - } - return fileName; - } + this.autoSeek = commandLine.hasOption( AbstractCLI.AUTO_OPTION_NAME ); - protected final int getIntegerOptionValue( CommandLine commandLine, char option ) { - try { - return Integer.parseInt( commandLine.getOptionValue( option ) ); - } catch ( NumberFormatException e ) { - throw new RuntimeException( this.invalidOptionString( commandLine, String.valueOf( option ) ) + ", not a valid integer", e ); + if ( commandLine.hasOption( THREADS_OPTION ) ) { + this.numThreads = ( Integer ) commandLine.getParsedOptionValue( THREADS_OPTION ); + if ( this.numThreads < 1 ) { + throw new IllegalArgumentException( "Number of threads must be greater than 1." ); + } + } else { + this.numThreads = 1; } - } - protected final int getIntegerOptionValue( CommandLine commandLine, String option ) { - try { - return Integer.parseInt( commandLine.getOptionValue( option ) ); - } catch ( NumberFormatException e ) { - throw new RuntimeException( this.invalidOptionString( commandLine, option ) + ", not a valid integer", e ); + if ( this.numThreads > 1 ) { + this.executorService = Executors.newFixedThreadPool( this.numThreads ); + } else { + this.executorService = Executors.newSingleThreadExecutor(); } - } - protected Date getLimitingDate() { - Date skipIfLastRunLaterThan = null; - if ( StringUtils.isNotBlank( mDate ) ) { - skipIfLastRunLaterThan = DateUtil.getRelativeDate( new Date(), mDate ); - AbstractCLI.log.info( "Analyses will be run only if last was older than " + skipIfLastRunLaterThan ); + if ( commandLine.hasOption( BATCH_FORMAT_OPTION ) ) { + try { + this.batchFormat = BatchFormat.valueOf( commandLine.getOptionValue( BATCH_FORMAT_OPTION ).toUpperCase() ); + } catch ( IllegalArgumentException e ) { + throw new ParseException( String.format( "Unsupported batch format: %s.", commandLine.getOptionValue( BATCH_FORMAT_OPTION ) ) ); + } + } else { + this.batchFormat = commandLine.hasOption( BATCH_OUTPUT_FILE_OPTION ) ? BatchFormat.TSV : BatchFormat.TEXT; } - return skipIfLastRunLaterThan; - } - - protected void printHelp( Options options ) { - new HelpFormatter().printHelp( new PrintWriter( System.err, true ), 150, - this.getCommandName() + " [options]", - this.getShortDesc() + "\n" + AbstractCLI.HEADER, - options, HelpFormatter.DEFAULT_LEFT_PAD, HelpFormatter.DEFAULT_DESC_PAD, AbstractCLI.FOOTER ); + this.batchOutputFile = ( File ) commandLine.getParsedOptionValue( BATCH_OUTPUT_FILE_OPTION ); } /** @@ -319,35 +366,61 @@ protected void printHelp( Options options ) { *

* Implement this to provide processing of options. It is called after {@link #buildOptions(Options)} and right before * {@link #doWork()}. + * @throws ParseException in case of unrecoverable failure (i.e. missing option or invalid value), an exception can + * be raised and will result in an exit code of {@link #FAILURE}. + */ + protected abstract void processOptions( CommandLine commandLine ) throws ParseException; + + /** + * Command line implementation. + *

+ * This is called after {@link #buildOptions(Options)} and {@link #processOptions(CommandLine)}, so the + * implementation can assume that all its arguments have already been initialized. * - * @throws Exception in case of unrecoverable failure (i.e. missing option or invalid value), an exception can be - * raised and will result in an exit code of {@link #FAILURE}. + * @throws Exception in case of unrecoverable failure, an exception is thrown and will result in a + * {@link #FAILURE} exit code, otherwise use {@link #addErrorObject} to indicate an + * error and resume processing */ - protected abstract void processOptions( CommandLine commandLine ) throws Exception; + protected abstract void doWork() throws Exception; + + /** + * Prompt the user for a confirmation or raise an exception to abort the {@link #doWork()} method. + */ + protected void promptConfirmationOrAbort( String message ) throws Exception { + if ( System.console() == null ) { + throw new IllegalStateException( "A console must be available for prompting confirmation." ); + } + String line = System.console().readLine( "WARNING: %s\nWARNING: Enter YES to continue: ", + message.replaceAll( "\n", "\nWARNING: " ) ); + if ( "YES".equals( line.trim() ) ) { + return; + } + throw new WorkAbortedException( "Confirmation failed, the command cannot proceed." ); + } /** * Add a success object to indicate success in a batch processing. *

- * This is further used in {@link #summarizeProcessing()} to summarize the execution of the command. + * This is further used in {@link #summarizeBatchProcessing()} to summarize the execution of the command. * * @param successObject object that was processed * @param message success message */ protected void addSuccessObject( Object successObject, String message ) { - successObjects.add( new BatchProcessingResult( successObject, message, null ) ); + batchProcessingResults.add( new BatchProcessingResult( false, successObject, message, null ) ); } /** * @see #addSuccessObject(Object, String) */ protected void addSuccessObject( Object successObject ) { - successObjects.add( new BatchProcessingResult( successObject, null, null ) ); + batchProcessingResults.add( new BatchProcessingResult( false, successObject, null, null ) ); } /** * Add an error object with a stacktrace to indicate failure in a batch processing. *

- * This is further used in {@link #summarizeProcessing()} to summarize the execution of the command. + * This is further used in {@link #summarizeBatchProcessing()} to summarize the execution of the command. *

* This is intended to be used when an {@link Exception} is caught. * @@ -356,7 +429,7 @@ protected void addSuccessObject( Object successObject ) { * @param throwable throwable to produce a stacktrace */ protected void addErrorObject( @Nullable Object errorObject, String message, Throwable throwable ) { - errorObjects.add( new BatchProcessingResult( errorObject, message, throwable ) ); + batchProcessingResults.add( new BatchProcessingResult( true, errorObject, message, throwable ) ); log.error( "Error while processing " + ( errorObject != null ? errorObject : "unknown object" ) + ":\n\t" + message, throwable ); } @@ -365,7 +438,7 @@ protected void addErrorObject( @Nullable Object errorObject, String message, Thr * @see #addErrorObject(Object, String) */ protected void addErrorObject( @Nullable Object errorObject, String message ) { - errorObjects.add( new BatchProcessingResult( errorObject, message, null ) ); + batchProcessingResults.add( new BatchProcessingResult( true, errorObject, message, null ) ); log.error( "Error while processing " + ( errorObject != null ? errorObject : "unknown object" ) + ":\n\t" + message ); } @@ -374,7 +447,7 @@ protected void addErrorObject( @Nullable Object errorObject, String message ) { * @see #addErrorObject(Object, String, Throwable) */ protected void addErrorObject( @Nullable Object errorObject, Exception exception ) { - errorObjects.add( new BatchProcessingResult( errorObject, exception.getMessage(), exception ) ); + batchProcessingResults.add( new BatchProcessingResult( true, errorObject, exception.getMessage(), exception ) ); log.error( "Error while processing " + ( errorObject != null ? errorObject : "unknown object" ), exception ); } @@ -382,8 +455,32 @@ protected void addErrorObject( @Nullable Object errorObject, Exception exception * Print out a summary of what the program did. Useful when analyzing lists of experiments etc. Use the * 'successObjects' and 'errorObjects' */ - private void summarizeProcessing() { - if ( successObjects.size() > 0 ) { + private void summarizeBatchProcessing() { + if ( batchProcessingResults.isEmpty() ) { + return; + } + if ( batchFormat != BatchFormat.SUPPRESS && batchOutputFile != null ) { + log.info( String.format( "Batch processing summary will be written to %s", batchOutputFile.getAbsolutePath() ) ); + } + try ( Writer dest = batchOutputFile != null ? new OutputStreamWriter( Files.newOutputStream( batchOutputFile.toPath() ) ) : null ) { + switch ( batchFormat ) { + case TEXT: + summarizeBatchProcessingToText( dest != null ? dest : System.out ); + break; + case TSV: + summarizeBatchProcessingToTsv( dest != null ? dest : System.out ); + break; + case SUPPRESS: + break; + } + } catch ( IOException e ) { + log.error( "Failed to summarize batch processing.", e ); + } + } + + private void summarizeBatchProcessingToText( Appendable dest ) throws IOException { + List successObjects = batchProcessingResults.stream().filter( bp -> !bp.isError() ).collect( Collectors.toList() ); + if ( !successObjects.isEmpty() ) { StringBuilder buf = new StringBuilder(); buf.append( "\n---------------------\nSuccessfully processed " ).append( successObjects.size() ) .append( " objects:\n" ); @@ -391,11 +488,11 @@ private void summarizeProcessing() { buf.append( result ).append( "\n" ); } buf.append( "---------------------\n" ); - - AbstractCLI.log.info( buf ); + dest.append( buf ); } - if ( errorObjects.size() > 0 ) { + List errorObjects = batchProcessingResults.stream().filter( BatchProcessingResult::isError ).collect( Collectors.toList() ); + if ( !errorObjects.isEmpty() ) { StringBuilder buf = new StringBuilder(); buf.append( "\n---------------------\nErrors occurred during the processing of " ) .append( errorObjects.size() ).append( " objects:\n" ); @@ -403,7 +500,19 @@ private void summarizeProcessing() { buf.append( result ).append( "\n" ); } buf.append( "---------------------\n" ); - AbstractCLI.log.error( buf ); + dest.append( buf ); + } + } + + private void summarizeBatchProcessingToTsv( Appendable dest ) throws IOException { + try ( CSVPrinter printer = new CSVPrinter( dest, CSVFormat.TDF ) ) { + for ( BatchProcessingResult result : batchProcessingResults ) { + printer.printRecord( + result.getSource(), + result.isError() ? "ERROR" : "SUCCESS", + result.getMessage(), + result.throwable != null ? ExceptionUtils.getRootCauseMessage( result.throwable ) : null ); + } } } @@ -414,47 +523,23 @@ private void summarizeProcessing() { protected List executeBatchTasks( Collection> tasks ) throws InterruptedException { List> futures = executorService.invokeAll( tasks ); List futureResults = new ArrayList<>( futures.size() ); - int i = 0; + int i = 1; for ( Future future : futures ) { try { futureResults.add( future.get() ); } catch ( ExecutionException e ) { - addErrorObject( null, String.format( "Batch task #%d failed", ++i ), e.getCause() ); + addErrorObject( null, String.format( "Batch task #%d failed", i ), e.getCause() ); + } finally { + i++; } } return futureResults; } - private String invalidOptionString( CommandLine commandLine, String option ) { - return "Invalid value '" + commandLine.getOptionValue( option ) + " for option " + option; - } - - /** - * Somewhat annoying: This causes subclasses to be unable to safely use 'h', 'p', 'u' and 'P' etc. for their own - * purposes. - */ - protected void processStandardOptions( CommandLine commandLine ) { - - if ( commandLine.hasOption( AbstractCLI.HOST_OPTION ) ) { - this.host = commandLine.getOptionValue( AbstractCLI.HOST_OPTION ); - } else { - this.host = DEFAULT_HOST; - } - - if ( commandLine.hasOption( AbstractCLI.PORT_OPTION ) ) { - this.port = this.getIntegerOptionValue( commandLine, AbstractCLI.PORT_OPTION ); - } else { - this.port = AbstractCLI.DEFAULT_PORT; - } - - if ( commandLine.hasOption( DATE_OPTION ) ) { - this.mDate = commandLine.getOptionValue( DATE_OPTION ); - } - - if ( this.numThreads < 1 ) { - throw new IllegalArgumentException( "Number of threads must be greater than 1." ); - } - this.executorService = new ForkJoinPool( this.numThreads ); + private enum BatchFormat { + TEXT, + TSV, + SUPPRESS } /** @@ -462,6 +547,7 @@ protected void processStandardOptions( CommandLine commandLine ) { */ @Value private static class BatchProcessingResult { + boolean isError; @Nullable Object source; @Nullable @@ -469,7 +555,8 @@ private static class BatchProcessingResult { @Nullable Throwable throwable; - public BatchProcessingResult( @Nullable Object source, @Nullable String message, @Nullable Throwable throwable ) { + public BatchProcessingResult( boolean isError, @Nullable Object source, @Nullable String message, @Nullable Throwable throwable ) { + this.isError = isError; this.source = source; this.message = message; this.throwable = throwable; @@ -480,10 +567,26 @@ public String toString() { StringBuilder buf = new StringBuilder(); buf.append( source != null ? source : "Unknown object" ); if ( message != null ) { - buf.append( ":\n\t" ) - .append( message.replace( "\n", "\n\t" ) ); + buf.append( "\t" ) + .append( message.replace( "\n", "\n\t" ) ); // FIXME We don't want newlines here at all, but I'm not sure what condition this is meant to fix exactly. + } + if ( throwable != null ) { + buf.append( "\t" ) + .append( "Reason: " ) + .append( ExceptionUtils.getRootCauseMessage( throwable ) ); } return buf.toString(); } } + + /** + * Exception raised when a {@link #doWork()} aborted by the user. + * @author poirigui + */ + private static class WorkAbortedException extends Exception { + + private WorkAbortedException( String message ) { + super( message ); + } + } } diff --git a/gemma-cli/src/main/java/ubic/gemma/core/util/AbstractCLIContextCLI.java b/gemma-cli/src/main/java/ubic/gemma/core/util/AbstractCLIContextCLI.java deleted file mode 100644 index 220e2b953d..0000000000 --- a/gemma-cli/src/main/java/ubic/gemma/core/util/AbstractCLIContextCLI.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * The gemma project - * - * Copyright (c) 2013 University of British Columbia - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ -package ubic.gemma.core.util; - -import org.apache.commons.cli.CommandLine; -import org.apache.commons.lang3.StringUtils; -import org.springframework.beans.factory.BeanFactory; -import ubic.gemma.core.apps.GemmaCLI.CommandGroup; -import ubic.gemma.model.expression.arrayDesign.ArrayDesign; -import ubic.gemma.model.genome.Taxon; -import ubic.gemma.persistence.service.common.auditAndSecurity.AuditEventService; -import ubic.gemma.persistence.service.common.auditAndSecurity.AuditTrailService; -import ubic.gemma.persistence.service.expression.arrayDesign.ArrayDesignService; -import ubic.gemma.persistence.service.genome.taxon.TaxonService; - -import java.io.BufferedReader; -import java.io.FileReader; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -/** - * Spring configuration for CLI. - * - * @author anton date: 18/02/13 - */ -public abstract class AbstractCLIContextCLI extends AbstractSpringAwareCLI { - - /** - * may be tab-delimited, only first column used, commented (#) lines are ignored. - * - * @param fileName the file name - * @return list of ee identifiers - * @throws IOException in case there is an IO error while reading the file - */ - protected static List readListFileToStrings( String fileName ) throws IOException { - List eeNames = new ArrayList<>(); - try ( BufferedReader in = new BufferedReader( new FileReader( fileName ) ) ) { - while ( in.ready() ) { - String line = in.readLine().trim(); - if ( line.startsWith( "#" ) ) { - continue; - } - if ( line.isEmpty() ) - continue; - String[] split = StringUtils.split( line, "\t" ); - eeNames.add( split[0] ); - } - return eeNames; - } - } - - protected Taxon setTaxonByName( CommandLine commandLine, TaxonService taxonService ) { - String taxonName = commandLine.getOptionValue( 't' ); - ubic.gemma.model.genome.Taxon taxon = taxonService.findByCommonName( taxonName ); - if ( taxon == null ) { - AbstractCLI.log.error( "ERROR: Cannot find taxon " + taxonName ); - } - return taxon; - } - - /** - * @param name of the array design to find. - * @param arrayDesignService the arrayDesignService to use for the AD retrieval - * @return an array design, if found. Bails otherwise with an error exit code - */ - protected ArrayDesign locateArrayDesign( String name, ArrayDesignService arrayDesignService ) { - - ArrayDesign arrayDesign = null; - - Collection byname = arrayDesignService.findByName( name.trim().toUpperCase() ); - if ( byname.size() > 1 ) { - throw new IllegalArgumentException( "Ambiguous name: " + name ); - } else if ( byname.size() == 1 ) { - arrayDesign = byname.iterator().next(); - } - - if ( arrayDesign == null ) { - arrayDesign = arrayDesignService.findByShortName( name ); - } - - if ( arrayDesign == null ) { - AbstractCLI.log.error( "No arrayDesign " + name + " found" ); - } - return arrayDesign; - } - -} diff --git a/gemma-cli/src/main/java/ubic/gemma/core/util/CLI.java b/gemma-cli/src/main/java/ubic/gemma/core/util/CLI.java index 9936dd9fa1..e012a00413 100644 --- a/gemma-cli/src/main/java/ubic/gemma/core/util/CLI.java +++ b/gemma-cli/src/main/java/ubic/gemma/core/util/CLI.java @@ -36,5 +36,5 @@ public interface CLI { * Execute the given command given CLI arguments. * @return an exit code */ - int executeCommand( String[] args ); + int executeCommand( String... args ); } diff --git a/gemma-cli/src/main/java/ubic/gemma/core/util/ConfigurationLinter.java b/gemma-cli/src/main/java/ubic/gemma/core/util/ConfigurationLinter.java new file mode 100644 index 0000000000..0c6491c155 --- /dev/null +++ b/gemma-cli/src/main/java/ubic/gemma/core/util/ConfigurationLinter.java @@ -0,0 +1,40 @@ +package ubic.gemma.core.util; + +import lombok.extern.apachecommons.CommonsLog; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Component; + +/** + * Lint various aspects of the configuration for the CLI profile. + */ +@CommonsLog +@Profile("cli") +@Component +public class ConfigurationLinter implements InitializingBean { + + @Value("${load.ontologies}") + private boolean autoLoadOntologies; + + @Value("${load.homologene}") + private boolean loadHomologene; + + @Value("${gemma.hibernate.hbm2ddl.auto}") + private String hbm2ddl; + + @Override + public void afterPropertiesSet() { + if ( autoLoadOntologies ) { + log.warn( "Auto-loading of ontologies is enabled, this is not recommended for the CLI. Disable it by setting load.ontologies=false in Gemma.properties." ); + } + + if ( loadHomologene ) { + log.warn( "Homologene is enabled, this is not recommended for the CLI. Disable it by setting load.homologene=false in Gemma.properties." ); + } + + if ( "validate".equals( hbm2ddl ) ) { + log.warn( "Hibernate is configured to validate the database schema, this is not recommended for the CLI. Disable it by setting gemma.hibernate.hbm2ddl.auto= in Gemma.properties." ); + } + } +} diff --git a/gemma-cli/src/main/java/ubic/gemma/core/util/FileUtils.java b/gemma-cli/src/main/java/ubic/gemma/core/util/FileUtils.java new file mode 100644 index 0000000000..03d26de2a7 --- /dev/null +++ b/gemma-cli/src/main/java/ubic/gemma/core/util/FileUtils.java @@ -0,0 +1,35 @@ +package ubic.gemma.core.util; + +import org.apache.commons.lang3.StringUtils; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public class FileUtils { + /** + * may be tab-delimited, only first column used, commented (#) lines are ignored. + * + * @param fileName the file name + * @return list of ee identifiers + * @throws IOException in case there is an IO error while reading the file + */ + public static List readListFileToStrings( String fileName ) throws IOException { + List eeNames = new ArrayList<>(); + try ( BufferedReader in = new BufferedReader( new FileReader( fileName ) ) ) { + while ( in.ready() ) { + String line = in.readLine().trim(); + if ( line.startsWith( "#" ) ) { + continue; + } + if ( line.isEmpty() ) + continue; + String[] split = StringUtils.split( line, "\t" ); + eeNames.add( split[0] ); + } + return eeNames; + } + } +} diff --git a/gemma-cli/src/test/java/ubic/gemma/core/apps/ArrayDesignMergeCliTest.java b/gemma-cli/src/test/java/ubic/gemma/core/apps/ArrayDesignMergeCliTest.java index ba2c761e1c..9c61a30f57 100644 --- a/gemma-cli/src/test/java/ubic/gemma/core/apps/ArrayDesignMergeCliTest.java +++ b/gemma-cli/src/test/java/ubic/gemma/core/apps/ArrayDesignMergeCliTest.java @@ -1,17 +1,20 @@ package ubic.gemma.core.apps; +import gemma.gsec.authentication.ManualAuthenticationService; import org.junit.After; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.security.test.context.support.WithSecurityContextTestExecutionListener; import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestExecutionListeners; import ubic.gemma.core.analysis.report.ArrayDesignReportService; import ubic.gemma.core.loader.expression.arrayDesign.ArrayDesignMergeService; -import ubic.gemma.core.util.AbstractCLI; import ubic.gemma.core.util.test.BaseCliTest; import ubic.gemma.model.expression.arrayDesign.ArrayDesign; +import ubic.gemma.persistence.service.common.auditAndSecurity.AuditTrailService; import ubic.gemma.persistence.service.expression.arrayDesign.ArrayDesignService; import ubic.gemma.persistence.util.TestComponent; @@ -23,17 +26,28 @@ import static org.mockito.Mockito.*; @ContextConfiguration +@TestExecutionListeners(WithSecurityContextTestExecutionListener.class) public class ArrayDesignMergeCliTest extends BaseCliTest { @Configuration @TestComponent - public static class ArrayDesignMergeCliTestContextConfiguration extends BaseCliTestContextConfiguration { + public static class ArrayDesignMergeCliTestContextConfiguration { @Bean public ArrayDesignMergeCli arrayDesignMergeCli() { return new ArrayDesignMergeCli(); } + @Bean + public ManualAuthenticationService manualAuthenticationService() { + return mock(); + } + + @Bean + public AuditTrailService auditTrailService() { + return mock(); + } + @Bean public ArrayDesignMergeService arrayDesignMergeService() { return mock( ArrayDesignMergeService.class ); @@ -76,8 +90,8 @@ public void test() { when( arrayDesignService.thaw( any( ArrayDesign.class ) ) ).thenAnswer( args -> args.getArgument( 0 ) ); when( arrayDesignService.thaw( anyCollection() ) ).thenAnswer( args -> args.getArgument( 0 ) ); Collection otherPlatforms = new HashSet<>( Arrays.asList( b, c ) ); - assertThat( arrayDesignMergeCli.executeCommand( new String[] { "-a", "1", "-o", "2,3", "-s", "4", "-n", "four is better than one" } ) ) - .isEqualTo( AbstractCLI.SUCCESS ); + assertThat( arrayDesignMergeCli.executeCommand( "-a", "1", "-o", "2,3", "-s", "4", "-n", "four is better than one" ) ) + .isEqualTo( 0 ); verify( arrayDesignMergeService ).merge( a, otherPlatforms, "four is better than one", "4", false ); } } diff --git a/gemma-cli/src/test/java/ubic/gemma/core/apps/ExternalDatabaseUpdaterCliTest.java b/gemma-cli/src/test/java/ubic/gemma/core/apps/ExternalDatabaseUpdaterCliTest.java index 0b689e8d92..f5935cac36 100644 --- a/gemma-cli/src/test/java/ubic/gemma/core/apps/ExternalDatabaseUpdaterCliTest.java +++ b/gemma-cli/src/test/java/ubic/gemma/core/apps/ExternalDatabaseUpdaterCliTest.java @@ -1,5 +1,6 @@ package ubic.gemma.core.apps; +import gemma.gsec.authentication.ManualAuthenticationService; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -7,12 +8,15 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.security.test.context.support.WithSecurityContextTestExecutionListener; import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestExecutionListeners; import ubic.gemma.core.security.authentication.UserManager; import ubic.gemma.core.util.test.BaseCliTest; import ubic.gemma.model.common.auditAndSecurity.User; import ubic.gemma.model.common.description.DatabaseType; import ubic.gemma.model.common.description.ExternalDatabase; +import ubic.gemma.persistence.service.common.auditAndSecurity.AuditTrailService; import ubic.gemma.persistence.service.common.description.ExternalDatabaseService; import ubic.gemma.persistence.util.TestComponent; @@ -23,11 +27,12 @@ import static org.mockito.Mockito.*; @ContextConfiguration +@TestExecutionListeners(WithSecurityContextTestExecutionListener.class) public class ExternalDatabaseUpdaterCliTest extends BaseCliTest { @Configuration @TestComponent - static class ExternalDatabaseUpdaterCliTestContextConfiguration extends BaseCliTestContextConfiguration { + static class ExternalDatabaseUpdaterCliTestContextConfiguration { @Bean public ExternalDatabaseUpdaterCli externalDatabaseUpdaterCli() { @@ -43,6 +48,16 @@ public ExternalDatabaseService externalDatabaseService() { public UserManager userManager() { return mock( UserManager.class ); } + + @Bean + public ManualAuthenticationService manualAuthenticationService() { + return mock(); + } + + @Bean + public AuditTrailService auditTrailService() { + return mock(); + } } @Autowired diff --git a/gemma-cli/src/test/java/ubic/gemma/core/apps/FactorValueMigratorCLITest.java b/gemma-cli/src/test/java/ubic/gemma/core/apps/FactorValueMigratorCLITest.java new file mode 100644 index 0000000000..93fc53ad7e --- /dev/null +++ b/gemma-cli/src/test/java/ubic/gemma/core/apps/FactorValueMigratorCLITest.java @@ -0,0 +1,165 @@ +package ubic.gemma.core.apps; + +import gemma.gsec.authentication.ManualAuthenticationService; +import org.apache.commons.lang3.RandomStringUtils; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.ClassPathResource; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.security.test.context.support.WithSecurityContextTestExecutionListener; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestExecutionListeners; +import org.springframework.transaction.PlatformTransactionManager; +import ubic.gemma.core.util.test.BaseCliTest; +import ubic.gemma.model.common.description.Characteristic; +import ubic.gemma.model.expression.experiment.FactorValue; +import ubic.gemma.model.expression.experiment.Statement; +import ubic.gemma.persistence.service.expression.experiment.FactorValueMigratorService; +import ubic.gemma.persistence.service.expression.experiment.FactorValueMigratorServiceImpl; +import ubic.gemma.persistence.service.expression.experiment.FactorValueService; +import ubic.gemma.persistence.util.TestComponent; + +import java.io.IOException; +import java.util.concurrent.atomic.AtomicLong; + +import static org.mockito.Mockito.*; + +@Deprecated +@ContextConfiguration +@TestExecutionListeners(WithSecurityContextTestExecutionListener.class) +public class FactorValueMigratorCLITest extends BaseCliTest { + + @Configuration + @TestComponent + static class FactorValueMigratorCLITestContextConfiguration { + + @Bean + public FactorValueMigratorCLI factorValueMigratorCLI() { + return new FactorValueMigratorCLI(); + } + + @Bean + public FactorValueMigratorService factorValueMigratorService() { + return new FactorValueMigratorServiceImpl(); + } + + @Bean + public FactorValueService factorValueService() { + return mock(); + } + + @Bean + public PlatformTransactionManager platformTransactionManager() { + return mock(); + } + + @Bean + public ManualAuthenticationService manualAuthenticationService() { + return mock(); + } + } + + @Autowired + private FactorValueMigratorCLI cli; + + @Autowired + private FactorValueService factorValueService; + + private final FactorValue[] fvs = { + createFactorValue( 1L, 1L, 2L, 3L ), + createFactorValue( 2L ), + createFactorValue( 3L, 4L ), + createFactorValue( 4L, 5L, 6L ), + createFactorValue( 5L, 7L, 8L, 9L ), + createFactorValue( 6L, 10L, 11L ), + createFactorValue( 7L, 12L, 13L ), + createFactorValue( 8L, 14L, 15L ) + }; + + private Characteristic getObjectById( long fvId, long id ) { + return fvs[( int ) ( fvId - 1 )].getOldStyleCharacteristics().stream() + .filter( c -> c.getId().equals( id ) ) + .findAny() + .orElse( null ); + } + + private String getCategory( long fvId, long id ) { + return getObjectById( fvId, id ).getCategory(); + } + + private String getObject( long fvId, long id ) { + return getObjectById( fvId, id ).getValue(); + } + + @Before + public void setUp() { + when( factorValueService.loadWithOldStyleCharacteristics( any(), anyBoolean() ) ) + .thenAnswer( a -> fvs[a.getArgument( 0, Long.class ).intValue() - 1] ); + when( factorValueService.countAll() ).thenReturn( ( long ) fvs.length ); + AtomicLong id = new AtomicLong( 0L ); + when( factorValueService.saveStatementIgnoreAcl( any(), any() ) ).thenAnswer( a -> { + Statement s = a.getArgument( 1, Statement.class ); + if ( s.getId() == null ) { + s.setId( id.incrementAndGet() ); + } + return s; + } ); + } + + @Test + @WithMockUser + public void testMigrateFactorValues() throws IOException { + cli.executeCommand( + "-migrationFile", new ClassPathResource( "ubic/gemma/core/apps/factor-value-migration.tsv" ).getFile().getAbsolutePath(), + "-batchFormat", "suppress" ); + verify( factorValueService, times( 8 ) ).loadWithOldStyleCharacteristics( any(), eq( false ) ); + verify( factorValueService ).saveStatementIgnoreAcl( any(), eq( createStatement( getCategory( 1L, 1L ), "Pax6", "has_modifier", getObject( 1L, 2L ), "has_modifier", getObject( 1L, 3L ) ) ) ); + verify( factorValueService ).saveStatementIgnoreAcl( any(), eq( createStatement( "Gene", "Pax6" ) ) ); + verify( factorValueService ).saveStatementIgnoreAcl( any(), eq( createStatement( getCategory( 3L, 4L ), getObject( 3L, 4L ) ) ) ); + verify( factorValueService ).saveStatementIgnoreAcl( any(), eq( createStatement( getCategory( 4L, 5L ), getObject( 4L, 5L ), "has_modifier", getObject( 4L, 6L ) ) ) ); + verify( factorValueService ).saveStatementIgnoreAcl( any(), eq( createStatement( getCategory( 5L, 7L ), getObject( 5L, 7L ), "has_modifier", getObject( 5L, 8L ), "has_modifier", getObject( 5L, 9L ) ) ) ); + verify( factorValueService ).saveStatementIgnoreAcl( any(), eq( createStatement( getCategory( 6L, 10L ), getObject( 6L, 10L ), "has_dose", "5mg", "has_modifier", getObject( 6L, 11L ) ) ) ); + verify( factorValueService ).saveStatementIgnoreAcl( any(), eq( createStatement( "genotype", "Pax7", "has_modifier", getObject( 7L, 12L ), "has_modifier", getObject( 7L, 13L ) ) ) ); + verify( factorValueService ).saveStatementIgnoreAcl( any(), eq( createStatement( getCategory( 8L, 14L ), getObject( 8L, 14L ), "has_modifier", getObject( 8L, 15L ), "has_modifier", getObject( 8L, 15L ) ) ) ); + verifyNoMoreInteractions( factorValueService ); + } + + private FactorValue createFactorValue( Long id, Long... osIds ) { + FactorValue fv = new FactorValue(); + fv.setId( id ); + for ( Long osId : osIds ) { + fv.getOldStyleCharacteristics().add( createCharacteristic( osId ) ); + } + return fv; + } + + private Characteristic createCharacteristic( Long id ) { + Characteristic c = Characteristic.Factory.newInstance(); + c.setCategory( RandomStringUtils.randomAlphanumeric( 10 ) ); + c.setValue( RandomStringUtils.randomAlphanumeric( 10 ) ); + c.setId( id ); + return c; + } + + private Statement createStatement( String category, String value ) { + return createStatement( category, value, null, null, null, null ); + } + + private Statement createStatement( String category, String value, String predicate, String object ) { + return createStatement( category, value, predicate, object, null, null ); + } + + private Statement createStatement( String category, String value, String predicate, String object, String secondPredicate, String secondObject ) { + Statement s = new Statement(); + s.setCategory( category ); + s.setSubject( value ); + s.setPredicate( predicate ); + s.setObject( object ); + s.setSecondPredicate( secondPredicate ); + s.setSecondObject( secondObject ); + return s; + } +} \ No newline at end of file diff --git a/gemma-cli/src/test/java/ubic/gemma/core/apps/FactorValueMigratorServiceTest.java b/gemma-cli/src/test/java/ubic/gemma/core/apps/FactorValueMigratorServiceTest.java new file mode 100644 index 0000000000..7388a7e3f9 --- /dev/null +++ b/gemma-cli/src/test/java/ubic/gemma/core/apps/FactorValueMigratorServiceTest.java @@ -0,0 +1,134 @@ +package ubic.gemma.core.apps; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests; +import org.springframework.transaction.PlatformTransactionManager; +import ubic.gemma.model.common.description.Characteristic; +import ubic.gemma.model.expression.experiment.FactorValue; +import ubic.gemma.model.expression.experiment.Statement; +import ubic.gemma.persistence.service.expression.experiment.FactorValueMigratorService; +import ubic.gemma.persistence.service.expression.experiment.FactorValueMigratorServiceImpl; +import ubic.gemma.persistence.service.expression.experiment.FactorValueService; +import ubic.gemma.persistence.util.TestComponent; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.*; + +@Deprecated +@ContextConfiguration +public class FactorValueMigratorServiceTest extends AbstractJUnit4SpringContextTests { + + @Configuration + @TestComponent + static class FVMSTCC { + + @Bean + public FactorValueMigratorService factorValueMigratorService() { + return new FactorValueMigratorServiceImpl(); + } + + @Bean + public FactorValueService factorValueService() { + return mock(); + } + + @Bean + public PlatformTransactionManager platformTransactionManager() { + return mock(); + } + } + + @Autowired + private FactorValueMigratorService factorValueMigratorService; + + @Autowired + private FactorValueService factorValueService; + + @Before + public void setUp() { + when( factorValueService.saveStatementIgnoreAcl( any(), any() ) ).thenAnswer( a -> a.getArgument( 1 ) ); + } + + @After + public void tearDown() { + reset( factorValueService ); + } + + @Test + public void testMigrationThatReusesExistingStatement() { + FactorValue fv = new FactorValue(); + Statement stmt = new Statement(); + stmt.setCategory( "genotype" ); + stmt.setSubject( "VPLL1" ); + fv.getCharacteristics().add( stmt ); + FactorValueMigratorService.Migration migration = FactorValueMigratorService.Migration.builder() + .factorValueId( 1L ) + .category( "genotype" ) + .subject( "VPLL1" ) + .build(); + when( factorValueService.loadWithOldStyleCharacteristics( 1L, false ) ).thenReturn( fv ); + factorValueMigratorService.performMigration( migration, false ); + verify( factorValueService ).loadWithOldStyleCharacteristics( 1L, false ); + verify( factorValueService ).saveStatementIgnoreAcl( same( fv ), same( stmt ) ); + } + + @Test + public void testMigrationThatReuseObject() { + FactorValue fv = new FactorValue(); + Characteristic c = new Characteristic(); + c.setId( 1L ); + c.setValue( "bob" ); + fv.getOldStyleCharacteristics().add( c ); + FactorValueMigratorService.Migration migration = FactorValueMigratorService.Migration.builder() + .factorValueId( 1L ) + .category( "genotype" ) + .subject( "VPLL1" ) + .predicate( "has" ) + .oldStyleCharacteristicIdUsedAsObject( 1L ) + .secondPredicate( "also has" ) + .oldStyleCharacteristicIdUsedAsSecondObject( 1L ) + .build(); + Statement stmt = new Statement(); + stmt.setCategory( "genotype" ); + stmt.setSubject( "VPLL1" ); + stmt.setPredicate( "has" ); + stmt.setObject( "bob" ); + stmt.setSecondPredicate( "also has" ); + stmt.setSecondObject( "bob" ); + when( factorValueService.loadWithOldStyleCharacteristics( 1L, false ) ).thenReturn( fv ); + factorValueMigratorService.performMigration( migration, false ); + verify( factorValueService ).loadWithOldStyleCharacteristics( 1L, false ); + verify( factorValueService ).saveStatementIgnoreAcl( same( fv ), eq( stmt ) ); + } + + @Test + public void testMigrationWithFreeTextSubject() { + FactorValue fv = new FactorValue(); + Characteristic c = new Characteristic(); + c.setId( 2L ); + c.setCategory( "bar" ); + c.setCategoryUri( "http://bar" ); + c.setValueUri( "http://foo/" ); + fv.getOldStyleCharacteristics().add( c ); + FactorValueMigratorService.Migration migration = FactorValueMigratorService.Migration.builder() + .factorValueId( 1L ) + .oldStyleCharacteristicIdUsedAsSubject( 2L ) + .subject( "foo" ) + .subjectUri( null ) + .build(); + when( factorValueService.loadWithOldStyleCharacteristics( 1L, false ) ).thenReturn( fv ); + FactorValueMigratorService.MigrationResult result = factorValueMigratorService.performMigration( migration, false ); + assertThat( result.getStatement() ) + .hasFieldOrPropertyWithValue( "category", "bar" ) + .hasFieldOrPropertyWithValue( "categoryUri", "http://bar" ) + .hasFieldOrPropertyWithValue( "subject", "foo" ) + .hasFieldOrPropertyWithValue( "subjectUri", null ); + verify( factorValueService ).saveStatementIgnoreAcl( same( fv ), any() ); + } +} \ No newline at end of file diff --git a/gemma-cli/src/test/java/ubic/gemma/core/apps/FindObsoleteTermsCliTest.java b/gemma-cli/src/test/java/ubic/gemma/core/apps/FindObsoleteTermsCliTest.java new file mode 100644 index 0000000000..168c84dc73 --- /dev/null +++ b/gemma-cli/src/test/java/ubic/gemma/core/apps/FindObsoleteTermsCliTest.java @@ -0,0 +1,69 @@ +package ubic.gemma.core.apps; + +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.task.AsyncTaskExecutor; +import org.springframework.core.task.SimpleAsyncTaskExecutor; +import org.springframework.test.context.ContextConfiguration; +import ubic.gemma.core.ontology.OntologyService; +import ubic.gemma.core.util.test.BaseCliTest; +import ubic.gemma.core.util.test.TestPropertyPlaceholderConfigurer; +import ubic.gemma.persistence.util.TestComponent; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +@ContextConfiguration +public class FindObsoleteTermsCliTest extends BaseCliTest { + + @Configuration + @TestComponent + static class FindObsoleteTermsCliTestContextConfiguration { + + @Bean + public static TestPropertyPlaceholderConfigurer testPlaceholderConfigurer() { + return new TestPropertyPlaceholderConfigurer( "load.ontologies=false" ); + } + + @Bean + public FindObsoleteTermsCli findObsoleteTermsCli() { + return new FindObsoleteTermsCli(); + } + + @Bean + public OntologyService ontologyService() { + return mock(); + } + + @Bean + public AsyncTaskExecutor ontologyTaskExecutor() { + return new SimpleAsyncTaskExecutor(); + } + + @Bean + public ubic.basecode.ontology.providers.OntologyService ontology1() { + return mock(); + } + } + + @Autowired + private FindObsoleteTermsCli findObsoleteTermsCli; + + @Autowired + private OntologyService ontologyService; + + @Autowired + private ubic.basecode.ontology.providers.OntologyService ontology1; + + @Test + public void test() { + assertEquals( 0, findObsoleteTermsCli.executeCommand() ); + verify( ontology1 ).setSearchEnabled( false ); + verify( ontology1 ).setInferenceMode( ubic.basecode.ontology.providers.OntologyService.InferenceMode.NONE ); + verify( ontology1 ).initialize( true, false ); + verify( ontologyService ).findObsoleteTermUsage(); + } +} \ No newline at end of file diff --git a/gemma-cli/src/test/java/ubic/gemma/core/apps/NCBIGene2GOAssociationLoaderCLITest.java b/gemma-cli/src/test/java/ubic/gemma/core/apps/NCBIGene2GOAssociationLoaderCLITest.java index abee3f0023..3afdd45593 100644 --- a/gemma-cli/src/test/java/ubic/gemma/core/apps/NCBIGene2GOAssociationLoaderCLITest.java +++ b/gemma-cli/src/test/java/ubic/gemma/core/apps/NCBIGene2GOAssociationLoaderCLITest.java @@ -1,17 +1,22 @@ package ubic.gemma.core.apps; +import gemma.gsec.authentication.ManualAuthenticationService; import org.junit.Test; import org.junit.experimental.categories.Category; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.security.test.context.support.WithSecurityContextTestExecutionListener; import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestExecutionListeners; import ubic.gemma.core.util.test.BaseCliTest; import ubic.gemma.core.util.test.category.SlowTest; import ubic.gemma.model.common.description.DatabaseType; import ubic.gemma.model.common.description.ExternalDatabase; +import ubic.gemma.persistence.persister.PersisterHelper; import ubic.gemma.persistence.service.association.Gene2GOAssociationService; +import ubic.gemma.persistence.service.common.auditAndSecurity.AuditTrailService; import ubic.gemma.persistence.service.common.description.ExternalDatabaseService; import ubic.gemma.persistence.service.genome.taxon.TaxonService; import ubic.gemma.persistence.util.TestComponent; @@ -21,11 +26,12 @@ @Category(SlowTest.class) @ContextConfiguration +@TestExecutionListeners(WithSecurityContextTestExecutionListener.class) public class NCBIGene2GOAssociationLoaderCLITest extends BaseCliTest { @Configuration @TestComponent - static class NCBIGene2GOAssociationLoaderCLITestContextConfiguration extends BaseCliTestContextConfiguration { + static class NCBIGene2GOAssociationLoaderCLITestContextConfiguration { @Bean public NCBIGene2GOAssociationLoaderCLI ncbiGene2GOAssociationLoaderCLI() { @@ -46,6 +52,21 @@ public Gene2GOAssociationService gene2GOAssociationService() { public ExternalDatabaseService externalDatabaseService() { return mock( ExternalDatabaseService.class ); } + + @Bean + public ManualAuthenticationService manualAuthenticationService() { + return mock(); + } + + @Bean + public AuditTrailService auditTrailService() { + return mock(); + } + + @Bean + public PersisterHelper persisterHelper() { + return mock(); + } } @Autowired diff --git a/gemma-cli/src/test/java/ubic/gemma/core/apps/RNASeqDataAddCliTest.java b/gemma-cli/src/test/java/ubic/gemma/core/apps/RNASeqDataAddCliTest.java index 641ce7bca9..3628e475c5 100644 --- a/gemma-cli/src/test/java/ubic/gemma/core/apps/RNASeqDataAddCliTest.java +++ b/gemma-cli/src/test/java/ubic/gemma/core/apps/RNASeqDataAddCliTest.java @@ -1,5 +1,6 @@ package ubic.gemma.core.apps; +import gemma.gsec.authentication.ManualAuthenticationService; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -10,7 +11,9 @@ import org.springframework.context.annotation.Scope; import org.springframework.core.io.ClassPathResource; import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.security.test.context.support.WithSecurityContextTestExecutionListener; import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestExecutionListeners; import ubic.gemma.core.analysis.service.ExpressionDataFileService; import ubic.gemma.core.genome.gene.service.GeneService; import ubic.gemma.core.loader.expression.DataUpdater; @@ -18,6 +21,9 @@ import ubic.gemma.core.util.test.BaseCliTest; import ubic.gemma.model.expression.arrayDesign.ArrayDesign; import ubic.gemma.model.expression.experiment.ExpressionExperiment; +import ubic.gemma.persistence.persister.PersisterHelper; +import ubic.gemma.persistence.service.common.auditAndSecurity.AuditEventService; +import ubic.gemma.persistence.service.common.auditAndSecurity.AuditTrailService; import ubic.gemma.persistence.service.expression.arrayDesign.ArrayDesignService; import ubic.gemma.persistence.service.expression.experiment.ExpressionExperimentService; import ubic.gemma.persistence.service.expression.experiment.ExpressionExperimentSetService; @@ -30,11 +36,12 @@ import static org.mockito.Mockito.*; @ContextConfiguration +@TestExecutionListeners(WithSecurityContextTestExecutionListener.class) public class RNASeqDataAddCliTest extends BaseCliTest { @Configuration @TestComponent - static class RNASeqDataAddCliTestContextConfiguration extends BaseCliTestContextConfiguration { + static class RNASeqDataAddCliTestContextConfiguration { @Bean @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) @@ -72,10 +79,30 @@ public ArrayDesignService arrayDesignService() { return mock(); } + @Bean + public ExpressionExperimentService expressionExperimentService() { + return mock(); + } + @Bean public ExpressionExperimentSetService expressionExperimentSetService() { return mock(); } + + @Bean + public ManualAuthenticationService manualAuthenticationService() { + return mock(); + } + + @Bean + public AuditTrailService auditTrailService() { + return mock(); + } + + @Bean + public AuditEventService auditEventService() { + return mock(); + } } @Autowired @@ -98,7 +125,7 @@ public ExpressionExperimentSetService expressionExperimentSetService() { public void setUp() throws IOException { ad = new ArrayDesign(); ee = new ExpressionExperiment(); - rpkmFile = new ClassPathResource( "test.rpkm.txt" ).getFile().getAbsolutePath(); + rpkmFile = new ClassPathResource( "ubic/gemma/core/apps/test.rpkm.txt" ).getFile().getAbsolutePath(); when( expressionExperimentService.findByShortName( "GSE000001" ) ).thenReturn( ee ); when( expressionExperimentService.thawLite( any() ) ).thenAnswer( a -> a.getArgument( 0 ) ); when( arrayDesignService.findByShortName( "test" ) ).thenReturn( ad ); diff --git a/gemma-cli/src/test/java/ubic/gemma/core/util/test/BaseCliTest.java b/gemma-cli/src/test/java/ubic/gemma/core/util/test/BaseCliTest.java index e05c57ed15..4413c44cd1 100644 --- a/gemma-cli/src/test/java/ubic/gemma/core/util/test/BaseCliTest.java +++ b/gemma-cli/src/test/java/ubic/gemma/core/util/test/BaseCliTest.java @@ -1,58 +1,13 @@ package ubic.gemma.core.util.test; -import gemma.gsec.authentication.ManualAuthenticationService; -import org.junit.Before; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.test.context.support.WithSecurityContextTestExecutionListener; import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.TestExecutionListeners; import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests; -import ubic.gemma.core.security.authentication.UserManager; -import ubic.gemma.persistence.persister.Persister; -import ubic.gemma.persistence.service.common.auditAndSecurity.AuditEventService; -import ubic.gemma.persistence.service.common.auditAndSecurity.AuditTrailService; -import ubic.gemma.persistence.service.expression.experiment.ExpressionExperimentService; import ubic.gemma.persistence.util.SpringProfiles; -import static org.mockito.Mockito.mock; - /** * Minimal setup */ @ActiveProfiles({ "cli", SpringProfiles.TEST }) -@TestExecutionListeners(WithSecurityContextTestExecutionListener.class) public abstract class BaseCliTest extends AbstractJUnit4SpringContextTests { - /** - * Basic context configuration - */ - public static class BaseCliTestContextConfiguration { - - @Bean - public ManualAuthenticationService manualAuthenticationService() { - return mock( ManualAuthenticationService.class ); - } - - @Bean - public AuditTrailService auditTrailService() { - return mock( AuditTrailService.class ); - } - - @Bean - public AuditEventService auditEventService() { - return mock( AuditEventService.class ); - } - - @Bean - public ExpressionExperimentService expressionExperimentService() { - return mock( ExpressionExperimentService.class ); - } - - @Bean - public Persister persisterHelper() { - return mock( Persister.class ); - } - } } diff --git a/gemma-cli/src/test/resources/ubic/gemma/core/apps/factor-value-migration.tsv b/gemma-cli/src/test/resources/ubic/gemma/core/apps/factor-value-migration.tsv new file mode 100644 index 0000000000..1e6870c771 --- /dev/null +++ b/gemma-cli/src/test/resources/ubic/gemma/core/apps/factor-value-migration.tsv @@ -0,0 +1,9 @@ +FactorValueID SubjectID ObjectID SecondObjectID SubjectURI Subject Predicate PredicateURI Object ObjectURI SecondPredicate SecondPredicateURI SecondObject SecondObjectURI Category CategoryURI +1 1 2 3 Pax6 has_modifier has_modifier "" "" "" +2 "" "" "" Pax6 "" "" "" Gene "" +3 4 "" "" "" "" "" "" "" +4 5 6 "" has_modifier "" "" "" "" +5 7 8 9 has_modifier has_modifier "" "" "" +6 10 "" 11 has_dose 5mg has_modifier "" "" "" +7 "" 12 13 Pax7 has_modifier has_modifier "" genotype "" +8 14 15 15 has_modifier has_modifier "" diff --git a/gemma-cli/src/test/resources/test.rpkm.txt b/gemma-cli/src/test/resources/ubic/gemma/core/apps/test.rpkm.txt similarity index 100% rename from gemma-cli/src/test/resources/test.rpkm.txt rename to gemma-cli/src/test/resources/ubic/gemma/core/apps/test.rpkm.txt diff --git a/gemma-core/pom.xml b/gemma-core/pom.xml index c0465c3ab8..e79a07adda 100644 --- a/gemma-core/pom.xml +++ b/gemma-core/pom.xml @@ -3,7 +3,7 @@ gemma gemma - 1.30.6 + 1.31.0 4.0.0 gemma-core diff --git a/gemma-core/src/main/java/ubic/gemma/core/analysis/expression/coexpression/links/LinkAnalysisServiceImpl.java b/gemma-core/src/main/java/ubic/gemma/core/analysis/expression/coexpression/links/LinkAnalysisServiceImpl.java index 94b4dd4f09..ac6d01ec82 100644 --- a/gemma-core/src/main/java/ubic/gemma/core/analysis/expression/coexpression/links/LinkAnalysisServiceImpl.java +++ b/gemma-core/src/main/java/ubic/gemma/core/analysis/expression/coexpression/links/LinkAnalysisServiceImpl.java @@ -454,16 +454,19 @@ private void qcCheck( LinkAnalysisConfig config, ExpressionExperiment ee ) throw "No batch information available, out of an abundance of caution we are skipping" ); } - if ( batchEffect.getPvalue() < 0.001 ) { + if ( batchEffect.getBatchEffectStatistics() == null ) { + throw new UnsuitableForAnalysisException( ee, + "Batch effect statistics are missing, it's not possible to detect a batch effect." ); + } - double componentVarianceProportion = batchEffect.getComponentVarianceProportion(); - Integer component = batchEffect.getComponent(); + if ( batchEffect.getBatchEffectStatistics().getPvalue() < 0.001 ) { + double componentVarianceProportion = batchEffect.getBatchEffectStatistics().getComponentVarianceProportion(); + int component = batchEffect.getBatchEffectStatistics().getComponent(); // don't worry if it is a "minor" component. remember that is must be one of the first few to make it // this far. if ( component > 2 && componentVarianceProportion < 0.1 ) { return; } - throw new UnsuitableForAnalysisException( ee, String.format( "Strong batch effect detected (%s)", batchEffect ) ); } diff --git a/gemma-core/src/main/java/ubic/gemma/core/analysis/expression/diff/BaselineSelection.java b/gemma-core/src/main/java/ubic/gemma/core/analysis/expression/diff/BaselineSelection.java index 0a5789f012..77b9e7e443 100644 --- a/gemma-core/src/main/java/ubic/gemma/core/analysis/expression/diff/BaselineSelection.java +++ b/gemma-core/src/main/java/ubic/gemma/core/analysis/expression/diff/BaselineSelection.java @@ -19,12 +19,13 @@ package ubic.gemma.core.analysis.expression.diff; -import org.apache.commons.lang3.StringUtils; import ubic.gemma.model.common.description.Characteristic; import ubic.gemma.model.expression.experiment.FactorValue; +import ubic.gemma.model.expression.experiment.Statement; -import java.util.HashSet; -import java.util.Set; +import java.util.*; + +import static org.apache.commons.lang3.StringUtils.normalizeSpace; /** * Utilities for deciding if a factor value is a baseline condition. @@ -32,129 +33,124 @@ * @author paul */ public class BaselineSelection { - private static final Set controlGroupTerms = new HashSet<>(); - // see bug 4316. This term is "control" - private static final String FORCED_BASELINE_VALUE_URI = "http://www.ebi.ac.uk/efo/EFO_0001461".toLowerCase(); - - static { - /* - * Values or ontology terms we treat as 'baseline'. - */ - BaselineSelection.controlGroupTerms - .add( "http://purl.obolibrary.org/obo/OBI_0000025".toLowerCase() ); // - reference substance - // role - - BaselineSelection.controlGroupTerms - .add( "http://purl.obolibrary.org/obo/OBI_0000220".toLowerCase() );// - reference subject role - BaselineSelection.controlGroupTerms - .add( "http://purl.obolibrary.org/obo/OBI_0000143".toLowerCase() );// - baseline participant - // role - - BaselineSelection.controlGroupTerms.add( "reference_substance_role" ); - BaselineSelection.controlGroupTerms.add( "reference_subject_role" ); - BaselineSelection.controlGroupTerms.add( "baseline_participant_role" ); - - BaselineSelection.controlGroupTerms.add( "control group" ); - BaselineSelection.controlGroupTerms.add( "control" ); - BaselineSelection.controlGroupTerms.add( "normal" ); - BaselineSelection.controlGroupTerms.add( "untreated" ); - BaselineSelection.controlGroupTerms.add( "baseline" ); - BaselineSelection.controlGroupTerms.add( "control_group" ); - BaselineSelection.controlGroupTerms.add( "wild_type" ); - BaselineSelection.controlGroupTerms.add( "wild type" ); - BaselineSelection.controlGroupTerms.add( "wild type genotype" ); - BaselineSelection.controlGroupTerms.add( "initial time point" ); - - BaselineSelection.controlGroupTerms.add( "to_be_treated_with_placebo_role" ); - - BaselineSelection.controlGroupTerms - .add( "http://purl.obolibrary.org/obo/OBI_0100046".toLowerCase() ); // phosphate buffered - // saline. - BaselineSelection.controlGroupTerms - .add( "http://mged.sourceforge.net/ontologies/MGEDOntology.owl#wild_type".toLowerCase() ); - - BaselineSelection.controlGroupTerms - .add( "http://purl.org/nbirn/birnlex/ontology/BIRNLex-Investigation.owl#birnlex_2201" - .toLowerCase() ); // control_group, old. - BaselineSelection.controlGroupTerms - .add( "http://ontology.neuinfo.org/NIF/DigitalEntities/NIF-Investigation.owl#birnlex_2201" - .toLowerCase() ); // control_group, new version.(retired) - - BaselineSelection.controlGroupTerms - .add( "http://ontology.neuinfo.org/NIF/DigitalEntities/NIF-Investigation.owl#birnlex_2001" - .toLowerCase() ); // " normal control_group", (retired) - - BaselineSelection.controlGroupTerms - .add( "http://purl.obolibrary.org/obo/OBI_0000825".toLowerCase() ); // - to be treated with - // placebo + // see bug 4316. This term is "control" + private static final String FORCED_BASELINE_VALUE_URI = "http://www.ebi.ac.uk/efo/EFO_0001461"; - BaselineSelection.controlGroupTerms - .add( "http://www.ebi.ac.uk/efo/EFO_0005168".toLowerCase() ); // wild type genotype + /** + * Values we treat as baseline. + */ + private static final Set controlGroupTerms = createTermSet( + "baseline participant role", + "baseline", + "control diet", + "control group", + "control", + "initial time point", + "normal", + "placebo", + "reference subject role", + "reference substance role", + "to be treated with placebo role", + "untreated", + "wild type control", + "wild type genotype", + "wild type" + ); + /** + * Ontology terms we treat as baseline. + */ + private static final Set controlGroupUris = createTermSet( + "http://mged.sourceforge.net/ontologies/MGEDOntology.owl#wild_type", + "http://ontology.neuinfo.org/NIF/DigitalEntities/NIF-Investigation.owl#birnlex_2001", // normal control_group (retired) + "http://ontology.neuinfo.org/NIF/DigitalEntities/NIF-Investigation.owl#birnlex_2201", // control_group, new version (retired) + "http://purl.obolibrary.org/obo/OBI_0000025", // reference substance + "http://purl.obolibrary.org/obo/OBI_0000143", // baseline participant role + "http://purl.obolibrary.org/obo/OBI_0000220", // reference subject role + "http://purl.obolibrary.org/obo/OBI_0000825", // to be treated with placebo + "http://purl.obolibrary.org/obo/OBI_0100046", // phosphate buffered saline + "http://purl.org/nbirn/birnlex/ontology/BIRNLex-Investigation.owl#birnlex_2201", // control group, old + "http://www.ebi.ac.uk/efo/EFO_0001461", // control + "http://www.ebi.ac.uk/efo/EFO_0001674", // placebo + "http://www.ebi.ac.uk/efo/EFO_0004425",// initial time point + "http://www.ebi.ac.uk/efo/EFO_0005168" // wild type genotype + ); - BaselineSelection.controlGroupTerms - .add( "http://www.ebi.ac.uk/efo/EFO_0004425".toLowerCase() ); // initial time point + /** + * Create an immutable, case-insensitive set. + */ + private static Set createTermSet( String... terms ) { + Set c = new TreeSet<>( Comparator.nullsLast( String.CASE_INSENSITIVE_ORDER ) ); + c.addAll( Arrays.asList( terms ) ); + return Collections.unmodifiableSet( c ); } + /** + * Check if a given factor value indicates a baseline condition. + */ public static boolean isBaselineCondition( FactorValue factorValue ) { - - if ( factorValue.getIsBaseline() != null ) - return factorValue.getIsBaseline(); - - // for backwards compatibility we check anyway - if ( factorValue.getMeasurement() != null ) { return false; - } else if ( factorValue.getCharacteristics().isEmpty() ) { - /* - * Just use the value. - */ - return StringUtils.isNotBlank( factorValue.getValue() ) && BaselineSelection.controlGroupTerms - .contains( factorValue.getValue().toLowerCase() ); - } else { - for ( Characteristic c : factorValue.getCharacteristics() ) { - if ( isBaselineCondition( c ) ) - return true; - } } - return false; + if ( factorValue.getIsBaseline() != null ) { + return factorValue.getIsBaseline(); + } + //noinspection deprecation + return factorValue.getCharacteristics().stream().anyMatch( BaselineSelection::isBaselineCondition ) + // for backwards compatibility we check anyway + || BaselineSelection.controlGroupTerms.contains( normalizeTerm( factorValue.getValue() ) ); } /** - * @param c characteristic - * @return true if this looks like a baseline condition + * Check if a given statement indicates a baseline condition. + */ + public static boolean isBaselineCondition( Statement c ) { + return BaselineSelection.controlGroupUris.contains( c.getSubjectUri() ) + || BaselineSelection.controlGroupUris.contains( c.getObjectUri() ) + || BaselineSelection.controlGroupUris.contains( c.getSecondObjectUri() ) + // free text checks + || ( c.getSubjectUri() == null && BaselineSelection.controlGroupTerms.contains( normalizeTerm( c.getSubject() ) ) ) + || ( c.getObjectUri() == null && BaselineSelection.controlGroupTerms.contains( normalizeTerm( c.getObject() ) ) ) + || ( c.getSecondObjectUri() == null && BaselineSelection.controlGroupTerms.contains( normalizeTerm( c.getSecondObject() ) ) ); + } + + /** + * Check if a given characteristic indicate a baseline condition. */ public static boolean isBaselineCondition( Characteristic c ) { - String valueUri = c.getValueUri(); + return BaselineSelection.controlGroupUris.contains( c.getValueUri() ) + || ( c.getValueUri() == null && BaselineSelection.controlGroupTerms.contains( normalizeTerm( c.getValue() ) ) ); + } - if ( StringUtils.isNotBlank( valueUri ) && BaselineSelection.controlGroupTerms - .contains( valueUri.toLowerCase() ) ) { - return true; + private static String normalizeTerm( String term ) { + if ( term == null ) { + return null; } - - return StringUtils.isNotBlank( c.getValue() ) && BaselineSelection.controlGroupTerms - .contains( c.getValue().toLowerCase() ); + return normalizeSpace( term.replace( '_', ' ' ) ); } /** * Check if this factor value is the baseline, overriding other possible baselines. - * - * @param fv factor value - * @return true if given fv is forced baseline + *

+ * A baseline can be *forced* in two ways: either by setting {@link FactorValue#setIsBaseline(Boolean)} to true or + * by adding a characteristic with the {@code FORCED_BASELINE_VALUE_URI} URI. In practice, this is not much + * different from {@link #isBaselineCondition(Statement)}, but there might be cases where you would want to indicate + * that the baseline was explicitly forced. */ public static boolean isForcedBaseline( FactorValue fv ) { - if ( fv.getMeasurement() != null || fv.getCharacteristics().isEmpty() ) { + if ( fv.getMeasurement() != null ) { return false; } - for ( Characteristic c : fv.getCharacteristics() ) { - String valueUri = c.getValueUri(); - if ( StringUtils.isNotBlank( valueUri ) && valueUri.toLowerCase() - .equals( BaselineSelection.FORCED_BASELINE_VALUE_URI ) ) { - return true; - } - + if ( fv.getIsBaseline() != null ) { + return fv.getIsBaseline(); } - return false; + return fv.getCharacteristics().stream().anyMatch( BaselineSelection::isForcedBaseline ); + } + + private static boolean isForcedBaseline( Statement stmt ) { + return BaselineSelection.FORCED_BASELINE_VALUE_URI.equalsIgnoreCase( stmt.getSubjectUri() ) + || BaselineSelection.FORCED_BASELINE_VALUE_URI.equalsIgnoreCase( stmt.getObjectUri() ) + || BaselineSelection.FORCED_BASELINE_VALUE_URI.equalsIgnoreCase( stmt.getSecondObjectUri() ); } } diff --git a/gemma-core/src/main/java/ubic/gemma/core/analysis/expression/diff/LinearModelAnalyzer.java b/gemma-core/src/main/java/ubic/gemma/core/analysis/expression/diff/LinearModelAnalyzer.java index ff3d7bce1b..7ccf21a933 100644 --- a/gemma-core/src/main/java/ubic/gemma/core/analysis/expression/diff/LinearModelAnalyzer.java +++ b/gemma-core/src/main/java/ubic/gemma/core/analysis/expression/diff/LinearModelAnalyzer.java @@ -471,7 +471,7 @@ public Collection run( ExpressionExperiment expr */ ExpressionExperimentSubSet eeSubSet = ExpressionExperimentSubSet.Factory.newInstance(); eeSubSet.setSourceExperiment( expressionExperiment ); - eeSubSet.setName( "Subset for " + subsetFactorValue ); + eeSubSet.setName( "Subset for " + FactorValueUtils.getSummaryString( subsetFactorValue ) ); Collection bioAssays = new HashSet<>(); for ( BioMaterial bm : bioMaterials ) { bioAssays.addAll( bm.getBioAssaysUsedIn() ); diff --git a/gemma-core/src/main/java/ubic/gemma/core/analysis/preprocess/SplitExperimentServiceImpl.java b/gemma-core/src/main/java/ubic/gemma/core/analysis/preprocess/SplitExperimentServiceImpl.java index c6b5da277b..9c5916759d 100644 --- a/gemma-core/src/main/java/ubic/gemma/core/analysis/preprocess/SplitExperimentServiceImpl.java +++ b/gemma-core/src/main/java/ubic/gemma/core/analysis/preprocess/SplitExperimentServiceImpl.java @@ -40,14 +40,12 @@ import ubic.gemma.model.expression.bioAssayData.RawExpressionDataVector; import ubic.gemma.model.expression.biomaterial.BioMaterial; import ubic.gemma.model.expression.biomaterial.Treatment; -import ubic.gemma.model.expression.experiment.ExperimentalDesign; -import ubic.gemma.model.expression.experiment.ExperimentalFactor; -import ubic.gemma.model.expression.experiment.ExpressionExperiment; -import ubic.gemma.model.expression.experiment.FactorValue; +import ubic.gemma.model.expression.experiment.*; import ubic.gemma.persistence.persister.Persister; import ubic.gemma.persistence.service.expression.bioAssayData.RawExpressionDataVectorService; import ubic.gemma.persistence.service.expression.experiment.ExpressionExperimentService; import ubic.gemma.persistence.service.expression.experiment.ExpressionExperimentSetService; +import ubic.gemma.persistence.service.expression.experiment.FactorValueService; import java.util.*; @@ -87,6 +85,9 @@ public class SplitExperimentServiceImpl implements SplitExperimentService { @Autowired private ExpressionExperimentSetService expressionExperimentSetService; + @Autowired + private FactorValueService factorValueService; + /* * (non-Javadoc) * @@ -324,7 +325,7 @@ public ExpressionExperimentSet split( ExpressionExperiment toSplit, Experimental ExpressionExperimentSet g = ExpressionExperimentSet.Factory.newInstance(); g.setDescription( "Parts of " + toSplit.getShortName() + " that were split on " + splitOn.getName() ); g.setName( toSplit.getShortName() + " splits" ); - g.setTaxon( toSplit.getBioAssays().iterator().next().getSampleUsed().getSourceTaxon() ); + g.setTaxon( toSplit.getTaxon() ); g.getExperiments().addAll( result ); g = this.expressionExperimentSetService.create( g ); @@ -344,7 +345,7 @@ public ExpressionExperimentSet split( ExpressionExperiment toSplit, Experimental static String generateNameForSplit( ExpressionExperiment toSplit, int splitNumber, FactorValue splitValue ) { String template = "Split part %d of: %s [%s = %s]"; String originalName = StringUtils.strip( toSplit.getName() ); - String factorValueString = splitValue.getDescriptiveString(); + String factorValueString = FactorValueUtils.getSummaryString( splitValue ); String newFullName = String.format( template, splitNumber, originalName, StringUtils.strip( splitValue.getExperimentalFactor().getCategory() != null ? splitValue.getExperimentalFactor().getCategory().getValue() : @@ -397,6 +398,7 @@ private Collection cloneExperimentalFactors( Collection result = new HashSet<>(); for ( ExperimentalFactor ef : experimentalFactors ) { ExperimentalFactor clone = ExperimentalFactor.Factory.newInstance(); + //noinspection deprecation clone.setAnnotations( this.cloneCharacteristics( ef.getAnnotations() ) ); if ( ef.getCategory() != null ) { clone.setCategory( this.cloneCharacteristic( ef.getCategory() ) ); @@ -418,8 +420,9 @@ private Collection cloneFactorValues( Collection facto Collection result = new HashSet<>(); for ( FactorValue fv : factorValues ) { FactorValue clone = FactorValue.Factory.newInstance( ef ); - clone.setCharacteristics( this.cloneCharacteristics( fv.getCharacteristics() ) ); + clone.setCharacteristics( cloneStatements( fv ) ); clone.setIsBaseline( fv.getIsBaseline() ); + //noinspection deprecation clone.setValue( fv.getValue() ); clone.setMeasurement( this.cloneMeasurement( fv.getMeasurement() ) ); result.add( clone ); @@ -430,6 +433,37 @@ private Collection cloneFactorValues( Collection facto return result; } + private Set cloneStatements( FactorValue fv ) { + Collection ch = fv.getCharacteristics(); + // pair of original -> clone + List result = new ArrayList<>( ch.size() ); + for ( Statement s : ch ) { + result.add( cloneStatement( s ) ); + } + return new HashSet<>( result ); + } + + private Statement cloneStatement( Statement s ) { + Statement clone = Statement.Factory.newInstance(); + clone.setName( s.getName() ); + clone.setDescription( s.getDescription() ); + clone.setOriginalValue( s.getOriginalValue() ); + clone.setSubject( s.getSubject() ); + clone.setSubjectUri( s.getSubjectUri() ); + clone.setCategory( s.getCategory() ); + clone.setCategoryUri( s.getCategoryUri() ); + clone.setEvidenceCode( s.getEvidenceCode() ); + clone.setPredicate( s.getPredicate() ); + clone.setPredicateUri( s.getPredicateUri() ); + clone.setObject( s.getObject() ); + clone.setObjectUri( s.getObjectUri() ); + clone.setSecondPredicate( s.getSecondPredicate() ); + clone.setSecondPredicateUri( s.getSecondPredicateUri() ); + clone.setSecondObject( s.getSecondObject() ); + clone.setSecondObjectUri( s.getSecondObjectUri() ); + return clone; + } + private Measurement cloneMeasurement( Measurement measurement ) { if ( measurement == null ) return null; @@ -451,6 +485,7 @@ private QuantitationType cloneQt( QuantitationType qt, ExpressionExperiment spli clone.setIsBackground( qt.getIsBackground() ); clone.setIsBackgroundSubtracted( qt.getIsBackgroundSubtracted() ); clone.setIsBatchCorrected( qt.getIsBatchCorrected() ); + //noinspection deprecation clone.setIsMaskedPreferred( qt.getIsMaskedPreferred() ); clone.setIsNormalized( qt.getIsNormalized() ); clone.setIsPreferred( qt.getIsPreferred() ); @@ -473,8 +508,16 @@ private Set cloneCharacteristics( Collection ch } private Characteristic cloneCharacteristic( Characteristic c ) { - return Characteristic.Factory.newInstance( c.getName(), c.getDescription(), c.getValue(), c.getValueUri(), - c.getCategory(), c.getCategoryUri(), c.getEvidenceCode() ); + Characteristic clone = Characteristic.Factory.newInstance(); + clone.setName( c.getName() ); + clone.setDescription( c.getDescription() ); + clone.setCategory( c.getCategory() ); + clone.setCategoryUri( c.getCategoryUri() ); + clone.setValue( c.getValue() ); + clone.setValueUri( c.getValueUri() ); + clone.setOriginalValue( c.getOriginalValue() ); + clone.setEvidenceCode( c.getEvidenceCode() ); + return clone; } private BioAssay cloneBioAssay( BioAssay ba ) { @@ -500,8 +543,14 @@ private BioAssay cloneBioAssay( BioAssay ba ) { private DatabaseEntry cloneAccession( DatabaseEntry de ) { if ( de == null ) return null; - return DatabaseEntry.Factory.newInstance( de.getAccession(), de.getAccessionVersion(), de.getUri(), - de.getExternalDatabase() ); + DatabaseEntry clone = DatabaseEntry.Factory.newInstance(); + clone.setAccession( de.getAccession() ); + clone.setAccessionVersion( de.getAccessionVersion() ); + //noinspection deprecation + clone.setUri( de.getUri() ); + clone.setExternalDatabase( de.getExternalDatabase() ); + ; + return clone; } private BioMaterial cloneBioMaterial( BioMaterial bm, BioAssay ba ) { diff --git a/gemma-core/src/main/java/ubic/gemma/core/analysis/preprocess/batcheffects/BatchEffectDetails.java b/gemma-core/src/main/java/ubic/gemma/core/analysis/preprocess/batcheffects/BatchEffectDetails.java index ea28370c11..f7db994f4a 100644 --- a/gemma-core/src/main/java/ubic/gemma/core/analysis/preprocess/batcheffects/BatchEffectDetails.java +++ b/gemma-core/src/main/java/ubic/gemma/core/analysis/preprocess/batcheffects/BatchEffectDetails.java @@ -14,7 +14,11 @@ */ package ubic.gemma.core.analysis.preprocess.batcheffects; -import ubic.gemma.model.common.auditAndSecurity.eventType.*; +import org.springframework.util.Assert; +import ubic.gemma.model.common.auditAndSecurity.eventType.BatchInformationFetchingEvent; +import ubic.gemma.model.common.auditAndSecurity.eventType.FailedBatchInformationFetchingEvent; +import ubic.gemma.model.common.auditAndSecurity.eventType.SingletonBatchInvalidEvent; +import ubic.gemma.model.common.auditAndSecurity.eventType.UninformativeFASTQHeadersForBatchingEvent; import javax.annotation.Nullable; @@ -25,75 +29,97 @@ */ public class BatchEffectDetails { - private Integer component = null; + public class BatchEffectStatistics { - private double componentVarianceProportion; - private final boolean dataWasBatchCorrected; - private boolean failedToGetBatchInformation = false; - private Boolean hadSingletonBatches = false; - private Boolean hadUninformativeHeaders = false; + private BatchEffectStatistics() { + + } + + /** + * A PCA component that is explained by the batch factor. It is 1-based. + */ + public int getComponent() { + return component; + } + + /** + * The variance explained by the component. + */ + public double getComponentVarianceProportion() { + return componentVarianceProportion; + } + + /** + * A P-value statistic for that component. + */ + public double getPvalue() { + return pvalue; + } + } + + /** + * Indicate if the batch information is present. + */ private final boolean hasBatchInformation; - private double pvalue; + /** + * Indicate if the batch information is uninformative. + */ + private final boolean hasUninformativeBatchInformation; + /** + * Indicate if the batch information is problematic. + */ + private final boolean hasProblematicBatchInformation; + /** + * Indicate if the dataset has singleton batches (i.e. a batch only one sample). + */ + private final boolean hasSingletonBatches; + /** + * Indicate if batch correction was performed on the expression data. + */ + private final boolean dataWasBatchCorrected; private final boolean singleBatch; - public BatchEffectDetails( @Nullable BatchInformationFetchingEvent infoEvent, boolean dataWasBatchCorrected, boolean singleBatch ) { + /* if present and suitable, those are filled */ + private boolean hasBatchEffectStatistics = false; + private double pvalue; + private int component; + private double componentVarianceProportion; - if ( infoEvent == null ) { - this.hasBatchInformation = false; + public BatchEffectDetails( @Nullable BatchInformationFetchingEvent infoEvent, boolean dataWasBatchCorrected, boolean singleBatch ) { + this.hasBatchInformation = infoEvent != null; + if ( infoEvent != null ) { + this.hasProblematicBatchInformation = FailedBatchInformationFetchingEvent.class.isAssignableFrom( ( infoEvent.getClass() ) ); + this.hasSingletonBatches = SingletonBatchInvalidEvent.class.isAssignableFrom( infoEvent.getClass() ); + this.hasUninformativeBatchInformation = UninformativeFASTQHeadersForBatchingEvent.class.isAssignableFrom( infoEvent.getClass() ); } else { - if ( SingletonBatchInvalidEvent.class.isAssignableFrom( infoEvent.getClass() ) ) { - this.hasBatchInformation = false; - this.hadSingletonBatches = true; - } else if ( UninformativeFASTQHeadersForBatchingEvent.class.isAssignableFrom( infoEvent.getClass() ) ) { - this.hasBatchInformation = false; - this.hadUninformativeHeaders = true; - } else if ( FailedBatchInformationMissingEvent.class.isAssignableFrom( infoEvent.getClass() ) ) { - this.hasBatchInformation = false; - this.failedToGetBatchInformation = true; - } else if ( FailedBatchInformationFetchingEvent.class.isAssignableFrom( ( infoEvent.getClass() ) ) ) { - this.hasBatchInformation = false; - this.failedToGetBatchInformation = true; - } else { - this.hasBatchInformation = true; - } + this.hasProblematicBatchInformation = false; + this.hasSingletonBatches = false; + this.hasUninformativeBatchInformation = false; } - this.dataWasBatchCorrected = dataWasBatchCorrected; this.singleBatch = singleBatch; this.pvalue = 1.0; } - public Integer getComponent() { - return component; - } - - public double getComponentVarianceProportion() { - return componentVarianceProportion; - } - public boolean getDataWasBatchCorrected() { return this.dataWasBatchCorrected; } - public Boolean getHadSingletonBatches() { - return hadSingletonBatches; - } - - public Boolean getHadUninformativeHeaders() { - return hadUninformativeHeaders; + public boolean getHasSingletonBatches() { + return hasSingletonBatches; } - public double getPvalue() { - return pvalue; + public boolean getHasUninformativeBatchInformation() { + return hasUninformativeBatchInformation; } public boolean hasBatchInformation() { return hasBatchInformation; } - public boolean isFailedToGetBatchInformation() { - return failedToGetBatchInformation; + public boolean hasProblematicBatchInformation() { + return hasProblematicBatchInformation; } /** @@ -105,22 +131,33 @@ public boolean isSingleBatch() { return singleBatch; } - public void setComponent( Integer component ) { - this.component = component; - } - - public void setComponentVarianceProportion( double componentVarianceProportion ) { - this.componentVarianceProportion = componentVarianceProportion; + @Nullable + public BatchEffectStatistics getBatchEffectStatistics() { + if ( hasBatchEffectStatistics ) { + return new BatchEffectStatistics(); + } else { + return null; + } } - public void setPvalue( double pvalue ) { - this.pvalue = pvalue; + public void setBatchEffectStatistics( double pVal, int i, double variance ) { + Assert.isTrue( pVal >= 0 ); + Assert.isTrue( pVal <= 1 ); + Assert.isTrue( i >= 1 ); + Assert.isTrue( variance >= 0 ); + this.hasBatchEffectStatistics = true; + this.pvalue = pVal; + this.component = i; + this.componentVarianceProportion = variance; } @Override public String toString() { - return String.format( "BatchEffectDetails [pvalue=%.2g, component=%d, varFraction=%.2f]", pvalue, component, - componentVarianceProportion ); + if ( hasBatchEffectStatistics ) { + return String.format( "BatchEffectDetails [pvalue=%.2g, component=%d, varFraction=%.2f]", pvalue, component, + componentVarianceProportion ); + } else { + return "BatchEffectDetails"; + } } - } diff --git a/gemma-core/src/main/java/ubic/gemma/core/analysis/preprocess/batcheffects/BatchInfoPopulationHelperServiceImpl.java b/gemma-core/src/main/java/ubic/gemma/core/analysis/preprocess/batcheffects/BatchInfoPopulationHelperServiceImpl.java index 4593f6c53d..a2ed01beb9 100644 --- a/gemma-core/src/main/java/ubic/gemma/core/analysis/preprocess/batcheffects/BatchInfoPopulationHelperServiceImpl.java +++ b/gemma-core/src/main/java/ubic/gemma/core/analysis/preprocess/batcheffects/BatchInfoPopulationHelperServiceImpl.java @@ -907,10 +907,10 @@ private ExperimentalFactor createExperimentalFactor( ExpressionExperiment ee FactorValue fv = FactorValue.Factory.newInstance(); fv.setIsBaseline( false ); /* we could set true for the first batch, but nobody cares. */ fv.setValue( batchId ); - Set chars = new HashSet<>(); - Characteristic c = Characteristic.Factory.newInstance(); + Set chars = new HashSet<>(); + Statement c = Statement.Factory.newInstance(); c.setCategory( ExperimentalDesignUtils.BATCH_FACTOR_CATEGORY_NAME ); - c.setValue( batchId ); + c.setSubject( batchId ); c.setCategoryUri( ExperimentalDesignUtils.BATCH_FACTOR_CATEGORY_URI ); c.setEvidenceCode( GOEvidenceCode.IIA ); diff --git a/gemma-core/src/main/java/ubic/gemma/core/analysis/preprocess/batcheffects/BatchInfoPopulationServiceImpl.java b/gemma-core/src/main/java/ubic/gemma/core/analysis/preprocess/batcheffects/BatchInfoPopulationServiceImpl.java index da61835bf1..fb16919694 100644 --- a/gemma-core/src/main/java/ubic/gemma/core/analysis/preprocess/batcheffects/BatchInfoPopulationServiceImpl.java +++ b/gemma-core/src/main/java/ubic/gemma/core/analysis/preprocess/batcheffects/BatchInfoPopulationServiceImpl.java @@ -82,17 +82,8 @@ public static boolean isBatchFactor( ExperimentalFactor ef ) { if ( c == null ) return false; - boolean isBatchFactor = false; - - boolean looksLikeBatch = ef.getName().equals( ExperimentalDesignUtils.BATCH_FACTOR_NAME ); - - if ( c.getCategory() != null && c.getCategory().equals( ExperimentalDesignUtils.BATCH_FACTOR_CATEGORY_NAME ) ) { - isBatchFactor = true; - } else if ( looksLikeBatch ) { - isBatchFactor = true; - } - - return isBatchFactor; + return ExperimentalDesignUtils.BATCH_FACTOR_CATEGORY_NAME.equals( c.getCategory() ) + || ExperimentalDesignUtils.BATCH_FACTOR_NAME.equals( ef.getName() ); } @Autowired diff --git a/gemma-core/src/main/java/ubic/gemma/core/analysis/preprocess/svd/SVDServiceHelperImpl.java b/gemma-core/src/main/java/ubic/gemma/core/analysis/preprocess/svd/SVDServiceHelperImpl.java index 30cf04807d..b81392d5b7 100644 --- a/gemma-core/src/main/java/ubic/gemma/core/analysis/preprocess/svd/SVDServiceHelperImpl.java +++ b/gemma-core/src/main/java/ubic/gemma/core/analysis/preprocess/svd/SVDServiceHelperImpl.java @@ -20,7 +20,8 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import ubic.basecode.dataStructure.matrix.DoubleMatrix; import ubic.basecode.math.CorrelationStats; import ubic.basecode.math.Distance; @@ -54,7 +55,7 @@ * @author paul * @see PrincipalComponentAnalysisService */ -@Component +@Service public class SVDServiceHelperImpl implements SVDServiceHelper { /** @@ -133,6 +134,7 @@ public static void populateBMFMap( Map> bi * @return value or null if there isn't one. */ @Override + @Transactional(readOnly = true) public SVDValueObject retrieveSvd( ExpressionExperiment ee ) { PrincipalComponentAnalysis pca = this.principalComponentAnalysisService.loadForExperiment( ee ); if ( pca == null ) @@ -147,6 +149,7 @@ public SVDValueObject retrieveSvd( ExpressionExperiment ee ) { } @Override + @Transactional public SVDValueObject svd( ExpressionExperiment ee ) throws SVDException { assert ee != null; @@ -177,6 +180,7 @@ public SVDValueObject svd( ExpressionExperiment ee ) throws SVDException { } @Override + @Transactional(readOnly = true) public Map getTopLoadedVectors( ExpressionExperiment ee, int component, int count ) { PrincipalComponentAnalysis pca = principalComponentAnalysisService.loadForExperiment( ee ); @@ -253,11 +257,13 @@ public Map getTopLoadedVectors( Expressio } @Override + @Transactional(readOnly = true) public boolean hasPca( ExpressionExperiment ee ) { return this.retrieveSvd( ee ) != null; } @Override + @Transactional(readOnly = true) public Set getImportantFactors( ExpressionExperiment ee, Collection experimentalFactors, Double importanceThreshold ) { Set importantFactors = new HashSet<>(); @@ -295,6 +301,7 @@ public Set getImportantFactors( ExpressionExperiment ee, } @Override + @Transactional(readOnly = true) public SVDValueObject svdFactorAnalysis( PrincipalComponentAnalysis pca ) { BioAssayDimension bad = pca.getBioAssayDimension(); @@ -335,6 +342,7 @@ public SVDValueObject svdFactorAnalysis( PrincipalComponentAnalysis pca ) { } @Override + @Transactional(readOnly = true) public SVDValueObject svdFactorAnalysis( ExpressionExperiment ee ) { PrincipalComponentAnalysis pca = principalComponentAnalysisService.loadForExperiment( ee ); if ( pca == null ) { @@ -426,7 +434,7 @@ private void analyzeComponent( SVDValueObject svo, int componentNumber, DoubleMa boolean initializing = false; if ( !svo.getFactors().containsKey( ef.getId() ) ) { - svo.getFactors().put( ef.getId(), new ArrayList() ); + svo.getFactors().put( ef.getId(), new ArrayList<>() ); initializing = true; } diff --git a/gemma-core/src/main/java/ubic/gemma/core/analysis/preprocess/svd/SVDServiceImpl.java b/gemma-core/src/main/java/ubic/gemma/core/analysis/preprocess/svd/SVDServiceImpl.java index 913dbf35e2..0716b83bf2 100644 --- a/gemma-core/src/main/java/ubic/gemma/core/analysis/preprocess/svd/SVDServiceImpl.java +++ b/gemma-core/src/main/java/ubic/gemma/core/analysis/preprocess/svd/SVDServiceImpl.java @@ -1,13 +1,13 @@ /* * The Gemma project - * + * * Copyright (c) 2011 University of British Columbia - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. @@ -16,9 +16,9 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import ubic.gemma.model.analysis.expression.pca.ProbeLoading; import ubic.gemma.model.expression.bioAssayData.DoubleVectorValueObject; -import ubic.gemma.model.expression.experiment.ExpressionExperiment; import ubic.gemma.persistence.service.expression.experiment.ExpressionExperimentService; import java.util.Map; @@ -41,44 +41,33 @@ public class SVDServiceImpl implements SVDService { * @return value or null if there isn't one. */ @Override + @Transactional(readOnly = true) public SVDValueObject getSvd( Long eeId ) { - ExpressionExperiment ee = expressionExperimentService.load( eeId ); - return svdServiceHelper.retrieveSvd( ee ); + return svdServiceHelper.retrieveSvd( expressionExperimentService.loadOrFail( eeId ) ); } @Override + @Transactional(readOnly = true) public SVDValueObject getSvdFactorAnalysis( Long eeId ) { - - ExpressionExperiment ee = expressionExperimentService.load( eeId ); - - return svdServiceHelper.svdFactorAnalysis( ee ); + return svdServiceHelper.svdFactorAnalysis( expressionExperimentService.loadOrFail( eeId ) ); } @Override + @Transactional(readOnly = true) public Map getTopLoadedVectors( Long eeId, int component, int count ) { - - ExpressionExperiment ee = expressionExperimentService.load( eeId ); - - if ( ee == null ) return null; - - return svdServiceHelper.getTopLoadedVectors( expressionExperimentService.thawBioAssays( ee ), component, count ); + return svdServiceHelper.getTopLoadedVectors( expressionExperimentService.thawBioAssays( expressionExperimentService.loadOrFail( eeId ) ), component, count ); } @Override + @Transactional(readOnly = true) public boolean hasPca( Long eeId ) { - ExpressionExperiment ee = expressionExperimentService.load( eeId ); - - return svdServiceHelper.hasPca( ee ); - + return svdServiceHelper.hasPca( expressionExperimentService.loadOrFail( eeId ) ); } @Override + @Transactional public SVDValueObject svd( Long eeId ) throws SVDException { - - ExpressionExperiment ee = expressionExperimentService.load( eeId ); - - return svdServiceHelper.svd( ee ); + return svdServiceHelper.svd( expressionExperimentService.loadOrFail( eeId ) ); } - } diff --git a/gemma-core/src/main/java/ubic/gemma/core/analysis/report/DatabaseViewGeneratorImpl.java b/gemma-core/src/main/java/ubic/gemma/core/analysis/report/DatabaseViewGeneratorImpl.java index aa6de7750e..f98c5769f5 100644 --- a/gemma-core/src/main/java/ubic/gemma/core/analysis/report/DatabaseViewGeneratorImpl.java +++ b/gemma-core/src/main/java/ubic/gemma/core/analysis/report/DatabaseViewGeneratorImpl.java @@ -35,6 +35,7 @@ import ubic.gemma.model.expression.experiment.ExperimentalFactor; import ubic.gemma.model.expression.experiment.ExpressionExperiment; import ubic.gemma.model.expression.experiment.FactorValue; +import ubic.gemma.model.expression.experiment.FactorValueUtils; import ubic.gemma.model.genome.Gene; import ubic.gemma.model.genome.Taxon; import ubic.gemma.persistence.service.analysis.expression.diff.DifferentialExpressionAnalysisService; @@ -119,7 +120,7 @@ private void generateDatasetTissueView( Integer limit, Collection"; if ( !Objects.equals( confound, ee.getBatchConfound() ) ) { ee.setBatchConfound( confound ); auditTrailService.addUpdateEvent( ee, BatchProblemsUpdateEvent.class, - ExpressionExperimentReportServiceImpl.NOTE_UPDATED_CONFOUND, confound != null ? confound : "" ); - log.info( "New batch confound for " + ee + ": " + ( confound != null ? confound : "" ) ); + ExpressionExperimentReportServiceImpl.NOTE_UPDATED_CONFOUND, confoundSummary ); + log.info( "New batch confound for " + ee + ": " + confoundSummary ); } - if ( !Objects.equals( effect, ee.getBatchEffect() ) ) { - auditTrailService.addUpdateEvent( ee, BatchProblemsUpdateEvent.class, - ExpressionExperimentReportServiceImpl.NOTE_UPDATED_EFFECT, effect ); + if ( !Objects.equals( effect, ee.getBatchEffect() ) || !Objects.equals( effectStatistics, ee.getBatchEffectStatistics() ) ) { ee.setBatchEffect( effect ); - log.info( "New batch effect for " + ee + ": " + effect ); + ee.setBatchEffectStatistics( effectStatistics ); + auditTrailService.addUpdateEvent( ee, BatchProblemsUpdateEvent.class, + ExpressionExperimentReportServiceImpl.NOTE_UPDATED_EFFECT, effectSummary ); + log.info( "New batch effect for " + ee + ": " + effectSummary ); } } diff --git a/gemma-core/src/main/java/ubic/gemma/core/analysis/service/ExpressionAnalysisResultSetFileServiceImpl.java b/gemma-core/src/main/java/ubic/gemma/core/analysis/service/ExpressionAnalysisResultSetFileServiceImpl.java index 5a4cff238d..48e6c79c49 100644 --- a/gemma-core/src/main/java/ubic/gemma/core/analysis/service/ExpressionAnalysisResultSetFileServiceImpl.java +++ b/gemma-core/src/main/java/ubic/gemma/core/analysis/service/ExpressionAnalysisResultSetFileServiceImpl.java @@ -118,7 +118,7 @@ private String formatMeasurement( Measurement measurement ) { + ( measurement.getUnit() != null ? measurement.getUnit().getUnitNameCV() : "" ); } - private String formatCharacteristics( Collection characteristics ) { + private String formatCharacteristics( Collection characteristics ) { return characteristics.stream().map( Characteristic::getValue ).collect( Collectors.joining( ", " ) ); } diff --git a/gemma-core/src/main/java/ubic/gemma/core/analysis/util/ExperimentalDesignUtils.java b/gemma-core/src/main/java/ubic/gemma/core/analysis/util/ExperimentalDesignUtils.java index ed5fe0296a..c533fc5e75 100644 --- a/gemma-core/src/main/java/ubic/gemma/core/analysis/util/ExperimentalDesignUtils.java +++ b/gemma-core/src/main/java/ubic/gemma/core/analysis/util/ExperimentalDesignUtils.java @@ -22,10 +22,7 @@ import ubic.gemma.model.common.description.Characteristic; import ubic.gemma.model.common.measurement.Measurement; import ubic.gemma.model.expression.biomaterial.BioMaterial; -import ubic.gemma.model.expression.experiment.ExperimentalFactor; -import ubic.gemma.model.expression.experiment.ExperimentalFactorValueObject; -import ubic.gemma.model.expression.experiment.FactorType; -import ubic.gemma.model.expression.experiment.FactorValue; +import ubic.gemma.model.expression.experiment.*; import ubic.gemma.persistence.service.expression.experiment.ExperimentalFactorService; import java.util.*; @@ -233,23 +230,6 @@ public static String nameForR( FactorValue fv, boolean isBaseline ) { return ExperimentalDesignUtils.FACTOR_VALUE_RNAME_PREFIX + fv.getId() + ( isBaseline ? "_base" : "" ); } - public static String prettyString( FactorValue fv ) { - - if ( fv.getMeasurement() != null ) { - return fv.getMeasurement().getValue(); - } else if ( fv.getCharacteristics().isEmpty() ) { - return fv.getValue(); - } - StringBuilder buf = new StringBuilder(); - for ( Characteristic c : fv.getCharacteristics() ) { - buf.append( c.getValue() ); - if ( fv.getCharacteristics().size() > 1 ) - buf.append( " | " ); - } - return buf.toString(); - - } - /** * @param factors factors * @param baselines baselines diff --git a/gemma-core/src/main/java/ubic/gemma/core/association/phenotype/PhenotypeAssoOntologyHelper.java b/gemma-core/src/main/java/ubic/gemma/core/association/phenotype/PhenotypeAssoOntologyHelper.java index ae8c8077c0..88e47eb82b 100644 --- a/gemma-core/src/main/java/ubic/gemma/core/association/phenotype/PhenotypeAssoOntologyHelper.java +++ b/gemma-core/src/main/java/ubic/gemma/core/association/phenotype/PhenotypeAssoOntologyHelper.java @@ -18,7 +18,7 @@ import ubic.basecode.ontology.providers.OntologyService; import ubic.gemma.core.search.SearchException; import ubic.gemma.model.common.description.Characteristic; -import ubic.gemma.model.genome.gene.phenotype.valueObject.CharacteristicValueObject; +import ubic.gemma.model.common.description.CharacteristicValueObject; import javax.annotation.Nullable; import java.util.Collection; diff --git a/gemma-core/src/main/java/ubic/gemma/core/association/phenotype/PhenotypeAssoOntologyHelperImpl.java b/gemma-core/src/main/java/ubic/gemma/core/association/phenotype/PhenotypeAssoOntologyHelperImpl.java index a2e138754f..4f7409ac43 100644 --- a/gemma-core/src/main/java/ubic/gemma/core/association/phenotype/PhenotypeAssoOntologyHelperImpl.java +++ b/gemma-core/src/main/java/ubic/gemma/core/association/phenotype/PhenotypeAssoOntologyHelperImpl.java @@ -28,7 +28,7 @@ import ubic.gemma.core.search.BaseCodeOntologySearchException; import ubic.gemma.core.search.SearchException; import ubic.gemma.model.common.description.Characteristic; -import ubic.gemma.model.genome.gene.phenotype.valueObject.CharacteristicValueObject; +import ubic.gemma.model.common.description.CharacteristicValueObject; import java.util.*; @@ -169,13 +169,9 @@ public Characteristic valueUri2Characteristic( String valueUri ) { */ private Set ontology2CharacteristicValueObject( Collection ontologyTerms ) { - Set characteristicsVO = new HashSet<>(); - for ( OntologyTerm ontologyTerm : ontologyTerms ) { - CharacteristicValueObject phenotype = new CharacteristicValueObject( -1L, - ontologyTerm.getLabel().toLowerCase(), ontologyTerm.getUri() ); - characteristicsVO.add( phenotype ); + characteristicsVO.add( new CharacteristicValueObject( ontologyTerm.getLabel().toLowerCase(), ontologyTerm.getUri() ) ); } return characteristicsVO; } diff --git a/gemma-core/src/main/java/ubic/gemma/core/association/phenotype/PhenotypeAssociationManagerService.java b/gemma-core/src/main/java/ubic/gemma/core/association/phenotype/PhenotypeAssociationManagerService.java index 492228129e..046abd61c2 100644 --- a/gemma-core/src/main/java/ubic/gemma/core/association/phenotype/PhenotypeAssociationManagerService.java +++ b/gemma-core/src/main/java/ubic/gemma/core/association/phenotype/PhenotypeAssociationManagerService.java @@ -19,6 +19,7 @@ import ubic.gemma.core.search.SearchException; import ubic.gemma.model.association.phenotype.PhenotypeAssociation; import ubic.gemma.model.common.description.BibliographicReferenceValueObject; +import ubic.gemma.model.common.description.CharacteristicValueObject; import ubic.gemma.model.common.description.ExternalDatabaseValueObject; import ubic.gemma.model.genome.Taxon; import ubic.gemma.model.genome.gene.GeneValueObject; diff --git a/gemma-core/src/main/java/ubic/gemma/core/association/phenotype/PhenotypeAssociationManagerServiceImpl.java b/gemma-core/src/main/java/ubic/gemma/core/association/phenotype/PhenotypeAssociationManagerServiceImpl.java index 2aeb9676f2..3bcec1822a 100644 --- a/gemma-core/src/main/java/ubic/gemma/core/association/phenotype/PhenotypeAssociationManagerServiceImpl.java +++ b/gemma-core/src/main/java/ubic/gemma/core/association/phenotype/PhenotypeAssociationManagerServiceImpl.java @@ -769,7 +769,7 @@ public Collection searchInDatabaseForPhenotype( Strin } return ontologyTermsFound.stream() - .map( t -> new CharacteristicValueObject( -1L, t.getLabel().toLowerCase(), t.getUri() ) ) + .map( t -> new CharacteristicValueObject( t.getLabel().toLowerCase(), t.getUri() ) ) .limit( maxResults > 0 ? maxResults : Long.MAX_VALUE ) .collect( Collectors.toCollection( TreeSet::new ) ); } @@ -824,7 +824,7 @@ public Collection searchOntologyForPhenotypes( String .loadAllNeurocartaPhenotypes(); for ( PhenotypeValueObject pvo : allNeurocartaPhenotypes ) { - CharacteristicValueObject cha = new CharacteristicValueObject( -1L, pvo.getValue(), pvo.getValueUri() ); + CharacteristicValueObject cha = new CharacteristicValueObject( pvo.getValue(), pvo.getValueUri() ); // set flag for UI, flag if the phenotype is on the Gene or if in the database cha.setAlreadyPresentOnGene( true ); cha.setAlreadyPresentInDatabase( true ); @@ -1752,7 +1752,7 @@ private TreeCharacteristicValueObject ontology2TreeCharacteristicValueObjects( O } } - return new TreeCharacteristicValueObject( -1L, ontologyTerm.getLabel(), ontologyTerm.getUri(), children ); + return new TreeCharacteristicValueObject( ontologyTerm.getLabel(), ontologyTerm.getUri(), children ); } private void countPrivateGeneForEachNode( TreeCharacteristicValueObject tc, Map> phenotypesGenesAssociations, Set visited ) { @@ -2021,7 +2021,7 @@ private void findParentRoot( TreeCharacteristicValueObject tc, if ( alreadyOnTree != null ) { alreadyOnTree.getChildren().add( tc ); } else { - TreeCharacteristicValueObject tree = new TreeCharacteristicValueObject( -1L, onTerm.getLabel(), + TreeCharacteristicValueObject tree = new TreeCharacteristicValueObject( onTerm.getLabel(), onTerm.getUri() ); // add children to the parent diff --git a/gemma-core/src/main/java/ubic/gemma/core/datastructure/matrix/BaseExpressionDataMatrix.java b/gemma-core/src/main/java/ubic/gemma/core/datastructure/matrix/BaseExpressionDataMatrix.java index 053080ec94..d189998f2e 100644 --- a/gemma-core/src/main/java/ubic/gemma/core/datastructure/matrix/BaseExpressionDataMatrix.java +++ b/gemma-core/src/main/java/ubic/gemma/core/datastructure/matrix/BaseExpressionDataMatrix.java @@ -258,7 +258,7 @@ void addToRowMaps( Integer row, CompositeSequence designElement ) { * For example, in the following diagram "-" indicates a biomaterial, while "*" indicates a bioassay. Each row of * "*" indicates samples run on a different microarray design (a different bio assay material). In the examples we * assume there is just a single biomaterial dimension. - * + * *

      * ---------------
      * *****              -- only a few samples run on this platform
@@ -268,7 +268,7 @@ void addToRowMaps( Integer row, CompositeSequence designElement ) {
      * 

* A simpler case: *

- * + * *
      * ---------------
      * ***************
@@ -278,7 +278,7 @@ void addToRowMaps( Integer row, CompositeSequence designElement ) {
      * 

* A more typical and easy case (one microarray design used): *

- * + * *
      * ----------------
      * ****************
@@ -286,7 +286,7 @@ void addToRowMaps( Integer row, CompositeSequence designElement ) {
      * 

* If every sample was run on two different array designs: *

- * + * *
      * ----------------
      * ****************
@@ -294,7 +294,7 @@ void addToRowMaps( Integer row, CompositeSequence designElement ) {
      * 
*

* Every sample was run on a different array design: - * + * *

      * -----------------------
      * ******
@@ -384,7 +384,9 @@ void selectVectors( Collection vectors ) {
                 this.getQuantitationTypes().add( vectorQuantitationType );
             } else {
                 if ( quantitationType != vectorQuantitationType ) {
-                    throw new IllegalArgumentException( "Cannot pass vectors from more than one quantitation type" );
+                    throw new IllegalArgumentException( "Cannot pass vectors from more than one quantitation type: " +
+                            vectorQuantitationType + " vs "
+                            + quantitationType );
                 }
 
             }
diff --git a/gemma-core/src/main/java/ubic/gemma/core/datastructure/matrix/ExperimentalDesignWriter.java b/gemma-core/src/main/java/ubic/gemma/core/datastructure/matrix/ExperimentalDesignWriter.java
index 55f9f1bdb6..80b3d8afa1 100644
--- a/gemma-core/src/main/java/ubic/gemma/core/datastructure/matrix/ExperimentalDesignWriter.java
+++ b/gemma-core/src/main/java/ubic/gemma/core/datastructure/matrix/ExperimentalDesignWriter.java
@@ -141,7 +141,7 @@ private void writeHeader( ExpressionExperiment expressionExperiment, Collection<
 
         for ( ExperimentalFactor ef : factors ) {
             buf.append( ExperimentalDesignImporterImpl.EXPERIMENTAL_FACTOR_DESCRIPTION_LINE_INDICATOR );
-            buf.append( ef.getName().replaceAll("\\s", ".") ).append( " :" );
+            buf.append( ef.getName().replaceAll( "\\s", "." ) ).append( " :" );
             if ( ef.getCategory() != null ) {
                 buf.append( " Category=" ).append( ef.getCategory().getValue().replaceAll( "\\s", "_" ) );
             }
diff --git a/gemma-core/src/main/java/ubic/gemma/core/datastructure/matrix/ExpressionDataMatrixColumnSort.java b/gemma-core/src/main/java/ubic/gemma/core/datastructure/matrix/ExpressionDataMatrixColumnSort.java
index c919ac3642..2333fad2f5 100644
--- a/gemma-core/src/main/java/ubic/gemma/core/datastructure/matrix/ExpressionDataMatrixColumnSort.java
+++ b/gemma-core/src/main/java/ubic/gemma/core/datastructure/matrix/ExpressionDataMatrixColumnSort.java
@@ -118,7 +118,7 @@ public static Map getBaselineLevels( List getBaselineLevels( List getFactors( Collection matrix, int
     }
 
     /**
+     * Produce a value for representing a factor value.
+     * 

+ * In the context of the design file, this is focusing on the value (i.e. subjects or measurement value) itself and + * not its metadata which are instead exposed in the file header. + *

* Replaces spaces and hyphens with underscores. - * * @param factorValue FV * @return replaced string */ public static String constructFactorValueName( FactorValue factorValue ) { - - StringBuilder buf = new StringBuilder(); - - if ( factorValue.getCharacteristics().size() > 0 ) { - for ( Characteristic c : factorValue.getCharacteristics() ) { - buf.append( StringUtils.strip( c.getValue() ) ); - if ( factorValue.getCharacteristics().size() > 1 ) - buf.append( " | " ); + String v; + if ( factorValue.getMeasurement() != null ) { + v = factorValue.getMeasurement().getValue(); + } else { + String valueFromStatements = factorValue.getCharacteristics().stream() + .map( Statement::getSubject ) + .collect( Collectors.joining( " | " ) ); + if ( StringUtils.isNotBlank( valueFromStatements ) ) { + v = valueFromStatements; + } else if ( StringUtils.isNotBlank( factorValue.getValue() ) ) { + v = factorValue.getValue(); + } else { + v = ""; // this is treated as NaN in most scenarios } - } else if ( factorValue.getMeasurement() != null ) { - buf.append( factorValue.getMeasurement().getValue() ); - } else if ( StringUtils.isNotBlank( factorValue.getValue() ) ) { - buf.append( StringUtils.strip( factorValue.getValue() ) ); } - - String matchedFactorValue = buf.toString(); - - matchedFactorValue = matchedFactorValue.trim(); - matchedFactorValue = matchedFactorValue.replaceAll( "-", "_" ); - matchedFactorValue = matchedFactorValue.replaceAll( "\\s", "_" ); - return matchedFactorValue; + return v.replace( '-', '_' ) + .replaceAll( "\\s+", "_" ); } /** diff --git a/gemma-core/src/main/java/ubic/gemma/core/expression/experiment/service/ExpressionExperimentSearchServiceImpl.java b/gemma-core/src/main/java/ubic/gemma/core/expression/experiment/service/ExpressionExperimentSearchServiceImpl.java index 2cff9f473b..6866a04e8e 100644 --- a/gemma-core/src/main/java/ubic/gemma/core/expression/experiment/service/ExpressionExperimentSearchServiceImpl.java +++ b/gemma-core/src/main/java/ubic/gemma/core/expression/experiment/service/ExpressionExperimentSearchServiceImpl.java @@ -18,8 +18,8 @@ */ package ubic.gemma.core.expression.experiment.service; -import com.google.common.collect.Sets; import gemma.gsec.SecurityService; +import org.apache.commons.collections4.SetUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -116,7 +116,7 @@ public Collection searchExpressionExperiments( } current = new HashSet<>( this.searchExpressionExperiments( s ) ); - all = Sets.intersection( all, current ); + all = SetUtils.intersection( all, current ); } return all; } diff --git a/gemma-core/src/main/java/ubic/gemma/core/genome/gene/service/GeneServiceImpl.java b/gemma-core/src/main/java/ubic/gemma/core/genome/gene/service/GeneServiceImpl.java index 0b21f1338d..6d02a65461 100755 --- a/gemma-core/src/main/java/ubic/gemma/core/genome/gene/service/GeneServiceImpl.java +++ b/gemma-core/src/main/java/ubic/gemma/core/genome/gene/service/GeneServiceImpl.java @@ -44,7 +44,7 @@ import ubic.gemma.model.genome.PhysicalLocationValueObject; import ubic.gemma.model.genome.Taxon; import ubic.gemma.model.genome.gene.*; -import ubic.gemma.model.genome.gene.phenotype.valueObject.CharacteristicValueObject; +import ubic.gemma.model.common.description.CharacteristicValueObject; import ubic.gemma.persistence.service.AbstractFilteringVoEnabledService; import ubic.gemma.persistence.service.AbstractService; import ubic.gemma.persistence.service.association.Gene2GOAssociationService; @@ -307,6 +307,7 @@ public GeneValueObject loadFullyPopulatedValueObject( Long id ) { GeneValueObject gvo = GeneValueObject.convert2ValueObject( gene ); + // FIXME: this is redundant as aliases are setup by the converter Collection aliasObjects = gene.getAliases(); SortedSet aliasStrings = new TreeSet<>(); for ( GeneAlias ga : aliasObjects ) { diff --git a/gemma-core/src/main/java/ubic/gemma/core/job/executor/common/TaskPostProcessing.java b/gemma-core/src/main/java/ubic/gemma/core/job/executor/common/TaskPostProcessing.java index ffa0301dea..8bf906a887 100644 --- a/gemma-core/src/main/java/ubic/gemma/core/job/executor/common/TaskPostProcessing.java +++ b/gemma-core/src/main/java/ubic/gemma/core/job/executor/common/TaskPostProcessing.java @@ -18,10 +18,10 @@ */ package ubic.gemma.core.job.executor.common; -import com.google.common.util.concurrent.ListenableFuture; import ubic.gemma.core.job.EmailNotificationContext; import ubic.gemma.core.job.TaskResult; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; /** @@ -29,5 +29,5 @@ * date: 10/02/13 */ public interface TaskPostProcessing { - void addEmailNotification( ListenableFuture future, EmailNotificationContext context, Executor executor ); + void addEmailNotification( CompletableFuture future, EmailNotificationContext context, Executor executor ); } diff --git a/gemma-core/src/main/java/ubic/gemma/core/job/executor/common/TaskPostProcessingImpl.java b/gemma-core/src/main/java/ubic/gemma/core/job/executor/common/TaskPostProcessingImpl.java index 152ede054d..f7d399c9c6 100644 --- a/gemma-core/src/main/java/ubic/gemma/core/job/executor/common/TaskPostProcessingImpl.java +++ b/gemma-core/src/main/java/ubic/gemma/core/job/executor/common/TaskPostProcessingImpl.java @@ -18,9 +18,6 @@ */ package ubic.gemma.core.job.executor.common; -import com.google.common.util.concurrent.FutureCallback; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -29,6 +26,7 @@ import ubic.gemma.core.job.TaskResult; import ubic.gemma.core.util.MailUtils; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; /** @@ -42,29 +40,13 @@ public class TaskPostProcessingImpl implements TaskPostProcessing { private MailUtils mailUtils; @Override - public void addEmailNotification( ListenableFuture future, EmailNotificationContext context, Executor executor ) { - FutureCallback emailNotificationCallback = this.createEmailNotificationFutureCallback( context ); - // This will be called when future with our running task is done. - Futures.addCallback( future, emailNotificationCallback, executor ); + public void addEmailNotification( CompletableFuture future, EmailNotificationContext context, Executor executor ) { + future.thenAcceptAsync( taskResult -> { + // This will be called when future with our running task is done. + mailUtils.sendTaskCompletedNotificationEmail( context, taskResult ); + }, executor ).exceptionally( throwable -> { + TaskPostProcessingImpl.log.error( "Shouldn't happen since we take care of exceptions inside ExecutingTask. ", throwable ); + return null; + } ); } - - private FutureCallback createEmailNotificationFutureCallback( final EmailNotificationContext context ) { - - return new FutureCallback() { - private final EmailNotificationContext emailNotificationContext = context; - - @Override - public void onSuccess( TaskResult taskResult ) { - mailUtils.sendTaskCompletedNotificationEmail( emailNotificationContext, taskResult ); - } - - @Override - public void onFailure( Throwable throwable ) { - TaskPostProcessingImpl.log - .error( "Shouldn't happen since we take care of exceptions inside ExecutingTask. " + throwable - .getMessage() ); - } - }; - } - } diff --git a/gemma-core/src/main/java/ubic/gemma/core/job/executor/webapp/SubmittedTaskLocal.java b/gemma-core/src/main/java/ubic/gemma/core/job/executor/webapp/SubmittedTaskLocal.java index 48e446de21..5648815c7a 100644 --- a/gemma-core/src/main/java/ubic/gemma/core/job/executor/webapp/SubmittedTaskLocal.java +++ b/gemma-core/src/main/java/ubic/gemma/core/job/executor/webapp/SubmittedTaskLocal.java @@ -18,7 +18,6 @@ */ package ubic.gemma.core.job.executor.webapp; -import com.google.common.util.concurrent.ListenableFuture; import ubic.gemma.core.job.EmailNotificationContext; import ubic.gemma.core.job.TaskCommand; import ubic.gemma.core.job.TaskResult; @@ -27,9 +26,7 @@ import java.util.Date; import java.util.Deque; import java.util.Queue; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executor; -import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.*; /** * SubmittedTask implementation representing the task running on local TaskRunningService. @@ -39,7 +36,7 @@ public class SubmittedTaskLocal extends SubmittedTaskAbstract { private final TaskPostProcessing taskPostProcessing; private final Deque progressUpdates = new LinkedBlockingDeque<>(); private final Executor executor; - private ListenableFuture future; + private CompletableFuture future; public SubmittedTaskLocal( TaskCommand taskCommand, TaskPostProcessing taskPostProcessing, Executor executor ) { super( taskCommand ); @@ -91,14 +88,13 @@ public synchronized void requestCancellation() { } } - @SuppressWarnings("unchecked") @Override public synchronized void addEmailAlert() { if ( emailAlert ) return; emailAlert = true; assert taskPostProcessing != null : "Task postprocessing was null"; - taskPostProcessing.addEmailNotification( ( ListenableFuture ) future, + taskPostProcessing.addEmailNotification( future, new EmailNotificationContext( taskCommand.getTaskId(), taskCommand.getSubmitter(), taskCommand.getTaskClass().getSimpleName() ), executor ); } @@ -123,14 +119,14 @@ public synchronized boolean isDone() { @SuppressWarnings("unused") // Possible external use - ListenableFuture getFuture() { + CompletableFuture getFuture() { return future; } /* * Package-private methods, used by TaskRunningService */ - void setFuture( ListenableFuture future ) { + void setFuture( CompletableFuture future ) { this.future = future; } diff --git a/gemma-core/src/main/java/ubic/gemma/core/job/executor/webapp/TaskRunningServiceImpl.java b/gemma-core/src/main/java/ubic/gemma/core/job/executor/webapp/TaskRunningServiceImpl.java index db4153de5c..c865b22f7f 100644 --- a/gemma-core/src/main/java/ubic/gemma/core/job/executor/webapp/TaskRunningServiceImpl.java +++ b/gemma-core/src/main/java/ubic/gemma/core/job/executor/webapp/TaskRunningServiceImpl.java @@ -18,14 +18,12 @@ */ package ubic.gemma.core.job.executor.webapp; -import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.ListeningExecutorService; -import com.google.common.util.concurrent.MoreExecutors; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.concurrent.DelegatingSecurityContextCallable; import org.springframework.stereotype.Component; +import org.springframework.util.Assert; import ubic.gemma.core.job.SubmittedTask; import ubic.gemma.core.job.TaskCommand; import ubic.gemma.core.job.TaskResult; @@ -37,10 +35,7 @@ import java.util.Collection; import java.util.Date; import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.Executors; - -import static com.google.common.base.Preconditions.checkNotNull; +import java.util.concurrent.*; /** * Handles the execution of tasks in threads that can be checked by clients later. @@ -51,8 +46,7 @@ @Component public class TaskRunningServiceImpl implements TaskRunningService { private static final Log log = LogFactory.getLog( TaskRunningServiceImpl.class ); - private final ListeningExecutorService executorService = MoreExecutors - .listeningDecorator( Executors.newFixedThreadPool( 20 ) ); + private final ExecutorService executorService = Executors.newFixedThreadPool( 20 ); private final Map submittedTasks = new ConcurrentHashMap<>(); @Autowired @@ -116,7 +110,14 @@ public void onProgress( String message ) { } } ); - ListenableFuture future = executorService.submit( new DelegatingSecurityContextCallable<>( executingTask, taskCommand.getSecurityContext() ) ); + Callable callable = new DelegatingSecurityContextCallable<>( executingTask, taskCommand.getSecurityContext() ); + CompletableFuture future = CompletableFuture.supplyAsync( () -> { + try { + return callable.call(); + } catch ( Exception e ) { + throw new RuntimeException( e ); + } + }, executorService ); submittedTask.setFuture( future ); // Adding post-processing steps, they will run on future completion. @@ -140,10 +141,10 @@ public String submitTaskCommand( final C taskCommand ) { } private void checkTask( Task task ) { - checkNotNull( task, "Must provide a task." ); + Assert.notNull( task, "Must provide a task." ); } private void checkTaskCommand( TaskCommand taskCommand ) { - checkNotNull( taskCommand.getTaskId(), "Must have taskId." ); + Assert.notNull( taskCommand.getTaskId(), "Must have taskId." ); } } diff --git a/gemma-core/src/main/java/ubic/gemma/core/loader/expression/geo/GeoConverter.java b/gemma-core/src/main/java/ubic/gemma/core/loader/expression/geo/GeoConverter.java index fffaef2d93..3da296754c 100644 --- a/gemma-core/src/main/java/ubic/gemma/core/loader/expression/geo/GeoConverter.java +++ b/gemma-core/src/main/java/ubic/gemma/core/loader/expression/geo/GeoConverter.java @@ -38,9 +38,13 @@ public interface GeoConverter extends Converter { @Override Collection convert( Collection geoObjects ); + Collection convert( Collection geoObjects, boolean skipDataVectors ); + @Override Object convert( GeoData geoObject ); + Object convert(GeoData geoObject, boolean skipDataVectors); + /** * Converts Geo subsets to experimental factors. This adds a new factor value to the experimental factor of an * experimental design, and adds the factor value to each BioMaterial of a specific BioAssay. diff --git a/gemma-core/src/main/java/ubic/gemma/core/loader/expression/geo/GeoConverterImpl.java b/gemma-core/src/main/java/ubic/gemma/core/loader/expression/geo/GeoConverterImpl.java index 00a8752f5c..6a3db6c13f 100644 --- a/gemma-core/src/main/java/ubic/gemma/core/loader/expression/geo/GeoConverterImpl.java +++ b/gemma-core/src/main/java/ubic/gemma/core/loader/expression/geo/GeoConverterImpl.java @@ -18,23 +18,6 @@ */ package ubic.gemma.core.loader.expression.geo; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - import org.apache.commons.lang3.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -43,34 +26,19 @@ import org.springframework.context.annotation.Scope; import org.springframework.core.io.ClassPathResource; import org.springframework.stereotype.Component; - import ubic.basecode.io.ByteArrayConverter; import ubic.gemma.core.loader.expression.arrayDesign.ArrayDesignSequenceProcessingServiceImpl; -import ubic.gemma.core.loader.expression.geo.model.GeoChannel; -import ubic.gemma.core.loader.expression.geo.model.GeoContact; -import ubic.gemma.core.loader.expression.geo.model.GeoData; -import ubic.gemma.core.loader.expression.geo.model.GeoDataset; +import ubic.gemma.core.loader.expression.geo.model.*; import ubic.gemma.core.loader.expression.geo.model.GeoDataset.ExperimentType; import ubic.gemma.core.loader.expression.geo.model.GeoDataset.PlatformType; -import ubic.gemma.core.loader.expression.geo.model.GeoPlatform; -import ubic.gemma.core.loader.expression.geo.model.GeoReplication; import ubic.gemma.core.loader.expression.geo.model.GeoReplication.ReplicationType; -import ubic.gemma.core.loader.expression.geo.model.GeoSample; -import ubic.gemma.core.loader.expression.geo.model.GeoSeries; import ubic.gemma.core.loader.expression.geo.model.GeoSeries.SeriesType; -import ubic.gemma.core.loader.expression.geo.model.GeoSubset; -import ubic.gemma.core.loader.expression.geo.model.GeoValues; -import ubic.gemma.core.loader.expression.geo.model.GeoVariable; import ubic.gemma.core.loader.expression.geo.model.GeoVariable.VariableType; import ubic.gemma.core.loader.expression.geo.util.GeoConstants; import ubic.gemma.core.loader.util.parser.ExternalDatabaseUtils; import ubic.gemma.model.association.GOEvidenceCode; import ubic.gemma.model.common.auditAndSecurity.Contact; -import ubic.gemma.model.common.description.BibliographicReference; -import ubic.gemma.model.common.description.Characteristic; -import ubic.gemma.model.common.description.DatabaseEntry; -import ubic.gemma.model.common.description.DatabaseType; -import ubic.gemma.model.common.description.ExternalDatabase; +import ubic.gemma.model.common.description.*; import ubic.gemma.model.common.quantitationtype.PrimitiveType; import ubic.gemma.model.common.quantitationtype.QuantitationType; import ubic.gemma.model.expression.arrayDesign.ArrayDesign; @@ -81,20 +49,23 @@ import ubic.gemma.model.expression.biomaterial.BioMaterial; import ubic.gemma.model.expression.biomaterial.Treatment; import ubic.gemma.model.expression.designElement.CompositeSequence; -import ubic.gemma.model.expression.experiment.ExperimentalDesign; -import ubic.gemma.model.expression.experiment.ExperimentalFactor; -import ubic.gemma.model.expression.experiment.ExpressionExperiment; -import ubic.gemma.model.expression.experiment.FactorType; -import ubic.gemma.model.expression.experiment.FactorValue; +import ubic.gemma.model.expression.experiment.*; import ubic.gemma.model.genome.Taxon; import ubic.gemma.model.genome.biosequence.BioSequence; import ubic.gemma.model.genome.biosequence.PolymerType; import ubic.gemma.model.genome.biosequence.SequenceType; -import ubic.gemma.model.genome.gene.phenotype.valueObject.CharacteristicBasicValueObject; import ubic.gemma.persistence.service.common.description.ExternalDatabaseService; import ubic.gemma.persistence.service.genome.taxon.TaxonService; import ubic.gemma.persistence.util.Settings; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + /** * Convert GEO domain objects into Gemma objects. Usually we trigger this by passing in GeoSeries objects. * GEO has four basic kinds of objects: Platforms (ArrayDesigns), Samples (BioAssays), Series (Experiments) and DataSets @@ -165,6 +136,7 @@ public class GeoConverterImpl implements GeoConverter { private ExternalDatabase genbank; private boolean splitByPlatform = false; private boolean forceConvertElements = false; + private boolean skipDataVectors = false; @Override public void clear() { @@ -175,8 +147,17 @@ public void clear() { taxonScientificNameMap.clear(); } + @Override public Collection convert( Collection geoObjects ) { + return convert( geoObjects, false ); + } + + @Override + public Collection convert( Collection geoObjects, boolean skipDataVectors ) { + + this.skipDataVectors = skipDataVectors; + for ( Object geoObject : geoObjects ) { Object convertedObject = this.convert( ( GeoData ) geoObject ); if ( convertedObject != null ) { @@ -195,6 +176,14 @@ public Collection convert( Collection geoObjects ) { @Override public Object convert( GeoData geoObject ) { + return convert( geoObject, false ); + } + + @Override + public Object convert( GeoData geoObject, boolean skipDataVectors ) { + + this.skipDataVectors = skipDataVectors; + if ( geoObject == null ) { GeoConverterImpl.log.warn( "Null object" ); return null; @@ -232,8 +221,8 @@ public void convertSubsetToExperimentalFactor( ExpressionExperiment expExp, GeoS Characteristic term = Characteristic.Factory.newInstance(); this.convertVariableType( term, geoSubSet.getType() ); term.setDescription( "Converted from GEO subset " + geoSubSet.getGeoAccession() ); - term.setValue( term.getCategory() );// for experimentalfactor.category we just copy the characteristic category - term.setValueUri( term.getCategoryUri() ); // for experimentalfactor.categoryUri we just copy the characteristic categoryUri + term.setValue( term.getCategory() );// for experimentalfactor.category we just copy the rawGEOString category + term.setValueUri( term.getCategoryUri() ); // for experimentalfactor.categoryUri we just copy the rawGEOString categoryUri experimentalFactor.setCategory( term ); experimentalFactor.setType( FactorType.CATEGORICAL ); @@ -580,9 +569,9 @@ private void checkForDataToSkip( GeoSeries series, Collection dataSetsTo if ( sample.getLibSource() != null && sample.getLibSource().equals( "transcriptomic" ) ) { // have to drill down. - if ( sample.getLibStrategy().equals( "RNA-Seq" ) || sample.getLibStrategy().equals( "ncRNA-Seq" ) - || sample.getLibStrategy().equals( "miRNA-Seq" ) || sample.getLibStrategy() - .equals( "ssRNA-seq" ) ) { + if ( sample.getLibStrategy().equals( "RNA-Seq" ) || sample.getLibStrategy() + .equals( "ssRNA-seq" ) || sample.getLibStrategy().equalsIgnoreCase( "Other" )) { + // I've added "other" to be allowed just to avoid being too strict, but removed miRNA and ncRNA. continue; } } @@ -790,15 +779,21 @@ private void convertChannel( GeoSample sample, GeoChannel channel, BioMaterial b * and the mappings are created manually, so many strings will not be matched. * Because characteristics on biomaterials are not mission-critical, it's not worth too much effort. * - * @param characteristic string to be parsed + * @param rawGEOString string to be parsed * @param bioMaterial to which characteristics will be added */ - void parseGEOSampleCharacteristicString( String characteristic, BioMaterial bioMaterial ) { + void parseGEOSampleCharacteristicString( String rawGEOString, BioMaterial bioMaterial ) { /* * Sometimes strings are like Age :8 weeks; Sex: M so we should first split on ";" - sometimes "," is used. + * However, "," or ";" can occur in other situations. So we first have to check whether there are multiple ":". + * Checking for "=" here is not going to work, as the example I have is "lithium use (non-user=0, user = 1):0", so there is still a rare possibility of parsing errors. */ - //log.info( characteristic ); - String[] topFields = characteristic.split( "[;,]" ); + String[] topFields = null; + if ( StringUtils.countMatches( rawGEOString, ":" ) > 1 ) { + topFields = rawGEOString.split( "[;,]" ); + } else { + topFields = new String[] { rawGEOString }; + } for ( String field : topFields ) { @@ -809,7 +804,7 @@ void parseGEOSampleCharacteristicString( String characteristic, BioMaterial bioM if ( fields.length != 2 ) { fields = field.split( "=", 2 ); // this shouldn't occur, but is present in some old GEO records apparently } - String defaultDescription = "GEO Sample characteristic"; + String defaultDescription = "GEO Sample rawGEOString"; if ( fields.length == 2 ) { String category = fields[0].trim().replaceAll( "\t", " " ).replaceAll( "_", " " ); @@ -823,7 +818,7 @@ void parseGEOSampleCharacteristicString( String characteristic, BioMaterial bioM VariableType vartype = GeoVariable.convertStringToType( category ); if ( vartype == null || vartype.equals( VariableType.other ) ) { - log.debug( "Could not parse into VariableType: " + category + " (in: " + characteristic + ")" ); + log.debug( "Could not parse into VariableType: " + category + " (in: " + rawGEOString + ")" ); gemmaChar.setCategory( category ); // This is not one of our "standard" categories, but it's okay gemmaChar.setValue( value ); gemmaChar.setDescription( defaultDescription ); @@ -834,7 +829,7 @@ void parseGEOSampleCharacteristicString( String characteristic, BioMaterial bioM assert !vartype.equals( VariableType.other ); this.convertVariableType( gemmaChar, vartype ); - CharacteristicBasicValueObject mappedValueTerm = ontologyLookupSampleCharacteristic( value, gemmaChar.getCategory() ); + CharacteristicValueObject mappedValueTerm = ontologyLookupSampleCharacteristic( value, gemmaChar.getCategory() ); try { if ( mappedValueTerm != null ) { @@ -849,7 +844,7 @@ void parseGEOSampleCharacteristicString( String characteristic, BioMaterial bioM bioMaterial.getCharacteristics().add( gemmaChar ); } catch ( Exception e ) { // conversion didn't work, fall back. (not sure why this would happen so adding logging) - log.warn( "Could not convert " + field + " to characteristic ", e ); + log.warn( "Could not convert " + field + " to rawGEOString ", e ); this.doFallback( bioMaterial, value, defaultDescription ); } @@ -865,7 +860,7 @@ void parseGEOSampleCharacteristicString( String characteristic, BioMaterial bioM * stored in valueStringToOntologyTermMappings.txt. * */ - private CharacteristicBasicValueObject ontologyLookupSampleCharacteristic( String value, String category ) { + private CharacteristicValueObject ontologyLookupSampleCharacteristic( String value, String category ) { if ( term2OntologyMappings.isEmpty() ) { initializeTerm2OntologyMappings(); } @@ -911,7 +906,7 @@ private void initializeTerm2OntologyMappings() { } if ( !term2OntologyMappings.containsKey( category ) ) { - term2OntologyMappings.put( category, new HashMap() ); + term2OntologyMappings.put( category, new HashMap() ); } if ( term2OntologyMappings.get( category ).containsKey( inputValue ) ) { @@ -919,7 +914,8 @@ private void initializeTerm2OntologyMappings() { continue; } - CharacteristicBasicValueObject c = new CharacteristicBasicValueObject( null, value, valueUri, category, categoryUri ); + // NOTE: extensions via modifiers is not to be supported here, as GEO only has key-value pairs. + CharacteristicValueObject c = new CharacteristicValueObject( value, valueUri, category, categoryUri ); term2OntologyMappings.get( category ).put( inputValue, c ); } } catch ( IOException e ) { @@ -928,7 +924,7 @@ private void initializeTerm2OntologyMappings() { } - private static Map> term2OntologyMappings = new ConcurrentHashMap<>(); + private static Map> term2OntologyMappings = new ConcurrentHashMap<>(); /** * Take contact and contributer information from a GeoSeries and put it in the ExpressionExperiment. @@ -1007,7 +1003,8 @@ private void convertDataset( GeoDataset geoDataset, ExpressionExperiment expExp ad.setDescription( ad.getDescription() + "\nFrom " + platform.getGeoAccession() + "\nLast Updated: " + platform .getLastUpdateDate() ); - this.convertDataSetDataVectors( geoDataset.getSeries().iterator().next().getValues(), geoDataset, expExp ); + if ( !skipDataVectors ) + this.convertDataSetDataVectors( geoDataset.getSeries().iterator().next().getValues(), geoDataset, expExp ); this.convertSubsetAssociations( expExp, geoDataset ); @@ -1288,7 +1285,8 @@ private ArrayDesign convertPlatform( GeoPlatform platform ) { */ private boolean convertPlatformElements( String identifier, GeoPlatform platform, ArrayDesign arrayDesign, Collection externalReferences, String probeOrganismColumn, ExternalDatabase externalDb, - List descriptions, List sequences, List probeOrganism, Taxon primaryTaxon ) { + List descriptions, List sequences, List probeOrganism, Taxon + primaryTaxon ) { /* * This is a very commonly found column name in files, it seems standard in GEO. If we don't find it, it's okay. @@ -1375,7 +1373,8 @@ private boolean convertPlatformElements( String identifier, GeoPlatform platform } private int processId( GeoPlatform platform, ArrayDesign arrayDesign, String probeOrganismColumn, - ExternalDatabase externalDb, List sequences, List probeOrganism, Taxon primaryTaxon, + ExternalDatabase externalDb, List sequences, List probeOrganism, Taxon + primaryTaxon, List cloneIdentifiers, List> externalRefs, Iterator descIter, Pattern refSeqAccessionPattern, boolean strictSelection, List skipped, Collection compositeSequences, int i, String id ) { @@ -1477,12 +1476,12 @@ private void checkCs( ArrayDesign arrayDesign, String externalAccession, String if ( StringUtils.isBlank( externalAccession ) && StringUtils.isBlank( cloneIdentifier ) ) { if ( GeoConverterImpl.log.isDebugEnabled() ) { GeoConverterImpl.log.debug( "Blank external reference and clone id for " + cs + " on " + arrayDesign - + ", no biological characteristic can be added." ); + + ", no biological rawGEOString can be added." ); } } else if ( probeTaxon == null ) { if ( GeoConverterImpl.log.isDebugEnabled() ) { GeoConverterImpl.log.debug( "No valid taxon identified for " + cs + " on " + arrayDesign - + ", no biological characteristic can be added." ); + + ", no biological rawGEOString can be added." ); } } else if ( probeTaxon.getId() != null ) { // IF there is no taxon given for probe do not create a biosequence otherwise bombs as there is no taxon @@ -1646,8 +1645,8 @@ private void convertPubMedIds( GeoSeries series, ExpressionExperiment expExp ) { /* * Note that this is apparently never actually used? */ - private Characteristic convertReplicatationType( ReplicationType repType ) { - Characteristic result = Characteristic.Factory.newInstance(); + private Statement convertReplicatationType( ReplicationType repType ) { + Statement result = Statement.Factory.newInstance(); result.setCategory( "replicate" ); result.setCategoryUri( "http://www.ebi.ac.uk/efo/EFO_0000683" /* replicate */ ); result.setEvidenceCode( GOEvidenceCode.IIA ); @@ -1693,7 +1692,7 @@ private ExperimentalFactor convertReplicationToFactor( GeoReplication replicatio private FactorValue convertReplicationToFactorValue( GeoReplication replication ) { FactorValue factorValue = FactorValue.Factory.newInstance(); - Characteristic term = this.convertReplicatationType( replication.getType() ); + Statement term = this.convertReplicatationType( replication.getType() ); factorValue.setValue( term.getValue() ); factorValue.getCharacteristics().add( term ); return factorValue; @@ -1712,7 +1711,8 @@ private void convertReplicationToFactorValue( GeoReplication replication, Experi * @param experimentalDesign experimental design * @return BA */ - private BioAssay convertSample( GeoSample sample, BioMaterial bioMaterial, ExperimentalDesign experimentalDesign ) { + private BioAssay convertSample( GeoSample sample, BioMaterial bioMaterial, ExperimentalDesign + experimentalDesign ) { if ( sample == null ) { GeoConverterImpl.log.warn( "Null sample" ); return null; @@ -1775,17 +1775,19 @@ private BioAssay convertSample( GeoSample sample, BioMaterial bioMaterial, Exper // Taxon lastTaxon = null; - for ( GeoPlatform platform : sample.getPlatforms() ) { - ArrayDesign arrayDesign; - if ( seenPlatforms.containsKey( platform.getGeoAccession() ) ) { - arrayDesign = seenPlatforms.get( platform.getGeoAccession() ); - } else { - // platform not exist yet - arrayDesign = this.convertPlatform( platform ); - } + if ( !this.skipDataVectors ) { + for ( GeoPlatform platform : sample.getPlatforms() ) { + ArrayDesign arrayDesign; + if ( seenPlatforms.containsKey( platform.getGeoAccession() ) ) { + arrayDesign = seenPlatforms.get( platform.getGeoAccession() ); + } else { + // platform not exist yet + arrayDesign = this.convertPlatform( platform ); + } - bioAssay.setArrayDesignUsed( arrayDesign ); + bioAssay.setArrayDesignUsed( arrayDesign ); + } } return bioAssay; @@ -2064,7 +2066,7 @@ private ExpressionExperiment convertSeriesSingle( GeoSeries series ) { if ( dataSets.size() == 0 ) { // we miss extra description and the subset information. - if ( series.getValues().hasData() ) { + if ( series.getValues().hasData() && !this.skipDataVectors ) { this.convertSeriesDataVectors( series, expExp ); } } else { @@ -2193,7 +2195,7 @@ private FactorValue convertSubsetDescriptionToFactorValue( GeoSubset geoSubSet, // By definition each subset defines a new factor value. FactorValue factorValue = FactorValue.Factory.newInstance(); - Characteristic term = Characteristic.Factory.newInstance(); + Statement term = Statement.Factory.newInstance(); this.convertVariableType( term, geoSubSet.getType() ); if ( term.getCategory() != null ) { term.setValue( geoSubSet.getDescription() ); @@ -2219,7 +2221,7 @@ private FactorValue convertSubsetDescriptionToFactorValue( GeoSubset geoSubSet, private FactorValue convertTypeToFactorValue( VariableType type, String value ) { FactorValue factorValue = FactorValue.Factory.newInstance(); - Characteristic term = Characteristic.Factory.newInstance(); + Statement term = Statement.Factory.newInstance(); this.convertVariableType( term, type ); if ( term.getCategory() != null ) { // is this right ??? factorValue.setValue( value ); @@ -2691,7 +2693,7 @@ private void doFallback( BioMaterial bioMaterial, String value, String defaultDe private FactorValue findMatchingExperimentalFactorValue( Collection experimentalFactors, FactorValue convertVariableToFactorValue ) { - Collection characteristics = convertVariableToFactorValue.getCharacteristics(); + Collection characteristics = convertVariableToFactorValue.getCharacteristics(); if ( characteristics.size() > 1 ) throw new UnsupportedOperationException( "Can't handle factor values with multiple characteristics in GEO conversion" ); diff --git a/gemma-core/src/main/java/ubic/gemma/core/loader/expression/geo/model/GeoDataset.java b/gemma-core/src/main/java/ubic/gemma/core/loader/expression/geo/model/GeoDataset.java index 918af18b13..5be88cfba8 100644 --- a/gemma-core/src/main/java/ubic/gemma/core/loader/expression/geo/model/GeoDataset.java +++ b/gemma-core/src/main/java/ubic/gemma/core/loader/expression/geo/model/GeoDataset.java @@ -249,6 +249,8 @@ public static ValueType convertStringToValueType( String string ) { return ValueType.logERatio; case "transformed count": return ValueType.transformedCount; + case "Z-score": + return ValueType.Zscore; default: throw new IllegalArgumentException( "Unknown value type " + string ); } @@ -536,7 +538,7 @@ public enum SampleType { } public enum ValueType { - count, logRatio, log2Ratio, log10ratio, logERatio, transformedCount + count, logRatio, log2Ratio, log10ratio, logERatio, transformedCount, Zscore } } diff --git a/gemma-core/src/main/java/ubic/gemma/core/loader/expression/geo/service/GeoServiceImpl.java b/gemma-core/src/main/java/ubic/gemma/core/loader/expression/geo/service/GeoServiceImpl.java index 75f02c70cb..85054d15b6 100644 --- a/gemma-core/src/main/java/ubic/gemma/core/loader/expression/geo/service/GeoServiceImpl.java +++ b/gemma-core/src/main/java/ubic/gemma/core/loader/expression/geo/service/GeoServiceImpl.java @@ -311,13 +311,17 @@ public void updateFromGEO( String geoAccession ) { Collection parseResult = geoDomainObjectGenerator.generate( geoAccession ); Object obj = parseResult.iterator().next(); GeoSeries series = ( GeoSeries ) obj; - Collection result = ( Collection ) geoConverter.convert( series ); + Collection result = ( Collection ) geoConverter.convert( series, true ); this.getPubMedInfo( result ); /* * We're never splitting by platform, so we should have only one result. */ - assert result.size() == 1; + if ( result.isEmpty() ) { + throw new IllegalStateException( "No result from fetching and coversion of " + geoAccession ); + } else if (result.size() > 1) { + throw new IllegalStateException( "Got more than one result from fetching and coversion of " + geoAccession ); + } ExpressionExperiment freshFromGEO = result.iterator().next(); diff --git a/gemma-core/src/main/java/ubic/gemma/core/loader/expression/geo/util/GeoConstants.java b/gemma-core/src/main/java/ubic/gemma/core/loader/expression/geo/util/GeoConstants.java index 0cc5306e98..becb5a8e79 100644 --- a/gemma-core/src/main/java/ubic/gemma/core/loader/expression/geo/util/GeoConstants.java +++ b/gemma-core/src/main/java/ubic/gemma/core/loader/expression/geo/util/GeoConstants.java @@ -63,6 +63,7 @@ public class GeoConstants { sequenceColumnNames = new HashSet<>(); GeoConstants.sequenceColumnNames.add( "SEQUENCE" ); // agilent. + GeoConstants.sequenceColumnNames.add( "PROBE_SEQUENCE" ); // e.g. GPL7182 //LMD 24/07/09 Bug 1647 probeOrganismColumnNames = new HashSet<>(); diff --git a/gemma-core/src/main/java/ubic/gemma/core/loader/expression/simple/ExperimentalDesignImporterImpl.java b/gemma-core/src/main/java/ubic/gemma/core/loader/expression/simple/ExperimentalDesignImporterImpl.java index 0c3dc61050..04ede86afb 100644 --- a/gemma-core/src/main/java/ubic/gemma/core/loader/expression/simple/ExperimentalDesignImporterImpl.java +++ b/gemma-core/src/main/java/ubic/gemma/core/loader/expression/simple/ExperimentalDesignImporterImpl.java @@ -218,7 +218,7 @@ private void addExperimentalFactorsToExperimentalDesign( ExperimentalDesign expe * @param experimentalDesign experimental design * @param factorValueLines Lines from file containing factor values and biomaterial ids * @param headerFields header fields - * @return Collection of biomaterials associated with this experiment, this is returned as + * @return Collection of biomaterials associated with this experiment, this is returned as * the biomaterial is in a * bioassay (first one retrieved) */ @@ -366,7 +366,7 @@ private void addFactorValuesToExperimentalFactor( ExperimentalFactor experimenta if ( factorType.equalsIgnoreCase( "CATEGORICAL" ) ) { ExperimentalDesignImporterImpl.log.debug( "Factor is categorical" ); - Characteristic newVc = Characteristic.Factory.newInstance(); + Statement newVc = Statement.Factory.newInstance(); if ( category != null ) { String category2 = category.getCategory(); assert category2 != null; @@ -414,7 +414,7 @@ private void addMeasurementToFactorValueOfTypeContinous( FactorValue factorValue /** * @param experimentalDesign Existing experimental design. * @param experimentalFactorFromFile The experimental factor in the file - * @return Check that experimental design does not already contain the experimental + * @return Check that experimental design does not already contain the experimental * factor. */ private boolean checkForDuplicateExperimentalFactorOnExperimentalDesign( ExperimentalDesign experimentalDesign, @@ -435,7 +435,7 @@ private boolean checkForDuplicateExperimentalFactorOnExperimentalDesign( Experim /** * @param bioMaterial bio material * @param factorValue factor value - * @return This method checks that the biomaterial does not already have a value for the factor. + * @return This method checks that the biomaterial does not already have a value for the factor. */ private boolean checkForDuplicateFactorOnBioMaterial( BioMaterial bioMaterial, FactorValue factorValue ) { boolean foundMatch = false; @@ -463,7 +463,7 @@ private boolean checkForDuplicateFactorOnBioMaterial( BioMaterial bioMaterial, F * @param externalId - the external id stored in the file, which might not be available (so this can * be null or * blank) - * @return The biomaterial in the expression experiment set matching the biosource name + * @return The biomaterial in the expression experiment set matching the biosource name * given in the first column of * the factor value line. */ @@ -501,7 +501,7 @@ private BioMaterial getBioMaterialFromExpressionExperiment( Collection> getMapFactorSampleValues( String[] headerFields, List factorValueLines ) { Map> factorSampleValues = new HashMap<>(); @@ -530,7 +530,7 @@ private Map> getMapFactorSampleValues( String[] headerFields /** * @param bioMaterials bio materials - * @return a map of various strings that we might find in a design importing file to the biomaterials. + * @return a map of various strings that we might find in a design importing file to the biomaterials. */ private Map mapBioMaterialsToNamePossibilities( Collection bioMaterials ) { Map biomaterialsInExpressionExperiment = new HashMap<>(); @@ -575,7 +575,7 @@ private Map mapBioMaterialsToNamePossibilities( Collection< * Does a lookup for the Ontology term to match the category. * * @param category category - * @return vocab characteristic + * @return vocab characteristic */ private Characteristic termForCategoryLookup( String category, Collection terms ) { diff --git a/gemma-core/src/main/java/ubic/gemma/core/ontology/AbstractOntologyResourceSimple.java b/gemma-core/src/main/java/ubic/gemma/core/ontology/AbstractOntologyResourceSimple.java new file mode 100644 index 0000000000..1dd2be213c --- /dev/null +++ b/gemma-core/src/main/java/ubic/gemma/core/ontology/AbstractOntologyResourceSimple.java @@ -0,0 +1,65 @@ +package ubic.gemma.core.ontology; + +import ubic.basecode.ontology.model.OntologyResource; + +import javax.annotation.Nullable; +import java.io.Serializable; +import java.util.Comparator; +import java.util.Objects; + +public abstract class AbstractOntologyResourceSimple implements OntologyResource, Serializable { + + private final String uri, label; + + protected AbstractOntologyResourceSimple( @Nullable String uri, String label ) { + this.uri = uri; + this.label = label; + } + + @Override + public String getLabel() { + return label; + } + + @Override + @Nullable + public String getUri() { + return uri; + } + + @Override + public boolean isObsolete() { + return false; + } + + @Nullable + @Override + public Double getScore() { + return null; + } + + @Override + public int compareTo( OntologyResource other ) { + return Objects.compare( getUri(), other.getUri(), Comparator.nullsLast( Comparator.naturalOrder() ) ); + } + + @Override + public boolean equals( Object obj ) { + if ( this == obj ) return true; + if ( obj == null ) return false; + if ( getClass() != obj.getClass() ) return false; + final OntologyResource other = ( OntologyResource ) obj; + if ( getLabel() == null ) { + if ( other.getLabel() != null ) return false; + } else if ( !getLabel().equals( other.getLabel() ) ) return false; + if ( getUri() == null ) { + if ( other.getUri() != null ) return false; + } else if ( !getUri().equals( other.getUri() ) ) return false; + return true; + } + + @Override + public int hashCode() { + return Objects.hash( label, uri ); + } +} diff --git a/gemma-core/src/main/java/ubic/gemma/core/ontology/FactorValueOntologyService.java b/gemma-core/src/main/java/ubic/gemma/core/ontology/FactorValueOntologyService.java new file mode 100644 index 0000000000..cbc3035321 --- /dev/null +++ b/gemma-core/src/main/java/ubic/gemma/core/ontology/FactorValueOntologyService.java @@ -0,0 +1,51 @@ +package ubic.gemma.core.ontology; + +import lombok.Value; +import ubic.basecode.ontology.model.OntologyIndividual; +import ubic.basecode.ontology.model.OntologyProperty; + +import javax.annotation.Nullable; +import java.io.Writer; +import java.util.Set; + +/** + * Ontology service for factor values and their annotations. + *

+ * There are two kind of entities represented in his ontologies: + *

    + *
  • Factor values (i.e. http://gemma.msl.ubc.ca/ont/TGFVO/1)
  • + *
  • Factor value annotations (i.e. http://gemma.msl.ubc.ca/ont/TGFVO/1/2) which can be either a subject, object or a characteristic
  • + *
+ * TODO: fully implement the {@link ubic.basecode.ontology.providers.OntologyService} interface. + */ +public interface FactorValueOntologyService { + + /** + * Obtain an individual from the ontology by URI. + */ + @Nullable + OntologyIndividual getIndividual( String uri ); + + /** + * Obtain annotations belonging to the given URI representing a factor value. + */ + Set getFactorValueAnnotations( String uri ); + + /** + * Represents an ontology statement. + * TODO: move this into baseCode. + */ + @Value + class OntologyStatement { + OntologyIndividual subject; + OntologyProperty predicate; + OntologyIndividual object; + } + + /** + * Obtain statements related to the given URI representing a factor value. + */ + Set getFactorValueStatements( String uri ); + + void writeToRdf( String iri, Writer writer ); +} diff --git a/gemma-core/src/main/java/ubic/gemma/core/ontology/FactorValueOntologyServiceImpl.java b/gemma-core/src/main/java/ubic/gemma/core/ontology/FactorValueOntologyServiceImpl.java new file mode 100644 index 0000000000..45744412b3 --- /dev/null +++ b/gemma-core/src/main/java/ubic/gemma/core/ontology/FactorValueOntologyServiceImpl.java @@ -0,0 +1,243 @@ +package ubic.gemma.core.ontology; + +import com.hp.hpl.jena.ontology.*; +import com.hp.hpl.jena.rdf.model.ModelFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import ubic.basecode.ontology.model.OntologyIndividual; +import ubic.basecode.ontology.model.OntologyTermSimple; +import ubic.gemma.core.ontology.jena.TGFVO; +import ubic.gemma.model.expression.experiment.FactorValue; +import ubic.gemma.model.expression.experiment.FactorValueUtils; +import ubic.gemma.model.expression.experiment.StatementValueObject; +import ubic.gemma.persistence.service.expression.experiment.FactorValueService; + +import javax.annotation.Nullable; +import java.io.Writer; +import java.util.*; +import java.util.stream.Collectors; + +import static ubic.gemma.core.ontology.FactorValueOntologyUtils.*; + +@Service +public class FactorValueOntologyServiceImpl implements FactorValueOntologyService { + + private final FactorValueService factorValueService; + + @Autowired + public FactorValueOntologyServiceImpl( FactorValueService factorValueService ) { + this.factorValueService = factorValueService; + } + + @Override + @Nullable + public OntologyIndividual getIndividual( String uri ) { + Long fvId = parseUri( uri ); + if ( fvId == null ) { + return null; + } + if ( isAnnotationUri( uri ) ) { + FactorValue fv = factorValueService.load( fvId ); + if ( fv == null ) { + return null; + } + Annotation annotation = getAnnotationsById( fv ).get( uri ); + if ( annotation == null ) { + return null; + } + return createIndividualFromAnnotation( uri, annotation ); + } else { + FactorValue fv = factorValueService.loadWithExperimentalFactor( fvId ); + if ( fv == null ) { + return null; + } + OntologyTermSimple instanceOf; + if ( fv.getExperimentalFactor() != null && fv.getExperimentalFactor().getCategory() != null ) { + String categoryUri = fv.getExperimentalFactor().getCategory().getCategoryUri(); + String category = fv.getExperimentalFactor().getCategory().getCategory(); + instanceOf = new OntologyTermSimple( categoryUri, category ); + } else { + instanceOf = null; + } + return new OntologyIndividualSimple( uri, FactorValueUtils.getSummaryString( fv ), instanceOf ); + } + } + + @Override + public Set getFactorValueAnnotations( String uri ) { + if ( isAnnotationUri( uri ) ) { + // this is a specific annotation ID + return Collections.emptySet(); + } + Long fvId = parseUri( uri ); + if ( fvId == null ) { + return Collections.emptySet(); + } + final FactorValue fv = factorValueService.load( fvId ); + if ( fv == null ) { + return Collections.emptySet(); + } + HashSet individuals = new HashSet<>(); + if ( fv.getMeasurement() != null ) { + OntologyTermSimple measurementClass = new OntologyTermSimple( TGFVO.Measurement.getURI(), "measurement" ); + individuals.add( new OntologyIndividualSimple( null, fv.getMeasurement().getValue(), measurementClass ) ); + } + for ( Map.Entry e : getAnnotationsById( fv ).entrySet() ) { + individuals.add( createIndividualFromAnnotation( e.getKey(), e.getValue() ) ); + } + return individuals; + } + + @Override + public Set getFactorValueStatements( String uri ) { + if ( isAnnotationUri( uri ) ) { + // this is a specific annotation ID + return Collections.emptySet(); + } + Long fvId = parseUri( uri ); + if ( fvId == null ) { + return Collections.emptySet(); + } + final FactorValue fv = factorValueService.load( fvId ); + if ( fv == null ) { + return Collections.emptySet(); + } + Map individuals = getAnnotationsById( fv ); + Set statements = fv.getCharacteristics().stream().map( StatementValueObject::new ).collect( Collectors.toSet() ); + Set ontStatements = new HashSet<>(); + FactorValueOntologyUtils.visitStatements( fvId, statements, ( svo, ids ) -> { + Annotation subjectId = individuals.get( ids.getSubjectId() ); + OntologyIndividual subject = createIndividualFromAnnotation( ids.getSubjectId(), subjectId ); + if ( ids.getObjectId() != null ) { + Annotation objectId = individuals.get( ids.getObjectId() ); + OntologyIndividual object = createIndividualFromAnnotation( ids.getObjectId(), objectId ); + ontStatements.add( new OntologyStatement( subject, new OntologyPropertySimple( svo.getPredicateUri(), svo.getPredicate() ), object ) ); + } + if ( ids.getSecondObjectId() != null ) { + Annotation secondObjectId = individuals.get( ids.getSecondObjectId() ); + OntologyIndividual object = createIndividualFromAnnotation( ids.getSecondObjectId(), secondObjectId ); + ontStatements.add( new OntologyStatement( subject, new OntologyPropertySimple( svo.getPredicateUri(), svo.getPredicate() ), object ) ); + } + } ); + return ontStatements; + } + + private OntologyIndividual createIndividualFromAnnotation( String uri, Annotation annotation ) { + return new OntologyIndividualSimple( uri, annotation.getLabel(), new OntologyTermSimple( annotation.getUri(), annotation.getLabel() ) ); + } + + /** + * Write a small RDF model for a given factor value or annotation. + */ + @Override + public void writeToRdf( String uri, Writer writer ) { + OntModel ontModel = ModelFactory.createOntologyModel( OntModelSpec.OWL_MEM ); + ontModel.setNsPrefix( "obo", "http://purl.obolibrary.org/obo/" ); + ontModel.setNsPrefix( "efo", "http://www.ebi.ac.uk/efo/" ); + ontModel.setNsPrefix( "tgemo", "http://gemma.msl.ubc.ca/ont/" ); + ontModel.setNsPrefix( "tgfvo", TGFVO.NS ); + createFactorValueOrAnnotationIndividual( ontModel, uri ); + ontModel.write( writer, "RDF/XML-ABBREV" ); + } + + private void createFactorValueOrAnnotationIndividual( OntModel ontModel, String uri ) { + Long fvId = parseUri( uri ); + if ( fvId == null ) { + return; + } + + FactorValue fv = factorValueService.loadWithExperimentalFactor( fvId ); + if ( fv == null ) { + return; + } + + Map annotationsById = getAnnotationsById( fv ); + + // this is a specific annotation ID + if ( isAnnotationUri( uri ) ) { + Annotation annotation = annotationsById.get( uri ); + createIndividual( ontModel, uri, annotation.getUri(), annotation.getLabel() ); + return; + } + + OntClass fvClass; + if ( fv.getExperimentalFactor().getCategory() != null ) { + fvClass = ontModel.createClass( fv.getExperimentalFactor().getCategory().getCategoryUri() ); + fvClass.setLabel( fv.getExperimentalFactor().getCategory().getCategory(), null ); + } else { + fvClass = null; + } + Individual fvI = ontModel.createIndividual( uri, fvClass ); + + if ( fv.getMeasurement() == null && annotationsById.isEmpty() ) { + return; + } + + // create basic definitions + ObjectProperty hasAnnotation = ontModel.createObjectProperty( TGFVO.hasAnnotation.getURI() ); + hasAnnotation.setLabel( "has annotation", null ); + ObjectProperty annotationOf = ontModel.createObjectProperty( TGFVO.annotationOf.getURI() ); + annotationOf.setLabel( "annotation of", null ); + annotationOf.setInverseOf( hasAnnotation ); + hasAnnotation.setInverseOf( annotationOf ); + + // extra definitions needed + if ( fv.getMeasurement() != null ) { + ObjectProperty hasMeasurement = ontModel.createObjectProperty( TGFVO.hasMeasurement.getURI() ); + hasMeasurement.setLabel( "has measurement", null ); + ObjectProperty measurementOf = ontModel.createObjectProperty( TGFVO.measurementOf.getURI() ); + measurementOf.setLabel( "measurement of", null ); + hasMeasurement.addSuperProperty( hasAnnotation ); + hasMeasurement.setInverseOf( measurementOf ); + measurementOf.addSuperProperty( annotationOf ); + measurementOf.setInverseOf( hasMeasurement ); + Individual measurement = ontModel.createIndividual( null, TGFVO.Measurement ); + String label = fv.getMeasurement().getValue(); + if ( fv.getMeasurement().getUnit() != null ) { + label += " " + fv.getMeasurement().getUnit().getUnitNameCV(); + } + measurement.setLabel( label, null ); + measurement.addProperty( TGFVO.hasRepresentation, fv.getMeasurement().getRepresentation().name() ); + if ( fv.getMeasurement().getUnit() != null ) { + measurement.addProperty( TGFVO.hasUnit, fv.getMeasurement().getUnit().getUnitNameCV() ); + } + measurement.addProperty( TGFVO.hasValue, fv.getMeasurement().getValue() ); + ontModel.add( fvI, hasMeasurement, measurement ); + } + + for ( Map.Entry e : annotationsById.entrySet() ) { + Individual annot = createIndividual( ontModel, e.getKey(), e.getValue().getUri(), e.getValue().getLabel() ); + ontModel.add( fvI, hasAnnotation, annot ); + } + + List statements = fv.getCharacteristics().stream().map( StatementValueObject::new ).collect( Collectors.toList() ); + FactorValueOntologyUtils.visitStatements( fv.getId(), statements, ( svo, annotationIds ) -> { + Individual subject = createIndividual( ontModel, annotationIds.getSubjectId(), svo.getCategoryUri(), svo.getCategory() ); + if ( annotationIds.getObjectId() != null ) { + Individual object = createIndividual( ontModel, annotationIds.getObjectId(), svo.getObjectUri(), svo.getObject() ); + ObjectProperty predicate = ontModel.createObjectProperty( svo.getPredicateUri() ); + predicate.setLabel( svo.getPredicate(), null ); + ontModel.add( subject, predicate, object ); + } + if ( annotationIds.getSecondObjectId() != null ) { + Individual object = createIndividual( ontModel, annotationIds.getSecondObjectId(), svo.getSecondObjectUri(), svo.getSecondObject() ); + ObjectProperty predicate = ontModel.createObjectProperty( svo.getSecondPredicateUri() ); + predicate.setLabel( svo.getSecondPredicate(), null ); + ontModel.add( subject, predicate, object ); + } + } ); + } + + private Individual createIndividual( OntModel ontModel, String uri, String classUri, String classLabel ) { + OntClass ontClass; + if ( classUri != null ) { + ontClass = ontModel.createClass( classUri ); + ontClass.setLabel( classLabel, null ); + } else { + ontClass = null; + } + Individual indI = ontModel.createIndividual( uri, ontClass ); + indI.setLabel( classLabel, null ); + return indI; + } +} diff --git a/gemma-core/src/main/java/ubic/gemma/core/ontology/FactorValueOntologyUtils.java b/gemma-core/src/main/java/ubic/gemma/core/ontology/FactorValueOntologyUtils.java new file mode 100644 index 0000000000..cb0d9b6d78 --- /dev/null +++ b/gemma-core/src/main/java/ubic/gemma/core/ontology/FactorValueOntologyUtils.java @@ -0,0 +1,154 @@ +package ubic.gemma.core.ontology; + +import lombok.Value; +import org.springframework.util.Assert; +import ubic.gemma.model.expression.experiment.FactorValue; +import ubic.gemma.model.expression.experiment.Statement; +import ubic.gemma.model.expression.experiment.StatementValueObject; + +import javax.annotation.Nullable; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.TreeSet; + +public class FactorValueOntologyUtils { + + private static final String URI_PREFIX = "http://gemma.msl.ubc.ca/ont/TGFVO/"; + + /** + * Obtain a suitable ontology ID for a given factor value ID. + */ + public static String getUri( Long factorValueId ) { + return URI_PREFIX + factorValueId; + } + + /** + * Obtain a suitable ontology ID for a given factor value. + */ + public static String getUri( FactorValue factorValue ) { + Assert.notNull( factorValue.getId() ); + return getUri( factorValue.getId() ); + } + + /** + * Extract a factor value ID from a factor value URI. + */ + @Nullable + public static Long parseUri( String uri ) { + if ( !uri.startsWith( FactorValueOntologyUtils.URI_PREFIX ) ) { + return null; + } + String t = uri.substring( FactorValueOntologyUtils.URI_PREFIX.length() ); + String[] pieces = t.split( "/", 2 ); + try { + return Long.parseLong( pieces[0] ); + } catch ( NumberFormatException e ) { + return null; + } + } + + /** + * Check if a URI refers to an annotation of a factor value. + */ + public static boolean isAnnotationUri( String uri ) { + if ( !uri.startsWith( FactorValueOntologyUtils.URI_PREFIX ) ) { + return false; + } + String t = uri.substring( FactorValueOntologyUtils.URI_PREFIX.length() ); + String[] pieces = t.split( "/", 2 ); + try { + if ( pieces.length == 2 ) { + Long.parseLong( pieces[0] ); + Long.parseLong( pieces[1] ); + return true; + } else { + return false; + } + } catch ( NumberFormatException e ) { + return false; + } + } + + @FunctionalInterface + public interface StatementVisitor { + void accept( StatementValueObject v, U u ) throws E; + } + + /** + * Visit the characteristics of a FactorValue and generate their annotation IDs. + *

+ * Characteristics also include subject-only statements. + */ + public static void visitCharacteristics( Long factorValueId, Collection statements, StatementVisitor visitor ) throws E { + long nextAvailableId = 1L; + for ( StatementValueObject svo : new TreeSet<>( statements ) ) { + visitor.accept( svo, getAnnotationId( factorValueId, nextAvailableId++ ) ); + if ( svo.getObject() != null ) { + nextAvailableId++; + } + if ( svo.getSecondObject() != null ) { + nextAvailableId++; + } + } + } + + @Value + public static class AnnotationIds { + String subjectId; + @Nullable + String objectId; + @Nullable + String secondObjectId; + } + + /** + * Visit the statements of a FactorValue and generate their annotation IDs. + */ + public static void visitStatements( Long factorValueId, Collection statements, StatementVisitor visitor ) throws E { + long nextAvailableId = 1L; + for ( StatementValueObject svo : new TreeSet<>( statements ) ) { + String subjectId = getAnnotationId( factorValueId, nextAvailableId++ ); + String objectId = null, secondObjectId = null; + if ( svo.getObject() != null ) { + objectId = getAnnotationId( factorValueId, nextAvailableId++ ); + } + if ( svo.getSecondObject() != null ) { + secondObjectId = getAnnotationId( factorValueId, nextAvailableId++ ); + } + if ( objectId != null || secondObjectId != null ) { + visitor.accept( svo, new AnnotationIds( subjectId, objectId, secondObjectId ) ); + } + } + } + + @Value + public static class Annotation { + String label; + @Nullable + String uri; + } + + /** + * Create a mapping of annotation IDs to annotations for a FactorValue. + */ + public static Map getAnnotationsById( FactorValue fv ) { + Assert.notNull( fv.getId() ); + Map result = new HashMap<>(); + long nextAvailableId = 1L; + for ( Statement s : new TreeSet<>( fv.getCharacteristics() ) ) { + result.put( getAnnotationId( fv.getId(), nextAvailableId++ ), new Annotation( s.getSubject(), s.getSubjectUri() ) ); + if ( s.getObject() != null ) { + result.put( getAnnotationId( fv.getId(), nextAvailableId++ ), new Annotation( s.getObject(), s.getObjectUri() ) ); + } + if ( s.getSecondObject() != null ) { + result.put( getAnnotationId( fv.getId(), nextAvailableId++ ), new Annotation( s.getSecondObject(), s.getSecondObjectUri() ) ); + } + } + return result; + } + + private static String getAnnotationId( Long factorValueId, Long id ) { + return URI_PREFIX + factorValueId + "/" + id; + } +} diff --git a/gemma-core/src/main/java/ubic/gemma/core/ontology/OntologyIndividualSimple.java b/gemma-core/src/main/java/ubic/gemma/core/ontology/OntologyIndividualSimple.java new file mode 100644 index 0000000000..1b60150d41 --- /dev/null +++ b/gemma-core/src/main/java/ubic/gemma/core/ontology/OntologyIndividualSimple.java @@ -0,0 +1,28 @@ +package ubic.gemma.core.ontology; + +import ubic.basecode.ontology.model.OntologyIndividual; +import ubic.basecode.ontology.model.OntologyTermSimple; + +import javax.annotation.Nullable; + +public class OntologyIndividualSimple extends AbstractOntologyResourceSimple implements OntologyIndividual { + + private final OntologyTermSimple instanceOf; + + /** + * + * @param uri + * @param label + * @param instanceOf the term this individual is an instance of which must be simple since this class has to be + * {@link java.io.Serializable}. + */ + public OntologyIndividualSimple( @Nullable String uri, String label, OntologyTermSimple instanceOf ) { + super( uri, label ); + this.instanceOf = instanceOf; + } + + @Override + public OntologyTermSimple getInstanceOf() { + return instanceOf; + } +} \ No newline at end of file diff --git a/gemma-core/src/main/java/ubic/gemma/core/ontology/OntologyPropertySimple.java b/gemma-core/src/main/java/ubic/gemma/core/ontology/OntologyPropertySimple.java new file mode 100644 index 0000000000..3612fcc46a --- /dev/null +++ b/gemma-core/src/main/java/ubic/gemma/core/ontology/OntologyPropertySimple.java @@ -0,0 +1,27 @@ +package ubic.gemma.core.ontology; + +import ubic.basecode.ontology.model.OntologyProperty; + +import javax.annotation.Nullable; + +/** + * Simple in-memory implementation of {@link OntologyProperty}. + * TODO: move this in baseCode and share some of the implementation details with {@link ubic.basecode.ontology.model.OntologyTermSimple} + * @author poirigui + */ +public class OntologyPropertySimple extends AbstractOntologyResourceSimple implements OntologyProperty { + + /** + * + * @param uri an URI or null if this is a free-text property + * @param label a label for the property + */ + public OntologyPropertySimple( @Nullable String uri, String label ) { + super( uri, label ); + } + + @Override + public boolean isFunctional() { + return false; + } +} diff --git a/gemma-core/src/main/java/ubic/gemma/core/ontology/OntologyService.java b/gemma-core/src/main/java/ubic/gemma/core/ontology/OntologyService.java index d0cb9856f1..dabd33995f 100644 --- a/gemma-core/src/main/java/ubic/gemma/core/ontology/OntologyService.java +++ b/gemma-core/src/main/java/ubic/gemma/core/ontology/OntologyService.java @@ -14,13 +14,14 @@ */ package ubic.gemma.core.ontology; +import ubic.basecode.ontology.model.OntologyProperty; import ubic.basecode.ontology.model.OntologyTerm; import ubic.gemma.core.search.SearchException; import ubic.gemma.model.common.description.Characteristic; import ubic.gemma.model.expression.biomaterial.BioMaterial; import ubic.gemma.model.expression.experiment.ExpressionExperiment; import ubic.gemma.model.genome.Taxon; -import ubic.gemma.model.genome.gene.phenotype.valueObject.CharacteristicValueObject; +import ubic.gemma.model.common.description.CharacteristicValueObject; import javax.annotation.Nullable; import java.util.Collection; @@ -34,15 +35,17 @@ public interface OntologyService { /** - * Will add the give vocab characteristic to the expression experiment. - * Does NOT handle persisting of the experiment afterwards. + *

+ * Locates usages of obsolete terms in Characteristics, ignoring Gene Ontology annotations. Requires the ontologies are loaded into memory. + *

+ *

+ * Will also find terms that are no longer in an ontology we use. + *

* - * @param vc If the evidence code is null, it will be filled in with IC. A category and value must be provided. - * @param ee ee + * @return map of value URI to a representative characteristic using the term. The latter will contain a count + * of how many ocurrences there were. */ - void addExpressionExperimentStatement( Characteristic vc, ExpressionExperiment ee ); - - Map countObsoleteOccurrences( int start, int stop, int step ); + Map findObsoleteTermUsage(); /** * Using the ontology and values in the database, for a search searchQuery given by the client give an ordered list @@ -83,6 +86,12 @@ Collection findExperimentsCharacteristicTags( String */ Collection getCategoryTerms(); + /** + * + * @return terms allowed for the predicate (relationship) in a Characteristic + */ + Collection getRelationTerms(); + /** * Obtain the parents of a collection of terms. * @see OntologyTerm#getParents(boolean, boolean) @@ -121,20 +130,8 @@ Collection findExperimentsCharacteristicTags( String void reindexAllOntologies(); /** - * Reinitialize all the ontologies "from scratch". This is necessary if indices are old etc. This should be + * Reinitialize (and reindex) all the ontologies "from scratch". This is necessary if indices are old etc. This should be * admin-only. */ - void reinitializeAllOntologies(); - - void removeBioMaterialStatement( Long characterId, BioMaterial bm ); - - /** - * Will persist the give vocab characteristic to the given biomaterial - * - * @param bm bm - * @param vc vc - */ - void saveBioMaterialStatement( Characteristic vc, BioMaterial bm ); - - Collection termsToCharacteristics( Collection terms ); + void reinitializeAndReindexAllOntologies(); } \ No newline at end of file diff --git a/gemma-core/src/main/java/ubic/gemma/core/ontology/OntologyServiceImpl.java b/gemma-core/src/main/java/ubic/gemma/core/ontology/OntologyServiceImpl.java index 4732db8e41..8b808d1a38 100644 --- a/gemma-core/src/main/java/ubic/gemma/core/ontology/OntologyServiceImpl.java +++ b/gemma-core/src/main/java/ubic/gemma/core/ontology/OntologyServiceImpl.java @@ -33,11 +33,10 @@ import org.springframework.core.task.TaskExecutor; import org.springframework.stereotype.Service; import ubic.basecode.ontology.model.AnnotationProperty; +import ubic.basecode.ontology.model.OntologyProperty; import ubic.basecode.ontology.model.OntologyTerm; import ubic.basecode.ontology.model.OntologyTermSimple; import ubic.basecode.ontology.providers.ExperimentalFactorOntologyService; -import ubic.basecode.ontology.providers.FMAOntologyService; -import ubic.basecode.ontology.providers.NIFSTDOntologyService; import ubic.basecode.ontology.providers.ObiService; import ubic.basecode.ontology.search.OntologySearch; import ubic.basecode.ontology.search.OntologySearchException; @@ -48,17 +47,13 @@ import ubic.gemma.core.search.SearchException; import ubic.gemma.core.search.SearchResult; import ubic.gemma.core.search.SearchService; -import ubic.gemma.model.association.GOEvidenceCode; import ubic.gemma.model.common.description.Characteristic; +import ubic.gemma.model.common.description.CharacteristicValueObject; import ubic.gemma.model.common.search.SearchSettings; -import ubic.gemma.model.expression.biomaterial.BioMaterial; -import ubic.gemma.model.expression.experiment.ExpressionExperiment; import ubic.gemma.model.genome.Gene; import ubic.gemma.model.genome.Taxon; import ubic.gemma.model.genome.gene.GeneValueObject; -import ubic.gemma.model.genome.gene.phenotype.valueObject.CharacteristicValueObject; import ubic.gemma.persistence.service.common.description.CharacteristicService; -import ubic.gemma.persistence.service.expression.biomaterial.BioMaterialService; import javax.annotation.Nullable; import java.io.BufferedReader; @@ -87,8 +82,6 @@ public class OntologyServiceImpl implements OntologyService, InitializingBean { PARENTS_CACHE_NAME = "OntologyService.parents", CHILDREN_CACHE_NAME = "OntologyService.children"; - @Autowired - private BioMaterialService bioMaterialService; @Autowired private CharacteristicService characteristicService; @Autowired @@ -102,12 +95,6 @@ public class OntologyServiceImpl implements OntologyService, InitializingBean { @Autowired private ExperimentalFactorOntologyService experimentalFactorOntologyService; - @Deprecated - @Autowired - private FMAOntologyService fmaOntologyService; - @Deprecated - @Autowired - private NIFSTDOntologyService nifstdOntologyService; @Autowired private ObiService obiService; @@ -130,12 +117,13 @@ public class OntologyServiceImpl implements OntologyService, InitializingBean { private OntologyCache ontologyCache; private Set categoryTerms = null; + private Set relationTerms = null; + @Override public void afterPropertiesSet() throws Exception { ontologyCache = new OntologyCache( cacheManager.getCache( PARENTS_CACHE_NAME ), cacheManager.getCache( CHILDREN_CACHE_NAME ) ); - if ( ontologyServiceFactories != null ) { + if ( ontologyServiceFactories != null && autoLoadOntologies ) { List enabledOntologyServices = ontologyServiceFactories.stream() - .filter( OntologyServiceFactory::isAutoLoaded ) .map( factory -> { try { return factory.getObject(); @@ -149,13 +137,14 @@ public void afterPropertiesSet() throws Exception { log.info( "The following ontologies are enabled:\n\t" + enabledOntologyServices.stream() .map( ubic.basecode.ontology.providers.OntologyService::toString ) .collect( Collectors.joining( "\n\t" ) ) ); - } else if ( autoLoadOntologies ) { + } else { log.warn( "No ontologies are enabled, consider enabling them by setting 'load.{name}Ontology' options in Gemma.properties." ); } } // remove GeneOntologyService, it was originally not included in the list before bean injection was used ontologyServices.remove( geneOntologyService ); initializeCategoryTerms(); + initializeRelationTerms(); } private void countOccurrences( Map results ) { @@ -399,32 +388,31 @@ public Collection findTermsInexact( String givenQuery @Override public Set getParents( Collection terms, boolean direct, boolean includeAdditionalProperties ) { - return combineInThreads( os -> ontologyCache.getParents( os, terms, direct, includeAdditionalProperties ) ); + Set toQuery = new HashSet<>( terms ); + Set results = new HashSet<>(); + while ( !toQuery.isEmpty() ) { + Set newResults = combineInThreads( os -> ontologyCache.getParents( os, toQuery, direct, includeAdditionalProperties ) ); + results.addAll( newResults ); + // toQuery = newResults - toQuery + newResults.removeAll( toQuery ); + toQuery.clear(); + toQuery.addAll( newResults ); + } + return results; } @Override public Set getChildren( Collection terms, boolean direct, boolean includeAdditionalProperties ) { - StopWatch timer = StopWatch.createStarted(); - try { - return combineInThreads( os -> { - StopWatch timer2 = StopWatch.createStarted(); - try { - return ontologyCache.getChildren( os, terms, direct, includeAdditionalProperties ); - } finally { - if ( timer2.getTime() > 1000 ) { - log.warn( String.format( "Gathering children of %d terms from %s took %d ms", terms.size(), os, timer2.getTime() ) ); - } else { - log.trace( String.format( "Gathering children of %d terms from %s took %d ms", terms.size(), os, timer2.getTime() ) ); - } - } - } ); - } finally { - if ( timer.getTime() > 1000 ) { - log.warn( String.format( "Gathering children of %d terms took %d ms", terms.size(), timer.getTime() ) ); - } else { - log.debug( String.format( "Gathering children of %d terms took %d ms", terms.size(), timer.getTime() ) ); - } + Set toQuery = new HashSet<>( terms ); + Set results = new HashSet<>(); + while ( !toQuery.isEmpty() ) { + Set newResults = combineInThreads( os -> ontologyCache.getChildren( os, toQuery, direct, includeAdditionalProperties ) ); + results.addAll( newResults ); + newResults.removeAll( toQuery ); + toQuery.clear(); + toQuery.addAll( newResults ); } + return results; } @Override @@ -442,6 +430,17 @@ public Collection getCategoryTerms() { .collect( Collectors.toSet() ); } + + @Override + public Collection getRelationTerms() { + // FIXME: it's not quite like categoryTerms so this map operation is probably not needed at all, the relations don't come from any particular ontology + return relationTerms.stream() + .map( term -> { + return term; + } ) + .collect( Collectors.toSet() ); + } + @Override public String getDefinition( String uri ) { if ( uri == null ) return null; @@ -498,7 +497,7 @@ public void reindexAllOntologies() { } @Override - public void reinitializeAllOntologies() { + public void reinitializeAndReindexAllOntologies() { for ( ubic.basecode.ontology.providers.OntologyService serv : this.ontologyServices ) { ontologyTaskExecutor.execute( () -> { serv.initialize( true, true ); @@ -507,75 +506,10 @@ public void reinitializeAllOntologies() { } } - @Override - public void removeBioMaterialStatement( Long characterId, BioMaterial bm ) { - Characteristic vc = characteristicService.load( characterId ); - if ( vc == null ) - throw new IllegalArgumentException( "No characteristic with id=" + characterId + " was foundF" ); - bm.getCharacteristics().remove( vc ); - characteristicService.remove( characterId ); - } - - @Override - public void saveBioMaterialStatement( Characteristic vc, BioMaterial bm ) { - - OntologyServiceImpl.log.debug( "Vocab Characteristic: " + vc ); - - vc.setEvidenceCode( GOEvidenceCode.IC ); // manually added characteristic - Set chars = new HashSet<>(); - chars.add( vc ); - - Set current = bm.getCharacteristics(); - if ( current == null ) - current = new HashSet<>( chars ); - else - current.addAll( chars ); - - for ( Characteristic characteristic : chars ) { - OntologyServiceImpl.log.info( "Adding characteristic to " + bm + " : " + characteristic ); - } - - bm.setCharacteristics( current ); - bioMaterialService.update( bm ); - - } - - @Override - public void addExpressionExperimentStatement( Characteristic vc, ExpressionExperiment ee ) { - if ( vc == null ) { - throw new IllegalArgumentException( "Null characteristic" ); - } - if ( StringUtils.isBlank( vc.getCategory() ) ) { - throw new IllegalArgumentException( "Must provide a category" ); - } - - if ( StringUtils.isBlank( vc.getValue() ) ) { - throw new IllegalArgumentException( "Must provide a value" ); - } - - if ( vc.getEvidenceCode() == null ) { - vc.setEvidenceCode( GOEvidenceCode.IC ); // assume: manually added characteristic - } - - if ( StringUtils.isNotBlank( vc.getValueUri() ) && this.isObsolete( vc.getValueUri() ) ) { - throw new IllegalArgumentException( vc + " is an obsolete term! Not saving." ); - } - - if ( ee == null ) - throw new IllegalArgumentException( "Experiment cannot be null" ); - - OntologyServiceImpl.log - .info( "Adding characteristic '" + vc.getValue() + "' to " + ee.getShortName() + " (ID=" + ee.getId() - + ") : " + vc ); - - ee.getCharacteristics().add( vc ); - } - /** * Convert raw ontology resources into Characteristics. */ - @Override - public Collection termsToCharacteristics( final Collection terms ) { + private Collection termsToCharacteristics( Collection terms ) { Collection results = new HashSet<>(); @@ -598,81 +532,84 @@ public Collection termsToCharacteristics( final Collection countObsoleteOccurrences( int start, int stop, int step ) { - Map vos = new HashMap<>(); - int minId = start; - int maxId = step; + private Characteristic termToCharacteristic( OntologyTerm res ) { + if ( res.isObsolete() ) { + OntologyServiceImpl.log.warn( "Skipping an obsolete term: " + res.getLabel() + " / " + res.getUri() ); + return null; + } + + Characteristic vc = Characteristic.Factory.newInstance(); + vc.setValue( res.getLabel() ); + vc.setValueUri( res.getUri() ); + vc.setDescription( res.getComment() ); - int nullCnt = 0; - int obsoleteCnt = 0; + if ( vc.getValue() == null ) { + OntologyServiceImpl.log + .warn( "Skipping a characteristic with no value: " + res.getLabel() + " / " + res.getUri() ); + return null; + } - // Loading all characteristics in steps - while ( maxId < stop ) { + return vc; + } + + @Override + public Map findObsoleteTermUsage() { + Map vos = new HashMap<>(); - OntologyServiceImpl.log.info( "Checking characteristics with IDs between " + minId + " and " + maxId ); + int start = 0; + int step = 5000; - List ids = new ArrayList<>( step ); - for ( int i = minId; i < maxId + 1; i++ ) { - ids.add( ( long ) i ); - } + int prevObsoleteCnt = 0; + int checked = 0; + CharacteristicValueObject lastObsolete = null; - minId = maxId + 1; - maxId += step; + while ( true ) { - Collection chars = characteristicService.load( ids ); + Collection chars = characteristicService.browse( start, step ); + start += step; if ( chars == null || chars.isEmpty() ) { - OntologyServiceImpl.log.info( "No characteristics in the current ID range, moving on." ); - continue; + break; } - OntologyServiceImpl.log.info( "Found " + chars.size() - + " characteristics in the current ID range, checking for obsoletes." ); - // Detect obsoletes for ( Characteristic ch : chars ) { - if ( StringUtils.isBlank( ch.getValueUri() ) ) { - nullCnt++; - } else if ( this.isObsolete( ch.getValueUri() ) ) { - String key = this.foundValueKey( ch ); - if ( !vos.containsKey( key ) ) { - vos.put( key, new CharacteristicValueObject( ch ) ); - } - vos.get( key ).incrementOccurrenceCount(); - obsoleteCnt++; - OntologyServiceImpl.log.info( "Found obsolete term: " + ch.getValue() + " / " + ch.getValueUri() ); + String valueUri = ch.getValueUri(); + if ( StringUtils.isBlank( valueUri ) ) { + continue; } - } - ids.clear(); - chars.clear(); - } + checked++; - OntologyServiceImpl.log.info( "Terms with empty uri: " + nullCnt ); - OntologyServiceImpl.log.info( "Obsolete terms found: " + obsoleteCnt ); + if ( this.getTerm( valueUri ) == null || this.isObsolete( valueUri ) ) { - return vos; - } + if ( valueUri.startsWith( "http://purl.org/commons/record/ncbi_gene" ) || valueUri.startsWith( "http://purl.obolibrary.org/obo/GO_" ) ) { + // these are false positives, they aren't in an ontology, and we aren't looking at GO Terms. + continue; + } - private Characteristic termToCharacteristic( OntologyTerm res ) { - if ( res.isObsolete() ) { - OntologyServiceImpl.log.warn( "Skipping an obsolete term: " + res.getLabel() + " / " + res.getUri() ); - return null; - } - Characteristic vc = Characteristic.Factory.newInstance(); - vc.setValue( res.getLabel() ); - vc.setValueUri( res.getUri() ); - vc.setDescription( res.getComment() ); + if ( !vos.containsKey( valueUri ) ) { + vos.put( valueUri, new CharacteristicValueObject( ch ) ); + } + vos.get( valueUri ).incrementOccurrenceCount(); + if ( log.isDebugEnabled() ) + OntologyServiceImpl.log.debug( "Found obsolete or missing term: " + ch.getValue() + " - " + valueUri ); + lastObsolete = vos.get( valueUri ); + } + } - if ( vc.getValue() == null ) { - OntologyServiceImpl.log - .warn( "Skipping a characteristic with no value: " + res.getLabel() + " / " + res.getUri() ); - return null; + if ( vos.size() > prevObsoleteCnt ) { + OntologyServiceImpl.log.info( "Found " + vos.size() + " obsolete or missing terms so far, tested " + checked + " characteristics" ); + OntologyServiceImpl.log.info( "Last obsolete term seen: " + lastObsolete.getValue() + " - " + lastObsolete.getValueUri() ); + } + + prevObsoleteCnt = vos.size(); } - return vc; + OntologyServiceImpl.log.info( "Done, obsolete or missing terms found: " + vos.size() ); + + return vos; } private void searchForCharacteristics( String queryString, Map previouslyUsedInSystem ) { @@ -705,7 +642,7 @@ private void searchForCharacteristics( String queryString, Map findCharacteristicsFromOntology( S List ontologyServicesToUse; if ( useNeuroCartaOntology ) { ontologyServicesToUse = Arrays.asList( - nifstdOntologyService, - fmaOntologyService, +// nifstdOntologyService, +// fmaOntologyService, obiService ); } else { ontologyServicesToUse = this.ontologyServices; @@ -736,8 +673,7 @@ private Collection findCharacteristicsFromOntology( S for ( OntologyTerm ontologyTerm : ontologyTerms ) { // if the ontology term wasnt already found in the database if ( characteristicFromDatabaseWithValueUri.get( ontologyTerm.getUri() ) == null ) { - CharacteristicValueObject phenotype = new CharacteristicValueObject( -1L, - ontologyTerm.getLabel().toLowerCase(), ontologyTerm.getUri() ); + CharacteristicValueObject phenotype = new CharacteristicValueObject( ontologyTerm.getLabel().toLowerCase(), ontologyTerm.getUri() ); characteristicsFromOntology.add( phenotype ); } } @@ -790,6 +726,25 @@ private void initializeCategoryTerms() throws IOException { } } + + private void initializeRelationTerms() throws IOException { + Set relationTerms = new HashSet<>(); + Resource resource = new ClassPathResource( "/ubic/gemma/core/ontology/Relation.terms.txt" ); + try ( BufferedReader reader = new BufferedReader( new InputStreamReader( resource.getInputStream() ) ) ) { + String line; + while ( ( line = reader.readLine() ) != null ) { + if ( line.startsWith( "#" ) || StringUtils.isEmpty( line ) ) + continue; + String[] f = StringUtils.split( line, '\t' ); + if ( f.length < 2 ) { + continue; + } + relationTerms.add( new OntologyPropertySimple( f[0], f[1] ) ); + } + this.relationTerms = Collections.unmodifiableSet( relationTerms ); + } + } + /** * given a collection of characteristics add them to the correct List */ diff --git a/gemma-core/src/main/java/ubic/gemma/core/ontology/OntologyUtils.java b/gemma-core/src/main/java/ubic/gemma/core/ontology/OntologyUtils.java index 4417bbce7b..f0bcf59228 100644 --- a/gemma-core/src/main/java/ubic/gemma/core/ontology/OntologyUtils.java +++ b/gemma-core/src/main/java/ubic/gemma/core/ontology/OntologyUtils.java @@ -1,7 +1,12 @@ package ubic.gemma.core.ontology; import lombok.extern.apachecommons.CommonsLog; +import ubic.basecode.ontology.model.OntologyTerm; import ubic.basecode.ontology.providers.OntologyService; +import ubic.gemma.model.common.description.Characteristic; + +import java.util.Collection; +import java.util.HashSet; /** * Utilities for working with ontologies. diff --git a/gemma-core/src/main/java/ubic/gemma/core/ontology/jena/TGFVO.java b/gemma-core/src/main/java/ubic/gemma/core/ontology/jena/TGFVO.java new file mode 100644 index 0000000000..461e7467f4 --- /dev/null +++ b/gemma-core/src/main/java/ubic/gemma/core/ontology/jena/TGFVO.java @@ -0,0 +1,55 @@ +package ubic.gemma.core.ontology.jena; + +import com.hp.hpl.jena.rdf.model.Property; +import com.hp.hpl.jena.rdf.model.Resource; +import com.hp.hpl.jena.rdf.model.ResourceFactory; + +/** + * Vocabulary for The Gemma Factor Value Ontology (TGFVO). + * @author poirigui + */ +public class TGFVO { + + /** + * Namespace used by TGFVO. + *

+ * This namespace is reserved for classes and properties that are necessary to connect factor values and their + * annotations. + */ + public static String NS = "http://gemma.msl.ubc.ca/ont/TGFVO#"; + + /** + * Relates a {@link ubic.gemma.model.expression.experiment.FactorValue} to one of its annotation which can be either + * a subject or an object of one if its {@link ubic.gemma.model.expression.experiment.Statement} or its measurement. + */ + public static Property hasAnnotation = ResourceFactory.createProperty( NS + "hasAnnotation" ); + /** + * Inverse of {@link #hasAnnotation}. + */ + public static Property annotationOf = ResourceFactory.createProperty( NS + "annotationOf" ); + /** + * Relates a {@link ubic.gemma.model.expression.experiment.FactorValue} to its measurement. + */ + public static Property hasMeasurement = ResourceFactory.createProperty( NS + "hasMeasurement" ); + /** + * Inverse of {@link #hasMeasurement}. + */ + public static Property measurementOf = ResourceFactory.createProperty( NS + "measurementOf" ); + + /** + * Represents a factor value {@link ubic.gemma.model.common.measurement.Measurement}. + */ + public static Resource Measurement = ResourceFactory.createResource( NS + "Measurement" ); + /** + * Relates a {@link ubic.gemma.model.common.measurement.Measurement} to its unit, if any. + */ + public static Property hasUnit = ResourceFactory.createProperty( NS + "hasUnit" ); + /** + * Relates a {@link ubic.gemma.model.common.measurement.Measurement} to its representation. + */ + public static Property hasRepresentation = ResourceFactory.createProperty( NS + "hasRepresentation" ); + /** + * Relates a {@link ubic.gemma.model.common.measurement.Measurement} to its value. + */ + public static Property hasValue = ResourceFactory.createProperty( NS + "hasValue" ); +} \ No newline at end of file diff --git a/gemma-core/src/main/java/ubic/gemma/core/ontology/jena/package-info.java b/gemma-core/src/main/java/ubic/gemma/core/ontology/jena/package-info.java new file mode 100644 index 0000000000..44e300d6ff --- /dev/null +++ b/gemma-core/src/main/java/ubic/gemma/core/ontology/jena/package-info.java @@ -0,0 +1,9 @@ +/** + * This package contains all the Jena-related code for ontologies that Gemma needs. + *

+ * Try to avoid depending directly on Jena more than necessary and favour high-level abstractions supplied by baseCode. + */ +@ParametersAreNonnullByDefault +package ubic.gemma.core.ontology.jena; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/gemma-core/src/main/java/ubic/gemma/core/ontology/providers/GeneOntologyServiceImpl.java b/gemma-core/src/main/java/ubic/gemma/core/ontology/providers/GeneOntologyServiceImpl.java index 7452044fc3..d36278a874 100644 --- a/gemma-core/src/main/java/ubic/gemma/core/ontology/providers/GeneOntologyServiceImpl.java +++ b/gemma-core/src/main/java/ubic/gemma/core/ontology/providers/GeneOntologyServiceImpl.java @@ -41,6 +41,7 @@ import ubic.gemma.persistence.service.association.Gene2GOAssociationService; import ubic.gemma.persistence.util.CacheUtils; +import java.io.InputStream; import java.util.*; import java.util.stream.Collectors; @@ -400,8 +401,8 @@ public void clearCaches() { } @Override - public void initialize( boolean forceLoad, boolean forceIndexing ) { - super.initialize( forceLoad, forceIndexing ); + public void initialize( InputStream stream, boolean forceIndexing ) { + super.initialize( stream, forceIndexing ); clearCaches(); } diff --git a/gemma-core/src/main/java/ubic/gemma/core/ontology/providers/OntologyServiceFactory.java b/gemma-core/src/main/java/ubic/gemma/core/ontology/providers/OntologyServiceFactory.java index aa5218b5bc..1d82455dd6 100644 --- a/gemma-core/src/main/java/ubic/gemma/core/ontology/providers/OntologyServiceFactory.java +++ b/gemma-core/src/main/java/ubic/gemma/core/ontology/providers/OntologyServiceFactory.java @@ -1,12 +1,10 @@ package ubic.gemma.core.ontology.providers; import lombok.extern.apachecommons.CommonsLog; -import org.apache.commons.lang3.StringUtils; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.config.AbstractFactoryBean; import org.springframework.core.task.TaskExecutor; import ubic.basecode.ontology.providers.OntologyService; -import ubic.basecode.util.Configuration; import javax.annotation.Nullable; import java.util.Set; @@ -16,14 +14,11 @@ * @param the type of ontology service this factory produces */ @CommonsLog +@SuppressWarnings("unused") public class OntologyServiceFactory extends AbstractFactoryBean { - /** - * Determine if ontologies are to be loaded on startup. - */ - private static final boolean isAutoLoad = ( StringUtils.isBlank( Configuration.getString( "load.ontologies" ) ) || Configuration.getBoolean( "load.ontologies" ) ); - private final Class ontologyServiceClass; + private boolean autoLoad = false; private boolean forceLoad = false; private boolean forceIndexing = false; private boolean loadInBackground = true; @@ -44,6 +39,13 @@ public OntologyServiceFactory( Class ontologyServiceClass ) { this.ontologyServiceClass = ontologyServiceClass; } + /** + * Enable loading of ontologies on startup. + */ + public void setAutoLoad( boolean autoLoad ) { + this.autoLoad = autoLoad; + } + /** * Force loading, regardless of the {@code load.{name}Ontology} property value. */ @@ -122,16 +124,6 @@ public void setAdditionalPropertyUris( @Nullable Set additionalPropertyU this.additionalPropertyUris = additionalPropertyUris; } - /** - * Check if the ontology returned by this factory will be loaded. - *

- * This happens if either the {@code load.ontologies} configuration key is set to true or the loading is forced via - * {@link #setForceLoad(boolean)}. - */ - public boolean isAutoLoaded() { - return isAutoLoad || forceLoad; - } - @Override public Class getObjectType() { return ontologyServiceClass; @@ -152,7 +144,7 @@ protected T createInstance() throws Exception { if ( additionalPropertyUris != null ) { service.setAdditionalPropertyUris( additionalPropertyUris ); } - if ( isAutoLoad || forceLoad ) { + if ( autoLoad ) { if ( loadInBackground ) { if ( ontologyTaskExecutor != null ) { ontologyTaskExecutor.execute( () -> service.initialize( forceLoad, forceIndexing ) ); diff --git a/gemma-core/src/main/java/ubic/gemma/core/search/GeneSetSearchImpl.java b/gemma-core/src/main/java/ubic/gemma/core/search/GeneSetSearchImpl.java index a2a0dec26f..a10a027c94 100644 --- a/gemma-core/src/main/java/ubic/gemma/core/search/GeneSetSearchImpl.java +++ b/gemma-core/src/main/java/ubic/gemma/core/search/GeneSetSearchImpl.java @@ -39,7 +39,7 @@ import ubic.gemma.model.genome.gene.GeneSetMember; import ubic.gemma.model.genome.gene.GeneSetValueObject; import ubic.gemma.model.genome.gene.GeneValueObject; -import ubic.gemma.model.genome.gene.phenotype.valueObject.CharacteristicValueObject; +import ubic.gemma.model.common.description.CharacteristicValueObject; import ubic.gemma.persistence.service.association.Gene2GOAssociationService; import ubic.gemma.persistence.service.genome.taxon.TaxonService; import ubic.gemma.persistence.util.EntityUtils; diff --git a/gemma-core/src/main/java/ubic/gemma/core/search/SearchResult.java b/gemma-core/src/main/java/ubic/gemma/core/search/SearchResult.java index d1c6bd2892..727e6a1a36 100644 --- a/gemma-core/src/main/java/ubic/gemma/core/search/SearchResult.java +++ b/gemma-core/src/main/java/ubic/gemma/core/search/SearchResult.java @@ -22,6 +22,7 @@ import lombok.EqualsAndHashCode; import lombok.ToString; import ubic.gemma.model.common.Identifiable; +import ubic.gemma.model.common.description.CharacteristicValueObject; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -51,7 +52,7 @@ public static Comparator> getComparator() { * Create a search result whose result class differ from the object. *

* This can be useful if you wrap a proxy, or don't want to expose the object class publicly. For example, our - * {@link ubic.gemma.model.association.phenotype.PhenotypeAssociation} use a {@link ubic.gemma.model.genome.gene.phenotype.valueObject.CharacteristicValueObject} + * {@link ubic.gemma.model.association.phenotype.PhenotypeAssociation} use a {@link CharacteristicValueObject} * for the result object. */ public static SearchResult from( Class resultType, T entity, double score, @Nullable Map highlights, Object source ) { diff --git a/gemma-core/src/main/java/ubic/gemma/core/search/SearchServiceImpl.java b/gemma-core/src/main/java/ubic/gemma/core/search/SearchServiceImpl.java index bde632bc0c..ad019b97f6 100644 --- a/gemma-core/src/main/java/ubic/gemma/core/search/SearchServiceImpl.java +++ b/gemma-core/src/main/java/ubic/gemma/core/search/SearchServiceImpl.java @@ -19,18 +19,15 @@ package ubic.gemma.core.search; -import com.google.common.collect.Sets; import gemma.gsec.util.SecurityUtil; -import lombok.Value; import lombok.extern.apachecommons.CommonsLog; +import org.apache.commons.collections4.SetUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.time.StopWatch; import org.apache.commons.text.StringEscapeUtils; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.cache.Cache; -import org.springframework.cache.annotation.Cacheable; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.ConverterNotFoundException; import org.springframework.core.convert.TypeDescriptor; @@ -65,7 +62,7 @@ import ubic.gemma.model.genome.gene.GeneSet; import ubic.gemma.model.genome.gene.GeneSetValueObject; import ubic.gemma.model.genome.gene.GeneValueObject; -import ubic.gemma.model.genome.gene.phenotype.valueObject.CharacteristicValueObject; +import ubic.gemma.model.common.description.CharacteristicValueObject; import ubic.gemma.model.genome.sequenceAnalysis.BioSequenceValueObject; import ubic.gemma.persistence.service.expression.arrayDesign.ArrayDesignService; import ubic.gemma.persistence.service.expression.experiment.BlacklistedEntityService; @@ -193,7 +190,7 @@ private void addAll( Collection> search @Transactional(readOnly = true) public SearchResultMap search( SearchSettings settings ) throws SearchException { if ( !supportedResultTypes.keySet().containsAll( settings.getResultTypes() ) ) { - throw new IllegalArgumentException( "The search settings contains unsupported result types:" + Sets.difference( settings.getResultTypes(), supportedResultTypes.keySet() ) + "." ); + throw new IllegalArgumentException( "The search settings contains unsupported result types:" + SetUtils.difference( settings.getResultTypes(), supportedResultTypes.keySet() ) + "." ); } StopWatch timer = StopWatch.createStarted(); diff --git a/gemma-core/src/main/java/ubic/gemma/core/search/source/HibernateSearchSource.java b/gemma-core/src/main/java/ubic/gemma/core/search/source/HibernateSearchSource.java index 67a49dd039..cf41a0dea3 100644 --- a/gemma-core/src/main/java/ubic/gemma/core/search/source/HibernateSearchSource.java +++ b/gemma-core/src/main/java/ubic/gemma/core/search/source/HibernateSearchSource.java @@ -66,7 +66,12 @@ public class HibernateSearchSource implements SearchSource, InitializingBean { "experimentalDesign.experimentalFactors.category.categoryUri", "experimentalDesign.experimentalFactors.category.category", "experimentalDesign.experimentalFactors.factorValues.characteristics.value", - "experimentalDesign.experimentalFactors.factorValues.characteristics.valueUri" }; + "experimentalDesign.experimentalFactors.factorValues.characteristics.valueUri", + "experimentalDesign.experimentalFactors.factorValues.characteristics.object", + "experimentalDesign.experimentalFactors.factorValues.characteristics.objectUri", + "experimentalDesign.experimentalFactors.factorValues.characteristics.secondObject", + "experimentalDesign.experimentalFactors.factorValues.characteristics.secondObjectUri" + }; private static final String[] GENE_FIELDS = { "name", "accessions.accession", "aliases.alias", diff --git a/gemma-core/src/main/java/ubic/gemma/core/tasks/maintenance/CharacteristicUpdateTaskImpl.java b/gemma-core/src/main/java/ubic/gemma/core/tasks/maintenance/CharacteristicUpdateTaskImpl.java index 82cc78cb2d..23be575b1b 100644 --- a/gemma-core/src/main/java/ubic/gemma/core/tasks/maintenance/CharacteristicUpdateTaskImpl.java +++ b/gemma-core/src/main/java/ubic/gemma/core/tasks/maintenance/CharacteristicUpdateTaskImpl.java @@ -33,6 +33,7 @@ import ubic.gemma.model.expression.biomaterial.BioMaterial; import ubic.gemma.model.expression.experiment.ExpressionExperiment; import ubic.gemma.model.expression.experiment.FactorValue; +import ubic.gemma.model.expression.experiment.Statement; import ubic.gemma.persistence.service.common.description.CharacteristicService; import ubic.gemma.persistence.service.expression.biomaterial.BioMaterialService; import ubic.gemma.persistence.service.expression.experiment.ExpressionExperimentService; @@ -85,23 +86,6 @@ public void setTaskCommand( CharacteristicUpdateCommand command ) { super.setTaskCommand( command ); } - private void addToParent( Characteristic c, Object parent ) { - if ( parent instanceof ExpressionExperiment ) { - ExpressionExperiment ee = ( ExpressionExperiment ) parent; - ee = expressionExperimentService.thawLite( ee ); - ee.getCharacteristics().add( c ); - expressionExperimentService.update( ee ); - } else if ( parent instanceof BioMaterial ) { - BioMaterial bm = ( BioMaterial ) parent; - bm.getCharacteristics().add( c ); - bioMaterialService.update( bm ); - } else if ( parent instanceof FactorValue ) { - FactorValue fv = ( FactorValue ) parent; - fv.getCharacteristics().add( c ); - factorValueService.update( fv ); - } - } - private Characteristic convertAvo2Characteristic( AnnotationValueObject avo ) { Characteristic vc = Characteristic.Factory.newInstance(); vc.setId( avo.getId() ); @@ -114,6 +98,18 @@ private Characteristic convertAvo2Characteristic( AnnotationValueObject avo ) { return vc; } + private Statement convertAvo2Statement( AnnotationValueObject avo ) { + Statement vc = Statement.Factory.newInstance(); + vc.setId( avo.getId() ); + vc.setCategory( avo.getClassName() ); + vc.setCategoryUri( StringUtils.stripToNull( avo.getClassUri() ) ); + vc.setSubject( avo.getTermName() ); + vc.setSubjectUri( StringUtils.stripToNull( avo.getTermUri() ) ); + if ( StringUtils.isNotBlank( avo.getEvidenceCode() ) ) + vc.setEvidenceCode( GOEvidenceCode.valueOf( avo.getEvidenceCode() ) ); + return vc; + } + /** * Convert incoming AVOs into Characteristics (if the AVO objectClass is not FactorValue) */ @@ -162,17 +158,16 @@ private Collection convertToFactorValuesWithCharacteristics( Collec "Don't use the annotator to update factor values that already have characteristics" ); } - Characteristic vc = convertAvo2Characteristic( avo ); + Statement vc = convertAvo2Statement( avo ); vc.setId( null ); if ( vc.getEvidenceCode() == null ) { vc.setEvidenceCode( GOEvidenceCode.IC ); } - vc = characteristicService.create( vc ); + vc = factorValueService.createStatement( fv, vc ); - fv.setValue( vc.getValue() ); - fv.getCharacteristics().add( vc ); + fv.setValue( vc.getSubject() ); result.add( fv ); @@ -186,7 +181,7 @@ private TaskResult doRemove() { Collection asChars = convertToCharacteristic( chars ); - if ( asChars.size() == 0 ) { + if ( asChars.isEmpty() ) { log.info( "No characteristic objects were received" ); return new TaskResult( taskCommand, false ); } @@ -213,7 +208,7 @@ private TaskResult doRemove() { */ private TaskResult doUpdate() { Collection avos = taskCommand.getAnnotationValueObjects(); - if ( avos.size() == 0 ) + if ( avos.isEmpty() ) return new TaskResult( taskCommand, false ); log.info( "Updating " + avos.size() + " characteristics or uncharacterized factor values..." ); StopWatch timer = new StopWatch(); @@ -222,7 +217,7 @@ private TaskResult doUpdate() { Collection asChars = convertToCharacteristic( avos ); Collection factorValues = convertToFactorValuesWithCharacteristics( avos ); - if ( asChars.size() == 0 && factorValues.size() == 0 ) { + if ( asChars.isEmpty() && factorValues.isEmpty() ) { log.info( "Nothing to update" ); return new TaskResult( taskCommand, false ); } @@ -231,7 +226,7 @@ private TaskResult doUpdate() { factorValueService.update( factorValue ); } - if ( asChars.size() == 0 ) + if ( asChars.isEmpty() ) return new TaskResult( taskCommand, true ); Map charToParent = characteristicService.getParents( asChars, null, -1 ); @@ -258,8 +253,6 @@ private TaskResult doUpdate() { throw new AccessDeniedException( "Access is denied" ); } - assert cFromDatabase != null; - // preserve original data (which might have been entered by us, but may be from GEO) if ( StringUtils.isBlank( cFromDatabase.getOriginalValue() ) ) { cFromDatabase.setOriginalValue( cFromDatabase.getValue() ); @@ -317,8 +310,7 @@ private void removeFromParent( Characteristic c, Object parent ) { bioMaterialService.update( bm ); } else if ( parent instanceof FactorValue ) { FactorValue fv = ( FactorValue ) parent; - fv.getCharacteristics().remove( c ); - factorValueService.update( fv ); + factorValueService.removeStatement( fv, ( Statement ) c ); } } } diff --git a/gemma-core/src/main/java/ubic/gemma/core/visualization/ExperimentalDesignVisualizationServiceImpl.java b/gemma-core/src/main/java/ubic/gemma/core/visualization/ExperimentalDesignVisualizationServiceImpl.java index c58da14ea2..9c111d9b9d 100644 --- a/gemma-core/src/main/java/ubic/gemma/core/visualization/ExperimentalDesignVisualizationServiceImpl.java +++ b/gemma-core/src/main/java/ubic/gemma/core/visualization/ExperimentalDesignVisualizationServiceImpl.java @@ -115,7 +115,7 @@ public Map * Fields and getters annotated with this are excluded from Jackson JSON serialization and will not appear in the Gemma * RESTful API. + * @author poirigui + * @see GemmaRestOnly for properties or types that should be exclusive to Gemma REST instead */ @Target({ ElementType.METHOD, ElementType.FIELD }) @Retention(RetentionPolicy.RUNTIME) diff --git a/gemma-core/src/main/java/ubic/gemma/model/association/GOEvidenceCode.java b/gemma-core/src/main/java/ubic/gemma/model/association/GOEvidenceCode.java index 70a7b53789..dcf15cdebf 100644 --- a/gemma-core/src/main/java/ubic/gemma/model/association/GOEvidenceCode.java +++ b/gemma-core/src/main/java/ubic/gemma/model/association/GOEvidenceCode.java @@ -120,4 +120,4 @@ Inferred from High Throughput Direct Assay (HDA) * Unsupported/unknown GO evidence code are mapped to this value. */ OTHER; -} +} \ No newline at end of file diff --git a/gemma-core/src/main/java/ubic/gemma/model/common/auditAndSecurity/eventType/BatchProblemsUpdateEvent.java b/gemma-core/src/main/java/ubic/gemma/model/common/auditAndSecurity/eventType/BatchProblemsUpdateEvent.java index 5e2b193229..4202c8795c 100644 --- a/gemma-core/src/main/java/ubic/gemma/model/common/auditAndSecurity/eventType/BatchProblemsUpdateEvent.java +++ b/gemma-core/src/main/java/ubic/gemma/model/common/auditAndSecurity/eventType/BatchProblemsUpdateEvent.java @@ -19,7 +19,17 @@ package ubic.gemma.model.common.auditAndSecurity.eventType; +import ubic.gemma.model.expression.experiment.ExpressionExperiment; + /** + * Event that tracks when batch effects or problems are detected. + *

+ * There are three relevant fields in {@link ExpressionExperiment} whose change triggers this event: + *

    + *
  • {@link ExpressionExperiment#getBatchEffect()}
  • + *
  • {@link ExpressionExperiment#getBatchEffectStatistics()}
  • + *
  • {@link ExpressionExperiment#getBatchConfound()}
  • + *
* @author paul */ public class BatchProblemsUpdateEvent extends ExpressionExperimentAnalysisEvent { diff --git a/gemma-core/src/main/java/ubic/gemma/model/common/description/AnnotationValueObject.java b/gemma-core/src/main/java/ubic/gemma/model/common/description/AnnotationValueObject.java index d1d4b14be0..a639591ace 100644 --- a/gemma-core/src/main/java/ubic/gemma/model/common/description/AnnotationValueObject.java +++ b/gemma-core/src/main/java/ubic/gemma/model/common/description/AnnotationValueObject.java @@ -61,6 +61,14 @@ public AnnotationValueObject( Long id ) { super( id ); } + public AnnotationValueObject( String classUri, String className, String termUri, String termName, Class objectClass ) { + this.classUri = classUri; + this.className = className; + this.termUri = termUri; + this.termName = termName; + this.objectClass = formatObjectClass( objectClass ); + } + public AnnotationValueObject( Characteristic c ) { super( c ); classUri = c.getCategoryUri(); @@ -73,10 +81,14 @@ public AnnotationValueObject( Characteristic c ) { public AnnotationValueObject( Characteristic c, Class objectClass ) { this( c ); + this.objectClass = formatObjectClass( objectClass ); + } + + private static String formatObjectClass( Class objectClass ) { if ( ExpressionExperiment.class.isAssignableFrom( objectClass ) ) { - this.objectClass = "ExperimentTag"; + return "ExperimentTag"; } else { - this.objectClass = objectClass.getSimpleName(); + return objectClass.getSimpleName(); } } diff --git a/gemma-core/src/main/java/ubic/gemma/model/common/description/Characteristic.java b/gemma-core/src/main/java/ubic/gemma/model/common/description/Characteristic.java index e5e874f9da..d82c265c0d 100644 --- a/gemma-core/src/main/java/ubic/gemma/model/common/description/Characteristic.java +++ b/gemma-core/src/main/java/ubic/gemma/model/common/description/Characteristic.java @@ -20,7 +20,6 @@ package ubic.gemma.model.common.description; import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.builder.HashCodeBuilder; import org.hibernate.search.annotations.Analyze; import org.hibernate.search.annotations.DocumentId; import org.hibernate.search.annotations.Field; @@ -28,6 +27,7 @@ import ubic.gemma.model.association.GOEvidenceCode; import ubic.gemma.model.common.AbstractDescribable; +import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.io.Serializable; import java.util.Comparator; @@ -44,29 +44,49 @@ * @author Paul */ @Indexed -public class Characteristic extends AbstractDescribable implements Serializable { +public class Characteristic extends AbstractDescribable implements Serializable, Comparable { private static final long serialVersionUID = -7242166109264718620L; + private static final Comparator BY_CATEGORY_COMPARATOR = ( c1, c2 ) -> CharacteristicUtils.compareTerm( c1.category, c1.categoryUri, c2.category, c2.categoryUri ); + + private static final Comparator BY_CATEGORY_AND_VALUE_COMPARATOR = BY_CATEGORY_COMPARATOR + .thenComparing( c -> c, ( c1, c2 ) -> CharacteristicUtils.compareTerm( c1.value, c1.valueUri, c2.value, c2.valueUri ) ); + + private static final Comparator COMPARATOR = BY_CATEGORY_AND_VALUE_COMPARATOR + .thenComparing( Characteristic::getId, Comparator.nullsLast( Comparator.naturalOrder() ) ); + + /** + * Obtain a full comparator for characteristics that fallbacks on the ID if everything else is equal. + *

+ * The following fields are compared: category, value, ID. + */ + public static Comparator getComparator() { + return COMPARATOR; + } + /** * Obtain a comparator to compare terms by category URI (or category if null) in a case-insensitive manner. + *

+ * Two terms that are equal in terms of category will be collapsed if using a {@link java.util.TreeSet}. + *

+ * Use this if you want to get a summary of the categories used by a collection of terms irrespective of their IDs. */ public static Comparator getByCategoryComparator() { - return Comparator - .comparing( Characteristic::getCategoryUri, Comparator.nullsLast( String.CASE_INSENSITIVE_ORDER ) ) - .thenComparing( Characteristic::getCategory, Comparator.nullsLast( String.CASE_INSENSITIVE_ORDER ) ); + return BY_CATEGORY_COMPARATOR; } /** * Obtain a comparator to order terms by value URI (or value if null) in a case-insensitive manner. + *

+ * Two terms that are equal in terms of category and value (i.e. sharing the same ID) will be collapsed if using a + * {@link java.util.TreeSet}. + *

+ * Use this if you want to get a summary of the annotations used by a collection of terms irrespective of their IDs. */ public static Comparator getByCategoryAndValueComparator() { - return Comparator - .comparing( Characteristic::getCategoryUri, Comparator.nullsLast( String.CASE_INSENSITIVE_ORDER ) ) - .thenComparing( Characteristic::getCategory, Comparator.nullsLast( String.CASE_INSENSITIVE_ORDER ) ) - .thenComparing( Characteristic::getValueUri, Comparator.nullsLast( String.CASE_INSENSITIVE_ORDER ) ) - .thenComparing( Characteristic::getValue, Comparator.nullsLast( String.CASE_INSENSITIVE_ORDER ) ); // there should be no null, but we better be safe than sorry + return BY_CATEGORY_AND_VALUE_COMPARATOR; } private String category; @@ -80,6 +100,14 @@ public static Comparator getByCategoryAndValueComparator() { private String value; @Nullable private String valueUri; + /** + * Indicate if this "old-style" characteristic has been migrated to a {@link ubic.gemma.model.expression.experiment.Statement}. + * This is only meaningful for {@link ubic.gemma.model.expression.experiment.FactorValue} characteristics. + * @deprecated do not rely on this field, it will be removed once the migration is completed. + */ + @Deprecated + private boolean migratedToStatement; + /** * No-arg constructor added to satisfy javabean contract @@ -176,13 +204,23 @@ public void setValueUri( @Nullable String uri ) { this.valueUri = uri; } - @Override - public int hashCode() { + @Deprecated + public boolean isMigratedToStatement() { + return migratedToStatement; + } - if ( this.getId() != null ) return this.getId().hashCode(); + @Deprecated + public void setMigratedToStatement( boolean migratedToStatement ) { + this.migratedToStatement = migratedToStatement; + } - return new HashCodeBuilder( 17, 1 ).append( this.getCategory() ) - .append( this.getValue() ).toHashCode(); + @Override + public int hashCode() { + if ( this.getId() != null ) { + return super.hashCode(); + } + return Objects.hash( StringUtils.lowerCase( categoryUri != null ? categoryUri : category ), + StringUtils.lowerCase( valueUri != null ? valueUri : value ) ); } @Override @@ -195,19 +233,20 @@ public boolean equals( Object object ) { return false; Characteristic that = ( Characteristic ) object; if ( this.getId() != null && that.getId() != null ) - return this.getId().equals( that.getId() ); + return super.equals( object ); /* * at this point, we know we have two Characteristics, at least one of which is transient, so we have to look at * the fields; we can't just compare the hashcodes because they also look at the id, so comparing one transient * and one persistent would always fail... */ - return Objects.equals( this.getCategory(), that.getCategory() ) && Objects - .equals( this.getValue(), that.getValue() ); + return CharacteristicUtils.equals( category, categoryUri, that.category, that.categoryUri ) + && CharacteristicUtils.equals( value, valueUri, that.value, that.valueUri ); + } - /* - * FIXME add uris - */ + @Override + public int compareTo( @Nonnull Characteristic characteristic ) { + return COMPARATOR.compare( this, characteristic ); } @Override @@ -219,7 +258,7 @@ public String toString() { b.append( " [" ).append( categoryUri ).append( "]" ); } } else if ( categoryUri != null ) { - b.append( " Value URI=" ).append( categoryUri ); + b.append( " Category URI=" ).append( categoryUri ); } else { b.append( " [No Category]" ); } diff --git a/gemma-core/src/main/java/ubic/gemma/model/common/description/CharacteristicUtils.java b/gemma-core/src/main/java/ubic/gemma/model/common/description/CharacteristicUtils.java new file mode 100644 index 0000000000..b9069f2b51 --- /dev/null +++ b/gemma-core/src/main/java/ubic/gemma/model/common/description/CharacteristicUtils.java @@ -0,0 +1,65 @@ +package ubic.gemma.model.common.description; + +import org.apache.commons.lang.StringUtils; + +import javax.annotation.Nullable; + +public class CharacteristicUtils { + + /** + * Compare a pair of ontology terms. + */ + public static boolean equals( String a, String aUri, String b, String bUri ) { + if ( aUri != null ^ bUri != null ) { + return false; // free-text v.s. ontology term, always false + } + return aUri != null ? StringUtils.equalsIgnoreCase( aUri, bUri ) : StringUtils.equalsIgnoreCase( a, b ); + } + + /** + * Compare a pair of ontology terms. + *

+ * Terms are sorted by label and then URI. If two term have an identical URI, this method will return zero + * regardless of the label. + *

+ * All URI and label comparisons are case-insensitive. + */ + public static int compareTerm( String a, @Nullable String aUri, String b, @Nullable String bUri ) { + if ( aUri != null && bUri != null ) { + int uriCmp = aUri.compareToIgnoreCase( bUri ); + if ( uriCmp == 0 ) { + return 0; // same URI, collapse the two terms + } else { + return compareLabel( a, b, uriCmp ); + } + } else if ( aUri != null ) { + return compareLabel( a, b, -1 ); + } else if ( bUri != null ) { + return compareLabel( a, b, 1 ); + } else if ( a != null && b != null ) { + return a.compareToIgnoreCase( b ); + } else if ( a != null ) { + return -1; + } else if ( b != null ) { + return 1; + } else { + return 0; + } + } + + private static int compareLabel( String a, String b, int uriCmp ) { + if ( a != null && b != null ) { + // different URIs with labels, compare labels + // if labels are identical, we don't want to collapse the terms, so fallback on the URI + int labelCmp = a.compareToIgnoreCase( b ); + return labelCmp != 0 ? labelCmp : uriCmp; + } else if ( a != null ) { + return -1; + } else if ( b != null ) { + return 1; + } else { + // a and b are null, fallback to comparing URIs + return uriCmp; + } + } +} diff --git a/gemma-core/src/main/java/ubic/gemma/model/common/description/CharacteristicValueObject.java b/gemma-core/src/main/java/ubic/gemma/model/common/description/CharacteristicValueObject.java new file mode 100644 index 0000000000..a798338aed --- /dev/null +++ b/gemma-core/src/main/java/ubic/gemma/model/common/description/CharacteristicValueObject.java @@ -0,0 +1,202 @@ +/* + * The Gemma project + * + * Copyright (c) 2011 University of British Columbia + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package ubic.gemma.model.common.description; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; +import org.apache.commons.lang3.StringUtils; +import ubic.gemma.model.IdentifiableValueObject; +import ubic.gemma.model.annotations.GemmaRestOnly; +import ubic.gemma.model.annotations.GemmaWebOnly; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.*; + +import static ubic.gemma.model.common.description.CharacteristicUtils.compareTerm; + +/** + * Value object representation of a {@link Characteristic}. + * @see Characteristic + * @author poirigui + */ +@Data +public class CharacteristicValueObject extends IdentifiableValueObject implements Comparable { + + private static final Comparator COMPARATOR = Comparator + .comparing( ( CharacteristicValueObject c ) -> c, ( c1, c2 ) -> compareTerm( c1.getCategory(), c1.getCategoryUri(), c2.getCategory(), c2.getCategoryUri() ) ) + .thenComparing( CharacteristicValueObject::getTaxon, Comparator.nullsLast( String.CASE_INSENSITIVE_ORDER ) ) + .thenComparing( ( CharacteristicValueObject c ) -> c, ( c1, c2 ) -> compareTerm( c1.getValue(), c1.getValueUri(), c2.getValue(), c2.getValueUri() ) ) + .thenComparing( CharacteristicValueObject::getId, Comparator.nullsLast( Comparator.naturalOrder() ) ); + + private String category; + private String categoryUri; + private String value; + private String valueUri; + + /** + * A unique ontology identifier (i.e. IRI) for this characteristic. + */ + @GemmaRestOnly + @JsonInclude(JsonInclude.Include.NON_NULL) + public String valueId; + + // TODO: all the following fields are Phenocarta-specific and should be relocated + + /** + * id used by url on the client side + */ + @GemmaWebOnly + private String urlId = ""; + @GemmaWebOnly + private boolean alreadyPresentInDatabase = false; + @GemmaWebOnly + private boolean alreadyPresentOnGene = false; + /** + * child term from a root + */ + @GemmaWebOnly + private boolean child = false; + @GemmaWebOnly + private int numTimesUsed = 0; + /** + * what Ontology uses this term + */ + @GemmaWebOnly + private String ontologyUsed = null; + @GemmaWebOnly + private long privateGeneCount = 0L; + /** + * number of occurrences in all genes + */ + @GemmaWebOnly + private long publicGeneCount = 0L; + /** + * root of a query + */ + @GemmaWebOnly + private boolean root = false; + @GemmaWebOnly + private String taxon = ""; + /** + * The definition of the value, if it is an ontology term, as supplied by the ontology. If the value is + * free text, this will be empty + */ + @GemmaWebOnly + private String valueDefinition = ""; + + /** + * Required when using the class as a spring bean. + */ + public CharacteristicValueObject() { + super(); + } + + public CharacteristicValueObject( Long id ) { + super( id ); + } + + public CharacteristicValueObject( Characteristic characteristic ) { + super( characteristic ); + this.category = characteristic.getCategory(); + this.categoryUri = characteristic.getCategoryUri(); + this.value = characteristic.getValue(); + this.valueUri = characteristic.getValueUri(); + this.urlId = parseUrlId( characteristic.getValueUri() ); + } + + public CharacteristicValueObject( String value, @Nullable String valueUri ) { + this.valueUri = valueUri; + this.value = value; + this.urlId = parseUrlId( valueUri ); + } + + public CharacteristicValueObject( String value, @Nullable String valueUri, String category, @Nullable String categoryUri ) { + this( value, valueUri ); + this.category = category; + this.categoryUri = categoryUri; + } + + public static Collection characteristic2CharacteristicVO( + Collection characteristics ) { + + Collection characteristicValueObjects; + + if ( characteristics instanceof List ) + characteristicValueObjects = new ArrayList<>(); + else + characteristicValueObjects = new HashSet<>(); + + for ( Characteristic characteristic : characteristics ) { + CharacteristicValueObject characteristicValueObject = new CharacteristicValueObject( characteristic ); + characteristicValueObjects.add( characteristicValueObject ); + } + return characteristicValueObjects; + } + + @Override + public int hashCode() { + if ( this.getId() != null ) { + return super.hashCode(); + } + return Objects.hash( StringUtils.lowerCase( categoryUri != null ? categoryUri : category ), + StringUtils.lowerCase( valueUri != null ? valueUri : value ) ); + } + + @Override + public boolean equals( Object object ) { + if ( object == null ) + return false; + if ( this == object ) + return true; + if ( !( object instanceof CharacteristicValueObject ) ) + return false; + CharacteristicValueObject that = ( CharacteristicValueObject ) object; + if ( this.getId() != null && that.getId() != null ) + return super.equals( object ); + return CharacteristicUtils.equals( category, categoryUri, that.category, that.categoryUri ) + && CharacteristicUtils.equals( value, valueUri, that.value, that.valueUri ); + } + + @Override + public int compareTo( @Nonnull CharacteristicValueObject that ) { + return COMPARATOR.compare( this, that ); + } + + @Override + public String toString() { + return String.format( "[Category=%s%s Value=%s%s]", + category, + categoryUri != null ? " (" + categoryUri + ")" : "", + value, + valueUri != null ? " (" + valueUri + ")" : "" ); + } + + public void incrementOccurrenceCount() { + this.numTimesUsed++; + } + + private static String parseUrlId( @Nullable String valueUri ) { + if ( StringUtils.isBlank( valueUri ) ) + return ""; + if ( valueUri.indexOf( "#" ) > 0 ) { + return valueUri.substring( valueUri.lastIndexOf( "#" ) + 1 ); + } else if ( valueUri.lastIndexOf( "/" ) > 0 ) { + return valueUri.substring( valueUri.lastIndexOf( "/" ) + 1 ); + } else { + return ""; + } + } +} diff --git a/gemma-core/src/main/java/ubic/gemma/model/common/search/SearchSettings.java b/gemma-core/src/main/java/ubic/gemma/model/common/search/SearchSettings.java index 78a0745445..4e48b6f29f 100644 --- a/gemma-core/src/main/java/ubic/gemma/model/common/search/SearchSettings.java +++ b/gemma-core/src/main/java/ubic/gemma/model/common/search/SearchSettings.java @@ -25,7 +25,6 @@ import org.apache.commons.lang3.StringUtils; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.document.Document; -import org.springframework.context.MessageSourceResolvable; import ubic.gemma.core.search.Highlighter; import ubic.gemma.model.common.Identifiable; import ubic.gemma.model.common.description.BibliographicReference; diff --git a/gemma-core/src/main/java/ubic/gemma/model/expression/bioAssayData/ExperimentExpressionLevelsValueObject.java b/gemma-core/src/main/java/ubic/gemma/model/expression/bioAssayData/ExperimentExpressionLevelsValueObject.java index 73ff2e3881..09f3474179 100644 --- a/gemma-core/src/main/java/ubic/gemma/model/expression/bioAssayData/ExperimentExpressionLevelsValueObject.java +++ b/gemma-core/src/main/java/ubic/gemma/model/expression/bioAssayData/ExperimentExpressionLevelsValueObject.java @@ -1,6 +1,6 @@ package ubic.gemma.model.expression.bioAssayData; -import com.google.common.base.Strings; +import org.apache.commons.lang3.StringUtils; import ubic.gemma.model.expression.bioAssay.BioAssayValueObject; import ubic.gemma.model.genome.Gene; @@ -83,7 +83,7 @@ public GeneElementExpressionsValueObject( String geneOfficialSymbol, Integer gen vectors = processed; } - if ( vectors.size() > 1 && !Strings.isNullOrEmpty( mode ) ) { // Consolidation requested + if ( vectors.size() > 1 && !StringUtils.isEmpty( mode ) ) { // Consolidation requested switch ( mode ) { case ( OPT_PICK_MAX ): elements.add( this.pickMax( vectors ) ); diff --git a/gemma-core/src/main/java/ubic/gemma/model/expression/biomaterial/BioMaterialValueObject.java b/gemma-core/src/main/java/ubic/gemma/model/expression/biomaterial/BioMaterialValueObject.java index 20b882f2cd..ba9f546d74 100644 --- a/gemma-core/src/main/java/ubic/gemma/model/expression/biomaterial/BioMaterialValueObject.java +++ b/gemma-core/src/main/java/ubic/gemma/model/expression/biomaterial/BioMaterialValueObject.java @@ -20,21 +20,20 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; import org.apache.commons.lang3.StringUtils; +import org.hibernate.Hibernate; import ubic.gemma.model.IdentifiableValueObject; import ubic.gemma.model.annotations.GemmaWebOnly; import ubic.gemma.model.common.description.Characteristic; +import ubic.gemma.model.common.description.CharacteristicValueObject; import ubic.gemma.model.expression.bioAssay.BioAssay; import ubic.gemma.model.expression.bioAssay.BioAssayValueObject; -import ubic.gemma.model.expression.experiment.ExperimentalFactor; -import ubic.gemma.model.expression.experiment.FactorValue; -import ubic.gemma.model.expression.experiment.FactorValueBasicValueObject; -import ubic.gemma.model.expression.experiment.FactorValueValueObject; -import ubic.gemma.model.genome.gene.phenotype.valueObject.CharacteristicValueObject; +import ubic.gemma.model.expression.experiment.*; -import java.io.Serializable; import java.util.*; /** @@ -93,6 +92,8 @@ public class BioMaterialValueObject extends IdentifiableValueObject /** * Map of ids (factor232) to a representation of the factor (e.g., the name). */ + @Deprecated + @Schema(description = "This is deprecated, use the `factorValues` collection instead.", deprecated = true) private Map factors; @GemmaWebOnly @@ -132,20 +133,20 @@ public BioMaterialValueObject( BioMaterial bm, boolean basic ) { } else { this.factorValueObjects.add( new FactorValueValueObject( fv ) ); } - ExperimentalFactor factor = fv.getExperimentalFactor(); String factorId = String.format( "factor%d", factor.getId() ); String factorValueId = String.format( "fv%d", fv.getId() ); - this.factors.put( factorId, factor.getName() ); - this.factorValues.put( factorValueId, this.getFactorValueString( fv ) ); - - if ( fv.getMeasurement() == null ) { - this.factorIdToFactorValueId.put( factorId, factorValueId ); + if ( Hibernate.isInitialized( factor ) ) { + this.factors.put( factorId, factor.getName() ); + } + if ( fv.getMeasurement() != null ) { + String value = fv.getMeasurement().getValue(); + this.factorValues.put( factorValueId, value ); + // for measurement, use the actual value, not the FV ID + this.factorIdToFactorValueId.put( factorId, value ); } else { - /* - * use the actual value, not the factorvalue id. - */ - this.factorIdToFactorValueId.put( factorId, factorValues.get( factorValueId ) ); + this.factorValues.put( factorValueId, FactorValueUtils.getSummaryString( fv, BioMaterialValueObject.CHARACTERISTIC_DELIMITER ) ); + this.factorIdToFactorValueId.put( factorId, factorValueId ); } } @@ -170,6 +171,7 @@ public BioMaterialValueObject( BioMaterial bm, BioAssay ba ) { } @JsonProperty("factorValues") + @ArraySchema(schema = @Schema(implementation = FactorValueBasicValueObject.class)) public Collection getFactorValues() { return basicFVs ? fVBasicVOs : factorValueObjects; } @@ -179,23 +181,13 @@ public Collection getFactorValues() { */ @Deprecated @JsonProperty("factorValueObjects") + @ArraySchema( + arraySchema = @Schema(description = "This property is redundant, use `factorValues` instead.", deprecated = true), + schema = @Schema(implementation = FactorValueBasicValueObject.class)) public Collection getFactorValueObjects() { return basicFVs ? fVBasicVOs : factorValueObjects; } - /** - * Format the value as a string, either using the characteristic, value or measurement. - */ - private String getFactorValueString( FactorValue value ) { - if ( !value.getCharacteristics().isEmpty() ) { - return StringUtils.join( value.getCharacteristics(), BioMaterialValueObject.CHARACTERISTIC_DELIMITER ); - } else if ( value.getMeasurement() != null ) { - return value.getMeasurement().getValue(); - } else { - return value.getValue(); - } - } - @Override public String toString() { return "BioMaterialValueObject{" + diff --git a/gemma-core/src/main/java/ubic/gemma/model/expression/experiment/BatchEffectType.java b/gemma-core/src/main/java/ubic/gemma/model/expression/experiment/BatchEffectType.java new file mode 100644 index 0000000000..2ac02d37db --- /dev/null +++ b/gemma-core/src/main/java/ubic/gemma/model/expression/experiment/BatchEffectType.java @@ -0,0 +1,46 @@ +package ubic.gemma.model.expression.experiment; + +/** + * Represents a batch effect. + * @author poirigui + */ +public enum BatchEffectType { + /** + * Indicate that there is no batch information available. + */ + NO_BATCH_INFO, + /** + * Batch contains singleton (i.e. batch with a single experiment) thus preventing any variance estimate. + */ + SINGLETON_BATCHES_FAILURE, + /** + * Batch information is uninformative. + */ + UNINFORMATIVE_HEADERS_FAILURE, + /** + * Batch information is problematic. + */ + PROBLEMATIC_BATCH_INFO_FAILURE, + /** + * Indicate that there is a batch effect. It might be correctable. + */ + BATCH_EFFECT_FAILURE, + /** + * Indicate that there was a significant batch effect that was corrected. + */ + BATCH_CORRECTED_SUCCESS, + /** + * Indicate that there is a single batch and thus there cannot be a batch effect. + */ + SINGLE_BATCH_SUCCESS, + /** + * Indicate that all information necessary is present, but the batch effect could not be determined. + *

+ * This can result from a failure to perform SVD or to find a suitable batch factor in the experimental design. + */ + BATCH_EFFECT_UNDETERMINED_FAILURE, + /** + * Indicate that there is no batch effect. Yay! + */ + NO_BATCH_EFFECT_SUCCESS +} diff --git a/gemma-core/src/main/java/ubic/gemma/model/expression/experiment/ExperimentalFactorValueObject.java b/gemma-core/src/main/java/ubic/gemma/model/expression/experiment/ExperimentalFactorValueObject.java index 636c0bdb6b..0d7196bfab 100644 --- a/gemma-core/src/main/java/ubic/gemma/model/expression/experiment/ExperimentalFactorValueObject.java +++ b/gemma-core/src/main/java/ubic/gemma/model/expression/experiment/ExperimentalFactorValueObject.java @@ -18,13 +18,14 @@ */ package ubic.gemma.model.expression.experiment; -import lombok.*; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; import org.apache.commons.lang3.StringUtils; import ubic.gemma.model.IdentifiableValueObject; import ubic.gemma.model.annotations.GemmaWebOnly; -import ubic.gemma.model.common.description.Characteristic; -import java.io.Serializable; import java.util.Collection; import java.util.HashSet; @@ -45,6 +46,8 @@ public class ExperimentalFactorValueObject extends IdentifiableValueObject bioAssays ) { super.setBioAssays( bioAssays ); diff --git a/gemma-core/src/main/java/ubic/gemma/model/expression/experiment/ExpressionExperimentValueObject.java b/gemma-core/src/main/java/ubic/gemma/model/expression/experiment/ExpressionExperimentValueObject.java index fb6490abaa..046b57a557 100644 --- a/gemma-core/src/main/java/ubic/gemma/model/expression/experiment/ExpressionExperimentValueObject.java +++ b/gemma-core/src/main/java/ubic/gemma/model/expression/experiment/ExpressionExperimentValueObject.java @@ -8,7 +8,8 @@ import gemma.gsec.model.Securable; import gemma.gsec.model.SecureValueObject; import gemma.gsec.util.SecurityUtil; -import lombok.*; +import lombok.Getter; +import lombok.Setter; import org.hibernate.Hibernate; import ubic.gemma.model.annotations.GemmaWebOnly; import ubic.gemma.model.common.auditAndSecurity.curation.AbstractCuratableValueObject; @@ -33,7 +34,15 @@ public class ExpressionExperimentValueObject extends AbstractCuratableValueObjec @JsonProperty("numberOfArrayDesigns") private Long arrayDesignCount; private String batchConfound; + /** + * Batch effect type. See {@link BatchEffectType} enum for possible values. + */ private String batchEffect; + /** + * Summary statistics of a batch effect is present. + */ + @Nullable + private String batchEffectStatistics; @JsonIgnore private Integer bioMaterialCount; @JsonIgnore @@ -117,7 +126,10 @@ public ExpressionExperimentValueObject( ExpressionExperiment ee ) { } // Batch info - batchEffect = ee.getBatchEffect(); + if ( ee.getBatchEffect() != null ) { + batchEffect = ee.getBatchEffect().name(); + } + batchEffectStatistics = ee.getBatchEffectStatistics(); batchConfound = ee.getBatchConfound(); // GEEQ: for administrators, create an admin geeq VO. Normal GEEQ VO otherwise. diff --git a/gemma-core/src/main/java/ubic/gemma/model/expression/experiment/FactorValue.java b/gemma-core/src/main/java/ubic/gemma/model/expression/experiment/FactorValue.java index fc5fe3c4ed..b7dff438a6 100644 --- a/gemma-core/src/main/java/ubic/gemma/model/expression/experiment/FactorValue.java +++ b/gemma-core/src/main/java/ubic/gemma/model/expression/experiment/FactorValue.java @@ -18,7 +18,6 @@ */ package ubic.gemma.model.expression.experiment; -import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.hibernate.search.annotations.DocumentId; import org.hibernate.search.annotations.Indexed; @@ -29,6 +28,7 @@ import javax.persistence.Transient; import java.io.Serializable; +import java.util.HashSet; import java.util.Set; /** @@ -51,9 +51,22 @@ public class FactorValue implements Identifiable, Serializable, gemma.gsec.model private Long id; private ExperimentalFactor experimentalFactor; private Measurement measurement; - private Set characteristics = new java.util.HashSet<>(); + private Set characteristics = new HashSet<>(); + /** + * Old-style characteristics from the 1.30 series. + *

+ * This will be removed when all the characteristics are ported to the new style using {@link Statement}. + */ @Deprecated - private Set statements = new java.util.HashSet<>(); + private Set oldStyleCharacteristics = new HashSet<>(); + + /** + * Indicate if this factor value needs attention. + *

+ * If this is the case, there might be a {@link ubic.gemma.model.common.auditAndSecurity.eventType.FactorValueNeedsAttentionEvent} + * event attached to the owning {@link ExpressionExperiment} with additional details. + */ + private boolean needsAttention; /** * No-arg constructor added to satisfy javabean contract @@ -71,13 +84,13 @@ public int hashCode() { HashCodeBuilder builder = new HashCodeBuilder( 17, 7 ).append( this.getExperimentalFactor() ).append( this.getMeasurement() ); if ( this.getCharacteristics() != null ) { for ( Characteristic c : this.getCharacteristics() ) { - - if (c == null) { + + if ( c == null ) { continue; } - + assert c != null; - + builder.append( c.hashCode() ); } } @@ -113,20 +126,10 @@ public String toString() { StringBuilder buf = new StringBuilder(); // this can be null in tests or with half-setup transient objects buf.append( "FactorValue " ).append( this.getId() ).append( ": " ); - + // the experimental factor is lazy-loaded, so it's safer to use the ID if ( this.getExperimentalFactor() != null ) - buf.append( this.getExperimentalFactor().getName() ).append( ":" ); - if ( this.getCharacteristics().size() > 0 ) { - for ( Characteristic c : this.getCharacteristics() ) { - buf.append( c.getValue() ); - if ( this.getCharacteristics().size() > 1 ) - buf.append( " | " ); - } - } else if ( this.getMeasurement() != null ) { - buf.append( this.getMeasurement().getValue() ); - } else if ( StringUtils.isNotBlank( this.getValue() ) ) { - buf.append( this.getValue() ); - } + buf.append( "ExperimentalFactor #" ).append( this.getExperimentalFactor().getId() ).append( ": " ); + buf.append( FactorValueUtils.getSummaryString( this ) ); return buf.toString(); } @@ -151,14 +154,24 @@ public void setSecurityOwner( ExpressionExperiment ee ) { } @IndexedEmbedded - public Set getCharacteristics() { + public Set getCharacteristics() { return this.characteristics; } - public void setCharacteristics( Set characteristics ) { + public void setCharacteristics( Set characteristics ) { this.characteristics = characteristics; } + @Deprecated + public Set getOldStyleCharacteristics() { + return oldStyleCharacteristics; + } + + @Deprecated + public void setOldStyleCharacteristics( Set oldCharacteristics ) { + this.oldStyleCharacteristics = oldCharacteristics; + } + public ExperimentalFactor getExperimentalFactor() { return this.experimentalFactor; } @@ -188,53 +201,24 @@ public void setMeasurement( ubic.gemma.model.common.measurement.Measurement meas } /** - * @deprecated use {@link #getCharacteristics()} instead. + * @deprecated use {@link #getMeasurement()} or {@link #getCharacteristics()} to retrieve the value. */ @Deprecated public String getValue() { return this.value; } - /** - * @deprecated use {@link #setCharacteristics(Set)} ()} instead. - */ @Deprecated public void setValue( String value ) { this.value = value; } - @Deprecated - public Set getStatements() { - return statements; + public boolean getNeedsAttention() { + return needsAttention; } - @Deprecated - public void setStatements( Set statements ) { - this.statements = statements; - } - - /** - * Produce a descriptive string for this factor value. - */ - @Transient - public String getDescriptiveString() { - if ( this.characteristics != null && !this.characteristics.isEmpty() ) { - StringBuilder fvString = new StringBuilder(); - boolean first = true; - for ( Characteristic c : this.characteristics ) { - if ( !first ) { - fvString.append( " " ); - } - fvString.append( StringUtils.strip( c.getValue() ) ); - first = false; - } - return fvString.toString(); - } else if ( this.measurement != null ) { - return StringUtils.strip( this.measurement.getValue() ); - } else if ( StringUtils.isNotBlank( this.value ) ) { - return StringUtils.strip( this.value ); - } - return "absent"; + public void setNeedsAttention( boolean troubled ) { + this.needsAttention = troubled; } private boolean checkGuts( FactorValue that ) { diff --git a/gemma-core/src/main/java/ubic/gemma/model/expression/experiment/FactorValueBasicValueObject.java b/gemma-core/src/main/java/ubic/gemma/model/expression/experiment/FactorValueBasicValueObject.java index 39cc5ab3a3..34cac44057 100644 --- a/gemma-core/src/main/java/ubic/gemma/model/expression/experiment/FactorValueBasicValueObject.java +++ b/gemma-core/src/main/java/ubic/gemma/model/expression/experiment/FactorValueBasicValueObject.java @@ -9,16 +9,15 @@ package ubic.gemma.model.expression.experiment; import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; -import org.apache.commons.lang3.StringUtils; +import org.hibernate.Hibernate; import ubic.gemma.model.IdentifiableValueObject; -import ubic.gemma.model.common.description.Characteristic; +import ubic.gemma.model.annotations.GemmaRestOnly; +import ubic.gemma.model.common.description.CharacteristicValueObject; import ubic.gemma.model.common.measurement.MeasurementValueObject; -import ubic.gemma.model.genome.gene.phenotype.valueObject.CharacteristicBasicValueObject; -import java.util.Comparator; -import java.util.Iterator; import java.util.List; import java.util.stream.Collectors; @@ -34,23 +33,49 @@ public class FactorValueBasicValueObject extends IdentifiableValueObject characteristics; - private boolean isMeasurement; + + /** + * The characteristics associated with this factor value. + */ + private List characteristics; + + /** + * The statements associated with this factor value. + */ + private List statements; /** * @deprecated use either {@link #characteristics} or {@link #measurement} */ @Deprecated + @Schema(description = "Use `summary` if you need a human-readable representation of this factor value or lookup the `characteristics` bag.", deprecated = true) private String value; + /** - * @deprecated define your own logic for summarizing a factor value + * Human-readable summary of the factor value. */ - @Deprecated private String summary; /** @@ -68,51 +93,32 @@ public FactorValueBasicValueObject( FactorValue fv ) { super( fv ); this.experimentalFactorId = fv.getExperimentalFactor().getId(); - if ( fv.getExperimentalFactor().getCategory() != null ) { - this.experimentalFactorCategory = new CharacteristicBasicValueObject( fv.getExperimentalFactor().getCategory() ); + if ( Hibernate.isInitialized( fv.getExperimentalFactor() ) ) { + if ( fv.getExperimentalFactor().getCategory() != null ) { + this.experimentalFactorCategory = new CharacteristicValueObject( fv.getExperimentalFactor().getCategory() ); + } } if ( fv.getMeasurement() != null ) { this.measurement = new MeasurementValueObject( fv.getMeasurement() ); - this.isMeasurement = true; } this.characteristics = fv.getCharacteristics().stream() - .map( CharacteristicBasicValueObject::new ) - .sorted( Comparator.comparing( CharacteristicBasicValueObject::getCategory, Comparator.nullsLast( Comparator.naturalOrder() ) ) - .thenComparing( CharacteristicBasicValueObject::getValue, Comparator.nullsLast( Comparator.naturalOrder() ) ) ) + .sorted() + .map( CharacteristicValueObject::new ) + .collect( Collectors.toList() ); + + this.statements = fv.getCharacteristics().stream() + .sorted() + .map( StatementValueObject::new ) .collect( Collectors.toList() ); this.value = fv.getValue(); - this.summary = getSummaryString( fv ); + this.summary = FactorValueUtils.getSummaryString( fv ); } @Override public String toString() { return "FactorValueValueObject [factor=" + summary + ", value=" + value + "]"; } - - // causes a conflict with getMeasurement... -// public Boolean isMeasurement() { -// return this.measurement != null; -// } - - static String getSummaryString( FactorValue fv ) { - StringBuilder buf = new StringBuilder(); - if ( fv.getCharacteristics().size() > 0 ) { - for ( Iterator iter = fv.getCharacteristics().iterator(); iter.hasNext(); ) { - Characteristic c = iter.next(); - buf.append( c.getValue() == null ? "[Unassigned]" : c.getValue() ); - if ( iter.hasNext() ) - buf.append( ", " ); - } - } else if ( fv.getMeasurement() != null ) { - buf.append( fv.getMeasurement().getValue() ); - } else if ( StringUtils.isNotBlank( fv.getValue() ) ) { - buf.append( fv.getValue() ); - } else { - buf.append( "?" ); - } - return buf.toString(); - } } diff --git a/gemma-core/src/main/java/ubic/gemma/model/expression/experiment/FactorValueUtils.java b/gemma-core/src/main/java/ubic/gemma/model/expression/experiment/FactorValueUtils.java new file mode 100644 index 0000000000..b78a918eb5 --- /dev/null +++ b/gemma-core/src/main/java/ubic/gemma/model/expression/experiment/FactorValueUtils.java @@ -0,0 +1,56 @@ +package ubic.gemma.model.expression.experiment; + +import org.apache.commons.lang3.StringUtils; +import ubic.gemma.model.common.measurement.Measurement; + +import java.util.Iterator; + +import static org.apache.commons.lang3.StringUtils.defaultIfBlank; + +public class FactorValueUtils { + + /** + * Produce a summary string for this factor value. + */ + public static String getSummaryString( FactorValue fv ) { + return getSummaryString( fv, ", " ); + } + + public static String getSummaryString( FactorValue fv, String statementDelimiter ) { + StringBuilder buf = new StringBuilder(); + if ( fv.getMeasurement() != null ) { + if ( fv.getExperimentalFactor() != null && fv.getExperimentalFactor().getCategory() != null ) { + buf.append( defaultIfBlank( fv.getExperimentalFactor().getCategory().getCategory(), "?" ) ) + .append( ": " ); + } + Measurement measurement = fv.getMeasurement(); + buf.append( defaultIfBlank( measurement.getValue(), "?" ) ); + if ( fv.getMeasurement().getUnit() != null ) { + buf.append( " " ).append( fv.getMeasurement().getUnit().getUnitNameCV() ); + } + } else if ( !fv.getCharacteristics().isEmpty() ) { + for ( Iterator iter = fv.getCharacteristics().iterator(); iter.hasNext(); ) { + Statement c = iter.next(); + buf.append( defaultIfBlank( c.getSubject(), "?" ) ); + if ( c.getObject() != null ) { + buf.append( " " ).append( defaultIfBlank( c.getPredicate(), "?" ) ); + buf.append( " " ).append( defaultIfBlank( c.getObject(), "?" ) ); + } + if ( c.getSecondObject() != null ) { + if ( c.getObject() != null ) { + buf.append( " and" ); + } + buf.append( " " ).append( defaultIfBlank( c.getSecondPredicate(), "?" ) ); + buf.append( " " ).append( defaultIfBlank( c.getSecondObject(), "?" ) ); + } + if ( iter.hasNext() ) + buf.append( statementDelimiter ); + } + } else if ( StringUtils.isNotBlank( fv.getValue() ) ) { + buf.append( fv.getValue() ); + } else { + buf.append( "?" ); + } + return buf.toString(); + } +} diff --git a/gemma-core/src/main/java/ubic/gemma/model/expression/experiment/FactorValueValueObject.java b/gemma-core/src/main/java/ubic/gemma/model/expression/experiment/FactorValueValueObject.java index 8c8bc04c3d..75fc74a6f3 100644 --- a/gemma-core/src/main/java/ubic/gemma/model/expression/experiment/FactorValueValueObject.java +++ b/gemma-core/src/main/java/ubic/gemma/model/expression/experiment/FactorValueValueObject.java @@ -10,15 +10,17 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; +import org.hibernate.Hibernate; import ubic.gemma.model.IdentifiableValueObject; +import ubic.gemma.model.annotations.GemmaRestOnly; +import ubic.gemma.model.annotations.GemmaWebOnly; import ubic.gemma.model.common.description.Characteristic; +import ubic.gemma.model.common.description.CharacteristicValueObject; import ubic.gemma.model.common.measurement.MeasurementValueObject; -import ubic.gemma.model.genome.gene.phenotype.valueObject.CharacteristicBasicValueObject; -import javax.annotation.Nullable; -import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; @@ -39,24 +41,78 @@ public class FactorValueValueObject extends IdentifiableValueObject private static final long serialVersionUID = 3378801249808036785L; + /** + * A unique ontology identifier (i.e. IRI) for this factor value. + */ + @GemmaRestOnly + private String ontologyId; + + @GemmaRestOnly + private Long experimentalFactorId; + + @GemmaRestOnly + private CharacteristicValueObject experimentalFactorCategory; + + /** + * Measurement object if this FactorValue is a measurement. + */ + @Schema(description = "This property exists only if a measurement") + @JsonProperty("measurement") + @JsonInclude(JsonInclude.Include.NON_NULL) + private MeasurementValueObject measurementObject; + + private List characteristics; + + private List statements; + + /** + * Human-readable summary of the factor value. + */ + private String summary; + + // fields of the characteristic being focused on + // this is used by the FV editor to model each individual characteristic with its FV + /** + * ID of the experimental factor this FV belongs to. + */ + @Schema(description = "Use `experimentalFactorId` instead.", deprecated = true) + private Long factorId; + /** + * It could be the id of the measurement if there is no characteristic. + */ + @Schema(description = "Use `measurement.id` or `characteristics.id` instead.", deprecated = true) + private Long charId; + @Schema(description = "Use experimentalFactorCategory.category instead.", deprecated = true) private String category; + @Schema(description = "Use experimentalFactorCategory.categoryUri instead.", deprecated = true) private String categoryUri; + @Deprecated + @Schema(description = "This property is never filled nor used; use `summary` if you need a human-readable representation of this factor value.", deprecated = true) private String description; + @Schema(description = "Use `summary` if you need a human-readable representation of this factor value or lookup the `characteristics` bag.", deprecated = true) private String factorValue; + @GemmaWebOnly private String value; + @GemmaWebOnly private String valueUri; - /** - * It could be the id of the measurement if there is no characteristic. - */ - private Long charId; - private Long factorId; - @JsonInclude(JsonInclude.Include.NON_NULL) - @JsonProperty("measurement") - private MeasurementValueObject measurementObject; - @JsonProperty("isMeasurement") - private boolean measurement = false; - @JsonInclude(JsonInclude.Include.NON_EMPTY) - private List characteristics; + @GemmaWebOnly + private String predicate; + @GemmaWebOnly + private String predicateUri; + @GemmaWebOnly + private String object; + @GemmaWebOnly + private String objectUri; + @GemmaWebOnly + private String secondPredicate; + @GemmaWebOnly + private String secondPredicateUri; + @GemmaWebOnly + private String secondObject; + @GemmaWebOnly + private String secondObjectUri; + @GemmaWebOnly + private Boolean needsAttention; /** * Required when using the class as a spring bean. @@ -70,76 +126,91 @@ public FactorValueValueObject( Long id ) { } /** - * @param c - specific characteristic we're focusing on (yes, this is confusing). This is necessary if the - * FactorValue has multiple characteristics. DO NOT pass in the ExperimentalFactor category, this - * just - * confuses things. - * If c is null, the plain "value" is used. - * @param value value + * Create a FactorValue VO. */ - public FactorValueValueObject( FactorValue value, @Nullable Characteristic c ) { + public FactorValueValueObject( FactorValue value ) { super( value ); - this.factorValue = FactorValueBasicValueObject.getSummaryString( value ); + + this.experimentalFactorId = value.getExperimentalFactor().getId(); this.factorId = value.getExperimentalFactor().getId(); + // make sure we fill in the category for this if no characteristic is being *focused* on + if ( Hibernate.isInitialized( value.getExperimentalFactor() ) ) { + Characteristic factorCategory = value.getExperimentalFactor().getCategory(); + if ( factorCategory != null ) { + this.experimentalFactorCategory = new CharacteristicValueObject( factorCategory ); + this.category = factorCategory.getCategory(); + this.categoryUri = factorCategory.getCategoryUri(); + } + } + if ( value.getMeasurement() != null ) { - this.setMeasurement( true ); - this.value = value.getMeasurement().getValue(); this.charId = value.getMeasurement().getId(); + this.factorValue = value.getMeasurement().getValue(); this.measurementObject = new MeasurementValueObject( value.getMeasurement() ); - } else if ( c != null && c.getId() != null ) { - this.charId = c.getId(); } else { - this.value = value.getValue(); - } - - if ( c != null ) { - this.category = c.getCategory(); - this.value = c.getValue(); // clobbers if we set it already - this.categoryUri = c.getCategoryUri(); - this.valueUri = c.getValueUri(); - } - - /* - * Make sure we fill in the Category for this. - */ - Characteristic factorCategory = value.getExperimentalFactor().getCategory(); - if ( this.category == null && factorCategory != null ) { - this.category = factorCategory.getCategory(); - this.categoryUri = factorCategory.getCategoryUri(); + this.factorValue = FactorValueUtils.getSummaryString( value ); } this.characteristics = value.getCharacteristics().stream() - .map( CharacteristicBasicValueObject::new ) - .sorted( Comparator.comparing( CharacteristicBasicValueObject::getCategory, Comparator.nullsLast( Comparator.naturalOrder() ) ) - .thenComparing( CharacteristicBasicValueObject::getValue, Comparator.nullsLast( Comparator.naturalOrder() ) ) ) + .sorted() + .map( CharacteristicValueObject::new ) .collect( Collectors.toList() ); - } - public FactorValueValueObject( FactorValue fv ) { - this( fv, pickCharacteristic( fv ) ); - } + this.statements = value.getCharacteristics().stream() + .sorted() + .map( StatementValueObject::new ) + .collect( Collectors.toList() ); - @Override - public String toString() { - return "FactorValueValueObject [factor=" + factorValue + ", value=" + value + "]"; + this.summary = FactorValueUtils.getSummaryString( value ); + + this.needsAttention = value.getNeedsAttention(); } + /** - * Pick an arbitrary characteristic from the factor value to represent it. + * Create a FactorValue VO focusing on a specific statement. + * + * @param fv a factor value + * @param c specific statement we're focusing on (yes, this is confusing). This is necessary if the factor + * value has multiple characteristics. DO NOT pass in the experimental factor category, this just + * confuses things. If c is null, the plain "value" is used. */ - private static Characteristic pickCharacteristic( FactorValue fv ) { - Characteristic c; - if ( fv.getCharacteristics().size() == 1 ) { - c = fv.getCharacteristics().iterator().next(); - } else if ( fv.getCharacteristics().size() > 1 ) { - /* - * Inadequate! Want to capture them all - use FactorValueBasicValueObject! - */ - c = fv.getCharacteristics().iterator().next(); - } else { - c = null; + public FactorValueValueObject( FactorValue fv, Statement c ) { + this( fv ); + if ( c.getId() != null && !fv.getCharacteristics().contains( c ) ) { + throw new IllegalArgumentException( "The focused characteristic does not belong to the factor value." ); + } + if ( fv.getMeasurement() != null ) { + throw new IllegalArgumentException( "Continuous factor values cannot have a focused characteristic." ); } - return c; + // fill in the details of the *focused* characteristic + this.charId = c.getId(); + this.category = c.getCategory(); + this.categoryUri = c.getCategoryUri(); + this.value = c.getValue(); // clobbers if we set it already + this.valueUri = c.getValueUri(); + this.predicate = c.getPredicate(); + this.predicateUri = c.getPredicateUri(); + this.object = c.getObject(); + this.objectUri = c.getObjectUri(); + this.secondPredicate = c.getSecondPredicate(); + this.secondPredicateUri = c.getSecondPredicateUri(); + this.secondObject = c.getSecondObject(); + this.secondObjectUri = c.getSecondObjectUri(); + } + + /** + * Indicate if this FactorValue is a measurement. + */ + @Schema(description = "Check if a `measurement` key exists instead.", deprecated = true) + @JsonProperty("isMeasurement") + public boolean isMeasurement() { + return this.measurementObject != null; + } + + @Override + public String toString() { + return "FactorValueValueObject [factor=" + factorValue + ", value=" + value + "]"; } } \ No newline at end of file diff --git a/gemma-core/src/main/java/ubic/gemma/model/expression/experiment/Statement.java b/gemma-core/src/main/java/ubic/gemma/model/expression/experiment/Statement.java new file mode 100644 index 0000000000..b6adac8448 --- /dev/null +++ b/gemma-core/src/main/java/ubic/gemma/model/expression/experiment/Statement.java @@ -0,0 +1,285 @@ +package ubic.gemma.model.expression.experiment; + +import org.apache.commons.lang.StringUtils; +import org.hibernate.search.annotations.Analyze; +import org.hibernate.search.annotations.Field; +import ubic.gemma.model.common.description.Characteristic; +import ubic.gemma.model.common.description.CharacteristicUtils; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import javax.persistence.Transient; +import java.util.Comparator; +import java.util.Objects; + +/** + * A special kind of characteristic that act as a statement. + *

+ * It can relate to up to two other objects, essentially forming two statements. This is a limited form of RDF-style + * triplet with the main limitation that a given subject can have up to two predicates and objects. + * @author poirigui + */ +public class Statement extends Characteristic { + + private static final Comparator COMPARATOR = Comparator + .comparing( ( Statement s ) -> s, ( s1, s2 ) -> CharacteristicUtils.compareTerm( s1.getCategory(), s1.getCategoryUri(), s2.getCategory(), s2.getCategoryUri() ) ) + .thenComparing( ( Statement s ) -> s, ( s1, s2 ) -> CharacteristicUtils.compareTerm( s1.getSubject(), s1.getSubjectUri(), s2.getSubject(), s2.getSubjectUri() ) ) + .thenComparing( ( Statement s ) -> s, ( s1, s2 ) -> CharacteristicUtils.compareTerm( s1.predicate, s1.predicateUri, s2.predicate, s2.predicateUri ) ) + .thenComparing( ( Statement s ) -> s, ( s1, s2 ) -> CharacteristicUtils.compareTerm( s1.object, s1.objectUri, s2.object, s2.objectUri ) ) + .thenComparing( ( Statement s ) -> s, ( s1, s2 ) -> CharacteristicUtils.compareTerm( s1.secondPredicate, s1.secondPredicateUri, s2.secondPredicate, s2.secondPredicateUri ) ) + .thenComparing( ( Statement s ) -> s, ( s1, s2 ) -> CharacteristicUtils.compareTerm( s1.secondObject, s1.secondObjectUri, s2.secondObject, s2.secondObjectUri ) ) + .thenComparing( Statement::getId, Comparator.nullsLast( Comparator.naturalOrder() ) ); + + public static class Factory { + public static Statement newInstance() { + return new Statement(); + } + } + + /** + * The predicate of the statement. + */ + @Nullable + private String predicate; + + /** + * The predicate URI of the statement. + */ + @Nullable + private String predicateUri; + + /** + * The object of the statement. + */ + @Nullable + private String object; + + @Nullable + private String objectUri; + + /** + * The second predicate. + */ + @Nullable + private String secondPredicate; + + @Nullable + private String secondPredicateUri; + + /** + * The second object. + */ + @Nullable + private String secondObject; + + @Nullable + private String secondObjectUri; + + /** + * @deprecated use {@link #getSubject()} instead + */ + @Override + @Deprecated + public String getValue() { + return super.getValue(); + } + + /** + * @deprecated use {@link #setSubject(String)} instead + */ + @Override + @Deprecated + public void setValue( String value ) { + super.setValue( value ); + } + + /** + * @deprecated use {@link #getSubjectUri()} instead + */ + @Override + @Deprecated + public String getValueUri() { + return super.getValueUri(); + } + + /** + * @deprecated use {@link #setSubjectUri(String)} instead + */ + @Override + @Deprecated + public void setValueUri( @Nullable String uri ) { + super.setValueUri( uri ); + } + + /** + * Obtain the subject of the statement. + */ + @Transient + public String getSubject() { + return super.getValue(); + } + + public void setSubject( String subject ) { + super.setValue( subject ); + } + + /** + * Obtain the subject URI of the statement. + */ + @Transient + public String getSubjectUri() { + return super.getValueUri(); + } + + public void setSubjectUri( String subject ) { + super.setValueUri( subject ); + } + + @Nullable + public String getPredicate() { + return predicate; + } + + public void setPredicate( @Nullable String predicate ) { + this.predicate = predicate; + } + + @Nullable + public String getPredicateUri() { + return predicateUri; + } + + public void setPredicateUri( @Nullable String predicateUri ) { + this.predicateUri = predicateUri; + } + + @Nullable + @Field + public String getObject() { + return object; + } + + public void setObject( @Nullable String object ) { + this.object = object; + } + + @Nullable + @Field(analyze = Analyze.NO) + public String getObjectUri() { + return objectUri; + } + + public void setObjectUri( @Nullable String objectUri ) { + this.objectUri = objectUri; + } + + @Nullable + public String getSecondPredicate() { + return secondPredicate; + } + + public void setSecondPredicate( @Nullable String secondPredicate ) { + this.secondPredicate = secondPredicate; + } + + @Nullable + public String getSecondPredicateUri() { + return secondPredicateUri; + } + + public void setSecondPredicateUri( @Nullable String secondPredicateUri ) { + this.secondPredicateUri = secondPredicateUri; + } + + @Nullable + @Field + public String getSecondObject() { + return secondObject; + } + + public void setSecondObject( @Nullable String secondObject ) { + this.secondObject = secondObject; + } + + @Nullable + @Field(analyze = Analyze.NO) + public String getSecondObjectUri() { + return secondObjectUri; + } + + public void setSecondObjectUri( @Nullable String secondObjectUri ) { + this.secondObjectUri = secondObjectUri; + } + + @Override + public boolean equals( Object object ) { + if ( object == null ) + return false; + if ( this == object ) + return true; + if ( !( object instanceof Statement ) ) + return false; + Statement that = ( Statement ) object; + if ( this.getId() != null && that.getId() != null ) + return super.equals( that ); + return super.equals( object ) + && CharacteristicUtils.equals( predicate, predicateUri, that.predicate, that.predicateUri ) + && CharacteristicUtils.equals( this.object, objectUri, that.object, that.objectUri ) + && CharacteristicUtils.equals( secondPredicate, secondPredicateUri, that.secondPredicate, that.secondPredicateUri ) + && CharacteristicUtils.equals( secondObject, secondObjectUri, that.secondObject, that.secondObjectUri ); + } + + @Override + public int hashCode() { + if ( this.getId() != null ) + return super.hashCode(); + // don't both hashing labels unless the URI is null + return super.hashCode() + 31 * Objects.hash( + StringUtils.lowerCase( predicateUri != null ? predicateUri : predicate ), + StringUtils.lowerCase( objectUri != null ? objectUri : object ), + StringUtils.lowerCase( secondPredicateUri != null ? secondPredicateUri : secondPredicate ), + StringUtils.lowerCase( secondObjectUri != null ? secondObjectUri : secondObject ) ); + } + + @Override + public int compareTo( @Nonnull Characteristic characteristic ) { + if ( characteristic instanceof Statement ) { + return COMPARATOR.compare( this, ( Statement ) characteristic ); + } + return super.compareTo( characteristic ); + } + + @Override + public String toString() { + StringBuilder b = new StringBuilder( super.toString() ); + if ( predicate != null ) { + b.append( " Predicate=" ).append( predicate ); + if ( predicateUri != null ) { + b.append( " [" ).append( predicateUri ).append( "]" ); + } + } else if ( predicateUri != null ) { + b.append( " Predicate URI=" ).append( predicateUri ); + } + if ( object != null ) { + b.append( " Object=" ).append( object ); + if ( objectUri != null ) { + b.append( " [" ).append( objectUri ).append( "]" ); + } + } + if ( secondPredicate != null ) { + b.append( " Second Predicate=" ).append( secondPredicate ); + if ( secondPredicateUri != null ) { + b.append( " [" ).append( secondPredicateUri ).append( "]" ); + } + } else if ( secondPredicateUri != null ) { + b.append( " Second Predicate URI=" ).append( secondPredicateUri ); + } + if ( secondObject != null ) { + b.append( " Second Object=" ).append( secondObject ); + if ( secondObjectUri != null ) { + b.append( " [" ).append( secondObjectUri ).append( "]" ); + } + } + return b.toString(); + } +} diff --git a/gemma-core/src/main/java/ubic/gemma/model/expression/experiment/StatementValueObject.java b/gemma-core/src/main/java/ubic/gemma/model/expression/experiment/StatementValueObject.java new file mode 100644 index 0000000000..94ff9ee8fe --- /dev/null +++ b/gemma-core/src/main/java/ubic/gemma/model/expression/experiment/StatementValueObject.java @@ -0,0 +1,106 @@ +package ubic.gemma.model.expression.experiment; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; +import lombok.EqualsAndHashCode; +import ubic.gemma.model.IdentifiableValueObject; +import ubic.gemma.model.annotations.GemmaRestOnly; +import ubic.gemma.model.annotations.GemmaWebOnly; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.Comparator; + +import static ubic.gemma.model.common.description.CharacteristicUtils.compareTerm; + +/** + * Represents a VO for a {@link Statement}, typically part of a {@link FactorValueBasicValueObject}. + *

+ * Most of the fields in here are reserved for Gemma Web and we are still discussing the best way to represent these for + * the REST API in #814. + * @see Statement + * @see FactorValueBasicValueObject + * @author poirigui + */ +@Data +@EqualsAndHashCode(callSuper = true) +@JsonIgnoreProperties({ "id" }) +public class StatementValueObject extends IdentifiableValueObject implements Comparable { + + /** + * It is critical that the order of the fields in the comparator is the same as the order of the fields in the + * {@link Statement} comparator since this is used to assign IDs to annotations (i.e. subjects and objects). + */ + private static final Comparator COMPARATOR = Comparator + .comparing( ( StatementValueObject c ) -> c, ( c1, c2 ) -> compareTerm( c1.getCategory(), c1.getCategoryUri(), c2.getCategory(), c2.getCategoryUri() ) ) + .thenComparing( ( StatementValueObject c ) -> c, ( c1, c2 ) -> compareTerm( c1.getSubject(), c1.getSubjectUri(), c2.getSubject(), c2.getSubjectUri() ) ) + .thenComparing( ( StatementValueObject c ) -> c, ( c1, c2 ) -> compareTerm( c1.getPredicate(), c1.getPredicateUri(), c2.getPredicate(), c2.getPredicateUri() ) ) + .thenComparing( ( StatementValueObject c ) -> c, ( c1, c2 ) -> compareTerm( c1.getObject(), c1.getObjectUri(), c2.getObject(), c2.getObjectUri() ) ) + .thenComparing( ( StatementValueObject c ) -> c, ( c1, c2 ) -> compareTerm( c1.getSecondPredicate(), c1.getSecondPredicateUri(), c2.getSecondPredicate(), c2.getSecondPredicateUri() ) ) + .thenComparing( ( StatementValueObject c ) -> c, ( c1, c2 ) -> compareTerm( c1.getSecondObject(), c1.getSecondObjectUri(), c2.getSecondObject(), c2.getSecondObjectUri() ) ) + .thenComparing( StatementValueObject::getId, Comparator.nullsLast( Comparator.naturalOrder() ) ); + + private String category; + @Nullable + private String categoryUri; + + private String subject; + @Nullable + private String subjectUri; + + private String predicate; + @Nullable + private String predicateUri; + + private String object; + @Nullable + private String objectUri; + + @GemmaWebOnly + private String secondPredicate; + @Nullable + @GemmaWebOnly + private String secondPredicateUri; + + @GemmaWebOnly + private String secondObject; + @Nullable + @GemmaWebOnly + private String secondObjectUri; + + /** + * A unique ontology identifier (i.e. IRI) for this subject. + */ + @GemmaRestOnly + private String subjectId; + /** + * A unique ontology identifier (i.e. IRI) for this object. + */ + @GemmaRestOnly + private String objectId; + + public StatementValueObject() { + super(); + } + + public StatementValueObject( Statement s ) { + super( s ); + this.category = s.getCategory(); + this.categoryUri = s.getCategoryUri(); + this.subject = s.getSubject(); + this.subjectUri = s.getSubjectUri(); + this.predicate = s.getPredicate(); + this.predicateUri = s.getPredicateUri(); + this.object = s.getObject(); + this.objectUri = s.getObjectUri(); + this.secondPredicate = s.getSecondPredicate(); + this.secondPredicateUri = s.getSecondPredicateUri(); + this.secondObject = s.getSecondObject(); + this.secondObjectUri = s.getSecondObjectUri(); + } + + @Override + public int compareTo( @Nonnull StatementValueObject other ) { + return COMPARATOR.compare( this, other ); + } +} diff --git a/gemma-core/src/main/java/ubic/gemma/model/genome/gene/GeneProduct.java b/gemma-core/src/main/java/ubic/gemma/model/genome/gene/GeneProduct.java index ba66290055..84dd7b1820 100644 --- a/gemma-core/src/main/java/ubic/gemma/model/genome/gene/GeneProduct.java +++ b/gemma-core/src/main/java/ubic/gemma/model/genome/gene/GeneProduct.java @@ -42,6 +42,12 @@ public class GeneProduct extends ChromosomeFeature { */ private Set exons = new java.util.HashSet<>(); private Gene gene; + /** + * Indicate if this GeneProduct is dummy. + *

+ * Dummy {@link GeneProduct} are not listed in the {@link Gene#getProducts()} associations. + */ + private boolean dummy; @Override public int hashCode() { @@ -157,6 +163,14 @@ public String getPreviousNcbiId() { return super.getPreviousNcbiId(); } + public boolean isDummy() { + return dummy; + } + + public void setDummy( boolean dummy ) { + this.dummy = dummy; + } + private int computeHashCode() { int hashCode = 0; diff --git a/gemma-core/src/main/java/ubic/gemma/model/genome/gene/GeneValueObject.java b/gemma-core/src/main/java/ubic/gemma/model/genome/gene/GeneValueObject.java index 66e61cb035..6d4410a7c4 100644 --- a/gemma-core/src/main/java/ubic/gemma/model/genome/gene/GeneValueObject.java +++ b/gemma-core/src/main/java/ubic/gemma/model/genome/gene/GeneValueObject.java @@ -29,7 +29,7 @@ import ubic.gemma.model.genome.Gene; import ubic.gemma.model.genome.Taxon; import ubic.gemma.model.genome.TaxonValueObject; -import ubic.gemma.model.genome.gene.phenotype.valueObject.CharacteristicValueObject; +import ubic.gemma.model.common.description.CharacteristicValueObject; import javax.annotation.Nullable; import java.io.Serializable; diff --git a/gemma-core/src/main/java/ubic/gemma/model/genome/gene/phenotype/valueObject/BibliographicPhenotypesValueObject.java b/gemma-core/src/main/java/ubic/gemma/model/genome/gene/phenotype/valueObject/BibliographicPhenotypesValueObject.java index 97fd14ccec..b2ef4f3bfc 100644 --- a/gemma-core/src/main/java/ubic/gemma/model/genome/gene/phenotype/valueObject/BibliographicPhenotypesValueObject.java +++ b/gemma-core/src/main/java/ubic/gemma/model/genome/gene/phenotype/valueObject/BibliographicPhenotypesValueObject.java @@ -16,6 +16,7 @@ import ubic.gemma.model.association.phenotype.PhenotypeAssociation; import ubic.gemma.model.common.description.Characteristic; +import ubic.gemma.model.common.description.CharacteristicValueObject; import java.io.Serializable; import java.util.Collection; diff --git a/gemma-core/src/main/java/ubic/gemma/model/genome/gene/phenotype/valueObject/CharacteristicBasicValueObject.java b/gemma-core/src/main/java/ubic/gemma/model/genome/gene/phenotype/valueObject/CharacteristicBasicValueObject.java deleted file mode 100644 index 08b2b64689..0000000000 --- a/gemma-core/src/main/java/ubic/gemma/model/genome/gene/phenotype/valueObject/CharacteristicBasicValueObject.java +++ /dev/null @@ -1,43 +0,0 @@ -package ubic.gemma.model.genome.gene.phenotype.valueObject; - -import lombok.Getter; -import lombok.Setter; -import ubic.gemma.model.IdentifiableValueObject; -import ubic.gemma.model.common.description.Characteristic; - -@Getter -@Setter -public class CharacteristicBasicValueObject extends IdentifiableValueObject { - protected String value; - protected String valueUri; - protected String category; - protected String categoryUri; - - /** - * Required when using the class as a spring bean. - */ - public CharacteristicBasicValueObject() { - super(); - } - - public CharacteristicBasicValueObject( Long id ) { - super( id ); - } - - public CharacteristicBasicValueObject( Long id, String value, String valueUri, String category, - String categoryUri ) { - super( id ); - this.value = value; - this.valueUri = valueUri; - this.category = category; - this.categoryUri = categoryUri; - } - - public CharacteristicBasicValueObject( Characteristic c ) { - super( c ); - this.value = c.getValue(); - this.valueUri = c.getValueUri(); - this.category = c.getCategory(); - this.categoryUri = c.getCategoryUri(); - } -} diff --git a/gemma-core/src/main/java/ubic/gemma/model/genome/gene/phenotype/valueObject/CharacteristicValueObject.java b/gemma-core/src/main/java/ubic/gemma/model/genome/gene/phenotype/valueObject/CharacteristicValueObject.java deleted file mode 100644 index 2036e9ca59..0000000000 --- a/gemma-core/src/main/java/ubic/gemma/model/genome/gene/phenotype/valueObject/CharacteristicValueObject.java +++ /dev/null @@ -1,236 +0,0 @@ -/* - * The Gemma project - * - * Copyright (c) 2011 University of British Columbia - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ -package ubic.gemma.model.genome.gene.phenotype.valueObject; - -import com.google.common.collect.ComparisonChain; -import com.google.common.collect.Ordering; -import lombok.Data; -import lombok.EqualsAndHashCode; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import ubic.gemma.model.IdentifiableValueObject; -import ubic.gemma.model.annotations.GemmaWebOnly; -import ubic.gemma.model.common.description.Characteristic; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; - -/** - * ValueObject wrapper for a Characteristic - * - * @see Characteristic - */ -@SuppressWarnings({ "WeakerAccess", "unused" }) // Used in frontend -@Data -public class CharacteristicValueObject extends IdentifiableValueObject - implements Comparable { - - private static final Log log = LogFactory.getLog( CharacteristicValueObject.class ); - /** - * id used by url on the client side - */ - @GemmaWebOnly - private String urlId = ""; - @GemmaWebOnly - private boolean alreadyPresentInDatabase = false; - @GemmaWebOnly - private boolean alreadyPresentOnGene = false; - private String category = ""; - private String categoryUri = null; - /** - * child term from a root - */ - @GemmaWebOnly - private boolean child = false; - @GemmaWebOnly - private int numTimesUsed = 0; - /** - * what Ontology uses this term - */ - @GemmaWebOnly - private String ontologyUsed = null; - @GemmaWebOnly - private long privateGeneCount = 0L; - /** - * number of occurrences in all genes - */ - @GemmaWebOnly - private long publicGeneCount = 0L; - /** - * root of a query - */ - @GemmaWebOnly - private boolean root = false; - - @GemmaWebOnly - private String taxon = ""; - - private String value = ""; - private String valueUri = null; - - /** - * The definition of the value, if it is an ontology term, as supplied by the ontology. If the value is - * free text, this will be empty - */ - @GemmaWebOnly - private String valueDefinition = ""; - - /** - * Required when using the class as a spring bean. - */ - public CharacteristicValueObject() { - super(); - } - - public CharacteristicValueObject( Long id ) { - super( id ); - } - - public CharacteristicValueObject( Characteristic characteristic ) { - super( characteristic ); - { - this.valueUri = characteristic.getValueUri(); - if ( this.valueUri != null ) - this.urlId = parseUrlId( this.valueUri ); - } - this.category = characteristic.getCategory(); - this.categoryUri = characteristic.getCategoryUri(); - this.value = characteristic.getValue(); - - if ( this.value == null ) { - CharacteristicValueObject.log - .warn( "Characteristic with null value. Id: " + this.id + " cat: " + this.category + " cat uri: " - + this.categoryUri ); - } - } - - public CharacteristicValueObject( Long id, String valueUri ) { - super( id ); - this.valueUri = valueUri; - this.urlId = parseUrlId( this.valueUri ); - if ( StringUtils.isNotBlank( this.urlId ) ) { - try { - // we don't always populate from the database, give it an id anyway - this.id = new Long( this.urlId.replaceAll( "[^\\d.]", "" ) ); - } catch ( Exception e ) { - CharacteristicValueObject.log - .error( "Problem making an id for Phenotype: " + this.urlId + ": " + e.getMessage() ); - } - } - } - - public CharacteristicValueObject( Long id, String value, String valueUri ) { - this( id, valueUri ); - this.value = value; - if ( this.value == null ) { - CharacteristicValueObject.log - .warn( "Characteristic with null value. Id: " + this.id + " cat: " + this.category + " cat uri: " - + this.categoryUri ); - } - } - - public CharacteristicValueObject( Long id, String value, String category, String valueUri, String categoryUri ) { - this( id, value, valueUri ); - this.category = category; - this.categoryUri = categoryUri; - } - - public static Collection characteristic2CharacteristicVO( - Collection characteristics ) { - - Collection characteristicValueObjects; - - if ( characteristics instanceof List ) - characteristicValueObjects = new ArrayList<>(); - else - characteristicValueObjects = new HashSet<>(); - - for ( Characteristic characteristic : characteristics ) { - CharacteristicValueObject characteristicValueObject = new CharacteristicValueObject( characteristic ); - characteristicValueObjects.add( characteristicValueObject ); - } - return characteristicValueObjects; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - if ( this.valueUri != null ) { - result = prime * result + this.valueUri.hashCode(); - } else if ( this.value != null ) { - result = prime * result + this.value.hashCode(); - } else { - result = prime * result + this.id.hashCode(); - } - return result; - } - - @Override - public int compareTo( CharacteristicValueObject o ) { - return ComparisonChain.start() - .compare( category, o.category, Ordering.from( String.CASE_INSENSITIVE_ORDER ).nullsLast() ) - .compare( taxon, o.taxon, Ordering.from( String.CASE_INSENSITIVE_ORDER ).nullsLast() ) - .compare( value, o.value, Ordering.from( String.CASE_INSENSITIVE_ORDER ).nullsLast() ) - .compare( valueUri, o.valueUri, Ordering.from( String.CASE_INSENSITIVE_ORDER ).nullsLast() ).result(); - } - - @Override - public boolean equals( Object obj ) { - if ( this == obj ) - return true; - if ( obj == null ) - return false; - if ( this.getClass() != obj.getClass() ) - return false; - CharacteristicValueObject other = ( CharacteristicValueObject ) obj; - if ( this.valueUri == null ) { - if ( other.valueUri != null ) - return false; - } else { - return this.valueUri.equals( other.valueUri ); - } - - if ( this.value == null ) { - return other.value == null; - } - return this.value.equals( other.value ); - - } - - @Override - public String toString() { - return "[Category= " + category + " Value=" + value + ( valueUri != null ? " (" + valueUri + ")" : "" ) + "]"; - } - - public void incrementOccurrenceCount() { - this.numTimesUsed++; - } - - private static String parseUrlId( String valueUri ) { - if ( StringUtils.isBlank( valueUri ) ) - return ""; - if ( valueUri.indexOf( "#" ) > 0 ) { - return valueUri.substring( valueUri.lastIndexOf( "#" ) + 1, valueUri.length() ); - } else if ( valueUri.lastIndexOf( "/" ) > 0 ) { - return valueUri.substring( valueUri.lastIndexOf( "/" ) + 1, valueUri.length() ); - } else { - return ""; - } - } -} diff --git a/gemma-core/src/main/java/ubic/gemma/model/genome/gene/phenotype/valueObject/DiffExpressionEvidenceValueObject.java b/gemma-core/src/main/java/ubic/gemma/model/genome/gene/phenotype/valueObject/DiffExpressionEvidenceValueObject.java index 789d5b1398..398ad3e079 100644 --- a/gemma-core/src/main/java/ubic/gemma/model/genome/gene/phenotype/valueObject/DiffExpressionEvidenceValueObject.java +++ b/gemma-core/src/main/java/ubic/gemma/model/genome/gene/phenotype/valueObject/DiffExpressionEvidenceValueObject.java @@ -22,6 +22,7 @@ import ubic.gemma.model.analysis.expression.diff.GeneDifferentialExpressionMetaAnalysisResult; import ubic.gemma.model.analysis.expression.diff.GeneDifferentialExpressionMetaAnalysisSummaryValueObject; import ubic.gemma.model.association.phenotype.DifferentialExpressionEvidence; +import ubic.gemma.model.common.description.CharacteristicValueObject; import java.util.SortedSet; diff --git a/gemma-core/src/main/java/ubic/gemma/model/genome/gene/phenotype/valueObject/EvidenceValueObject.java b/gemma-core/src/main/java/ubic/gemma/model/genome/gene/phenotype/valueObject/EvidenceValueObject.java index 172bb2b0a8..b9c67e3700 100644 --- a/gemma-core/src/main/java/ubic/gemma/model/genome/gene/phenotype/valueObject/EvidenceValueObject.java +++ b/gemma-core/src/main/java/ubic/gemma/model/genome/gene/phenotype/valueObject/EvidenceValueObject.java @@ -23,6 +23,7 @@ import ubic.gemma.model.association.phenotype.PhenotypeAssociationPublication; import ubic.gemma.model.association.phenotype.PhenotypeMappingType; import ubic.gemma.model.common.description.Characteristic; +import ubic.gemma.model.common.description.CharacteristicValueObject; import java.io.Serializable; import java.util.HashSet; diff --git a/gemma-core/src/main/java/ubic/gemma/model/genome/gene/phenotype/valueObject/ExperimentalEvidenceValueObject.java b/gemma-core/src/main/java/ubic/gemma/model/genome/gene/phenotype/valueObject/ExperimentalEvidenceValueObject.java index ddb36827d3..c73aa7186b 100644 --- a/gemma-core/src/main/java/ubic/gemma/model/genome/gene/phenotype/valueObject/ExperimentalEvidenceValueObject.java +++ b/gemma-core/src/main/java/ubic/gemma/model/genome/gene/phenotype/valueObject/ExperimentalEvidenceValueObject.java @@ -24,8 +24,9 @@ import java.util.Collection; import java.util.TreeSet; -import org.apache.commons.lang3.StringUtils; +import ubic.gemma.model.common.description.CharacteristicValueObject; +@Deprecated public class ExperimentalEvidenceValueObject extends EvidenceValueObject { private static final long serialVersionUID = 4243531745086284715L; @@ -44,18 +45,11 @@ public ExperimentalEvidenceValueObject( Long id ) { public ExperimentalEvidenceValueObject( ExperimentalEvidence experimentalEvidence ) { super( experimentalEvidence ); - Collection collectionCharacteristics = experimentalEvidence.getExperiment() .getCharacteristics(); - if ( collectionCharacteristics != null ) { for ( Characteristic c : collectionCharacteristics ) { - - CharacteristicValueObject chaValueObject = new CharacteristicValueObject( c.getId(), - c.getValue(), c.getCategory(), c.getValueUri(), c.getCategoryUri() ); - - this.experimentCharacteristics.add( chaValueObject ); - + this.experimentCharacteristics.add( new CharacteristicValueObject( c ) ); } } } diff --git a/gemma-core/src/main/java/ubic/gemma/model/genome/gene/phenotype/valueObject/GeneEvidenceValueObject.java b/gemma-core/src/main/java/ubic/gemma/model/genome/gene/phenotype/valueObject/GeneEvidenceValueObject.java index 0461a9649e..17d5243fca 100644 --- a/gemma-core/src/main/java/ubic/gemma/model/genome/gene/phenotype/valueObject/GeneEvidenceValueObject.java +++ b/gemma-core/src/main/java/ubic/gemma/model/genome/gene/phenotype/valueObject/GeneEvidenceValueObject.java @@ -20,6 +20,7 @@ package ubic.gemma.model.genome.gene.phenotype.valueObject; import ubic.gemma.model.association.phenotype.PhenotypeAssociation; +import ubic.gemma.model.common.description.CharacteristicValueObject; import ubic.gemma.model.genome.Gene; import ubic.gemma.model.genome.gene.GeneValueObject; diff --git a/gemma-core/src/main/java/ubic/gemma/model/genome/gene/phenotype/valueObject/GenericEvidenceValueObject.java b/gemma-core/src/main/java/ubic/gemma/model/genome/gene/phenotype/valueObject/GenericEvidenceValueObject.java index fc4b6555c5..2ce95bc456 100644 --- a/gemma-core/src/main/java/ubic/gemma/model/genome/gene/phenotype/valueObject/GenericEvidenceValueObject.java +++ b/gemma-core/src/main/java/ubic/gemma/model/genome/gene/phenotype/valueObject/GenericEvidenceValueObject.java @@ -19,6 +19,7 @@ package ubic.gemma.model.genome.gene.phenotype.valueObject; import ubic.gemma.model.association.phenotype.GenericEvidence; +import ubic.gemma.model.common.description.CharacteristicValueObject; import java.util.SortedSet; diff --git a/gemma-core/src/main/java/ubic/gemma/model/genome/gene/phenotype/valueObject/TreeCharacteristicValueObject.java b/gemma-core/src/main/java/ubic/gemma/model/genome/gene/phenotype/valueObject/TreeCharacteristicValueObject.java index bb82323363..563df1228b 100644 --- a/gemma-core/src/main/java/ubic/gemma/model/genome/gene/phenotype/valueObject/TreeCharacteristicValueObject.java +++ b/gemma-core/src/main/java/ubic/gemma/model/genome/gene/phenotype/valueObject/TreeCharacteristicValueObject.java @@ -19,6 +19,8 @@ package ubic.gemma.model.genome.gene.phenotype.valueObject; +import ubic.gemma.model.common.description.CharacteristicValueObject; + import java.util.Collection; import java.util.HashSet; import java.util.Set; @@ -48,14 +50,14 @@ public TreeCharacteristicValueObject() { super(); } - public TreeCharacteristicValueObject( Long id, String value, String valueUri ) { - super( id, value, "", valueUri, "" ); + public TreeCharacteristicValueObject( String value, String valueUri ) { + super( value, valueUri, "", "" ); this._id = this.getUrlId(); } - public TreeCharacteristicValueObject( Long id, String value, String valueUri, + public TreeCharacteristicValueObject( String value, String valueUri, TreeSet children ) { - super( id, value, "", valueUri, "" ); + super( value, valueUri, "", "" ); this.children = children; this._id = this.getUrlId(); } diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/hibernate/BatchEffectType.java b/gemma-core/src/main/java/ubic/gemma/persistence/hibernate/BatchEffectType.java new file mode 100644 index 0000000000..2e44bfadcc --- /dev/null +++ b/gemma-core/src/main/java/ubic/gemma/persistence/hibernate/BatchEffectType.java @@ -0,0 +1,25 @@ +package ubic.gemma.persistence.hibernate; + +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.type.EnumType; + +import java.sql.ResultSet; +import java.sql.SQLException; + +/** + * Workaround to handle existing batch effect value in the database. + *

+ * FIXME: remove this once the 1.31 is out and the database has been fully migrated (see #894 for details). + * @author poirigui + */ +public class BatchEffectType extends EnumType { + + @Override + public Object nullSafeGet( ResultSet rs, String[] names, SessionImplementor session, Object owner ) throws SQLException { + String value = rs.getString( names[0] ); + if ( value != null && value.startsWith( "This data set may have a batch artifact" ) ) { + return ubic.gemma.model.expression.experiment.BatchEffectType.NO_BATCH_INFO; + } + return super.nullSafeGet( rs, names, session, owner ); + } +} diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/AbstractFilteringVoEnabledDao.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/AbstractFilteringVoEnabledDao.java index 634a9bd57f..a3e5273375 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/service/AbstractFilteringVoEnabledDao.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/AbstractFilteringVoEnabledDao.java @@ -113,7 +113,7 @@ public void registerProperty( String propertyName, boolean useSubquery ) { if ( useSubquery ) { filterablePropertiesViaSubquery.add( propertyName ); } - log.debug( String.format( "Registered property %s.", propertyName ) ); + log.trace( String.format( "Registered property %s.", propertyName ) ); } else { throw new IllegalArgumentException( String.format( "Filterable property %s is already registered.", propertyName ) ); @@ -138,7 +138,7 @@ public void registerProperties( String... propertyNames ) throws IllegalArgument props.stream().filter( filterableProperties::contains ).collect( Collectors.joining( ", " ) ) ) ); } filterableProperties.addAll( props ); - log.debug( String.format( "Registered properties: %s.", String.join( ", ", propertyNames ) ) ); + log.trace( String.format( "Registered properties: %s.", String.join( ", ", propertyNames ) ) ); } /** @@ -146,7 +146,7 @@ public void registerProperties( String... propertyNames ) throws IllegalArgument */ public void unregisterProperty( String propertyName ) { if ( filterableProperties.remove( propertyName ) ) { - log.debug( String.format( "Unregistered property %s.", propertyName ) ); + log.trace( String.format( "Unregistered property %s.", propertyName ) ); } else { throw new IllegalArgumentException( String.format( "No such filterable properties %s.", propertyName ) ); } @@ -159,7 +159,7 @@ public void unregisterProperty( String propertyName ) { public void unregisterProperties( Predicate predicate ) throws IllegalArgumentException { int sizeBefore = filterableProperties.size(); if ( filterableProperties.removeIf( predicate ) ) { - log.debug( String.format( "Unregistered %d properties using a predicate.", sizeBefore - filterableProperties.size() ) ); + log.trace( String.format( "Unregistered %d properties using a predicate.", sizeBefore - filterableProperties.size() ) ); } else { throw new IllegalArgumentException( "No filterable properties matched the supplied predicate." ); } @@ -212,13 +212,13 @@ public void registerEntity( String prefix, Class entityClass, int maxDepth, b if ( maxDepth > 1 ) { registerEntity( prefix + propertyName + ".", propertyType.getReturnedClass(), maxDepth - 1, useSubquery ); } else { - log.debug( String.format( "Max depth reached, will not recurse into %s", propertyName ) ); + log.trace( String.format( "Max depth reached, will not recurse into %s", propertyName ) ); } } else if ( propertyType.isCollectionType() ) { // special case for collection size, regardless of its type registerProperty( prefix + propertyName + ".size", useSubquery ); } else if ( propertyType instanceof MaterializedBlobType || propertyType instanceof MaterializedClobType || propertyType instanceof MaterializedNClobType ) { - log.debug( String.format( "Property %s%s of type %s was excluded in %s: BLOBs and CLOBs are not exposed by default.", + log.trace( String.format( "Property %s%s of type %s was excluded in %s: BLOBs and CLOBs are not exposed by default.", prefix, propertyName, propertyType.getName(), entityClass.getName() ) ); } else if ( Filter.getConversionService().canConvert( String.class, propertyType.getReturnedClass() ) ) { registerProperty( prefix + propertyName, useSubquery ); @@ -227,7 +227,7 @@ public void registerEntity( String prefix, Class entityClass, int maxDepth, b prefix, propertyName, propertyType.getReturnedClass().getName(), entityClass.getName() ) ); } } - log.debug( String.format( "Registered entity %s %s.", entityClass.getName(), summarizePrefix( prefix ) ) ); + log.trace( String.format( "Registered entity %s %s.", entityClass.getName(), summarizePrefix( prefix ) ) ); } /** @@ -252,7 +252,7 @@ public void unregisterEntity( String prefix, Class entityClass ) { log.warn( String.format( "While unregistering %s %s, no properties were removed. Is it possible that a parent prefix was already removed?", entityClass.getName(), summarizePrefix( prefix ) ) ); } - log.debug( String.format( "Registered entity %s under %s.", entityClass.getName(), summarizePrefix( prefix ) ) ); + log.trace( String.format( "Registered entity %s under %s.", entityClass.getName(), summarizePrefix( prefix ) ) ); } else { throw new IllegalArgumentException( String.format( "No entity of type %s is registered %s.", entityClass.getName(), summarizePrefix( prefix ) ) ); @@ -276,7 +276,7 @@ public void registerAlias( String prefix, @Nullable String objectAlias, Class public void registerAlias( String prefix, @Nullable String objectAlias, Class propertyType, @Nullable String aliasFor, int maxDepth, boolean useSubquery ) { filterablePropertyAliases.add( new FilterablePropertyAlias( prefix, objectAlias, propertyType, aliasFor ) ); registerEntity( prefix, propertyType, maxDepth, useSubquery ); - log.debug( String.format( "Registered alias for %s (%s) %s.", objectAlias, propertyType.getName(), summarizePrefix( prefix ) ) ); + log.trace( String.format( "Registered alias for %s (%s) %s.", objectAlias, propertyType.getName(), summarizePrefix( prefix ) ) ); } private String summarizePrefix( String prefix ) { diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/AbstractService.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/AbstractService.java index 2cd5547515..e4ac355a1d 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/service/AbstractService.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/AbstractService.java @@ -113,6 +113,17 @@ public O loadOrFail( Long id, Supplier exceptionSupplie return entity; } + @Nonnull + @Override + @Transactional(readOnly = true) + public O loadOrFail( Long id, Function exceptionSupplier ) throws T { + O entity = mainDao.load( id ); + if ( entity == null ) { + throw exceptionSupplier.apply( String.format( "No %s with ID %d.", mainDao.getElementClass().getName(), id ) ); + } + return entity; + } + @Nonnull @Override @Transactional(readOnly = true) @@ -171,8 +182,11 @@ public void update( O entity ) { /** * Ensure that a given entity is in the current session. *

- * If not found in the current session, it will be retrieved from the DAO. + * If not found in the current session, it will be retrieved from the persistent storage. + * @deprecated avoid using this if possible and ensure that all operations are properly enclosed by a single + * Hibernate session */ + @Deprecated @CheckReturnValue protected O ensureInSession( O entity ) { Long id = entity.getId(); @@ -187,9 +201,11 @@ protected O ensureInSession( O entity ) { *

* Implementation note: if all entities are already in the session - or are transient, this call is very fast and * does not involve any database interaction, otherwise the persistent entities are fetched in bulk. - * * @see #ensureInSession(Identifiable) + * @deprecated avoid using this if possible and ensure that all operations are properly enclosed by a single + * Hibernate session */ + @Deprecated @CheckReturnValue protected Collection ensureInSession( Collection entities ) { boolean allEntitiesAlreadyInSession = true; @@ -202,8 +218,11 @@ protected Collection ensureInSession( Collection entities ) { } // no need to sort or fetch anything, just return the input - if ( allEntitiesAlreadyInSession ) + if ( allEntitiesAlreadyInSession ) { + log.debug( String.format( "All %d %s entities were already in the session; returning early.", + entities.size(), getElementClass().getName() ) ); return entities; + } // bulk load the remaining persistent entities (if any) Set ids = new HashSet<>( entities.size() ); @@ -216,6 +235,8 @@ protected Collection ensureInSession( Collection entities ) { } } if ( !ids.isEmpty() ) { + log.debug( String.format( "%d %s entities will be loaded from persistent storage.", + ids.size(), getElementClass().getName() ) ); result.addAll( mainDao.load( ids ) ); } diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/BaseReadOnlyService.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/BaseReadOnlyService.java index e5fa7f1fcd..d03a39994f 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/service/BaseReadOnlyService.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/BaseReadOnlyService.java @@ -63,9 +63,24 @@ public interface BaseReadOnlyService { @Nonnull O loadOrFail( Long id ) throws NullPointerException; + /** + * Load an entity or fail with the supplied exception. + * @throws T if the entity does not exist in the persistent storage + */ @Nonnull O loadOrFail( Long id, Supplier exceptionSupplier ) throws T; + /** + * Load an entity or fail with the supplied exception; the message is generated automatically. + * @throws T if the entity does not exist in the persistent storage + */ + @Nonnull + O loadOrFail( Long id, Function exceptionSupplier ) throws T; + + /** + * Load an entity or fail with the supplied exception and message. + * @throws T if the entity does not exist in the persistent storage + */ @Nonnull O loadOrFail( Long id, Function exceptionSupplier, String message ) throws T; diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/TableMaintenanceUtil.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/TableMaintenanceUtil.java index 9f8f9993cf..b0d300b37c 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/service/TableMaintenanceUtil.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/TableMaintenanceUtil.java @@ -27,6 +27,22 @@ */ public interface TableMaintenanceUtil { + /** + * Query space used by the GENE2CS table. + *

+ * You may also want to synchronize to {@link ubic.gemma.model.expression.arrayDesign.ArrayDesign}, + * {@link ubic.gemma.model.expression.designElement.CompositeSequence} and {@link ubic.gemma.model.genome.Gene} + * since entries in the GENE2CS table are removed in cascade. + */ + String GENE2CS_QUERY_SPACE = "GENE2CS"; + /** + * Query space used by the EXPRESSION_EXPERIMENT2CHARACTERISTIC table. + *

+ * You may also want to synchronized to {@link ubic.gemma.model.common.description.Characteristic} since entries in + * the EE2C table are removed in cascade. + */ + String EE2C_QUERY_SPACE = "EXPRESSION_EXPERIMENT2CHARACTERISTIC"; + /** * If necessary, update the GENE2CS table. */ diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/TableMaintenanceUtilImpl.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/TableMaintenanceUtilImpl.java index 6c6c3a57d7..35315f04d8 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/service/TableMaintenanceUtilImpl.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/TableMaintenanceUtilImpl.java @@ -46,7 +46,9 @@ import ubic.gemma.persistence.util.Settings; import javax.annotation.Nullable; -import java.io.*; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.nio.file.Path; @@ -97,8 +99,8 @@ public class TableMaintenanceUtilImpl implements TableMaintenanceUtil { + "join EXPERIMENTAL_FACTOR EF on EXPERIMENTAL_DESIGN.ID = EF.EXPERIMENTAL_DESIGN_FK " + "join FACTOR_VALUE FV on FV.EXPERIMENTAL_FACTOR_FK = EF.ID " + "join CHARACTERISTIC C on FV.ID = C.FACTOR_VALUE_FK " - // exclude statements from being added (see https://github.com/PavlidisLab/Gemma/issues/909 for details) - + "where I.class = 'ExpressionExperiment' and C.class is null"; + // remove C.class = 'Statement' once the old-style characteristics are removed (see https://github.com/PavlidisLab/Gemma/issues/929 for details) + + "where I.class = 'ExpressionExperiment' and C.class = 'Statement'"; private static final Path DEFAULT_GENE2CS_INFO_PATH = Paths.get( Settings.getString( "gemma.appdata.home" ), "DbReports", "gene2cs.info" ); private static final Log log = LogFactory.getLog( TableMaintenanceUtil.class.getName() ); @Autowired @@ -189,7 +191,7 @@ public void updateGene2CsEntries() { public int updateExpressionExperiment2CharacteristicEntries() { log.info( "Updating the EXPRESSION_EXPERIMENT2CHARACTERISTIC table..." ); int updated = sessionFactory.getCurrentSession().createSQLQuery( E2C_QUERY ) - .addSynchronizedQuerySpace( "EXPRESSION_EXPERIMENT2CHARACTERISTIC" ) + .addSynchronizedQuerySpace( EE2C_QUERY_SPACE ) .setParameter( "eeClass", ExpressionExperiment.class ) .setParameter( "bmClass", BioMaterial.class ) .setParameter( "edClass", ExperimentalDesign.class ) @@ -224,7 +226,7 @@ private void generateGene2CsEntries() { TableMaintenanceUtilImpl.log.info( "Updating the GENE2CS table..." ); int updated = this.sessionFactory.getCurrentSession() .createSQLQuery( TableMaintenanceUtilImpl.GENE2CS_REPOPULATE_QUERY ) - .addSynchronizedQuerySpace( "GENE2CS" ) + .addSynchronizedQuerySpace( GENE2CS_QUERY_SPACE ) .executeUpdate(); TableMaintenanceUtilImpl.log.info( String.format( "Done regenerating the GENE2CS table; %d entries were updated.", updated ) ); } diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/analysis/expression/diff/DifferentialExpressionAnalysisServiceImpl.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/analysis/expression/diff/DifferentialExpressionAnalysisServiceImpl.java index fd79cec015..f3b6ab521e 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/service/analysis/expression/diff/DifferentialExpressionAnalysisServiceImpl.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/analysis/expression/diff/DifferentialExpressionAnalysisServiceImpl.java @@ -30,10 +30,8 @@ import ubic.gemma.model.analysis.expression.diff.DifferentialExpressionAnalysisValueObject; import ubic.gemma.model.analysis.expression.diff.ExpressionAnalysisResultSet; import ubic.gemma.model.analysis.expression.diff.GeneDifferentialExpressionMetaAnalysis; -import ubic.gemma.model.expression.experiment.BioAssaySet; -import ubic.gemma.model.expression.experiment.ExperimentalFactor; -import ubic.gemma.model.expression.experiment.ExpressionExperiment; -import ubic.gemma.model.expression.experiment.ExpressionExperimentDetailsValueObject; +import ubic.gemma.model.expression.bioAssay.BioAssay; +import ubic.gemma.model.expression.experiment.*; import ubic.gemma.model.genome.Gene; import ubic.gemma.model.genome.Taxon; import ubic.gemma.persistence.service.AbstractService; @@ -153,6 +151,11 @@ public DifferentialExpressionAnalysis thaw( DifferentialExpressionAnalysis diffe Hibernate.initialize( differentialExpressionAnalysis ); Hibernate.initialize( differentialExpressionAnalysis.getExperimentAnalyzed() ); Hibernate.initialize( differentialExpressionAnalysis.getExperimentAnalyzed().getBioAssays() ); + for ( BioAssay bm : differentialExpressionAnalysis.getExperimentAnalyzed().getBioAssays() ) { + for ( FactorValue fv : bm.getSampleUsed().getFactorValues() ) { + Hibernate.initialize( fv.getExperimentalFactor() ); + } + } Hibernate.initialize( differentialExpressionAnalysis.getProtocol() ); @@ -221,7 +224,7 @@ public Map metas = this.geneDiffExMetaAnalysisDao diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/analysis/expression/diff/ExpressionAnalysisResultSetDaoImpl.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/analysis/expression/diff/ExpressionAnalysisResultSetDaoImpl.java index cd6f9eba7f..ef0eeac834 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/service/analysis/expression/diff/ExpressionAnalysisResultSetDaoImpl.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/analysis/expression/diff/ExpressionAnalysisResultSetDaoImpl.java @@ -20,10 +20,7 @@ import lombok.extern.apachecommons.CommonsLog; import org.apache.commons.lang3.time.StopWatch; -import org.hibernate.Criteria; -import org.hibernate.Hibernate; -import org.hibernate.Query; -import org.hibernate.SessionFactory; +import org.hibernate.*; import org.hibernate.criterion.Projections; import org.hibernate.criterion.Restrictions; import org.hibernate.sql.JoinType; @@ -37,6 +34,8 @@ import ubic.gemma.model.common.description.Characteristic; import ubic.gemma.model.common.description.DatabaseEntry; import ubic.gemma.model.common.protocol.Protocol; +import ubic.gemma.model.expression.arrayDesign.ArrayDesign; +import ubic.gemma.model.expression.designElement.CompositeSequence; import ubic.gemma.model.expression.experiment.BioAssaySet; import ubic.gemma.model.expression.experiment.ExpressionExperiment; import ubic.gemma.model.expression.experiment.ExpressionExperimentSubSet; @@ -50,6 +49,8 @@ import java.util.Map; import java.util.stream.Collectors; +import static ubic.gemma.persistence.service.TableMaintenanceUtil.GENE2CS_QUERY_SPACE; + /** * @author Paul */ @@ -192,13 +193,21 @@ protected void configureFilterableProperties( FilterablePropertiesConfigurer con // use the characteristics instead configurer.registerAlias( "analysis.subsetFactorValue.characteristics.", "sfvc", Characteristic.class, null, 1 ); + configurer.unregisterProperty( "analysis.subsetFactorValue.characteristics.migratedToStatement" ); configurer.unregisterProperty( "analysis.subsetFactorValue.characteristics.originalValue" ); + configurer.unregisterProperty( "analysis.subsetFactorValue.isBaseline" ); + configurer.unregisterProperty( "analysis.subsetFactorValue.needsAttention" ); + configurer.unregisterProperty( "analysis.subsetFactorValue.oldStyleCharacteristics.size" ); configurer.unregisterProperty( "analysis.subsetFactorValue.value" ); - // baseline is always baseline - configurer.unregisterProperty( "baselineGroup.isBaseline" ); configurer.registerAlias( "baselineGroup.characteristics.", "bc", Characteristic.class, null, 1 ); + configurer.unregisterProperty( "baselineGroup.characteristics.migratedToStatement" ); configurer.unregisterProperty( "baselineGroup.characteristics.originalValue" ); + configurer.unregisterProperty( "baselineGroup.experimentalFactor.annotations.size" ); + configurer.unregisterProperty( "baselineGroup.experimentalFactor.factorValues.size" ); + configurer.unregisterProperty( "baselineGroup.isBaseline" ); + configurer.unregisterProperty( "baselineGroup.needsAttention" ); + configurer.unregisterProperty( "baselineGroup.oldStyleCharacteristics.size" ); configurer.unregisterProperty( "baselineGroup.value" ); // not relevant @@ -228,10 +237,15 @@ public Map> loadResultToGenesMap( ExpressionAnalysisResultSet r + "join GENE2CS on GENE2CS.CS = result.PROBE_FK " + "join CHROMOSOME_FEATURE as {gene} on {gene}.ID = GENE2CS.GENE " + "where result.RESULT_SET_FK = :rsid" ) - .addSynchronizedQuerySpace( "GENE2CS" ) + .addSynchronizedQuerySpace( GENE2CS_QUERY_SPACE ) + .addSynchronizedEntityClass( ArrayDesign.class ) + .addSynchronizedEntityClass( CompositeSequence.class ) + .addSynchronizedEntityClass( Gene.class ) .addScalar( "RESULT_ID", StandardBasicTypes.LONG ) .addEntity( "gene", Gene.class ) .setParameter( "rsid", resultSet.getId() ) + // analysis results are immutable and the GENE2CS is generated, so flushing is pointless + .setFlushMode( FlushMode.MANUAL ) .setCacheable( true ); //noinspection unchecked diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/association/phenotype/PhenotypeAssociationDao.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/association/phenotype/PhenotypeAssociationDao.java index eca5d9b2b6..e0c89a54de 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/service/association/phenotype/PhenotypeAssociationDao.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/association/phenotype/PhenotypeAssociationDao.java @@ -21,7 +21,7 @@ import ubic.gemma.model.common.description.ExternalDatabase; import ubic.gemma.model.genome.Taxon; import ubic.gemma.model.genome.gene.GeneValueObject; -import ubic.gemma.model.genome.gene.phenotype.valueObject.CharacteristicValueObject; +import ubic.gemma.model.common.description.CharacteristicValueObject; import ubic.gemma.model.genome.gene.phenotype.valueObject.ExternalDatabaseStatisticsValueObject; import ubic.gemma.model.genome.gene.phenotype.valueObject.GeneEvidenceValueObject; import ubic.gemma.model.genome.gene.phenotype.valueObject.PhenotypeValueObject; diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/association/phenotype/PhenotypeAssociationDaoImpl.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/association/phenotype/PhenotypeAssociationDaoImpl.java index afbdf0e4d9..389ce606e5 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/service/association/phenotype/PhenotypeAssociationDaoImpl.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/association/phenotype/PhenotypeAssociationDaoImpl.java @@ -32,7 +32,7 @@ import ubic.gemma.model.genome.Taxon; import ubic.gemma.model.genome.TaxonValueObject; import ubic.gemma.model.genome.gene.GeneValueObject; -import ubic.gemma.model.genome.gene.phenotype.valueObject.CharacteristicValueObject; +import ubic.gemma.model.common.description.CharacteristicValueObject; import ubic.gemma.model.genome.gene.phenotype.valueObject.ExternalDatabaseStatisticsValueObject; import ubic.gemma.model.genome.gene.phenotype.valueObject.GeneEvidenceValueObject; import ubic.gemma.model.genome.gene.phenotype.valueObject.PhenotypeValueObject; diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/association/phenotype/service/PhenotypeAssociationService.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/association/phenotype/service/PhenotypeAssociationService.java index 339196928f..fe38ec1fc0 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/service/association/phenotype/service/PhenotypeAssociationService.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/association/phenotype/service/PhenotypeAssociationService.java @@ -20,7 +20,7 @@ import ubic.gemma.model.common.description.ExternalDatabase; import ubic.gemma.model.genome.Taxon; import ubic.gemma.model.genome.gene.GeneValueObject; -import ubic.gemma.model.genome.gene.phenotype.valueObject.CharacteristicValueObject; +import ubic.gemma.model.common.description.CharacteristicValueObject; import ubic.gemma.model.genome.gene.phenotype.valueObject.ExternalDatabaseStatisticsValueObject; import ubic.gemma.model.genome.gene.phenotype.valueObject.GeneEvidenceValueObject; import ubic.gemma.model.genome.gene.phenotype.valueObject.PhenotypeValueObject; diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/association/phenotype/service/PhenotypeAssociationServiceImpl.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/association/phenotype/service/PhenotypeAssociationServiceImpl.java index ac5da1a78e..abb9af7016 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/service/association/phenotype/service/PhenotypeAssociationServiceImpl.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/association/phenotype/service/PhenotypeAssociationServiceImpl.java @@ -22,7 +22,7 @@ import ubic.gemma.model.common.description.ExternalDatabase; import ubic.gemma.model.genome.Taxon; import ubic.gemma.model.genome.gene.GeneValueObject; -import ubic.gemma.model.genome.gene.phenotype.valueObject.CharacteristicValueObject; +import ubic.gemma.model.common.description.CharacteristicValueObject; import ubic.gemma.model.genome.gene.phenotype.valueObject.ExternalDatabaseStatisticsValueObject; import ubic.gemma.model.genome.gene.phenotype.valueObject.GeneEvidenceValueObject; import ubic.gemma.model.genome.gene.phenotype.valueObject.PhenotypeValueObject; diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/common/auditAndSecurity/AuditEventDao.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/common/auditAndSecurity/AuditEventDao.java index 5ac927e6d4..6a0e59399d 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/service/common/auditAndSecurity/AuditEventDao.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/common/auditAndSecurity/AuditEventDao.java @@ -20,10 +20,8 @@ import ubic.gemma.model.common.Auditable; import ubic.gemma.model.common.auditAndSecurity.AuditEvent; -import ubic.gemma.model.common.auditAndSecurity.AuditEventValueObject; import ubic.gemma.model.common.auditAndSecurity.eventType.AuditEventType; import ubic.gemma.persistence.service.BaseDao; -import ubic.gemma.persistence.service.BaseVoEnabledDao; import java.util.Collection; import java.util.Date; @@ -49,8 +47,13 @@ public interface AuditEventDao extends BaseDao { */ AuditEvent getLastEvent( Auditable auditable, Class type ); + /** + * Obtain the latest {@link AuditEvent} of a specified type, excluding a certain number of types. + */ + AuditEvent getLastEvent( Auditable auditable, Class type, Collection> excludedTypes ); + Map, Map> getLastEventsByType( - Collection auditables, Collection> types ); + Collection auditables, Collection> types ); /** * Get auditables that have been Created since the given date diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/common/auditAndSecurity/AuditEventDaoImpl.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/common/auditAndSecurity/AuditEventDaoImpl.java index 5b0759687c..023b8ccfb7 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/service/common/auditAndSecurity/AuditEventDaoImpl.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/common/auditAndSecurity/AuditEventDaoImpl.java @@ -32,6 +32,7 @@ import ubic.gemma.model.common.auditAndSecurity.eventType.AuditEventType; import ubic.gemma.persistence.service.AbstractDao; +import javax.annotation.Nullable; import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; @@ -47,8 +48,8 @@ public class AuditEventDaoImpl extends AbstractDao implements AuditE * Classes that we track for 'updated since'. This is used for "What's new" functionality. */ private static final String[] AUDITABLES_TO_TRACK_FOR_WHATS_NEW = { - "ubic.gemma.model.expression.arrayDesign.ArrayDesign", - "ubic.gemma.model.expression.experiment.ExpressionExperiment" }; + "ubic.gemma.model.expression.arrayDesign.ArrayDesign", + "ubic.gemma.model.expression.experiment.ExpressionExperiment" }; @Autowired public AuditEventDaoImpl( SessionFactory sessionFactory ) { @@ -67,25 +68,30 @@ public List getEvents( final Auditable auditable ) { Long id = auditable.getAuditTrail().getId(); //noinspection unchecked return this.getSessionFactory().getCurrentSession() - .createQuery( "select e from AuditTrail t join t.events e where t.id = :id order by e.date,e.id " ) - .setParameter( "id", id ).list(); + .createQuery( "select e from AuditTrail t join t.events e where t.id = :id order by e.date,e.id " ) + .setParameter( "id", id ).list(); } @Override public AuditEvent getLastEvent( Auditable auditable, Class type ) { - return getLastEvents( Collections.singleton( auditable ), type ).get( auditable ); + return getLastEvents( Collections.singleton( auditable ), type, null ).get( auditable ); + } + + @Override + public AuditEvent getLastEvent( Auditable auditable, Class type, Collection> excludedTypes ) { + return getLastEvents( Collections.singleton( auditable ), type, excludedTypes ).get( auditable ); } @Override public Map, Map> getLastEventsByType( - Collection auditables, Collection> types ) { + Collection auditables, Collection> types ) { Map, Map> results = new HashMap<>(); for ( Class ti : types ) { - Map results2 = getLastEvents( auditables, ti ); + Map results2 = getLastEvents( auditables, ti, null ); results.put( ti, results2.entrySet().stream() - .filter( e -> ti.isAssignableFrom( e.getValue().getEventType().getClass() ) ) - .collect( Collectors.toMap( Map.Entry::getKey, Map.Entry::getValue ) ) ); + .filter( e -> ti.isAssignableFrom( e.getValue().getEventType().getClass() ) ) + .collect( Collectors.toMap( Map.Entry::getKey, Map.Entry::getValue ) ) ); } return results; } @@ -102,7 +108,7 @@ public Collection getNewSinceDate( Date date ) { Collection result = new HashSet<>(); for ( String clazz : AuditEventDaoImpl.AUDITABLES_TO_TRACK_FOR_WHATS_NEW ) { String queryString = "select distinct adb from " + clazz - + " adb inner join adb.auditTrail atr inner join atr.events as ae where ae.date > :date and ae.action='C'"; + + " adb inner join adb.auditTrail atr inner join atr.events as ae where ae.date > :date and ae.action='C'"; this.tryAddAllToResult( result, queryString, date ); } return result; @@ -120,7 +126,7 @@ public Collection getUpdatedSinceDate( Date date ) { Collection result = new HashSet<>(); for ( String clazz : AuditEventDaoImpl.AUDITABLES_TO_TRACK_FOR_WHATS_NEW ) { String queryString = "select distinct adb from " + clazz - + " adb inner join adb.auditTrail atr inner join atr.events as ae where ae.date > :date and ae.action='U'"; + + " adb inner join adb.auditTrail atr inner join atr.events as ae where ae.date > :date and ae.action='U'"; this.tryAddAllToResult( result, queryString, date ); } return result; @@ -133,9 +139,9 @@ public boolean hasEvent( Auditable a, Class type ) { @Override public void retainHavingEvent( final Collection a, - final Class type ) { + final Class type ) { - final Map events = this.getLastEvents( a, type ); + final Map events = this.getLastEvents( a, type, null ); CollectionUtils.filter( a, events::containsKey ); @@ -143,17 +149,17 @@ public void retainHavingEvent( final Collection a, @Override public void retainLackingEvent( final Collection a, - final Class type ) { + final Class type ) { StopWatch timer = new StopWatch(); timer.start(); - final Map events = this.getLastEvents( a, type ); + final Map events = this.getLastEvents( a, type, null ); AbstractDao.log.info( "Phase I: " + timer.getTime() + "ms" ); CollectionUtils.filter( a, ( Predicate ) arg0 -> !events.containsKey( arg0 ) ); } - private Map getLastEvents( final Collection auditables, Class type ) { + private Map getLastEvents( final Collection auditables, Class types, @Nullable Collection> excludedTypes ) { if ( auditables.isEmpty() ) { return Collections.emptyMap(); } @@ -164,25 +170,32 @@ private Map getLastEvents( final Collection atMap = auditables.stream() - .collect( Collectors.toMap( a -> a.getAuditTrail().getId(), Function.identity() ) ); + .collect( Collectors.toMap( a -> a.getAuditTrail().getId(), Function.identity() ) ); - Set> classes = getClassHierarchy( type ); + Set> classes = getClassHierarchy( types ); + + // remove all the types we don't want + if ( excludedTypes != null ) { + for ( Class excludedType : excludedTypes ) { + classes.removeAll( getClassHierarchy( excludedType ) ); + } + } //language=HQL final String queryString = "select trail.id, ae from AuditTrail trail " - + "join trail.events ae " - + "join fetch ae.eventType et " // fetching here prevents a separate select query - + "where trail.id in :trails and type(et) in :classes " - // annoyingly, Hibernate does not select the latest event when grouping by trail, so we have to fetch - // them all - + "group by trail, ae " - // latest by date or ID to break ties - + "order by ae.date desc, ae.id desc"; + + "join trail.events ae " + + "join fetch ae.eventType et " // fetching here prevents a separate select query + + "where trail.id in :trails and type(et) in :classes " + // annoyingly, Hibernate does not select the latest event when grouping by trail, so we have to fetch + // them all + + "group by trail, ae " + // latest by date or ID to break ties + + "order by ae.date desc, ae.id desc"; Query queryObject = this.getSessionFactory().getCurrentSession() - .createQuery( queryString ) - .setParameterList( "trails", atMap.keySet() ) - .setParameterList( "classes", classes ); + .createQuery( queryString ) + .setParameterList( "trails", atMap.keySet() ) + .setParameterList( "classes", classes ); List qr = queryObject.list(); for ( Object o : qr ) { @@ -196,8 +209,8 @@ private Map getLastEvents( final Collection 500 ) { AbstractDao.log.info( String.format( "Last event of type %s (closure: %s) retrieved for %d items in %d ms", - type.getName(), classes.stream().map( Class::getName ).collect( Collectors.joining( ", " ) ), - auditables.size(), timer.getTime() ) ); + types.getName(), classes.stream().map( Class::getName ).collect( Collectors.joining( ", " ) ), + auditables.size(), timer.getTime() ) ); } return result; @@ -228,7 +241,7 @@ private Set> getClassHierarchy( Class type ) classes.add( Class.forName( className ) ); } catch ( ClassNotFoundException e ) { log.error( String.format( "Failed to find subclass %s of %s, it will not be included in the query." - , className, type.getName() ), e ); + , className, type.getName() ), e ); } } return classes; diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/common/auditAndSecurity/AuditEventService.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/common/auditAndSecurity/AuditEventService.java index d66285739c..1a6ae126b7 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/service/common/auditAndSecurity/AuditEventService.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/common/auditAndSecurity/AuditEventService.java @@ -23,6 +23,7 @@ import ubic.gemma.model.common.auditAndSecurity.AuditEvent; import ubic.gemma.model.common.auditAndSecurity.eventType.AuditEventType; +import javax.annotation.Nullable; import java.util.Collection; import java.util.Date; import java.util.List; @@ -36,9 +37,14 @@ public interface AuditEventService { @Secured({ "IS_AUTHENTICATED_ANONYMOUSLY" }) List getEvents( Auditable auditable ); + @Nullable @Secured({ "IS_AUTHENTICATED_ANONYMOUSLY" }) AuditEvent getLastEvent( Auditable auditable, Class type ); + @Nullable + @Secured({ "IS_AUTHENTICATED_ANONYMOUSLY" }) + AuditEvent getLastEvent( Auditable auditable, Class type, Collection> excludedTypes ); + /** * Fast method to retrieve auditEventTypes of multiple classes. * @@ -49,7 +55,7 @@ public interface AuditEventService { */ @Secured({ "IS_AUTHENTICATED_ANONYMOUSLY" }) Map, Map> getLastEvents( - Collection auditables, Collection> types ); + Collection auditables, Collection> types ); /** * @param date date diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/common/auditAndSecurity/AuditEventServiceImpl.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/common/auditAndSecurity/AuditEventServiceImpl.java index 5b6efcefb8..d0f9196afe 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/service/common/auditAndSecurity/AuditEventServiceImpl.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/common/auditAndSecurity/AuditEventServiceImpl.java @@ -55,10 +55,16 @@ public AuditEvent getLastEvent( Auditable auditable, Class type, Collection> excludedTypes ) { + return auditEventDao.getLastEvent( auditable, type, excludedTypes ); + } + @Override @Transactional(readOnly = true) public Map, Map> getLastEvents( - Collection auditables, Collection> types ) { + Collection auditables, Collection> types ) { return this.auditEventDao.getLastEventsByType( auditables, types ); } diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/common/description/CharacteristicDao.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/common/description/CharacteristicDao.java index e2093f947e..25a5c1fcc9 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/service/common/description/CharacteristicDao.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/common/description/CharacteristicDao.java @@ -21,9 +21,9 @@ import lombok.Value; import ubic.gemma.model.common.Identifiable; import ubic.gemma.model.common.description.Characteristic; +import ubic.gemma.model.common.description.CharacteristicValueObject; import ubic.gemma.model.expression.experiment.ExpressionExperiment; import ubic.gemma.model.genome.Taxon; -import ubic.gemma.model.genome.gene.phenotype.valueObject.CharacteristicValueObject; import ubic.gemma.persistence.service.BrowsingDao; import ubic.gemma.persistence.service.FilteringVoEnabledDao; diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/common/description/CharacteristicDaoImpl.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/common/description/CharacteristicDaoImpl.java index fa79e59b68..03ce573a10 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/service/common/description/CharacteristicDaoImpl.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/common/description/CharacteristicDaoImpl.java @@ -35,6 +35,7 @@ import ubic.gemma.model.common.Identifiable; import ubic.gemma.model.common.description.BibliographicReference; import ubic.gemma.model.common.description.Characteristic; +import ubic.gemma.model.common.description.CharacteristicValueObject; import ubic.gemma.model.expression.biomaterial.BioMaterial; import ubic.gemma.model.expression.experiment.ExperimentalDesign; import ubic.gemma.model.expression.experiment.ExperimentalFactor; @@ -42,7 +43,6 @@ import ubic.gemma.model.expression.experiment.FactorValue; import ubic.gemma.model.genome.Taxon; import ubic.gemma.model.genome.gene.GeneSet; -import ubic.gemma.model.genome.gene.phenotype.valueObject.CharacteristicValueObject; import ubic.gemma.persistence.service.AbstractNoopFilteringVoEnabledDao; import ubic.gemma.persistence.util.AclQueryUtils; import ubic.gemma.persistence.util.EntityUtils; @@ -52,6 +52,8 @@ import java.util.*; import java.util.stream.Collectors; +import static ubic.gemma.persistence.service.TableMaintenanceUtil.EE2C_QUERY_SPACE; + /** * @author Luke * @author Paul @@ -162,7 +164,7 @@ private Query prepareExperimentsByUrisQuery( Collection uris, @Nullable .addScalar( "VALUE_URI", StandardBasicTypes.STRING ) .addScalar( "EXPRESSION_EXPERIMENT_FK", StandardBasicTypes.LONG ) // invalidate the cache when the EE2C table is updated - .addSynchronizedQuerySpace( "EXPRESSION_EXPERIMENT2CHARACTERISTIC" ) + .addSynchronizedQuerySpace( EE2C_QUERY_SPACE ) // invalidate the cache when new characteristics are added/removed .addSynchronizedEntityClass( Characteristic.class ); diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/common/description/CharacteristicService.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/common/description/CharacteristicService.java index bbea74ebc9..70322dc63b 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/service/common/description/CharacteristicService.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/common/description/CharacteristicService.java @@ -23,7 +23,7 @@ import ubic.gemma.model.common.description.Characteristic; import ubic.gemma.model.expression.experiment.ExpressionExperiment; import ubic.gemma.model.genome.Taxon; -import ubic.gemma.model.genome.gene.phenotype.valueObject.CharacteristicValueObject; +import ubic.gemma.model.common.description.CharacteristicValueObject; import ubic.gemma.persistence.service.BaseService; import ubic.gemma.persistence.service.BaseVoEnabledService; import ubic.gemma.persistence.service.FilteringVoEnabledDao; diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/common/description/CharacteristicServiceImpl.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/common/description/CharacteristicServiceImpl.java index 97dedfc09b..d599921480 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/service/common/description/CharacteristicServiceImpl.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/common/description/CharacteristicServiceImpl.java @@ -23,10 +23,11 @@ import org.springframework.transaction.annotation.Transactional; import ubic.gemma.model.common.Identifiable; import ubic.gemma.model.common.description.Characteristic; +import ubic.gemma.model.common.description.CharacteristicValueObject; import ubic.gemma.model.expression.experiment.ExpressionExperiment; import ubic.gemma.model.genome.Taxon; -import ubic.gemma.model.genome.gene.phenotype.valueObject.CharacteristicValueObject; import ubic.gemma.persistence.service.AbstractFilteringVoEnabledService; +import ubic.gemma.persistence.service.expression.experiment.StatementDao; import javax.annotation.Nullable; import java.util.Collection; @@ -45,7 +46,7 @@ public class CharacteristicServiceImpl extends AbstractFilteringVoEnabledService private final CharacteristicDao characteristicDao; @Autowired - public CharacteristicServiceImpl( CharacteristicDao characteristicDao ) { + public CharacteristicServiceImpl( CharacteristicDao characteristicDao, StatementDao statementDao ) { super( characteristicDao ); this.characteristicDao = characteristicDao; } diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/arrayDesign/ArrayDesignDaoImpl.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/arrayDesign/ArrayDesignDaoImpl.java index 8f3bbb180d..85a43136cb 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/arrayDesign/ArrayDesignDaoImpl.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/arrayDesign/ArrayDesignDaoImpl.java @@ -34,6 +34,7 @@ import ubic.gemma.model.expression.bioAssay.BioAssay; import ubic.gemma.model.expression.designElement.CompositeSequence; import ubic.gemma.model.expression.experiment.ExpressionExperiment; +import ubic.gemma.model.genome.Gene; import ubic.gemma.model.genome.Taxon; import ubic.gemma.model.genome.biosequence.BioSequence; import ubic.gemma.model.genome.sequenceAnalysis.AnnotationAssociation; @@ -51,6 +52,8 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; +import static ubic.gemma.persistence.service.TableMaintenanceUtil.GENE2CS_QUERY_SPACE; + /** * @author pavlidis * @see ubic.gemma.model.expression.arrayDesign.ArrayDesign @@ -726,6 +729,10 @@ public long numGenes( ArrayDesign arrayDesign ) { return ( ( BigInteger ) getSessionFactory().getCurrentSession().createSQLQuery( "select count(distinct g2cs.GENE) from GENE2CS g2cs " + "where g2cs.AD = :arrayDesignId" ) + .addSynchronizedQuerySpace( GENE2CS_QUERY_SPACE ) + .addSynchronizedEntityClass( ArrayDesign.class ) + .addSynchronizedEntityClass( CompositeSequence.class ) + .addSynchronizedEntityClass( Gene.class ) .setParameter( "arrayDesignId", arrayDesign.getId() ) .uniqueResult() ).longValue(); } diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/arrayDesign/CuratableService.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/arrayDesign/CuratableService.java index 3654e2de0a..437222f338 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/arrayDesign/CuratableService.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/arrayDesign/CuratableService.java @@ -59,6 +59,11 @@ public interface CuratableService C loadOrFail( Long id, Supplier exceptionSupplier ) throws T; + @Nonnull + @Override + @Secured({ "IS_AUTHENTICATED_ANONYMOUSLY", "AFTER_ACL_READ" }) + C loadOrFail( Long id, Function exceptionSupplier ) throws T; + @Nonnull @Override @Secured({ "IS_AUTHENTICATED_ANONYMOUSLY", "AFTER_ACL_READ" }) diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/bioAssay/BioAssayDao.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/bioAssay/BioAssayDao.java index a3dbc9482d..e42dca6f5d 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/bioAssay/BioAssayDao.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/bioAssay/BioAssayDao.java @@ -40,9 +40,5 @@ public interface BioAssayDao extends FilteringVoEnabledDao findByAccession( String accession ); - void thaw( BioAssay bioAssay ); - - Collection thaw( Collection bioAssays ); - List loadValueObjects( Collection entities, Map arrayDesignValueObjects, boolean basic ); } diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/bioAssay/BioAssayDaoImpl.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/bioAssay/BioAssayDaoImpl.java index 12d110c1b8..6693495dc1 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/bioAssay/BioAssayDaoImpl.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/bioAssay/BioAssayDaoImpl.java @@ -19,8 +19,6 @@ package ubic.gemma.persistence.service.expression.bioAssay; import org.apache.commons.lang3.StringUtils; -import org.hibernate.Hibernate; -import org.hibernate.Session; import org.hibernate.SessionFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; @@ -28,10 +26,8 @@ import ubic.gemma.model.expression.bioAssay.BioAssay; import ubic.gemma.model.expression.bioAssay.BioAssayValueObject; import ubic.gemma.model.expression.bioAssayData.BioAssayDimension; -import ubic.gemma.model.expression.biomaterial.BioMaterial; import ubic.gemma.persistence.service.AbstractNoopFilteringVoEnabledDao; -import ubic.gemma.persistence.service.expression.arrayDesign.ArrayDesignDao; -import ubic.gemma.persistence.util.*; +import ubic.gemma.persistence.util.BusinessKey; import java.util.*; @@ -41,9 +37,6 @@ @Repository public class BioAssayDaoImpl extends AbstractNoopFilteringVoEnabledDao implements BioAssayDao { - @Autowired - private ArrayDesignDao arrayDesignDao; - @Autowired public BioAssayDaoImpl( SessionFactory sessionFactory ) { super( BioAssay.class, sessionFactory ); @@ -75,34 +68,6 @@ public Collection findByAccession( String accession ) { .setParameter( "accession", accession ).list(); } - @Override - public void thaw( final BioAssay bioAssay ) { - try { - Hibernate.initialize( bioAssay.getArrayDesignUsed() ); - Hibernate.initialize( bioAssay.getOriginalPlatform() ); - BioMaterial bm = bioAssay.getSampleUsed(); - Hibernate.initialize( bm ); - Hibernate.initialize( bm.getBioAssaysUsedIn() ); - Hibernate.initialize( bm.getFactorValues() ); - } catch ( Throwable th ) { - throw new RuntimeException( - "Error performing 'BioAssayDao.thawRawAndProcessed(BioAssay bioAssay)' --> " + th, th ); - } - } - - @Override - public Collection thaw( Collection bioAssays ) { - if ( bioAssays.isEmpty() ) - return bioAssays; - //noinspection unchecked - return this.getSessionFactory().getCurrentSession() - .createQuery( "select distinct b from BioAssay b left join fetch b.arrayDesignUsed" - + " left join fetch b.sampleUsed bm" - + " left join bm.factorValues left join bm.bioAssaysUsedIn where b.id in (:ids) " ) - .setParameterList( "ids", EntityUtils.getIds( bioAssays ) ) - .list(); - } - /** * Method that allows specification of FactorValueBasicValueObject in the bioMaterialVOs * diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/bioAssay/BioAssayServiceImpl.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/bioAssay/BioAssayServiceImpl.java index 0c8ea5f548..3d3a1712fc 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/bioAssay/BioAssayServiceImpl.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/bioAssay/BioAssayServiceImpl.java @@ -93,15 +93,19 @@ public void removeBioMaterialAssociation( final BioAssay bioAssay, final BioMate @Override @Transactional(readOnly = true) public BioAssay thaw( BioAssay bioAssay ) { - bioAssay = loadOrFail( bioAssay.getId() ); - this.bioAssayDao.thaw( bioAssay ); + bioAssay = ensureInSession( bioAssay ); + this.bioMaterialDao.thaw( bioAssay.getSampleUsed() ); return bioAssay; } @Override @Transactional(readOnly = true) public Collection thaw( Collection bioAssays ) { - return this.bioAssayDao.thaw( bioAssays ); + bioAssays = ensureInSession( bioAssays ); + for ( BioAssay bioAssay : bioAssays ) { + this.bioMaterialDao.thaw( bioAssay.getSampleUsed() ); + } + return bioAssays; } @Override diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/bioAssayData/AbstractDesignElementDataVectorDao.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/bioAssayData/AbstractDesignElementDataVectorDao.java index 4416be0824..51d61c3a92 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/bioAssayData/AbstractDesignElementDataVectorDao.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/bioAssayData/AbstractDesignElementDataVectorDao.java @@ -97,7 +97,8 @@ public void thaw( Collection designElementDataVectors ) { + "left join fetch ba.sampleUsed bm " + "left join fetch ba.originalPlatform " + "left join fetch ba.arrayDesignUsed " - + "left join fetch bm.factorValues " + + "left join fetch bm.factorValues fv " + + "left join fetch fv.experimentalFactor " + "fetch all properties " + "where bad in :dims" ) .setParameterList( "dims", dims ) diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/bioAssayData/BioAssayDimensionDaoImpl.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/bioAssayData/BioAssayDimensionDaoImpl.java index a2dabfb6f3..f6aaba7071 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/bioAssayData/BioAssayDimensionDaoImpl.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/bioAssayData/BioAssayDimensionDaoImpl.java @@ -19,7 +19,10 @@ package ubic.gemma.persistence.service.expression.bioAssayData; import org.apache.commons.lang3.StringUtils; -import org.hibernate.*; +import org.hibernate.Criteria; +import org.hibernate.FlushMode; +import org.hibernate.Hibernate; +import org.hibernate.SessionFactory; import org.hibernate.criterion.CriteriaSpecification; import org.hibernate.criterion.Restrictions; import org.springframework.beans.factory.annotation.Autowired; @@ -28,6 +31,7 @@ import ubic.gemma.model.expression.bioAssayData.BioAssayDimension; import ubic.gemma.model.expression.bioAssayData.BioAssayDimensionValueObject; import ubic.gemma.model.expression.biomaterial.BioMaterial; +import ubic.gemma.model.expression.experiment.FactorValue; import ubic.gemma.persistence.service.AbstractVoEnabledDao; import java.util.Collection; @@ -126,6 +130,9 @@ public void thaw( final BioAssayDimension bioAssayDimension ) { Hibernate.initialize( bm ); Hibernate.initialize( bm.getBioAssaysUsedIn() ); Hibernate.initialize( bm.getFactorValues() ); + for ( FactorValue fv : bm.getFactorValues() ) { + Hibernate.initialize( fv.getExperimentalFactor() ); + } } } } diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/biomaterial/BioMaterialDao.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/biomaterial/BioMaterialDao.java index 128f79e992..6c6f66fda0 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/biomaterial/BioMaterialDao.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/biomaterial/BioMaterialDao.java @@ -43,8 +43,10 @@ public interface BioMaterialDao extends BaseVoEnabledDao + * The following fields are initialized: sourceTaxon, treatments and factorValues.experimentalFactor. + */ void thaw( BioMaterial bioMaterial ); - - Collection thaw( Collection bioMaterials ); - } diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/biomaterial/BioMaterialDaoImpl.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/biomaterial/BioMaterialDaoImpl.java index d058b8889f..9affb1276c 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/biomaterial/BioMaterialDaoImpl.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/biomaterial/BioMaterialDaoImpl.java @@ -18,7 +18,10 @@ */ package ubic.gemma.persistence.service.expression.biomaterial; -import org.hibernate.*; +import org.hibernate.Criteria; +import org.hibernate.Hibernate; +import org.hibernate.ObjectNotFoundException; +import org.hibernate.SessionFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; import ubic.gemma.model.expression.biomaterial.BioMaterial; @@ -28,11 +31,8 @@ import ubic.gemma.persistence.service.AbstractDao; import ubic.gemma.persistence.service.AbstractVoEnabledDao; import ubic.gemma.persistence.util.BusinessKey; -import ubic.gemma.persistence.util.EntityUtils; import java.util.Collection; -import java.util.LinkedHashSet; -import java.util.List; /** * @author pavlidis @@ -112,22 +112,11 @@ public ExpressionExperiment getExpressionExperiment( Long bioMaterialId ) { @Override public void thaw( final BioMaterial bioMaterial ) { - Hibernate.initialize( bioMaterial ); Hibernate.initialize( bioMaterial.getSourceTaxon() ); - Hibernate.initialize( bioMaterial.getBioAssaysUsedIn() ); Hibernate.initialize( bioMaterial.getTreatments() ); - Hibernate.initialize( bioMaterial.getFactorValues() ); - } - - @Override - public Collection thaw( Collection bioMaterials ) { - if ( bioMaterials.isEmpty() ) - return bioMaterials; - //noinspection unchecked - return this.getSessionFactory().getCurrentSession().createQuery( - "select distinct b from BioMaterial b left join fetch b.sourceTaxon left join fetch b.bioAssaysUsedIn" - + " left join fetch b.treatments left join fetch b.factorValues where b.id in (:ids)" ) - .setParameterList( "ids", EntityUtils.getIds( bioMaterials ) ).list(); + for ( FactorValue fv : bioMaterial.getFactorValues() ) { + Hibernate.initialize( fv.getExperimentalFactor() ); + } } @Override diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/biomaterial/BioMaterialService.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/biomaterial/BioMaterialService.java index c3f2584cc4..17b9ad7a29 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/biomaterial/BioMaterialService.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/biomaterial/BioMaterialService.java @@ -19,11 +19,11 @@ package ubic.gemma.persistence.service.expression.biomaterial; import org.springframework.security.access.annotation.Secured; +import ubic.gemma.model.common.description.Characteristic; import ubic.gemma.model.expression.biomaterial.BioMaterial; import ubic.gemma.model.expression.biomaterial.BioMaterialValueObject; import ubic.gemma.model.expression.experiment.ExpressionExperiment; import ubic.gemma.model.expression.experiment.FactorValue; -import ubic.gemma.persistence.service.BaseImmutableService; import ubic.gemma.persistence.service.BaseService; import ubic.gemma.persistence.service.BaseVoEnabledService; @@ -111,4 +111,16 @@ public interface BioMaterialService extends BaseService, BaseVoEnab String getBioMaterialIdList( Collection bioMaterials ); + /** + * Will persist the give vocab characteristic to the given biomaterial + * @see ubic.gemma.persistence.service.expression.experiment.ExpressionExperimentService#addCharacteristic(ExpressionExperiment, Characteristic) + */ + void addCharacteristic( BioMaterial bm, Characteristic vc ); + + + /** + * Remove the given characteristic from the given biomaterial + * @throws IllegalArgumentException if the characteristic does not belong to the biomaterial + */ + void removeCharacteristic( BioMaterial bm, Characteristic vc ); } diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/biomaterial/BioMaterialServiceImpl.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/biomaterial/BioMaterialServiceImpl.java index ee29879fd6..0882121ffe 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/biomaterial/BioMaterialServiceImpl.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/biomaterial/BioMaterialServiceImpl.java @@ -18,6 +18,9 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.Assert; +import ubic.gemma.model.association.GOEvidenceCode; +import ubic.gemma.model.common.description.Characteristic; import ubic.gemma.model.common.measurement.Measurement; import ubic.gemma.model.common.measurement.MeasurementType; import ubic.gemma.model.common.quantitationtype.PrimitiveType; @@ -29,6 +32,7 @@ import ubic.gemma.model.expression.experiment.FactorValue; import ubic.gemma.persistence.service.AbstractService; import ubic.gemma.persistence.service.AbstractVoEnabledService; +import ubic.gemma.persistence.service.common.description.CharacteristicService; import ubic.gemma.persistence.service.expression.bioAssay.BioAssayDao; import ubic.gemma.persistence.service.expression.experiment.ExperimentalFactorDao; import ubic.gemma.persistence.service.expression.experiment.FactorValueDao; @@ -48,6 +52,8 @@ public class BioMaterialServiceImpl extends AbstractVoEnabledService thaw( Collection bioMaterials ) { - return this.bioMaterialDao.thaw( bioMaterials ); + bioMaterials = ensureInSession( bioMaterials ); + bioMaterials.forEach( this.bioMaterialDao::thaw ); + return bioMaterials; } @Override @@ -169,6 +177,38 @@ public String getBioMaterialIdList( Collection bioMaterials ) { } + @Override + @Transactional + public void addCharacteristic( BioMaterial bm, Characteristic vc ) { + BioMaterialServiceImpl.log.debug( "Vocab Characteristic: " + vc ); + + vc.setEvidenceCode( GOEvidenceCode.IC ); // manually added characteristic + Set chars = new HashSet<>(); + chars.add( vc ); + + Set current = bm.getCharacteristics(); + if ( current == null ) + current = new HashSet<>( chars ); + else + current.addAll( chars ); + + for ( Characteristic characteristic : chars ) { + BioMaterialServiceImpl.log.info( "Adding characteristic to " + bm + " : " + characteristic ); + } + + bm.setCharacteristics( current ); + update( bm ); + } + + @Override + public void removeCharacteristic( BioMaterial bm, Characteristic characterId ) { + Assert.notNull( characterId.getId(), "The characteristic must be persistent." ); + if ( !bm.getCharacteristics().remove( characterId ) ) { + throw new IllegalArgumentException( String.format( "%s does not belong to %s", characterId, bm ) ); + } + characteristicService.remove( characterId ); + } + private BioMaterial update( BioMaterialValueObject bmvo ) { BioMaterial bm = Objects.requireNonNull( this.load( bmvo.getId() ), String.format( "No BioMaterial with ID %d.", bmvo.getId() ) ); diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/designElement/CompositeSequenceDaoImpl.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/designElement/CompositeSequenceDaoImpl.java index 4c7ab9e135..6e57a336e9 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/designElement/CompositeSequenceDaoImpl.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/designElement/CompositeSequenceDaoImpl.java @@ -45,6 +45,8 @@ import javax.annotation.Nullable; import java.util.*; +import static ubic.gemma.persistence.service.TableMaintenanceUtil.GENE2CS_QUERY_SPACE; + /** * @author pavlidis */ @@ -253,6 +255,10 @@ public Map> getGenes( Collection csIdBatch = new HashSet<>(); for ( CompositeSequence cs : compositeSequences ) { diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/BioAssaySetService.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/BioAssaySetService.java new file mode 100644 index 0000000000..329171813a --- /dev/null +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/BioAssaySetService.java @@ -0,0 +1,18 @@ +package ubic.gemma.persistence.service.expression.experiment; + +import org.springframework.transaction.annotation.Transactional; +import ubic.gemma.model.expression.experiment.BioAssaySet; +import ubic.gemma.persistence.service.BaseReadOnlyService; + +import java.util.Collection; + +/** + * Generic service for dealing with all subclasses of {@link BioAssaySet}. + * @author poirigui + */ +public interface BioAssaySetService extends BaseReadOnlyService { + + void remove( Collection entities ); + + void remove( BioAssaySet entity ); +} diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/BioAssaySetServiceImpl.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/BioAssaySetServiceImpl.java new file mode 100644 index 0000000000..97fca7ccdb --- /dev/null +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/BioAssaySetServiceImpl.java @@ -0,0 +1,154 @@ +package ubic.gemma.persistence.service.expression.experiment; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import ubic.gemma.model.expression.experiment.BioAssaySet; +import ubic.gemma.model.expression.experiment.ExpressionExperiment; +import ubic.gemma.model.expression.experiment.ExpressionExperimentSubSet; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.function.Function; +import java.util.function.Supplier; + +@Service +public class BioAssaySetServiceImpl implements BioAssaySetService { + + @Autowired + private ExpressionExperimentService expressionExperimentService; + + @Autowired + private ExpressionExperimentSubSetService expressionExperimentSubSetService; + + @Override + public Class getElementClass() { + return BioAssaySet.class; + } + + @Nullable + @Override + @Transactional(readOnly = true) + public BioAssaySet find( BioAssaySet entity ) { + if ( entity instanceof ExpressionExperiment ) { + return expressionExperimentService.find( ( ExpressionExperiment ) entity ); + } else if ( entity instanceof ExpressionExperimentSubSet ) { + return expressionExperimentSubSetService.find( ( ExpressionExperimentSubSet ) entity ); + } else { + throw new IllegalArgumentException( String.format( "Unsupported BioAssaySet subtype %s.", entity.getClass().getName() ) ); + } + } + + @Nonnull + @Override + @Transactional(readOnly = true) + public BioAssaySet findOrFail( BioAssaySet entity ) { + if ( entity instanceof ExpressionExperiment ) { + return expressionExperimentService.findOrFail( ( ExpressionExperiment ) entity ); + } else if ( entity instanceof ExpressionExperimentSubSet ) { + return expressionExperimentSubSetService.findOrFail( ( ExpressionExperimentSubSet ) entity ); + } else { + throw new IllegalArgumentException( String.format( "Unsupported BioAssaySet subtype %s.", entity.getClass().getName() ) ); + } + } + + @Override + public Collection load( Collection ids ) { + Collection result = new ArrayList<>(); + result.addAll( expressionExperimentService.load( ids ) ); + result.addAll( expressionExperimentSubSetService.load( ids ) ); + return result; + } + + @Nullable + @Override + public BioAssaySet load( Long id ) { + BioAssaySet bas; + bas = expressionExperimentService.load( id ); + if ( bas != null ) { + return bas; + } + return expressionExperimentSubSetService.load( id ); + } + + @Nonnull + @Override + public BioAssaySet loadOrFail( Long id ) throws NullPointerException { + BioAssaySet bas; + bas = expressionExperimentService.load( id ); + if ( bas != null ) { + return bas; + } + return expressionExperimentSubSetService.loadOrFail( id ); + } + + @Nonnull + @Override + public BioAssaySet loadOrFail( Long id, Supplier exceptionSupplier ) throws T { + BioAssaySet bas; + bas = expressionExperimentService.load( id ); + if ( bas != null ) { + return bas; + } + return expressionExperimentSubSetService.loadOrFail( id, exceptionSupplier ); + } + + @Nonnull + @Override + public BioAssaySet loadOrFail( Long id, Function exceptionSupplier ) throws T { + BioAssaySet bas; + bas = expressionExperimentService.load( id ); + if ( bas != null ) { + return bas; + } + return expressionExperimentSubSetService.loadOrFail( id, exceptionSupplier ); + } + + @Nonnull + @Override + public BioAssaySet loadOrFail( Long id, Function exceptionSupplier, String message ) throws T { + BioAssaySet bas; + bas = expressionExperimentService.load( id ); + if ( bas != null ) { + return bas; + } + return expressionExperimentSubSetService.loadOrFail( id, exceptionSupplier, message ); + } + + @Override + @Transactional(readOnly = true) + public Collection loadAll() { + ArrayList results = new ArrayList<>(); + results.addAll( expressionExperimentService.loadAll() ); + results.addAll( expressionExperimentSubSetService.loadAll() ); + return results; + } + + @Override + @Transactional(readOnly = true) + public long countAll() { + return expressionExperimentService.countAll() + expressionExperimentSubSetService.countAll(); + } + + @Override + @Transactional + public void remove( Collection entities ) { + for ( BioAssaySet bas : entities ) { + remove( bas ); + } + } + + @Override + @Transactional + public void remove( BioAssaySet entity ) { + if ( entity instanceof ExpressionExperiment ) { + expressionExperimentService.remove( ( ExpressionExperiment ) entity ); + } else if ( entity instanceof ExpressionExperimentSubSet ) { + expressionExperimentSubSetService.remove( ( ExpressionExperimentSubSet ) entity ); + } else { + throw new IllegalArgumentException( String.format( "Unsupported BioAssaySet subtype %s.", entity.getClass().getName() ) ); + } + } +} diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/ExperimentalDesignDao.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/ExperimentalDesignDao.java index f23808e4ad..0e6fa7fd75 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/ExperimentalDesignDao.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/ExperimentalDesignDao.java @@ -22,6 +22,8 @@ import ubic.gemma.model.expression.experiment.ExpressionExperiment; import ubic.gemma.persistence.service.BaseDao; +import javax.annotation.Nullable; + /** * @see ubic.gemma.model.expression.experiment.ExperimentalDesign */ @@ -30,4 +32,12 @@ public interface ExperimentalDesignDao extends BaseDao { ExperimentalDesign loadWithExperimentalFactors( Long id ); ExpressionExperiment getExpressionExperiment( ExperimentalDesign experimentalDesign ); + + /** + * Pick a random experimental design that needs attention. + * @param excludedDesign an excluded design from sampling + * @return a random experimental design that needs attention or null if none are found + */ + @Nullable + ExperimentalDesign getRandomExperimentalDesignThatNeedsAttention( ExperimentalDesign excludedDesign ); } diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/ExperimentalDesignDaoImpl.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/ExperimentalDesignDaoImpl.java index e33b16fb8b..583a8014b5 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/ExperimentalDesignDaoImpl.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/ExperimentalDesignDaoImpl.java @@ -18,7 +18,7 @@ */ package ubic.gemma.persistence.service.expression.experiment; -import org.hibernate.Criteria; +import org.apache.commons.lang3.RandomUtils; import org.hibernate.SessionFactory; import org.hibernate.criterion.Restrictions; import org.springframework.beans.factory.annotation.Autowired; @@ -27,6 +27,7 @@ import ubic.gemma.model.expression.experiment.ExpressionExperiment; import ubic.gemma.persistence.service.AbstractDao; +import javax.annotation.Nullable; import java.util.List; /** @@ -65,4 +66,21 @@ public ExpressionExperiment getExpressionExperiment( final ExperimentalDesign ex .createQuery( "select distinct ee FROM ExpressionExperiment as ee where ee.experimentalDesign = :ed " ) .setParameter( "ed", experimentalDesign ).uniqueResult(); } + + @Nullable + @Override + public ExperimentalDesign getRandomExperimentalDesignThatNeedsAttention( ExperimentalDesign excludedDesign ) { + Long numThatNeedsAttention = ( Long ) getSessionFactory().getCurrentSession() + .createQuery( "select count(distinct ed) from ExperimentalDesign ed join ed.experimentalFactors ef " + + "join ef.factorValues fv where ed.id != :edId and fv.needsAttention = true" ) + .setParameter( "edId", excludedDesign.getId() ) + .uniqueResult(); + return ( ExperimentalDesign ) getSessionFactory().getCurrentSession() + .createQuery( "select distinct ed from ExperimentalDesign ed join ed.experimentalFactors ef " + + "join ef.factorValues fv where ed.id != :edId and fv.needsAttention = true" ) + .setParameter( "edId", excludedDesign.getId() ) + .setFirstResult( RandomUtils.nextInt( 0, numThatNeedsAttention.intValue() ) ) + .setMaxResults( 1 ) + .uniqueResult(); + } } \ No newline at end of file diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/ExperimentalDesignService.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/ExperimentalDesignService.java index 5259e0a65d..95f8853f68 100755 --- a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/ExperimentalDesignService.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/ExperimentalDesignService.java @@ -62,4 +62,14 @@ public interface ExperimentalDesignService extends BaseService + * This operation is reserved to administrators. + * @see ExperimentalDesignDao#getRandomExperimentalDesignThatNeedsAttention(ExperimentalDesign) + */ + @Nullable + @Secured({ "GROUP_ADMIN" }) + ExperimentalDesign getRandomExperimentalDesignThatNeedsAttention( ExperimentalDesign excludeDesign ); } diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/ExperimentalDesignServiceImpl.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/ExperimentalDesignServiceImpl.java index 4ccaef9f74..6cf4a95a91 100755 --- a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/ExperimentalDesignServiceImpl.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/ExperimentalDesignServiceImpl.java @@ -18,7 +18,6 @@ */ package ubic.gemma.persistence.service.expression.experiment; -import org.hibernate.Hibernate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -26,6 +25,8 @@ import ubic.gemma.model.expression.experiment.ExpressionExperiment; import ubic.gemma.persistence.service.AbstractService; +import javax.annotation.Nullable; + /** * Spring Service base class for ubic.gemma.model.expression.experiment.ExperimentalDesignService, provides * access to all services and entities referenced by this service. @@ -58,4 +59,10 @@ public ExpressionExperiment getExpressionExperiment( ExperimentalDesign experime return this.experimentalDesignDao.getExpressionExperiment( experimentalDesign ); } + @Nullable + @Override + @Transactional(readOnly = true) + public ExperimentalDesign getRandomExperimentalDesignThatNeedsAttention( ExperimentalDesign excludedDesign ) { + return experimentalDesignDao.getRandomExperimentalDesignThatNeedsAttention( excludedDesign ); + } } \ No newline at end of file diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/ExpressionExperimentDao.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/ExpressionExperimentDao.java index e000510fa8..e23278f8df 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/ExpressionExperimentDao.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/ExpressionExperimentDao.java @@ -64,6 +64,8 @@ public interface ExpressionExperimentDao Collection findByExpressedGene( Gene gene, Double rank ); + ExpressionExperiment findByDesign( ExperimentalDesign ed ); + ExpressionExperiment findByFactor( ExperimentalFactor ef ); ExpressionExperiment findByFactorValue( FactorValue fv ); @@ -250,7 +252,7 @@ Map> getSampleRemovalEvents( Collection getAnnotationsByBioMaterials( Long eeId ); - Collection getAnnotationsByFactorvalues( Long eeId ); + Collection getAnnotationsByFactorValues( Long eeId ); /** * Obtain all annotations, grouped by applicable level. diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/ExpressionExperimentDaoImpl.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/ExpressionExperimentDaoImpl.java index 4ea295fabd..7d83798372 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/ExpressionExperimentDaoImpl.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/ExpressionExperimentDaoImpl.java @@ -22,7 +22,6 @@ import gemma.gsec.acl.domain.AclSid; import lombok.Value; import org.apache.commons.lang3.NotImplementedException; -import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.time.StopWatch; import org.hibernate.*; import org.hibernate.criterion.Restrictions; @@ -66,6 +65,7 @@ import static java.util.stream.Collectors.groupingBy; import static java.util.stream.Collectors.summingLong; +import static ubic.gemma.persistence.service.TableMaintenanceUtil.EE2C_QUERY_SPACE; /** * @author pavlidis @@ -310,6 +310,14 @@ public Collection findByExpressedGene( Gene gene, Double r return this.load( eeIds ); } + @Override + public ExpressionExperiment findByDesign( ExperimentalDesign ed ) { + return ( ExpressionExperiment ) getSessionFactory().getCurrentSession() + .createQuery( "select ee from ExpressionExperiment ee where ee.experimentalDesign = :ed" ) + .setParameter( "ed", ed ) + .uniqueResult(); + } + @Override public ExpressionExperiment findByFactor( ExperimentalFactor ef ) { //language=HQL @@ -523,7 +531,7 @@ public Collection getAnnotationsByBioMaterials( * Note we're not using 'distinct' here but the 'equals' for AnnotationValueObject should aggregate these. More * work to do. */ - List raw = this.getSessionFactory().getCurrentSession().createQuery( + List raw = this.getSessionFactory().getCurrentSession().createQuery( "select c from ExpressionExperiment e " + "join e.bioAssays ba join ba.sampleUsed bm " + "join bm.characteristics c where e.id= :eeid" ).setParameter( "eeid", eeId ).list(); @@ -531,20 +539,24 @@ public Collection getAnnotationsByBioMaterials( /* * TODO we need to filter these better; some criteria could be included in the query */ - for ( Object o : raw ) { - Characteristic c = ( Characteristic ) o; + for ( Characteristic c : raw ) { // filter. Could include this in the query if it isn't too complicated. - if ( StringUtils.isBlank( c.getCategoryUri() ) ) { + if ( c.getCategoryUri() == null ) { continue; } - if ( StringUtils.isBlank( c.getValueUri() ) ) { + if ( c.getValueUri() == null ) { continue; } - if ( c.getCategory().equals( "MaterialType" ) || c.getCategory().equals( "molecular entity" ) - || c.getCategory().equals( "LabelCompound" ) ) { + if ( "MaterialType".equalsIgnoreCase( c.getCategory() ) + || "molecular entity".equalsIgnoreCase( c.getCategory() ) + || "LabelCompound".equalsIgnoreCase( c.getCategory() ) ) { + continue; + } + + if ( BaselineSelection.isBaselineCondition( c ) ) { continue; } @@ -558,8 +570,9 @@ public Collection getAnnotationsByBioMaterials( } @Override - public Collection getAnnotationsByFactorvalues( Long eeId ) { - List raw = this.getSessionFactory().getCurrentSession().createQuery( "select c from ExpressionExperiment e " + public Collection getAnnotationsByFactorValues( Long eeId ) { + //noinspection unchecked + List raw = this.getSessionFactory().getCurrentSession().createQuery( "select c from ExpressionExperiment e " + "join e.experimentalDesign ed join ed.experimentalFactors ef join ef.factorValues fv " + "join fv.characteristics c where e.id= :eeid " ).setParameter( "eeid", eeId ).list(); @@ -567,35 +580,41 @@ public Collection getAnnotationsByFactorvalues( * FIXME filtering here is going to have to be more elaborate for this to be useful. */ Collection results = new HashSet<>(); - for ( Object o : raw ) { - Characteristic c = ( Characteristic ) o; + for ( Statement c : raw ) { + // ignore baseline conditions + if ( BaselineSelection.isBaselineCondition( c ) ) { + continue; + } - // ignore free text values - if ( StringUtils.isBlank( c.getValueUri() ) ) { + // ignore batch factors + if ( ExperimentalDesignUtils.BATCH_FACTOR_CATEGORY_NAME.equals( c.getCategory() ) + || ExperimentalDesignUtils.BATCH_FACTOR_CATEGORY_URI.equals( c.getCategoryUri() ) ) { continue; } - // ignore baseline and batch factorvalues (could include in the query) - if ( BaselineSelection.isBaselineCondition( c ) || ( StringUtils.isNotBlank( c.getCategory() ) - && c.getCategory().equals( ExperimentalDesignUtils.BATCH_FACTOR_CATEGORY_NAME ) ) ) { + // ignore timepoints + if ( "http://www.ebi.ac.uk/efo/EFO_0000724".equals( c.getCategoryUri() ) ) { continue; } - // ignore timepoint. - if ( StringUtils.isNotBlank( c.getCategoryUri() ) && c.getCategoryUri() - .equals( "http://www.ebi.ac.uk/efo/EFO_0000724" ) ) { + // DE_include/exclude + if ( "http://gemma.msl.ubc.ca/ont/TGEMO_00013".equals( c.getSubjectUri() ) ) continue; + if ( "http://gemma.msl.ubc.ca/ont/TGEMO_00014".equals( c.getSubjectUri() ) ) + continue; + + // ignore free text values + if ( c.getSubjectUri() != null ) { + results.add( new AnnotationValueObject( c, FactorValue.class ) ); } - if ( StringUtils.isNotBlank( c.getValueUri() ) ) { - // DE_include/exclude - if ( c.getValueUri().equals( "http://gemma.msl.ubc.ca/ont/TGEMO_00013" ) ) - continue; - if ( c.getValueUri().equals( "http://gemma.msl.ubc.ca/ont/TGEMO_00014" ) ) - continue; + if ( c.getObject() != null && c.getObjectUri() != null ) { + results.add( new AnnotationValueObject( c.getCategoryUri(), c.getCategory(), c.getObjectUri(), c.getObject(), FactorValue.class ) ); } - results.add( new AnnotationValueObject( c, FactorValue.class ) ); + if ( c.getSecondObject() != null && c.getSecondObjectUri() != null ) { + results.add( new AnnotationValueObject( c.getCategoryUri(), c.getCategory(), c.getSecondObjectUri(), c.getSecondObject(), FactorValue.class ) ); + } } return results; @@ -683,7 +702,7 @@ public Map getCategoriesUsageFrequency( @Nullable Collecti .addScalar( "CATEGORY", StandardBasicTypes.STRING ) .addScalar( "CATEGORY_URI", StandardBasicTypes.STRING ) .addScalar( "EE_COUNT", StandardBasicTypes.LONG ) - .addSynchronizedQuerySpace( "EXPRESSION_EXPERIMENT2CHARACTERISTIC" ) + .addSynchronizedQuerySpace( EE2C_QUERY_SPACE ) .addSynchronizedEntityClass( Characteristic.class ) .setCacheable( true ); if ( eeIds != null ) { @@ -1780,41 +1799,41 @@ public void remove( ExpressionExperiment ee ) { ee.getCurationDetails().setLastNoteUpdateEvent( null ); ee.getCurationDetails().setLastTroubledEvent( null ); - Collection dims = this.getBioAssayDimensions( ee ); - // dissociate this EE from other parts for ( ExpressionExperiment e : ee.getOtherParts() ) { e.getOtherParts().remove( ee ); } ee.getOtherParts().clear(); + // detach from BA dimensions + Collection dims = this.getBioAssayDimensions( ee ); for ( BioAssayDimension dim : dims ) { dim.getBioAssays().clear(); } - // we don't want to remove a biomaterial twice if it is attached to multiple BAs - Collection bioMaterialsToDelete = new HashSet<>(); - Collection bioAssays = ee.getBioAssays(); - for ( BioAssay ba : bioAssays ) { - // relations to files cascade, so we only have to worry about biomaterials, which aren't cascaded from - // anywhere. BioAssay -> BioMaterial is many-to-one, but bioassaySet (experiment) owns the bioAssay. - BioMaterial biomaterial = ba.getSampleUsed(); - if ( biomaterial == null ) { - log.warn( "BioAssay " + ba + " has no associated BioMaterial when attempting to remove its parent ExpressionExperiment. It will be skipped for now." ); - continue; - } - bioMaterialsToDelete.add( biomaterial ); - ba.setSampleUsed( null ); + // first pass, detach BAs from the samples + for ( BioAssay ba : ee.getBioAssays() ) { + ba.getSampleUsed().getBioAssaysUsedIn().remove( ba ); } - // We remove them here in case they are associated to more than one bioassay-- no cascade is possible. - for ( BioMaterial bm : bioMaterialsToDelete ) { - bm.getFactorValues().clear(); - bm.getBioAssaysUsedIn().clear(); - getSessionFactory().getCurrentSession().delete( bm ); + // second pass, delete samples that are no longer attached to BAs + Set samplesToRemove = new HashSet<>(); + for ( BioAssay ba : ee.getBioAssays() ) { + if ( ba.getSampleUsed().getBioAssaysUsedIn().isEmpty() ) { + samplesToRemove.add( ba.getSampleUsed() ); + } else { + log.warn( String.format( "%s is attached to more than one ExpressionExperiment, the sample will not be deleted.", ba.getSampleUsed() ) ); + } } super.remove( ee ); + + // those need to be removed afterward because otherwise the BioAssay.sampleUsed would become transient while + // cascading and that is not allowed in the data model + log.debug( String.format( "Removing %d BioMaterial that are no longer attached to any BioAssay", samplesToRemove.size() ) ); + for ( BioMaterial bm : samplesToRemove ) { + getSessionFactory().getCurrentSession().delete( bm ); + } } @Override @@ -1849,6 +1868,9 @@ public void thawWithoutVectors( final ExpressionExperiment ee ) { BioMaterial bm = ba.getSampleUsed(); if ( bm != null ) { Hibernate.initialize( bm.getFactorValues() ); + for ( FactorValue fv : bm.getFactorValues() ) { + Hibernate.initialize( fv.getExperimentalFactor() ); + } Hibernate.initialize( bm.getTreatments() ); } } @@ -1863,6 +1885,9 @@ public void thawBioAssays( ExpressionExperiment expressionExperiment ) { for ( BioAssay ba : expressionExperiment.getBioAssays() ) { Hibernate.initialize( ba.getArrayDesignUsed() ); Hibernate.initialize( ba.getSampleUsed() ); + for ( FactorValue fv : ba.getSampleUsed().getFactorValues() ) { + Hibernate.initialize( fv.getExperimentalFactor() ); + } Hibernate.initialize( ba.getOriginalPlatform() ); } } @@ -1897,6 +1922,12 @@ public void thawForFrontEnd( final ExpressionExperiment expressionExperiment ) { if ( expressionExperiment.getExperimentalDesign() != null ) { Hibernate.initialize( expressionExperiment.getExperimentalDesign() ); Hibernate.initialize( expressionExperiment.getExperimentalDesign().getExperimentalFactors() ); + for ( ExperimentalFactor ef : expressionExperiment.getExperimentalDesign().getExperimentalFactors() ) { + Hibernate.initialize( ef ); + for ( FactorValue fv : ef.getFactorValues() ) { + Hibernate.initialize( fv.getExperimentalFactor() ); // is it even necessary? + } + } Hibernate.initialize( expressionExperiment.getExperimentalDesign().getTypes() ); } @@ -2072,15 +2103,21 @@ protected void configureFilterableProperties( FilterablePropertiesConfigurer con // attached terms configurer.registerAlias( "characteristics.", CHARACTERISTIC_ALIAS, Characteristic.class, null, 1, true ); configurer.unregisterProperty( "characteristics.originalValue" ); + configurer.unregisterProperty( "characteristics.migratedToStatement" ); configurer.registerAlias( "experimentalDesign.experimentalFactors.factorValues.characteristics.", FACTOR_VALUE_CHARACTERISTIC_ALIAS, Characteristic.class, null, 1, true ); configurer.unregisterProperty( "experimentalDesign.experimentalFactors.factorValues.characteristics.originalValue" ); + configurer.unregisterProperty( "experimentalDesign.experimentalFactors.factorValues.characteristics.migratedToStatement" ); configurer.registerAlias( "bioAssays.sampleUsed.characteristics.", BIO_MATERIAL_CHARACTERISTIC_ALIAS, Characteristic.class, null, 1, true ); + configurer.unregisterProperty( "bioAssays.sampleUsed.characteristics.migratedToStatement" ); configurer.unregisterProperty( "bioAssays.sampleUsed.characteristics.originalValue" ); configurer.registerAlias( "allCharacteristics.", ALL_CHARACTERISTIC_ALIAS, Characteristic.class, null, 1, true ); configurer.unregisterProperty( "allCharacteristics.originalValue" ); + configurer.unregisterProperty( "allCharacteristics.migratedToStatement" ); configurer.registerAlias( "bioAssays.", BIO_ASSAY_ALIAS, BioAssay.class, null, 2, true ); configurer.unregisterProperty( "bioAssays.accession.Uri" ); + configurer.unregisterProperty( "bioAssays.sampleUsed.factorValues.size" ); + configurer.unregisterProperty( "bioAssays.sampleUsed.treatments.size" ); // this is not useful, unless we add an alias to the alternate names configurer.unregisterProperties( p -> p.endsWith( "alternateNames.size" ) ); diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/ExpressionExperimentService.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/ExpressionExperimentService.java index c44e58fa86..afc8138440 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/ExpressionExperimentService.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/ExpressionExperimentService.java @@ -189,6 +189,9 @@ ExpressionExperiment addRawVectors( ExpressionExperiment eeToUpdate, @Secured({ "IS_AUTHENTICATED_ANONYMOUSLY", "AFTER_ACL_COLLECTION_READ" }) Collection findByExpressedGene( Gene gene, double rank ); + @Secured({ "IS_AUTHENTICATED_ANONYMOUSLY", "AFTER_ACL_READ" }) + ExpressionExperiment findByDesign( ExperimentalDesign ed ); + @Secured({ "IS_AUTHENTICATED_ANONYMOUSLY", "AFTER_ACL_READ" }) ExpressionExperiment findByFactor( ExperimentalFactor factor ); @@ -337,6 +340,7 @@ class CharacteristicWithUsageStatisticsAndOntologyTerm { String getBatchConfound( ExpressionExperiment ee ); /** + * Obtain the full batch effect details of a given experiment. * @param ee experiment * @return details for the principal component most associated with batches (even if it isn't "significant"). Note * that we don't look at every component, just the first few. @@ -344,12 +348,17 @@ class CharacteristicWithUsageStatisticsAndOntologyTerm { BatchEffectDetails getBatchEffectDetails( ExpressionExperiment ee ); /** - * Composes a string describing the batch effect state of the given experiment. - * + * Obtain a {@link BatchEffectType} describing the batch effect state of the given experiment. * @param ee the experiment to get the batch effect for. - * @return a string describing the batch effect. If there is no batch effect on the given ee, null is returned. */ - String getBatchEffect( ExpressionExperiment ee ); + BatchEffectType getBatchEffect( ExpressionExperiment ee ); + + /** + * Obtain a string describing the summary statistics of a batch effect is present in the given experiment. + * @return summary statistics or null if there is no batch effect + */ + @Nullable + String getBatchEffectStatistics( ExpressionExperiment ee ); /** * @param expressionExperiment experiment @@ -565,19 +574,10 @@ Map> getSampleRemovalEvents( /** * Will add the vocab characteristic to the expression experiment and persist the changes. * - * @param vc If the evidence code is null, it will be filled in with IC. A category and value must be provided. - * @param ee the experiment to add the characteristics to. - */ - void saveExpressionExperimentStatement( Characteristic vc, ExpressionExperiment ee ); - - /** - * Will add all the vocab characteristics to the expression experiment and persist the changes. - * - * @param vc Collection of the characteristics to be added to the experiment. If the evidence code is null, it will - * be filled in with IC. A category and value must be provided. * @param ee the experiment to add the characteristics to. + * @param vc If the evidence code is null, it will be filled in with IC. A category and value must be provided. */ - void saveExpressionExperimentStatements( Collection vc, ExpressionExperiment ee ); + void addCharacteristic( ExpressionExperiment ee, Characteristic vc ); @CheckReturnValue @Secured({ "IS_AUTHENTICATED_ANONYMOUSLY", "ACL_SECURABLE_READ" }) diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/ExpressionExperimentServiceImpl.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/ExpressionExperimentServiceImpl.java index 2a28b13281..ddc53498ee 100755 --- a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/ExpressionExperimentServiceImpl.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/ExpressionExperimentServiceImpl.java @@ -18,10 +18,10 @@ */ package ubic.gemma.persistence.service.expression.experiment; -import com.google.common.base.Strings; import gemma.gsec.SecurityService; import lombok.Value; import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.commons.math3.exception.NotStrictlyPositiveException; import org.hibernate.Hibernate; import org.springframework.beans.factory.annotation.Autowired; @@ -29,6 +29,7 @@ import org.springframework.security.access.SecurityConfig; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.Assert; import ubic.basecode.ontology.model.OntologyTerm; import ubic.basecode.ontology.model.OntologyTermSimple; import ubic.gemma.core.analysis.preprocess.batcheffects.BatchConfound; @@ -43,6 +44,7 @@ import ubic.gemma.core.search.SearchService; import ubic.gemma.core.util.ListUtils; import ubic.gemma.model.analysis.expression.ExpressionExperimentSet; +import ubic.gemma.model.association.GOEvidenceCode; import ubic.gemma.model.common.auditAndSecurity.AuditEvent; import ubic.gemma.model.common.auditAndSecurity.eventType.*; import ubic.gemma.model.common.description.AnnotationValueObject; @@ -79,6 +81,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import static java.util.Objects.requireNonNull; import static ubic.gemma.persistence.service.SubqueryUtils.guessAliases; /** @@ -154,7 +157,7 @@ public ExperimentalFactor addFactor( ExpressionExperiment ee, ExperimentalFactor @Transactional public void addFactorValue( ExpressionExperiment ee, FactorValue fv ) { assert fv.getExperimentalFactor() != null; - ExpressionExperiment experiment = Objects.requireNonNull( expressionExperimentDao.load( ee.getId() ) ); + ExpressionExperiment experiment = requireNonNull( expressionExperimentDao.load( ee.getId() ) ); fv.setSecurityOwner( experiment ); Collection efs = experiment.getExperimentalDesign().getExperimentalFactors(); fv = this.factorValueService.create( fv ); @@ -388,6 +391,12 @@ public Collection findByExpressedGene( final Gene gene, fi return this.expressionExperimentDao.findByExpressedGene( gene, rank ); } + @Override + @Transactional(readOnly = true) + public ExpressionExperiment findByDesign( ExperimentalDesign ed ) { + return this.expressionExperimentDao.findByDesign( ed ); + } + /** * @see ExpressionExperimentService#findByFactor(ExperimentalFactor) */ @@ -487,38 +496,35 @@ public Map getAnnotationCountsByIds( final Collection ids ) { @Override @Transactional(readOnly = true) public Set getAnnotationsById( Long eeId ) { - ExpressionExperiment expressionExperiment = Objects.requireNonNull( this.load( eeId ) ); + ExpressionExperiment expressionExperiment = requireNonNull( this.load( eeId ) ); Set annotations = new HashSet<>(); + Collection seenTerms = new TreeSet<>( String.CASE_INSENSITIVE_ORDER ); - Collection seenTerms = new HashSet<>(); for ( Characteristic c : expressionExperiment.getCharacteristics() ) { - AnnotationValueObject annotationValue = new AnnotationValueObject( c, ExpressionExperiment.class ); - - annotations.add( annotationValue ); - seenTerms.add( annotationValue.getTermName() ); + if ( seenTerms.add( annotationValue.getTermName() ) ) { + annotations.add( annotationValue ); + } } /* - * TODO If can be done without much slowdown, add: certain selected (constant?) characteristics from - * biomaterials? (non-redundant with tags) + * TODO If can be done without much slowdown, add: certain characteristics from factor values? (non-baseline, + * non-batch, non-redundant with tags). This is tricky because they are so specific... */ - for ( AnnotationValueObject v : this.getAnnotationsByBioMaterials( eeId ) ) { - if ( !seenTerms.contains( v.getTermName() ) ) { + for ( AnnotationValueObject v : this.getAnnotationsByFactorValues( eeId ) ) { + if ( seenTerms.add( v.getTermName() ) ) { annotations.add( v ); } - seenTerms.add( v.getTermName() ); } /* - * TODO If can be done without much slowdown, add: certain characteristics from factor values? (non-baseline, - * non-batch, non-redundant with tags). This is tricky because they are so specific... + * TODO If can be done without much slowdown, add: certain selected (constant?) characteristics from + * biomaterials? (non-redundant with tags) */ - for ( AnnotationValueObject v : this.getAnnotationsByFactorValues( eeId ) ) { - if ( !seenTerms.contains( v.getTermName() ) ) { + for ( AnnotationValueObject v : this.getAnnotationsByBioMaterials( eeId ) ) { + if ( seenTerms.add( v.getTermName() ) ) { annotations.add( v ); } - seenTerms.add( v.getTermName() ); } return annotations; @@ -893,7 +899,7 @@ public String getBatchConfound( ExpressionExperiment ee ) { } } - return Strings.emptyToNull( result.toString() ); + return StringUtils.stripToNull( result.toString() ); } private boolean checkIfSingleBatch( ExpressionExperiment ee ) { @@ -920,25 +926,30 @@ public BatchEffectDetails getBatchEffectDetails( ExpressionExperiment ee ) { BatchEffectDetails details = new BatchEffectDetails( this.checkBatchFetchStatus( ee ), this.getHasBeenBatchCorrected( ee ), this.checkIfSingleBatch( ee ) ); - if ( !details.hasBatchInformation() || details.isSingleBatch() || details.isFailedToGetBatchInformation() - || details.getHadUninformativeHeaders() || details.getHadSingletonBatches() ) { + // if missing or failed, we can't compute a P-value + if ( !details.hasBatchInformation() || details.hasProblematicBatchInformation() ) { + return details; + } + + // we can't compute a P-value for a single batch + if ( details.isSingleBatch() ) { return details; } for ( ExperimentalFactor ef : ee.getExperimentalDesign().getExperimentalFactors() ) { if ( BatchInfoPopulationServiceImpl.isBatchFactor( ef ) ) { SVDValueObject svd = svdService.getSvdFactorAnalysis( ee.getId() ); - if ( svd == null ) + if ( svd == null ) { + log.warn( "SVD was null for " + ef + ", can't compute batch effect statistics." ); break; + } double minP = 1.0; for ( Integer component : svd.getFactorPvals().keySet() ) { Map cmpEffects = svd.getFactorPvals().get( component ); Double pVal = cmpEffects.get( ef.getId() ); if ( pVal != null && pVal < minP ) { - details.setPvalue( pVal ); - details.setComponent( component + 1 ); - details.setComponentVarianceProportion( svd.getVariances()[component] ); + details.setBatchEffectStatistics( pVal, component + 1, svd.getVariances()[component] ); minP = pVal; } @@ -946,6 +957,9 @@ public BatchEffectDetails getBatchEffectDetails( ExpressionExperiment ee ) { return details; } } + + log.warn( String.format( "No suitable batch factor was found for %s to obtain batch effect statistics.", ee ) ); + return details; } @@ -954,31 +968,42 @@ public BatchEffectDetails getBatchEffectDetails( ExpressionExperiment ee ) { */ @Override @Transactional(readOnly = true) - public String getBatchEffect( ExpressionExperiment ee ) { + public BatchEffectType getBatchEffect( ExpressionExperiment ee ) { BatchEffectDetails beDetails = this.getBatchEffectDetails( ee ); - - String result; if ( !beDetails.hasBatchInformation() ) { - result = "NO_BATCH_INFO"; - } else if ( beDetails.getHadSingletonBatches() ) { - result = "SINGLETON_BATCHES_FAILURE"; - } else if ( beDetails.getHadUninformativeHeaders() ) { - result = "UNINFORMATIVE_HEADERS_FAILURE"; + return BatchEffectType.NO_BATCH_INFO; + } else if ( beDetails.getHasSingletonBatches() ) { + return BatchEffectType.SINGLETON_BATCHES_FAILURE; + } else if ( beDetails.getHasUninformativeBatchInformation() ) { + return BatchEffectType.UNINFORMATIVE_HEADERS_FAILURE; } else if ( beDetails.isSingleBatch() ) { - result = "SINGLE_BATCH_SUCCESS"; + return BatchEffectType.SINGLE_BATCH_SUCCESS; } else if ( beDetails.getDataWasBatchCorrected() ) { - result = "BATCH_CORRECTED_SUCCESS"; // Checked for in ExpressionExperimentDetails.js::renderStatus() - } else if ( beDetails.isFailedToGetBatchInformation() ) { - result = "NO_BATCH_INFO"; // sort of generic - } else if ( beDetails.getPvalue() < ExpressionExperimentServiceImpl.BATCH_EFFECT_THRESHOLD ) { - result = String.format( "This data set may have a batch artifact%s, p=%.5g", - beDetails.getComponent() != null ? " (PC " + beDetails.getComponent() + ")" : "", - beDetails.getPvalue() ); + // Checked for in ExpressionExperimentDetails.js::renderStatus() + return BatchEffectType.BATCH_CORRECTED_SUCCESS; + } else if ( beDetails.hasProblematicBatchInformation() ) { + // sort of generic + return BatchEffectType.PROBLEMATIC_BATCH_INFO_FAILURE; + } else if ( beDetails.getBatchEffectStatistics() == null ) { + return BatchEffectType.BATCH_EFFECT_UNDETERMINED_FAILURE; + } else if ( beDetails.getBatchEffectStatistics().getPvalue() < ExpressionExperimentServiceImpl.BATCH_EFFECT_THRESHOLD ) { + return BatchEffectType.BATCH_EFFECT_FAILURE; } else { - result = "NO_BATCH_EFFECT_SUCCESS"; + return BatchEffectType.NO_BATCH_EFFECT_SUCCESS; } + } - return result; + @Nullable + @Override + @Transactional(readOnly = true) + public String getBatchEffectStatistics( ExpressionExperiment ee ) { + BatchEffectDetails beDetails = this.getBatchEffectDetails( ee ); + if ( beDetails.getBatchEffectStatistics() != null ) { + return String.format( "This data set may have a batch artifact (PC %d), p=%.5g", + beDetails.getBatchEffectStatistics().getComponent(), + beDetails.getBatchEffectStatistics().getPvalue() ); + } + return null; } @Override @@ -1276,30 +1301,28 @@ public ExpressionExperiment replaceRawVectors( ExpressionExperiment ee, /** * Will add the characteristic to the expression experiment and persist the changes. * - * @param vc If the evidence code is null, it will be filled in with IC. A category and value must be provided. * @param ee the experiment to add the characteristics to. + * @param vc If the evidence code is null, it will be filled in with IC. A category and value must be provided. */ @Override @Transactional - public void saveExpressionExperimentStatement( Characteristic vc, ExpressionExperiment ee ) { - ee = this.thawLite( Objects.requireNonNull( this.load( ee.getId() ) ) ); // Necessary to make sure we have the persistent version of the given ee. - ontologyService.addExpressionExperimentStatement( vc, ee ); - this.update( ee ); - } + public void addCharacteristic( ExpressionExperiment ee, Characteristic vc ) { + Assert.isTrue( StringUtils.isNotBlank( vc.getCategory() ), "Must provide a category" ); + Assert.isTrue( StringUtils.isNotBlank( vc.getValue() ), "Must provide a value" ); - /** - * Will add all the vocab characteristics to the expression experiment and persist the changes. - * - * @param vc Collection of the characteristics to be added to the experiment. If the evidence code is null, it will - * be filled in with IC. A category and value must be provided. - * @param ee the experiment to add the characteristics to. - */ - @Override - @Transactional - public void saveExpressionExperimentStatements( Collection vc, ExpressionExperiment ee ) { - for ( Characteristic characteristic : vc ) { - this.saveExpressionExperimentStatement( characteristic, ee ); + ee = ensureInSession( ee ); + + if ( vc.getEvidenceCode() == null ) { + log.debug( String.format( "No evidence code set for %s, defaulting to %s.", vc, GOEvidenceCode.IC ) ); + vc.setEvidenceCode( GOEvidenceCode.IC ); // assume: manually added characteristic } + + ExpressionExperimentServiceImpl.log + .info( "Adding characteristic '" + vc.getValue() + "' to " + ee.getShortName() + " (ID=" + ee.getId() + + ") : " + vc ); + + ee.getCharacteristics().add( vc ); + this.update( ee ); } @Override @@ -1408,7 +1431,7 @@ public void remove( Collection entities ) { } private Collection getAnnotationsByFactorValues( Long eeId ) { - return this.expressionExperimentDao.getAnnotationsByFactorvalues( eeId ); + return this.expressionExperimentDao.getAnnotationsByFactorValues( eeId ); } private Collection getAnnotationsByBioMaterials( Long eeId ) { diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/ExpressionExperimentSubSetDaoImpl.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/ExpressionExperimentSubSetDaoImpl.java index 2d9657a0cb..331047c60e 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/ExpressionExperimentSubSetDaoImpl.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/ExpressionExperimentSubSetDaoImpl.java @@ -1,8 +1,8 @@ /* * The Gemma project. - * + * * Copyright (c) 2006-2007 University of British Columbia - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -63,8 +63,8 @@ public ExpressionExperimentSubSet find( ExpressionExperimentSubSet entity ) { public Collection getFactorValuesUsed( ExpressionExperimentSubSet entity, ExperimentalFactor factor ) { //noinspection unchecked return this.getSessionFactory().getCurrentSession().createQuery( - "select distinct fv from ExpressionExperimentSubSet es join es.bioAssays ba join ba.sampleUsed bm " - + "join bm.factorValues fv where es=:es and fv.experimentalFactor = :ef " ) + "select distinct fv from ExpressionExperimentSubSet es join es.bioAssays ba join ba.sampleUsed bm " + + "join bm.factorValues fv where es=:es and fv.experimentalFactor = :ef " ) .setParameter( "es", entity ).setParameter( "ef", factor ).list(); } @@ -72,8 +72,8 @@ public Collection getFactorValuesUsed( ExpressionExperimentSubSet e public Collection getFactorValuesUsed( Long subSetId, Long experimentalFactor ) { //noinspection unchecked List list = this.getSessionFactory().getCurrentSession().createQuery( - "select distinct fv from ExpressionExperimentSubSet es join es.bioAssays ba join ba.sampleUsed bm " - + "join bm.factorValues fv where es.id=:es and fv.experimentalFactor.id = :ef " ) + "select distinct fv from ExpressionExperimentSubSet es join es.bioAssays ba join ba.sampleUsed bm " + + "join bm.factorValues fv where es.id=:es and fv.experimentalFactor.id = :ef " ) .setParameter( "es", subSetId ).setParameter( "ef", experimentalFactor ).list(); Collection result = new HashSet<>(); for ( FactorValue fv : list ) { diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/FactorValueDao.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/FactorValueDao.java index 6bd846af6d..182523d8c5 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/FactorValueDao.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/FactorValueDao.java @@ -20,10 +20,11 @@ import ubic.gemma.model.expression.experiment.FactorValue; import ubic.gemma.model.expression.experiment.FactorValueValueObject; -import ubic.gemma.persistence.service.BaseVoEnabledDao; import ubic.gemma.persistence.service.FilteringVoEnabledDao; import java.util.Collection; +import java.util.Map; +import java.util.Set; /** * @see ubic.gemma.model.expression.experiment.FactorValue @@ -39,4 +40,20 @@ public interface FactorValueDao extends FilteringVoEnabledDao findByValue( String valuePrefix ); + @Deprecated + FactorValue loadWithOldStyleCharacteristics( Long id, boolean readOnly ); + + /** + * Load all the factor values IDs with their number of old-style characteristics. + * @param excludedIds list of excluded IDs + */ + @Deprecated + Map loadIdsWithNumberOfOldStyleCharacteristics( Set excludedIds ); + + /** + * Update a FactorValue without involving ACL advice. + * @deprecated do not use this, it is only a workaround to make FV migration faster + */ + @Deprecated + void updateIgnoreAcl( FactorValue fv ); } diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/FactorValueDaoImpl.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/FactorValueDaoImpl.java index f14b58aeff..0bac3c746d 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/FactorValueDaoImpl.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/FactorValueDaoImpl.java @@ -19,11 +19,11 @@ package ubic.gemma.persistence.service.expression.experiment; import org.hibernate.Criteria; +import org.hibernate.Hibernate; import org.hibernate.SessionFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; import ubic.gemma.model.expression.biomaterial.BioMaterial; -import ubic.gemma.model.expression.experiment.ExperimentalFactor; import ubic.gemma.model.expression.experiment.FactorValue; import ubic.gemma.model.expression.experiment.FactorValueValueObject; import ubic.gemma.persistence.service.AbstractDao; @@ -33,6 +33,9 @@ import javax.annotation.Nullable; import java.util.Collection; import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; /** *

@@ -56,47 +59,65 @@ public Collection findByValue( String valuePrefix ) { .setParameter( "q", valuePrefix + "%" ).list(); } + @Override + @Deprecated + public FactorValue loadWithOldStyleCharacteristics( Long id, boolean readOnly ) { + boolean previousReadOnly = getSessionFactory().getCurrentSession().isDefaultReadOnly(); + try { + getSessionFactory().getCurrentSession().setDefaultReadOnly( readOnly ); + FactorValue fv = load( id ); + if ( fv != null ) { + Hibernate.initialize( fv.getOldStyleCharacteristics() ); + } + return fv; + } finally { + getSessionFactory().getCurrentSession().setDefaultReadOnly( previousReadOnly ); + } + } + + @Override + @Deprecated + public Map loadIdsWithNumberOfOldStyleCharacteristics( Set excludedIds ) { + List result; + if ( excludedIds.isEmpty() ) { + //noinspection unchecked + result = ( List ) this.getSessionFactory().getCurrentSession() + .createQuery( "select fv.id, size(fv.oldStyleCharacteristics) from FactorValue fv group by fv order by id" ) + .list(); + } else { + //noinspection unchecked + result = ( List ) this.getSessionFactory().getCurrentSession() + .createQuery( "select fv.id, size(fv.oldStyleCharacteristics) from FactorValue fv where fv.id not in :ids group by fv order by id" ) + .setParameterList( "ids", excludedIds ) + .list(); + } + return result.stream().collect( Collectors.toMap( row -> ( Long ) row[0], row -> ( Integer ) row[1] ) ); + } + + @Override + public void updateIgnoreAcl( FactorValue fv ) { + update( fv ); + } + @Override public void remove( @Nullable final FactorValue factorValue ) { if ( factorValue == null ) return; - //noinspection unchecked - Collection bms = this.getSessionFactory().getCurrentSession() - .createQuery( "select distinct bm from BioMaterial as bm join bm.factorValues fv where fv = :fv" ) - .setParameter( "fv", factorValue ).list(); - - AbstractDao.log.info( "Disassociating " + factorValue + " from " + bms.size() + " biomaterials" ); - for ( BioMaterial bioMaterial : bms ) { - AbstractDao.log.info( "Processing " + bioMaterial ); // temporary, debugging. - if ( bioMaterial.getFactorValues().remove( factorValue ) ) { - this.getSessionFactory().getCurrentSession().update( bioMaterial ); - } else { - AbstractDao.log.warn( "Unexpectedly the factor value was not actually associated with " + bioMaterial ); - } - } + // detach from the experimental factor + factorValue.getExperimentalFactor().getFactorValues().remove( factorValue ); - // detach from all associated experimental factors + // detach from any sample //noinspection unchecked - List efs = this.getSessionFactory().getCurrentSession() - .createQuery( "select ef from ExperimentalFactor ef join ef.factorValues fv where fv = :fv" ) + List bms = this.getSessionFactory().getCurrentSession() + .createQuery( "select distinct bm from BioMaterial bm " + + "join bm.factorValues fv where fv = :fv" ) .setParameter( "fv", factorValue ) .list(); - AbstractDao.log.info( "Disassociating " + factorValue + " from " + efs.size() + " experimental factors" ); - for ( ExperimentalFactor ef : efs ) { - ef.getFactorValues().remove( factorValue ); - this.getSessionFactory().getCurrentSession().update( ef ); - } - - // remove any attached statements since those are not mapped in the collection - // remove this in the 1.31 since it will become unnecessary (see https://github.com/PavlidisLab/Gemma/issues/909) - int removedStatements = getSessionFactory().getCurrentSession() - .createSQLQuery( "delete from CHARACTERISTIC where class = 'Statement' and FACTOR_VALUE_FK = :fvId" ) - .setParameter( "fvId", factorValue.getId() ) - .executeUpdate(); - if ( removedStatements > 0 ) { - log.info( String.format( "Removed %d statements from %s", removedStatements, factorValue ) ); + for ( BioMaterial bm : bms ) { + bm.getFactorValues().remove( factorValue ); } + AbstractDao.log.debug( String.format( "%s was detached from %d samples.", factorValue, bms.size() ) ); super.remove( factorValue ); } diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/FactorValueMigratorService.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/FactorValueMigratorService.java new file mode 100644 index 0000000000..63de6ba791 --- /dev/null +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/FactorValueMigratorService.java @@ -0,0 +1,113 @@ +package ubic.gemma.persistence.service.expression.experiment; + +import lombok.Builder; +import lombok.Value; +import ubic.gemma.model.common.description.Characteristic; +import ubic.gemma.model.expression.experiment.Statement; + +import javax.annotation.Nullable; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public interface FactorValueMigratorService { + + /** + * Perform the given migration. + * @param migration a migration to perform + * @param noop if true, do not save the resulting statement + * @return the created or updated statement + */ + MigrationResult performMigration( Migration migration, boolean noop ); + + /** + * Perform multiple migrations in a single transaction. + * @param migrations a list of migrations to perform + * @return a list of created or updated statements + * @throws MigrationFailedException if any of the migrations fail, the first failed migration is stored in the + * exception wrapper + */ + List performMultipleMigrations( List migrations, boolean noop ) throws MigrationFailedException; + + /** + * Migrate all the old-style characteristics of a given factor value to subject-only statements. + *

+ * The FV will be marked as troubled. + * @param fvId ID of the factor value to migrate + * @param migratedOldStyleCharacteristicIds a set of already migrated old-style characteristic IDs, if null the + * {@link Characteristic#isMigratedToStatement()} flag will be used to + * determine if a characteristic has already been migrated + * @param noop if true, do not save the resulting statements + * @return list of created or updated statements from the given factor value + */ + List performMigrationOfRemainingOldStyleCharacteristics( Long fvId, Set migratedOldStyleCharacteristicIds, boolean noop ); + + /** + * Migrate all remaining factor values that have not been migrated yet. + *

+ * All the FVs with zero or one characteristics will be migrated automatically unless migratedToSubjectOnly is set, + * in which case all the old-style characteristics will be migrated to subject-only statements. + * @param migratedFactorValueIds IDs of already migrated FVs + * @param migrateNonTrivialCases allow migration to subject-only statements for FVs with more than one old-style + * characteristics; those FVs will be marked as troubled. + * @param noop if true, do not save the resulting statements + * @return created of updated statements organized by factor value ID + */ + Map> performMigrationOfRemainingFactorValues( Set migratedFactorValueIds, boolean migrateNonTrivialCases, boolean noop ); + + @Value + @Builder + class Migration { + Long factorValueId; + + @Nullable + Long oldStyleCharacteristicIdUsedAsSubject; + String category; + String categoryUri; + String subject; + String subjectUri; + + // first predicate + String predicate; + String predicateUri; + @Nullable + Long oldStyleCharacteristicIdUsedAsObject; + String object; + String objectUri; + + // second predicate + String secondPredicate; + String secondPredicateUri; + @Nullable + Long oldStyleCharacteristicIdUsedAsSecondObject; + String secondObject; + String secondObjectUri; + } + + @Value + class MigrationResult { + Long factorValueId; + Statement statement; + /** + * Indicate if the statement was created as part of the migration or simply updated. + */ + boolean created; + } + + /** + * Keep track of the first failed migration when multiple migrations are performed in a single transaction. + */ + class MigrationFailedException extends RuntimeException { + + private final Migration migration; + + public MigrationFailedException( Migration migration, Throwable cause ) { + super( cause ); + this.migration = migration; + } + + public Migration getMigration() { + return migration; + } + } +} diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/FactorValueMigratorServiceImpl.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/FactorValueMigratorServiceImpl.java new file mode 100644 index 0000000000..ae6d2c69eb --- /dev/null +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/FactorValueMigratorServiceImpl.java @@ -0,0 +1,410 @@ +package ubic.gemma.persistence.service.expression.experiment; + +import lombok.extern.apachecommons.CommonsLog; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.time.StopWatch; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.TransactionTemplate; +import ubic.gemma.model.common.description.Characteristic; +import ubic.gemma.model.expression.experiment.FactorValue; +import ubic.gemma.model.expression.experiment.Statement; +import ubic.gemma.persistence.util.EntityUtils; + +import javax.annotation.Nullable; +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +@Service +@CommonsLog +@Deprecated +public class FactorValueMigratorServiceImpl implements FactorValueMigratorService { + + @Autowired + private FactorValueService factorValueService; + + @Autowired + private PlatformTransactionManager platformTransactionManager; + + @Override + @Transactional + public MigrationResult performMigration( Migration migration, boolean noop ) { + if ( migration.getFactorValueId() == null ) { + throw new IllegalArgumentException( "The FactorValue ID cannot be null." ); + } + + FactorValue fv = factorValueService.loadWithOldStyleCharacteristics( migration.getFactorValueId(), noop ); + + if ( fv == null ) { + throw new IllegalArgumentException( String.format( "No FactorValue with ID %d.", migration.getFactorValueId() ) ); + } + + Map oldStyleCharacteristicsById = EntityUtils.getIdMap( fv.getOldStyleCharacteristics() ); + + validateMigration( migration, fv, oldStyleCharacteristicsById ); + + Statement statement; + + if ( migration.getOldStyleCharacteristicIdUsedAsSubject() != null ) { + Characteristic c = oldStyleCharacteristicsById.get( migration.getOldStyleCharacteristicIdUsedAsSubject() ); + // if the characteristic was already migrated + statement = findStatementByCategoryAndSubject( fv, c ) + .orElseGet( Statement::new ); + if ( c.isMigratedToStatement() && statement.getId() == null ) { + log.warn( c + " claims to be migrated to statement, but there is no statement with its category/subject." ); + } + populateFromOldStyleCharacteristic( statement, c ); + c.setMigratedToStatement( true ); + log.debug( c + " has been migrated to a statement subject." ); + } else { + statement = new Statement(); + } + + if ( migration.getCategory() != null ) { + statement.setCategory( migration.getCategory() ); + statement.setCategoryUri( migration.getCategoryUri() ); + } else if ( migration.getCategoryUri() != null ) { + statement.setCategoryUri( migration.getCategoryUri() ); + } + + if ( migration.getSubject() != null ) { + statement.setSubject( migration.getSubject() ); + statement.setSubjectUri( migration.getSubjectUri() ); + } else if ( migration.getSubjectUri() != null ) { + statement.setSubjectUri( migration.getSubjectUri() ); + } + + // if an identical statement exists, reuse it instead of creating a duplicate + statement = findStatementByCategoryAndSubject( fv, statement ).orElse( statement ); + + statement.setPredicate( migration.getPredicate() ); + statement.setPredicateUri( migration.getPredicateUri() ); + + if ( migration.getOldStyleCharacteristicIdUsedAsObject() != null ) { + Characteristic c = oldStyleCharacteristicsById.get( migration.getOldStyleCharacteristicIdUsedAsObject() ); + statement.setObject( c.getValue() ); + statement.setObjectUri( c.getValueUri() ); + c.setMigratedToStatement( true ); + log.debug( c + " has been migrated to a statement object." ); + } + + if ( migration.getObject() != null ) { + statement.setObject( migration.getObject() ); + statement.setObjectUri( migration.getObjectUri() ); + } else if ( statement.getObjectUri() != null ) { + statement.setObjectUri( migration.getObjectUri() ); + } + + statement.setSecondPredicate( migration.getSecondPredicate() ); + statement.setSecondPredicateUri( migration.getSecondPredicateUri() ); + + if ( migration.getOldStyleCharacteristicIdUsedAsSecondObject() != null ) { + Characteristic c = oldStyleCharacteristicsById.get( migration.getOldStyleCharacteristicIdUsedAsSecondObject() ); + statement.setSecondObject( c.getValue() ); + statement.setSecondObjectUri( c.getValueUri() ); + c.setMigratedToStatement( true ); + log.debug( c + " has been migrated to a statement second object." ); + } + + if ( migration.getSecondObject() != null ) { + statement.setSecondObject( migration.getSecondObject() ); + statement.setSecondObjectUri( migration.getSecondObjectUri() ); + } else if ( migration.getSecondObjectUri() != null ) { + statement.setSecondObjectUri( migration.getSecondObjectUri() ); + } + + boolean isTransient = statement.getId() == null; + + log.debug( String.format( "FactorValue #%d: %s %s", fv.getId(), isTransient ? "Created" : "Updated", statement ) ); + + return new MigrationResult( fv.getId(), noop ? statement : factorValueService.saveStatementIgnoreAcl( fv, statement ), isTransient ); + } + + @Override + @Transactional + public List performMultipleMigrations( List migrations, boolean noop ) throws MigrationFailedException { + List result = new ArrayList<>( migrations.size() ); + for ( Migration migration : migrations ) { + try { + result.add( performMigration( migration, noop ) ); + } catch ( Exception e ) { + throw new MigrationFailedException( migration, e ); + } + } + return result; + } + + @Override + @Transactional + public List performMigrationOfRemainingOldStyleCharacteristics( Long fvId, Set migratedOldStyleCharacteristicIds, boolean noop ) { + FactorValue fv = factorValueService.loadWithOldStyleCharacteristics( fvId, noop ); + + if ( fv == null ) { + throw new IllegalArgumentException( String.format( "No FactorValue with ID %d.", fvId ) ); + } + + List results = performMigrationOfRemainingOldStyleCharacteristics( fv, migratedOldStyleCharacteristicIds, false, noop ); + + if ( !results.isEmpty() && !fv.getNeedsAttention() ) { + String note = "Marked as needs attention because some if its old-style characteristics were automatically migrated."; + if ( !noop ) { + factorValueService.markAsNeedsAttention( fv, note ); + } + log.warn( "FactorValue #" + fv.getId() + ": " + note ); + } + + return results; + } + + @Override + @Transactional(propagation = Propagation.NEVER) + public Map> performMigrationOfRemainingFactorValues( Set migratedFactorValueIds, boolean migrateNonTrivialCases, boolean noop ) { + Map> result = new HashMap<>(); + long total = factorValueService.countAll(); + // this way we avoid loading old-style characteristics unless we need to; + if ( total > 0 ) { + log.info( String.format( "Loading %d factor values that haven't been migrated yet...", + total - migratedFactorValueIds.size() ) ); + } + Map fvs = factorValueService.loadIdsWithNumberOfOldStyleCharacteristics( migratedFactorValueIds ); + if ( fvs.isEmpty() ) { + log.info( "There are no more factor values to migrate." ); + return result; + } + TransactionTemplate tt = new TransactionTemplate( platformTransactionManager ); + int index = 0; + int done = 0; + int remaining = fvs.size(); + Map> fvToWarnAboutBySize = new HashMap<>(); + StopWatch timer = StopWatch.createStarted(); + log.info( String.format( "Migrating %d out of %d remaining factor values...", remaining, total ) ); + for ( Map.Entry e : fvs.entrySet() ) { + Long fvId = e.getKey(); + Integer numberOfOldStyleCharacteristics = e.getValue(); + if ( numberOfOldStyleCharacteristics == 0 ) { + log.debug( String.format( "FactorValue #%d doesn't have any old-style characteristics, no migration needed!", fvId ) ); + remaining--; + } else if ( numberOfOldStyleCharacteristics == 1 ) { + result.computeIfAbsent( fvId, k -> new ArrayList<>() ) + .addAll( tt.execute( ts -> { + FactorValue fv = factorValueService.loadWithOldStyleCharacteristics( fvId, noop ); + if ( fv == null ) { + log.warn( String.format( "No FactorValue with ID %d.", fvId ) ); + return Collections.emptyList(); + } + return performMigrationOfRemainingOldStyleCharacteristics( fv, null, false, noop ); + } ) ); + done++; + } else if ( migrateNonTrivialCases ) { + result.computeIfAbsent( fvId, k -> new ArrayList<>() ) + .addAll( tt.execute( ts -> { + FactorValue fv = factorValueService.loadWithOldStyleCharacteristics( fvId, noop ); + if ( fv == null ) { + log.warn( String.format( "No FactorValue with ID %d.", fvId ) ); + return Collections.emptyList(); + } + // we need to skip characteristics flagged as already migrated because they might have + // already been covered by a curator-supplied migration + List results = performMigrationOfRemainingOldStyleCharacteristics( fv, null, true, noop ); + if ( !results.isEmpty() && !fv.getNeedsAttention() ) { + String note = String.format( "Marked as needs attention because %d of its old-style characteristics were automatically migrated to subject-only statements.", results.size() ); + if ( !noop ) { + factorValueService.markAsNeedsAttention( fv, note ); + } + log.warn( "FactorValue #" + fv.getId() + ": " + note ); + } + return results; + } ) ); + } else { + // 2 or more old-style characteristics, those should have been in the migration file + log.debug( String.format( "FactorValue #%d has %d old-style characteristics and was missing from the migration file.", + fvId, numberOfOldStyleCharacteristics ) ); + fvToWarnAboutBySize + .computeIfAbsent( numberOfOldStyleCharacteristics, k -> new ArrayList<>() ) + .add( fvId ); + remaining--; + } + if ( ( ++index % 100 ) == 0 ) { + warnUnsupportedFactorValues( fvToWarnAboutBySize ); + log.info( String.format( "Migrated %d/%d factor values so far %f fv/s...", done, remaining, + 1000.0 * done / timer.getTime( TimeUnit.MILLISECONDS ) ) ); + } + } + warnUnsupportedFactorValues( fvToWarnAboutBySize ); + log.info( String.format( "Migrated %d factor values in %d ms.", done, timer.getTime( TimeUnit.MILLISECONDS ) ) ); + return result; + } + + /** + * Perform the migration of the remaining old-style characteristics of a given factor value. + *

+ * This is an automated step: each old-style characteristic is migrated as a subject-only statement. + * @param fv a factor value to migrate + * @param migratedOldStyleCharacteristicIds a set of already migrated old-style characteristics to be ignored, a + * warning is produced if an old-style characteristic is marked as migrated + * to statement and not present in that set, or null to ignore + * @param skipAlreadyMigrated skip characteristics that are flagged as already migrated to statement + * as per {@link Characteristic#isMigratedToStatement()} + * @param noop noop mode: the factor value and characteristics are loaded in read-only + * mode and no changes are persisted + * @return the result of the migration of each old-style characteristic + */ + private List performMigrationOfRemainingOldStyleCharacteristics( FactorValue fv, @Nullable Set migratedOldStyleCharacteristicIds, boolean skipAlreadyMigrated, boolean noop ) { + List result = new ArrayList<>(); + for ( Characteristic c : fv.getOldStyleCharacteristics() ) { + if ( skipAlreadyMigrated && c.isMigratedToStatement() ) { + log.debug( "FactorValue #" + fv.getId() + ": Ignoring " + c + " as it was already migrated to a statement." ); + continue; + } + if ( migratedOldStyleCharacteristicIds != null && migratedOldStyleCharacteristicIds.contains( c.getId() ) ) { + log.debug( "FactorValue #" + fv.getId() + ": Ignoring " + c + " as it was already migrated to a statement." ); + continue; + } + if ( migratedOldStyleCharacteristicIds != null && c.isMigratedToStatement() ) { + log.warn( "FactorValue #" + fv.getId() + ": " + c + " has already been migrated to a statement, but its ID is not in the set of migrated old-style characteristics." ); + } + // convert to simple statement + Statement statement = findStatementByCategoryAndSubject( fv, c ) + .orElseGet( Statement::new ); + populateFromOldStyleCharacteristic( statement, c ); + c.setMigratedToStatement( true ); + boolean isTransient = fv.getId() == null; + log.debug( "FactorValue #" + fv.getId() + ": " + ( isTransient ? "Created" : "Updated" ) + statement ); + result.add( new MigrationResult( fv.getId(), noop ? statement : factorValueService.saveStatementIgnoreAcl( fv, statement ), isTransient ) ); + } + if ( result.isEmpty() ) { + log.debug( "FactorValue #" + fv.getId() + ": No old-style characteristics to migrate." ); + } + return result; + } + + private void warnUnsupportedFactorValues( Map> fvToWarnAboutBySize ) { + for ( Map.Entry> entry : fvToWarnAboutBySize.entrySet() ) { + if ( !entry.getValue().isEmpty() ) { + log.warn( String.format( "%d factor values have %d old-style characteristics and were not already migrated: %s", + entry.getValue().size(), entry.getKey(), + entry.getValue().stream().map( String::valueOf ).collect( Collectors.joining( ", " ) ) ) ); + entry.getValue().clear(); + } + } + } + + /** + * Populate a statement with the data from an old-style characteristic. + */ + private void populateFromOldStyleCharacteristic( Statement statement, Characteristic c ) { + // make sure those are not erased + if ( statement.getOriginalValue() != null && !StringUtils.equalsIgnoreCase( statement.getOriginalValue(), c.getOriginalValue() ) ) { + log.warn( String.format( "%s's original value will change: %s -> %s", statement, statement.getOriginalValue(), c.getOriginalValue() ) ); + } + if ( statement.getEvidenceCode() != null && !Objects.equals( statement.getEvidenceCode(), c.getEvidenceCode() ) ) { + log.warn( String.format( "%s's evidence code will change: %s -> %s", statement, statement.getEvidenceCode(), c.getEvidenceCode() ) ); + } + // make sure to copy over cosmetic changes (case, etc.) + statement.setCategory( c.getCategory() ); + statement.setCategoryUri( c.getCategoryUri() ); + statement.setSubject( c.getValue() ); + statement.setSubjectUri( c.getValueUri() ); + statement.setOriginalValue( c.getOriginalValue() ); + statement.setEvidenceCode( c.getEvidenceCode() ); + } + + /** + * Find a statement that shares the same category/subject of a given old-style characteristic. + */ + private Optional findStatementByCategoryAndSubject( FactorValue fv, Characteristic c ) { + for ( Statement s : fv.getCharacteristics() ) { + if ( ( s.getCategoryUri() != null ? StringUtils.equalsIgnoreCase( s.getCategoryUri(), c.getCategoryUri() ) : StringUtils.equalsIgnoreCase( s.getCategory(), c.getCategory() ) ) + && ( s.getSubjectUri() != null ? StringUtils.equalsIgnoreCase( s.getSubjectUri(), c.getValueUri() ) : StringUtils.equalsIgnoreCase( s.getSubject(), c.getValue() ) ) ) { + log.debug( "FactorValue #" + fv.getId() + ": A statement with the same category and subject already exists, reusing it." ); + return Optional.of( s ); + } + } + return Optional.empty(); + } + + /** + * Validate the given migration. + */ + private void validateMigration( Migration migration, FactorValue fv, Map oldStyleCharacteristicsById ) { + if ( migration.getOldStyleCharacteristicIdUsedAsSubject() != null ) { + if ( !oldStyleCharacteristicsById.containsKey( migration.getOldStyleCharacteristicIdUsedAsSubject() ) ) { + throw new IllegalArgumentException( String.format( "Old-style characteristic with ID %d is not associated with %s.", migration.getOldStyleCharacteristicIdUsedAsSubject(), fv ) ); + } + Characteristic c = oldStyleCharacteristicsById.get( migration.getOldStyleCharacteristicIdUsedAsSubject() ); + validateTerm( migration.getCategory() != null ? migration.getCategory() : c.getCategory(), migration.getCategoryUri() != null ? migration.getCategoryUri() : c.getCategoryUri(), "category" ); + validateTerm( migration.getSubject() != null ? migration.getSubject() : c.getValue(), migration.getSubjectUri() != null ? migration.getSubjectUri() : c.getValueUri(), "subject" ); + } else { + // ensure that a valid subject is supplied + validateTerm( migration.getCategory(), migration.getCategoryUri(), "category" ); + validateTerm( migration.getSubject(), migration.getSubjectUri(), "subject" ); + } + + if ( migration.getOldStyleCharacteristicIdUsedAsObject() != null ) { + if ( !oldStyleCharacteristicsById.containsKey( migration.getOldStyleCharacteristicIdUsedAsObject() ) ) { + throw new IllegalArgumentException( String.format( "Old-style characteristic with ID %d is not associated with %s.", migration.getOldStyleCharacteristicIdUsedAsObject(), fv ) ); + } + Characteristic c = oldStyleCharacteristicsById.get( migration.getOldStyleCharacteristicIdUsedAsObject() ); + validateTerm( migration.getPredicate(), migration.getPredicateUri(), "predicate" ); + validateTerm( migration.getObject() != null ? migration.getObject() : c.getValue(), migration.getObjectUri() != null ? migration.getObjectUri() : c.getValueUri(), "object" ); + } else if ( migration.getObject() != null ) { + validateTerm( migration.getPredicate(), migration.getPredicateUri(), "predicate" ); + validateTerm( migration.getObject(), migration.getObjectUri(), "object" ); + } else { + // statement has no object + ensureTermIsNull( migration.getPredicate(), migration.getPredicateUri(), "There is no object, the %s must be blank.", "predicate" ); + } + + if ( migration.getOldStyleCharacteristicIdUsedAsSecondObject() != null ) { + if ( !oldStyleCharacteristicsById.containsKey( migration.getOldStyleCharacteristicIdUsedAsSecondObject() ) ) { + throw new IllegalArgumentException( String.format( "Old-style characteristic with ID %d is not associated with %s.", migration.getOldStyleCharacteristicIdUsedAsSecondObject(), fv ) ); + } + Characteristic c = oldStyleCharacteristicsById.get( migration.getOldStyleCharacteristicIdUsedAsSecondObject() ); + validateTerm( migration.getSecondPredicate(), migration.getSecondPredicateUri(), "second predicate" ); + validateTerm( migration.getSecondObject() != null ? migration.getSecondObject() : c.getValue(), migration.getSecondObjectUri() != null ? migration.getSecondObjectUri() : c.getValueUri(), "second object" ); + } else if ( migration.getSecondObject() != null ) { + // ensure that a valid second predicate & object are supplied + validateTerm( migration.getSecondPredicate(), migration.getSecondPredicateUri(), "second predicate" ); + validateTerm( migration.getSecondObject(), migration.getSecondObjectUri(), "second object" ); + } else { + // statement has no second object + ensureTermIsNull( migration.getSecondPredicate(), migration.getSecondPredicateUri(), "There is no second object, the %s must be blank.", "second predicate" ); + } + } + + private void validateTerm( String term, @Nullable String termUri, String name ) { + if ( StringUtils.isBlank( term ) ) { + throw new IllegalArgumentException( String.format( "A %s cannot be blank.", name ) ); + } + if ( termUri != null && StringUtils.isBlank( termUri ) ) { + throw new IllegalArgumentException( String.format( "A %s URI cannot be blank, although it may be null for free-text %s.", name, pluralize( name ) ) ); + } + if ( term.length() > 255 ) { + throw new IllegalArgumentException( String.format( "A %s cannot exceed 255 characters", name ) ); + } + if ( termUri != null && termUri.length() > 255 ) { + throw new IllegalArgumentException( String.format( "A %s URI cannot exceed 255 characters", name ) ); + } + } + + private String pluralize( String s ) { + if ( s.endsWith( "y" ) ) { + return s.substring( 0, s.length() - 1 ) + "ies"; + } else { + return s + "s"; + } + } + + private void ensureTermIsNull( @Nullable String term, @Nullable String termUri, String message, String name ) { + if ( term != null ) { + throw new IllegalArgumentException( String.format( message, name ) ); + } + if ( termUri != null ) { + throw new IllegalArgumentException( String.format( message, name + " URI" ) ); + } + } +} diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/FactorValueService.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/FactorValueService.java index c7595aee63..3fa8a27a23 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/FactorValueService.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/FactorValueService.java @@ -21,11 +21,15 @@ import org.springframework.security.access.annotation.Secured; import ubic.gemma.model.expression.experiment.FactorValue; import ubic.gemma.model.expression.experiment.FactorValueValueObject; -import ubic.gemma.persistence.service.BaseImmutableService; +import ubic.gemma.model.expression.experiment.Statement; import ubic.gemma.persistence.service.BaseService; import ubic.gemma.persistence.service.FilteringVoEnabledService; +import javax.annotation.CheckReturnValue; +import javax.annotation.Nullable; import java.util.Collection; +import java.util.Map; +import java.util.Set; /** * @author kelsey @@ -44,14 +48,75 @@ public interface FactorValueService extends BaseService, FilteringV @Secured({ "IS_AUTHENTICATED_ANONYMOUSLY", "AFTER_ACL_READ" }) FactorValue load( Long id ); - @Override - @Secured({ "IS_AUTHENTICATED_ANONYMOUSLY", "AFTER_ACL_COLLECTION_READ" }) - Collection loadAll(); + @Secured({ "IS_AUTHENTICATED_ANONYMOUSLY", "AFTER_ACL_READ" }) + FactorValue loadWithExperimentalFactor( Long id ); + + /** + * Load a {@link FactorValue} with an initialized experimental factor or fail. + */ + @Secured({ "IS_AUTHENTICATED_ANONYMOUSLY", "AFTER_ACL_READ" }) + FactorValue loadWithExperimentalFactorOrFail( Long id ); + + /** + * Load a {@link FactorValue} along with its old-style characteristics. + * @deprecated do not use this, it is only meant for the purpose of migrating old-style characteristics to + * statements + */ + @Nullable + @Deprecated + @Secured({ "GROUP_ADMIN" }) + // FIXME: use @Secured({ "IS_AUTHENTICATED_ANONYMOUSLY", "AFTER_ACL_READ" }), but some FVs have broken ACLs + FactorValue loadWithOldStyleCharacteristics( Long id, boolean readOnly ); + + /** + * @see FactorValueDao#loadIdsWithNumberOfOldStyleCharacteristics(Set) + * @deprecated do not use, this is only for migrating old-style characteristics to statements and will be removed + */ + @Deprecated + @Secured({ "GROUP_ADMIN" }) + // FIXME: use @Secured({ "IS_AUTHENTICATED_ANONYMOUSLY", "AFTER_ACL_MAP_READ" }), but some FVs have broken ACLs + Map loadIdsWithNumberOfOldStyleCharacteristics( Set excludedIds ); @Override @Secured({ "GROUP_USER", "ACL_SECURABLE_EDIT" }) void remove( FactorValue factorValue ); + /** + * Create a given statement and add it to the given factor value. + * @param factorValue the factor value to add the statement to + * @param statement the statement to be created and added to the factor value + * @return the created statement + * @throws IllegalArgumentException if the statement already exists + */ + @CheckReturnValue + @Secured({ "GROUP_USER", "ACL_SECURABLE_EDIT" }) + Statement createStatement( FactorValue factorValue, Statement statement ); + + /** + * Create a given statement as per {@link #createStatement(FactorValue, Statement)} if it is transient, otherwise + * update an existing statement. + */ + @CheckReturnValue + @Secured({ "GROUP_USER", "ACL_SECURABLE_EDIT" }) + Statement saveStatement( FactorValue fv, Statement statement ); + + /** + * Save a statement ignoring ACLs. + *

+ * This requires the {@code GROUP_ADMIN} authority. + * @deprecated do not use this, it is meant for FactorValue migration only + */ + @Deprecated + @CheckReturnValue + @Secured({ "GROUP_ADMIN" }) + Statement saveStatementIgnoreAcl( FactorValue fv, Statement statement ); + + /** + * Remove a statement from a factor value. + */ + @Secured({ "GROUP_USER", "ACL_SECURABLE_EDIT" }) + void removeStatement( FactorValue fv, Statement c ); + @Override @Secured({ "GROUP_USER", "ACL_SECURABLE_COLLECTION_EDIT" }) void update( Collection factorValues ); @@ -60,4 +125,22 @@ public interface FactorValueService extends BaseService, FilteringV @Secured({ "GROUP_USER", "ACL_SECURABLE_EDIT" }) void update( FactorValue factorValue ); + /** + * Mark a given factor value as needs attention. + * @param factorValue a factor value to mark as needs attention + * @param note note to use for the {@link ubic.gemma.model.common.auditAndSecurity.eventType.FactorValueNeedsAttentionEvent} + * @throws IllegalArgumentException if the factor value already needs attention + */ + @Secured({ "GROUP_USER", "ACL_SECURABLE_EDIT" }) + void markAsNeedsAttention( FactorValue factorValue, String note ); + + /** + * Clear a needs attention flag on a given factor value. + * @param factorValue a factor value whose needs flag will be cleared + * @param note a note to use for the {@link ubic.gemma.model.common.auditAndSecurity.eventType.DoesNotNeedAttentionEvent} + * if the dataset does not need attention for any other reason. + * @throws IllegalArgumentException if the factor value does not need attention + */ + @Secured({ "GROUP_USER", "ACL_SECURABLE_EDIT" }) + void clearNeedsAttentionFlag( FactorValue factorValue, String note ); } diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/FactorValueServiceImpl.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/FactorValueServiceImpl.java index cd58ebdb8c..e04d5f20d9 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/FactorValueServiceImpl.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/FactorValueServiceImpl.java @@ -14,14 +14,27 @@ */ package ubic.gemma.persistence.service.expression.experiment; +import org.hibernate.Hibernate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.Assert; +import ubic.gemma.model.common.auditAndSecurity.AuditEvent; +import ubic.gemma.model.common.auditAndSecurity.eventType.DoesNotNeedAttentionEvent; +import ubic.gemma.model.common.auditAndSecurity.eventType.FactorValueNeedsAttentionEvent; +import ubic.gemma.model.common.auditAndSecurity.eventType.NeedsAttentionEvent; +import ubic.gemma.model.expression.experiment.ExpressionExperiment; import ubic.gemma.model.expression.experiment.FactorValue; import ubic.gemma.model.expression.experiment.FactorValueValueObject; +import ubic.gemma.model.expression.experiment.Statement; import ubic.gemma.persistence.service.AbstractFilteringVoEnabledService; +import ubic.gemma.persistence.service.common.auditAndSecurity.AuditEventService; +import ubic.gemma.persistence.service.common.auditAndSecurity.AuditTrailService; import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Set; /** *

@@ -37,20 +50,122 @@ public class FactorValueServiceImpl extends AbstractFilteringVoEnabledService implements FactorValueService { + private final ExpressionExperimentService expressionExperimentService; + private final AuditTrailService auditTrailService; + private final AuditEventService auditEventService; private final FactorValueDao factorValueDao; + private final StatementDao statementDao; @Autowired - public FactorValueServiceImpl( FactorValueDao factorValueDao ) { + public FactorValueServiceImpl( ExpressionExperimentService expressionExperimentService, AuditTrailService auditTrailService, AuditEventService auditEventService, FactorValueDao factorValueDao, StatementDao statementDao ) { super( factorValueDao ); + this.expressionExperimentService = expressionExperimentService; + this.auditTrailService = auditTrailService; + this.auditEventService = auditEventService; this.factorValueDao = factorValueDao; + this.statementDao = statementDao; } @Override @Transactional(readOnly = true) + public FactorValue loadWithExperimentalFactor( Long id ) { + FactorValue fv = load( id ); + if ( fv != null ) { + Hibernate.initialize( fv.getExperimentalFactor() ); + } + return fv; + } + + @Override + @Transactional(readOnly = true) + public FactorValue loadWithExperimentalFactorOrFail( Long id ) { + FactorValue fv = loadOrFail( id ); + Hibernate.initialize( fv.getExperimentalFactor() ); + return fv; + } + + @Override + @Deprecated + @Transactional(readOnly = true) + public FactorValue loadWithOldStyleCharacteristics( Long id, boolean readOnly ) { + return factorValueDao.loadWithOldStyleCharacteristics( id, readOnly ); + } + + @Override + @Deprecated + @Transactional(readOnly = true) + public Map loadIdsWithNumberOfOldStyleCharacteristics( Set excludedIds ) { + return factorValueDao.loadIdsWithNumberOfOldStyleCharacteristics( excludedIds ); + } + + @Override + @Deprecated + @Transactional(readOnly = true) public Collection findByValue( String valuePrefix ) { return this.factorValueDao.findByValue( valuePrefix ); } + @Override + @Transactional + public Statement createStatement( FactorValue factorValue, Statement statement ) { + Assert.notNull( factorValue.getId(), "The factor value must be persistent." ); + Assert.isNull( statement.getId(), "The statement to be added must not be persistent." ); + // note here that once the statement is persisted, it is compared by ID, so it won't clash with any other + // identical statement + statement = this.statementDao.create( statement ); + factorValue.getCharacteristics().add( statement ); + factorValueDao.update( factorValue ); + return statement; + } + + @Override + @Transactional + public Statement saveStatement( FactorValue fv, Statement statement ) { + Assert.notNull( fv.getId(), "The factor value must be persistent." ); + if ( statement.getId() != null && !fv.getCharacteristics().contains( statement ) ) { + throw new IllegalArgumentException( "The given persistent statement is not associated with the factor value." ); + } + // same for createStatement() not applies here and in addition, if the statement already belongs to the FV, this + // is a noop + if ( statement.getId() == null ) { + statement = statementDao.create( statement ); + fv.getCharacteristics().add( statement ); + } + factorValueDao.update( fv ); + return statement; + } + + @Override + @Transactional + public Statement saveStatementIgnoreAcl( FactorValue fv, Statement statement ) { + Assert.notNull( fv.getId(), "The factor value must be persistent." ); + if ( statement.getId() != null && !fv.getCharacteristics().contains( statement ) ) { + throw new IllegalArgumentException( "The given persistent statement is not associated with the factor value." ); + } + // same for createStatement() not applies here and in addition, if the statement already belongs to the FV, this + // is a noop + if ( statement.getId() == null ) { + statement = statementDao.create( statement ); + fv.getCharacteristics().add( statement ); + } + // careful here! + factorValueDao.updateIgnoreAcl( fv ); + return statement; + } + + @Override + @Transactional + public void removeStatement( FactorValue fv, Statement statement ) { + Assert.notNull( fv.getId(), "The factor value must be persistent." ); + Assert.notNull( statement.getId(), "The statement to be removed must be persistent." ); + if ( !fv.getCharacteristics().remove( statement ) ) { + throw new IllegalArgumentException( String.format( "%s is not associated with %s", statement, fv ) ); + } + this.factorValueDao.update( fv ); + // now we can safely delete it + this.statementDao.remove( statement ); + } + @Override @Transactional public void remove( FactorValue entity ) { @@ -58,7 +173,44 @@ public void remove( FactorValue entity ) { } @Override + @Transactional public void remove( Collection entities ) { super.remove( ensureInSession( entities ) ); } + + @Override + @Transactional + public void markAsNeedsAttention( FactorValue factorValue, String note ) { + Assert.isTrue( !factorValue.getNeedsAttention(), "This FactorValue already needs attention." ); + ExpressionExperiment ee = expressionExperimentService.findByFactorValue( factorValue ); + factorValue.setNeedsAttention( true ); + factorValueDao.update( factorValue ); + if ( ee != null ) { + auditTrailService.addUpdateEvent( ee, FactorValueNeedsAttentionEvent.class, String.format( "%s needs attention: %s", factorValue, note ) ); + } + } + + @Override + @Transactional + public void clearNeedsAttentionFlag( FactorValue factorValue, String note ) { + Assert.isTrue( factorValue.getNeedsAttention(), "This FactorValue does not need attention." ); + ExpressionExperiment ee = expressionExperimentService.findByFactorValue( factorValue ); + factorValue.setNeedsAttention( false ); + factorValueDao.update( factorValue ); + if ( ee != null ) { + boolean needsAttention = ee.getCurationDetails().getNeedsAttention(); + AuditEvent ae = auditEventService.getLastEvent( ee, NeedsAttentionEvent.class, + Collections.singleton( FactorValueNeedsAttentionEvent.class ) ); + AuditEvent nae = auditEventService.getLastEvent( ee, DoesNotNeedAttentionEvent.class ); + // ensure that the last NeedsAttentionEvent hasn't been fixed already + boolean hasNeedsAttentionEvent = ae != null && ( nae == null || ae.getDate().after( nae.getDate() ) ); + // check if all the FVs are OK + boolean hasFactorValueThatNeedsAttention = ee.getExperimentalDesign().getExperimentalFactors().stream() + .flatMap( ef -> ef.getFactorValues().stream() ) + .anyMatch( FactorValue::getNeedsAttention ); + if ( needsAttention && !hasNeedsAttentionEvent && !hasFactorValueThatNeedsAttention ) { + auditTrailService.addUpdateEvent( ee, DoesNotNeedAttentionEvent.class, note ); + } + } + } } \ No newline at end of file diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/GeeqServiceImpl.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/GeeqServiceImpl.java index f7d52428c8..53964cfe83 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/GeeqServiceImpl.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/GeeqServiceImpl.java @@ -36,6 +36,7 @@ import ubic.gemma.core.datastructure.matrix.ExpressionDataDoubleMatrix; import ubic.gemma.model.common.auditAndSecurity.eventType.GeeqEvent; import ubic.gemma.model.common.description.BibliographicReference; +import ubic.gemma.model.common.description.Characteristic; import ubic.gemma.model.common.quantitationtype.QuantitationType; import ubic.gemma.model.expression.arrayDesign.ArrayDesign; import ubic.gemma.model.expression.arrayDesign.TechnologyType; @@ -520,9 +521,12 @@ private void scoreBatchEffect( ExpressionExperiment ee, Geeq gq, boolean infoDet } else { BatchEffectDetails be = expressionExperimentService.getBatchEffectDetails( ee ); hasInfo = be.hasBatchInformation(); - hasStrong = be.getPvalue() < 0.0001; - hasNone = be.getPvalue() > 0.1; corrected = be.getDataWasBatchCorrected(); + BatchEffectDetails.BatchEffectStatistics statistics = be.getBatchEffectStatistics(); + if ( statistics != null ) { + hasStrong = statistics.getPvalue() < 0.0001; + hasNone = statistics.getPvalue() > 0.1; + } } } @@ -588,8 +592,9 @@ private int leastReplicates( ExpressionExperiment ee ) { Collection removeFvs = new LinkedList<>(); for ( FactorValue fv : fvs ) { ExperimentalFactor ef = fv.getExperimentalFactor(); - if ( ExperimentalDesignUtils.isBatch( ef ) || DE_EXCLUDE - .equalsIgnoreCase( fv.getDescriptiveString() ) || ef.getType().equals( FactorType.CONTINUOUS ) ) { + if ( ExperimentalDesignUtils.isBatch( ef ) + || fv.getCharacteristics().stream().map( Characteristic::getValue ).anyMatch( DE_EXCLUDE::equalsIgnoreCase ) + || ef.getType().equals( FactorType.CONTINUOUS ) ) { removeFvs.add( fv ); // always remove batch factor values and DE_EXCLUDE values } else { if ( !keepEfs.contains( ef ) && keepEfs.size() <= GeeqServiceImpl.MAX_EFS_REPLICATE_CHECK ) { diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/StatementDao.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/StatementDao.java new file mode 100644 index 0000000000..239033e205 --- /dev/null +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/StatementDao.java @@ -0,0 +1,7 @@ +package ubic.gemma.persistence.service.expression.experiment; + +import ubic.gemma.model.expression.experiment.Statement; +import ubic.gemma.persistence.service.BaseDao; + +public interface StatementDao extends BaseDao { +} diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/StatementDaoImpl.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/StatementDaoImpl.java new file mode 100644 index 0000000000..8db586a7ac --- /dev/null +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/StatementDaoImpl.java @@ -0,0 +1,16 @@ +package ubic.gemma.persistence.service.expression.experiment; + +import org.hibernate.SessionFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Repository; +import ubic.gemma.model.expression.experiment.Statement; +import ubic.gemma.persistence.service.AbstractDao; + +@Repository +public class StatementDaoImpl extends AbstractDao implements StatementDao { + + @Autowired + public StatementDaoImpl( SessionFactory sessionFactory ) { + super( Statement.class, sessionFactory ); + } +} diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/genome/GeneDaoImpl.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/genome/GeneDaoImpl.java index 7f5135a0cf..60037a9108 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/service/genome/GeneDaoImpl.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/genome/GeneDaoImpl.java @@ -412,7 +412,13 @@ public Collection thawLite( final Collection genes ) { @Override public Gene thawLite( final Gene gene ) { - return this.thaw( gene ); + if ( gene.getId() == null ) + return gene; + + return ( Gene ) this.getSessionFactory().getCurrentSession() + .createQuery( "select g from Gene g left join fetch g.taxon left join fetch g.products where g.id=:gid" ) + .setParameter( "gid", gene.getId() ) + .uniqueResult(); } @Override @@ -507,6 +513,11 @@ public void remove( Gene gene ) { .createQuery( "delete from Gene2GOAssociation g2g where g2g.gene = :g" ) .setParameter( "g", gene ) .executeUpdate(); + // those genes are not visible from the products collection + int removedDummyProducts = this.getSessionFactory().getCurrentSession() + .createQuery( "delete from GeneProduct where gene = :g and dummy = true" ) + .setParameter( "g", gene ) + .executeUpdate(); super.remove( gene ); } diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/util/AclQueryUtils.java b/gemma-core/src/main/java/ubic/gemma/persistence/util/AclQueryUtils.java index 66ac454c81..81e8d96598 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/util/AclQueryUtils.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/util/AclQueryUtils.java @@ -9,6 +9,7 @@ import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.type.IntegerType; import org.springframework.security.acls.domain.BasePermission; +import org.springframework.util.Assert; import java.util.Arrays; @@ -30,12 +31,17 @@ public class AclQueryUtils { /** - * Alias used. + * Alias used by {@link #formAclRestrictionClause(String, int)} and {@link #formNativeAclJoinClause(String)} for the + * object identity {@link gemma.gsec.acl.domain.AclObjectIdentity} and the owner identity {@link gemma.gsec.acl.domain.AclSid}. */ public static final String AOI_ALIAS = "aoi", - SID_ALIAS = "sid", - ACE_ALIAS = "ace"; + SID_ALIAS = "sid"; + + /** + * Do not refer to ACEs in your code, it might not be present in the query. + */ + private static final String ACE_ALIAS = "ace"; /** * Parameter name prefix to avoid clashes with user-defined parameters. @@ -49,13 +55,13 @@ public class AclQueryUtils { * Select all the SIDs that belong to a given user (specified by a :userName parameter). */ //language=HQL - static final String CURRENT_USER_SIDS_HQL = + private static final String CURRENT_USER_SIDS_HQL = "select sid from UserGroup as ug join ug.authorities as ga, AclGrantedAuthoritySid sid " + "where sid.grantedAuthority = CONCAT('GROUP_', ga.authority) " + "and ug.name in (select ug.name from UserGroup ug join ug.groupMembers memb where memb.userName = :" + USER_NAME_PARAM + ")"; //language=HQL - static final String ANONYMOUS_SID_HQL = "select sid from AclGrantedAuthoritySid sid where sid.grantedAuthority = 'IS_AUTHENTICATED_ANONYMOUSLY'"; + private static final String ANONYMOUS_SID_HQL = "select sid from AclGrantedAuthoritySid sid where sid.grantedAuthority = 'IS_AUTHENTICATED_ANONYMOUSLY'"; /** * Native SQL version of {@link #CURRENT_USER_SIDS_HQL}. @@ -72,22 +78,36 @@ public class AclQueryUtils { //language=SQL static final String ANONYMOUS_SID_SQL = "select sid.ID from ACLSID sid where sid.GRANTED_AUTHORITY = 'IS_AUTHENTICATED_ANONYMOUSLY'"; + /** + * Create a HQL restriction clause with the {@link BasePermission#READ} permission. + * @see #formAclRestrictionClause(String, int) + */ + public static String formAclRestrictionClause( String aoiIdColumn ) { + return formAclRestrictionClause( aoiIdColumn, BasePermission.READ.getMask() ); + } + /** * Create an HQL join clause for {@link gemma.gsec.acl.domain.AclObjectIdentity}, {@link gemma.gsec.acl.domain.AclGrantedAuthoritySid} - * and a restriction clause to limit the result only to objects the currently logged user can access. + * and a restriction clause to limit the result only to objects the current user can access. *

* Ensure that you use {@link #addAclParameters(Query, Class)} afterward to bind the query parameters. *

+ * Important note: when using this, ensure that you have a {@code group by} clause in your query, otherwise + * entities with multiple ACL entries will be duplicated in the results. + *

* FIXME: this ACL jointure is really annoying because it is one-to-many, maybe handling everything in a sub-query - * would be preferable? + * would be preferable? * - * @param aoiIdColumn column for the identifier e.g. "ee.id" + * @param aoiIdColumn column name to match against the ACL object identity, the object class is passed via + * {@link #addAclParameters(Query, Class)} afterward + * @param mask a mask with requested permissions * @return clause to add to the query after any jointure */ - public static String formAclRestrictionClause( String aoiIdColumn ) { + public static String formAclRestrictionClause( String aoiIdColumn, int mask ) { if ( StringUtils.isBlank( aoiIdColumn ) ) { throw new IllegalArgumentException( "Object identity column cannot be empty." ); } + Assert.isTrue( mask > 0, "The mask must have at least one bit set." ); //language=HQL String q = ", AclObjectIdentity as " + AOI_ALIAS + " join " + AOI_ALIAS + ".ownerSid " + SID_ALIAS; // for non-admin, we have to include aoi.entries @@ -99,18 +119,16 @@ public static String formAclRestrictionClause( String aoiIdColumn ) { // add ACL restrictions if ( !SecurityUtil.isUserAdmin() ) { if ( SecurityUtil.isUserAnonymous() ) { - // For anonymous users, only pick publicly readable data //language=HQL - q += " and (bitwise_and(" + ACE_ALIAS + ".mask, " + BasePermission.READ.getMask() + ") <> 0 and " + ACE_ALIAS + ".sid in (" + ANONYMOUS_SID_HQL + "))"; + q += " and (bitwise_and(" + ACE_ALIAS + ".mask, " + mask + ") <> 0 and " + ACE_ALIAS + ".sid in (" + ANONYMOUS_SID_HQL + "))"; } else { - // For non-admin users, pick non-troubled, publicly readable data and data that are readable by them or a group they belong to q += " and (" // user own the object + SID_ALIAS + ".principal = :" + USER_NAME_PARAM + " " // specific rights to the object - + "or (" + ACE_ALIAS + ".sid in (" + CURRENT_USER_SIDS_HQL + ") and bitwise_and(" + ACE_ALIAS + ".mask, " + ( BasePermission.READ.getMask() | BasePermission.WRITE.getMask() ) + ") <> 0) " + + "or (" + ACE_ALIAS + ".sid in (" + CURRENT_USER_SIDS_HQL + ") and bitwise_and(" + ACE_ALIAS + ".mask, " + mask + ") <> 0) " // publicly available - + "or (" + ACE_ALIAS + ".sid in (" + ANONYMOUS_SID_HQL + ") and bitwise_and(" + ACE_ALIAS + ".mask, " + BasePermission.READ.getMask() + ") <> 0)" + + "or (" + ACE_ALIAS + ".sid in (" + ANONYMOUS_SID_HQL + ") and bitwise_and(" + ACE_ALIAS + ".mask, " + mask + ") <> 0)" + ")"; } } @@ -122,6 +140,11 @@ public static String formAclRestrictionClause( String aoiIdColumn ) { *

* Note: unlike the HQL version, this query uses {@code on} to restrict the jointure, so you can define the * {@code where} clause yourself. + *

+ * Important note: when using this, ensure that you have a {@code group by} clause in your query, otherwise + * entities with multiple ACL entries will be duplicated in the results. + * @param aoiIdColumn column name to match against the ACL object identity, the object class is passed via + * {@link #addAclParameters(Query, Class)} afterward * * @see #formAclRestrictionClause(String) */ @@ -130,8 +153,8 @@ public static String formNativeAclJoinClause( String aoiIdColumn ) { throw new IllegalArgumentException( "Object identity column cannot be empty." ); } //language=SQL - String q = " left join ACLOBJECTIDENTITY " + AOI_ALIAS + " on (" + AOI_ALIAS + ".OBJECT_CLASS = :" + AOI_TYPE_PARAM + " and " + AOI_ALIAS + ".OBJECT_ID = " + aoiIdColumn + ") " - + "left join ACLSID " + SID_ALIAS + " on (" + SID_ALIAS + ".ID = " + AOI_ALIAS + ".OWNER_SID_FK)"; + String q = " join ACLOBJECTIDENTITY " + AOI_ALIAS + " on (" + AOI_ALIAS + ".OBJECT_CLASS = :" + AOI_TYPE_PARAM + " and " + AOI_ALIAS + ".OBJECT_ID = " + aoiIdColumn + ") " + + "join ACLSID " + SID_ALIAS + " on (" + SID_ALIAS + ".ID = " + AOI_ALIAS + ".OWNER_SID_FK)"; // for non-admin, we have to include aoi.entries // if aoi.entries is empty, the user might still be the owner, so we use a left join @@ -142,22 +165,34 @@ public static String formNativeAclJoinClause( String aoiIdColumn ) { } /** - * Native flavour of the ACL restriction clause. - * - * @see #formAclRestrictionClause(String) + * Native flavour of the ACL restriction clause with a {@link BasePermission#READ} permission. + * @see #formNativeAclRestrictionClause(SessionFactoryImplementor, int) */ public static String formNativeAclRestrictionClause( SessionFactoryImplementor sessionFactoryImplementor ) { + return formNativeAclRestrictionClause( sessionFactoryImplementor, BasePermission.READ.getMask() ); + } + + /** + * Native flavour of the ACL restriction clause. + * @param sessionFactoryImplementor a session factory implementor that will be used to adjust the SQL generated + * based on the dialect + * @param mask a mask with requested permissions + * @see #formAclRestrictionClause(String, int) + */ + public static String formNativeAclRestrictionClause( SessionFactoryImplementor sessionFactoryImplementor, int mask ) { SQLFunction bitwiseAnd = sessionFactoryImplementor.getSqlFunctionRegistry().findSQLFunction( "bitwise_and" ); - String read = bitwiseAnd.render( new IntegerType(), Arrays.asList( ACE_ALIAS + ".MASK", BasePermission.READ.getMask() ), sessionFactoryImplementor ); - String readOrWrite = bitwiseAnd.render( new IntegerType(), Arrays.asList( ACE_ALIAS + ".MASK", BasePermission.READ.getMask() | BasePermission.WRITE.getMask() ), sessionFactoryImplementor ); + String renderedMask = bitwiseAnd.render( new IntegerType(), Arrays.asList( ACE_ALIAS + ".MASK", mask ), sessionFactoryImplementor ); //language=SQL if ( SecurityUtil.isUserAnonymous() ) { - return " and (" + read + " <> 0 and " + ACE_ALIAS + ".SID_FK in (" + ANONYMOUS_SID_SQL + "))"; + return " and (" + renderedMask + " <> 0 and " + ACE_ALIAS + ".SID_FK in (" + ANONYMOUS_SID_SQL + "))"; } else if ( !SecurityUtil.isUserAdmin() ) { return " and (" + // user owns the object + SID_ALIAS + ".PRINCIPAL = :" + USER_NAME_PARAM + " " - + "or (" + ACE_ALIAS + ".SID_FK in (" + CURRENT_USER_SIDS_SQL + ") and " + readOrWrite + " <> 0) " - + "or (" + ACE_ALIAS + ".SID_FK in (" + ANONYMOUS_SID_SQL + ") and " + read + " <> 0)" + // specific rights to the object + + "or (" + ACE_ALIAS + ".SID_FK in (" + CURRENT_USER_SIDS_SQL + ") and " + renderedMask + " <> 0) " + // publicly available + + "or (" + ACE_ALIAS + ".SID_FK in (" + ANONYMOUS_SID_SQL + ") and " + renderedMask + " <> 0)" + ")"; } else { // For administrators, no filtering is needed, so the ACE is completely skipped from the where clause. @@ -177,13 +212,15 @@ public static String formNativeAclRestrictionClause( SessionFactoryImplementor s * @throws QueryParameterException if any defined parameters are missing, which is typically due to a missing prior * {@link #formAclRestrictionClause(String)}. */ + @SuppressWarnings("StatementWithEmptyBody") public static void addAclParameters( Query query, Class aoiType ) throws QueryParameterException { query.setParameter( AOI_TYPE_PARAM, aoiType.getCanonicalName() ); if ( SecurityUtil.isUserAnonymous() ) { - // sid 4 = IS_AUTHENTICATED_ANONYMOUSLY + // a constant is used directly in ANONYMOUS_SID_SQL, so no binding is necessary } else if ( !SecurityUtil.isUserAdmin() ) { query.setParameter( USER_NAME_PARAM, SecurityUtil.getCurrentUsername() ); + } else { + // For administrators, no filtering is needed, so the ACE is completely skipped from the where clause. } - // For administrators, no filtering is needed, so the ACE is completely skipped from the where clause. } } diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/util/CommonQueries.java b/gemma-core/src/main/java/ubic/gemma/persistence/util/CommonQueries.java index 8fb4de2bab..2050865c40 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/util/CommonQueries.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/util/CommonQueries.java @@ -31,6 +31,8 @@ import java.util.*; +import static ubic.gemma.persistence.service.TableMaintenanceUtil.GENE2CS_QUERY_SPACE; + /** * Contains methods to perform 'common' queries that are needed across DAOs. * @@ -241,7 +243,10 @@ public static Map> getCs2GeneIdMap( Collection gene queryObject.setParameterList( "ads", arrayDesigns ); queryObject.setParameterList( "geneIds", genes ); queryObject.setReadOnly( true ); - queryObject.setFlushMode( FlushMode.MANUAL ); + queryObject.addSynchronizedQuerySpace( GENE2CS_QUERY_SPACE ); + queryObject.addSynchronizedEntityClass( ArrayDesign.class ); + queryObject.addSynchronizedEntityClass( CompositeSequence.class ); + queryObject.addSynchronizedEntityClass( Gene.class ); CommonQueries.addGeneIds( cs2genes, queryObject ); @@ -337,7 +342,10 @@ public static Map> getCs2GeneMapForProbes( Collection filterProbesByPlatform( Collection probes, queryObject.addScalar( "csid", LongType.INSTANCE ); queryObject.setParameterList( "probes", probes, LongType.INSTANCE ); queryObject.setParameterList( "adids", arrayDesignIds, LongType.INSTANCE ); + queryObject.addSynchronizedQuerySpace( GENE2CS_QUERY_SPACE ); + queryObject.addSynchronizedEntityClass( ArrayDesign.class ); + queryObject.addSynchronizedEntityClass( CompositeSequence.class ); + queryObject.addSynchronizedEntityClass( Gene.class ); //noinspection unchecked return queryObject.list(); } diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/util/EhcacheConfig.java b/gemma-core/src/main/java/ubic/gemma/persistence/util/EhcacheConfig.java new file mode 100644 index 0000000000..7c89da247c --- /dev/null +++ b/gemma-core/src/main/java/ubic/gemma/persistence/util/EhcacheConfig.java @@ -0,0 +1,38 @@ +package ubic.gemma.persistence.util; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.cache.ehcache.EhCacheManagerFactoryBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class EhcacheConfig { + + private static final String EHCACHE_DISK_STORE_DIR = "ehcache.disk.store.dir"; + + /** + * The ehcache.xml assume that {@code -Dehcache.disk.store.dir} is set. If not set explicitly as a JVM option, we + * retrieve the value from the {@code gemma.cache.dir} configuration. + *

+ * The Ehcache cache manager is shared so that it can be reused by Hibernate. + *

+ * This definition is also reused by gsec. + */ + @Bean + public FactoryBean ehcache( @Value("${gemma.cache.dir}") String gemmaCacheDir ) { + String cacheDir; + if ( ( cacheDir = System.getProperty( EHCACHE_DISK_STORE_DIR ) ) == null ) { + System.setProperty( EHCACHE_DISK_STORE_DIR, gemmaCacheDir ); + cacheDir = gemmaCacheDir; + } + if ( StringUtils.isBlank( cacheDir ) ) { + throw new RuntimeException( String.format( "The cache directory is set, either provide it via -D%s or set gemma.cache.dir in your Gemma.properties.", + EHCACHE_DISK_STORE_DIR ) ); + } + EhCacheManagerFactoryBean bean = new EhCacheManagerFactoryBean(); + bean.setShared( true ); + return bean; + } +} diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/util/Settings.java b/gemma-core/src/main/java/ubic/gemma/persistence/util/Settings.java index 13db317fdf..8649ab13b1 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/util/Settings.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/util/Settings.java @@ -74,8 +74,8 @@ public class Settings { private static final Log log = LogFactory.getLog( Settings.class.getName() ); static { - config = new CompositeConfiguration(); + Settings.config.addConfiguration( new SystemConfiguration() ); /* @@ -88,11 +88,36 @@ public class Settings { boolean userConfigLoaded = false; + String gemmaConfig = System.getProperty( "gemma.config" ); + if ( gemmaConfig != null ) { + File f = Paths.get( gemmaConfig ).toFile(); + try { + log.debug( "Loading user configuration from " + f.getAbsolutePath() + " since -Dgemma.config is defined." ); + Settings.config.addConfiguration( ConfigUtils.loadConfig( f ) ); + userConfigLoaded = true; + } catch ( ConfigurationException e ) { + throw new RuntimeException( f.getAbsolutePath() + " could not be loaded.", e ); + } + } + String catalinaBase; - if ( ( catalinaBase = System.getenv( "CATALINA_BASE" ) ) != null ) { + if ( !userConfigLoaded && ( catalinaBase = System.getenv( "CATALINA_BASE" ) ) != null ) { File f = Paths.get( catalinaBase, Settings.USER_CONFIGURATION ).toFile(); - log.debug( "Loading configuration from " + f.getAbsolutePath() + " since $CATALINA_BASE is defined." ); + if ( f.exists() ) { + try { + log.debug( "Loading user configuration from " + f.getAbsolutePath() + " since $CATALINA_BASE is defined." ); + Settings.config.addConfiguration( ConfigUtils.loadConfig( f ) ); + userConfigLoaded = true; + } catch ( ConfigurationException e ) { + throw new RuntimeException( f.getAbsolutePath() + " could not be loaded.", e ); + } + } + } + + File f = Paths.get( System.getProperty( "user.home" ), Settings.USER_CONFIGURATION ).toFile(); + if ( !userConfigLoaded && f.exists() ) { try { + log.debug( "Loading user configuration from " + f.getAbsolutePath() + "." ); Settings.config.addConfiguration( ConfigUtils.loadConfig( f ) ); userConfigLoaded = true; } catch ( ConfigurationException e ) { @@ -100,48 +125,41 @@ public class Settings { } } - try { - Settings.config.addConfiguration( ConfigUtils.loadConfig( Settings.USER_CONFIGURATION ) ); - } catch ( ConfigurationException e ) { - if ( !userConfigLoaded ) { - throw new RuntimeException( Settings.USER_CONFIGURATION + " could not be loaded and no other user configuration were supplied.", e ); - } + if ( !userConfigLoaded ) { + throw new RuntimeException( Settings.USER_CONFIGURATION + " could not be loaded and no other user configuration were supplied." ); } + log.debug( "Loading default configurations from classpath." ); + try { // Default comes first. - PropertiesConfiguration pc = ConfigUtils.loadClasspathConfig( Settings.DEFAULT_CONFIGURATION ); - // ConfigurationUtils.dump( pc, System.err ); - Settings.config.addConfiguration( pc ); + Settings.config.addConfiguration( ConfigUtils.loadClasspathConfig( Settings.DEFAULT_CONFIGURATION ) ); } catch ( ConfigurationException e ) { - throw new RuntimeException( "Default configuration could not be loaded: " + e.getMessage(), e ); + throw new RuntimeException( "Default configuration could not be loaded.", e ); } try { - PropertiesConfiguration pc = ConfigUtils.loadClasspathConfig( Settings.BUILTIN_CONFIGURATION ); - Settings.config.addConfiguration( pc ); + Settings.config.addConfiguration( ConfigUtils.loadClasspathConfig( Settings.BUILTIN_CONFIGURATION ) ); } catch ( ConfigurationException e ) { - throw new RuntimeException( "Extra built-in configuration could not be loaded: " + e.getMessage(), e ); + throw new RuntimeException( "Extra built-in configuration could not be loaded.", e ); } - try { - String gemmaAppDataHome = Settings.config.getString( "gemma.appdata.home" ); - if ( StringUtils.isNotBlank( gemmaAppDataHome ) ) { - PropertiesConfiguration pc = ConfigUtils - .loadConfig( gemmaAppDataHome + File.separatorChar + "local.properties" ); - Settings.config.addConfiguration( pc ); - + String gemmaAppDataHome = Settings.config.getString( "gemma.appdata.home" ); + if ( StringUtils.isNotBlank( gemmaAppDataHome ) ) { + File f2 = Paths.get( gemmaAppDataHome, "local.properties" ).toFile(); + try { + log.debug( "Loading configuration from " + f2.getAbsolutePath() + "." ); + Settings.config.addConfiguration( ConfigUtils.loadConfig( f2 ) ); + } catch ( ConfigurationException e ) { + throw new RuntimeException( "Local configuration could not be loaded from " + f2.getAbsolutePath() + ".", e ); } - } catch ( ConfigurationException e ) { - // that's okay } try { PropertiesConfiguration pc = ConfigUtils.loadClasspathConfig( "ubic/gemma/version.properties" ); - Settings.config.addConfiguration( pc ); } catch ( ConfigurationException e ) { - Settings.log.debug( "version.properties not found" ); + log.warn( "The ubic/gemma/version.properties resource was not found; run `mvn generate-resources -pl gemma-core` to generate it.", e ); } // step through the result and do a final round of variable substitution. @@ -157,10 +175,10 @@ public class Settings { } } - if ( Settings.log.isDebugEnabled() ) { - Settings.log.debug( "********** Configuration details ***********" ); + if ( Settings.log.isTraceEnabled() ) { + Settings.log.trace( "********** Configuration details ***********" ); ConfigurationUtils.dump( Settings.config, System.err ); - Settings.log.debug( "********** End of configuration details ***********" ); + Settings.log.trace( "********** End of configuration details ***********" ); } } diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/util/SettingsConfig.java b/gemma-core/src/main/java/ubic/gemma/persistence/util/SettingsConfig.java index 78fe3defc0..57b6771bb5 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/util/SettingsConfig.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/util/SettingsConfig.java @@ -5,12 +5,13 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; import org.springframework.core.env.MutablePropertySources; +import org.springframework.core.env.PropertiesPropertySource; +import org.springframework.core.env.PropertySource; import org.springframework.core.env.PropertySources; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.support.ResourcePropertySource; -import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -38,29 +39,44 @@ public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderCon private static PropertySources propertySources() throws IOException { MutablePropertySources result = new MutablePropertySources(); + + result.addLast( new PropertiesPropertySource( "system", System.getProperties() ) ); + boolean userConfigLoaded = false; - // load configuration from $CATALINA_BASE - // TODO: move this in Gemma Web - String catalinaBase = System.getenv( "CATALINA_BASE" ); - if ( catalinaBase != null ) { - Path p = Paths.get( catalinaBase, "Gemma.properties" ); - File f = p.toFile(); - log.debug( "Loading configuration from " + f.getAbsolutePath() + " since $CATALINA_BASE is defined." ); - FileSystemResource r = new FileSystemResource( f ); + String gemmaConfig = System.getProperty( "gemma.config" ); + if ( gemmaConfig != null ) { + Path p = Paths.get( gemmaConfig ); + log.debug( "Loading user configuration from " + p.toAbsolutePath() + " since -Dgemma.config is defined." ); + FileSystemResource r = new FileSystemResource( p.toFile() ); if ( !r.exists() ) { - throw new RuntimeException( f.getAbsolutePath() + " could not be loaded." ); + throw new RuntimeException( p + " could not be loaded." ); } warnIfReadableByGroupOrOthers( p ); result.addLast( new ResourcePropertySource( r ) ); userConfigLoaded = true; } + // load configuration from $CATALINA_BASE + // TODO: move this in Gemma Web + String catalinaBase; + if ( !userConfigLoaded && ( catalinaBase = System.getenv( "CATALINA_BASE" ) ) != null ) { + Path p = Paths.get( catalinaBase, "Gemma.properties" ); + FileSystemResource r = new FileSystemResource( p.toFile() ); + if ( r.exists() ) { + log.debug( "Loading user configuration from " + p.toAbsolutePath() + " since $CATALINA_BASE is defined." ); + warnIfReadableByGroupOrOthers( p ); + result.addLast( new ResourcePropertySource( r ) ); + userConfigLoaded = true; + } + } + // load configuration from the home directory // TODO: move this in Gemma CLI Path p = Paths.get( System.getProperty( "user.home" ), "Gemma.properties" ); FileSystemResource r = new FileSystemResource( p.toFile() ); - if ( r.exists() ) { + if ( !userConfigLoaded && r.exists() ) { + log.debug( "Loading user configuration from " + p.toAbsolutePath() + "." ); warnIfReadableByGroupOrOthers( p ); result.addLast( new ResourcePropertySource( r ) ); userConfigLoaded = true; @@ -71,10 +87,26 @@ private static PropertySources propertySources() throws IOException { throw new RuntimeException( "Gemma.properties could not be loaded and no other user configuration were supplied." ); } + log.debug( "Loading default configuration files from classpath." ); result.addLast( new ResourcePropertySource( new ClassPathResource( "default.properties" ) ) ); result.addLast( new ResourcePropertySource( new ClassPathResource( "project.properties" ) ) ); - // FIXME: local local.properties from ${gemma.appdata.home} - result.addLast( new ResourcePropertySource( new ClassPathResource( "ubic/gemma/version.properties" ) ) ); + for ( PropertySource ps : result ) { + String appDataHome = ( String ) ps.getProperty( "gemma.appdata.home" ); + if ( appDataHome != null ) { + // FIXME: handle placeholder substitution in gemma.appdata.home + Path p2 = Paths.get( appDataHome, "local.properties" ); + log.debug( "Loading configuration from " + p2.toAbsolutePath() + "." ); + result.addLast( new ResourcePropertySource( new FileSystemResource( p2.toFile() ) ) ); + break; + } + } + + ClassPathResource versionResource = new ClassPathResource( "ubic/gemma/version.properties" ); + if ( versionResource.exists() ) { + result.addLast( new ResourcePropertySource( versionResource ) ); + } else { + log.warn( "The ubic/gemma/version.properties resource was not found; run `mvn generate-resources -pl gemma-core` to generate it." ); + } return result; } diff --git a/gemma-core/src/main/resources/default.properties b/gemma-core/src/main/resources/default.properties index bd0f54ff83..6cef3e03f6 100755 --- a/gemma-core/src/main/resources/default.properties +++ b/gemma-core/src/main/resources/default.properties @@ -19,6 +19,8 @@ gemma.search.dir=${gemma.appdata.home}/searchIndices gemma.compass.dir=${gemma.search.dir} # The expected structure in this root directory is then: ${gemma.fastq.headers.dir}/GSExxx/GSMxxx/SRRxxx.fastq.header gemma.fastq.headers.dir=${gemma.appdata.home}/fastqHeaders +# Directory for on-disk cache, overwritten by the ehcache.disk.store.dir system property +gemma.cache.dir=${gemma.appdata.home}/cache ##################################################### # EMAIL and URLS # base url for the system, used in formed URLs @@ -38,30 +40,30 @@ ga.debug=false ##################################################### # Database configuration gemma.db.host=localhost +gemma.db.port=3306 +gemma.db.name=gemd gemma.db.driver=com.mysql.cj.jdbc.Driver -gemma.db.url=jdbc:mysql://${gemma.db.host}:3306/gemd?useSSL=false&rewriteBatchedStatements=true +gemma.db.url=jdbc:mysql://${gemma.db.host}:${gemma.db.port}/${gemma.db.name}?useSSL=false&rewriteBatchedStatements=true +# You must override these settings: gemma.db.user=gemmauser -# You must override this setting. gemma.db.password=XXXXXX # This ensure some basic behaviors of our database gemma.db.sqlMode=STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION # Default timezone for storage of DATETIME that are mapped to exact moments (i.e. java.util.Date) gemma.db.timezone=America/Vancouver -# Maximum pool size for the collection pool +# Maximum size for the connections pool gemma.db.maximumPoolSize=10 ############################################################ # SECURITY # Used to elevate authorities for some methods. -gemma.runas.password=gemmarunaspassword +gemma.runas.password=XXXXXXX # Used to provide authentication for threads that run in the server but are autonomous; e.g. scheduled tasks. # It is suggested you change this password for maximum security! gemma.agent.userName=gemmaAgent -gemma.agent.password=gemmaAgent +gemma.agent.password=XXXXXXXX # If you want to use reCaptcha (http://recaptcha.net/), set these. gemma.recaptcha.privateKey= gemma.recaptcha.publicKey= -# Should object access be logged. Expensive. -gemma.acl.audit=false ###################################################### # Sequence and other analysis configuration # Change these if the programs are not in your path. @@ -109,7 +111,7 @@ gemma.goldenpath.db.driver=${gemma.db.driver} gemma.goldenpath.db.host=${gemma.db.host} gemma.goldenpath.db.user=${gemma.db.user} gemma.goldenpath.db.password=${gemma.db.password} -gemma.goldenpath.db.port=3306 +gemma.goldenpath.db.port=${gemma.db.port} gemma.goldenpath.db.human=hg38 gemma.goldenpath.db.mouse=mm39 gemma.goldenpath.db.rat=rn7 @@ -123,6 +125,8 @@ gemma.goldenpath.db.zebrafish=danRer7 quartzOn=false ########################################################### # ONTOLOGIES AND OTHER DATA SOURCES TO USE +# Pool size for loading ontologies +gemma.ontology.loader.corePoolSize=4 # Other ontologies are configured with defaults through basecode.properties # by default, same place as other search indices ontology.index.dir=${gemma.search.dir} @@ -182,10 +186,15 @@ gemma.localTasks.corePoolSize=16 ############################################################ # CONFIGURATION USED ONLY IN TESTS # Separate test database if desired -gemma.testdb.url=jdbc:mysql://${gemma.db.host}/gemdtest?useSSL=false&rewriteBatchedStatements=true +# The defaults will work out-of-the-box with the testdb declared in docker-compose.yml +gemma.testdb.host=localhost +gemma.testdb.port=3307 +gemma.testdb.name=gemdtest +gemma.testdb.url=jdbc:mysql://${gemma.testdb.host}:${gemma.testdb.port}/${gemma.testdb.name}?useSSL=false&rewriteBatchedStatements=true gemma.testdb.driver=${gemma.db.driver} -gemma.testdb.user=root -gemma.testdb.password=XXXXXXX +gemma.testdb.user=gemmatest +gemma.testdb.password=1234 +gemma.testdb.timezone=${gemma.db.timezone} gemma.testdb.sqlMode=${gemma.db.sqlMode} #the external database id to exclude by default in phenocarta gemma.neurocarta.exluded_database_id=85 diff --git a/gemma-core/src/main/resources/sql/init-entities.sql b/gemma-core/src/main/resources/sql/init-entities.sql index bf37c5ba16..14d4c9b5ed 100644 --- a/gemma-core/src/main/resources/sql/init-entities.sql +++ b/gemma-core/src/main/resources/sql/init-entities.sql @@ -35,8 +35,8 @@ insert into AUDIT_TRAIL VALUES (3); set @n:=now(); --- username=gemmaAgent: id = 2, password = 'gemmaAgent', audit trail #2, using salt={username} -insert into CONTACT (ID, CLASS, NAME, LAST_NAME, USER_NAME, PASSWORD, ENABLED, EMAIL, PASSWORD_HINT) values (2, "User", "gemmaAgent", "", "gemmaAgent", "a99c3785155e31ac8f9273537f14e9304cc22f20", 1, "pavlab-support@msl.ubc.ca", "hint"); +-- username=gemmaAgent: id = 2, password = 'XXXXXXXX', audit trail #2, using salt={username} +insert into CONTACT (ID, CLASS, NAME, LAST_NAME, USER_NAME, PASSWORD, ENABLED, EMAIL, PASSWORD_HINT) values (2, "User", "gemmaAgent", "", "gemmaAgent", "2db458c67b4b52bba0184611c302c9c174ce8de4", 1, "pavlab-support@msl.ubc.ca", "hint"); -- username=administrator: id = 1, password = 'administrator', audit trail #1 using salt=username ('administrator') insert into CONTACT (ID, CLASS, NAME, LAST_NAME, USER_NAME, PASSWORD, ENABLED, EMAIL, PASSWORD_HINT) values (1, "User", "administrator", "", "administrator", "b7338dcc17d6b6c199a75540aab6d0506567b980", 1, "pavlab-support@msl.ubc.ca", "hint"); diff --git a/gemma-core/src/main/resources/sql/init-indices.sql b/gemma-core/src/main/resources/sql/init-indices.sql index 74684066a2..17bf61ec80 100644 --- a/gemma-core/src/main/resources/sql/init-indices.sql +++ b/gemma-core/src/main/resources/sql/init-indices.sql @@ -4,7 +4,7 @@ ALTER TABLE ACLSID ADD INDEX class (class); ALTER TABLE CURATION_DETAILS - ADD INDEX TROUBLED_IX (TROUBLED); + ADD INDEX TROUBLED_IX (TROUBLED); ALTER TABLE BIO_SEQUENCE ADD INDEX name (NAME); @@ -48,11 +48,16 @@ ALTER TABLE ANALYSIS_RESULT_SET ADD INDEX ANALYSIS_RESULT_SET_NUMBER_OF_PROBES_TESTED (NUMBER_OF_PROBES_TESTED); -- no URI exceeds 100 characters in practice, so we only index a prefix ALTER TABLE CHARACTERISTIC + ADD INDEX class (class), ADD INDEX CHARACTERISTIC_VALUE (VALUE), ADD INDEX CHARACTERISTIC_CATEGORY (CATEGORY), ADD INDEX CHARACTERISTIC_VALUE_URI_VALUE (VALUE_URI(100), VALUE), ADD INDEX CHARACTERISTIC_CATEGORY_URI_CATEGORY_VALUE_URI_VALUE (CATEGORY_URI(100), CATEGORY, VALUE_URI(100), VALUE), - ADD INDEX CHARACTERISTIC_EVIDENCE_CODE(EVIDENCE_CODE); + ADD INDEX CHARACTERISTIC_EVIDENCE_CODE (EVIDENCE_CODE), + ADD INDEX CHARACTERISTIC_PREDICATE_URI_PREDICATE (PREDICATE_URI(100), PREDICATE), + ADD INDEX CHARACTERISTIC_OBJECT_URI_OBJECT (OBJECT_URI(100), OBJECT), + ADD INDEX CHARACTERISTIC_SECOND_PREDICATE_URI_SECOND_PREDICATE (SECOND_PREDICATE_URI(100), SECOND_PREDICATE), + ADD INDEX CHARACTERISTIC_SECOND_OBJECT_URI_SECOND_OBJECT (SECOND_OBJECT_URI(100), SECOND_OBJECT); ALTER TABLE GENE_SET ADD INDEX name (NAME); ALTER TABLE PROCESSED_EXPRESSION_DATA_VECTOR @@ -112,13 +117,13 @@ ALTER TABLE MEASUREMENT ADD INDEX MEASUREMENT_KIND_CV (KIND_C_V), ADD INDEX MEASUREMENT_OTHER_KIND (OTHER_KIND), ADD INDEX MEASUREMENT_REPRESENTATION (REPRESENTATION), - ADD INDEX MEASUREMENT_TYPE(TYPE), + ADD INDEX MEASUREMENT_TYPE (TYPE), ADD INDEX MEASUREMENT_VALUE (VALUE); ALTER TABLE GEEQ ADD INDEX GEEQ_DETECTED_QUALITY_SCORE (DETECTED_QUALITY_SCORE), ADD INDEX GEEQ_DETECTED_SUITABILITY_SCORE (DETECTED_SUITABILITY_SCORE), - ADD INDEX GEEQ_MANUAL_QUALITY_SCORE(MANUAL_QUALITY_SCORE), - ADD INDEX GEEQ_MANUAL_QUALITY_OVERRIDE(MANUAL_QUALITY_OVERRIDE), - ADD INDEX GEEQ_MANUAL_SUITABILITY_SCORE(MANUAL_SUITABILITY_SCORE), - ADD INDEX GEEQ_MANUAL_SUITABILITY_OVERRIDE(MANUAL_SUITABILITY_OVERRIDE); \ No newline at end of file + ADD INDEX GEEQ_MANUAL_QUALITY_SCORE (MANUAL_QUALITY_SCORE), + ADD INDEX GEEQ_MANUAL_QUALITY_OVERRIDE (MANUAL_QUALITY_OVERRIDE), + ADD INDEX GEEQ_MANUAL_SUITABILITY_SCORE (MANUAL_SUITABILITY_SCORE), + ADD INDEX GEEQ_MANUAL_SUITABILITY_OVERRIDE (MANUAL_SUITABILITY_OVERRIDE); \ No newline at end of file diff --git a/gemma-core/1.30.4.sql b/gemma-core/src/main/resources/sql/migrations/db.1.30.4.sql similarity index 100% rename from gemma-core/1.30.4.sql rename to gemma-core/src/main/resources/sql/migrations/db.1.30.4.sql diff --git a/gemma-core/src/main/resources/sql/migrations/db.1.31.0.sql b/gemma-core/src/main/resources/sql/migrations/db.1.31.0.sql new file mode 100644 index 0000000000..2c0f000225 --- /dev/null +++ b/gemma-core/src/main/resources/sql/migrations/db.1.31.0.sql @@ -0,0 +1,30 @@ +alter table INVESTIGATION + modify column BATCH_EFFECT VARCHAR(255), + add column BATCH_EFFECT_STATISTICS TEXT after BATCH_EFFECT; +update INVESTIGATION +set BATCH_EFFECT_STATISTICS = BATCH_EFFECT, + BATCH_EFFECT = 'BATCH_EFFECT_FAILURE' +where BATCH_EFFECT like 'This data set may have a batch artifact%'; +alter table CHROMOSOME_FEATURE + add column DUMMY TINYINT not null default false; +alter table BIO_ASSAY + modify column SAMPLE_USED_FK BIGINT not null; +-- allow distinguishing characteristics from statements +alter table CHARACTERISTIC + add column MIGRATED_TO_STATEMENT TINYINT not null default false after ORIGINAL_VALUE, + add column PREDICATE VARCHAR(255) after MIGRATED_TO_STATEMENT, + add column PREDICATE_URI VARCHAR(255) after PREDICATE, + add column OBJECT VARCHAR(255) after PREDICATE_URI, + add column OBJECT_URI VARCHAR(255) after OBJECT, + add column SECOND_PREDICATE VARCHAR(255) after OBJECT_URI, + add column SECOND_PREDICATE_URI VARCHAR(255) after SECOND_PREDICATE, + add column SECOND_OBJECT VARCHAR(255) after SECOND_PREDICATE_URI, + add column SECOND_OBJECT_URI VARCHAR(255) after SECOND_OBJECT; +-- add indices we need for querying triplets +alter table CHARACTERISTIC + add index CHARACTERISTIC_PREDICATE_URI_PREDICATE (PREDICATE_URI(100), PREDICATE), + add index CHARACTERISTIC_OBJECT_URI_OBJECT (OBJECT_URI(100), OBJECT), + add index CHARACTERISTIC_SECOND_PREDICATE_URI_SECOND_PREDICATE (SECOND_PREDICATE_URI(100), SECOND_PREDICATE), + add index CHARACTERISTIC_SECOND_OBJECT_URI_SECOND_OBJECT (SECOND_OBJECT_URI(100), SECOND_OBJECT); +alter table FACTOR_VALUE + add column NEEDS_ATTENTION TINYINT not null default false; \ No newline at end of file diff --git a/gemma-core/src/main/resources/ubic/gemma/applicationContext-dataSource.xml b/gemma-core/src/main/resources/ubic/gemma/applicationContext-dataSource.xml index 6deff58fb2..38b3baa494 100644 --- a/gemma-core/src/main/resources/ubic/gemma/applicationContext-dataSource.xml +++ b/gemma-core/src/main/resources/ubic/gemma/applicationContext-dataSource.xml @@ -23,14 +23,14 @@ - + - ${gemma.db.timezone} - sql_mode='${gemma.db.sqlMode}' + ${gemma.testdb.timezone} + sql_mode='${gemma.testdb.sqlMode}' diff --git a/gemma-core/src/main/resources/ubic/gemma/applicationContext-hibernate.xml b/gemma-core/src/main/resources/ubic/gemma/applicationContext-hibernate.xml index 5c1f505efc..d54c8ddd18 100644 --- a/gemma-core/src/main/resources/ubic/gemma/applicationContext-hibernate.xml +++ b/gemma-core/src/main/resources/ubic/gemma/applicationContext-hibernate.xml @@ -45,15 +45,15 @@ - + - + @@ -61,7 +61,9 @@ ${gemma.hibernate.hbm2ddl.auto} ubic.gemma.persistence.hibernate.MySQL57InnoDBDialect - org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory + org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory + true true @@ -91,12 +93,6 @@ - - - - - - diff --git a/gemma-core/src/main/resources/ubic/gemma/applicationContext-ontology.xml b/gemma-core/src/main/resources/ubic/gemma/applicationContext-ontology.xml index b0c6ba1577..5e66a26e4d 100644 --- a/gemma-core/src/main/resources/ubic/gemma/applicationContext-ontology.xml +++ b/gemma-core/src/main/resources/ubic/gemma/applicationContext-ontology.xml @@ -1,81 +1,86 @@ - + xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd" + profile="!test"> - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/gemma-core/src/main/resources/ubic/gemma/core/loader/affy.celmappings.properties b/gemma-core/src/main/resources/ubic/gemma/core/loader/affy.celmappings.properties index 0e44a7b9f4..3a9289f673 100644 --- a/gemma-core/src/main/resources/ubic/gemma/core/loader/affy.celmappings.properties +++ b/gemma-core/src/main/resources/ubic/gemma/core/loader/affy.celmappings.properties @@ -39,7 +39,7 @@ HU6800=GPL80 PrimeView=GPL15207 U133AAofAv2=GPL4685 U133_X3P=GPL1352 -U133_X3Pa520049=GPL1352 +U133_X3Pa520049=GPL1352 HG-U219=GPL13667 # Mouse Mu11KsubA=GPL75 @@ -62,6 +62,7 @@ MoGene-2_1-st=GPL17400 Clariom_S_Mouse=GPL23038 Clariom_S_Rat=GPL23040 MTA-1_0=GPL20775 +NuGO_Mm1a520177=GPL7440 # Rat RaGene-1_0-st-v1=GPL6247 RaGene-1_1-st=GPL11534 diff --git a/gemma-core/src/main/resources/ubic/gemma/core/ontology/EFO.factor.categories.txt b/gemma-core/src/main/resources/ubic/gemma/core/ontology/EFO.factor.categories.txt index e8c26ddcc6..3b2e2d1b1e 100644 --- a/gemma-core/src/main/resources/ubic/gemma/core/ontology/EFO.factor.categories.txt +++ b/gemma-core/src/main/resources/ubic/gemma/core/ontology/EFO.factor.categories.txt @@ -10,20 +10,16 @@ http://www.ebi.ac.uk/efo/EFO_0000399 developmental stage http://www.ebi.ac.uk/efo/EFO_0000408 disease http://gemma.msl.ubc.ca/ont/TGEMO_00101 disease model http://www.ebi.ac.uk/efo/EFO_0000410 disease staging -http://www.ebi.ac.uk/efo/EFO_0000434 ecotype -http://www.ebi.ac.uk/efo/EFO_0000470 environmental stress http://www.ebi.ac.uk/efo/EFO_0000513 genotype http://www.ebi.ac.uk/efo/EFO_0000523 growth condition http://www.ebi.ac.uk/efo/EFO_0000635 organism part http://www.ebi.ac.uk/efo/EFO_0000651 phenotype http://purl.obolibrary.org/obo/PATO_0000047 biological sex http://www.ebi.ac.uk/efo/EFO_0000727 treatment -http://www.ebi.ac.uk/efo/EFO_0001702 temperature http://www.ebi.ac.uk/efo/EFO_0004444 environmental history http://www.ebi.ac.uk/efo/EFO_0005135 strain http://www.ebi.ac.uk/efo/EFO_0002755 diet http://purl.obolibrary.org/obo/GO_0007610 behavior -http://www.ebi.ac.uk/efo/EFO_0002571 medical procedure # OK for EFC and EFV but not EEtag http://www.ebi.ac.uk/efo/EFO_0005067 block http://www.ebi.ac.uk/efo/EFO_0000246 age @@ -39,6 +35,11 @@ http://purl.obolibrary.org/obo/GO_0008150 biological process http://www.ebi.ac.uk/efo/EFO_0000724 timepoint # OK for EEtag only http://www.ebi.ac.uk/efo/EFO_0001426 study design +# too rarely used to be useful +#http://www.ebi.ac.uk/efo/EFO_0002571 medical procedure +#http://www.ebi.ac.uk/efo/EFO_0000470 environmental stress +#http://www.ebi.ac.uk/efo/EFO_0000434 ecotype +#http://www.ebi.ac.uk/efo/EFO_0001702 temperature # Never to use any more for ee tags, factors, factor values. # But can be used for biomaterial characteristics #http://www.ebi.ac.uk/efo/EFO_0000562 labelling diff --git a/gemma-core/src/main/resources/ubic/gemma/core/ontology/Relation.terms.txt b/gemma-core/src/main/resources/ubic/gemma/core/ontology/Relation.terms.txt new file mode 100644 index 0000000000..78707b99e1 --- /dev/null +++ b/gemma-core/src/main/resources/ubic/gemma/core/ontology/Relation.terms.txt @@ -0,0 +1,27 @@ +# Terms usable for relations among concepts. +http://purl.obolibrary.org/obo/RO_0000087 has role +http://purl.obolibrary.org/obo/RO_0001000 derives from +http://purl.obolibrary.org/obo/RO_0000053 has characteristic +http://purl.obolibrary.org/obo/ENVO_01003004 derives from part of +http://purl.obolibrary.org/obo/RO_0002260 has biological role +http://purl.obolibrary.org/obo/RO_0000086 has quality +http://purl.obolibrary.org/obo/RO_0002573 has modifier +http://purl.obolibrary.org/obo/RO_0016002 has disease +http://purl.obolibrary.org/obo/RO_0002200 has phenotype +http://purl.obolibrary.org/obo/RO_0001025 located in +http://purl.obolibrary.org/obo/GENO_0000222 has_genotype +http://purl.obolibrary.org/obo/GENO_0000413 has_allele +http://purl.obolibrary.org/obo/GENO_0000447 is_gene_target_of +http://purl.obolibrary.org/obo/CLO_0037209 derived from cell +http://purl.obolibrary.org/obo/CLO_0037210 derived from cell line +http://purl.obolibrary.org/obo/CLO_0000015 derives from patient having disease +http://purl.obolibrary.org/obo/RO_0002220 adjacent to +http://purl.obolibrary.org/obo/RO_0002503 toward +http://www.ebi.ac.uk/efo/EFO_0000784 has_disease_location +http://gemma.msl.ubc.ca/ont/TGEMO_00167 delivered for duration +http://gemma.msl.ubc.ca/ont/TGEMO_00166 delivered at dose +http://gemma.msl.ubc.ca/ont/TGEMO_00168 has developmental stage +http://gemma.msl.ubc.ca/ont/TGEMO_00169 positive for product of gene +http://gemma.msl.ubc.ca/ont/TGEMO_00170 negative for product of gene +http://gemma.msl.ubc.ca/ont/TGEMO_00171 induced by +http://gemma.msl.ubc.ca/ont/TGEMO_00183 delivered to \ No newline at end of file diff --git a/gemma-core/src/main/resources/ubic/gemma/core/ontology/valueStringToOntologyTermMappings.txt b/gemma-core/src/main/resources/ubic/gemma/core/ontology/valueStringToOntologyTermMappings.txt index e80be3c506..855b8abfde 100644 --- a/gemma-core/src/main/resources/ubic/gemma/core/ontology/valueStringToOntologyTermMappings.txt +++ b/gemma-core/src/main/resources/ubic/gemma/core/ontology/valueStringToOntologyTermMappings.txt @@ -4,15 +4,15 @@ # it won't be mapped. # String value value valueUri category categoryUri 126S6/SvEvTac 129S6 http://www.ebi.ac.uk/efo/EFO_0000598 strain http://www.ebi.ac.uk/efo/EFO_0005135 -129 129 http://www.ebi.ac.uk/efo/EFO_0000597 strain http://www.ebi.ac.uk/efo/EFO_0005135 -129/Sv 129S/Sv http://www.ebi.ac.uk/efo/EFO_0000599 strain http://www.ebi.ac.uk/efo/EFO_0005135 +129 129 mouse strain http://www.ebi.ac.uk/efo/EFO_0000597 strain http://www.ebi.ac.uk/efo/EFO_0005135 +129/Sv 129/Sv http://www.ebi.ac.uk/efo/EFO_0000599 strain http://www.ebi.ac.uk/efo/EFO_0005135 129SV 129/Sv http://www.ebi.ac.uk/efo/EFO_0000599 strain http://www.ebi.ac.uk/efo/EFO_0005135 17beta-estradiol 17beta-estradiol http://purl.obolibrary.org/obo/CHEBI_16469 treatment http://www.ebi.ac.uk/efo/EFO_0000727 A/J A/J http://www.ebi.ac.uk/efo/EFO_0001327 strain http://www.ebi.ac.uk/efo/EFO_0005135 A549 A549 cell http://purl.obolibrary.org/obo/CLO_0001601 cell line http://purl.obolibrary.org/obo/CLO_0000031 A549 cells A549 cell http://purl.obolibrary.org/obo/CLO_0001601 cell line http://purl.obolibrary.org/obo/CLO_0000031 A549 epithelial cells A549 cell http://purl.obolibrary.org/obo/CLO_0001601 cell line http://purl.obolibrary.org/obo/CLO_0000031 -abdominal subcutaneous adipose tissue abdominal subcutaneous adipose tissue http://purl.obolibrary.org/obo/UBERON_0014455 organism part http://www.ebi.ac.uk/efo/EFO_0000635 +abdominal subcutaneous adipose tissue subcutaneous abdominal adipose tissue http://purl.obolibrary.org/obo/UBERON_0014455 organism part http://www.ebi.ac.uk/efo/EFO_0000635 ActD actinomycin D http://purl.obolibrary.org/obo/CHEBI_27666 treatment http://www.ebi.ac.uk/efo/EFO_0000408 active tuberculosis tuberculosis http://purl.obolibrary.org/obo/MONDO_0018076 disease http://www.ebi.ac.uk/efo/EFO_0000408 Acute hippocampal slice Ammon's horn http://purl.obolibrary.org/obo/UBERON_0001954 organism part http://www.ebi.ac.uk/efo/EFO_0000635 @@ -41,15 +41,14 @@ Asthma asthma http://purl.obolibrary.org/obo/MONDO_0004979 disease http://www.eb astrocyte astrocyte http://purl.obolibrary.org/obo/CL_0000127 cell type http://www.ebi.ac.uk/efo/EFO_0000324 astrocytes astrocyte http://purl.obolibrary.org/obo/CL_0000127 cell type http://www.ebi.ac.uk/efo/EFO_0000324 autism autism spectrum disorder http://purl.obolibrary.org/obo/MONDO_0005258 disease http://www.ebi.ac.uk/efo/EFO_0000408 -Autism autism http://purl.obolibrary.org/obo/MONDO_0005260 disease http://www.ebi.ac.uk/efo/EFO_0000408 B cell B cell http://purl.obolibrary.org/obo/CL_0000236 cell type http://www.ebi.ac.uk/efo/EFO_0000324 B cells B cell http://purl.obolibrary.org/obo/CL_0000236 cell type http://www.ebi.ac.uk/efo/EFO_0000324 B-cell lymphoma B-cell neoplasm http://purl.obolibrary.org/obo/MONDO_0004095 disease http://www.ebi.ac.uk/efo/EFO_0000408 B6 C57BL/6 http://www.ebi.ac.uk/efo/EFO_0022397 strain http://www.ebi.ac.uk/efo/EFO_0005135 B6C3F1 B6C3F1 http://www.ebi.ac.uk/efo/EFO_0001340 strain http://www.ebi.ac.uk/efo/EFO_0005135 BALB BALB/c http://www.ebi.ac.uk/efo/EFO_0000602 strain http://www.ebi.ac.uk/efo/EFO_0005135 -BALB/c BALB/c http://www.ebi.ac.uk/efo/EFO_0000602 strain http://www.ebi.ac.uk/efo/EFO_0005138 -BALB/cJ BALB/cJ http://www.ebi.ac.uk/efo/EFO_0000602 strain http://www.ebi.ac.uk/efo/EFO_0005135 +BALB/c BALB/c http://www.ebi.ac.uk/efo/EFO_0000602 strain http://www.ebi.ac.uk/efo/EFO_0005135 +BALB/cJ BALB/c http://www.ebi.ac.uk/efo/EFO_0000602 strain http://www.ebi.ac.uk/efo/EFO_0005135 BEAS-2B BEAS-2B cell http://purl.obolibrary.org/obo/CLO_0001925 cell line http://purl.obolibrary.org/obo/CLO_0000031 Biceps biceps brachii http://purl.obolibrary.org/obo/UBERON_0001507 organism part http://www.ebi.ac.uk/efo/EFO_0000635 bipolar bipolar disorder http://purl.obolibrary.org/obo/MONDO_0004985 disease http://www.ebi.ac.uk/efo/EFO_0000408 @@ -60,7 +59,7 @@ blood blood http://purl.obolibrary.org/obo/UBERON_0000178 organism part http://w blood cells blood cell http://purl.obolibrary.org/obo/CL_0000081 cell type http://www.ebi.ac.uk/efo/EFO_0000324 Blood sample blood http://purl.obolibrary.org/obo/UBERON_0000178 organism part http://www.ebi.ac.uk/efo/EFO_0000635 Blood Vessel blood vessel http://purl.obolibrary.org/obo/UBERON_0001981 organism part http://www.ebi.ac.uk/efo/EFO_0000635 -bone bone http://purl.obolibrary.org/obo/UBERON_0002481 organism part http://www.ebi.ac.uk/efo/EFO_0000635 +bone bone tissue http://purl.obolibrary.org/obo/UBERON_0002481 organism part http://www.ebi.ac.uk/efo/EFO_0000635 bone marrow bone marrow http://purl.obolibrary.org/obo/UBERON_0002371 organism part http://www.ebi.ac.uk/efo/EFO_0000635 Bone Marrow cells bone marrow cell http://purl.obolibrary.org/obo/CL_0002092 cell type http://www.ebi.ac.uk/efo/EFO_0000324 bone marrow derived macrophage bone marrow macrophage http://purl.obolibrary.org/obo/CL_0002476 cell type http://www.ebi.ac.uk/efo/EFO_0000324 @@ -94,38 +93,37 @@ C3H Sprague Dawley http://www.ebi.ac.uk/efo/EFO_0001352 strain http://www.ebi.ac C3H/HeJ C3H/HeJ http://www.ebi.ac.uk/efo/EFO_0001329 strain http://www.ebi.ac.uk/efo/EFO_0005135 C56BL/6J C57BL/6J http://www.ebi.ac.uk/efo/EFO_0000606 strain http://www.ebi.ac.uk/efo/EFO_0005135 C57/B6 C57BL/6 http://www.ebi.ac.uk/efo/EFO_0022397 strain http://www.ebi.ac.uk/efo/EFO_0005135 -C57/BL6 C57BL/6 http://www.ebi.ac.uk/efo/EFO_0022397 strain http://www.ebi.ac.uk/efo/EFO_0005138 +C57/BL6 C57BL/6 http://www.ebi.ac.uk/efo/EFO_0022397 strain http://www.ebi.ac.uk/efo/EFO_0005135 C57B6 C57BL/6 http://www.ebi.ac.uk/efo/EFO_0022397 strain http://www.ebi.ac.uk/efo/EFO_0005135 C57BL C57BL/6 http://www.ebi.ac.uk/efo/EFO_0022397 strain http://www.ebi.ac.uk/efo/EFO_0005135 -c57bl/6 C57BL/6 http://www.ebi.ac.uk/efo/EFO_0022397 strain http://www.ebi.ac.uk/efo/EFO_0005138 C57BL/6 C57BL/6 http://www.ebi.ac.uk/efo/EFO_0022397 strain http://www.ebi.ac.uk/efo/EFO_0005135 -C57BL/6J C57BL/6J http://www.ebi.ac.uk/efo/EFO_0005138 strain http://www.ebi.ac.uk/efo/EFO_0005138 -C57BL/6J mouse C57BL/6J http://www.ebi.ac.uk/efo/EFO_0005138 strain http://www.ebi.ac.uk/efo/EFO_0005135 +C57BL/6J C57BL/6J http://www.ebi.ac.uk/efo/EFO_0000606 strain http://www.ebi.ac.uk/efo/EFO_0005135 +C57BL/6J mouse C57BL/6J http://www.ebi.ac.uk/efo/EFO_0000606 strain http://www.ebi.ac.uk/efo/EFO_0005135 C57BL/6N C57BL/6 http://www.ebi.ac.uk/efo/EFO_0022397 strain http://www.ebi.ac.uk/efo/EFO_0005135 C57BL6 C57BL/6 http://www.ebi.ac.uk/efo/EFO_0022397 strain http://www.ebi.ac.uk/efo/EFO_0005135 -C57BL6/J C57BL/6J http://www.ebi.ac.uk/efo/EFO_0005138 strain http://www.ebi.ac.uk/efo/EFO_0005135 -C57BL6J C57BL/6J http://www.ebi.ac.uk/efo/EFO_0005138 strain http://www.ebi.ac.uk/efo/EFO_0005135 +C57BL6/J C57BL/6J http://www.ebi.ac.uk/efo/EFO_0000606 strain http://www.ebi.ac.uk/efo/EFO_0005135 +C57BL6J C57BL/6J http://www.ebi.ac.uk/efo/EFO_0000606 strain http://www.ebi.ac.uk/efo/EFO_0005135 CA1 hippocampal subregion CA1 field of hippocampus http://purl.obolibrary.org/obo/UBERON_0003881 organism part http://www.ebi.ac.uk/efo/EFO_0000635 CA1 hippocampus CA1 field of hippocampus http://purl.obolibrary.org/obo/UBERON_0003881 organism part http://www.ebi.ac.uk/efo/EFO_0000635 Caffeine caffeine http://purl.obolibrary.org/obo/CHEBI_27732 treatment http://www.ebi.ac.uk/efo/EFO_0000727 Calu-3 Calu-3 cell http://purl.obolibrary.org/obo/CLO_0002192 cell line http://purl.obolibrary.org/obo/CLO_0000031 cancer cancer http://purl.obolibrary.org/obo/MONDO_0004992 disease http://www.ebi.ac.uk/efo/EFO_0000408 cancer tumor cancer http://purl.obolibrary.org/obo/MONDO_0004992 disease http://www.ebi.ac.uk/efo/EFO_0000408 -Canton S Canton S http://www.ebi.ac.uk/efo/EFO_0001325 strain http://www.ebi.ac.uk/efo/EFO_0005135 +Canton S Canton-S http://www.ebi.ac.uk/efo/EFO_0001325 strain http://www.ebi.ac.uk/efo/EFO_0005135 CAU European http://purl.obolibrary.org/obo/HANCESTRO_0005 population http://purl.obolibrary.org/obo/OBI_0000181 caucasian European http://purl.obolibrary.org/obo/HANCESTRO_0005 population http://purl.obolibrary.org/obo/OBI_0000181 Caucasian origin European http://purl.obolibrary.org/obo/HANCESTRO_0005 population http://purl.obolibrary.org/obo/OBI_0000181 CBA/CaJ CBA/CaJ http://www.ebi.ac.uk/efo/EFO_0001336 strain http://www.ebi.ac.uk/efo/EFO_0005135 CBA/J CBA/J http://www.ebi.ac.uk/efo/EFO_0007735 strain http://www.ebi.ac.uk/efo/EFO_0005135 -CD-1 Crl:CD1(ICR) http://www.ebi.ac.uk/efo/EFO_0005180 strain http://www.ebi.ac.uk/efo/EFO_0005135 -CD1 Crl:CD1(ICR) http://www.ebi.ac.uk/efo/EFO_0005180 strain http://www.ebi.ac.uk/efo/EFO_0005135 +CD-1 CD1 mus strain http://www.ebi.ac.uk/efo/EFO_0005180 strain http://www.ebi.ac.uk/efo/EFO_0005135 +CD1 CD1 mus strain http://www.ebi.ac.uk/efo/EFO_0005180 strain http://www.ebi.ac.uk/efo/EFO_0005135 CD34+ mononuclear mononuclear cell http://purl.obolibrary.org/obo/CL_0000842 cell type http://www.ebi.ac.uk/efo/EFO_0000324 CD4+ lymphocyte CD4-positive, alpha-beta T cell http://purl.obolibrary.org/obo/CL_0000624 cell type http://www.ebi.ac.uk/efo/EFO_0000324 CD4+ T cell CD4-positive, alpha-beta T cell http://purl.obolibrary.org/obo/CL_0000624 cell type http://www.ebi.ac.uk/efo/EFO_0000324 CD4+ T cells CD4-positive, alpha-beta T cell http://purl.obolibrary.org/obo/CL_0000624 cell type http://www.ebi.ac.uk/efo/EFO_0000324 CD4+ T-cells CD4-positive, alpha-beta T cell http://purl.obolibrary.org/obo/CL_0000624 cell type http://www.ebi.ac.uk/efo/EFO_0000324 CD8 T cells CD4-positive, alpha-beta T cell http://purl.obolibrary.org/obo/CL_0000624 cell type http://www.ebi.ac.uk/efo/EFO_0000324 -CD8+ T cells CD8-positive, alpha beta T cell http://purl.obolibrary.org/obo/CL_0000625 cell type http://www.ebi.ac.uk/efo/EFO_0000324 +CD8+ T cells CD8-positive, alpha-beta T cell http://purl.obolibrary.org/obo/CL_0000625 cell type http://www.ebi.ac.uk/efo/EFO_0000324 CDF Fisher 344 Fischer 344 http://www.ebi.ac.uk/efo/EFO_0000176 strain http://www.ebi.ac.uk/efo/EFO_0005135 Celiac Disease celiac disease http://purl.obolibrary.org/obo/MONDO_0005130 disease http://www.ebi.ac.uk/efo/EFO_0000408 Central Nucleus of Amygdala central amygdaloid nucleus http://purl.obolibrary.org/obo/UBERON_0002883 organism part http://www.ebi.ac.uk/efo/EFO_0000635 @@ -185,7 +183,7 @@ DLBCL diffuse large B-cell lymphoma http://purl.obolibrary.org/obo/MONDO_0018905 DLBCL frozen biopsy diffuse large B-cell lymphoma http://purl.obolibrary.org/obo/MONDO_0018905 disease http://www.ebi.ac.uk/efo/EFO_0000408 DLBCL patient sample diffuse large B-cell lymphoma http://purl.obolibrary.org/obo/MONDO_0018905 disease http://www.ebi.ac.uk/efo/EFO_0000408 DLPFC dorsolateral prefrontal cortex http://purl.obolibrary.org/obo/UBERON_0009834 organism part http://www.ebi.ac.uk/efo/EFO_0000635 -DMSO DMSO http://purl.obolibrary.org/obo/CHEBI_28262 treatment http://www.ebi.ac.uk/efo/EFO_0000727 +DMSO dimethyl sulfoxide http://purl.obolibrary.org/obo/CHEBI_28262 treatment http://www.ebi.ac.uk/efo/EFO_0000727 dorsal forebrain forebrain http://purl.obolibrary.org/obo/UBERON_0001890 organism part http://www.ebi.ac.uk/efo/EFO_0000635 dorsal hippocampus Ammon's horn http://purl.obolibrary.org/obo/UBERON_0001954 organism part http://www.ebi.ac.uk/efo/EFO_0000635 Dorsal Lateral Prefrontal Cortex dorsolateral prefrontal cortex http://purl.obolibrary.org/obo/UBERON_0009834 organism part http://www.ebi.ac.uk/efo/EFO_0000635 @@ -193,7 +191,7 @@ dorsal raphe nucleus dorsal raphe nucleus http://purl.obolibrary.org/obo/UBERON_ Dorsal root ganglia dorsal root ganglion http://purl.obolibrary.org/obo/UBERON_0000044 organism part http://www.ebi.ac.uk/efo/EFO_0000635 dorsal root ganglion dorsal root ganglion http://purl.obolibrary.org/obo/UBERON_0000044 organism part http://www.ebi.ac.uk/efo/EFO_0000635 dorsal skin skin of back http://purl.obolibrary.org/obo/UBERON_0001068 organism part http://www.ebi.ac.uk/efo/EFO_0000635 -dorsolateral prefrontal cortex dorsalateral prefrontal cortex http://purl.obolibrary.org/obo/UBERON_0009834 organism part http://www.ebi.ac.uk/efo/EFO_0000635 +dorsolateral prefrontal cortex dorsolateral prefrontal cortex http://purl.obolibrary.org/obo/UBERON_0009834 organism part http://www.ebi.ac.uk/efo/EFO_0000635 doxycycline doxycycline http://purl.obolibrary.org/obo/CHEBI_50845 treatment http://www.ebi.ac.uk/efo/EFO_0000727 DRG dorsal root ganglion http://purl.obolibrary.org/obo/UBERON_0000044 organism part http://www.ebi.ac.uk/efo/EFO_0000635 EBV-transformed B lymphocytes lymphoblastoid cell line http://www.ebi.ac.uk/efo/EFO_0005292 cell line http://purl.obolibrary.org/obo/CLO_0000031 @@ -203,7 +201,7 @@ embryo embryo stage http://purl.obolibrary.org/obo/UBERON_0000068 developmental embryonic fibroblast embryonic fibroblast http://purl.obolibrary.org/obo/CL_2000042 cell type http://www.ebi.ac.uk/efo/EFO_0000324 embryonic fibroblasts embryonic fibroblast http://purl.obolibrary.org/obo/CL_2000042 cell type http://www.ebi.ac.uk/efo/EFO_0000324 embryonic stem cell embryonic stem cell http://purl.obolibrary.org/obo/CL_0002322 cell type http://www.ebi.ac.uk/efo/EFO_0000324 -Embryonic stem cells embyronic stem cell http://purl.obolibrary.org/obo/CL_0002322 cell type http://www.ebi.ac.uk/efo/EFO_0000324 +Embryonic stem cells embryonic stem cell http://purl.obolibrary.org/obo/CL_0002322 cell type http://www.ebi.ac.uk/efo/EFO_0000324 endometrium endometrium http://purl.obolibrary.org/obo/UBERON_0001295 organism part http://www.ebi.ac.uk/efo/EFO_0000635 endothelial cell endothelial cell http://purl.obolibrary.org/obo/CL_0000115 cell type http://www.ebi.ac.uk/efo/EFO_0000324 Entorhinal Cortex entorhinal cortex http://purl.obolibrary.org/obo/UBERON_0002728 organism part http://www.ebi.ac.uk/efo/EFO_0000635 @@ -246,8 +244,7 @@ frozen tissue of ovarian tumors ovarian cancer http://purl.obolibrary.org/obo/MO frozen tissue of primary breast tumors breast cancer http://purl.obolibrary.org/obo/MONDO_0007254 disease http://www.ebi.ac.uk/efo/EFO_0000408 full-thickness colon colon http://purl.obolibrary.org/obo/UBERON_0001155 organism part http://www.ebi.ac.uk/efo/EFO_0000635 fvb FVB http://www.ebi.ac.uk/efo/EFO_0022483 strain http://www.ebi.ac.uk/efo/EFO_0005135 -FVB FVB http://www.ebi.ac.uk/efo/EFO_0022483 strain http://www.ebi.ac.uk/efo/EFO_0005135 -fvb/n FVB/N hhttp://www.ebi.ac.uk/efo/EFO_0022467 strain http://www.ebi.ac.uk/efo/EFO_0005137 +fvb/n FVB/N http://www.ebi.ac.uk/efo/EFO_0022467 strain http://www.ebi.ac.uk/efo/EFO_0005137 fvb/nj FVB/NJ http://www.ebi.ac.uk/efo/EFO_0007728 strain http://www.ebi.ac.uk/efo/EFO_0005136 gactrocnemius muscle gastrocnemius http://purl.obolibrary.org/obo/UBERON_0001388 organism part http://www.ebi.ac.uk/efo/EFO_0000635 gastric adenocarcinoma gastric adenocarcinoma http://purl.obolibrary.org/obo/MONDO_0005036 disease http://www.ebi.ac.uk/efo/EFO_0000408 @@ -269,8 +266,8 @@ Healthy volunteer reference subject role http://purl.obolibrary.org/obo/OBI_0000 healthy volunteers reference subject role http://purl.obolibrary.org/obo/OBI_0000220 disease http://www.ebi.ac.uk/efo/EFO_0000408 Heart heart http://purl.obolibrary.org/obo/UBERON_0000948 organism part http://www.ebi.ac.uk/efo/EFO_0000635 heart left ventricle cardiac ventricle http://purl.obolibrary.org/obo/UBERON_0002082 organism part http://www.ebi.ac.uk/efo/EFO_0000635 -HEK293T RCB2202 cell http://purl.obolibrary.org/obo/CLO_0050894 cell line http://purl.obolibrary.org/obo/CLO_0000031 -HeLa HeLa http://purl.obolibrary.org/obo/CLO_0050910 cell line http://purl.obolibrary.org/obo/CLO_0000031 +HEK293T HEK293T cell http://purl.obolibrary.org/obo/CLO_0037372 cell line http://purl.obolibrary.org/obo/CLO_0000031 +HeLa HeLa cell http://purl.obolibrary.org/obo/CLO_0003684 cell line http://purl.obolibrary.org/obo/CLO_0000031 hepatocellular carcinoma hepatocellular carcinoma http://purl.obolibrary.org/obo/MONDO_0007256 disease http://www.ebi.ac.uk/efo/EFO_0000408 hepatocellular carcinoma (HCC) hepatocellular carcinoma http://purl.obolibrary.org/obo/MONDO_0007256 disease http://www.ebi.ac.uk/efo/EFO_0000408 hepatocyte hepatocyte http://purl.obolibrary.org/obo/CL_0000182 cell type http://www.ebi.ac.uk/efo/EFO_0000324 @@ -365,9 +362,9 @@ Major Depression major depressive disorder http://purl.obolibrary.org/obo/MONDO_ Major Depressive Disorder major depressive disorder http://purl.obolibrary.org/obo/MONDO_0002009 disease http://www.ebi.ac.uk/efo/EFO_0000408 major depressive disorder (MDD) major depressive disorder http://purl.obolibrary.org/obo/MONDO_0002009 disease http://www.ebi.ac.uk/efo/EFO_0000408 male male http://purl.obolibrary.org/obo/PATO_0000384 biological sex http://purl.obolibrary.org/obo/PATO_0000047 -Male C57BL/6J C57BL/6J http://www.ebi.ac.uk/efo/EFO_0005138 strain http://www.ebi.ac.uk/efo/EFO_0005135 +Male C57BL/6J C57BL/6J http://www.ebi.ac.uk/efo/EFO_0000606 strain http://www.ebi.ac.uk/efo/EFO_0005135 Males male http://purl.obolibrary.org/obo/PATO_0000384 biological sex http://purl.obolibrary.org/obo/PATO_0000047 -malignant malignant http://purl.obolibrary.org/obo/PATO_0002097 disease http://www.ebi.ac.uk/efo/EFO_0000408 +malignant neoplastic, malignant http://purl.obolibrary.org/obo/PATO_0002097 disease http://www.ebi.ac.uk/efo/EFO_0000408 Malignant ovarian ovarian cancer http://purl.obolibrary.org/obo/MONDO_0008170 disease http://www.ebi.ac.uk/efo/EFO_0000408 Mammary fat pad mammary fat pad http://purl.obolibrary.org/obo/UBERON_0012282 organism part http://www.ebi.ac.uk/efo/EFO_0000635 mammary gland mammary gland http://purl.obolibrary.org/obo/UBERON_0001911 organism part http://www.ebi.ac.uk/efo/EFO_0000635 @@ -403,7 +400,6 @@ midbrain midbrain http://purl.obolibrary.org/obo/UBERON_0001891 organism part ht middle ear middle ear http://purl.obolibrary.org/obo/UBERON_0001756 organism part http://www.ebi.ac.uk/efo/EFO_0000635 MKN45 RCB1001 cell http://purl.obolibrary.org/obo/CLO_0050797 cell line http://purl.obolibrary.org/obo/CLO_0000031 mock infected control http://www.ebi.ac.uk/efo/EFO_0001461 treatment http://www.ebi.ac.uk/efo/EFO_0000727 -Mock Infected control http://www.ebi.ac.uk/efo/EFO_0001461 treatment http://www.ebi.ac.uk/efo/EFO_0000727 moderate-to-severe psoriasis psoriasis http://purl.obolibrary.org/obo/MONDO_0005083 disease http://www.ebi.ac.uk/efo/EFO_0000408 molecule subtype: total RNA total RNA http://www.ebi.ac.uk/efo/EFO_0004964 collection of material http://www.ebi.ac.uk/efo/EFO_0005066 monocyte monocyte http://purl.obolibrary.org/obo/CL_0000576 cell type http://www.ebi.ac.uk/efo/EFO_0000324 @@ -412,7 +408,7 @@ monocyte-derived macrophage macrophage http://purl.obolibrary.org/obo/CL_0000235 Monocyte-derived macrophages macrophage http://purl.obolibrary.org/obo/CL_0000235 cell type http://www.ebi.ac.uk/efo/EFO_0000324 monocytes monocyte http://purl.obolibrary.org/obo/CL_0000576 cell type http://www.ebi.ac.uk/efo/EFO_0000324 mononuclear cells from bone marrow mononuclear cell of bone marrow http://purl.obolibrary.org/obo/CL_0010004 cell type http://www.ebi.ac.uk/efo/EFO_0000324 -Motor Cortex motor cortex http://purl.obolibrary.org/obo/UBERON_0001384 organism part http://www.ebi.ac.uk/efo/EFO_0000635 +Motor Cortex primary motor cortex http://purl.obolibrary.org/obo/UBERON_0001384 organism part http://www.ebi.ac.uk/efo/EFO_0000635 multiple myeloma plasma cell myeloma http://purl.obolibrary.org/obo/MONDO_0009693 disease http://www.ebi.ac.uk/efo/EFO_0000408 multiple sclerosis multiple sclerosis http://purl.obolibrary.org/obo/MONDO_0005301 disease http://www.ebi.ac.uk/efo/EFO_0000408 musculus vastus lateralis vastus lateralis http://purl.obolibrary.org/obo/UBERON_0001379 organism part http://www.ebi.ac.uk/efo/EFO_0000635 @@ -428,8 +424,8 @@ neocortex neocortex http://purl.obolibrary.org/obo/UBERON_0001950 organism part neonatal neonate http://www.ebi.ac.uk/efo/EFO_0001372 developmental stage http://www.ebi.ac.uk/efo/EFO_0000399 neonatal foreskin prepuce of penis http://purl.obolibrary.org/obo/UBERON_0001332 organism part http://www.ebi.ac.uk/efo/EFO_0000635 neonate neonate http://www.ebi.ac.uk/efo/EFO_0001372 developmental stage http://www.ebi.ac.uk/efo/EFO_0000399 -Neural Progenitor Cells neuroblast http://purl.obolibrary.org/obo/CL_0000031 cell type http://www.ebi.ac.uk/efo/EFO_0000324 -Neural stem cells neuronal stem cell http://purl.obolibrary.org/obo/CL_0000047 cell type http://www.ebi.ac.uk/efo/EFO_0000324 +Neural Progenitor Cells neuroblast (sensu Vertebrata) http://purl.obolibrary.org/obo/CL_0000031 cell type http://www.ebi.ac.uk/efo/EFO_0000324 +Neural stem cells neural stem cell http://purl.obolibrary.org/obo/CL_0000047 cell type http://www.ebi.ac.uk/efo/EFO_0000324 neuroblastoma neuroblastoma http://purl.obolibrary.org/obo/MONDO_0005072 disease http://www.ebi.ac.uk/efo/EFO_0000408 Neuroblastoma tumor obtained at diagnosis neuroblastoma http://purl.obolibrary.org/obo/MONDO_0005072 disease http://www.ebi.ac.uk/efo/EFO_0000408 Neuron neuron http://purl.obolibrary.org/obo/CL_0000540 cell type http://www.ebi.ac.uk/efo/EFO_0000324 @@ -456,11 +452,11 @@ osteosarcoma osteosarcoma http://purl.obolibrary.org/obo/MONDO_0009807 disease h osteosarcoma cells osteosarcoma http://purl.obolibrary.org/obo/MONDO_0009807 disease http://www.ebi.ac.uk/efo/EFO_0000408 ovarian carcinoma tumor ovarian carcinoma http://purl.obolibrary.org/obo/MONDO_0005140 disease http://www.ebi.ac.uk/efo/EFO_0000408 ovarian tumors ovarian cancer http://purl.obolibrary.org/obo/MONDO_0008170 disease http://www.ebi.ac.uk/efo/EFO_0000408 -ovary female gonad http://purl.obolibrary.org/obo/UBERON_0000992 organism part http://www.ebi.ac.uk/efo/EFO_0000635 +ovary ovary http://purl.obolibrary.org/obo/UBERON_0000992 organism part http://www.ebi.ac.uk/efo/EFO_0000635 Paclitaxel paclitaxel http://purl.obolibrary.org/obo/CHEBI_45863 treatment http://www.ebi.ac.uk/efo/EFO_0000727 pancreas pancreas http://purl.obolibrary.org/obo/UBERON_0001264 organism part http://www.ebi.ac.uk/efo/EFO_0000635 Pancreas Tissue pancreas http://purl.obolibrary.org/obo/UBERON_0001264 organism part http://www.ebi.ac.uk/efo/EFO_0000635 -pancreatic islets Islet of Langerhans http://purl.obolibrary.org/obo/UBERON_0000006 organism part http://www.ebi.ac.uk/efo/EFO_0000635 +pancreatic islets islet of Langerhans http://purl.obolibrary.org/obo/UBERON_0000006 organism part http://www.ebi.ac.uk/efo/EFO_0000635 Pancreatic Tumor malignant pancreatic neoplasm http://purl.obolibrary.org/obo/MONDO_0009831 disease http://www.ebi.ac.uk/efo/EFO_0000408 papillary serous ovarian adenocarcinoma ovarian serous adenocarcinoma http://purl.obolibrary.org/obo/MONDO_0005211 disease http://www.ebi.ac.uk/efo/EFO_0000408 Papillary Thyroid Carcinoma thyroid gland papillary carcinoma http://purl.obolibrary.org/obo/MONDO_0005075 disease http://www.ebi.ac.uk/efo/EFO_0000408 @@ -532,11 +528,10 @@ PTCL mature T-cell and NK-cell non-Hodgkin lymphoma http://purl.obolibrary.org/o pulmonary epithelial cells epithelial cell of lung http://purl.obolibrary.org/obo/CL_0000082 cell type http://www.ebi.ac.uk/efo/EFO_0000324 Purkinje Cells Purkinje cell http://purl.obolibrary.org/obo/CL_0000121 cell type http://www.ebi.ac.uk/efo/EFO_0000324 putamen putamen http://purl.obolibrary.org/obo/UBERON_0001874 organism part http://www.ebi.ac.uk/efo/EFO_0000635 -putamen putamen http://purl.obolibrary.org/obo/UBERON_0001874 organism part http://www.ebi.ac.uk/efo/EFO_0000635 pyramidal neuron cell pyramidal neuron http://purl.obolibrary.org/obo/CL_0000598 cell type http://www.ebi.ac.uk/efo/EFO_0000324 Quadriceps quadriceps femoris http://purl.obolibrary.org/obo/UBERON_0001377 organism part http://www.ebi.ac.uk/efo/EFO_0000635 quadriceps muscle quadriceps femoris http://purl.obolibrary.org/obo/UBERON_0001377 organism part http://www.ebi.ac.uk/efo/EFO_0000635 -rat adipose tissue adipsoe tissue http://purl.obolibrary.org/obo/UBERON_0001013 organism part http://www.ebi.ac.uk/efo/EFO_0000635 +rat adipose tissue adipose tissue http://purl.obolibrary.org/obo/UBERON_0001013 organism part http://www.ebi.ac.uk/efo/EFO_0000635 Rat gastrocnemius muscle gastrocnemius http://purl.obolibrary.org/obo/UBERON_0001388 organism part http://www.ebi.ac.uk/efo/EFO_0000635 Rat Raphe raphe nuclei http://purl.obolibrary.org/obo/UBERON_0004684 organism part http://www.ebi.ac.uk/efo/EFO_0000635 Rat Somatomotor Cortex primary motor cortex http://purl.obolibrary.org/obo/UBERON_0001384 organism part http://www.ebi.ac.uk/efo/EFO_0000635 @@ -567,7 +562,7 @@ Sepsis sepsis http://purl.obolibrary.org/obo/MP_0005044 disease http://www.ebi.a Septum septum http://purl.obolibrary.org/obo/UBERON_0003037 organism part http://www.ebi.ac.uk/efo/EFO_0000635 SH-SY5Y SH-SY5Y cell http://purl.obolibrary.org/obo/CLO_0009015 cell line http://purl.obolibrary.org/obo/CLO_0000031 Sjögrens syndrome Sjogren syndrome http://purl.obolibrary.org/obo/MONDO_0010030 disease http://www.ebi.ac.uk/efo/EFO_0000408 -skeletal muscle skeletal muscle http://purl.obolibrary.org/obo/UBERON_0001134 organism part http://www.ebi.ac.uk/efo/EFO_0000635 +skeletal muscle skeletal muscle tissue http://purl.obolibrary.org/obo/UBERON_0001134 organism part http://www.ebi.ac.uk/efo/EFO_0000635 Skeletal muscle biopsy skeletal muscle tissue http://purl.obolibrary.org/obo/UBERON_0001134 organism part http://www.ebi.ac.uk/efo/EFO_0000635 skin skin of body http://purl.obolibrary.org/obo/UBERON_0002097 organism part http://www.ebi.ac.uk/efo/EFO_0000635 Skin punch biopsy skin of body http://purl.obolibrary.org/obo/UBERON_0002097 organism part http://www.ebi.ac.uk/efo/EFO_0000635 @@ -588,16 +583,16 @@ sprague dawley Sprague Dawley http://www.ebi.ac.uk/efo/EFO_0001352 strain http:/ Sprague-Dawley Sprague Dawley http://www.ebi.ac.uk/efo/EFO_0001352 strain http://www.ebi.ac.uk/efo/EFO_0005135 Sprague-Dawley (SD) Sprague Dawley http://www.ebi.ac.uk/efo/EFO_0001352 strain http://www.ebi.ac.uk/efo/EFO_0005135 Sprague-Dawley rat Sprague Dawley http://www.ebi.ac.uk/efo/EFO_0001352 strain http://www.ebi.ac.uk/efo/EFO_0005135 -Sprague-Dawley rats Sprague Dawley http://www.ebi.ac.uk/efo/EFO_0001352 strain http://www.ebi.ac.uk/efo/EFO_0005135 +Sprague-Dawley rats Sprague Dawley http://www.ebi.ac.uk/efo/EFO_0001352 strain http://www.ebi.ac.uk/efo/EFO_0005135 sputum sputum http://purl.obolibrary.org/obo/UBERON_0007311 organism part http://www.ebi.ac.uk/efo/EFO_0000635 Squamous cell carcinoma squamous cell carcinoma http://purl.obolibrary.org/obo/MONDO_0005096 disease http://www.ebi.ac.uk/efo/EFO_0000408 sscortex secondary somatosensory cortex http://purl.obolibrary.org/obo/UBERON_0008934 organism part http://www.ebi.ac.uk/efo/EFO_0000635 stem cells stem cell http://purl.obolibrary.org/obo/CL_0000034 cell type http://www.ebi.ac.uk/efo/EFO_0000324 -stress environmental stress http://www.ebi.ac.uk/efo/EFO_0000470 environmental history http://www.ebi.ac.uk/efo/EFO_0004444 +stress environmental stress treatment http://www.ebi.ac.uk/efo/EFO_0000470 environmental history http://www.ebi.ac.uk/efo/EFO_0004444 striatum striatum http://purl.obolibrary.org/obo/UBERON_0002435 organism part http://www.ebi.ac.uk/efo/EFO_0000635 -stroke stroke http://purl.obolibrary.org/obo/MONDO_0005098 disease http://www.ebi.ac.uk/efo/EFO_0000408 +stroke stroke disorder http://purl.obolibrary.org/obo/MONDO_0005098 disease http://www.ebi.ac.uk/efo/EFO_0000408 subcortical white matter white matter http://purl.obolibrary.org/obo/UBERON_0002316 organism part http://www.ebi.ac.uk/efo/EFO_0000635 -subcutaneous adipose subcutaneous adipose http://purl.obolibrary.org/obo/UBERON_0002190 organism part http://www.ebi.ac.uk/efo/EFO_0000635 +subcutaneous adipose subcutaneous adipose tissue http://purl.obolibrary.org/obo/UBERON_0002190 organism part http://www.ebi.ac.uk/efo/EFO_0000635 Subcutaneous Fat subcutaneous adipose tissue http://purl.obolibrary.org/obo/UBERON_0002190 organism part http://www.ebi.ac.uk/efo/EFO_0000635 Substantia nigra substantia nigra http://purl.obolibrary.org/obo/UBERON_0002038 organism part http://www.ebi.ac.uk/efo/EFO_0000635 superior cervical ganglia (SCG) superior cervical ganglion http://purl.obolibrary.org/obo/UBERON_0001989 organism part http://www.ebi.ac.uk/efo/EFO_0000635 @@ -624,10 +619,9 @@ thyroid thyroid gland http://purl.obolibrary.org/obo/UBERON_0002046 organism par Tibialis anterior muscle tibialis anterior http://purl.obolibrary.org/obo/UBERON_0001385 organism part http://www.ebi.ac.uk/efo/EFO_0000635 TK6 TK6 cell http://purl.obolibrary.org/obo/CLO_0009357 cell line http://purl.obolibrary.org/obo/CLO_0000031 total RNA total RNA http://www.ebi.ac.uk/efo/EFO_0004964 collection of material http://purl.obolibrary.org/obo/CL_0000233 -trabecular bone trabecular bone http://purl.obolibrary.org/obo/UBERON_0002483 organism part http://www.ebi.ac.uk/efo/EFO_0000635 +trabecular bone trabecular bone tissue http://purl.obolibrary.org/obo/UBERON_0002483 organism part http://www.ebi.ac.uk/efo/EFO_0000635 trachea trachea http://purl.obolibrary.org/obo/UBERON_0003126 organism part http://www.ebi.ac.uk/efo/EFO_0000635 triple-negative breast cancer triple-negative breast carcinoma http://purl.obolibrary.org/obo/MONDO_0005494 disease http://www.ebi.ac.uk/efo/EFO_0000408 -triple-negative breast cancer triple-negative breast carcinoma http://purl.obolibrary.org/obo/MONDO_0005494 disease http://www.ebi.ac.uk/efo/EFO_0000408 Tuberculosis tuberculosis http://purl.obolibrary.org/obo/MONDO_0018076 disease http://www.ebi.ac.uk/efo/EFO_0000408 U2OS U-2 OS cell http://purl.obolibrary.org/obo/CLO_0009454 cell line http://purl.obolibrary.org/obo/CLO_0000031 U937 U937(CD59+) cell http://purl.obolibrary.org/obo/CLO_0009466 cell line http://purl.obolibrary.org/obo/CLO_0000031 @@ -642,7 +636,7 @@ urinary bladder urinary bladder http://purl.obolibrary.org/obo/UBERON_0001255 or uterus uterus http://purl.obolibrary.org/obo/UBERON_0000995 organism part http://www.ebi.ac.uk/efo/EFO_0000635 vastus lateralis vastus lateralis http://purl.obolibrary.org/obo/UBERON_0001379 organism part http://www.ebi.ac.uk/efo/EFO_0000635 vastus lateralis (quadriceps femoris) vastus lateralis http://purl.obolibrary.org/obo/UBERON_0001379 organism part http://www.ebi.ac.uk/efo/EFO_0000635 -vastus lateralis muscle vastus lateralis muscle http://purl.obolibrary.org/obo/UBERON_0001379 organism part http://www.ebi.ac.uk/efo/EFO_0000635 +vastus lateralis muscle vastus lateralis http://purl.obolibrary.org/obo/UBERON_0001379 organism part http://www.ebi.ac.uk/efo/EFO_0000635 vehicle control control http://www.ebi.ac.uk/efo/EFO_0001461 treatment http://www.ebi.ac.uk/efo/EFO_0000727 ventral midbrain midbrain http://purl.obolibrary.org/obo/UBERON_0001891 organism part http://www.ebi.ac.uk/efo/EFO_0000635 Ventricles cardiac ventricle http://purl.obolibrary.org/obo/UBERON_0002082 organism part http://www.ebi.ac.uk/efo/EFO_0000635 @@ -653,7 +647,7 @@ w European http://purl.obolibrary.org/obo/HANCESTRO_0005 population http://purl. WBC leukocyte http://purl.obolibrary.org/obo/CL_0000738 cell type http://www.ebi.ac.uk/efo/EFO_0000324 White European http://purl.obolibrary.org/obo/HANCESTRO_0005 population http://purl.obolibrary.org/obo/OBI_0000181 White Adipose Tissue white adipose tissue http://purl.obolibrary.org/obo/UBERON_0001347 organism part http://www.ebi.ac.uk/efo/EFO_0000635 -white blood cells white blood cell http://purl.obolibrary.org/obo/CL_0000738 cell type http://www.ebi.ac.uk/efo/EFO_0000324 +white blood cells leukocyte http://purl.obolibrary.org/obo/CL_0000738 cell type http://www.ebi.ac.uk/efo/EFO_0000324 white matter white matter http://purl.obolibrary.org/obo/UBERON_0002316 organism part http://www.ebi.ac.uk/efo/EFO_0000635 Whole animal body proper http://purl.obolibrary.org/obo/UBERON_0013702 organism part http://www.ebi.ac.uk/efo/EFO_0000635 whole blood blood http://purl.obolibrary.org/obo/UBERON_0000178 organism part http://www.ebi.ac.uk/efo/EFO_0000635 diff --git a/gemma-core/src/main/resources/ubic/gemma/model/analysis/Investigation.hbm.xml b/gemma-core/src/main/resources/ubic/gemma/model/analysis/Investigation.hbm.xml index 30f57a2867..e30bb299a6 100644 --- a/gemma-core/src/main/resources/ubic/gemma/model/analysis/Investigation.hbm.xml +++ b/gemma-core/src/main/resources/ubic/gemma/model/analysis/Investigation.hbm.xml @@ -68,9 +68,18 @@ - + + + + + ubic.gemma.model.expression.experiment.BatchEffectType + true + + + + - diff --git a/gemma-core/src/main/resources/ubic/gemma/model/common/description/Characteristic.hbm.xml b/gemma-core/src/main/resources/ubic/gemma/model/common/description/Characteristic.hbm.xml index e73d053f8e..ca8e1c6548 100644 --- a/gemma-core/src/main/resources/ubic/gemma/model/common/description/Characteristic.hbm.xml +++ b/gemma-core/src/main/resources/ubic/gemma/model/common/description/Characteristic.hbm.xml @@ -1,12 +1,12 @@ + "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> - + @@ -49,5 +49,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gemma-core/src/main/resources/ubic/gemma/model/expression/bioAssay/BioAssay.hbm.xml b/gemma-core/src/main/resources/ubic/gemma/model/expression/bioAssay/BioAssay.hbm.xml index 2a04eb5651..f9dfdd0f4d 100644 --- a/gemma-core/src/main/resources/ubic/gemma/model/expression/bioAssay/BioAssay.hbm.xml +++ b/gemma-core/src/main/resources/ubic/gemma/model/expression/bioAssay/BioAssay.hbm.xml @@ -1,11 +1,11 @@ + "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> - + @@ -36,21 +36,25 @@ - + - + - + - + - - - + + + diff --git a/gemma-core/src/main/resources/ubic/gemma/model/expression/biomaterial/BioMaterial.hbm.xml b/gemma-core/src/main/resources/ubic/gemma/model/expression/biomaterial/BioMaterial.hbm.xml index 741d640a09..413103ffe9 100644 --- a/gemma-core/src/main/resources/ubic/gemma/model/expression/biomaterial/BioMaterial.hbm.xml +++ b/gemma-core/src/main/resources/ubic/gemma/model/expression/biomaterial/BioMaterial.hbm.xml @@ -1,11 +1,11 @@ - + + "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> - + @@ -17,40 +17,42 @@ - + - + - + - + - + - + - + diff --git a/gemma-core/src/main/resources/ubic/gemma/model/expression/experiment/FactorValue.hbm.xml b/gemma-core/src/main/resources/ubic/gemma/model/expression/experiment/FactorValue.hbm.xml index 4265406d32..8fad5e4466 100644 --- a/gemma-core/src/main/resources/ubic/gemma/model/expression/experiment/FactorValue.hbm.xml +++ b/gemma-core/src/main/resources/ubic/gemma/model/expression/experiment/FactorValue.hbm.xml @@ -17,9 +17,11 @@ - + + + + cascade="none" lazy="proxy" fetch="select"> @@ -28,17 +30,18 @@ - - + + - + - - - + + + diff --git a/gemma-core/src/main/resources/ubic/gemma/model/genome/ChromosomeFeature.hbm.xml b/gemma-core/src/main/resources/ubic/gemma/model/genome/ChromosomeFeature.hbm.xml index 10c0070ecf..9d59919c3c 100644 --- a/gemma-core/src/main/resources/ubic/gemma/model/genome/ChromosomeFeature.hbm.xml +++ b/gemma-core/src/main/resources/ubic/gemma/model/genome/ChromosomeFeature.hbm.xml @@ -35,6 +35,9 @@ + + + @@ -64,7 +67,8 @@ - + + diff --git a/gemma-core/src/test/java/ubic/gemma/core/analysis/expression/diff/BaseAnalyzerConfigurationTest.java b/gemma-core/src/test/java/ubic/gemma/core/analysis/expression/diff/BaseAnalyzerConfigurationTest.java index 6be9288621..f010841e3b 100644 --- a/gemma-core/src/test/java/ubic/gemma/core/analysis/expression/diff/BaseAnalyzerConfigurationTest.java +++ b/gemma-core/src/test/java/ubic/gemma/core/analysis/expression/diff/BaseAnalyzerConfigurationTest.java @@ -18,7 +18,6 @@ */ package ubic.gemma.core.analysis.expression.diff; -import lombok.SneakyThrows; import org.apache.commons.lang3.RandomStringUtils; import org.junit.After; import org.junit.Before; @@ -32,7 +31,6 @@ import ubic.gemma.core.analysis.service.ExpressionDataMatrixService; import ubic.gemma.core.datastructure.matrix.ExpressionDataDoubleMatrix; import ubic.gemma.core.util.test.BaseSpringContextTest; -import ubic.gemma.model.common.description.Characteristic; import ubic.gemma.model.common.quantitationtype.*; import ubic.gemma.model.expression.arrayDesign.ArrayDesign; import ubic.gemma.model.expression.arrayDesign.TechnologyType; @@ -147,9 +145,9 @@ public void setUp() throws Exception { factorValueA1 = FactorValue.Factory.newInstance(); factorValueA1.setId( 1001L ); factorValueA1.setValue( "cerebellum" ); - Characteristic characteristicA1 = Characteristic.Factory.newInstance(); + Statement characteristicA1 = Statement.Factory.newInstance(); characteristicA1.setValue( factorValueA1.getValue() ); - Set characteristicsA1 = new HashSet<>(); + Set characteristicsA1 = new HashSet<>(); characteristicsA1.add( characteristicA1 ); factorValueA1.setCharacteristics( characteristicsA1 ); factorValueA1.setExperimentalFactor( experimentalFactorA_Area ); @@ -158,9 +156,9 @@ public void setUp() throws Exception { factorValueA2.setIsBaseline( true ); factorValueA2.setValue( "amygdala" ); factorValueA2.setId( 1002L ); - Characteristic characteristicA2 = Characteristic.Factory.newInstance(); + Statement characteristicA2 = Statement.Factory.newInstance(); characteristicA2.setValue( factorValueA2.getValue() ); - Set characteristicsA2 = new HashSet<>(); + Set characteristicsA2 = new HashSet<>(); characteristicsA2.add( characteristicA2 ); factorValueA2.setCharacteristics( characteristicsA2 ); factorValueA2.setExperimentalFactor( experimentalFactorA_Area ); @@ -180,9 +178,9 @@ public void setUp() throws Exception { FactorValue factorValueB1 = FactorValue.Factory.newInstance(); factorValueB1.setValue( "pcp" ); factorValueB1.setId( 1003L ); - Characteristic characteristicB1 = Characteristic.Factory.newInstance(); + Statement characteristicB1 = Statement.Factory.newInstance(); characteristicB1.setValue( factorValueB1.getValue() ); - Set characteristicsB1 = new HashSet<>(); + Set characteristicsB1 = new HashSet<>(); characteristicsB1.add( characteristicB1 ); factorValueB1.setCharacteristics( characteristicsB1 ); factorValueB1.setExperimentalFactor( experimentalFactorB ); @@ -190,9 +188,9 @@ public void setUp() throws Exception { factorValueB2 = FactorValue.Factory.newInstance(); factorValueB2.setValue( "control_group" ); factorValueB2.setId( 1004L ); - Characteristic characteristicB2 = Characteristic.Factory.newInstance(); + Statement characteristicB2 = Statement.Factory.newInstance(); characteristicB2.setValue( factorValueB2.getValue() ); - Set characteristicsB2 = new HashSet<>(); + Set characteristicsB2 = new HashSet<>(); characteristicsB2.add( characteristicB2 ); factorValueB2.setCharacteristics( characteristicsB2 ); factorValueB2.setExperimentalFactor( experimentalFactorB ); diff --git a/gemma-core/src/test/java/ubic/gemma/core/analysis/expression/diff/BaselineSelectionTest.java b/gemma-core/src/test/java/ubic/gemma/core/analysis/expression/diff/BaselineSelectionTest.java new file mode 100644 index 0000000000..c3d50682c0 --- /dev/null +++ b/gemma-core/src/test/java/ubic/gemma/core/analysis/expression/diff/BaselineSelectionTest.java @@ -0,0 +1,83 @@ +package ubic.gemma.core.analysis.expression.diff; + +import org.junit.Test; +import ubic.gemma.model.common.measurement.Measurement; +import ubic.gemma.model.expression.experiment.FactorValue; +import ubic.gemma.model.expression.experiment.Statement; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class BaselineSelectionTest { + + @Test + public void testBaseline() { + FactorValue fv = new FactorValue(); + assertFalse( BaselineSelection.isBaselineCondition( fv ) ); + + fv = new FactorValue(); + fv.getCharacteristics().add( createStatement( "control", "http://www.ebi.ac.uk/efo/EFO_0001461" ) ); + assertTrue( BaselineSelection.isBaselineCondition( fv ) ); + + fv = new FactorValue(); + fv.getCharacteristics().add( createStatement( "control", null ) ); + assertTrue( BaselineSelection.isBaselineCondition( fv ) ); + + fv = new FactorValue(); + fv.getCharacteristics().add( createStatement( "CONTROL", null ) ); + assertTrue( BaselineSelection.isBaselineCondition( fv ) ); + + fv = new FactorValue(); + fv.getCharacteristics().add( createStatement( " control ", null ) ); + assertTrue( BaselineSelection.isBaselineCondition( fv ) ); + + fv = new FactorValue(); + fv.getCharacteristics().add( createStatement( " initial time point", null ) ); + assertTrue( BaselineSelection.isBaselineCondition( fv ) ); + + fv = new FactorValue(); + fv.getCharacteristics().add( createStatement( "initial_time_point", null ) ); + assertTrue( BaselineSelection.isBaselineCondition( fv ) ); + + // a "control" term is used, but it's not a control term URI + fv = new FactorValue(); + fv.getCharacteristics().add( createStatement( "control", "http://www.ebi.ac.uk/efo/EFO_0001462" ) ); + assertFalse( BaselineSelection.isBaselineCondition( fv ) ); + + fv = new FactorValue(); + fv.setMeasurement( new Measurement() ); + fv.getCharacteristics().add( createStatement( "control", "http://www.ebi.ac.uk/efo/EFO_0001461" ) ); + assertFalse( BaselineSelection.isBaselineCondition( fv ) ); + } + + @Test + public void testForcedBaseline() { + FactorValue fv = new FactorValue(); + fv.setIsBaseline( true ); + assertTrue( BaselineSelection.isBaselineCondition( fv ) ); + assertTrue( BaselineSelection.isForcedBaseline( fv ) ); + + fv = new FactorValue(); + fv.getCharacteristics().add( createStatement( "control", "http://www.ebi.ac.uk/efo/EFO_0001461" ) ); + assertTrue( BaselineSelection.isBaselineCondition( fv ) ); + assertTrue( BaselineSelection.isForcedBaseline( fv ) ); + + fv = new FactorValue(); + fv.getCharacteristics().add( createStatement( "control", "http://www.ebi.ac.uk/EfO/efo_0001461" ) ); + assertTrue( BaselineSelection.isBaselineCondition( fv ) ); + assertTrue( BaselineSelection.isForcedBaseline( fv ) ); + + fv = new FactorValue(); + fv.setIsBaseline( false ); + fv.getCharacteristics().add( createStatement( "control", "http://www.ebi.ac.uk/efo/EFO_0001461" ) ); + assertFalse( BaselineSelection.isBaselineCondition( fv ) ); + assertFalse( BaselineSelection.isForcedBaseline( fv ) ); + } + + private Statement createStatement( String subject, String subjectUri ) { + Statement s = new Statement(); + s.setSubject( subject ); + s.setSubjectUri( subjectUri ); + return s; + } +} \ No newline at end of file diff --git a/gemma-core/src/test/java/ubic/gemma/core/analysis/preprocess/SplitExperimentTest.java b/gemma-core/src/test/java/ubic/gemma/core/analysis/preprocess/SplitExperimentTest.java index a9e83fed14..7333d51443 100644 --- a/gemma-core/src/test/java/ubic/gemma/core/analysis/preprocess/SplitExperimentTest.java +++ b/gemma-core/src/test/java/ubic/gemma/core/analysis/preprocess/SplitExperimentTest.java @@ -32,17 +32,18 @@ import ubic.gemma.core.util.test.BaseSpringContextTest; import ubic.gemma.core.util.test.category.SlowTest; import ubic.gemma.model.analysis.expression.ExpressionExperimentSet; -import ubic.gemma.model.common.description.Characteristic; +import ubic.gemma.model.expression.bioAssay.BioAssay; import ubic.gemma.model.expression.bioAssayData.ProcessedExpressionDataVector; import ubic.gemma.model.expression.bioAssayData.RawExpressionDataVector; -import ubic.gemma.model.expression.experiment.BioAssaySet; -import ubic.gemma.model.expression.experiment.ExperimentalFactor; -import ubic.gemma.model.expression.experiment.ExpressionExperiment; -import ubic.gemma.model.expression.experiment.FactorValue; +import ubic.gemma.model.expression.biomaterial.BioMaterial; +import ubic.gemma.model.expression.experiment.*; +import ubic.gemma.persistence.service.expression.experiment.BioAssaySetService; import ubic.gemma.persistence.service.expression.experiment.ExpressionExperimentService; import java.io.InputStream; import java.util.Collection; +import java.util.Set; +import java.util.stream.Collectors; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.Assert.*; @@ -70,10 +71,15 @@ public class SplitExperimentTest extends BaseSpringContextTest { @Autowired private ExpressionExperimentService eeService; + @Autowired + private BioAssaySetService bioAssaySetService; + @Autowired private SecurityService securityService; + /* fixtures */ private Collection ees; + private ExpressionExperimentSet results; @Test @Category(SlowTest.class) @@ -117,12 +123,30 @@ public void testSplitGSE17183ByOrganismPart() throws Exception { assertNotNull( splitOn ); - ExpressionExperimentSet results = splitService.split( ee, splitOn, true ); + results = splitService.split( ee, splitOn, true ); + assertEquals( splitOn.getFactorValues().size(), results.getExperiments().size() ); for ( BioAssaySet b : results.getExperiments() ) { ExpressionExperiment e = eeService.thaw( ( ExpressionExperiment ) b ); + // sanity checks for the clones + assertNotEquals( ee.getAccession().getId(), e.getAccession().getId() ); + assertEquals( ee.getTaxon(), e.getTaxon() ); + assertNotEquals( ee.getAuditTrail().getId(), e.getAuditTrail().getId() ); + assertNotEquals( ee.getCurationDetails().getId(), e.getCurationDetails().getId() ); + assertNotEquals( ee.getExperimentalDesign().getId(), e.getExperimentalDesign().getId() ); + assertEquals( ee.getPrimaryPublication(), e.getPrimaryPublication() ); + + // make sure that clones are used for BAs and BMs + Set bms = ee.getBioAssays().stream().map( BioAssay::getSampleUsed ).collect( Collectors.toSet() ); + for ( BioAssay ba : e.getBioAssays() ) { + assertFalse( ee.getBioAssays().contains( ba ) ); + assertFalse( bms.contains( ba.getSampleUsed() ) ); + } + + assertEquals( 100, e.getNumberOfDataVectors().intValue() ); + Collection rvs = e.getRawExpressionDataVectors(); assertEquals( 100, rvs.size() ); @@ -178,7 +202,7 @@ public void testSplitGSE123753ByCollectionOfMaterial() throws Exception { assertNotNull( splitOn ); - ExpressionExperimentSet results = splitService.split( ee, splitOn, false ); + results = splitService.split( ee, splitOn, false ); assertEquals( splitOn.getFactorValues().size(), results.getExperiments().size() ); } @@ -186,8 +210,8 @@ public void testSplitGSE123753ByCollectionOfMaterial() throws Exception { public void testGenerateName() { ExpressionExperiment ee = new ExpressionExperiment(); ee.setName( "test" ); - Characteristic c = new Characteristic(); - c.setValue( "bar" ); + Statement c = new Statement(); + c.setSubject( "bar" ); FactorValue fv = new FactorValue(); fv.getCharacteristics().add( c ); ExperimentalFactor ef = new ExperimentalFactor(); @@ -199,7 +223,7 @@ public void testGenerateName() { assertEquals( 255, name.length() ); assertEquals( "Split part 1 of: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa… [foo = bar]", name ); ee.setName( "test" ); - c.setValue( String.join( "", java.util.Collections.nCopies( 255, "a" ) ) ); + c.setSubject( String.join( "", java.util.Collections.nCopies( 255, "a" ) ) ); assertThatThrownBy( () -> SplitExperimentServiceImpl.generateNameForSplit( ee, 1, fv ) ) .isInstanceOf( IllegalArgumentException.class ) .hasMessageContaining( "It's not possible to truncate the name of the split such that it won't exceed 255 characters." ); @@ -207,15 +231,26 @@ public void testGenerateName() { // make sure that whitespaces before the ellipsis are trimmed int lengthOfEverythingElse = "Split part 1 of: [foo = bar]".length(); ee.setName( String.join( "", java.util.Collections.nCopies( 255 - lengthOfEverythingElse - 1, "a" ) ) + " " ); - c.setValue( "bar" ); + c.setSubject( "bar" ); assertEquals( 254, SplitExperimentServiceImpl.generateNameForSplit( ee, 1, fv ).length() ); assertEquals( "Split part 1 of: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa… [foo = bar]", name ); } @After public void teardown() throws Exception { + // remove original dataset if ( ees != null ) { - eeService.remove( ees ); + bioAssaySetService.remove( ees ); + } + // remove any created subsets + if ( results != null ) { + for ( BioAssaySet b : results.getExperiments() ) { + if ( b instanceof ExpressionExperiment ) { + bioAssaySetService.remove( b ); + } else if ( b instanceof ExpressionExperimentSubSet ) { + bioAssaySetService.remove( b ); + } + } } } } diff --git a/gemma-core/src/test/java/ubic/gemma/core/analysis/report/ExpressionExperimentReportServiceTest.java b/gemma-core/src/test/java/ubic/gemma/core/analysis/report/ExpressionExperimentReportServiceTest.java index 109bba85d4..e413e4edba 100644 --- a/gemma-core/src/test/java/ubic/gemma/core/analysis/report/ExpressionExperimentReportServiceTest.java +++ b/gemma-core/src/test/java/ubic/gemma/core/analysis/report/ExpressionExperimentReportServiceTest.java @@ -8,6 +8,7 @@ import org.springframework.security.core.context.SecurityContextHolder; import ubic.gemma.core.util.test.BaseSpringContextTest; import ubic.gemma.core.util.test.category.SlowTest; +import ubic.gemma.model.expression.experiment.BatchEffectType; import ubic.gemma.model.expression.experiment.ExpressionExperiment; import ubic.gemma.persistence.service.expression.experiment.ExpressionExperimentService; @@ -48,7 +49,7 @@ public void testRecalculateBatchInfo() { SecurityContextHolder.setContext( previousContext ); } ee = expressionExperimentService.thawLite( ee ); - assertEquals( "NO_BATCH_INFO", ee.getBatchEffect() ); + assertEquals( BatchEffectType.NO_BATCH_INFO, ee.getBatchEffect() ); assertNull( ee.getBatchConfound() ); } @@ -64,7 +65,7 @@ public void testRecalculateExperimentBatchInfo() { SecurityContextHolder.setContext( previousContext ); } ee = expressionExperimentService.thawLite( ee ); - assertEquals( "NO_BATCH_INFO", ee.getBatchEffect() ); + assertEquals( BatchEffectType.NO_BATCH_INFO, ee.getBatchEffect() ); assertNull( ee.getBatchConfound() ); } @@ -80,7 +81,7 @@ public void testRecalculateBatchInfoWithMissingDesign() { SecurityContextHolder.setContext( previousContext ); } ee = expressionExperimentService.thawLite( ee ); - assertEquals( "NO_BATCH_INFO", ee.getBatchEffect() ); + assertEquals( BatchEffectType.NO_BATCH_INFO, ee.getBatchEffect() ); assertNull( ee.getBatchConfound() ); } } \ No newline at end of file diff --git a/gemma-core/src/test/java/ubic/gemma/core/analysis/service/GeneMultifunctionalityPopulationServiceTest.java b/gemma-core/src/test/java/ubic/gemma/core/analysis/service/GeneMultifunctionalityPopulationServiceTest.java index c015b7a727..1fafbce1bf 100644 --- a/gemma-core/src/test/java/ubic/gemma/core/analysis/service/GeneMultifunctionalityPopulationServiceTest.java +++ b/gemma-core/src/test/java/ubic/gemma/core/analysis/service/GeneMultifunctionalityPopulationServiceTest.java @@ -30,7 +30,6 @@ import org.springframework.test.annotation.DirtiesContext; import ubic.gemma.core.genome.gene.service.GeneService; import ubic.gemma.core.ontology.providers.GeneOntologyService; -import ubic.gemma.core.ontology.OntologyTestUtils; import ubic.gemma.core.util.test.BaseSpringContextTest; import ubic.gemma.core.util.test.category.SlowTest; import ubic.gemma.model.association.GOEvidenceCode; @@ -46,6 +45,8 @@ import ubic.gemma.persistence.service.association.Gene2GOAssociationService; import ubic.gemma.persistence.service.common.description.ExternalDatabaseService; +import java.io.IOException; +import java.io.InputStream; import java.util.Collection; import java.util.Date; import java.util.List; @@ -87,8 +88,11 @@ public void tearDown() { @Before public void setUp() throws Exception { - OntologyTestUtils.initialize( goService, new GZIPInputStream( - new ClassPathResource( "/data/loader/ontology/molecular-function.test.owl.gz" ).getInputStream() ) ); + // force-load specific terms to get consistent results, this dirites the context + try ( InputStream stream = new GZIPInputStream( + new ClassPathResource( "/data/loader/ontology/molecular-function.test.owl.gz" ).getInputStream() ) ) { + goService.initialize( stream, false ); + } testTaxon = taxonService.findOrCreate( Taxon.Factory .newInstance( "foobly" + RandomStringUtils.randomAlphabetic( 2 ), diff --git a/gemma-core/src/test/java/ubic/gemma/core/datastructure/matrix/DetectFactorBaselineTest.java b/gemma-core/src/test/java/ubic/gemma/core/datastructure/matrix/DetectFactorBaselineTest.java index 8c6b65040d..6cabbc41b5 100644 --- a/gemma-core/src/test/java/ubic/gemma/core/datastructure/matrix/DetectFactorBaselineTest.java +++ b/gemma-core/src/test/java/ubic/gemma/core/datastructure/matrix/DetectFactorBaselineTest.java @@ -16,8 +16,8 @@ import org.junit.Test; import ubic.gemma.core.analysis.expression.diff.BaselineSelection; -import ubic.gemma.model.common.description.Characteristic; import ubic.gemma.model.expression.experiment.FactorValue; +import ubic.gemma.model.expression.experiment.Statement; import static org.junit.Assert.assertTrue; @@ -32,7 +32,7 @@ public void testIsBaselineA() { FactorValue fv = FactorValue.Factory.newInstance(); fv.setValue( "fv" ); - Characteristic c = Characteristic.Factory.newInstance(); + Statement c = Statement.Factory.newInstance(); c.setValue( "control_group" ); fv.getCharacteristics().add( c ); @@ -47,7 +47,7 @@ public void testIsBaselineB() { FactorValue fv = FactorValue.Factory.newInstance(); fv.setValue( "fv" ); - Characteristic c = Characteristic.Factory.newInstance(); + Statement c = Statement.Factory.newInstance(); c.setValueUri( "http://purl.org/nbirn/birnlex/ontology/BIRNLex-Investigation.owl#birnlex_2201" ); fv.getCharacteristics().add( c ); @@ -62,7 +62,7 @@ public void testIsNotBaselineA() { FactorValue fv = FactorValue.Factory.newInstance(); fv.setValue( "fv" ); - Characteristic c = Characteristic.Factory.newInstance(); + Statement c = Statement.Factory.newInstance(); c.setValueUri( "http://purl.org/obo/owl/CHEBI#CHEBI_16236" ); fv.getCharacteristics().add( c ); diff --git a/gemma-core/src/test/java/ubic/gemma/core/loader/expression/geo/GeoCharacteristicParseTest.java b/gemma-core/src/test/java/ubic/gemma/core/loader/expression/geo/GeoCharacteristicParseTest.java index 8353704075..1d497997eb 100644 --- a/gemma-core/src/test/java/ubic/gemma/core/loader/expression/geo/GeoCharacteristicParseTest.java +++ b/gemma-core/src/test/java/ubic/gemma/core/loader/expression/geo/GeoCharacteristicParseTest.java @@ -43,9 +43,10 @@ public final void testParseGEOSampleCharacteristic() throws Exception { assertEquals( "http://purl.obolibrary.org/obo/PATO_0000047", c.getCategoryUri() ); assertEquals( "http://purl.obolibrary.org/obo/PATO_0000384", c.getValueUri() ); - t = BioMaterial.Factory.newInstance(); - g.parseGEOSampleCharacteristicString( "Sex: M,Tissue=brain", t ); - check2chars( t ); + // I'm sorry but this case is just too crazy (mixing : and = ) and leaves other situations unaddressed like the lithium use (non-user=0, user = 1): 0 below. +// t = BioMaterial.Factory.newInstance(); +// g.parseGEOSampleCharacteristicString( "Sex: M,Tissue=brain", t ); +// check2chars( t ); t = BioMaterial.Factory.newInstance(); g.parseGEOSampleCharacteristicString( "Sex : M; Tissue:brain ", t ); @@ -92,6 +93,15 @@ public final void testParseGEOSampleCharacteristic() throws Exception { assertEquals( "fibroblast", c.getValue() ); assertEquals( "http://www.ebi.ac.uk/efo/EFO_0000324", c.getCategoryUri() ); assertEquals( "http://purl.obolibrary.org/obo/CL_0000057", c.getValueUri() ); + + + // test ugly packing of information in sample info line + // lithium use (non-user=0, user = 1): 0 + t = BioMaterial.Factory.newInstance(); + g.parseGEOSampleCharacteristicString( "lithium use (non-user=0, user = 1): 0", t ); + c = t.getCharacteristics().iterator().next(); + assertEquals( "lithium use (non-user=0, user = 1)", c.getCategory() ); + assertEquals( "0", c.getValue() ); } /** diff --git a/gemma-core/src/test/java/ubic/gemma/core/loader/expression/geo/GeoTermReplacementTest.java b/gemma-core/src/test/java/ubic/gemma/core/loader/expression/geo/GeoTermReplacementTest.java new file mode 100644 index 0000000000..8a5eaac41c --- /dev/null +++ b/gemma-core/src/test/java/ubic/gemma/core/loader/expression/geo/GeoTermReplacementTest.java @@ -0,0 +1,148 @@ +package ubic.gemma.core.loader.expression.geo; + +import lombok.Value; +import lombok.extern.apachecommons.CommonsLog; +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVParser; +import org.apache.commons.csv.CSVRecord; +import org.assertj.core.api.SoftAssertions; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import ubic.basecode.ontology.model.AnnotationProperty; +import ubic.basecode.ontology.model.OntologyTerm; +import ubic.basecode.ontology.providers.*; +import ubic.gemma.core.ontology.providers.MondoOntologyService; +import ubic.gemma.core.ontology.providers.PatoOntologyService; +import ubic.gemma.core.util.test.category.SlowTest; + +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorCompletionService; +import java.util.concurrent.Executors; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assume.assumeNoException; + +/** + * Test replacements for GEO terms. + */ +@CommonsLog +@Category(SlowTest.class) +public class GeoTermReplacementTest { + + private static final List ontologies = new ArrayList<>(); + + static { + addOntology( new CellLineOntologyService() ); + addOntology( new CellTypeOntologyService() ); + addOntology( new ObiService() ); + addOntology( new MondoOntologyService() ); + addOntology( new UberonOntologyService() ); + addOntology( new PatoOntologyService() ); + addOntology( new MammalianPhenotypeOntologyService() ); + // FIXME: addOntology( new ChebiOntologyService() ); + // EFO is a grab bag, so we list it last + addOntology( new ExperimentalFactorOntologyService() ); + } + + private static void addOntology( OntologyService ontology ) { + ontology.setInferenceMode( OntologyService.InferenceMode.NONE ); + ontology.setSearchEnabled( false ); + ontology.setProcessImports( false ); + ontologies.add( ontology ); + } + + @Value + static class Rec { + String synonym; + String value; + String valueUri; + String category; + String categoryUri; + } + + @Test + public void test() throws Exception { + ExecutorCompletionService cs = new ExecutorCompletionService<>( Executors.newFixedThreadPool( 8 ) ); + for ( OntologyService os : ontologies ) { + cs.submit( () -> { + os.initialize( true, false ); + return os; + } ); + } + + List records = new ArrayList<>(); + try ( InputStream is = getClass().getResourceAsStream( "/ubic/gemma/core/ontology/valueStringToOntologyTermMappings.txt" ) ) { + assertNotNull( is ); + for ( CSVRecord record : CSVParser.parse( is, StandardCharsets.UTF_8, CSVFormat.TDF.withCommentMarker( '#' ) ) ) { + String synonym = record.get( 0 ); + String value = record.get( 1 ); + String valueUri = record.get( 2 ); + String category = record.get( 3 ); + String categoryUri = record.get( 4 ); + // FIXME: enable CHEBI + if ( valueUri.startsWith( "http://purl.obolibrary.org/obo/CHEBI_" ) ) { + continue; + } + // FIXME: enable HANCESTRO + if ( valueUri.startsWith( "http://purl.obolibrary.org/obo/HANCESTRO_" ) ) { + continue; + } + if ( valueUri.startsWith( "http://purl.obolibrary.org/obo/NCBITaxon_" ) ) { + continue; + } + records.add( new Rec( synonym, value, valueUri, category, categoryUri ) ); + } + } + + SoftAssertions assertions = new SoftAssertions(); + + Set seen = new HashSet<>(); + int failures = 0; + for ( int i = 0; i < ontologies.size(); i++ ) { + OntologyService os; + try { + os = cs.take().get(); + } catch ( ExecutionException e ) { + // skip the test if ontologies cannot be loaded + assumeNoException( e ); + return; + } + for ( Rec rec : records ) { + // skip undeclared terms + OntologyTerm term = os.getTerm( rec.valueUri ); + if ( term == null ) { + continue; + } + // skip terms lacking a label + if ( term.getAnnotations().stream().map( AnnotationProperty::getProperty ).noneMatch( "label"::equals ) ) { + continue; + } + // CLO has a typo, ignore it + if ( "http://purl.obolibrary.org/obo/CL_0000047".equals( term.getUri() ) && "neuronal stem cell".equals( term.getLabel() ) ) { + continue; + } + if ( "http://purl.obolibrary.org/obo/CL_0000136".equals( term.getUri() ) && "adipocyte".equals( term.getLabel() ) ) { + continue; + } + seen.add( rec.synonym ); + assertions.assertThat( rec.value ) + .withFailMessage( "%s: Replace '%s' with '%s' %s", rec.synonym, rec.value, term.getLabel(), term.getUri() ) + .isEqualTo( term.getLabel() ); + } + } + + for ( Rec rec : records ) { + assertions.assertThat( seen ) + .withFailMessage( "%s: No term found for %s in any ontology", rec.synonym, rec.valueUri ) + .contains( rec.synonym ); + } + + assertions.assertAll(); + } +} diff --git a/gemma-core/src/test/java/ubic/gemma/core/ontology/CharacteristicSortTest.java b/gemma-core/src/test/java/ubic/gemma/core/ontology/CharacteristicSortTest.java index ab677e073b..3143373f7d 100644 --- a/gemma-core/src/test/java/ubic/gemma/core/ontology/CharacteristicSortTest.java +++ b/gemma-core/src/test/java/ubic/gemma/core/ontology/CharacteristicSortTest.java @@ -14,11 +14,9 @@ */ package ubic.gemma.core.ontology; -import org.assertj.core.api.Assertions; -import org.assertj.core.groups.Tuple; import org.junit.Test; import ubic.gemma.model.common.description.Characteristic; -import ubic.gemma.model.genome.gene.phenotype.valueObject.CharacteristicValueObject; +import ubic.gemma.model.common.description.CharacteristicValueObject; import java.util.ArrayList; import java.util.List; diff --git a/gemma-core/src/test/java/ubic/gemma/core/ontology/FactorValueOntologyServiceTest.java b/gemma-core/src/test/java/ubic/gemma/core/ontology/FactorValueOntologyServiceTest.java new file mode 100644 index 0000000000..4dc96da88a --- /dev/null +++ b/gemma-core/src/test/java/ubic/gemma/core/ontology/FactorValueOntologyServiceTest.java @@ -0,0 +1,60 @@ +package ubic.gemma.core.ontology; + +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests; +import ubic.gemma.model.expression.experiment.ExperimentalFactor; +import ubic.gemma.model.expression.experiment.FactorValue; +import ubic.gemma.model.expression.experiment.Statement; +import ubic.gemma.persistence.service.expression.experiment.FactorValueService; +import ubic.gemma.persistence.util.TestComponent; + +import java.io.StringWriter; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.*; + +@ContextConfiguration +public class FactorValueOntologyServiceTest extends AbstractJUnit4SpringContextTests { + + @Configuration + @TestComponent + static class FVOSTCC { + + @Bean + public FactorValueService factorValueService() { + return mock(); + } + + @Bean + public FactorValueOntologyService factorValueOntologyService( FactorValueService fvs ) { + return new FactorValueOntologyServiceImpl( fvs ); + } + } + + @Autowired + private FactorValueOntologyService factorValueOntologyService; + + @Autowired + private FactorValueService factorValueService; + + @Test + public void testWriteToRdf() { + FactorValue fv = new FactorValue(); + fv.setExperimentalFactor( new ExperimentalFactor() ); + fv.setId( 1L ); + Statement s = new Statement(); + s.setSubject( "foo" ); + fv.getCharacteristics().add( s ); + when( factorValueService.loadWithExperimentalFactor( 1L ) ).thenReturn( fv ); + StringWriter writer = new StringWriter(); + factorValueOntologyService.writeToRdf( "http://gemma.msl.ubc.ca/ont/TGFVO/1", writer ); + verify( factorValueService ).loadWithExperimentalFactor( 1L ); + assertThat( writer.toString() ) + .contains( "http://gemma.msl.ubc.ca/ont/TGFVO/1" ) + .contains( "http://gemma.msl.ubc.ca/ont/TGFVO/1/1" ); + } +} \ No newline at end of file diff --git a/gemma-core/src/test/java/ubic/gemma/core/ontology/FactorValueOntologyUtilsTest.java b/gemma-core/src/test/java/ubic/gemma/core/ontology/FactorValueOntologyUtilsTest.java new file mode 100644 index 0000000000..1c71ae33c4 --- /dev/null +++ b/gemma-core/src/test/java/ubic/gemma/core/ontology/FactorValueOntologyUtilsTest.java @@ -0,0 +1,51 @@ +package ubic.gemma.core.ontology; + +import org.junit.Test; +import ubic.gemma.model.expression.experiment.FactorValue; +import ubic.gemma.model.expression.experiment.Statement; + +import static org.assertj.core.api.Assertions.assertThat; +import static ubic.gemma.core.ontology.FactorValueOntologyUtils.*; + +public class FactorValueOntologyUtilsTest { + + @Test + public void testGetUri() { + assertThat( getUri( 1L ) ) + .isEqualTo( "http://gemma.msl.ubc.ca/ont/TGFVO/1" ); + assertThat( isAnnotationUri( "http://gemma.msl.ubc.ca/ont/TGFVO/1" ) ).isFalse(); + assertThat( isAnnotationUri( "http://gemma.msl.ubc.ca/ont/TGFVO/1/3" ) ).isTrue(); + assertThat( isAnnotationUri( "http://gemma.msl.ubc.ca/ont/TGFVO/1/foo" ) ).isFalse(); + assertThat( isAnnotationUri( "http://gemma.msl.ubc.ca/ont/TGFVO/1/3/4" ) ).isFalse(); + } + + @Test + public void testParseUri() { + parseUri( "http://gemma.msl.ubc.ca/ont/TGFVO/1" ); + parseUri( "http://gemma.msl.ubc.ca/ont/TGFVO/1/3" ); + assertThat( parseUri( "http://gemma.msl.ubc.ca/ont/TGEMO_000001" ) ) + .isNull(); + } + + @Test + public void testGetAnnotationsById() { + FactorValue fv = new FactorValue(); + fv.setId( 1L ); + Statement s = new Statement(); + s.setSubject( "foo" ); + s.setSubjectUri( "foo" ); + s.setObject( "bar" ); + s.setObjectUri( "bar" ); + fv.getCharacteristics().add( s ); + assertThat( getAnnotationsById( fv ) ) + .hasSize( 2 ) + .hasEntrySatisfying( "http://gemma.msl.ubc.ca/ont/TGFVO/1/1", v -> { + assertThat( v.getUri() ).isEqualTo( "foo" ); + assertThat( v.getLabel() ).isEqualTo( "foo" ); + } ) + .hasEntrySatisfying( "http://gemma.msl.ubc.ca/ont/TGFVO/1/2", v -> { + assertThat( v.getUri() ).isEqualTo( "bar" ); + assertThat( v.getLabel() ).isEqualTo( "bar" ); + } ); + } +} \ No newline at end of file diff --git a/gemma-core/src/test/java/ubic/gemma/core/ontology/GemmaAndExperimentalFactorOntologyTest.java b/gemma-core/src/test/java/ubic/gemma/core/ontology/GemmaAndExperimentalFactorOntologyTest.java new file mode 100644 index 0000000000..65d251685a --- /dev/null +++ b/gemma-core/src/test/java/ubic/gemma/core/ontology/GemmaAndExperimentalFactorOntologyTest.java @@ -0,0 +1,149 @@ +package ubic.gemma.core.ontology; + +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.cache.CacheManager; +import org.springframework.cache.concurrent.ConcurrentMapCacheManager; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.task.AsyncTaskExecutor; +import org.springframework.core.task.SimpleAsyncTaskExecutor; +import org.springframework.core.task.TaskExecutor; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests; +import ubic.basecode.ontology.model.OntologyTerm; +import ubic.basecode.ontology.providers.ExperimentalFactorOntologyService; +import ubic.basecode.ontology.providers.ObiService; +import ubic.gemma.core.genome.gene.service.GeneService; +import ubic.gemma.core.ontology.providers.GemmaOntologyService; +import ubic.gemma.core.ontology.providers.GeneOntologyService; +import ubic.gemma.core.search.SearchService; +import ubic.gemma.core.util.test.TestPropertyPlaceholderConfigurer; +import ubic.gemma.core.util.test.category.SlowTest; +import ubic.gemma.persistence.service.common.description.CharacteristicService; +import ubic.gemma.persistence.util.TestComponent; + +import java.util.Collections; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertNotNull; +import static org.mockito.Mockito.mock; + +/** + * This test covers cases where inference is done across two distinct {@link ubic.basecode.ontology.providers.OntologyService}. + * @author poirigui + */ +@Category(SlowTest.class) +@ContextConfiguration +public class GemmaAndExperimentalFactorOntologyTest extends AbstractJUnit4SpringContextTests { + + @Configuration + @TestComponent + static class GemmaOntologyAndEfoTestContextConfiguration { + + @Bean + public static TestPropertyPlaceholderConfigurer testPropertyPlaceholderConfigurer() { + return new TestPropertyPlaceholderConfigurer( "load.ontologies=false" ); + } + + @Bean + public ExperimentalFactorOntologyService experimentalFactorOntologyService() { + ExperimentalFactorOntologyService ontology = new ExperimentalFactorOntologyService(); + ontology.setSearchEnabled( false ); + ontology.initialize( true, false ); + return ontology; + } + + @Bean + public GemmaOntologyService gemmaOntologyService() { + GemmaOntologyService ontology = new GemmaOntologyService(); + ontology.setProcessImports( false ); // FIXME: remove this once https://github.com/PavlidisLab/TGEMO/pull/20 is merged in TGEMO + ontology.initialize( true, false ); + return ontology; + } + + @Bean + public OntologyService ontologyService() { + return new OntologyServiceImpl(); + } + + @Bean + public CharacteristicService characteristicService() { + return mock(); + } + + @Bean + public SearchService searchService() { + return mock(); + } + + @Bean + public GeneOntologyService geneOntologyService() { + return mock(); + } + + @Bean + public GeneService geneService() { + return mock(); + } + + @Bean + public AsyncTaskExecutor taskExecutor() { + return new SimpleAsyncTaskExecutor(); + } + + @Bean + public ObiService obiService() { + return mock(); + } + + @Bean + @Qualifier("ontologyTaskExecutor") + public TaskExecutor ontologyTaskExecutor() { + return mock(); + } + + @Bean + public CacheManager cacheManager() { + return new ConcurrentMapCacheManager(); + } + } + + @Autowired + private ExperimentalFactorOntologyService experimentalFactorOntologyService; + + @Autowired + private GemmaOntologyService gemmaOntologyService; + + @Autowired + private OntologyService ontologyService; + + @Test + @Category(SlowTest.class) + public void testInferenceInGemma() { + OntologyTerm overexpression = ontologyService.getTerm( "http://gemma.msl.ubc.ca/ont/TGEMO_00004" ); + assertNotNull( overexpression ); + + assertThat( gemmaOntologyService.getParents( Collections.singleton( overexpression ), false, false ) ) + .extracting( OntologyTerm::getUri ) + .containsExactlyInAnyOrder( "http://www.ebi.ac.uk/efo/EFO_0000510" ); + + OntologyTerm geneticModification = experimentalFactorOntologyService.getTerm( "http://www.ebi.ac.uk/efo/EFO_0000510" ); + assertNotNull( geneticModification ); + + assertThat( experimentalFactorOntologyService.getParents( Collections.singleton( geneticModification ), false, false ) ) + .extracting( OntologyTerm::getUri ) + .containsExactlyInAnyOrder( "http://www.ebi.ac.uk/efo/EFO_0000001", + "http://purl.obolibrary.org/obo/BFO_0000015" ); + + // ensure that parents are combined when using the OS + assertThat( ontologyService.getParents( Collections.singleton( overexpression ), false, false ) ) + .extracting( OntologyTerm::getUri ) + .containsExactlyInAnyOrder( + "http://www.ebi.ac.uk/efo/EFO_0000001", + "http://purl.obolibrary.org/obo/BFO_0000015", + "http://www.ebi.ac.uk/efo/EFO_0000510" ); + } +} diff --git a/gemma-core/src/test/java/ubic/gemma/core/ontology/OntologyLoadingTest.java b/gemma-core/src/test/java/ubic/gemma/core/ontology/OntologyLoadingTest.java new file mode 100644 index 0000000000..2caa8120fa --- /dev/null +++ b/gemma-core/src/test/java/ubic/gemma/core/ontology/OntologyLoadingTest.java @@ -0,0 +1,108 @@ +package ubic.gemma.core.ontology; + +import lombok.extern.apachecommons.CommonsLog; +import org.apache.commons.lang3.time.StopWatch; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.core.task.AsyncTaskExecutor; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests; +import ubic.basecode.ontology.providers.OntologyService; +import ubic.basecode.ontology.providers.*; +import ubic.gemma.core.ontology.providers.GemmaOntologyService; +import ubic.gemma.core.ontology.providers.MondoOntologyService; +import ubic.gemma.core.util.test.category.SlowTest; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * This test does not use the test profile as it aims to verify that all the ontologies we use in production are working + * properly. + * + * @author poirigui + */ +@CommonsLog +@ContextConfiguration(locations = { "classpath*:ubic/gemma/applicationContext-ontology.xml", "classpath:ubic/gemma/core/ontology/OntologyLoadingTest-context.xml" }) +public class OntologyLoadingTest extends AbstractJUnit4SpringContextTests { + + @Autowired + private List ontologyServices; + + @Autowired + @Qualifier("ontologyTaskExecutor") + private AsyncTaskExecutor taskExecutor; + + @Autowired + private ExperimentalFactorOntologyService efo; + + @Autowired + private ChebiOntologyService chebi; + + @Autowired + private GemmaOntologyService tgemo; + + @Autowired + private MammalianPhenotypeOntologyService mp; + + @Autowired + private MondoOntologyService mondo; + + @Autowired + private CellLineOntologyService clo; + + @Autowired + private CellTypeOntologyService cl; + + @Autowired + private HumanPhenotypeOntologyService hpo; + + @Autowired + private UberonOntologyService uberon; + + @Test + public void testThatChebiDoesNotHaveInferenceEnabled() { + assertThat( chebi.getInferenceMode() ).isEqualTo( OntologyService.InferenceMode.NONE ); + } + + @Test + public void testThatTGEMODoesNotProcessImports() { + assertThat( tgemo.getProcessImports() ).isFalse(); + } + + @Test + @Category(SlowTest.class) + public void testInitializeAllOntologies() { + // these are notoriously slow, so we skip them + List ignoredOntologies = Arrays.asList( efo, chebi, mp, mondo, clo, cl, hpo, uberon ); + List services = new ArrayList<>(); + List> futures = new ArrayList<>(); + for ( OntologyService os : ontologyServices ) { + if ( ignoredOntologies.contains( os ) ) { + continue; + } + services.add( os ); + futures.add( taskExecutor.submit( () -> { + StopWatch timer = StopWatch.createStarted(); + os.initialize( true, true ); + log.info( String.format( "Initializing %s took %d s.", os, timer.getTime( TimeUnit.SECONDS ) ) ); + } ) ); + } + long totalTimeMs = 60 * 1000L; // 1 minute + long initialTimeMs = System.currentTimeMillis(); + assertThat( futures ).zipSatisfy( services, ( future, os ) -> { + long elapsedTimeMs = System.currentTimeMillis() - initialTimeMs; + assertThat( future ) + .describedAs( os.toString() ) + .succeedsWithin( Duration.ofMillis( Math.max( totalTimeMs - elapsedTimeMs, 0 ) ) ); + } ); + } +} diff --git a/gemma-core/src/test/java/ubic/gemma/core/ontology/OntologyServiceIntegrationTest.java b/gemma-core/src/test/java/ubic/gemma/core/ontology/OntologyServiceIntegrationTest.java deleted file mode 100644 index b98e12ed95..0000000000 --- a/gemma-core/src/test/java/ubic/gemma/core/ontology/OntologyServiceIntegrationTest.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * The Gemma project - * - * Copyright (c) 2007-2013 University of British Columbia - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ -package ubic.gemma.core.ontology; - -import org.junit.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.test.annotation.DirtiesContext; -import ubic.basecode.ontology.model.OntologyTerm; -import ubic.basecode.ontology.providers.UberonOntologyService; -import ubic.basecode.ontology.search.OntologySearchException; -import ubic.gemma.core.ontology.providers.MondoOntologyService; -import ubic.gemma.core.search.SearchException; -import ubic.gemma.core.util.test.BaseSpringContextTest; -import ubic.gemma.model.genome.gene.phenotype.valueObject.CharacteristicValueObject; - -import java.util.Collection; -import java.util.Collections; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.*; - -/** - * @author paul - */ -@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) -public class OntologyServiceIntegrationTest extends BaseSpringContextTest { - - @Autowired - private OntologyService os; - - @Autowired - private MondoOntologyService diseaseOntologyService; - - @Autowired - private UberonOntologyService uberonOntologyService; - - @Test - public void test() throws SearchException, OntologySearchException, InterruptedException { - assertEquals( ubic.basecode.ontology.providers.OntologyService.LanguageLevel.FULL, diseaseOntologyService.getLanguageLevel() ); - assertEquals( ubic.basecode.ontology.providers.OntologyService.InferenceMode.TRANSITIVE, diseaseOntologyService.getInferenceMode() ); - OntologyTestUtils.initialize( diseaseOntologyService, - this.getClass().getResourceAsStream( "/data/loader/ontology/dotest.owl.xml" ) ); - - Collection name = os.findTermsInexact( "diarrhea", null ); - - assertFalse( name.isEmpty() ); - - OntologyTerm t1 = os.getTerm( "http://purl.obolibrary.org/obo/DOID_0050001" ); - assertNotNull( t1 ); - - // Actinomadura madurae infectious disease - assertTrue( os.isObsolete( "http://purl.obolibrary.org/obo/DOID_0050001" ) ); - - // inflammatory diarrhea, not obsolete as of May 2012. - assertNotNull( os.getTerm( "http://purl.obolibrary.org/obo/DOID_0050132" ) ); - assertFalse( os.isObsolete( "http://purl.obolibrary.org/obo/DOID_0050132" ) ); - } - - @Test - public void testSubstantiaNigraInUberon() throws InterruptedException { - assertEquals( ubic.basecode.ontology.providers.OntologyService.LanguageLevel.FULL, uberonOntologyService.getLanguageLevel() ); - assertEquals( ubic.basecode.ontology.providers.OntologyService.InferenceMode.TRANSITIVE, uberonOntologyService.getInferenceMode() ); - OntologyUtils.ensureInitialized( uberonOntologyService ); - OntologyTerm brain = os.getTerm( "http://purl.obolibrary.org/obo/UBERON_0000955" ); - assertNotNull( brain ); - OntologyTerm substantiaNigra = os.getTerm( "http://purl.obolibrary.org/obo/UBERON_0002038" ); - assertNotNull( substantiaNigra ); - OntologyTerm substantiaNigraParsCompacta = os.getTerm( "http://purl.obolibrary.org/obo/UBERON_0001965" ); - assertNotNull( substantiaNigraParsCompacta ); - assertThat( os.getChildren( Collections.singleton( brain ), false, true ) ) - .contains( substantiaNigra, substantiaNigraParsCompacta ); - assertThat( os.getChildren( Collections.singleton( substantiaNigra ), false, true ) ) - .contains( substantiaNigraParsCompacta ); - } -} diff --git a/gemma-core/src/test/java/ubic/gemma/core/ontology/OntologyServiceTest.java b/gemma-core/src/test/java/ubic/gemma/core/ontology/OntologyServiceTest.java index 3b318d840a..f6d992c8e3 100644 --- a/gemma-core/src/test/java/ubic/gemma/core/ontology/OntologyServiceTest.java +++ b/gemma-core/src/test/java/ubic/gemma/core/ontology/OntologyServiceTest.java @@ -22,7 +22,6 @@ import ubic.gemma.model.common.search.SearchSettings; import ubic.gemma.model.genome.Gene; import ubic.gemma.persistence.service.common.description.CharacteristicService; -import ubic.gemma.persistence.service.expression.biomaterial.BioMaterialService; import ubic.gemma.persistence.util.TestComponent; import java.util.Collections; @@ -53,11 +52,6 @@ public ChebiOntologyService chebiOntologyService() { return mock( ChebiOntologyService.class ); } - @Bean - public BioMaterialService bioMaterialService() { - return mock(); - } - @Bean public CharacteristicService characteristicService() { return mock(); diff --git a/gemma-core/src/test/java/ubic/gemma/core/ontology/OntologyTestUtils.java b/gemma-core/src/test/java/ubic/gemma/core/ontology/OntologyTestUtils.java deleted file mode 100644 index aa682fc459..0000000000 --- a/gemma-core/src/test/java/ubic/gemma/core/ontology/OntologyTestUtils.java +++ /dev/null @@ -1,25 +0,0 @@ -package ubic.gemma.core.ontology; - -import lombok.extern.apachecommons.CommonsLog; -import ubic.basecode.ontology.providers.OntologyService; - -import java.io.InputStream; - -/** - * Utilities for testing ontologies. - */ -@CommonsLog -public class OntologyTestUtils { - - /** - * Initialize an ontology, cancelling any pending initialization. - */ - public static void initialize( OntologyService os, InputStream stream ) throws InterruptedException { - if ( os.isInitializationThreadAlive() ) { - log.warn( String.format( "Cancelling pending initialization of %s...", os ) ); - os.cancelInitializationThread(); - os.waitForInitializationThread(); - } - os.initialize( stream, false ); - } -} diff --git a/gemma-core/src/test/java/ubic/gemma/core/ontology/providers/DiseaseOntologyTest.java b/gemma-core/src/test/java/ubic/gemma/core/ontology/providers/DiseaseOntologyTest.java new file mode 100644 index 0000000000..7ba3aaf7d6 --- /dev/null +++ b/gemma-core/src/test/java/ubic/gemma/core/ontology/providers/DiseaseOntologyTest.java @@ -0,0 +1,40 @@ +package ubic.gemma.core.ontology.providers; + +import org.junit.Test; +import org.springframework.core.io.ClassPathResource; +import ubic.basecode.ontology.model.OntologyTerm; +import ubic.basecode.ontology.providers.DiseaseOntologyService; +import ubic.basecode.ontology.search.OntologySearchException; +import ubic.gemma.core.search.SearchException; + +import java.io.IOException; +import java.util.Collection; + +import static org.junit.Assert.*; + +public class DiseaseOntologyTest { + + @Test + public void test() throws SearchException, OntologySearchException, InterruptedException, IOException { + DiseaseOntologyService diseaseOntologyService = new DiseaseOntologyService(); + assertEquals( ubic.basecode.ontology.providers.OntologyService.LanguageLevel.FULL, diseaseOntologyService.getLanguageLevel() ); + assertEquals( ubic.basecode.ontology.providers.OntologyService.InferenceMode.TRANSITIVE, diseaseOntologyService.getInferenceMode() ); + diseaseOntologyService.initialize( new ClassPathResource( "/data/loader/ontology/dotest.owl.xml" ).getInputStream(), false ); + + Collection name = diseaseOntologyService.findTerm( "diarrhea" ); + + assertFalse( name.isEmpty() ); + + OntologyTerm term; + + // Actinomadura madurae infectious disease + term = diseaseOntologyService.getTerm( "http://purl.obolibrary.org/obo/DOID_0050001" ); + assertNotNull( term ); + assertTrue( term.isObsolete() ); + + // inflammatory diarrhea, not obsolete as of May 2012. + term = diseaseOntologyService.getTerm( "http://purl.obolibrary.org/obo/DOID_0050132" ); + assertNotNull( term ); + assertFalse( term.isObsolete() ); + } +} diff --git a/gemma-core/src/test/java/ubic/gemma/core/ontology/providers/GemmaOntologyServiceTest.java b/gemma-core/src/test/java/ubic/gemma/core/ontology/providers/GemmaOntologyServiceTest.java new file mode 100644 index 0000000000..bf879da829 --- /dev/null +++ b/gemma-core/src/test/java/ubic/gemma/core/ontology/providers/GemmaOntologyServiceTest.java @@ -0,0 +1,29 @@ +package ubic.gemma.core.ontology.providers; + +import org.junit.Test; +import org.junit.experimental.categories.Category; +import ubic.basecode.ontology.model.OntologyTerm; +import ubic.gemma.core.util.test.category.SlowTest; + +import java.util.Collections; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +public class GemmaOntologyServiceTest { + + @Test + public void test() { + GemmaOntologyService gemmaOntology = new GemmaOntologyService(); + gemmaOntology.setProcessImports( false ); // FIXME: remove this once https://github.com/PavlidisLab/TGEMO/pull/20 is merged + assertEquals( ubic.basecode.ontology.providers.OntologyService.LanguageLevel.FULL, gemmaOntology.getLanguageLevel() ); + assertEquals( ubic.basecode.ontology.providers.OntologyService.InferenceMode.TRANSITIVE, gemmaOntology.getInferenceMode() ); + gemmaOntology.initialize( true, false ); + OntologyTerm overexpression = gemmaOntology.getTerm( "http://gemma.msl.ubc.ca/ont/TGEMO_00004" ); + assertNotNull( overexpression ); + assertThat( gemmaOntology.getParents( Collections.singleton( overexpression ), false, false ) ) + .extracting( OntologyTerm::getUri ) + .contains( "http://www.ebi.ac.uk/efo/EFO_0000510" ); + } +} diff --git a/gemma-core/src/test/java/ubic/gemma/core/ontology/providers/GeneOntologyService2Test.java b/gemma-core/src/test/java/ubic/gemma/core/ontology/providers/GeneOntologyService2Test.java index 5548064438..e82e273e13 100644 --- a/gemma-core/src/test/java/ubic/gemma/core/ontology/providers/GeneOntologyService2Test.java +++ b/gemma-core/src/test/java/ubic/gemma/core/ontology/providers/GeneOntologyService2Test.java @@ -14,9 +14,9 @@ */ package ubic.gemma.core.ontology.providers; -import org.junit.Before; import org.junit.Test; import org.junit.experimental.categories.Category; +import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.CacheManager; import org.springframework.cache.concurrent.ConcurrentMapCacheManager; @@ -27,7 +27,6 @@ import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests; import ubic.basecode.ontology.model.OntologyTerm; import ubic.gemma.core.genome.gene.service.GeneService; -import ubic.gemma.core.ontology.OntologyTestUtils; import ubic.gemma.core.ontology.providers.GeneOntologyServiceImpl.GOAspect; import ubic.gemma.core.util.test.TestPropertyPlaceholderConfigurer; import ubic.gemma.core.util.test.category.SlowTest; @@ -50,7 +49,7 @@ */ @Category(SlowTest.class) @ContextConfiguration -public class GeneOntologyService2Test extends AbstractJUnit4SpringContextTests { +public class GeneOntologyService2Test extends AbstractJUnit4SpringContextTests implements InitializingBean { @Configuration @TestComponent @@ -85,11 +84,13 @@ public CacheManager cacheManager() { @Autowired private GeneOntologyService gos; - @Before - public void setUp() throws InterruptedException, IOException { - InputStream is = new GZIPInputStream( - new ClassPathResource( "/data/loader/ontology/go.bptest.owl.gz" ).getInputStream() ); - OntologyTestUtils.initialize( gos, is ); + @Override + public void afterPropertiesSet() throws Exception { + if ( !gos.isOntologyLoaded() ) { + InputStream is = new GZIPInputStream( + new ClassPathResource( "/data/loader/ontology/go.bptest.owl.gz" ).getInputStream() ); + gos.initialize( is, false ); + } } @Test diff --git a/gemma-core/src/test/java/ubic/gemma/core/ontology/providers/GeneOntologyServiceTest.java b/gemma-core/src/test/java/ubic/gemma/core/ontology/providers/GeneOntologyServiceTest.java index 49fdd0f1a3..107d13b01b 100644 --- a/gemma-core/src/test/java/ubic/gemma/core/ontology/providers/GeneOntologyServiceTest.java +++ b/gemma-core/src/test/java/ubic/gemma/core/ontology/providers/GeneOntologyServiceTest.java @@ -20,8 +20,8 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.junit.Before; import org.junit.Test; +import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.CacheManager; import org.springframework.cache.concurrent.ConcurrentMapCacheManager; @@ -33,7 +33,6 @@ import ubic.basecode.ontology.model.OntologyTerm; import ubic.basecode.ontology.search.OntologySearchException; import ubic.gemma.core.genome.gene.service.GeneService; -import ubic.gemma.core.ontology.OntologyTestUtils; import ubic.gemma.core.util.test.TestPropertyPlaceholderConfigurer; import ubic.gemma.persistence.service.association.Gene2GOAssociationService; import ubic.gemma.persistence.util.TestComponent; @@ -50,7 +49,7 @@ * @author Paul */ @ContextConfiguration -public class GeneOntologyServiceTest extends AbstractJUnit4SpringContextTests { +public class GeneOntologyServiceTest extends AbstractJUnit4SpringContextTests implements InitializingBean { private static final Log log = LogFactory.getLog( GeneOntologyServiceTest.class.getName() ); @Configuration @@ -86,15 +85,17 @@ public CacheManager cacheManager() { @Autowired private GeneOntologyService gos; - @Before - public void setUp() throws InterruptedException, IOException { - /* - * Note that this test file is out of date in some ways. See GeneOntologyServiceTest2 - */ - InputStream is = new GZIPInputStream( - new ClassPathResource( "/data/loader/ontology/molecular-function.test.owl.gz" ).getInputStream() ); - // we must force indexing to get consistent test results - OntologyTestUtils.initialize( gos, is ); + @Override + public void afterPropertiesSet() throws Exception { + if ( !gos.isOntologyLoaded() ) { + /* + * Note that this test file is out of date in some ways. See GeneOntologyServiceTest2 + */ + InputStream is = new GZIPInputStream( + new ClassPathResource( "/data/loader/ontology/molecular-function.test.owl.gz" ).getInputStream() ); + // we must force indexing to get consistent test results + gos.initialize( is, true ); + } } @Test diff --git a/gemma-core/src/test/java/ubic/gemma/core/ontology/providers/PatoOntologyServiceTest.java b/gemma-core/src/test/java/ubic/gemma/core/ontology/providers/PatoOntologyServiceTest.java index 663e56e188..ce7bd3aa3c 100644 --- a/gemma-core/src/test/java/ubic/gemma/core/ontology/providers/PatoOntologyServiceTest.java +++ b/gemma-core/src/test/java/ubic/gemma/core/ontology/providers/PatoOntologyServiceTest.java @@ -2,7 +2,9 @@ import lombok.extern.apachecommons.CommonsLog; import org.junit.Test; +import org.junit.experimental.categories.Category; import ubic.basecode.ontology.search.OntologySearchException; +import ubic.gemma.core.util.test.category.SlowTest; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -11,6 +13,7 @@ public class PatoOntologyServiceTest { @Test + @Category(SlowTest.class) public void test() throws OntologySearchException { PatoOntologyService pato = new PatoOntologyService(); pato.initialize( true, true ); diff --git a/gemma-core/src/test/java/ubic/gemma/core/ontology/providers/UberonOntologyTest.java b/gemma-core/src/test/java/ubic/gemma/core/ontology/providers/UberonOntologyTest.java new file mode 100644 index 0000000000..302b6e242f --- /dev/null +++ b/gemma-core/src/test/java/ubic/gemma/core/ontology/providers/UberonOntologyTest.java @@ -0,0 +1,35 @@ +package ubic.gemma.core.ontology.providers; + +import org.junit.Test; +import org.junit.experimental.categories.Category; +import ubic.basecode.ontology.model.OntologyTerm; +import ubic.basecode.ontology.providers.UberonOntologyService; +import ubic.gemma.core.util.test.category.SlowTest; + +import java.util.Collections; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +public class UberonOntologyTest { + + @Test + @Category(SlowTest.class) + public void testSubstantiaNigraInUberon() throws InterruptedException { + UberonOntologyService uberonOntologyService = new UberonOntologyService(); + assertEquals( ubic.basecode.ontology.providers.OntologyService.LanguageLevel.FULL, uberonOntologyService.getLanguageLevel() ); + assertEquals( ubic.basecode.ontology.providers.OntologyService.InferenceMode.TRANSITIVE, uberonOntologyService.getInferenceMode() ); + uberonOntologyService.initialize( true, false ); + OntologyTerm brain = uberonOntologyService.getTerm( "http://purl.obolibrary.org/obo/UBERON_0000955" ); + assertNotNull( brain ); + OntologyTerm substantiaNigra = uberonOntologyService.getTerm( "http://purl.obolibrary.org/obo/UBERON_0002038" ); + assertNotNull( substantiaNigra ); + OntologyTerm substantiaNigraParsCompacta = uberonOntologyService.getTerm( "http://purl.obolibrary.org/obo/UBERON_0001965" ); + assertNotNull( substantiaNigraParsCompacta ); + assertThat( uberonOntologyService.getChildren( Collections.singleton( brain ), false, true ) ) + .contains( substantiaNigra, substantiaNigraParsCompacta ); + assertThat( uberonOntologyService.getChildren( Collections.singleton( substantiaNigra ), false, true ) ) + .contains( substantiaNigraParsCompacta ); + } +} diff --git a/gemma-core/src/test/java/ubic/gemma/core/search/SearchServiceIntegrationTest.java b/gemma-core/src/test/java/ubic/gemma/core/search/SearchServiceIntegrationTest.java index 25bba53797..a09eddeac2 100644 --- a/gemma-core/src/test/java/ubic/gemma/core/search/SearchServiceIntegrationTest.java +++ b/gemma-core/src/test/java/ubic/gemma/core/search/SearchServiceIntegrationTest.java @@ -31,7 +31,6 @@ import ubic.gemma.core.genome.gene.service.GeneService; import ubic.gemma.core.loader.entrez.pubmed.PubMedXMLFetcher; import ubic.gemma.core.ontology.OntologyService; -import ubic.gemma.core.ontology.OntologyTestUtils; import ubic.gemma.core.tasks.maintenance.IndexerTask; import ubic.gemma.core.tasks.maintenance.IndexerTaskCommand; import ubic.gemma.core.util.test.BaseSpringContextTest; @@ -47,6 +46,7 @@ import ubic.gemma.persistence.service.common.description.CharacteristicService; import ubic.gemma.persistence.service.expression.experiment.ExpressionExperimentService; +import java.io.IOException; import java.io.InputStream; import java.util.*; @@ -57,7 +57,6 @@ /** * @author kelsey */ -@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) public class SearchServiceIntegrationTest extends BaseSpringContextTest { private static final String GENE_URI = "http://purl.org/commons/record/ncbi_gene/"; private static final String SPINAL_CORD = "http://purl.obolibrary.org/obo/FMA_7647"; @@ -94,12 +93,6 @@ public class SearchServiceIntegrationTest extends BaseSpringContextTest { @Before public void setUp() throws Exception { - try ( InputStream is = this.getClass().getResourceAsStream( "/data/loader/ontology/fma.test.owl" ) ) { - assert is != null; - // this abuses the service as our example is a legacy FMA test (not uberon), but it doesn't matter since we're loading from a file anyway. - // this will fail if the loading of uberon is enabled - it will collide. - OntologyTestUtils.initialize( fmaOntologyService, is ); - } ee = this.getTestPersistentBasicExpressionExperiment(); Characteristic eeCharSpinalCord = Characteristic.Factory.newInstance(); @@ -150,7 +143,15 @@ public void tearDown() { * are found, -- requires LARQ index. */ @Test - public void testGeneralSearch4Brain() throws SearchException { + @DirtiesContext + public void testGeneralSearch4Brain() throws SearchException, IOException { + try ( InputStream is = this.getClass().getResourceAsStream( "/data/loader/ontology/fma.test.owl" ) ) { + assert is != null; + // this abuses the service as our example is a legacy FMA test (not uberon), but it doesn't matter since we're loading from a file anyway. + // this will fail if the loading of uberon is enabled - it will collide. + fmaOntologyService.initialize( is, true ); + } + SearchSettings settings = SearchSettings.builder() .query( "Brain" ) // should hit 'cavity of brain'. .resultType( ExpressionExperiment.class ) @@ -234,6 +235,7 @@ public void testSearchByBibRefIdProblems() throws SearchException { } @Test + @Category(SlowTest.class) public void testSearchByBibRefIdProblemsB() throws SearchException { PubMedXMLFetcher fetcher = new PubMedXMLFetcher(); BibliographicReference bibref = fetcher.retrieveByHTTP( 22780917 ); @@ -308,7 +310,15 @@ public void testSearchByBibRefIdProblemsB() throws SearchException { * Test we find EE tagged with a child term that matches the given uri. */ @Test - public void testURIChildSearch() throws SearchException { + @DirtiesContext + public void testURIChildSearch() throws SearchException, IOException { + try ( InputStream is = this.getClass().getResourceAsStream( "/data/loader/ontology/fma.test.owl" ) ) { + assert is != null; + // this abuses the service as our example is a legacy FMA test (not uberon), but it doesn't matter since we're loading from a file anyway. + // this will fail if the loading of uberon is enabled - it will collide. + fmaOntologyService.initialize( is, true ); + } + SearchSettings settings = SearchSettings.builder() .query( "http://purl.obolibrary.org/obo/FMA_83153" ) // OrganComponent of Neuraxis; superclass of // 'spinal cord'. diff --git a/gemma-core/src/test/java/ubic/gemma/core/search/SearchServiceVoConversionTest.java b/gemma-core/src/test/java/ubic/gemma/core/search/SearchServiceVoConversionTest.java index 86b6943d98..d5c66f2e01 100644 --- a/gemma-core/src/test/java/ubic/gemma/core/search/SearchServiceVoConversionTest.java +++ b/gemma-core/src/test/java/ubic/gemma/core/search/SearchServiceVoConversionTest.java @@ -31,7 +31,7 @@ import ubic.gemma.model.genome.Taxon; import ubic.gemma.model.genome.gene.DatabaseBackedGeneSetValueObject; import ubic.gemma.model.genome.gene.GeneSet; -import ubic.gemma.model.genome.gene.phenotype.valueObject.CharacteristicValueObject; +import ubic.gemma.model.common.description.CharacteristicValueObject; import ubic.gemma.persistence.service.expression.arrayDesign.ArrayDesignService; import ubic.gemma.persistence.service.expression.designElement.CompositeSequenceService; import ubic.gemma.persistence.service.expression.experiment.BlacklistedEntityService; diff --git a/gemma-core/src/test/java/ubic/gemma/core/search/source/HibernateSearchSourceTest.java b/gemma-core/src/test/java/ubic/gemma/core/search/source/HibernateSearchSourceTest.java index c42f962765..f87dfabd68 100644 --- a/gemma-core/src/test/java/ubic/gemma/core/search/source/HibernateSearchSourceTest.java +++ b/gemma-core/src/test/java/ubic/gemma/core/search/source/HibernateSearchSourceTest.java @@ -11,7 +11,7 @@ import ubic.gemma.core.util.test.BaseDatabaseTest; import ubic.gemma.model.common.description.BibliographicReference; import ubic.gemma.model.common.search.SearchSettings; -import ubic.gemma.model.expression.experiment.ExpressionExperiment; +import ubic.gemma.model.expression.experiment.*; import ubic.gemma.model.genome.Taxon; import ubic.gemma.persistence.util.TestComponent; @@ -82,4 +82,45 @@ public void testSearchExpressionExperiment() throws HibernateSearchException { .containsEntry( "primaryPublication.title", "The greatest hello world!" ); } ); } + + @Test + public void testSearchExpressionExperimentByStatementObject() throws HibernateSearchException { + FullTextSession fts = Search.getFullTextSession( sessionFactory.getCurrentSession() ); + Taxon taxon = new Taxon(); + fts.persist( taxon ); + ExpressionExperiment ee = new ExpressionExperiment(); + ExperimentalDesign ed = new ExperimentalDesign(); + ExperimentalFactor ef = new ExperimentalFactor(); + ef.setType( FactorType.CATEGORICAL ); + ef.setExperimentalDesign( ed ); + FactorValue fv = new FactorValue(); + fv.setExperimentalFactor( ef ); + Statement s = new Statement(); + s.setSubject( "BRCA1" ); + s.setObject( "Overexpression" ); + fv.getCharacteristics().add( s ); + ef.getFactorValues().add( fv ); + ed.getExperimentalFactors().add( ef ); + ee.setExperimentalDesign( ed ); + fts.persist( ee ); + fts.flushToIndexes(); + assertThat( hibernateSearchSource.searchExpressionExperiment( SearchSettings.builder() + .query( "BRCA1" ) + .build() ) ) + .anySatisfy( r -> { + assertThat( r.getResultObject() ).isEqualTo( ee ); + } ); + assertThat( hibernateSearchSource.searchExpressionExperiment( SearchSettings.builder() + .query( "Overexpression" ) + .build() ) ) + .anySatisfy( r -> { + assertThat( r.getResultObject() ).isEqualTo( ee ); + } ); + assertThat( hibernateSearchSource.searchExpressionExperiment( SearchSettings.builder() + .query( "BRCA1 Overexpression" ) + .build() ) ) + .anySatisfy( r -> { + assertThat( r.getResultObject() ).isEqualTo( ee ); + } ); + } } \ No newline at end of file diff --git a/gemma-core/src/test/java/ubic/gemma/core/security/audit/AuditAdviceTest.java b/gemma-core/src/test/java/ubic/gemma/core/security/audit/AuditAdviceTest.java index 24169d9354..7d520018ff 100644 --- a/gemma-core/src/test/java/ubic/gemma/core/security/audit/AuditAdviceTest.java +++ b/gemma-core/src/test/java/ubic/gemma/core/security/audit/AuditAdviceTest.java @@ -31,9 +31,11 @@ import ubic.gemma.model.common.auditAndSecurity.AuditEvent; import ubic.gemma.model.common.auditAndSecurity.AuditTrail; import ubic.gemma.model.expression.bioAssay.BioAssay; +import ubic.gemma.model.expression.biomaterial.BioMaterial; import ubic.gemma.model.expression.experiment.ExperimentalFactor; import ubic.gemma.model.expression.experiment.ExpressionExperiment; import ubic.gemma.persistence.service.common.auditAndSecurity.AuditEventService; +import ubic.gemma.persistence.service.expression.biomaterial.BioMaterialService; import ubic.gemma.persistence.service.expression.experiment.ExpressionExperimentService; import java.util.Collection; @@ -93,6 +95,9 @@ public void testAuditCreateAndDeleteExpressionExperiment() { } + @Autowired + private BioMaterialService bioMaterialService; + @Test public void testCascadingCreateOnUpdate() { ExpressionExperiment ee = this.getTestPersistentCompleteExpressionExperiment( false ); @@ -104,10 +109,15 @@ public void testCascadingCreateOnUpdate() { // should have create only assertEquals( 1, ee.getAuditTrail().getEvents().size() ); + BioMaterial bm = BioMaterial.Factory.newInstance(); + bm.setSourceTaxon( ee.getTaxon() ); + bm = bioMaterialService.create( bm ); + BioAssay ba = BioAssay.Factory.newInstance(); String name = RandomStringUtils.randomAlphabetic( 20 ); ba.setName( name ); ba.setArrayDesignUsed( ee.getBioAssays().iterator().next().getArrayDesignUsed() ); + ba.setSampleUsed( bm ); ee.getBioAssays().add( ba ); this.expressionExperimentService.update( ee ); diff --git a/gemma-core/src/test/java/ubic/gemma/core/util/test/BaseDatabaseTest.java b/gemma-core/src/test/java/ubic/gemma/core/util/test/BaseDatabaseTest.java index 27fae5ca18..c4de79b004 100644 --- a/gemma-core/src/test/java/ubic/gemma/core/util/test/BaseDatabaseTest.java +++ b/gemma-core/src/test/java/ubic/gemma/core/util/test/BaseDatabaseTest.java @@ -27,8 +27,6 @@ import org.springframework.security.acls.domain.SpringCacheBasedAclCache; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.test.context.support.WithSecurityContextTestExecutionListener; -import org.springframework.test.context.TestExecutionListeners; import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests; import org.springframework.test.jdbc.JdbcTestUtils; import org.springframework.transaction.PlatformTransactionManager; @@ -43,7 +41,6 @@ * * @author poirigui */ -@TestExecutionListeners(WithSecurityContextTestExecutionListener.class) public abstract class BaseDatabaseTest extends AbstractTransactionalJUnit4SpringContextTests { protected abstract static class BaseDatabaseTestContextConfiguration { diff --git a/gemma-core/src/test/java/ubic/gemma/core/util/test/BaseSpringContextTest.java b/gemma-core/src/test/java/ubic/gemma/core/util/test/BaseSpringContextTest.java index 214bec5518..d4fb370c69 100644 --- a/gemma-core/src/test/java/ubic/gemma/core/util/test/BaseSpringContextTest.java +++ b/gemma-core/src/test/java/ubic/gemma/core/util/test/BaseSpringContextTest.java @@ -40,7 +40,7 @@ import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests; import org.springframework.test.jdbc.JdbcTestUtils; -import ubic.gemma.core.util.test.category.SpringContextTest; +import ubic.gemma.core.util.test.category.IntegrationTest; import ubic.gemma.model.analysis.Analysis; import ubic.gemma.model.association.BioSequence2GeneProduct; import ubic.gemma.model.common.auditAndSecurity.Contact; @@ -77,7 +77,7 @@ * @author pavlidis */ @ActiveProfiles(SpringProfiles.TEST) -@Category(SpringContextTest.class) +@Category(IntegrationTest.class) @SuppressWarnings({ "WeakerAccess", "SameParameterValue", "unused" }) // Better left as is for future convenience @ContextConfiguration(locations = { "classpath*:ubic/gemma/applicationContext-*.xml" }) public abstract class BaseSpringContextTest extends AbstractJUnit4SpringContextTests implements InitializingBean { diff --git a/gemma-core/src/test/java/ubic/gemma/core/util/test/HibernateConfigTest.java b/gemma-core/src/test/java/ubic/gemma/core/util/test/HibernateConfigTest.java index 9f6996a3e2..e38599fed2 100644 --- a/gemma-core/src/test/java/ubic/gemma/core/util/test/HibernateConfigTest.java +++ b/gemma-core/src/test/java/ubic/gemma/core/util/test/HibernateConfigTest.java @@ -2,23 +2,31 @@ import lombok.extern.apachecommons.CommonsLog; import net.sf.ehcache.Cache; +import org.apache.commons.io.file.PathUtils; import org.hibernate.SessionFactory; import org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory; import org.hibernate.cfg.Settings; import org.hibernate.internal.SessionFactoryImpl; import org.hibernate.metadata.ClassMetadata; import org.hibernate.persister.entity.EntityPersister; +import org.junit.AfterClass; +import org.junit.BeforeClass; import org.junit.Test; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.cache.ehcache.EhCacheManagerFactoryBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.DependsOn; +import org.springframework.context.annotation.Import; import org.springframework.orm.hibernate4.LocalSessionFactoryBean; import org.springframework.test.context.ContextConfiguration; +import ubic.gemma.persistence.util.EhcacheConfig; import ubic.gemma.persistence.util.TestComponent; import javax.sql.DataSource; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Map; import static org.junit.Assert.*; @@ -27,18 +35,30 @@ @ContextConfiguration public class HibernateConfigTest extends BaseDatabaseTest { + private static Path cacheDir; + + @BeforeClass + public static void createCacheDirectory() throws IOException { + cacheDir = Files.createTempDirectory( "gemma-cache" ); + } + + @AfterClass + public static void removeCacheDirectory() throws IOException { + PathUtils.deleteDirectory( cacheDir ); + } + + @Import(EhcacheConfig.class) @Configuration @TestComponent static class HibernateConfigTestContextConfiguration extends BaseDatabaseTestContextConfiguration { @Bean - public FactoryBean cacheManager() { - EhCacheManagerFactoryBean bean = new EhCacheManagerFactoryBean(); - bean.setShared( true ); - return bean; + public static TestPropertyPlaceholderConfigurer propertyPlaceholderConfigurer() { + return new TestPropertyPlaceholderConfigurer( "gemma.cache.dir=" + cacheDir ); } @Override + @DependsOn("ehcache") public FactoryBean sessionFactory( DataSource dataSource ) { FactoryBean factory = super.sessionFactory( dataSource ); ( ( LocalSessionFactoryBean ) factory ).getHibernateProperties() diff --git a/gemma-core/src/test/java/ubic/gemma/core/util/test/PersistentDummyObjectHelper.java b/gemma-core/src/test/java/ubic/gemma/core/util/test/PersistentDummyObjectHelper.java index 74ffdd0d4e..89f7f2bd94 100644 --- a/gemma-core/src/test/java/ubic/gemma/core/util/test/PersistentDummyObjectHelper.java +++ b/gemma-core/src/test/java/ubic/gemma/core/util/test/PersistentDummyObjectHelper.java @@ -862,7 +862,7 @@ protected Set getFactorValues( ExperimentalFactor ef, fv.setValue( "Factor value " + RandomStringUtils .randomNumeric( PersistentDummyObjectHelper.RANDOM_STRING_LENGTH ) ); fv.setExperimentalFactor( ef ); - fv.setCharacteristics( Collections.singleton( getTestCharacteristic( "name" + RandomStringUtils.randomNumeric( RANDOM_STRING_LENGTH ), fv.getValue() ) ) ); + fv.setCharacteristics( Collections.singleton( getTestStatement( "name" + RandomStringUtils.randomNumeric( RANDOM_STRING_LENGTH ), fv.getValue() ) ) ); fvCol.add( fv ); } @@ -875,6 +875,13 @@ protected Characteristic getTestCharacteristic( String name, String value ) { return Characteristic.Factory.newInstance( name, null, value, null, null, null, null ); } + protected Statement getTestStatement( String name, String value ) { + Statement s = Statement.Factory.newInstance(); + s.setName( name ); + s.setValue( value ); + return s; + } + private Set addQuantitationTypes( Set quantitationTypes ) { for ( int quantitationTypeNum = 0; quantitationTypeNum < PersistentDummyObjectHelper.NUM_QUANTITATION_TYPES; quantitationTypeNum++ ) { QuantitationType q = PersistentDummyObjectHelper.getTestNonPersistentQuantitationType(); diff --git a/gemma-core/src/test/java/ubic/gemma/core/util/test/category/IntegrationTest.java b/gemma-core/src/test/java/ubic/gemma/core/util/test/category/IntegrationTest.java new file mode 100644 index 0000000000..7ee4f00d29 --- /dev/null +++ b/gemma-core/src/test/java/ubic/gemma/core/util/test/category/IntegrationTest.java @@ -0,0 +1,8 @@ +package ubic.gemma.core.util.test.category; + +/** + * Mark a test as an integration test. + * @author poirigui + */ +public interface IntegrationTest { +} diff --git a/gemma-core/src/test/java/ubic/gemma/core/util/test/category/SlowTest.java b/gemma-core/src/test/java/ubic/gemma/core/util/test/category/SlowTest.java index 292b397a9c..7a3fa8a798 100644 --- a/gemma-core/src/test/java/ubic/gemma/core/util/test/category/SlowTest.java +++ b/gemma-core/src/test/java/ubic/gemma/core/util/test/category/SlowTest.java @@ -2,7 +2,7 @@ /** * Used to mark slow tests. - * + *

* For reference, a slow test usually takes more 10 seconds to finish. */ public class SlowTest { diff --git a/gemma-core/src/test/java/ubic/gemma/core/util/test/category/SpringContextTest.java b/gemma-core/src/test/java/ubic/gemma/core/util/test/category/SpringContextTest.java deleted file mode 100644 index 29334c3f0c..0000000000 --- a/gemma-core/src/test/java/ubic/gemma/core/util/test/category/SpringContextTest.java +++ /dev/null @@ -1,8 +0,0 @@ -package ubic.gemma.core.util.test.category; - -/** - * Mark tests requiring a full Spring context to run. - * @author poirigui - */ -public interface SpringContextTest { -} diff --git a/gemma-core/src/test/java/ubic/gemma/core/util/test/suite/FastIntegrationTests.java b/gemma-core/src/test/java/ubic/gemma/core/util/test/suite/FastIntegrationTests.java index 45ea65a54b..0ba3d6fc1d 100644 --- a/gemma-core/src/test/java/ubic/gemma/core/util/test/suite/FastIntegrationTests.java +++ b/gemma-core/src/test/java/ubic/gemma/core/util/test/suite/FastIntegrationTests.java @@ -6,7 +6,8 @@ import ubic.gemma.core.util.test.category.SlowTest; @RunWith(Categories.class) +@Categories.IncludeCategory(IntegrationTests.class) @Categories.ExcludeCategory(SlowTest.class) -@Suite.SuiteClasses({ IntegrationTests.class }) +@Suite.SuiteClasses(AllTests.class) public class FastIntegrationTests { } diff --git a/gemma-core/src/test/java/ubic/gemma/core/util/test/suite/FastTests.java b/gemma-core/src/test/java/ubic/gemma/core/util/test/suite/FastTests.java index 78b03e921b..db3f2222cd 100644 --- a/gemma-core/src/test/java/ubic/gemma/core/util/test/suite/FastTests.java +++ b/gemma-core/src/test/java/ubic/gemma/core/util/test/suite/FastTests.java @@ -11,6 +11,6 @@ */ @RunWith(Categories.class) @Categories.ExcludeCategory(SlowTest.class) -@Suite.SuiteClasses({ AllTests.class }) +@Suite.SuiteClasses(AllTests.class) public class FastTests { } diff --git a/gemma-core/src/test/java/ubic/gemma/core/util/test/suite/FastUnitTests.java b/gemma-core/src/test/java/ubic/gemma/core/util/test/suite/FastUnitTests.java index 305728192a..a4b6e7fdf1 100644 --- a/gemma-core/src/test/java/ubic/gemma/core/util/test/suite/FastUnitTests.java +++ b/gemma-core/src/test/java/ubic/gemma/core/util/test/suite/FastUnitTests.java @@ -3,10 +3,14 @@ import org.junit.experimental.categories.Categories; import org.junit.runner.RunWith; import org.junit.runners.Suite; +import ubic.gemma.core.util.test.category.IntegrationTest; import ubic.gemma.core.util.test.category.SlowTest; +/** + * Fast unit tests. + */ @RunWith(Categories.class) -@Categories.ExcludeCategory(SlowTest.class) -@Suite.SuiteClasses(UnitTests.class) +@Categories.ExcludeCategory({ IntegrationTest.class, SlowTest.class }) +@Suite.SuiteClasses(AllTests.class) public class FastUnitTests extends UnitTests { } diff --git a/gemma-core/src/test/java/ubic/gemma/core/util/test/suite/IntegrationTests.java b/gemma-core/src/test/java/ubic/gemma/core/util/test/suite/IntegrationTests.java index 100bb2da8b..b9308a1cb4 100644 --- a/gemma-core/src/test/java/ubic/gemma/core/util/test/suite/IntegrationTests.java +++ b/gemma-core/src/test/java/ubic/gemma/core/util/test/suite/IntegrationTests.java @@ -3,10 +3,10 @@ import org.junit.experimental.categories.Categories; import org.junit.runner.RunWith; import org.junit.runners.Suite; -import ubic.gemma.core.util.test.category.SpringContextTest; +import ubic.gemma.core.util.test.category.IntegrationTest; @RunWith(Categories.class) -@Categories.IncludeCategory(SpringContextTest.class) +@Categories.IncludeCategory(IntegrationTest.class) @Suite.SuiteClasses({ AllTests.class }) public class IntegrationTests { } diff --git a/gemma-core/src/test/java/ubic/gemma/core/util/test/suite/SlowTests.java b/gemma-core/src/test/java/ubic/gemma/core/util/test/suite/SlowTests.java index 1badeade33..0e6c273ab3 100644 --- a/gemma-core/src/test/java/ubic/gemma/core/util/test/suite/SlowTests.java +++ b/gemma-core/src/test/java/ubic/gemma/core/util/test/suite/SlowTests.java @@ -13,6 +13,6 @@ */ @RunWith(Categories.class) @Categories.IncludeCategory(SlowTest.class) -@Suite.SuiteClasses({ AllTests.class }) +@Suite.SuiteClasses(AllTests.class) public class SlowTests { } diff --git a/gemma-core/src/test/java/ubic/gemma/core/util/test/suite/UnitTests.java b/gemma-core/src/test/java/ubic/gemma/core/util/test/suite/UnitTests.java index 5e4a77ed9b..1b65bd1edb 100644 --- a/gemma-core/src/test/java/ubic/gemma/core/util/test/suite/UnitTests.java +++ b/gemma-core/src/test/java/ubic/gemma/core/util/test/suite/UnitTests.java @@ -3,10 +3,10 @@ import org.junit.experimental.categories.Categories; import org.junit.runner.RunWith; import org.junit.runners.Suite; -import ubic.gemma.core.util.test.category.SpringContextTest; +import ubic.gemma.core.util.test.category.IntegrationTest; @RunWith(Categories.class) -@Categories.ExcludeCategory(SpringContextTest.class) -@Suite.SuiteClasses({ AllTests.class }) +@Categories.ExcludeCategory(IntegrationTest.class) +@Suite.SuiteClasses(AllTests.class) public class UnitTests { } diff --git a/gemma-core/src/test/java/ubic/gemma/model/analysis/expression/coexpression/SupportDetailsTest.java b/gemma-core/src/test/java/ubic/gemma/model/analysis/expression/coexpression/SupportDetailsTest.java index 84e58fe64b..4d3ed78291 100644 --- a/gemma-core/src/test/java/ubic/gemma/model/analysis/expression/coexpression/SupportDetailsTest.java +++ b/gemma-core/src/test/java/ubic/gemma/model/analysis/expression/coexpression/SupportDetailsTest.java @@ -19,7 +19,6 @@ package ubic.gemma.model.analysis.expression.coexpression; -import com.google.common.io.ByteStreams; import com.googlecode.javaewah.EWAHCompressedBitmap; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; @@ -27,7 +26,9 @@ import ubic.basecode.util.RegressionTesting; import ubic.gemma.model.genome.Gene; +import java.io.ByteArrayInputStream; import java.io.DataInput; +import java.io.DataInputStream; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -47,7 +48,7 @@ public void testSerializeEWAHCompressedBitmap() throws Exception { // 000006AA00000001000000000000003600000000 = empty after removal. Byte[] or = new Byte[] { 0, 0, 6, ( byte ) 0xAA, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 36, 0, 0, 0, 0 }; System.err.println( StringUtils.join( or, "," ) ); - DataInput c = ByteStreams.newDataInput( ArrayUtils.toPrimitive( or ) ); + DataInput c = new DataInputStream( new ByteArrayInputStream( ArrayUtils.toPrimitive( or ) ) ); EWAHCompressedBitmap ff = new EWAHCompressedBitmap(); ff.deserialize( c ); assertTrue( ff.getPositions().isEmpty() ); @@ -55,14 +56,14 @@ public void testSerializeEWAHCompressedBitmap() throws Exception { // 0000000200000001000000000000000200000000 = also empty after removal. or = new Byte[] { 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0 }; System.err.println( StringUtils.join( or, "," ) ); - c = ByteStreams.newDataInput( ArrayUtils.toPrimitive( or ) ); + c = new DataInputStream( new ByteArrayInputStream( ArrayUtils.toPrimitive( or ) ) ); ff = new EWAHCompressedBitmap(); ff.deserialize( c ); assertTrue( ff.getPositions().isEmpty() ); Byte[] or2 = new Byte[] { 0, 0, 4, 22, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 32, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0 }; - DataInput di = ByteStreams.newDataInput( ArrayUtils.toPrimitive( or2 ) ); + DataInput di = new DataInputStream( new ByteArrayInputStream( ArrayUtils.toPrimitive( or2 ) ) ); EWAHCompressedBitmap f = new EWAHCompressedBitmap(); f.deserialize( di ); RegressionTesting.closeEnough( new Integer[] { 1045 }, f.getPositions().toArray( new Integer[] {} ) ); @@ -70,7 +71,7 @@ public void testSerializeEWAHCompressedBitmap() throws Exception { Byte[] or3 = new Byte[] { 0x00, 0x00, 0x19, 0x78, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ( byte ) 0xC0, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, ( byte ) 0x8A, 0x00, ( byte ) 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02 }; - di = ByteStreams.newDataInput( ArrayUtils.toPrimitive( or3 ) ); + di = new DataInputStream( new ByteArrayInputStream( ArrayUtils.toPrimitive( or3 ) ) ); f = new EWAHCompressedBitmap(); f.deserialize( di ); // expect : 1998,1999,6519 @@ -79,7 +80,7 @@ public void testSerializeEWAHCompressedBitmap() throws Exception { // empty after initialization Byte[] or4 = new Byte[] { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; - di = ByteStreams.newDataInput( ArrayUtils.toPrimitive( or4 ) ); + di = new DataInputStream( new ByteArrayInputStream( ArrayUtils.toPrimitive( or4 ) ) ); f = new EWAHCompressedBitmap(); f.deserialize( di ); assertTrue( f.getPositions().isEmpty() ); @@ -88,7 +89,7 @@ public void testSerializeEWAHCompressedBitmap() throws Exception { // or = new Byte[] { 00, 00, 00, 02, 00, 00, 00, 02, 00, 00, 00, 02, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, // 02, 00, 00, 00, 00 }; // System.err.println( StringUtils.join( or, "," ) ); - // c = ByteStreams.newDataInput( ArrayUtils.toPrimitive( or ) ); + // c = new DataInputStream(new ByteArrayInputStream( ArrayUtils.toPrimitive( or ) ); // ff = new EWAHCompressedBitmap(); // ff.deserialize( c ); // assertTrue( "" + ff.getPositions(), ff.getPositions().isEmpty() ); @@ -96,7 +97,7 @@ public void testSerializeEWAHCompressedBitmap() throws Exception { // 00000003000000020000000200000000000000000000000600000000: 1,2 or = new Byte[] { 0, 0, 0, 3, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0 }; System.err.println( StringUtils.join( or, "," ) ); - c = ByteStreams.newDataInput( ArrayUtils.toPrimitive( or ) ); + c = new DataInputStream( new ByteArrayInputStream( ArrayUtils.toPrimitive( or ) ) ); ff = new EWAHCompressedBitmap(); ff.deserialize( c ); // assertTrue( "" + ff.getPositions(), ff.getPositions().isEmpty() ); @@ -104,7 +105,7 @@ public void testSerializeEWAHCompressedBitmap() throws Exception { // 00000003000000020000000200000000000000000000000400000000: 2 or = new Byte[] { 0, 0, 0, 3, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0 }; System.err.println( StringUtils.join( or, "," ) ); - c = ByteStreams.newDataInput( ArrayUtils.toPrimitive( or ) ); + c = new DataInputStream( new ByteArrayInputStream( ArrayUtils.toPrimitive( or ) ) ); ff = new EWAHCompressedBitmap(); ff.deserialize( c ); // assertTrue( "" + ff.getPositions(), ff.getPositions().isEmpty() ); @@ -112,7 +113,7 @@ public void testSerializeEWAHCompressedBitmap() throws Exception { // 0000003E00000001000000000000000200000000 or = new Byte[] { 0, 0, 0, ( byte ) 0x3E, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0 }; System.err.println( StringUtils.join( or, "," ) ); - c = ByteStreams.newDataInput( ArrayUtils.toPrimitive( or ) ); + c = new DataInputStream( new ByteArrayInputStream( ArrayUtils.toPrimitive( or ) ) ); ff = new EWAHCompressedBitmap(); ff.deserialize( c ); assertTrue( ff.getPositions().isEmpty() ); @@ -120,7 +121,7 @@ public void testSerializeEWAHCompressedBitmap() throws Exception { // 0000003F00000001000000000000000200000000 or = new Byte[] { 0, 0, 0, ( byte ) 0x3F, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0 }; System.err.println( StringUtils.join( or, "," ) ); - c = ByteStreams.newDataInput( ArrayUtils.toPrimitive( or ) ); + c = new DataInputStream( new ByteArrayInputStream( ArrayUtils.toPrimitive( or ) ) ); ff = new EWAHCompressedBitmap(); ff.deserialize( c ); assertTrue( ff.getPositions().isEmpty() ); @@ -128,7 +129,7 @@ public void testSerializeEWAHCompressedBitmap() throws Exception { // 0000000500000001000000000000000200000000 or = new Byte[] { 0, 0, 0, 5, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0 }; System.err.println( StringUtils.join( or, "," ) ); - c = ByteStreams.newDataInput( ArrayUtils.toPrimitive( or ) ); + c = new DataInputStream( new ByteArrayInputStream( ArrayUtils.toPrimitive( or ) ) ); ff = new EWAHCompressedBitmap(); ff.deserialize( c ); assertTrue( ff.getPositions().isEmpty() ); @@ -136,7 +137,7 @@ public void testSerializeEWAHCompressedBitmap() throws Exception { // 0000015300000001000000000000000C00000000 or = new Byte[] { 0, 0, 1, 53, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, ( byte ) 0x0C, 0, 0, 0, 0 }; System.err.println( StringUtils.join( or, "," ) ); - c = ByteStreams.newDataInput( ArrayUtils.toPrimitive( or ) ); + c = new DataInputStream( new ByteArrayInputStream( ArrayUtils.toPrimitive( or ) ) ); ff = new EWAHCompressedBitmap(); ff.deserialize( c ); assertTrue( ff.getPositions().isEmpty() ); @@ -144,7 +145,7 @@ public void testSerializeEWAHCompressedBitmap() throws Exception { // 000002BB00000001000000000000001600000000 or = new Byte[] { 0, 0, 2, ( byte ) 0xBB, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0 }; System.err.println( StringUtils.join( or, "," ) ); - c = ByteStreams.newDataInput( ArrayUtils.toPrimitive( or ) ); + c = new DataInputStream( new ByteArrayInputStream( ArrayUtils.toPrimitive( or ) ) ); ff = new EWAHCompressedBitmap(); ff.deserialize( c ); assertTrue( ff.getPositions().isEmpty() ); diff --git a/gemma-core/src/test/java/ubic/gemma/model/association/phenotype/PhenotypeAssociationTest.java b/gemma-core/src/test/java/ubic/gemma/model/association/phenotype/PhenotypeAssociationTest.java index 8d47a8f404..4418a5bc63 100644 --- a/gemma-core/src/test/java/ubic/gemma/model/association/phenotype/PhenotypeAssociationTest.java +++ b/gemma-core/src/test/java/ubic/gemma/model/association/phenotype/PhenotypeAssociationTest.java @@ -22,19 +22,19 @@ import org.junit.Test; import org.junit.experimental.categories.Category; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.ClassPathResource; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.test.annotation.DirtiesContext; import ubic.basecode.ontology.model.OntologyTerm; import ubic.basecode.ontology.providers.HumanPhenotypeOntologyService; import ubic.basecode.ontology.providers.MammalianPhenotypeOntologyService; import ubic.gemma.core.association.phenotype.PhenotypeAssociationManagerService; -import ubic.gemma.core.ontology.OntologyTestUtils; -import ubic.gemma.core.ontology.OntologyUtils; import ubic.gemma.core.ontology.providers.MondoOntologyService; import ubic.gemma.core.search.SearchException; import ubic.gemma.core.security.authentication.UserManager; import ubic.gemma.core.util.test.BaseSpringContextTest; import ubic.gemma.core.util.test.category.SlowTest; +import ubic.gemma.model.common.description.CharacteristicValueObject; import ubic.gemma.model.common.description.CitationValueObject; import ubic.gemma.model.common.description.ExternalDatabase; import ubic.gemma.model.common.description.ExternalDatabaseValueObject; @@ -46,6 +46,7 @@ import ubic.gemma.persistence.service.association.phenotype.PhenotypeAssociationDaoImpl; import ubic.gemma.persistence.service.association.phenotype.service.PhenotypeAssociationService; +import java.io.InputStream; import java.util.*; import static org.junit.Assert.*; @@ -83,11 +84,14 @@ public class PhenotypeAssociationTest extends BaseSpringContextTest { @Before public void setUp() throws Exception { - OntologyTestUtils.initialize( diseaseOntologyService, - this.getClass().getResourceAsStream( "/data/loader/ontology/dotest.owl.xml" ) ); - OntologyUtils.ensureInitialized( humanPhenotypeOntologyService ); - OntologyUtils.ensureInitialized( mammalianPhenotypeOntologyService ); - + // this part dirties the context + if ( !diseaseOntologyService.isOntologyLoaded() ) { + try ( InputStream stream = new ClassPathResource( "/data/loader/ontology/dotest.owl.xml" ).getInputStream() ) { + diseaseOntologyService.initialize( stream, false ); + } + humanPhenotypeOntologyService.initialize( true, false ); + mammalianPhenotypeOntologyService.initialize( true, false ); + } // create what will be needed for tests this.createGene(); this.createExternalDatabase(); @@ -174,7 +178,6 @@ public void testloadAllNeurocartaPhenotypes() { } @Test - @Category(SlowTest.class) public void testLoadAllPhenotypeUris() { Set uris = this.phenotypeAssociationService.loadAllUsedPhenotypeUris(); assertTrue( !uris.isEmpty() ); @@ -323,7 +326,7 @@ private void createLiteratureEvidence( int geneNCBIid, String uri ) { SortedSet phenotypes = new TreeSet<>(); - CharacteristicValueObject characteristicValueObject = new CharacteristicValueObject( -1L, uri ); + CharacteristicValueObject characteristicValueObject = new CharacteristicValueObject( "", uri ); phenotypes.add( characteristicValueObject ); diff --git a/gemma-core/src/test/java/ubic/gemma/model/common/description/CharacteristicServiceTest.java b/gemma-core/src/test/java/ubic/gemma/model/common/description/CharacteristicServiceTest.java index 5cae86cf6f..ee875a66ed 100644 --- a/gemma-core/src/test/java/ubic/gemma/model/common/description/CharacteristicServiceTest.java +++ b/gemma-core/src/test/java/ubic/gemma/model/common/description/CharacteristicServiceTest.java @@ -31,6 +31,7 @@ import ubic.gemma.model.expression.experiment.ExperimentalFactor; import ubic.gemma.model.expression.experiment.ExpressionExperiment; import ubic.gemma.model.expression.experiment.FactorValue; +import ubic.gemma.model.expression.experiment.Statement; import ubic.gemma.persistence.service.TableMaintenanceUtil; import ubic.gemma.persistence.service.common.description.CharacteristicService; import ubic.gemma.persistence.service.expression.biomaterial.BioMaterialService; @@ -87,7 +88,7 @@ public void setUp() throws Exception { } FactorValue fv = ef.getFactorValues().iterator().next(); - fv.setCharacteristics( this.getTestPersistentCharacteristics( 1 ) ); + fv.setCharacteristics( this.getTestPersistentStatements( 1 ) ); fvService.update( fv ); tableMaintenanceUtil.updateExpressionExperiment2CharacteristicEntries(); @@ -159,6 +160,19 @@ private Set getTestPersistentCharacteristics( int n ) { return chars; } + private Set getTestPersistentStatements( int n ) { + Set chars = new HashSet<>(); + for ( int i = 0; i < n; ++i ) { + Statement c = Statement.Factory.newInstance(); + c.setCategory( "test" ); + c.setValue( RandomStringUtils.randomNumeric( 10 ) ); + c.setValueUri( "http://www.ebi.ac.uk/efo/EFO_" + RandomStringUtils.randomAlphabetic( 7 ) ); + characteristicService.create( c ); + chars.add( c ); + } + return chars; + } + // @SuppressWarnings("unchecked") // public void testGetByTaxon() throws Exception { // TaxonService taxonService = ( TaxonService ) this.getBean( "taxonService" ); diff --git a/gemma-core/src/test/java/ubic/gemma/model/common/description/CharacteristicTest.java b/gemma-core/src/test/java/ubic/gemma/model/common/description/CharacteristicTest.java index b9c2fd539f..b673c257f5 100644 --- a/gemma-core/src/test/java/ubic/gemma/model/common/description/CharacteristicTest.java +++ b/gemma-core/src/test/java/ubic/gemma/model/common/description/CharacteristicTest.java @@ -9,10 +9,25 @@ import java.util.SortedSet; import java.util.TreeSet; -import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.api.Assertions.assertThat; public class CharacteristicTest { + @Test + public void testEquals() { + Characteristic a = createTransientCharacteristic( "a", null ); + Characteristic A = createTransientCharacteristic( "A", null ); + assertThat( createTransientCharacteristic( "a", null ) ) + .isEqualTo( a ) + .hasSameHashCodeAs( a ) + .isEqualByComparingTo( a ) + .isEqualTo( A ) + .hasSameHashCodeAs( A ) + .isEqualByComparingTo( A ) + .isNotEqualTo( createTransientCharacteristic( "a", null, "foo", null ) ) + .isNotEqualByComparingTo( createTransientCharacteristic( "a", null, "foo", null ) ); + } + @Test public void testComparator() { List cs = Arrays.asList( @@ -31,23 +46,34 @@ public void testComparator() { assertThat( sortedCs ) .extracting( "valueUri", "value" ) .containsExactly( + Tuple.tuple( "d", "D" ), + Tuple.tuple( null, "TEST" ), Tuple.tuple( "a", null ), Tuple.tuple( "b", null ), Tuple.tuple( "C", null ), - Tuple.tuple( "d", "D" ), Tuple.tuple( "e", null ), - Tuple.tuple( null, "TEST" ), Tuple.tuple( null, null ) ); } + private static Characteristic createTransientCharacteristic( @Nullable String valueUri, @Nullable String value, @Nullable String categoryUri, @Nullable String category ) { + Characteristic c = new Characteristic(); + c.setValueUri( valueUri ); + c.setValue( value ); + c.setCategoryUri( categoryUri ); + c.setCategory( category ); + return c; + } + + private static Characteristic createTransientCharacteristic( @Nullable String valueUri, @Nullable String value ) { + return createTransientCharacteristic( valueUri, value, null, null ); + } + private static long i = 0L; private static Characteristic createCharacteristic( @Nullable String valueUri, @Nullable String value ) { - Characteristic c = new Characteristic(); + Characteristic c = createTransientCharacteristic( valueUri, value ); c.setId( ++i ); // to mimic different terms being aggregated by value/value URI - c.setValueUri( valueUri ); - c.setValue( value ); return c; } diff --git a/gemma-core/src/test/java/ubic/gemma/model/common/description/CharacteristicUtilsTest.java b/gemma-core/src/test/java/ubic/gemma/model/common/description/CharacteristicUtilsTest.java new file mode 100644 index 0000000000..93e1c65cab --- /dev/null +++ b/gemma-core/src/test/java/ubic/gemma/model/common/description/CharacteristicUtilsTest.java @@ -0,0 +1,18 @@ +package ubic.gemma.model.common.description; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class CharacteristicUtilsTest { + + @Test + public void test() { + // terms with identical URIs are collapsed + assertEquals( 0, CharacteristicUtils.compareTerm( "a", "test", "b", "test" ) ); + // terms with different URIs are compared by label + assertEquals( -1, CharacteristicUtils.compareTerm( "a", "test", "b", "bar" ) ); + assertEquals( 1, CharacteristicUtils.compareTerm( "b", "test", "a", "bar" ) ); + } + +} \ No newline at end of file diff --git a/gemma-core/src/test/java/ubic/gemma/model/expression/experiment/ExpressionExperimentServiceIntegrationTest.java b/gemma-core/src/test/java/ubic/gemma/model/expression/experiment/ExpressionExperimentServiceIntegrationTest.java index d197f10649..f4633ccb40 100644 --- a/gemma-core/src/test/java/ubic/gemma/model/expression/experiment/ExpressionExperimentServiceIntegrationTest.java +++ b/gemma-core/src/test/java/ubic/gemma/model/expression/experiment/ExpressionExperimentServiceIntegrationTest.java @@ -30,7 +30,6 @@ import ubic.gemma.model.common.auditAndSecurity.Contact; import ubic.gemma.model.common.description.Characteristic; import ubic.gemma.model.common.description.DatabaseEntry; -import ubic.gemma.model.common.description.ExternalDatabase; import ubic.gemma.model.common.quantitationtype.QuantitationType; import ubic.gemma.model.expression.arrayDesign.ArrayDesign; import ubic.gemma.model.expression.bioAssay.BioAssay; @@ -63,6 +62,7 @@ public class ExpressionExperimentServiceIntegrationTest extends BaseSpringContextTest { private static final String EE_NAME = RandomStringUtils.randomAlphanumeric( 20 ); + @Autowired private ExpressionExperimentService expressionExperimentService; @Autowired @@ -74,48 +74,41 @@ public class ExpressionExperimentServiceIntegrationTest extends BaseSpringContex @Autowired private CharacteristicService characteristicService; - private static ExpressionExperiment ee = null; - private ExternalDatabase ed; - private String accession; + /** + * A collection of {@link ExpressionExperiment} that will be removed at the end of the test. + */ + private List ees; @Before - public void setUp() throws Exception { - ee = this.getTestPersistentCompleteExpressionExperiment( false ); - ee.setName( ExpressionExperimentServiceIntegrationTest.EE_NAME ); - - DatabaseEntry accessionEntry = this.getTestPersistentDatabaseEntry(); - accession = accessionEntry.getAccession(); - ed = accessionEntry.getExternalDatabase(); - ee.setAccession( accessionEntry ); - - Contact c = this.getTestPersistentContact(); - ee.setOwner( c ); - - ee.getCharacteristics().add( Characteristic.Factory.newInstance() ); - - expressionExperimentService.update( ee ); - ee = expressionExperimentService.thaw( ee ); + public void setUp() { + ees = new ArrayList<>(); } @After public void tearDown() { - if ( ee != null ) { - expressionExperimentService.remove( ee ); - } + expressionExperimentService.remove( ees ); + ees.clear(); } @Test public final void testFindByAccession() { - DatabaseEntry accessionEntry = DatabaseEntry.Factory.newInstance( ed ); - accessionEntry.setAccession( accession ); + ExpressionExperiment ee = createExpressionExperiment(); + DatabaseEntry de = createDatabaseEntry(); + + ee.setAccession( de ); + expressionExperimentService.update( ee ); + + DatabaseEntry accessionEntry = DatabaseEntry.Factory.newInstance( de.getExternalDatabase() ); + accessionEntry.setAccession( de.getAccession() ); Collection expressionExperiment = expressionExperimentService .findByAccession( accessionEntry ); - assertTrue( expressionExperiment.size() > 0 ); + assertFalse( expressionExperiment.isEmpty() ); } @Test public void testFindByFactor() { + ExpressionExperiment ee = createExpressionExperiment(); ExperimentalDesign design = ee.getExperimentalDesign(); assertNotNull( design.getExperimentalFactors() ); ExperimentalFactor ef = design.getExperimentalFactors().iterator().next(); @@ -127,6 +120,7 @@ public void testFindByFactor() { @Test public void testFindByFactorValue() { + ExpressionExperiment ee = createExpressionExperiment(); ExperimentalDesign design = ee.getExperimentalDesign(); assertNotNull( design.getExperimentalFactors() ); ExperimentalFactor ef = design.getExperimentalFactors().iterator().next(); @@ -139,6 +133,7 @@ public void testFindByFactorValue() { @Test public void testFindByFactorValueId() { + ExpressionExperiment ee = createExpressionExperiment(); ExperimentalDesign design = ee.getExperimentalDesign(); assertNotNull( design.getExperimentalFactors() ); ExperimentalFactor ef = design.getExperimentalFactors().iterator().next(); @@ -152,23 +147,27 @@ public void testFindByFactorValueId() { @Test public void testLoadAllValueObjects() { + ExpressionExperiment ee = createExpressionExperiment(); Collection vos = expressionExperimentService.loadAllValueObjects(); - assertNotNull( vos ); - assertTrue( vos.size() > 0 ); + assertThat( vos ) + .extracting( ExpressionExperimentValueObject::getId ) + .contains( ee.getId() ); // FIXME: use containsExactly, but there are unexpected fixtures from other tests } @Test public void testGetByTaxon() { + ExpressionExperiment ee = createExpressionExperiment(); Taxon taxon = taxonService.findByCommonName( "mouse" ); Collection list = expressionExperimentService.findByTaxon( taxon ); assertNotNull( list ); + assertTrue( list.contains( ee ) ); Taxon checkTaxon = expressionExperimentService.getTaxon( list.iterator().next() ); assertEquals( taxon, checkTaxon ); - } @Test public final void testGetDesignElementDataVectorsByQt() { + ExpressionExperiment ee = createExpressionExperiment(); QuantitationType quantitationType = ee.getRawExpressionDataVectors().iterator().next().getQuantitationType(); Collection quantitationTypes = new HashSet<>(); quantitationTypes.add( quantitationType ); @@ -178,23 +177,26 @@ public final void testGetDesignElementDataVectorsByQt() { @Test public final void testGetPerTaxonCount() { + ExpressionExperiment ee = createExpressionExperiment(); Map counts = expressionExperimentService.getPerTaxonCount(); - long oldCount = counts.get( taxonService.findByCommonName( "mouse" ) ); + Long oldCount = counts.get( taxonService.findByCommonName( "mouse" ) ); assertNotNull( counts ); expressionExperimentService.remove( ee ); - ee = null; + ees.remove( ee ); counts = expressionExperimentService.getPerTaxonCount(); assertEquals( oldCount - 1, counts.getOrDefault( taxonService.findByCommonName( "mouse" ), 0L ).longValue() ); } @Test public final void testGetQuantitationTypes() { + ExpressionExperiment ee = createExpressionExperiment(); Collection types = expressionExperimentService.getQuantitationTypes( ee ); assertEquals( 2, types.size() ); } @Test public void testGetPreferredQuantitationType() { + ExpressionExperiment ee = createExpressionExperiment(); QuantitationType qt = expressionExperimentService.getPreferredQuantitationType( ee ); assertNotNull( qt ); assertTrue( qt.getIsPreferred() ); @@ -202,22 +204,26 @@ public void testGetPreferredQuantitationType() { @Test public void testGetBioMaterialCount() { + ExpressionExperiment ee = createExpressionExperiment(); assertEquals( 8, expressionExperimentService.getBioMaterialCount( ee ) ); } @Test public void testGetQuantitationTypeCount() { + ExpressionExperiment ee = createExpressionExperiment(); Map qts = expressionExperimentService.getQuantitationTypeCount( ee ); assertEquals( 2, qts.size() ); } @Test public void testGetDesignElementDataVectorCount() { + ExpressionExperiment ee = createExpressionExperiment(); assertEquals( 24, expressionExperimentService.getDesignElementDataVectorCount( ee ) ); } @Test public final void testGetQuantitationTypesForArrayDesign() { + ExpressionExperiment ee = createExpressionExperiment(); ArrayDesign ad = ee.getRawExpressionDataVectors().iterator().next().getDesignElement().getArrayDesign(); Collection types = expressionExperimentService.getQuantitationTypes( ee, ad ); assertEquals( 2, types.size() ); @@ -226,6 +232,7 @@ public final void testGetQuantitationTypesForArrayDesign() { @Test public final void testGetRawExpressionDataVectors() { ExpressionExperiment eel = this.getTestPersistentCompleteExpressionExperiment( false ); + ees.add( eel ); Collection designElements = new HashSet<>(); QuantitationType quantitationType = eel.getRawExpressionDataVectors().iterator().next().getQuantitationType(); Collection allv = eel.getRawExpressionDataVectors(); @@ -313,6 +320,7 @@ public final void testGetFilter() { @Test public final void testLoadValueObjectsByIds() { + ExpressionExperiment ee = createExpressionExperiment(); Collection ids = new HashSet<>(); Long id = ee.getId(); ids.add( id ); @@ -323,6 +331,7 @@ public final void testLoadValueObjectsByIds() { @Test public void testLoadValueObjectsByCharacteristic() { + ExpressionExperiment ee = createExpressionExperiment(); Characteristic c = ee.getCharacteristics().stream().findFirst().orElse( null ); assertThat( c ).isNotNull(); Filter of = expressionExperimentService.getFilter( "characteristics.id", Filter.Operator.eq, c.getId().toString() ); @@ -355,6 +364,7 @@ public void testLoadValueObjectsBySampleUsedCharacteristic() { @Test public void testLoadValueObjectsByBioAssay() { + ExpressionExperiment ee = createExpressionExperiment(); BioAssay ba = ee.getBioAssays().stream().findFirst().orElse( null ); assertThat( ba ).isNotNull(); Filter of = expressionExperimentService.getFilter( "bioAssays.id", Filter.Operator.eq, ba.getId().toString() ); @@ -372,6 +382,7 @@ public void testLoadValueObjectsByBioAssay() { @Test public void testLoadBlacklistedValueObjects() { + ExpressionExperiment ee = createExpressionExperiment(); blacklistedEntityService.blacklistExpressionExperiment( ee, "Don't feel bad!" ); assertThat( blacklistedEntityService.isBlacklisted( ee ) ).isTrue(); Slice result = expressionExperimentService.loadBlacklistedValueObjects( null, null, 0, 10 ); @@ -418,6 +429,7 @@ public void testFilterBySuitabilityScoreAsNonAdmin() { @Test public void testCacheInvalidationWhenACharacteristicIsDeleted() { + ExpressionExperiment ee = createExpressionExperiment(); Characteristic c = new Characteristic(); c.setCategory( "bar" ); c.setValue( "foo" ); @@ -463,7 +475,7 @@ public void testSaveWithTransientEntity() { ExpressionExperiment ee = new ExpressionExperiment(); ExpressionExperiment createdEE = expressionExperimentService.save( ee ); assertNotNull( createdEE.getId() ); - // ees.add( createdEE ); + ees.add( createdEE ); assertThat( createdEE.getAuditTrail().getEvents() ) .extracting( AuditEvent::getAction ) .containsExactly( AuditAction.CREATE ); @@ -481,4 +493,22 @@ public void testUpdateWithTransientEntity() { .isInstanceOf( IllegalArgumentException.class ) .hasMessageContaining( "ID is required to be non-null" ); } + + private ExpressionExperiment createExpressionExperiment() { + ExpressionExperiment ee = this.getTestPersistentCompleteExpressionExperiment( false ); + ees.add( ee ); + ee.setName( ExpressionExperimentServiceIntegrationTest.EE_NAME ); + + Contact c = this.getTestPersistentContact(); + ee.setOwner( c ); + + ee.getCharacteristics().add( Characteristic.Factory.newInstance() ); + + expressionExperimentService.update( ee ); + return expressionExperimentService.thaw( ee ); + } + + private DatabaseEntry createDatabaseEntry() { + return this.getTestPersistentDatabaseEntry(); + } } diff --git a/gemma-core/src/test/java/ubic/gemma/model/expression/experiment/FactorValueServiceIntegrationTest.java b/gemma-core/src/test/java/ubic/gemma/model/expression/experiment/FactorValueServiceIntegrationTest.java new file mode 100644 index 0000000000..6891a05a70 --- /dev/null +++ b/gemma-core/src/test/java/ubic/gemma/model/expression/experiment/FactorValueServiceIntegrationTest.java @@ -0,0 +1,94 @@ +/* + * The Gemma project + * + * Copyright (c) 2011 University of British Columbia + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package ubic.gemma.model.expression.experiment; + +import org.assertj.core.api.Assertions; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import ubic.gemma.core.util.test.BaseSpringContextTest; +import ubic.gemma.model.expression.bioAssay.BioAssay; +import ubic.gemma.model.expression.biomaterial.BioMaterial; +import ubic.gemma.persistence.service.expression.biomaterial.BioMaterialService; +import ubic.gemma.persistence.service.expression.experiment.ExpressionExperimentService; +import ubic.gemma.persistence.service.expression.experiment.FactorValueService; + +import static org.junit.Assert.*; + +/** + * @author paul + */ +public class FactorValueServiceIntegrationTest extends BaseSpringContextTest { + + @Autowired + private FactorValueService factorValueService; + + @Autowired + private ExpressionExperimentService expressionExperimentService; + + @Autowired + private BioMaterialService bioMaterialService; + + private ExpressionExperiment ee; + + @Before + public void setUp() { + ee = getTestPersistentCompleteExpressionExperiment( false ); + } + + @After + public void tearDown() { + expressionExperimentService.remove( ee ); + } + + @Test + public void testDelete() { + ee = expressionExperimentService.thawLite( ee ); + + FactorValue fv = ee.getExperimentalDesign().getExperimentalFactors().iterator().next().getFactorValues() + .iterator().next(); + + // attach the FV to all the samples + for ( BioAssay ba : ee.getBioAssays() ) { + BioMaterial bm = ba.getSampleUsed(); + bm.getFactorValues().add( fv ); + bioMaterialService.update( bm ); + } + + ee = expressionExperimentService.thawLite( ee ); + Assertions.assertThat( ee.getBioAssays() ).allSatisfy( ba -> { + BioMaterial bm = ba.getSampleUsed(); + assertTrue( bm.getFactorValues().contains( fv ) ); + } ); + + // delete the FV + Long id = fv.getId(); + factorValueService.remove( fv ); + assertNull( factorValueService.load( id ) ); + + ee = expressionExperimentService.thawLite( ee ); + Assertions.assertThat( ee.getBioAssays() ).allSatisfy( ba -> { + BioMaterial bm = ba.getSampleUsed(); + assertFalse( bm.getFactorValues().contains( fv ) ); + } ); + } + +} diff --git a/gemma-core/src/test/java/ubic/gemma/model/expression/experiment/FactorValueServiceTest.java b/gemma-core/src/test/java/ubic/gemma/model/expression/experiment/FactorValueServiceTest.java index 141cc10ca3..875243b925 100644 --- a/gemma-core/src/test/java/ubic/gemma/model/expression/experiment/FactorValueServiceTest.java +++ b/gemma-core/src/test/java/ubic/gemma/model/expression/experiment/FactorValueServiceTest.java @@ -1,40 +1,72 @@ -/* - * The Gemma project - * - * Copyright (c) 2011 University of British Columbia - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - package ubic.gemma.model.expression.experiment; +import org.hibernate.SessionFactory; +import org.junit.After; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.access.AccessDecisionManager; +import org.springframework.test.context.ContextConfiguration; +import ubic.gemma.core.util.test.BaseDatabaseTest; +import ubic.gemma.model.common.auditAndSecurity.AuditEvent; +import ubic.gemma.model.common.auditAndSecurity.eventType.DoesNotNeedAttentionEvent; +import ubic.gemma.model.common.auditAndSecurity.eventType.FactorValueNeedsAttentionEvent; +import ubic.gemma.model.common.auditAndSecurity.eventType.NeedsAttentionEvent; +import ubic.gemma.persistence.service.common.auditAndSecurity.AuditEventService; +import ubic.gemma.persistence.service.common.auditAndSecurity.AuditTrailService; +import ubic.gemma.persistence.service.expression.experiment.*; +import ubic.gemma.persistence.util.TestComponent; -import ubic.gemma.core.util.test.BaseSpringContextTest; -import ubic.gemma.model.expression.bioAssay.BioAssay; -import ubic.gemma.model.expression.biomaterial.BioMaterial; -import ubic.gemma.persistence.service.expression.biomaterial.BioMaterialService; -import ubic.gemma.persistence.service.expression.experiment.ExpressionExperimentService; -import ubic.gemma.persistence.service.expression.experiment.FactorValueService; +import java.util.Collections; +import java.util.Date; +import java.util.Set; import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +@ContextConfiguration +public class FactorValueServiceTest extends BaseDatabaseTest { + + @Configuration + @TestComponent + static class FactorValueServiceTestContextConfiguration extends BaseDatabaseTestContextConfiguration { -/** - * @author paul - */ -public class FactorValueServiceTest extends BaseSpringContextTest { + @Bean + public FactorValueDao factorValueDao( SessionFactory sessionFactory ) { + return new FactorValueDaoImpl( sessionFactory ); + } + + @Bean + public StatementDao statementDao( SessionFactory sessionFactory ) { + return new StatementDaoImpl( sessionFactory ); + } + + @Bean + public FactorValueService factorValueService( ExpressionExperimentService expressionExperimentService, AuditTrailService auditTrailService, AuditEventService auditEventService, FactorValueDao factorValueDao, StatementDao statementDao ) { + return new FactorValueServiceImpl( expressionExperimentService, auditTrailService, auditEventService, factorValueDao, statementDao ); + } + + @Bean + public ExpressionExperimentService expressionExperimentService() { + return mock(); + } + + @Bean + public AuditTrailService auditTrailService() { + return mock(); + } + + @Bean + public AuditEventService auditEventService() { + return mock(); + } + + @Bean + public AccessDecisionManager accessDecisionManager() { + return mock(); + } + } @Autowired private FactorValueService factorValueService; @@ -42,45 +74,184 @@ public class FactorValueServiceTest extends BaseSpringContextTest { @Autowired private ExpressionExperimentService expressionExperimentService; - @Autowired - private BioMaterialService bioMaterialService; + @After + public void tearDown() { + reset( auditTrailService, auditEventService ); + } @Test - public void testDelete() { + public void testCreateStatement() { + FactorValue fv = createFactorValue(); + Statement s1; + s1 = Statement.Factory.newInstance(); + s1.setObject( "test" ); + fv.getCharacteristics().add( s1 ); + s1 = factorValueService.createStatement( fv, s1 ); + assertNotNull( s1.getId() ); + assertTrue( fv.getCharacteristics().contains( s1 ) ); + } - ExpressionExperiment ee = super.getTestPersistentCompleteExpressionExperiment( false ); + @Test + public void testCreateStatementWithDetachedFactorValue() { + FactorValue fv = createFactorValue(); + sessionFactory.getCurrentSession().evict( fv ); + assertFalse( sessionFactory.getCurrentSession().contains( fv ) ); + Statement s1; + s1 = Statement.Factory.newInstance(); + s1.setObject( "test" ); + fv.getCharacteristics().add( s1 ); + s1 = factorValueService.createStatement( fv, s1 ); + assertNotNull( s1.getId() ); + assertTrue( fv.getCharacteristics().contains( s1 ) ); + assertTrue( sessionFactory.getCurrentSession().contains( fv ) ); + } - ee = expressionExperimentService.thawLite( ee ); + @Test + public void testSaveStatement() { + FactorValue fv = createFactorValue(); + Statement s1; + s1 = Statement.Factory.newInstance(); + s1.setObject( "test" ); + fv.getCharacteristics().add( s1 ); + s1 = factorValueService.saveStatement( fv, s1 ); + assertNotNull( s1.getId() ); + s1 = factorValueService.saveStatement( fv, s1 ); + Long previousId = s1.getId(); + assertEquals( previousId, s1.getId() ); + } - FactorValue fv = ee.getExperimentalDesign().getExperimentalFactors().iterator().next().getFactorValues() - .iterator().next(); + @Test + public void testRemoveStatement() { + FactorValue fv = createFactorValue(); + Statement s1; + s1 = Statement.Factory.newInstance(); + s1.setObject( "test" ); + fv.getCharacteristics().add( s1 ); + sessionFactory.getCurrentSession().persist( fv ); + assertNotNull( fv.getId() ); + assertNotNull( s1.getId() ); - for ( BioAssay ba : ee.getBioAssays() ) { - BioMaterial bm = ba.getSampleUsed(); - bm.getFactorValues().add( fv ); - bioMaterialService.update( bm ); + // later on + fv = reload( fv ); + s1 = ( Statement ) sessionFactory.getCurrentSession().get( Statement.class, s1.getId() ); + factorValueService.removeStatement( fv, s1 ); - } + fv = reload( fv ); + assertTrue( fv.getCharacteristics().isEmpty() ); + } - ee = expressionExperimentService.thawLite( ee ); + @Test + public void testRemoveDetachedStatementFromDetachedFactorValue() { + FactorValue fv = createFactorValue(); + Statement s1; + s1 = Statement.Factory.newInstance(); + s1.setObject( "test" ); + sessionFactory.getCurrentSession().persist( s1 ); + fv.getCharacteristics().add( s1 ); + sessionFactory.getCurrentSession().persist( fv ); + sessionFactory.getCurrentSession().flush(); + sessionFactory.getCurrentSession().evict( fv ); + factorValueService.removeStatement( fv, s1 ); + } - // done with setup + @Test + public void testRemoveUnrelatedStatementRaisesAnException() { + FactorValue fv = createFactorValue(); + Statement s = Statement.Factory.newInstance(); + sessionFactory.getCurrentSession().persist( s ); + assertThrows( IllegalArgumentException.class, () -> factorValueService.removeStatement( fv, s ) ); + } - for ( BioAssay ba : ee.getBioAssays() ) { - BioMaterial bm = ba.getSampleUsed(); - assertTrue( bm.getFactorValues().size() > 0 ); - fv = bm.getFactorValues().iterator().next(); + @Autowired + private AuditTrailService auditTrailService; - } + @Autowired + private AuditEventService auditEventService; - assertNotNull( fv ); + @Test + public void testClearNeedsAttentionFlag() { + FactorValue fv = createFactorValue(); + ExpressionExperiment ee = new ExpressionExperiment(); + ee.setExperimentalDesign( fv.getExperimentalFactor().getExperimentalDesign() ); + sessionFactory.getCurrentSession().persist( ee ); + when( expressionExperimentService.findByFactorValue( fv ) ).thenReturn( ee ); + factorValueService.markAsNeedsAttention( fv, "test" ); + ee.getCurationDetails().setNeedsAttention( true ); + assertTrue( fv.getNeedsAttention() ); + verify( auditTrailService ).addUpdateEvent( ee, FactorValueNeedsAttentionEvent.class, "FactorValue " + fv.getId() + ": ExperimentalFactor #" + fv.getExperimentalFactor().getId() + ": ? needs attention: test" ); + when( auditEventService.getLastEvent( ee, NeedsAttentionEvent.class, Collections.singleton( FactorValueNeedsAttentionEvent.class ) ) ) + .thenReturn( null ); + when( auditEventService.getLastEvent( ee, DoesNotNeedAttentionEvent.class ) ) + .thenReturn( null ); + factorValueService.clearNeedsAttentionFlag( fv, "foo" ); + verify( auditEventService ).getLastEvent( ee, NeedsAttentionEvent.class, Collections.singleton( FactorValueNeedsAttentionEvent.class ) ); + verify( auditEventService ).getLastEvent( ee, DoesNotNeedAttentionEvent.class ); + verify( auditTrailService ).addUpdateEvent( ee, DoesNotNeedAttentionEvent.class, "foo" ); + } - Long id = fv.getId(); + @Test + public void testClearNeedsAttentionFlagThatAlreadyNeedsAttention() { + FactorValue fv = createFactorValue(); + ExpressionExperiment ee = new ExpressionExperiment(); + ee.setExperimentalDesign( fv.getExperimentalFactor().getExperimentalDesign() ); + sessionFactory.getCurrentSession().persist( ee ); + when( expressionExperimentService.findByFactorValue( fv ) ).thenReturn( ee ); + factorValueService.markAsNeedsAttention( fv, "test" ); + ee.getCurationDetails().setNeedsAttention( true ); + assertTrue( fv.getNeedsAttention() ); + verify( auditTrailService ).addUpdateEvent( ee, FactorValueNeedsAttentionEvent.class, "FactorValue " + fv.getId() + ": ExperimentalFactor #" + fv.getExperimentalFactor().getId() + ": ? needs attention: test" ); + when( auditEventService.getLastEvent( ee, NeedsAttentionEvent.class, Collections.singleton( FactorValueNeedsAttentionEvent.class ) ) ) + .thenReturn( new AuditEvent() ); + when( auditEventService.getLastEvent( ee, DoesNotNeedAttentionEvent.class ) ) + .thenReturn( null ); + factorValueService.clearNeedsAttentionFlag( fv, "foo" ); + verify( auditEventService ).getLastEvent( ee, NeedsAttentionEvent.class, Collections.singleton( FactorValueNeedsAttentionEvent.class ) ); + verify( auditEventService ).getLastEvent( ee, DoesNotNeedAttentionEvent.class ); + verifyNoMoreInteractions( auditEventService ); + } - factorValueService.remove( fv ); + @Test + public void testClearNeedsAttentionFlagWhenANeedsAttentionEventWasResolved() { + FactorValue fv = createFactorValue(); + ExpressionExperiment ee = new ExpressionExperiment(); + ee.setExperimentalDesign( fv.getExperimentalFactor().getExperimentalDesign() ); + sessionFactory.getCurrentSession().persist( ee ); + when( expressionExperimentService.findByFactorValue( fv ) ).thenReturn( ee ); + factorValueService.markAsNeedsAttention( fv, "test" ); + ee.getCurationDetails().setNeedsAttention( true ); + assertTrue( fv.getNeedsAttention() ); + verify( auditTrailService ).addUpdateEvent( ee, FactorValueNeedsAttentionEvent.class, "FactorValue " + fv.getId() + ": ExperimentalFactor #" + fv.getExperimentalFactor().getId() + ": ? needs attention: test" ); + when( auditEventService.getLastEvent( ee, NeedsAttentionEvent.class, Collections.singleton( FactorValueNeedsAttentionEvent.class ) ) ) + .thenReturn( AuditEvent.Factory.newInstance( new Date( 1000 ), null, null, null, null, new NeedsAttentionEvent() ) ); + when( auditEventService.getLastEvent( ee, DoesNotNeedAttentionEvent.class ) ) + .thenReturn( AuditEvent.Factory.newInstance( new Date( 2000 ), null, null, null, null, new NeedsAttentionEvent() ) ); + factorValueService.clearNeedsAttentionFlag( fv, "foo" ); + verify( auditEventService ).getLastEvent( ee, NeedsAttentionEvent.class, Collections.singleton( FactorValueNeedsAttentionEvent.class ) ); + verify( auditEventService ).getLastEvent( ee, DoesNotNeedAttentionEvent.class ); + verify( auditTrailService ).addUpdateEvent( ee, DoesNotNeedAttentionEvent.class, "foo" ); + } - assertNull( factorValueService.load( id ) ); + private FactorValue createFactorValue() { + return createFactorValue( Collections.emptySet() ); + } + private FactorValue createFactorValue( Set statements ) { + ExperimentalDesign ed = new ExperimentalDesign(); + sessionFactory.getCurrentSession().persist( ed ); + ExperimentalFactor ef = new ExperimentalFactor(); + ef.setType( FactorType.CATEGORICAL ); + ef.setExperimentalDesign( ed ); + sessionFactory.getCurrentSession().persist( ef ); + FactorValue fv = FactorValue.Factory.newInstance(); + fv.setExperimentalFactor( ef ); + fv.getCharacteristics().addAll( statements ); + sessionFactory.getCurrentSession().persist( fv ); + return fv; } + private FactorValue reload( FactorValue fv ) { + sessionFactory.getCurrentSession().flush(); + sessionFactory.getCurrentSession().clear(); + return ( FactorValue ) sessionFactory.getCurrentSession().get( FactorValue.class, fv.getId() ); + } } diff --git a/gemma-core/src/test/java/ubic/gemma/model/expression/experiment/FactorValueUtilsTest.java b/gemma-core/src/test/java/ubic/gemma/model/expression/experiment/FactorValueUtilsTest.java new file mode 100644 index 0000000000..7d8ca77d50 --- /dev/null +++ b/gemma-core/src/test/java/ubic/gemma/model/expression/experiment/FactorValueUtilsTest.java @@ -0,0 +1,84 @@ +package ubic.gemma.model.expression.experiment; + +import org.junit.Test; +import ubic.gemma.model.common.description.Characteristic; +import ubic.gemma.model.common.measurement.Measurement; +import ubic.gemma.model.common.measurement.MeasurementType; +import ubic.gemma.model.common.measurement.Unit; +import ubic.gemma.model.common.quantitationtype.PrimitiveType; + +import static org.junit.Assert.assertEquals; + +public class FactorValueUtilsTest { + + @Test + public void test() { + FactorValue fv; + + fv = new FactorValue(); + fv.getCharacteristics().add( createStatement( "methoxyacetic acid", "delivered at dose", "650 mg/kg" ) ); + assertEquals( "methoxyacetic acid delivered at dose 650 mg/kg", FactorValueUtils.getSummaryString( fv ) ); + + fv = new FactorValue(); + fv.getCharacteristics().add( createStatement( "methoxyacetic acid", "delivered at dose", "650 mg/kg", "for duration", "4h" ) ); + assertEquals( "methoxyacetic acid delivered at dose 650 mg/kg and for duration 4h", FactorValueUtils.getSummaryString( fv ) ); + + fv = new FactorValue(); + fv.getCharacteristics().add( createStatement( "methoxyacetic acid", null, null, "for duration", "4h" ) ); + assertEquals( "methoxyacetic acid for duration 4h", FactorValueUtils.getSummaryString( fv ) ); + } + + @Test + public void testMeasurement() { + FactorValue fv = new FactorValue(); + fv.setExperimentalFactor( new ExperimentalFactor() ); + Measurement m = Measurement.Factory.newInstance( MeasurementType.ABSOLUTE, "5.0", PrimitiveType.DOUBLE ); + m.setUnit( Unit.Factory.newInstance( "mg" ) ); + fv.setMeasurement( m ); + assertEquals( "5.0 mg", FactorValueUtils.getSummaryString( fv ) ); + } + + + @Test + public void testMeasurementWithoutCategory() { + FactorValue fv = new FactorValue(); + Measurement m = Measurement.Factory.newInstance( MeasurementType.ABSOLUTE, "5.0", PrimitiveType.DOUBLE ); + m.setUnit( Unit.Factory.newInstance( "years" ) ); + fv.setMeasurement( m ); + assertEquals( "5.0 years", FactorValueUtils.getSummaryString( fv ) ); + } + + @Test + public void testMeasurementWithCategory() { + ExperimentalFactor age = new ExperimentalFactor(); + Characteristic ageC = new Characteristic(); + ageC.setCategory( "age" ); + age.setCategory( ageC ); + FactorValue fv = new FactorValue(); + fv.setExperimentalFactor( age ); + Measurement m = Measurement.Factory.newInstance( MeasurementType.ABSOLUTE, "5.0", PrimitiveType.DOUBLE ); + m.setUnit( Unit.Factory.newInstance( "years" ) ); + fv.setMeasurement( m ); + assertEquals( "age: 5.0 years", FactorValueUtils.getSummaryString( fv ) ); + } + + private Statement createStatement( String subject, String predicate, String object, String secondPredicate, String secondObject ) { + Statement s = new Statement(); + s.setSubject( subject ); + s.setPredicate( predicate ); + s.setObject( object ); + s.setSecondPredicate( secondPredicate ); + s.setSecondObject( secondObject ); + return s; + } + + private Statement createStatement( String subject, String predicate, String object ) { + return createStatement( subject, predicate, object, null, null ); + } + + private Measurement createMeasurement( String value ) { + Measurement m = new Measurement(); + m.setValue( value ); + return m; + } +} \ No newline at end of file diff --git a/gemma-core/src/test/java/ubic/gemma/model/expression/experiment/StatementTest.java b/gemma-core/src/test/java/ubic/gemma/model/expression/experiment/StatementTest.java new file mode 100644 index 0000000000..d4cb193eaf --- /dev/null +++ b/gemma-core/src/test/java/ubic/gemma/model/expression/experiment/StatementTest.java @@ -0,0 +1,44 @@ +package ubic.gemma.model.expression.experiment; + +import org.junit.Test; + +import javax.annotation.Nullable; + +import static org.assertj.core.api.Assertions.assertThat; + +public class StatementTest { + + @Test + public void testEqual() { + Statement s = createStatement( null, "foo", null, "bar", null, "foo" ); + assertThat( createStatement( null, "foo", null, "bar", null, "foo" ) ) + .isEqualByComparingTo( s ) + .hasSameHashCodeAs( s ) + .isEqualTo( s ); + Statement sWithDifferentCase = createStatement( null, "Foo", null, "baR", null, "fOo" ); + assertThat( createStatement( null, "foo", null, "bar", null, "foo" ) ) + .isEqualByComparingTo( sWithDifferentCase ) + .hasSameHashCodeAs( sWithDifferentCase ) + .isEqualTo( sWithDifferentCase ); + // two statements with the same predicate label but differing URIs + assertThat( createStatement( "", "", "http://foo", "foo", "", "" ) ) + .isNotEqualTo( createStatement( "", "", "http://bar", "foo", "", "" ) ) + .isNotEqualByComparingTo( createStatement( "", "", "http://bar", "foo", "", "" ) ); + // two statements with the same predicate URI but different labels + assertThat( createStatement( "", "", "http://foo", "foo", "", "" ) ) + .isEqualTo( createStatement( "", "", "http://foo", "bar", "", "" ) ) + .isEqualByComparingTo( createStatement( "", "", "http://foo", "bar", "", "" ) ) + .hasSameHashCodeAs( createStatement( "", "", "http://foo", "bar", "", "" ) ); + } + + private Statement createStatement( @Nullable String valueUri, String value, @Nullable String predicateUri, String predicate, @Nullable String objectUri, String object ) { + Statement s = new Statement(); + s.setSubjectUri( valueUri ); + s.setSubject( value ); + s.setPredicateUri( predicateUri ); + s.setPredicate( predicate ); + s.setObjectUri( objectUri ); + s.setObject( object ); + return s; + } +} \ No newline at end of file diff --git a/gemma-core/src/test/java/ubic/gemma/model/genome/gene/GeneSetServiceTest.java b/gemma-core/src/test/java/ubic/gemma/model/genome/gene/GeneSetServiceTest.java index 84ef6582a6..428058f843 100644 --- a/gemma-core/src/test/java/ubic/gemma/model/genome/gene/GeneSetServiceTest.java +++ b/gemma-core/src/test/java/ubic/gemma/model/genome/gene/GeneSetServiceTest.java @@ -24,20 +24,22 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.junit.experimental.categories.Category; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.ClassPathResource; import org.springframework.test.annotation.DirtiesContext; import ubic.gemma.core.genome.gene.service.GeneSetService; import ubic.gemma.core.ontology.providers.GeneOntologyService; -import ubic.gemma.core.ontology.OntologyTestUtils; import ubic.gemma.core.search.GeneSetSearch; import ubic.gemma.core.util.test.BaseSpringContextTest; +import ubic.gemma.core.util.test.category.SlowTest; import ubic.gemma.model.association.GOEvidenceCode; import ubic.gemma.model.association.Gene2GOAssociation; import ubic.gemma.model.common.description.Characteristic; import ubic.gemma.model.genome.Gene; import ubic.gemma.persistence.service.association.Gene2GOAssociationService; +import java.io.IOException; import java.io.InputStream; import java.util.Collection; import java.util.Collections; @@ -48,7 +50,6 @@ /** * @author klc */ -@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) public class GeneSetServiceTest extends BaseSpringContextTest { static private final String GOTERM_INDB = "GO_0000310"; @@ -72,13 +73,8 @@ public class GeneSetServiceTest extends BaseSpringContextTest { @Before public void setUp() throws Exception { - InputStream is = new GZIPInputStream( - new ClassPathResource( "/data/loader/ontology/molecular-function.test.owl.gz" ).getInputStream() ); - OntologyTestUtils.initialize( geneOntologyService, is ); - g = this.getTestPersistentGene(); g3 = this.getTestPersistentGene(); - } @After @@ -134,7 +130,11 @@ public void testFindByGene() { } @Test - public void testFindByGoId() { + @DirtiesContext + public void testFindByGoId() throws IOException { + InputStream is = new GZIPInputStream( + new ClassPathResource( "/data/loader/ontology/molecular-function.test.owl.gz" ).getInputStream() ); + geneOntologyService.initialize( is, false ); Characteristic oe = Characteristic.Factory.newInstance(); oe.setValueUri( GeneOntologyService.BASE_GO_URI + GeneSetServiceTest.GOTERM_INDB ); diff --git a/gemma-core/src/test/java/ubic/gemma/model/genome/gene/phenotype/valueObject/CharacteristicValueObjectTest.java b/gemma-core/src/test/java/ubic/gemma/model/genome/gene/phenotype/valueObject/CharacteristicValueObjectTest.java index c294a9c830..46cf2f6d64 100644 --- a/gemma-core/src/test/java/ubic/gemma/model/genome/gene/phenotype/valueObject/CharacteristicValueObjectTest.java +++ b/gemma-core/src/test/java/ubic/gemma/model/genome/gene/phenotype/valueObject/CharacteristicValueObjectTest.java @@ -19,9 +19,10 @@ package ubic.gemma.model.genome.gene.phenotype.valueObject; -import com.google.common.collect.Lists; import junit.framework.TestCase; +import ubic.gemma.model.common.description.CharacteristicValueObject; +import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -33,6 +34,13 @@ public class CharacteristicValueObjectTest extends TestCase { private CharacteristicValueObject a; private CharacteristicValueObject b; + @Override + protected void setUp() throws Exception { + super.setUp(); + a = new CharacteristicValueObject(); + b = new CharacteristicValueObject(); + } + public void testEqualsA() { TestCase.assertTrue( a.equals( b ) ); } @@ -85,7 +93,7 @@ public void testCompareToNull() { public void testCompareToCategory() { a.setCategory( "aaa" ); TestCase.assertEquals( b.compareTo( a ), -a.compareTo( b ) ); - TestCase.assertTrue( a.compareTo( b ) > 0 ); + TestCase.assertTrue( a.compareTo( b ) < 0 ); b.setCategory( "aaa" ); TestCase.assertEquals( b.compareTo( a ), -a.compareTo( b ) ); @@ -113,7 +121,7 @@ public void testCompareToTaxon() { public void testCompareToValue() { a.setValue( "aaa" ); TestCase.assertEquals( b.compareTo( a ), -a.compareTo( b ) ); - TestCase.assertTrue( a.compareTo( b ) > 0 ); + TestCase.assertTrue( a.compareTo( b ) < 0 ); b.setValue( "aaa" ); TestCase.assertEquals( b.compareTo( a ), -a.compareTo( b ) ); @@ -173,20 +181,11 @@ public void testCompareToOrdering() { TestCase.assertTrue( d.compareTo( e ) < 0 ); // sorting - List toSort = Lists.newArrayList( e, d, c, b, a ); + List toSort = Arrays.asList( e, d, c, b, a ); + List expectedOrder = Arrays.asList( a, b, c, d, e ); Collections.sort( toSort ); - - Long i = 1L; - for ( CharacteristicValueObject cvo : toSort ) { - TestCase.assertEquals( i, cvo.getId() ); - i++; + for ( int i = 0; i < toSort.size(); i++ ) { + TestCase.assertSame( expectedOrder.get( i ), toSort.get( i ) ); } } - - @Override - protected void setUp() throws Exception { - super.setUp(); - a = new CharacteristicValueObject( 1L ); - b = new CharacteristicValueObject( 2L ); - } } diff --git a/gemma-core/src/test/java/ubic/gemma/persistence/service/TableMaintenanceUtilIntegrationTest.java b/gemma-core/src/test/java/ubic/gemma/persistence/service/TableMaintenanceUtilIntegrationTest.java index 5631db9651..6d6ba32f06 100644 --- a/gemma-core/src/test/java/ubic/gemma/persistence/service/TableMaintenanceUtilIntegrationTest.java +++ b/gemma-core/src/test/java/ubic/gemma/persistence/service/TableMaintenanceUtilIntegrationTest.java @@ -6,6 +6,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.security.test.context.support.WithSecurityContextTestExecutionListener; +import org.springframework.test.context.TestExecutionListeners; import ubic.gemma.core.util.test.BaseSpringContextTest; import ubic.gemma.persistence.util.Settings; @@ -18,6 +20,7 @@ /** * @author poirigui */ +@TestExecutionListeners(WithSecurityContextTestExecutionListener.class) public class TableMaintenanceUtilIntegrationTest extends BaseSpringContextTest { private static final Path GENE2CS_PATH = Paths.get( Settings.getString( "gemma.appdata.home" ), "DbReports", "gene2cs.info" ); diff --git a/gemma-core/src/test/java/ubic/gemma/persistence/service/analysis/expression/diff/ExpressionAnalysisResultSetServiceTest.java b/gemma-core/src/test/java/ubic/gemma/persistence/service/analysis/expression/diff/ExpressionAnalysisResultSetServiceTest.java index 267cdf8230..145b12c6cd 100644 --- a/gemma-core/src/test/java/ubic/gemma/persistence/service/analysis/expression/diff/ExpressionAnalysisResultSetServiceTest.java +++ b/gemma-core/src/test/java/ubic/gemma/persistence/service/analysis/expression/diff/ExpressionAnalysisResultSetServiceTest.java @@ -1,17 +1,17 @@ package ubic.gemma.persistence.service.analysis.expression.diff; -import com.google.common.collect.Lists; import org.junit.Ignore; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import ubic.gemma.core.util.test.BaseSpringContextTest; import ubic.gemma.model.analysis.expression.diff.DifferentialExpressionAnalysisResultSetValueObject; -import ubic.gemma.persistence.util.Filters; import ubic.gemma.persistence.util.Filter; +import ubic.gemma.persistence.util.Filters; import ubic.gemma.persistence.util.Slice; import ubic.gemma.persistence.util.Sort; import javax.annotation.Nullable; +import java.util.Collections; import java.util.List; import static org.assertj.core.api.Assertions.assertThat; @@ -28,7 +28,7 @@ public class ExpressionAnalysisResultSetServiceTest extends BaseSpringContextTes @Test public void testLoadValueObjects() { - Filters filters = Filters.by( expressionAnalysisResultSetService.getFilter( "id", Filter.Operator.in, Lists.newArrayList( "1123898912" ) ) ); + Filters filters = Filters.by( expressionAnalysisResultSetService.getFilter( "id", Filter.Operator.in, Collections.singletonList( "1123898912" ) ) ); Sort sort = expressionAnalysisResultSetService.getSort( "id", Sort.Direction.DESC ); List results = expressionAnalysisResultSetService.loadValueObjects( filters, sort ); assertThat( results ).isEmpty(); @@ -36,7 +36,7 @@ public void testLoadValueObjects() { @Test public void testLoadValueObjectsWithPagination() { - Filters filters = Filters.by( expressionAnalysisResultSetService.getFilter( "id", Filter.Operator.in, Lists.newArrayList( "1123898912" ) ) ); + Filters filters = Filters.by( expressionAnalysisResultSetService.getFilter( "id", Filter.Operator.in, Collections.singletonList( "1123898912" ) ) ); Sort sort = expressionAnalysisResultSetService.getSort( "id", Sort.Direction.DESC ); Slice results = expressionAnalysisResultSetService.loadValueObjects( filters, sort, 10, 20 ); assertThat( results ).isEmpty(); diff --git a/gemma-core/src/test/java/ubic/gemma/persistence/service/common/description/CharacteristicDaoImplTest.java b/gemma-core/src/test/java/ubic/gemma/persistence/service/common/description/CharacteristicDaoImplTest.java index ca775a8d5e..5627ed2941 100644 --- a/gemma-core/src/test/java/ubic/gemma/persistence/service/common/description/CharacteristicDaoImplTest.java +++ b/gemma-core/src/test/java/ubic/gemma/persistence/service/common/description/CharacteristicDaoImplTest.java @@ -21,7 +21,9 @@ import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.security.test.context.support.WithSecurityContextTestExecutionListener; import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestExecutionListeners; import ubic.gemma.core.util.test.BaseDatabaseTest; import ubic.gemma.model.analysis.Investigation; import ubic.gemma.model.association.Gene2GOAssociation; @@ -42,6 +44,7 @@ import static org.mockito.Mockito.mock; @ContextConfiguration +@TestExecutionListeners(WithSecurityContextTestExecutionListener.class) public class CharacteristicDaoImplTest extends BaseDatabaseTest { @Configuration @@ -207,6 +210,7 @@ public void testFindExperimentsByUrisAsAdmin() { ee.getCharacteristics().add( c ); sessionFactory.getCurrentSession().persist( ee ); sessionFactory.getCurrentSession().flush(); + aclService.createAcl( new AclObjectIdentity( ExpressionExperiment.class, ee.getId() ) ); int updated = tableMaintenanceUtil.updateExpressionExperiment2CharacteristicEntries(); assertThat( updated ).isEqualTo( 1 ); sessionFactory.getCurrentSession().flush(); @@ -216,8 +220,21 @@ public void testFindExperimentsByUrisAsAdmin() { } @Test - public void testGetParents() { + public void testDiscriminator() { Characteristic c = createCharacteristic( "test", "test" ); + sessionFactory.getCurrentSession().persist( c ); + List clazz = ( List ) sessionFactory.getCurrentSession() + .createSQLQuery( "select C.class from CHARACTERISTIC C where C.ID = :id" ) + .setParameter( "id", c.getId() ) + .list(); + assertThat( clazz ) + .hasSize( 1 ) + .allSatisfy( s -> assertThat( s ).isNull() ); + } + + @Test + public void testGetParents() { + Statement c = createStatement( "test", "test" ); ExperimentalDesign ed = ExperimentalDesign.Factory.newInstance(); sessionFactory.getCurrentSession().persist( ed ); ExperimentalFactor ef = new ExperimentalFactor(); @@ -246,4 +263,10 @@ private Characteristic createCharacteristic( @Nullable String valueUri, String v return c; } + private Statement createStatement( @Nullable String valueUri, String value ) { + Statement c = new Statement(); + c.setValueUri( valueUri ); + c.setValue( value ); + return c; + } } \ No newline at end of file diff --git a/gemma-core/src/test/java/ubic/gemma/persistence/service/expression/arrayDesign/ArrayDesignDaoTest.java b/gemma-core/src/test/java/ubic/gemma/persistence/service/expression/arrayDesign/ArrayDesignDaoTest.java index c54882aa7c..4064e8ae1e 100644 --- a/gemma-core/src/test/java/ubic/gemma/persistence/service/expression/arrayDesign/ArrayDesignDaoTest.java +++ b/gemma-core/src/test/java/ubic/gemma/persistence/service/expression/arrayDesign/ArrayDesignDaoTest.java @@ -8,7 +8,9 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.security.test.context.support.WithSecurityContextTestExecutionListener; import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestExecutionListeners; import ubic.gemma.core.util.test.BaseDatabaseTest; import ubic.gemma.core.util.test.category.SlowTest; import ubic.gemma.model.common.description.DatabaseEntry; @@ -30,6 +32,7 @@ import static org.junit.Assert.*; @ContextConfiguration +@TestExecutionListeners(WithSecurityContextTestExecutionListener.class) public class ArrayDesignDaoTest extends BaseDatabaseTest { @Configuration diff --git a/gemma-core/src/test/java/ubic/gemma/persistence/service/expression/biomaterial/BioMaterialDaoTest.java b/gemma-core/src/test/java/ubic/gemma/persistence/service/expression/biomaterial/BioMaterialDaoTest.java new file mode 100644 index 0000000000..21b4f2b097 --- /dev/null +++ b/gemma-core/src/test/java/ubic/gemma/persistence/service/expression/biomaterial/BioMaterialDaoTest.java @@ -0,0 +1,77 @@ +package ubic.gemma.persistence.service.expression.biomaterial; + +import org.hibernate.Hibernate; +import org.hibernate.SessionFactory; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.ContextConfiguration; +import ubic.gemma.core.util.test.BaseDatabaseTest; +import ubic.gemma.model.expression.biomaterial.BioMaterial; +import ubic.gemma.model.expression.biomaterial.Treatment; +import ubic.gemma.model.expression.experiment.ExperimentalDesign; +import ubic.gemma.model.expression.experiment.ExperimentalFactor; +import ubic.gemma.model.expression.experiment.FactorType; +import ubic.gemma.model.expression.experiment.FactorValue; +import ubic.gemma.model.genome.Taxon; +import ubic.gemma.persistence.util.TestComponent; + +import static org.assertj.core.api.Assertions.assertThat; + +@ContextConfiguration +public class BioMaterialDaoTest extends BaseDatabaseTest { + + @Configuration + @TestComponent + static class BioMaterialDaoTestContextConfiguration extends BaseDatabaseTestContextConfiguration { + + @Bean + public BioMaterialDao bioMaterialDao( SessionFactory sessionFactory ) { + return new BioMaterialDaoImpl( sessionFactory ); + } + } + + @Autowired + private BioMaterialDao bioMaterialDao; + + @Test + public void testThaw() { + Taxon taxon = new Taxon(); + sessionFactory.getCurrentSession().persist( taxon ); + ExperimentalDesign ed = new ExperimentalDesign(); + sessionFactory.getCurrentSession().persist( ed ); + ExperimentalFactor ef = new ExperimentalFactor(); + ef.setExperimentalDesign( ed ); + ef.setType( FactorType.CATEGORICAL ); + sessionFactory.getCurrentSession().persist( ef ); + FactorValue fv = new FactorValue(); + fv.setExperimentalFactor( ef ); + sessionFactory.getCurrentSession().persist( fv ); + BioMaterial bm = new BioMaterial(); + bm.setSourceTaxon( taxon ); + bm.getFactorValues().add( fv ); + bm.getTreatments().add( new Treatment() ); + sessionFactory.getCurrentSession().persist( bm ); + sessionFactory.getCurrentSession().flush(); + sessionFactory.getCurrentSession().clear(); + bm = bioMaterialDao.load( bm.getId() ); + assertThat( bm ).isNotNull(); + assertThat( bm.getTreatments() ) + .matches( ts -> !Hibernate.isInitialized( ts ) ); + assertThat( bm.getFactorValues() ) + .extracting( FactorValue::getExperimentalFactor ) + .noneMatch( Hibernate::isInitialized ); + bioMaterialDao.thaw( bm ); + assertThat( bm.getTreatments() ) + .matches( Hibernate::isInitialized ) + .hasSize( 1 ); + assertThat( bm.getFactorValues() ) + .satisfies( Hibernate::isInitialized ) + .hasSize( 1 ) + .allSatisfy( fv2 -> { + assertThat( fv2.getExperimentalFactor() ) + .matches( Hibernate::isInitialized ); + } ); + } +} \ No newline at end of file diff --git a/gemma-core/src/test/java/ubic/gemma/persistence/service/expression/designElement/CompositeSequenceDaoTest.java b/gemma-core/src/test/java/ubic/gemma/persistence/service/expression/designElement/CompositeSequenceDaoTest.java index 0bfee7d955..ada6e56cf5 100644 --- a/gemma-core/src/test/java/ubic/gemma/persistence/service/expression/designElement/CompositeSequenceDaoTest.java +++ b/gemma-core/src/test/java/ubic/gemma/persistence/service/expression/designElement/CompositeSequenceDaoTest.java @@ -7,7 +7,9 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.security.test.context.support.WithSecurityContextTestExecutionListener; import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestExecutionListeners; import ubic.gemma.core.util.test.BaseDatabaseTest; import ubic.gemma.model.expression.arrayDesign.ArrayDesign; import ubic.gemma.model.expression.designElement.CompositeSequence; @@ -19,6 +21,7 @@ import static org.junit.Assert.*; @ContextConfiguration +@TestExecutionListeners(WithSecurityContextTestExecutionListener.class) public class CompositeSequenceDaoTest extends BaseDatabaseTest { @Configuration diff --git a/gemma-core/src/test/java/ubic/gemma/persistence/service/expression/experiment/ExpressionExperimentDaoTest.java b/gemma-core/src/test/java/ubic/gemma/persistence/service/expression/experiment/ExpressionExperimentDaoTest.java index d60c7f630f..38ba47506b 100644 --- a/gemma-core/src/test/java/ubic/gemma/persistence/service/expression/experiment/ExpressionExperimentDaoTest.java +++ b/gemma-core/src/test/java/ubic/gemma/persistence/service/expression/experiment/ExpressionExperimentDaoTest.java @@ -1,5 +1,7 @@ package ubic.gemma.persistence.service.expression.experiment; +import gemma.gsec.acl.domain.AclObjectIdentity; +import gemma.gsec.acl.domain.AclService; import org.assertj.core.api.Assertions; import org.assertj.core.api.InstanceOfAssertFactories; import org.hibernate.Hibernate; @@ -9,14 +11,18 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.security.test.context.support.WithSecurityContextTestExecutionListener; import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestExecutionListeners; import ubic.gemma.core.util.test.BaseDatabaseTest; import ubic.gemma.model.common.description.Characteristic; import ubic.gemma.model.expression.arrayDesign.ArrayDesign; import ubic.gemma.model.expression.bioAssay.BioAssay; import ubic.gemma.model.expression.bioAssayData.ProcessedExpressionDataVector; import ubic.gemma.model.expression.bioAssayData.RawExpressionDataVector; -import ubic.gemma.model.expression.experiment.*; +import ubic.gemma.model.expression.biomaterial.BioMaterial; +import ubic.gemma.model.expression.experiment.ExperimentalDesign; +import ubic.gemma.model.expression.experiment.ExpressionExperiment; import ubic.gemma.model.genome.Taxon; import ubic.gemma.persistence.util.*; @@ -29,6 +35,7 @@ import static org.junit.Assert.*; @ContextConfiguration +@TestExecutionListeners(WithSecurityContextTestExecutionListener.class) public class ExpressionExperimentDaoTest extends BaseDatabaseTest { @Configuration @@ -44,11 +51,26 @@ public ExpressionExperimentDao expressionExperimentDao( SessionFactory sessionFa @Autowired private ExpressionExperimentDao expressionExperimentDao; + @Autowired + private AclService aclService; + + @Test + public void testGetFilterableProperties() { + Assertions.assertThat( expressionExperimentDao.getFilterableProperties() ) + .contains( "experimentalDesign.experimentalFactors.factorValues.characteristics.valueUri" ) + // those are hidden for now (see https://github.com/PavlidisLab/Gemma/pull/789) + .noneMatch( s -> s.startsWith( "experimentalDesign.experimentalFactors.factorValues.characteristics.predicate" ) ) + .noneMatch( s -> s.startsWith( "experimentalDesign.experimentalFactors.factorValues.characteristics.object." ) ) + .noneMatch( s -> s.startsWith( "experimentalDesign.experimentalFactors.factorValues.characteristics.secondPredicate" ) ) + .noneMatch( s -> s.startsWith( "experimentalDesign.experimentalFactors.factorValues.characteristics.secondObject." ) ); + } + @Test public void testThawTransientEntity() { ExpressionExperiment ee = new ExpressionExperiment(); ee.setExperimentalDesign( new ExperimentalDesign() ); BioAssay ba = new BioAssay(); + ba.setSampleUsed( new BioMaterial() ); ba.setArrayDesignUsed( new ArrayDesign() ); ee.setBioAssays( Collections.singleton( ba ) ); ee.setRawExpressionDataVectors( Collections.singleton( new RawExpressionDataVector() ) ); @@ -173,7 +195,8 @@ public void testGetAnnotationUsageFrequencyRetainMentionedTerm() { public void testGetAnnotationUsageFrequencyExcludingFreeTextTerms() { Characteristic c = createCharacteristic( "foo", "foo", "bar", "bar" ); Characteristic c1 = createCharacteristic( "foo", "foo", "bar", null ); - Assertions.assertThat( expressionExperimentDao.getAnnotationsUsageFrequency( null, null, 10, 1, null, null, Collections.singleton( null ), null ) ) + Map cs = expressionExperimentDao.getAnnotationsUsageFrequency( null, null, 10, 1, null, null, Collections.singleton( null ), null ); + Assertions.assertThat( cs ) .containsEntry( c, 1L ) .doesNotContainKey( c1 ); } @@ -217,6 +240,7 @@ public void testGetAnnotationUsageFrequencyWithUncategorizedCategory() { private Characteristic createCharacteristic( @Nullable String category, @Nullable String categoryUri, String value, @Nullable String valueUri ) { ExpressionExperiment ee = new ExpressionExperiment(); sessionFactory.getCurrentSession().persist( ee ); + aclService.createAcl( new AclObjectIdentity( ExpressionExperiment.class, ee.getId() ) ); Characteristic c = new Characteristic(); c.setCategory( category ); c.setCategoryUri( categoryUri ); @@ -328,33 +352,38 @@ public void testSubqueryWithMultipleJointures() { } @Test - public void testRemoveWithStatement() { - ExpressionExperiment ee = new ExpressionExperiment(); - ExperimentalFactor ef = new ExperimentalFactor(); - ef.setType( FactorType.CATEGORICAL ); - Characteristic c = new Characteristic(); - FactorValue fv = new FactorValue(); - fv.getCharacteristics().add( c ); - fv.setExperimentalFactor( ef ); - ef.getFactorValues().add( fv ); - ExperimentalDesign ed = new ExperimentalDesign(); - ef.setExperimentalDesign( ed ); - ed.getExperimentalFactors().add( ef ); - ed.getExperimentalFactors().add( new ExperimentalFactor() ); - ee.setExperimentalDesign( ed ); - // convert the characteristic to a statement - sessionFactory.getCurrentSession().persist( ee ); - sessionFactory.getCurrentSession() - .createSQLQuery( "update CHARACTERISTIC set class = 'Statement' where ID = :id" ) - .setParameter( "id", c.getId() ) - .executeUpdate(); + public void testRemoveExperimentWithSharedBioMaterial() { + Taxon taxon = new Taxon(); + sessionFactory.getCurrentSession().persist( taxon ); + ArrayDesign arrayDesign = new ArrayDesign(); + arrayDesign.setPrimaryTaxon( taxon ); + sessionFactory.getCurrentSession().persist( arrayDesign ); + BioMaterial bm = new BioMaterial(); + bm.setSourceTaxon( taxon ); + sessionFactory.getCurrentSession().persist( bm ); + BioAssay ba1 = new BioAssay(); + ba1.setArrayDesignUsed( arrayDesign ); + ba1.setSampleUsed( bm ); + bm.getBioAssaysUsedIn().add( ba1 ); + BioAssay ba2 = new BioAssay(); + ba2.setArrayDesignUsed( arrayDesign ); + ba2.setSampleUsed( bm ); + ExpressionExperiment ee1 = new ExpressionExperiment(); + ee1.getBioAssays().add( ba1 ); + ExpressionExperiment ee2 = new ExpressionExperiment(); + ee2.getBioAssays().add( ba2 ); + sessionFactory.getCurrentSession().persist( ee1 ); + sessionFactory.getCurrentSession().persist( ee2 ); sessionFactory.getCurrentSession().flush(); sessionFactory.getCurrentSession().clear(); - ee = expressionExperimentDao.load( ee.getId() ); - assertNotNull( ee ); - assertTrue( ee.getExperimentalDesign().getExperimentalFactors().iterator().next().getFactorValues().iterator().next().getCharacteristics().isEmpty() ); - expressionExperimentDao.remove( ee ); - sessionFactory.getCurrentSession().flush(); + ee1 = expressionExperimentDao.load( ee1.getId() ); + assertNotNull( ee1 ); + expressionExperimentDao.remove( ee1 ); + // verify that the sample still exists and is still attached to ee2 + bm = ( BioMaterial ) sessionFactory.getCurrentSession().get( BioMaterial.class, bm.getId() ); + assertNotNull( bm ); + assertFalse( bm.getBioAssaysUsedIn().contains( ba1 ) ); + assertTrue( bm.getBioAssaysUsedIn().contains( ba2 ) ); } private ExpressionExperiment reload( ExpressionExperiment e ) { diff --git a/gemma-core/src/test/java/ubic/gemma/persistence/service/expression/experiment/FactorValueDaoTest.java b/gemma-core/src/test/java/ubic/gemma/persistence/service/expression/experiment/FactorValueDaoTest.java index d4ef53b3a3..3f2c03c8a2 100644 --- a/gemma-core/src/test/java/ubic/gemma/persistence/service/expression/experiment/FactorValueDaoTest.java +++ b/gemma-core/src/test/java/ubic/gemma/persistence/service/expression/experiment/FactorValueDaoTest.java @@ -8,13 +8,17 @@ import org.springframework.test.context.ContextConfiguration; import ubic.gemma.core.util.test.BaseDatabaseTest; import ubic.gemma.model.common.description.Characteristic; -import ubic.gemma.model.expression.experiment.ExperimentalDesign; -import ubic.gemma.model.expression.experiment.ExperimentalFactor; -import ubic.gemma.model.expression.experiment.FactorType; -import ubic.gemma.model.expression.experiment.FactorValue; +import ubic.gemma.model.expression.biomaterial.BioMaterial; +import ubic.gemma.model.expression.experiment.*; +import ubic.gemma.model.genome.Taxon; import ubic.gemma.persistence.util.TestComponent; +import java.sql.PreparedStatement; +import java.util.Collections; +import java.util.Set; + import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.*; @ContextConfiguration public class FactorValueDaoTest extends BaseDatabaseTest { @@ -33,7 +37,7 @@ public FactorValueDao factorValueDao( SessionFactory sessionFactory ) { private FactorValueDao factorValueDao; @Test - public void testFactorValueStatementsNotListedInCharacteristics() { + public void testFactorValueStatementsNotListedInOldStyleCharacteristics() { ExperimentalDesign ed = new ExperimentalDesign(); sessionFactory.getCurrentSession().persist( ed ); ExperimentalFactor ef = new ExperimentalFactor(); @@ -42,19 +46,19 @@ public void testFactorValueStatementsNotListedInCharacteristics() { sessionFactory.getCurrentSession().persist( ef ); FactorValue fv = new FactorValue(); fv.setExperimentalFactor( ef ); - Characteristic c1 = new Characteristic(); - c1.setValue( "test" ); - Characteristic c2 = new Characteristic(); - c1.setValue( "test2" ); + Statement c1 = new Statement(); + c1.setSubject( "test" ); + Statement c2 = new Statement(); + c1.setSubject( "test2" ); fv.getCharacteristics().add( c1 ); fv.getCharacteristics().add( c2 ); fv = factorValueDao.create( fv ); assertThat( fv.getId() ).isNotNull(); assertThat( c1.getId() ).isNotNull(); assertThat( c2.getId() ).isNotNull(); - // make c2 a statement + // make c2 an old-style characteristic sessionFactory.getCurrentSession() - .createSQLQuery( "update CHARACTERISTIC set class = 'Statement' where ID = :id" ) + .createSQLQuery( "update CHARACTERISTIC set class = null where ID = :id" ) .setParameter( "id", c2.getId() ) .executeUpdate(); sessionFactory.getCurrentSession().flush(); @@ -65,6 +69,106 @@ public void testFactorValueStatementsNotListedInCharacteristics() { assertThat( fv.getCharacteristics() ) .contains( c1 ) .doesNotContain( c2 ); + assertThat( fv.getOldStyleCharacteristics() ) + .doesNotContain( c1 ) + .contains( c2 ); + factorValueDao.remove( fv ); + // make sure that statements and old-style characteristics are removed in cascade + assertThat( sessionFactory.getCurrentSession().get( Statement.class, c1.getId() ) ).isNull(); + assertThat( sessionFactory.getCurrentSession().get( Characteristic.class, c2.getId() ) ).isNull(); + } + + @Test + public void testPersistStatement() { + FactorValue fv = createFactorValue(); + Statement s1, s2; + s1 = Statement.Factory.newInstance(); + s1.setSubject( "1" ); + s2 = Statement.Factory.newInstance(); + s2.setSubject( "2" ); + fv.getCharacteristics().add( s1 ); + fv.getCharacteristics().add( s2 ); + sessionFactory.getCurrentSession().persist( fv ); + assertNotNull( fv.getId() ); + assertNotNull( s1.getId() ); + assertNotNull( s2.getId() ); // persisted in cascade + fv = reload( fv ); + assertEquals( 2, fv.getCharacteristics().size() ); + assertTrue( fv.getCharacteristics().contains( s1 ) ); + } + + @Test + public void testLoadFactorValueWithRegularCharacteristic() { + FactorValue fv = createFactorValue(); + Statement s1, s2; + s1 = Statement.Factory.newInstance(); + s1.setSubject( "1" ); + s2 = Statement.Factory.newInstance(); + s2.setSubject( "2" ); + fv.getCharacteristics().add( s1 ); + fv.getCharacteristics().add( s2 ); + sessionFactory.getCurrentSession().persist( fv ); + // create a regular characteristic + FactorValue finalFv = fv; + sessionFactory.getCurrentSession().doWork( work -> { + PreparedStatement stmt = work.prepareStatement( "insert into CHARACTERISTIC (CATEGORY, CATEGORY_URI, `VALUE`, VALUE_URI, FACTOR_VALUE_FK) values ('foo', null, 'foo', null, ?)" ); + stmt.setLong( 1, finalFv.getId() ); + assertEquals( 1, stmt.executeUpdate() ); + } ); + fv = reload( fv ); + fv.getCharacteristics().forEach( System.out::println ); + assertEquals( 2, fv.getCharacteristics().size() ); + assertEquals( 1, fv.getOldStyleCharacteristics().size() ); + } + + @Test + public void testRemove() { + Taxon taxon = new Taxon(); + sessionFactory.getCurrentSession().persist( taxon ); + FactorValue fv = createFactorValue(); + ExperimentalFactor ef = fv.getExperimentalFactor(); + BioMaterial bm = new BioMaterial(); + bm.setSourceTaxon( taxon ); + bm.getFactorValues().add( fv ); + sessionFactory.getCurrentSession().persist( bm ); + sessionFactory.getCurrentSession().flush(); + sessionFactory.getCurrentSession().clear(); + ef = ( ExperimentalFactor ) sessionFactory.getCurrentSession().get( ExperimentalFactor.class, ef.getId() ); + fv = ( FactorValue ) sessionFactory.getCurrentSession().get( FactorValue.class, fv.getId() ); + bm = ( BioMaterial ) sessionFactory.getCurrentSession().get( BioMaterial.class, bm.getId() ); + assertTrue( ef.getFactorValues().contains( fv ) ); + assertTrue( bm.getFactorValues().contains( fv ) ); factorValueDao.remove( fv ); + sessionFactory.getCurrentSession().flush(); + sessionFactory.getCurrentSession().clear(); + ef = ( ExperimentalFactor ) sessionFactory.getCurrentSession().get( ExperimentalFactor.class, ef.getId() ); + fv = ( FactorValue ) sessionFactory.getCurrentSession().get( FactorValue.class, fv.getId() ); + bm = ( BioMaterial ) sessionFactory.getCurrentSession().get( BioMaterial.class, bm.getId() ); + assertFalse( ef.getFactorValues().contains( fv ) ); + assertFalse( bm.getFactorValues().contains( fv ) ); + } + + private FactorValue createFactorValue() { + return createFactorValue( Collections.emptySet() ); + } + + private FactorValue createFactorValue( Set statements ) { + ExperimentalDesign ed = new ExperimentalDesign(); + sessionFactory.getCurrentSession().persist( ed ); + ExperimentalFactor ef = new ExperimentalFactor(); + ef.setType( FactorType.CATEGORICAL ); + ef.setExperimentalDesign( ed ); + sessionFactory.getCurrentSession().persist( ef ); + FactorValue fv = FactorValue.Factory.newInstance(); + fv.setExperimentalFactor( ef ); + fv.getCharacteristics().addAll( statements ); + sessionFactory.getCurrentSession().persist( fv ); + return fv; + } + + private FactorValue reload( FactorValue fv ) { + sessionFactory.getCurrentSession().flush(); + sessionFactory.getCurrentSession().clear(); + return ( FactorValue ) sessionFactory.getCurrentSession().get( FactorValue.class, fv.getId() ); } } \ No newline at end of file diff --git a/gemma-core/src/test/java/ubic/gemma/persistence/service/genome/GeneDaoTest.java b/gemma-core/src/test/java/ubic/gemma/persistence/service/genome/GeneDaoTest.java index 03ee6877ff..370adc685d 100644 --- a/gemma-core/src/test/java/ubic/gemma/persistence/service/genome/GeneDaoTest.java +++ b/gemma-core/src/test/java/ubic/gemma/persistence/service/genome/GeneDaoTest.java @@ -3,15 +3,16 @@ import org.hibernate.SessionFactory; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.cache.CacheManager; -import org.springframework.cache.concurrent.ConcurrentMapCacheManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.test.context.ContextConfiguration; import ubic.gemma.core.util.test.BaseDatabaseTest; import ubic.gemma.model.genome.Gene; +import ubic.gemma.model.genome.gene.GeneProduct; import ubic.gemma.persistence.util.TestComponent; +import static org.assertj.core.api.Assertions.assertThat; + @ContextConfiguration public class GeneDaoTest extends BaseDatabaseTest { @@ -33,4 +34,23 @@ public void testRemove() { Gene g = geneDao.create( Gene.Factory.newInstance() ); geneDao.remove( g ); } + + @Test + public void testRemoveWithDummyGeneProducts() { + Gene g = geneDao.create( Gene.Factory.newInstance() ); + GeneProduct gp = new GeneProduct(); + gp.setDummy( true ); + gp.setGene( g ); + sessionFactory.getCurrentSession().persist( gp ); + g = reload( g ); + assertThat( g.getProducts() ).doesNotContain( gp ); + geneDao.remove( g ); + sessionFactory.getCurrentSession().flush(); + } + + private Gene reload( Gene g ) { + sessionFactory.getCurrentSession().flush(); + sessionFactory.getCurrentSession().evict( g ); + return ( Gene ) sessionFactory.getCurrentSession().get( Gene.class, g.getId() ); + } } \ No newline at end of file diff --git a/gemma-core/src/test/resources/ubic/gemma/applicationContext-ontology.xml b/gemma-core/src/test/resources/ubic/gemma/applicationContext-ontology.xml index 32a885d9f4..c9e0c8cb4b 100644 --- a/gemma-core/src/test/resources/ubic/gemma/applicationContext-ontology.xml +++ b/gemma-core/src/test/resources/ubic/gemma/applicationContext-ontology.xml @@ -3,33 +3,46 @@ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd" profile="test"> + + + + + + + + + + + + + \ No newline at end of file diff --git a/gemma-core/src/test/resources/ubic/gemma/core/ontology/OntologyLoadingTest-context.xml b/gemma-core/src/test/resources/ubic/gemma/core/ontology/OntologyLoadingTest-context.xml new file mode 100644 index 0000000000..e68339e0a5 --- /dev/null +++ b/gemma-core/src/test/resources/ubic/gemma/core/ontology/OntologyLoadingTest-context.xml @@ -0,0 +1,14 @@ + + + + + + false + 4 + + + + + \ No newline at end of file diff --git a/gemma-groovy-support/pom.xml b/gemma-groovy-support/pom.xml index 1f560e8e8f..14cbb44c34 100644 --- a/gemma-groovy-support/pom.xml +++ b/gemma-groovy-support/pom.xml @@ -6,7 +6,7 @@ gemma gemma - 1.30.6 + 1.31.0 gemma-groovy-support diff --git a/gemma-rest/pom.xml b/gemma-rest/pom.xml index 3eeedf83d5..2eae50918f 100644 --- a/gemma-rest/pom.xml +++ b/gemma-rest/pom.xml @@ -5,7 +5,7 @@ gemma gemma - 1.30.6 + 1.31.0 4.0.0 @@ -155,6 +155,14 @@ antlr4-runtime ${antlr4.version} + + + + com.jayway.jsonpath + json-path-assert + 2.8.0 + test + diff --git a/gemma-rest/src/main/java/ubic/gemma/rest/AnnotationsWebService.java b/gemma-rest/src/main/java/ubic/gemma/rest/AnnotationsWebService.java index fee220de70..683e13ab60 100644 --- a/gemma-rest/src/main/java/ubic/gemma/rest/AnnotationsWebService.java +++ b/gemma-rest/src/main/java/ubic/gemma/rest/AnnotationsWebService.java @@ -39,7 +39,7 @@ import ubic.gemma.model.expression.experiment.ExpressionExperiment; import ubic.gemma.model.expression.experiment.ExpressionExperimentValueObject; import ubic.gemma.model.genome.Taxon; -import ubic.gemma.model.genome.gene.phenotype.valueObject.CharacteristicValueObject; +import ubic.gemma.model.common.description.CharacteristicValueObject; import ubic.gemma.persistence.service.common.description.CharacteristicService; import ubic.gemma.persistence.service.expression.experiment.ExpressionExperimentService; import ubic.gemma.persistence.util.Filters; diff --git a/gemma-rest/src/main/java/ubic/gemma/rest/SearchWebService.java b/gemma-rest/src/main/java/ubic/gemma/rest/SearchWebService.java index 9e23f8009e..94295d6842 100644 --- a/gemma-rest/src/main/java/ubic/gemma/rest/SearchWebService.java +++ b/gemma-rest/src/main/java/ubic/gemma/rest/SearchWebService.java @@ -32,7 +32,7 @@ import ubic.gemma.model.genome.TaxonValueObject; import ubic.gemma.model.genome.gene.GeneSetValueObject; import ubic.gemma.model.genome.gene.GeneValueObject; -import ubic.gemma.model.genome.gene.phenotype.valueObject.CharacteristicValueObject; +import ubic.gemma.model.common.description.CharacteristicValueObject; import ubic.gemma.model.genome.sequenceAnalysis.BioSequenceValueObject; import ubic.gemma.persistence.service.expression.arrayDesign.ArrayDesignService; import ubic.gemma.persistence.service.genome.taxon.TaxonService; diff --git a/gemma-rest/src/main/java/ubic/gemma/rest/serializers/AbstractFactorValueValueObjectSerializer.java b/gemma-rest/src/main/java/ubic/gemma/rest/serializers/AbstractFactorValueValueObjectSerializer.java new file mode 100644 index 0000000000..593682c557 --- /dev/null +++ b/gemma-rest/src/main/java/ubic/gemma/rest/serializers/AbstractFactorValueValueObjectSerializer.java @@ -0,0 +1,72 @@ +package ubic.gemma.rest.serializers; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import lombok.SneakyThrows; +import ubic.gemma.core.ontology.FactorValueOntologyServiceImpl; +import ubic.gemma.model.expression.experiment.StatementValueObject; + +import java.io.IOException; +import java.util.Collection; + +import static ubic.gemma.core.ontology.FactorValueOntologyUtils.visitCharacteristics; +import static ubic.gemma.core.ontology.FactorValueOntologyUtils.visitStatements; + +/** + * Base serializer for {@link ubic.gemma.model.expression.experiment.FactorValue} VOs. + *

+ * See {@link FactorValueOntologyServiceImpl} for the logic related to how URIs are generated. + */ +public abstract class AbstractFactorValueValueObjectSerializer extends StdSerializer { + protected AbstractFactorValueValueObjectSerializer( Class t ) { + super( t ); + } + + protected void writeCharacteristics( Long factorValueId, Collection cvos, JsonGenerator jsonGenerator ) throws IOException { + jsonGenerator.writeArrayFieldStart( "characteristics" ); + visitCharacteristics( factorValueId, cvos, ( cvo, valueId ) -> { + writeCharacteristic( cvo.getId(), cvo.getCategory(), cvo.getCategoryUri(), valueId, cvo.getSubject(), cvo.getSubjectUri(), jsonGenerator ); + } ); + jsonGenerator.writeEndArray(); + } + + protected void writeStatements( Long factorValueId, Collection svos, JsonGenerator jsonGenerator ) throws IOException { + jsonGenerator.writeArrayFieldStart( "statements" ); + visitStatements( factorValueId, svos, ( svo, assignedIds ) -> { + if ( assignedIds.getObjectId() != null ) { + writeStatement( svo.getCategory(), svo.getCategoryUri(), assignedIds.getSubjectId(), svo.getSubject(), svo.getSubjectUri(), svo.getPredicate(), svo.getPredicateUri(), assignedIds.getObjectId(), svo.getObject(), svo.getObjectUri(), jsonGenerator ); + } + if ( assignedIds.getSecondObjectId() != null ) { + writeStatement( svo.getCategory(), svo.getCategoryUri(), assignedIds.getSubjectId(), svo.getSubject(), svo.getSubjectUri(), svo.getSecondPredicate(), svo.getSecondPredicateUri(), assignedIds.getSecondObjectId(), svo.getSecondObject(), svo.getSecondObjectUri(), jsonGenerator ); + } + } ); + jsonGenerator.writeEndArray(); + } + + @SneakyThrows + private void writeCharacteristic( Long id, String category, String categoryUri, String valueId, String value, String valueUri, JsonGenerator jsonGenerator ) { + jsonGenerator.writeStartObject(); + jsonGenerator.writeObjectField( "id", id ); + jsonGenerator.writeObjectField( "category", category ); + jsonGenerator.writeObjectField( "categoryUri", categoryUri ); + jsonGenerator.writeObjectField( "valueId", valueId ); + jsonGenerator.writeObjectField( "value", value ); + jsonGenerator.writeObjectField( "valueUri", valueUri ); + jsonGenerator.writeEndObject(); + } + + private void writeStatement( String category, String categoryUri, String subjectId, String subject, String subjectUri, String predicate, String predicateUri, String objectId, String object, String objectUri, JsonGenerator jsonGenerator ) throws IOException { + jsonGenerator.writeStartObject(); + jsonGenerator.writeObjectField( "category", category ); + jsonGenerator.writeObjectField( "categoryUri", categoryUri ); + jsonGenerator.writeObjectField( "subjectId", subjectId ); + jsonGenerator.writeObjectField( "subject", subject ); + jsonGenerator.writeObjectField( "subjectUri", subjectUri ); + jsonGenerator.writeObjectField( "predicate", predicate ); + jsonGenerator.writeObjectField( "predicateUri", predicateUri ); + jsonGenerator.writeObjectField( "objectId", objectId ); + jsonGenerator.writeObjectField( "object", object ); + jsonGenerator.writeObjectField( "objectUri", objectUri ); + jsonGenerator.writeEndObject(); + } +} diff --git a/gemma-rest/src/main/java/ubic/gemma/rest/serializers/FactorValueBasicValueObjectSerializer.java b/gemma-rest/src/main/java/ubic/gemma/rest/serializers/FactorValueBasicValueObjectSerializer.java new file mode 100644 index 0000000000..d923b805a1 --- /dev/null +++ b/gemma-rest/src/main/java/ubic/gemma/rest/serializers/FactorValueBasicValueObjectSerializer.java @@ -0,0 +1,33 @@ +package ubic.gemma.rest.serializers; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import ubic.gemma.core.ontology.FactorValueOntologyUtils; +import ubic.gemma.model.expression.experiment.FactorValueBasicValueObject; + +import java.io.IOException; + +public class FactorValueBasicValueObjectSerializer extends AbstractFactorValueValueObjectSerializer { + + public FactorValueBasicValueObjectSerializer() { + super( FactorValueBasicValueObject.class ); + } + + @Override + public void serialize( FactorValueBasicValueObject factorValueBasicValueObject, JsonGenerator jsonGenerator, SerializerProvider serializerProvider ) throws IOException { + jsonGenerator.writeStartObject(); + jsonGenerator.writeObjectField( "id", factorValueBasicValueObject.getId() ); + jsonGenerator.writeStringField( "ontologyId", FactorValueOntologyUtils.getUri( factorValueBasicValueObject.getId() ) ); + jsonGenerator.writeObjectField( "experimentalFactorId", factorValueBasicValueObject.getExperimentalFactorId() ); + jsonGenerator.writeObjectField( "experimentalFactorCategory", factorValueBasicValueObject.getExperimentalFactorCategory() ); + if ( factorValueBasicValueObject.getMeasurement() != null ) { + jsonGenerator.writeObjectField( "measurement", factorValueBasicValueObject.getMeasurement() ); + } + writeCharacteristics( factorValueBasicValueObject.getId(), factorValueBasicValueObject.getStatements(), jsonGenerator ); + writeStatements( factorValueBasicValueObject.getId(), factorValueBasicValueObject.getStatements(), jsonGenerator ); + //noinspection deprecation + jsonGenerator.writeStringField( "value", factorValueBasicValueObject.getValue() ); + jsonGenerator.writeStringField( "summary", factorValueBasicValueObject.getSummary() ); + jsonGenerator.writeEndObject(); + } +} diff --git a/gemma-rest/src/main/java/ubic/gemma/rest/serializers/FactorValueValueObjectSerializer.java b/gemma-rest/src/main/java/ubic/gemma/rest/serializers/FactorValueValueObjectSerializer.java new file mode 100644 index 0000000000..9d95a53e1c --- /dev/null +++ b/gemma-rest/src/main/java/ubic/gemma/rest/serializers/FactorValueValueObjectSerializer.java @@ -0,0 +1,38 @@ +package ubic.gemma.rest.serializers; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import ubic.gemma.core.ontology.FactorValueOntologyUtils; +import ubic.gemma.model.expression.experiment.FactorValueValueObject; + +import java.io.IOException; + +public class FactorValueValueObjectSerializer extends AbstractFactorValueValueObjectSerializer { + + public FactorValueValueObjectSerializer() { + super( FactorValueValueObject.class ); + } + + @Override + public void serialize( FactorValueValueObject factorValueValueObject, JsonGenerator jsonGenerator, SerializerProvider serializerProvider ) throws IOException { + jsonGenerator.writeStartObject(); + jsonGenerator.writeObjectField( "id", factorValueValueObject.getId() ); + jsonGenerator.writeStringField( "ontologyId", FactorValueOntologyUtils.getUri( factorValueValueObject.getId() ) ); + jsonGenerator.writeObjectField( "experimentalFactorId", factorValueValueObject.getExperimentalFactorId() ); + jsonGenerator.writeObjectField( "experimentalFactorCategory", factorValueValueObject.getExperimentalFactorCategory() ); + jsonGenerator.writeObjectField( "factorId", factorValueValueObject.getFactorId() ); + jsonGenerator.writeObjectField( "charId", factorValueValueObject.getCharId() ); + jsonGenerator.writeStringField( "category", factorValueValueObject.getCategory() ); + jsonGenerator.writeStringField( "categoryUri", factorValueValueObject.getCategoryUri() ); + jsonGenerator.writeStringField( "description", factorValueValueObject.getDescription() ); + jsonGenerator.writeObjectField( "factorValue", factorValueValueObject.getFactorValue() ); + jsonGenerator.writeBooleanField( "isMeasurement", factorValueValueObject.getMeasurementObject() != null ); + if ( factorValueValueObject.getMeasurementObject() != null ) { + jsonGenerator.writeObjectField( "measurement", factorValueValueObject.getMeasurementObject() ); + } + writeCharacteristics( factorValueValueObject.getId(), factorValueValueObject.getStatements(), jsonGenerator ); + writeStatements( factorValueValueObject.getId(), factorValueValueObject.getStatements(), jsonGenerator ); + jsonGenerator.writeStringField( "summary", factorValueValueObject.getSummary() ); + jsonGenerator.writeEndObject(); + } +} diff --git a/gemma-rest/src/main/java/ubic/gemma/rest/util/JacksonConfig.java b/gemma-rest/src/main/java/ubic/gemma/rest/util/JacksonConfig.java index 651070a501..6e04c6ac91 100644 --- a/gemma-rest/src/main/java/ubic/gemma/rest/util/JacksonConfig.java +++ b/gemma-rest/src/main/java/ubic/gemma/rest/util/JacksonConfig.java @@ -1,11 +1,14 @@ package ubic.gemma.rest.util; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.util.StdDateFormat; import io.swagger.v3.core.util.Json; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; +import ubic.gemma.rest.serializers.FactorValueBasicValueObjectSerializer; +import ubic.gemma.rest.serializers.FactorValueValueObjectSerializer; import ubic.gemma.rest.swagger.resolver.CustomModelResolver; /** @@ -25,6 +28,9 @@ public class JacksonConfig { @Primary public ObjectMapper objectMapper() { return new ObjectMapper() + // handle special serialization of statements + .registerModule( new SimpleModule().addSerializer( new FactorValueValueObjectSerializer() ) ) + .registerModule( new SimpleModule().addSerializer( new FactorValueBasicValueObjectSerializer() ) ) // parse and render date as ISO 9601 .setDateFormat( new StdDateFormat() ); } diff --git a/gemma-rest/src/main/resources/openapi-configuration.yaml b/gemma-rest/src/main/resources/openapi-configuration.yaml index d613c6d9f6..fbcff0f1fe 100644 --- a/gemma-rest/src/main/resources/openapi-configuration.yaml +++ b/gemma-rest/src/main/resources/openapi-configuration.yaml @@ -8,7 +8,7 @@ openAPI: url: https://dev.gemma.msl.ubc.ca/rest/v2 info: title: Gemma RESTful API - version: 2.7.1 + version: 2.7.2 description: | This website documents the usage of the [Gemma RESTful API](https://gemma.msl.ubc.ca/rest/v2/). Here you can find example script usage of the API, as well as graphical interface for each endpoint, with description of its @@ -17,178 +17,8 @@ openAPI: Use of this webpage and the Gemma Web services, including the REST API, is subject to [these terms and conditions](https://pavlidislab.github.io/Gemma/terms.html). Please read these in full before continuing to use this webpage or any other part of the Gemma system. - ## Updates - - ### Update 2.7.1 - - - improved highlights of search results - - fix the limit for getDatasetsAnnotations() endpoint to 5000 in the specification - - fix missing initialization of datasets retrieved from the cache - - Highlights now use Markdown syntax for formatting. Fields for highlighted ontology terms now use complete object - path instead of just `term`. Last but not least, highlights from multiple results are merged. - - ### Update 2.7.0 - - New endpoints for counting the number of results: `getNumberOfDatasets`, `getNumberOfPlatforms`, - `getNumberOfResultSets`. These endpoints are faster than looking up `totalElements` as no data is retrieved or - converted. - - Datasets can now be filtered by annotations at the sample, factor value and all levels using the three newly - exposed `experimentalDesign.experimentalFactors.factorValues.characteristics`, `bioAssays.sampleUsed.characteristics` - and `allCharacteristics` collections. The two useful available properties for filtering are `value` and `valueUri`. - - New `getDatasetsAnnotationsUsageStatistics`, `getDatasetsPlatformsUsageStatistics` and `getDatasetsTaxaUsageStatistics` - endpoints for retrieving annotations, platforms and taxa used by the matched datasets. The endpoint accepts the - same `filter` argument of `getDatasets`, allowing one to easily navigate terms, platforms and taxa available for - filtering furthermore. - - Properties available for filtering and sorting are enumerated in the description of the corresponding parameter. - There's been a number of fixes and additional tests performed to ensure that all advertised properties are working - as expected. - - The `FilterArg` and `SortArg`-based parameters now have OpenAPI extensions to enumerate available properties in a - structured format under the `x-gemma-filterable-properties`key. Possible values are exposed for enumerated types. - - ```yaml - x-gemma-filterable-properties: - - name: technologyType - type: string - allowedValues: - - value: ONECOLOR - label: One Color - - value: SEQUENCING - label: Sequencing - security: - basicAuth: [GROUP_ADMIN] - cookieAuth: [GROUP_ADMIN] - ``` - - Some of the exposed properties such as `geeq.publicSuitabilityScore` require specific authorities to use. This is - documented in `x-filterable-properties` by specifying a [Security Requirement Object](https://spec.openapis.org/oas/latest.html#security-requirement-object). - - Types that use the `[]` suffix are using sub-queries under the hood. It implies that the entity will be matched if - at least one related entity matches the supplied filter. For example, the `characteristics.valueUri = http://purl.obolibrary.org/obo/UBERON_0002107` - filter will match datasets with at least one `UBERON:0002107` tag attached. - - Filtered endpoints (including paginated and limited ones) now expose a `groupBy` array that enumerates all the - properties used to group results. This helps clear confusion about what constitute a business key in the returned - response. - - ### Update 2.6.1 - - Add support for filtering platforms by taxon ID, common name, scientific name, etc. for the `/platforms` endpoint. - - ### Update 2.6.0 - - Add a new `externalDatabases` attribute to the main endpoint that displays version of some of the main external - databases that we are using. This exposes versions and last updates for genomes, gene annotations, GO terms, and - much more! - - The `ExternalDatabaseValueObject` now exposes a `description` which provides additional details. - - ### Update 2.5.2 - - Restore `factors` in `BioMaterialValueObject` as it is being still used by our RNA-Seq pipeline. The attribute is - deprecated and should not be used and will be removed when we find a suitable alternative. - - Introduce a new endpoint to retrieve quantitation types for a given dataset and parameters to retrieve expression - data by quantitation type to `getDatasetProcessedExpression` and `getDatasetRawExpression`. As it was too difficult - to extends `getDatasetExpression` to also support quantitation type while retaining the filter feature, we decided - to deprecate it and reintroduce filtering for both raw and processed expression in the future. - - Fix return type for `getResultSets` which was incorrectly referring to a renamed VO. - - Annotate all possible types for `SearchResult.resultObject`. This incidentally includes the `GeneSetValueObject` - in the specification which is not exposed elsewhere in the API. - - ### Update 2.5.1 - - Restore `objectClass` visibility in `AnnotationValueObject`. - - Fix incorrect response types for annotations search endpoints returning datasets. - - ### Update 2.5.0 - - Major cleanups were performed in this release in order to stabilize the specification. Numerous properties from - Gemma Web that were never intended to be exposed in Gemma REST have been hidden. It's a bit too much to describe - in here, but you can navigate to the schemas section below to get a good glance at the models. - - Favour `numberOfSomething` instead of `somethingCount` which is clearer. The older names are kept for - backward-compatibility, but should be considered deprecated. - - Gene aliases and multifunctionality rank are now filled in `GeneValueObject`. - - Uniformly use `TaxonValueObject` to represent taxon. This is breaking change for the `ExpressionExperimentValueObject` - and `ArrayDesignValueObject` as their `taxon` property will be an `object` instead of a `string`. Properties such - as `taxonId` are now deprecated and `taxon.id` should be used instead. - - Entities that have IDs now all inherit from `IdentifiableValueObject`. This implies that you can assume the - presence of an `id` in a search result `resultObject` attribute for example. - - New `/search` endpoint! for an unified search experience. Annotation-based search endpoints under `/annotations` - are now deprecated. - - New API docs! While not as nice looking, the previous theme will be gradually ported to Swagger UI as we focused - on functionality over prettiness for this release. - - ### Update 2.4.0 through 2.4.1 - - Release notes for the 2.4 series were not written down, so I'll try to do my best to recall features that were - introduced at that time. - - An [OpenAPI](https://www.openapis.org/) specification was introduced and available under `/rest/v2/openapi.json`, - although not fully stabilized. - - Add a `/resultSets` endpoint to navigate result sets directly, by ID or by dataset. - - Add a `/resultSets/{resultSetId}` endpoint to retrieve a specific result set by its ID. This endpoint can be - negotiated with an `Accept: text/tab-separated-values` header to obtain a TSV representation. - - Add a `/datasets/{dataset}/analyses/differential/resultSets` endpoint that essentially redirect to a specific - `/resultSet` endpoint by dataset ID. - - Add an endpoint to retrieve preferred raw expression vectors. - - ### Update 2.3.4 - - November 6th, 2018 - - November 6th [2.3.4] Bug fixes in the dataset search endpoint. - - November 5th [2.3.3] Added filtering parameters to dataset search. - - October 25th [2.3.2] Changed behavior of the dataset search endpoint to more closely match the Gemma web interface. - - October 2nd [2.3.1] Added group information to the User value object. - - September 27th [2.3.0] Breaking change in Taxa: Abbreviation property has been removed and is therefore no longer - an accepted identifier. - - ### Update 2.2.6 - - June 7th, 2018 - - Code maintenance, bug fixes. Geeq scores stable and made public. - - June 7th [2.2.6] Added: User authentication endpoint. - - May 2nd [2.2.5] Fixed: Cleaned up and optimized platforms/elements endpoint, removed redundant information - (recursive properties nesting). - - April 12th [2.2.3] Fixed: Array arguments not handling non-string properties properly, e.g. `ncbiIds` of genes. - - April 9th [2.2.1] Fixed: Filter argument not working when the filtered field was a primitive type. This most - significantly allows filtering by geeq boolean and double properties. - - ### Update 2.2.0 - - February 8th, 2018 - - Breaking change in the 'Dataset differential analysis' endpoint: - - No longer using `qValueThreshold` parameter. - - Response format changed, now using `DifferentialExpressionAnalysisValueObject` instead of `DifferentialExpressionValueObject` - - [Experimental] Added Geeq (Gene Expression Experiment Quality) scores to the dataset value objects + You can [consult the CHANGELOG.md file](https://gemma.msl.ubc.ca/resources/restapidocs/CHANGELOG.md) to view + release notes and recent changes to the Gemma RESTful API. termsOfService: https://pavlidislab.github.io/Gemma/terms.html contact: name: Pavlidis Lab Support diff --git a/gemma-rest/src/main/resources/restapidocs/CHANGELOG.md b/gemma-rest/src/main/resources/restapidocs/CHANGELOG.md new file mode 100644 index 0000000000..c7e1801e5d --- /dev/null +++ b/gemma-rest/src/main/resources/restapidocs/CHANGELOG.md @@ -0,0 +1,225 @@ +## Updates + +### Update 2.7.2 + +Expose statements in `FactorValueValueObject` and `FactorValueBasicValueObject`. + +A statement is constituted of three part: a subject, a predicate and an object. All those entities are identified by +using IDs from the Gemma Factor Value Ontology (TGFVO). Those IDs are not stable as they depend on the numerical ID +assigned to the factor value and the order in which an entity appears in the characteristics and statements. + +Entities that are not involved in any statement are retained in the `characteristics` collection. Those entities are +also being assigned an ID from TGFVO. + +```yaml +id: 1 # ID of the factor value +ontologyId: http://gemma.msl.ubc.ca/ont/TGFVO/1 # an TGFVO ID for the factor value +experimentalFactorId: 1 +experimentalFactorCategory: + - id: 1 + category: genotype + categoryUri: null + valueId: http://gemma.msl.ubc.ca/ont/TGFVO/1/3 # an TGFVO ID for the value + value: + valueUri: +value: # deprecated +summary: # a summary of the factor value (those have been significantly improved!) +characteristics: + - id: 1 + category: genotype + categoryUri: null + valueId: http://gemma.msl.ubc.ca/ont/TGFVO/1/3 # an TGFVO ID for the value + value: + valueUri: +statements: + - category: genotype + categoryUri: null + subjectId: http://gemma.msl.ubc.ca/ont/TGFVO/1/1 # an TFGVO ID for the subject + subject: PAX6 + subjectUri: null + predicate: has modifier + predicateUri: null # + objectId: http://gemma.msl.ubc.ca/ont/TGFVO/1/2 # an TGFVO ID for the object + object: over-expression # a modifier term + objectUri: null # +``` + +`FactorValueBasicValueObject.summary` is no longer deprecated and have been significantly improved by incorporating the +statements. + +`CharacteristicValueObject` and `CharacteristicBasicValueObject` have been unified in a single class. + +### Update 2.7.1 + +- improved highlights of search results +- fix the limit for getDatasetsAnnotations() endpoint to 5000 in the specification +- fix missing initialization of datasets retrieved from the cache + +Highlights now use Markdown syntax for formatting. Fields for highlighted ontology terms now use complete object +path instead of just `term`. Last but not least, highlights from multiple results are merged. + +### Update 2.7.0 + +New endpoints for counting the number of results: `getNumberOfDatasets`, `getNumberOfPlatforms`, +`getNumberOfResultSets`. These endpoints are faster than looking up `totalElements` as no data is retrieved or +converted. + +Datasets can now be filtered by annotations at the sample, factor value and all levels using the three newly +exposed `experimentalDesign.experimentalFactors.factorValues.characteristics`, `bioAssays.sampleUsed.characteristics` +and `allCharacteristics` collections. The two useful available properties for filtering are `value` and `valueUri`. + +New `getDatasetsAnnotationsUsageStatistics`, `getDatasetsPlatformsUsageStatistics` and `getDatasetsTaxaUsageStatistics` +endpoints for retrieving annotations, platforms and taxa used by the matched datasets. The endpoint accepts the +same `filter` argument of `getDatasets`, allowing one to easily navigate terms, platforms and taxa available for +filtering furthermore. + +Properties available for filtering and sorting are enumerated in the description of the corresponding parameter. +There's been a number of fixes and additional tests performed to ensure that all advertised properties are working +as expected. + +The `FilterArg` and `SortArg`-based parameters now have OpenAPI extensions to enumerate available properties in a +structured format under the `x-gemma-filterable-properties`key. Possible values are exposed for enumerated types. + +```yaml +x-gemma-filterable-properties: + - name: technologyType +type: string +allowedValues: + - value: ONECOLOR + label: One Color + - value: SEQUENCING + label: Sequencing +security: + basicAuth: [GROUP_ADMIN] + cookieAuth: [GROUP_ADMIN] +``` + +Some of the exposed properties such as `geeq.publicSuitabilityScore` require specific authorities to use. This is +documented in `x-filterable-properties` by specifying +a [Security Requirement Object](https://spec.openapis.org/oas/latest.html#security-requirement-object). + +Types that use the `[]` suffix are using sub-queries under the hood. It implies that the entity will be matched if +at least one related entity matches the supplied filter. For example, +the `characteristics.valueUri = http://purl.obolibrary.org/obo/UBERON_0002107` +filter will match datasets with at least one `UBERON:0002107` tag attached. + +Filtered endpoints (including paginated and limited ones) now expose a `groupBy` array that enumerates all the +properties used to group results. This helps clear confusion about what constitute a business key in the returned +response. + +### Update 2.6.1 + +Add support for filtering platforms by taxon ID, common name, scientific name, etc. for the `/platforms` endpoint. + +### Update 2.6.0 + +Add a new `externalDatabases` attribute to the main endpoint that displays version of some of the main external +databases that we are using. This exposes versions and last updates for genomes, gene annotations, GO terms, and +much more! + +The `ExternalDatabaseValueObject` now exposes a `description` which provides additional details. + +### Update 2.5.2 + +Restore `factors` in `BioMaterialValueObject` as it is being still used by our RNA-Seq pipeline. The attribute is +deprecated and should not be used and will be removed when we find a suitable alternative. + +Introduce a new endpoint to retrieve quantitation types for a given dataset and parameters to retrieve expression +data by quantitation type to `getDatasetProcessedExpression` and `getDatasetRawExpression`. As it was too difficult +to extends `getDatasetExpression` to also support quantitation type while retaining the filter feature, we decided +to deprecate it and reintroduce filtering for both raw and processed expression in the future. + +Fix return type for `getResultSets` which was incorrectly referring to a renamed VO. + +Annotate all possible types for `SearchResult.resultObject`. This incidentally includes the `GeneSetValueObject` +in the specification which is not exposed elsewhere in the API. + +### Update 2.5.1 + +Restore `objectClass` visibility in `AnnotationValueObject`. + +Fix incorrect response types for annotations search endpoints returning datasets. + +### Update 2.5.0 + +Major cleanups were performed in this release in order to stabilize the specification. Numerous properties from +Gemma Web that were never intended to be exposed in Gemma REST have been hidden. It's a bit too much to describe +in here, but you can navigate to the schemas section below to get a good glance at the models. + +Favour `numberOfSomething` instead of `somethingCount` which is clearer. The older names are kept for +backward-compatibility, but should be considered deprecated. + +Gene aliases and multifunctionality rank are now filled in `GeneValueObject`. + +Uniformly use `TaxonValueObject` to represent taxon. This is breaking change for the `ExpressionExperimentValueObject` +and `ArrayDesignValueObject` as their `taxon` property will be an `object` instead of a `string`. Properties such +as `taxonId` are now deprecated and `taxon.id` should be used instead. + +Entities that have IDs now all inherit from `IdentifiableValueObject`. This implies that you can assume the +presence of an `id` in a search result `resultObject` attribute for example. + +New `/search` endpoint! for an unified search experience. Annotation-based search endpoints under `/annotations` +are now deprecated. + +New API docs! While not as nice looking, the previous theme will be gradually ported to Swagger UI as we focused +on functionality over prettiness for this release. + +### Update 2.4.0 through 2.4.1 + +Release notes for the 2.4 series were not written down, so I'll try to do my best to recall features that were +introduced at that time. + +An [OpenAPI](https://www.openapis.org/) specification was introduced and available under `/rest/v2/openapi.json`, +although not fully stabilized. + +Add a `/resultSets` endpoint to navigate result sets directly, by ID or by dataset. + +Add a `/resultSets/{resultSetId}` endpoint to retrieve a specific result set by its ID. This endpoint can be +negotiated with an `Accept: text/tab-separated-values` header to obtain a TSV representation. + +Add a `/datasets/{dataset}/analyses/differential/resultSets` endpoint that essentially redirect to a specific +`/resultSet` endpoint by dataset ID. + +Add an endpoint to retrieve preferred raw expression vectors. + +### Update 2.3.4 + +November 6th, 2018 + +November 6th [2.3.4] Bug fixes in the dataset search endpoint. + +November 5th [2.3.3] Added filtering parameters to dataset search. + +October 25th [2.3.2] Changed behavior of the dataset search endpoint to more closely match the Gemma web interface. + +October 2nd [2.3.1] Added group information to the User value object. + +September 27th [2.3.0] Breaking change in Taxa: Abbreviation property has been removed and is therefore no longer +an accepted identifier. + +### Update 2.2.6 + +June 7th, 2018 + +Code maintenance, bug fixes. Geeq scores stable and made public. + +June 7th [2.2.6] Added: User authentication endpoint. + +May 2nd [2.2.5] Fixed: Cleaned up and optimized platforms/elements endpoint, removed redundant information +(recursive properties nesting). + +April 12th [2.2.3] Fixed: Array arguments not handling non-string properties properly, e.g. `ncbiIds` of genes. + +April 9th [2.2.1] Fixed: Filter argument not working when the filtered field was a primitive type. This most +significantly allows filtering by geeq boolean and double properties. + +### Update 2.2.0 + +February 8th, 2018 + +Breaking change in the 'Dataset differential analysis' endpoint: + +- No longer using `qValueThreshold` parameter. +- Response format changed, now using `DifferentialExpressionAnalysisValueObject` instead + of `DifferentialExpressionValueObject` +- [Experimental] Added Geeq (Gene Expression Experiment Quality) scores to the dataset value objects \ No newline at end of file diff --git a/gemma-web/src/main/webapp/resources/restapidocs/index.jsp b/gemma-rest/src/main/resources/restapidocs/index.jsp similarity index 100% rename from gemma-web/src/main/webapp/resources/restapidocs/index.jsp rename to gemma-rest/src/main/resources/restapidocs/index.jsp diff --git a/gemma-web/src/main/webapp/resources/restapidocs/style.css b/gemma-rest/src/main/resources/restapidocs/style.css similarity index 100% rename from gemma-web/src/main/webapp/resources/restapidocs/style.css rename to gemma-rest/src/main/resources/restapidocs/style.css diff --git a/gemma-web/src/main/webapp/resources/restapidocs/swagger-ui-bundle.js b/gemma-rest/src/main/resources/restapidocs/swagger-ui-bundle.js similarity index 100% rename from gemma-web/src/main/webapp/resources/restapidocs/swagger-ui-bundle.js rename to gemma-rest/src/main/resources/restapidocs/swagger-ui-bundle.js diff --git a/gemma-web/src/main/webapp/resources/restapidocs/swagger-ui-bundle.js.map b/gemma-rest/src/main/resources/restapidocs/swagger-ui-bundle.js.map similarity index 100% rename from gemma-web/src/main/webapp/resources/restapidocs/swagger-ui-bundle.js.map rename to gemma-rest/src/main/resources/restapidocs/swagger-ui-bundle.js.map diff --git a/gemma-web/src/main/webapp/resources/restapidocs/swagger-ui.css b/gemma-rest/src/main/resources/restapidocs/swagger-ui.css similarity index 100% rename from gemma-web/src/main/webapp/resources/restapidocs/swagger-ui.css rename to gemma-rest/src/main/resources/restapidocs/swagger-ui.css diff --git a/gemma-web/src/main/webapp/resources/restapidocs/swagger-ui.css.map b/gemma-rest/src/main/resources/restapidocs/swagger-ui.css.map similarity index 100% rename from gemma-web/src/main/webapp/resources/restapidocs/swagger-ui.css.map rename to gemma-rest/src/main/resources/restapidocs/swagger-ui.css.map diff --git a/gemma-rest/src/test/java/ubic/gemma/rest/serializers/FactorValueValueObjectSerializerTest.java b/gemma-rest/src/test/java/ubic/gemma/rest/serializers/FactorValueValueObjectSerializerTest.java new file mode 100644 index 0000000000..c587cc3cee --- /dev/null +++ b/gemma-rest/src/test/java/ubic/gemma/rest/serializers/FactorValueValueObjectSerializerTest.java @@ -0,0 +1,82 @@ +package ubic.gemma.rest.serializers; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.jayway.jsonassert.JsonAssert; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests; +import ubic.gemma.model.expression.experiment.ExperimentalFactor; +import ubic.gemma.model.expression.experiment.FactorValue; +import ubic.gemma.model.expression.experiment.FactorValueValueObject; +import ubic.gemma.model.expression.experiment.Statement; +import ubic.gemma.persistence.util.TestComponent; +import ubic.gemma.rest.util.JacksonConfig; + +import java.text.ParseException; + +@ContextConfiguration +public class FactorValueValueObjectSerializerTest extends AbstractJUnit4SpringContextTests { + + @Configuration + @TestComponent + @Import(JacksonConfig.class) + static class FactorValueValueObjectSerializerTestContextConfiguration { + } + + @Autowired + private ObjectMapper objectMapper; + + + @Test + public void test() throws JsonProcessingException, ParseException { + FactorValue fv = new FactorValue(); + fv.setId( 1L ); + fv.setExperimentalFactor( new ExperimentalFactor() ); + fv.getCharacteristics().add( createCharacteristic( 1L, "foo", null, "bar", null ) ); + fv.getCharacteristics().add( createStatement( 2L, "foo", null, "bar", null, "has role", null, "control", null ) ); + FactorValueValueObject fvvo = new FactorValueValueObject( fv ); + JsonAssert.with( objectMapper.writeValueAsString( fvvo ) ) + .assertEquals( "$.characteristics[0].id", 2 ) + .assertEquals( "$.characteristics[0].category", "foo" ) + .assertEquals( "$.characteristics[0].valueId", "http://gemma.msl.ubc.ca/ont/TGFVO/1/1" ) + .assertEquals( "$.characteristics[0].value", "bar" ) + .assertEquals( "$.characteristics[1].id", 1 ) + .assertEquals( "$.characteristics[1].category", "foo" ) + .assertEquals( "$.characteristics[1].valueId", "http://gemma.msl.ubc.ca/ont/TGFVO/1/3" ) + .assertEquals( "$.characteristics[1].value", "bar" ) + .assertEquals( "$.statements[0].category", "foo" ) + .assertEquals( "$.statements[0].subjectId", "http://gemma.msl.ubc.ca/ont/TGFVO/1/1" ) + .assertEquals( "$.statements[0].subject", "bar" ) + .assertEquals( "$.statements[0].predicate", "has role" ) + .assertEquals( "$.statements[0].objectId", "http://gemma.msl.ubc.ca/ont/TGFVO/1/2" ) + .assertEquals( "$.statements[0].object", "control" ); + } + + private Statement createCharacteristic( Long id, String category, String categoryUri, String value, String valueUri ) { + Statement statement = new Statement(); + statement.setId( id ); + statement.setCategory( category ); + statement.setCategoryUri( categoryUri ); + statement.setSubject( value ); + statement.setSubjectUri( valueUri ); + return statement; + } + + private Statement createStatement( Long id, String category, String categoryUri, String subject, String subjectUri, String predicate, String predicateUri, String object, String objectUri ) { + Statement statement = new Statement(); + statement.setId( id ); + statement.setCategory( category ); + statement.setCategoryUri( categoryUri ); + statement.setSubject( subject ); + statement.setSubjectUri( subjectUri ); + statement.setPredicate( predicate ); + statement.setPredicateUri( predicateUri ); + statement.setObject( object ); + statement.setObjectUri( objectUri ); + return statement; + } +} \ No newline at end of file diff --git a/gemma-rest/src/test/java/ubic/gemma/rest/util/BaseJerseyIntegrationTest.java b/gemma-rest/src/test/java/ubic/gemma/rest/util/BaseJerseyIntegrationTest.java index c27b0c9781..5062301717 100644 --- a/gemma-rest/src/test/java/ubic/gemma/rest/util/BaseJerseyIntegrationTest.java +++ b/gemma-rest/src/test/java/ubic/gemma/rest/util/BaseJerseyIntegrationTest.java @@ -6,17 +6,16 @@ import org.junit.experimental.categories.Category; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ContextConfiguration; import ubic.gemma.core.util.test.TestAuthenticationUtils; -import ubic.gemma.core.util.test.category.SpringContextTest; +import ubic.gemma.core.util.test.category.IntegrationTest; /** * Base class for Jersey-based integration tests. * * @author poirigui */ -@Category(SpringContextTest.class) +@Category(IntegrationTest.class) @ContextConfiguration(locations = { "classpath*:ubic/gemma/applicationContext-*.xml" }) public abstract class BaseJerseyIntegrationTest extends BaseJerseyTest { diff --git a/gemma-rest/src/test/java/ubic/gemma/rest/util/BaseJerseyTest.java b/gemma-rest/src/test/java/ubic/gemma/rest/util/BaseJerseyTest.java index 3e6d043c09..60cd2c0230 100644 --- a/gemma-rest/src/test/java/ubic/gemma/rest/util/BaseJerseyTest.java +++ b/gemma-rest/src/test/java/ubic/gemma/rest/util/BaseJerseyTest.java @@ -47,7 +47,7 @@ protected final TestContainerFactory getTestContainerFactory() throws TestContai } @Override - public final void afterPropertiesSet() throws Exception { + public final void afterPropertiesSet() { application.property( "contextConfig", applicationContext ); } diff --git a/gemma-web/pom.xml b/gemma-web/pom.xml index 61953bb94e..d1b18a3956 100644 --- a/gemma-web/pom.xml +++ b/gemma-web/pom.xml @@ -3,7 +3,7 @@ gemma gemma - 1.30.6 + 1.31.0 4.0.0 gemma-web @@ -101,7 +101,14 @@ org.apache.maven.plugins maven-war-plugin - 3.3.2 + + + + ${project.basedir}/../gemma-rest/src/main/resources/restapidocs + resources/restapidocs + + + @@ -182,7 +189,7 @@ org.json json - 20230227 + 20231013 diff --git a/gemma-web/src/main/config/log4j-dev.properties b/gemma-web/src/main/config/log4j-dev.properties index 7e6caf3581..3001a5b4b8 100644 --- a/gemma-web/src/main/config/log4j-dev.properties +++ b/gemma-web/src/main/config/log4j-dev.properties @@ -45,6 +45,7 @@ log4j.logger.org.springframework.beans.GenericTypeAwarePropertyDescriptor=FATAL log4j.logger.org.springframework.scheduling.quartz=INFO log4j.logger.org.springframework.security.access.event.LoggerListener=INFO log4j.logger.org.springframework.security.authentication.event.LoggerListener=ERROR +log4j.logger.org.springframework.web.servlet.PageNotFound=ERROR # DWR # suppress inspection "SpellCheckingInspection" diff --git a/gemma-web/src/main/config/log4j.properties b/gemma-web/src/main/config/log4j.properties index 5fab3b4305..1b5bda4a32 100644 --- a/gemma-web/src/main/config/log4j.properties +++ b/gemma-web/src/main/config/log4j.properties @@ -119,6 +119,7 @@ log4j.logger.org.springframework.beans.GenericTypeAwarePropertyDescriptor=FATAL log4j.logger.org.springframework.scheduling.quartz=INFO log4j.logger.org.springframework.security.access.event.LoggerListener=INFO log4j.logger.org.springframework.security.authentication.event.LoggerListener=ERROR +log4j.logger.org.springframework.web.servlet.PageNotFound=ERROR # DWR # suppress inspection "SpellCheckingInspection" diff --git a/gemma-web/src/main/java/ubic/gemma/web/controller/BaseFormController.java b/gemma-web/src/main/java/ubic/gemma/web/controller/BaseFormController.java index 3fd03b0a44..180cccef7d 100644 --- a/gemma-web/src/main/java/ubic/gemma/web/controller/BaseFormController.java +++ b/gemma-web/src/main/java/ubic/gemma/web/controller/BaseFormController.java @@ -1,8 +1,8 @@ /* * The Gemma project - * + * * Copyright (c) 2006 University of British Columbia - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -46,9 +46,11 @@ * Implementation of SimpleFormController that contains convenience methods for subclasses. For * example, getting the current user and saving messages/errors. This class is intended to be a base class for all Form * controllers. + * @deprecated {@link SimpleFormController} is deprecated, use annotations-based GET/POST mapping instead. * * @author pavlidis (originally based on Appfuse code) */ +@Deprecated public abstract class BaseFormController extends SimpleFormController { protected static final Log log = LogFactory.getLog( BaseFormController.class.getName() ); diff --git a/gemma-web/src/main/java/ubic/gemma/web/controller/OntologyController.java b/gemma-web/src/main/java/ubic/gemma/web/controller/OntologyController.java index c154d4aaa1..10bc6fc8d6 100644 --- a/gemma-web/src/main/java/ubic/gemma/web/controller/OntologyController.java +++ b/gemma-web/src/main/java/ubic/gemma/web/controller/OntologyController.java @@ -1,28 +1,49 @@ package ubic.gemma.web.controller; +import lombok.extern.apachecommons.CommonsLog; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.view.RedirectView; import org.springframework.web.util.UriComponentsBuilder; +import ubic.basecode.ontology.model.OntologyIndividual; +import ubic.basecode.ontology.model.OntologyResource; import ubic.basecode.ontology.model.OntologyTerm; +import ubic.gemma.core.ontology.FactorValueOntologyService; import ubic.gemma.core.ontology.providers.GemmaOntologyService; import ubic.gemma.web.util.EntityNotFoundException; import ubic.gemma.web.util.ServiceUnavailableException; +import java.io.StringWriter; +import java.util.List; +import java.util.Optional; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +import static org.apache.commons.text.StringEscapeUtils.escapeHtml4; + /** * Provide minimal support for exposing Gemma ontology. * @author poirigui */ @Controller +@CommonsLog public class OntologyController { + private static final String + TGEMO_URI_PREFIX = "http://gemma.msl.ubc.ca/ont/", + TGFVO_URI_PREFIX = "http://gemma.msl.ubc.ca/ont/TGFVO/"; + + private static final MediaType RDF_XML = MediaType.parseMediaType( "application/rdf+xml" ); + @Autowired private GemmaOntologyService gemmaOntologyService; + @Autowired + private FactorValueOntologyService factorValueOntologyService; + @RequestMapping(value = "/ont/TGEMO.OWL", method = RequestMethod.GET) public RedirectView getOntology() { String gemmaOntologyUrl = gemmaOntologyService.getOntologyUrl(); @@ -36,7 +57,7 @@ public RedirectView getTerm( @PathVariable("termId") String termId ) { if ( !gemmaOntologyService.isOntologyLoaded() ) { throw new ServiceUnavailableException( "TGEMO is not loaded." ); } - String iri = "http://gemma.msl.ubc.ca/ont/" + termId; + String iri = TGEMO_URI_PREFIX + termId; OntologyTerm term = gemmaOntologyService.getTerm( iri ); if ( term == null ) { throw new EntityNotFoundException( String.format( "No term with IRI %s in TGEMO.", iri ) ); @@ -50,4 +71,88 @@ public RedirectView getTerm( @PathVariable("termId") String termId ) { redirectView.setStatusCode( HttpStatus.FOUND ); return redirectView; } + + @ResponseBody + @RequestMapping(value = "/ont/TGFVO/{factorValueId}", method = RequestMethod.GET, produces = { MediaType.TEXT_HTML_VALUE, "application/rdf+xml" }) + public String getFactorValue( @PathVariable("factorValueId") Long factorValueId, @RequestHeader(value = "Accept", required = false) String acceptHeader ) { + String iri = TGFVO_URI_PREFIX + factorValueId; + OntologyIndividual oi = factorValueOntologyService.getIndividual( iri ); + if ( oi == null ) { + throw new EntityNotFoundException( String.format( "No individual with IRI %s in TGFVO.", iri ) ); + } + MediaType mediaType = Optional.ofNullable( acceptHeader ) + .map( MediaType::parseMediaTypes ) + .map( List::stream ) + .flatMap( Stream::findFirst ) + .orElse( MediaType.TEXT_HTML ); + if ( mediaType.isCompatibleWith( RDF_XML ) ) { + StringWriter sw = new StringWriter(); + factorValueOntologyService.writeToRdf( iri, sw ); + return sw.toString(); + } + StringBuilder s = new StringBuilder(); + s.append( String.format( "FactorValue #%d: %s", factorValueId, escapeHtml4( oi.getLabel() ) ) ); + s.append( "

" ); + s.append( String.format( "

FactorValue #%d: %s

", factorValueId, renderOntologyResource( oi ) ) ); + s.append( "
    " ); + if ( oi.getInstanceOf() != null ) { + s.append( "
  • instance of " ).append( renderOntologyResource( oi.getInstanceOf() ) ).append( "
  • " ); + } + for ( OntologyIndividual relatedOi : factorValueOntologyService.getFactorValueAnnotations( iri ) ) { + s.append( "
  • has annotation " ).append( renderOntologyResource( relatedOi ) ).append( "
  • " ); + } + for ( FactorValueOntologyService.OntologyStatement st : factorValueOntologyService.getFactorValueStatements( iri ) ) { + s.append( String.format( "
  • %s %s %s
  • ", renderOntologyResource( st.getSubject() ), renderOntologyResource( st.getPredicate() ), renderOntologyResource( st.getObject() ) ) ); + } + s.append( "
" ); + s.append( "
" ); + return s.toString(); + } + + @ResponseBody + @RequestMapping(value = "/ont/TGFVO/{factorValueId}/{annotationId}", method = RequestMethod.GET, produces = { MediaType.TEXT_HTML_VALUE, "application/rdf+xml" }) + public String getFactorValueAnnotation( @PathVariable("factorValueId") Long factorValueId, @PathVariable("annotationId") Long annotationId, @RequestHeader(value = "Accept", required = false) String acceptHeader ) { + String iri = TGFVO_URI_PREFIX + factorValueId + "/" + annotationId; + OntologyIndividual oi = factorValueOntologyService.getIndividual( iri ); + if ( oi == null ) { + throw new EntityNotFoundException( String.format( "No individual with IRI %s in TGFVO.", iri ) ); + } + MediaType mediaType = Optional.ofNullable( acceptHeader ) + .map( MediaType::parseMediaTypes ) + .map( List::stream ) + .flatMap( Stream::findFirst ) + .orElse( MediaType.TEXT_HTML ); + if ( mediaType.isCompatibleWith( RDF_XML ) ) { + StringWriter sw = new StringWriter(); + factorValueOntologyService.writeToRdf( iri, sw ); + return sw.toString(); + } + StringBuilder s = new StringBuilder(); + s.append( String.format( "Annotation #%d of FactorValue #%d: %s", annotationId, factorValueId, oi.getLabel() ) ); + s.append( "
" ); + s.append( String.format( "

Annotation #%d of FactorValue #%d: %s

", annotationId, factorValueId, renderOntologyResource( oi ) ) ); + s.append( "
    " ); + if ( oi.getInstanceOf() != null ) { + s.append( "
  • instance of " ).append( renderOntologyResource( oi.getInstanceOf() ) ).append( "
  • " ); + } + OntologyIndividual factorValueOi = factorValueOntologyService.getIndividual( TGFVO_URI_PREFIX + factorValueId ); + if ( factorValueOi != null ) { + s.append( "
  • annotation of " ).append( renderOntologyResource( factorValueOi ) ).append( "
  • " ); + } + s.append( "
" ); + s.append( "
" ); + return s.toString(); + } + + private String renderOntologyResource( OntologyResource oi ) { + if ( oi.getUri() == null ) { + return escapeHtml4( oi.getLabel() ); + } else { + return String.format( "%s", + escapeHtml4( oi.getUri() + .replaceFirst( "^" + Pattern.quote( TGFVO_URI_PREFIX ), "/ont/TGFVO/" ) + .replaceFirst( "^" + Pattern.quote( TGEMO_URI_PREFIX ), "/ont/" ) ), + escapeHtml4( oi.getLabel() ) ); + } + } } diff --git a/gemma-web/src/main/java/ubic/gemma/web/controller/PhenotypeController.java b/gemma-web/src/main/java/ubic/gemma/web/controller/PhenotypeController.java index 2f89081278..d44832329c 100644 --- a/gemma-web/src/main/java/ubic/gemma/web/controller/PhenotypeController.java +++ b/gemma-web/src/main/java/ubic/gemma/web/controller/PhenotypeController.java @@ -29,6 +29,7 @@ import ubic.gemma.model.analysis.expression.diff.GeneDifferentialExpressionMetaAnalysis; import ubic.gemma.model.association.phenotype.PhenotypeAssociation; import ubic.gemma.model.common.description.BibliographicReferenceValueObject; +import ubic.gemma.model.common.description.CharacteristicValueObject; import ubic.gemma.model.common.description.ExternalDatabaseValueObject; import ubic.gemma.model.genome.gene.phenotype.EvidenceFilter; import ubic.gemma.model.genome.gene.phenotype.valueObject.*; @@ -156,7 +157,7 @@ public ValidateEvidenceValueObject makeDifferentialExpressionEvidencesFromDiffEx geneDifferentialExpressionMetaAnalysisId, phenotypes, selectionThreshold ); // get the permission of the metaAnalysis - EntityDelegator ed = new EntityDelegator(); + EntityDelegator ed = new EntityDelegator<>(); ed.setId( geneDifferentialExpressionMetaAnalysisId ); ed.setClassDelegatingFor( GeneDifferentialExpressionMetaAnalysis.class.getName() ); @@ -169,7 +170,7 @@ public ValidateEvidenceValueObject makeDifferentialExpressionEvidencesFromDiffEx return validateEvidenceValueObject; } - public ValidateEvidenceValueObject processPhenotypeAssociationForm( EvidenceValueObject evidenceValueObject ) { + public ValidateEvidenceValueObject processPhenotypeAssociationForm( EvidenceValueObject evidenceValueObject ) { ValidateEvidenceValueObject validateEvidenceValueObject; try { @@ -237,7 +238,7 @@ public ModelAndView showPhenotypeAssociationManager() { return new ModelAndView( "phenotypeAssociationManager" ); } - public ValidateEvidenceValueObject validatePhenotypeAssociationForm( EvidenceValueObject evidenceValueObject ) { + public ValidateEvidenceValueObject validatePhenotypeAssociationForm( EvidenceValueObject evidenceValueObject ) { ValidateEvidenceValueObject validateEvidenceValueObject; try { validateEvidenceValueObject = this.phenotypeAssociationManagerService diff --git a/gemma-web/src/main/java/ubic/gemma/web/controller/common/CharacteristicBrowserController.java b/gemma-web/src/main/java/ubic/gemma/web/controller/common/CharacteristicBrowserController.java index 56bfe665c7..4ae1de8ff5 100644 --- a/gemma-web/src/main/java/ubic/gemma/web/controller/common/CharacteristicBrowserController.java +++ b/gemma-web/src/main/java/ubic/gemma/web/controller/common/CharacteristicBrowserController.java @@ -35,6 +35,7 @@ import ubic.gemma.model.expression.experiment.ExperimentalFactor; import ubic.gemma.model.expression.experiment.ExpressionExperiment; import ubic.gemma.model.expression.experiment.FactorValue; +import ubic.gemma.model.expression.experiment.FactorValueUtils; import ubic.gemma.persistence.service.common.description.CharacteristicService; import ubic.gemma.persistence.service.expression.experiment.FactorValueService; import ubic.gemma.web.remote.JsonReaderResponse; @@ -195,13 +196,11 @@ public Collection findCharacteristicsCustom( String query if ( results.size() < MAX_RESULTS && searchFVVs ) { // non-characteristics. Collection factorValues = factorValueService.findByValue( queryString ); for ( FactorValue factorValue : factorValues ) { - if ( factorValue.getCharacteristics().size() > 0 ) - continue; - if ( StringUtils.isBlank( factorValue.getValue() ) ) + if ( !factorValue.getCharacteristics().isEmpty() ) continue; AnnotationValueObject avo = new AnnotationValueObject( factorValue.getId() ); - avo.setTermName( factorValue.getValue() ); + avo.setTermName( FactorValueUtils.getSummaryString( factorValue ) ); avo.setObjectClass( FactorValue.class.getSimpleName() ); populateParentInformation( avo, factorValue ); @@ -257,7 +256,7 @@ private void populateParentInformation( AnnotationValueObject avo, @Nullable Ide avo.setParentLink( AnchorTagUtil.getBioMaterialLink( bm, String.format( "Sample: %s", bm.getName() ), servletContext ) ); } else if ( annotatedItem instanceof FactorValue ) { FactorValue fv = ( FactorValue ) annotatedItem; - avo.setParentDescription( String.format( "FactorValue: %s", fv.getDescriptiveString() ) ); + avo.setParentDescription( String.format( "FactorValue: %s", FactorValueUtils.getSummaryString( fv ) ) ); ExperimentalFactor ef = fv.getExperimentalFactor(); avo.setParentOfParentLink( AnchorTagUtil.getExperimentalDesignLink( ef.getExperimentalDesign(), "Exp Fac: " + ef.getName() + " (" + StringUtils.abbreviate( ef.getDescription(), 50 ) + ")", servletContext ) ); diff --git a/gemma-web/src/main/java/ubic/gemma/web/controller/common/auditAndSecurity/SecurityController.java b/gemma-web/src/main/java/ubic/gemma/web/controller/common/auditAndSecurity/SecurityController.java index d3d2105ae0..fdb1abb7bc 100644 --- a/gemma-web/src/main/java/ubic/gemma/web/controller/common/auditAndSecurity/SecurityController.java +++ b/gemma-web/src/main/java/ubic/gemma/web/controller/common/auditAndSecurity/SecurityController.java @@ -1,19 +1,20 @@ /* * The Gemma project - * + * * Copyright (c) 2012 University of British Columbia - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ package ubic.gemma.web.controller.common.auditAndSecurity; +import gemma.gsec.model.Securable; import org.springframework.dao.DataIntegrityViolationException; import ubic.gemma.persistence.service.expression.experiment.ExpressionExperimentService; import ubic.gemma.web.remote.EntityDelegator; @@ -70,7 +71,7 @@ public interface SecurityController { Collection getGroupMembers( String groupName ); - SecurityInfoValueObject getSecurityInfo( EntityDelegator ed ); + SecurityInfoValueObject getSecurityInfo( EntityDelegator ed ); /** * AJAX @@ -83,17 +84,17 @@ public interface SecurityController { */ Collection getUsersData( String currentGroup, boolean privateOnly ); - boolean makeGroupReadable( EntityDelegator ed, String groupName ); + boolean makeGroupReadable( EntityDelegator ed, String groupName ); - boolean makeGroupWriteable( EntityDelegator ed, String groupName ); + boolean makeGroupWriteable( EntityDelegator ed, String groupName ); - boolean makePrivate( EntityDelegator ed ); + boolean makePrivate( EntityDelegator ed ); - boolean makePublic( EntityDelegator ed ); + boolean makePublic( EntityDelegator ed ); - boolean removeGroupReadable( EntityDelegator ed, String groupName ); + boolean removeGroupReadable( EntityDelegator ed, String groupName ); - boolean removeGroupWriteable( EntityDelegator ed, String groupName ); + boolean removeGroupWriteable( EntityDelegator ed, String groupName ); boolean removeUsersFromGroup( String[] userNames, String groupName ); diff --git a/gemma-web/src/main/java/ubic/gemma/web/controller/common/auditAndSecurity/SecurityControllerImpl.java b/gemma-web/src/main/java/ubic/gemma/web/controller/common/auditAndSecurity/SecurityControllerImpl.java index 612331996b..9889b3eb02 100644 --- a/gemma-web/src/main/java/ubic/gemma/web/controller/common/auditAndSecurity/SecurityControllerImpl.java +++ b/gemma-web/src/main/java/ubic/gemma/web/controller/common/auditAndSecurity/SecurityControllerImpl.java @@ -272,7 +272,7 @@ public Collection getGroupMembers( String groupName ) { @Override // @Transactional(readOnly = true) - public SecurityInfoValueObject getSecurityInfo( EntityDelegator ed ) { + public SecurityInfoValueObject getSecurityInfo( EntityDelegator ed ) { // TODO Figure out why Transaction(readOnly = true) throws an error when this method is called from // SecurityManager.js (Bug 3941) @@ -304,42 +304,42 @@ public Collection getUsersData( String currentGroup, bo } @Override - public boolean makeGroupReadable( EntityDelegator ed, String groupName ) { + public boolean makeGroupReadable( EntityDelegator ed, String groupName ) { Securable s = this.getSecurable( ed ); securityService.makeReadableByGroup( s, groupName ); return true; } @Override - public boolean makeGroupWriteable( EntityDelegator ed, String groupName ) { + public boolean makeGroupWriteable( EntityDelegator ed, String groupName ) { Securable s = this.getSecurable( ed ); securityService.makeWriteableByGroup( s, groupName ); return true; } @Override - public boolean makePrivate( EntityDelegator ed ) { + public boolean makePrivate( EntityDelegator ed ) { Securable s = this.getSecurable( ed ); securityService.makePrivate( s ); return true; } @Override - public boolean makePublic( EntityDelegator ed ) { + public boolean makePublic( EntityDelegator ed ) { Securable s = this.getSecurable( ed ); securityService.makePublic( s ); return true; } @Override - public boolean removeGroupReadable( EntityDelegator ed, String groupName ) { + public boolean removeGroupReadable( EntityDelegator ed, String groupName ) { Securable s = this.getSecurable( ed ); securityService.makeUnreadableByGroup( s, groupName ); return true; } @Override - public boolean removeGroupWriteable( EntityDelegator ed, String groupName ) { + public boolean removeGroupWriteable( EntityDelegator ed, String groupName ) { Securable s = this.getSecurable( ed ); securityService.makeUnwriteableByGroup( s, groupName ); return true; @@ -361,7 +361,7 @@ public void setExpressionExperimentService( ExpressionExperimentService expressi @Override public SecurityInfoValueObject updatePermission( SecurityInfoValueObject settings ) { - EntityDelegator sd = new EntityDelegator(); + EntityDelegator sd = new EntityDelegator<>(); sd.setId( settings.getEntityId() ); sd.setClassDelegatingFor( settings.getEntityClazz() ); Securable s = this.getSecurable( sd ); @@ -517,34 +517,24 @@ private Collection getGroupsUserCanEdit() { * @return securable * @throws IllegalArgumentException if the Securable cannot be loaded */ - private Securable getSecurable( EntityDelegator ed ) { - String classDelegatingFor = ed.getClassDelegatingFor(); - - Class clazz; + private Securable getSecurable( EntityDelegator ed ) { Securable s; - try { - clazz = Class.forName( classDelegatingFor ); - } catch ( ClassNotFoundException e1 ) { - throw new RuntimeException( e1 ); - } - if ( ExpressionExperiment.class.isAssignableFrom( clazz ) ) { + if ( ed.holds( ExpressionExperiment.class ) ) { s = expressionExperimentService.load( ed.getId() ); - } else if ( GeneSet.class.isAssignableFrom( clazz ) ) { + } else if ( ed.holds( GeneSet.class ) ) { s = geneSetService.load( ed.getId() ); - } else if ( ExpressionExperimentSet.class.isAssignableFrom( clazz ) ) { + } else if ( ed.holds( ExpressionExperimentSet.class ) ) { s = expressionExperimentSetService.load( ed.getId() ); - } else if ( PhenotypeAssociation.class.isAssignableFrom( clazz ) ) { + } else if ( ed.holds( PhenotypeAssociation.class ) ) { s = phenotypeAssociationService.load( ed.getId() ); - } else if ( GeneDifferentialExpressionMetaAnalysis.class.isAssignableFrom( clazz ) ) { + } else if ( ed.holds( GeneDifferentialExpressionMetaAnalysis.class ) ) { s = geneDiffExMetaAnalysisService.load( ed.getId() ); } else { - throw new UnsupportedOperationException( clazz + " not supported by security controller yet" ); + throw new IllegalArgumentException( "Delegating " + ed.getClassDelegatingFor() + " is not supported by security controller yet." ); } - if ( s == null ) { - throw new EntityNotFoundException( clazz.getSimpleName() + " with ID " + ed.getId() + " does not exist or user does not have access." ); + throw new EntityNotFoundException( "Entity does not exist or user does not have access." ); } - return s; } diff --git a/gemma-web/src/main/java/ubic/gemma/web/controller/common/auditAndSecurity/SignupController.java b/gemma-web/src/main/java/ubic/gemma/web/controller/common/auditAndSecurity/SignupController.java index 0046d9040b..482fc920b1 100644 --- a/gemma-web/src/main/java/ubic/gemma/web/controller/common/auditAndSecurity/SignupController.java +++ b/gemma-web/src/main/java/ubic/gemma/web/controller/common/auditAndSecurity/SignupController.java @@ -162,20 +162,14 @@ public void signup( if ( reCaptcha.isPrivateKeySet() ) { if ( !reCaptcha.validateRequest( request ).isValid() ) { - JSONObject json = new JSONObject(); - json.put( "success", false ); - json.put( "message", "Captcha was not entered correctly." ); - JsonUtil.writeToResponse( json, response ); + JsonUtil.writeErrorToResponse( HttpServletResponse.SC_BAD_REQUEST, "Captcha was not entered correctly.", response ); return; } } if ( password.length() < UserFormMultiActionController.MIN_PASSWORD_LENGTH || !password.equals( cPass ) ) { - JSONObject json = new JSONObject(); - json.put( "success", false ); - json.put( "message", "Password was not valid or didn't match" ); - JsonUtil.writeToResponse( json, response ); + JsonUtil.writeErrorToResponse( HttpServletResponse.SC_BAD_REQUEST, "Password was not valid or didn't match", response ); return; } @@ -187,10 +181,7 @@ public void signup( */ if ( !email.matches( "^(\\w+)([-+.][\\w]+)*@(\\w[-\\w]*\\.){1,5}([A-Za-z]){2,4}$" ) || !email .equals( cEmail ) ) { - JSONObject json = new JSONObject(); - json.put( "success", false ); - json.put( "message", "Email was not valid or didn't match" ); - JsonUtil.writeToResponse( json, response ); + JsonUtil.writeErrorToResponse( HttpServletResponse.SC_BAD_REQUEST, "Email was not valid or didn't match", response ); return; } diff --git a/gemma-web/src/main/java/ubic/gemma/web/controller/expression/arrayDesign/ArrayDesignController.java b/gemma-web/src/main/java/ubic/gemma/web/controller/expression/arrayDesign/ArrayDesignController.java index 87cccd15ac..848875403b 100644 --- a/gemma-web/src/main/java/ubic/gemma/web/controller/expression/arrayDesign/ArrayDesignController.java +++ b/gemma-web/src/main/java/ubic/gemma/web/controller/expression/arrayDesign/ArrayDesignController.java @@ -75,14 +75,14 @@ Collection getArrayDesigns( Long[] arrayDesignIds, boole /** * Exposed for AJAX calls. */ - Collection getCsSummaries( EntityDelegator ed ); + Collection getCsSummaries( EntityDelegator ed ); Collection getDesignSummaries( ArrayDesign arrayDesign ); /** * @return the HTML to display. */ - Map getReportHtml( EntityDelegator ed ); + Map getReportHtml( EntityDelegator ed ); String getSummaryForArrayDesign( Long id ); @@ -90,7 +90,7 @@ Collection getArrayDesigns( Long[] arrayDesignIds, boole ArrayDesignValueObject loadArrayDesignsSummary(); - String remove( EntityDelegator ed ); + String remove( EntityDelegator ed ); /** * Show all array designs, or according to a list of IDs passed in. @@ -118,7 +118,7 @@ Collection getArrayDesigns( Long[] arrayDesignIds, boole @RequestMapping("/showExpressionExperiments.html") ModelAndView showExpressionExperiments( @RequestParam("id") Long id ); - String updateReport( EntityDelegator ed ); + String updateReport( EntityDelegator ed ); String updateReportById( Long id ); diff --git a/gemma-web/src/main/java/ubic/gemma/web/controller/expression/arrayDesign/ArrayDesignControllerImpl.java b/gemma-web/src/main/java/ubic/gemma/web/controller/expression/arrayDesign/ArrayDesignControllerImpl.java index 560e9ed1d1..6d550e6e79 100644 --- a/gemma-web/src/main/java/ubic/gemma/web/controller/expression/arrayDesign/ArrayDesignControllerImpl.java +++ b/gemma-web/src/main/java/ubic/gemma/web/controller/expression/arrayDesign/ArrayDesignControllerImpl.java @@ -160,13 +160,13 @@ public ModelAndView delete( @RequestParam("id") Long id ) { @RequestMapping(value = "/downloadAnnotationFile.html", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE) public void downloadAnnotationFile( @RequestParam("id") Long arrayDesignId, @RequestParam(value = "fileType", required = false) String fileType, HttpServletResponse response ) throws IOException { - if ( fileType == null ) { + if ( fileType == null || fileType.equalsIgnoreCase( "allParents" ) ) { fileType = ArrayDesignAnnotationService.STANDARD_FILE_SUFFIX; } else if ( fileType.equalsIgnoreCase( "noParents" ) ) { fileType = ArrayDesignAnnotationService.NO_PARENTS_FILE_SUFFIX; - } else if ( fileType.equalsIgnoreCase( "bioProcess" ) ) + } else if ( fileType.equalsIgnoreCase( "bioProcess" ) ) { fileType = ArrayDesignAnnotationService.BIO_PROCESS_FILE_SUFFIX; - else { + } else { throw new IllegalArgumentException( "Unknown file type for the 'fileType' query parameter." ); } ArrayDesign arrayDesign = arrayDesignService.load( arrayDesignId ); @@ -301,7 +301,7 @@ public Collection getArrayDesigns( Long[] arrayDesignIds } @Override - public Collection getCsSummaries( EntityDelegator ed ) { + public Collection getCsSummaries( EntityDelegator ed ) { ArrayDesign arrayDesign = arrayDesignService.load( ed.getId() ); return this.getDesignSummaries( arrayDesign ); } @@ -398,7 +398,7 @@ private void setSummaryInfo( ArrayDesignValueObjectExt result, Long id ) { } @Override - public Map getReportHtml( EntityDelegator ed ) { + public Map getReportHtml( EntityDelegator ed ) { assert ed.getId() != null; ArrayDesignValueObject summary = arrayDesignReportService.getSummaryObject( ed.getId() ); Map result = new HashMap<>(); @@ -453,7 +453,7 @@ public ArrayDesignValueObject loadArrayDesignsSummary() { } @Override - public String remove( EntityDelegator ed ) { + public String remove( EntityDelegator ed ) { ArrayDesign arrayDesign = arrayDesignService.load( ed.getId() ); if ( arrayDesign == null ) { throw new EntityNotFoundException( ed.getId() + " not found" ); @@ -540,7 +540,7 @@ public ModelAndView showExpressionExperiments( @RequestParam("id") Long id ) { } @Override - public String updateReport( EntityDelegator ed ) { + public String updateReport( EntityDelegator ed ) { GenerateArraySummaryLocalTask job = new GenerateArraySummaryLocalTask( new TaskCommand( ed.getId() ) ); return taskRunningService.submitTask( job ); } diff --git a/gemma-web/src/main/java/ubic/gemma/web/controller/expression/arrayDesign/ArrayDesignFormController.java b/gemma-web/src/main/java/ubic/gemma/web/controller/expression/arrayDesign/ArrayDesignFormController.java index 4b27e9fa17..a5ad4e0c66 100644 --- a/gemma-web/src/main/java/ubic/gemma/web/controller/expression/arrayDesign/ArrayDesignFormController.java +++ b/gemma-web/src/main/java/ubic/gemma/web/controller/expression/arrayDesign/ArrayDesignFormController.java @@ -141,8 +141,8 @@ protected ModelAndView getCancelView( HttpServletRequest request ) { .collect( Collectors.toList() ); @Override - protected Map referenceData( HttpServletRequest request ) { - Map> mapping = new HashMap>(); + protected Map> referenceData( HttpServletRequest request ) { + Map> mapping = new HashMap<>(); mapping.put( "technologyTypes", new ArrayList<>( TECHNOLOGY_TYPES ) ); return mapping; } diff --git a/gemma-web/src/main/java/ubic/gemma/web/controller/expression/biomaterial/BioMaterialController.java b/gemma-web/src/main/java/ubic/gemma/web/controller/expression/biomaterial/BioMaterialController.java index dfc92de11e..e3e5558efd 100644 --- a/gemma-web/src/main/java/ubic/gemma/web/controller/expression/biomaterial/BioMaterialController.java +++ b/gemma-web/src/main/java/ubic/gemma/web/controller/expression/biomaterial/BioMaterialController.java @@ -78,10 +78,10 @@ public class BioMaterialController { * biomaterials * will remove the previous one and add the new one. */ - public void addFactorValueTo( Collection bmIds, EntityDelegator factorValueId ) { + public void addFactorValueTo( Collection bmIds, EntityDelegator factorValueId ) { Collection bms = this.getBioMaterials( bmIds ); - FactorValue factorVToAdd = factorValueService.loadOrFail( factorValueId.getId() ); + FactorValue factorVToAdd = factorValueService.loadWithExperimentalFactorOrFail( factorValueId.getId() ); ExperimentalFactor eFactor = factorVToAdd.getExperimentalFactor(); for ( BioMaterial material : bms ) { @@ -121,7 +121,7 @@ public ModelAndView annot( HttpServletRequest request, HttpServletResponse respo return mav; } - public Collection getAnnotation( EntityDelegator bm ) { + public Collection getAnnotation( EntityDelegator bm ) { if ( bm == null || bm.getId() == null ) return null; BioMaterial bioM = bioMaterialService.loadOrFail( bm.getId() ); @@ -169,7 +169,7 @@ public Collection getBioMaterialsForEE( Long id ) { return bioMaterials; } - public Collection getFactorValues( EntityDelegator bm ) { + public Collection getFactorValues( EntityDelegator bm ) { if ( bm == null || bm.getId() == null ) return null; diff --git a/gemma-web/src/main/java/ubic/gemma/web/controller/expression/designElement/CompositeSequenceController.java b/gemma-web/src/main/java/ubic/gemma/web/controller/expression/designElement/CompositeSequenceController.java index 6a800f9c2d..2f43b06511 100644 --- a/gemma-web/src/main/java/ubic/gemma/web/controller/expression/designElement/CompositeSequenceController.java +++ b/gemma-web/src/main/java/ubic/gemma/web/controller/expression/designElement/CompositeSequenceController.java @@ -136,7 +136,7 @@ public Collection getGeneCsSummaries( Long gene /** * Exposed for AJAX calls. */ - public Collection getGeneMappingSummary( EntityDelegator csd ) { + public Collection getGeneMappingSummary( EntityDelegator csd ) { log.debug( "Started processing AJAX call: getGeneMappingSummary" ); if ( csd == null || csd.getId() == null ) { return new HashSet<>(); diff --git a/gemma-web/src/main/java/ubic/gemma/web/controller/expression/experiment/AnnotationController.java b/gemma-web/src/main/java/ubic/gemma/web/controller/expression/experiment/AnnotationController.java index 12890d3eb6..0084168412 100644 --- a/gemma-web/src/main/java/ubic/gemma/web/controller/expression/experiment/AnnotationController.java +++ b/gemma-web/src/main/java/ubic/gemma/web/controller/expression/experiment/AnnotationController.java @@ -24,15 +24,16 @@ import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; +import ubic.basecode.ontology.model.OntologyProperty; import ubic.basecode.ontology.model.OntologyTerm; import ubic.gemma.core.job.executor.webapp.TaskRunningService; import ubic.gemma.core.ontology.OntologyService; import ubic.gemma.core.search.SearchException; import ubic.gemma.model.common.description.Characteristic; +import ubic.gemma.model.common.description.CharacteristicValueObject; import ubic.gemma.model.expression.biomaterial.BioMaterial; import ubic.gemma.model.expression.experiment.ExpressionExperiment; import ubic.gemma.model.genome.Taxon; -import ubic.gemma.model.genome.gene.phenotype.valueObject.CharacteristicValueObject; import ubic.gemma.persistence.service.common.description.CharacteristicService; import ubic.gemma.persistence.service.expression.biomaterial.BioMaterialService; import ubic.gemma.persistence.service.expression.experiment.ExpressionExperimentService; @@ -78,11 +79,15 @@ public Collection getCategoryTerms() { return ontologyService.getCategoryTerms(); } + public Collection getRelationTerms() { + return ontologyService.getRelationTerms(); + } + public void createBiomaterialTag( Characteristic vc, Long id ) { BioMaterial bm = bioMaterialService.loadOrFail( id, EntityNotFoundException::new, "No such BioMaterial with id=" + id ); bm = bioMaterialService.thaw( bm ); - ontologyService.saveBioMaterialStatement( vc, bm ); + bioMaterialService.addCharacteristic( bm, vc ); } /** @@ -94,8 +99,13 @@ public void createBiomaterialTag( Characteristic vc, Long id ) { public void createExperimentTag( Characteristic vc, Long id ) { ExpressionExperiment ee = expressionExperimentService.loadAndThawLiteOrFail( id, EntityNotFoundException::new, "No such experiment with id=" + id ); - ontologyService.addExpressionExperimentStatement( vc, ee ); - expressionExperimentService.update( ee ); + if ( vc == null ) { + throw new IllegalArgumentException( "Null characteristic" ); + } + if ( ontologyService.isObsolete( vc.getValueUri() ) ) { + throw new IllegalArgumentException( vc + " is an obsolete term! Not saving." ); + } + expressionExperimentService.addCharacteristic( ee, vc ); } /** @@ -141,13 +151,13 @@ public void reinitializeOntologyIndices() { log.warn( "Attempt to run ontology re-indexing as non-admin." ); return; } - ontologyService.reinitializeAllOntologies(); + ontologyService.reinitializeAndReindexAllOntologies(); } public void removeBiomaterialTag( Characteristic vc, Long id ) { BioMaterial bm = bioMaterialService.loadOrFail( id, EntityNotFoundException::new, "No such BioMaterial with id=" + id ); bm = bioMaterialService.thaw( bm ); - ontologyService.removeBioMaterialStatement( vc.getId(), bm ); + bioMaterialService.removeCharacteristic( bm, vc ); } public void removeExperimentTag( Collection characterIds, Long eeId ) { diff --git a/gemma-web/src/main/java/ubic/gemma/web/controller/expression/experiment/DEDVController.java b/gemma-web/src/main/java/ubic/gemma/web/controller/expression/experiment/DEDVController.java index f52afba6ec..1fa4d27219 100644 --- a/gemma-web/src/main/java/ubic/gemma/web/controller/expression/experiment/DEDVController.java +++ b/gemma-web/src/main/java/ubic/gemma/web/controller/expression/experiment/DEDVController.java @@ -36,7 +36,6 @@ import ubic.gemma.core.visualization.ExperimentalDesignVisualizationService; import ubic.gemma.model.analysis.expression.diff.DifferentialExpressionValueObject; import ubic.gemma.model.analysis.expression.pca.ProbeLoading; -import ubic.gemma.model.common.description.Characteristic; import ubic.gemma.model.expression.bioAssay.BioAssay; import ubic.gemma.model.expression.bioAssay.BioAssayValueObject; import ubic.gemma.model.expression.bioAssayData.DoubleVectorValueObject; @@ -1374,27 +1373,6 @@ private String composeFacvalStr( Map fvs, Double valueOrId ) FactorValue facVal = fvs.get( id ); assert facVal != null; fvs.put( facVal.getId(), facVal ); - return getFactorValueDisplayString( facVal ); - } - - private String getFactorValueDisplayString( FactorValue facVal ) { - - StringBuilder facValsStrBuff = new StringBuilder(); - - if ( facVal.getCharacteristics() == null || facVal.getCharacteristics().isEmpty() ) { - facValsStrBuff.append( facVal.getValue() ).append( ", " ); - } - for ( Characteristic characteristic : facVal.getCharacteristics() ) { - facValsStrBuff.append( characteristic.getValue() ).append( ", " ); - } - if ( facValsStrBuff.length() > 0 ) { - facValsStrBuff.delete( facValsStrBuff.length() - 2, facValsStrBuff.length() ); - } - if ( facValsStrBuff.length() == 0 ) { - facValsStrBuff.append( "FactorValue id:" ).append( Math.round( facVal.getId() ) ) - .append( " was not null but no value was found." ); - } - - return facValsStrBuff.toString(); + return FactorValueUtils.getSummaryString( facVal ); } } \ No newline at end of file diff --git a/gemma-web/src/main/java/ubic/gemma/web/controller/expression/experiment/DesignMatrixRowValueObject.java b/gemma-web/src/main/java/ubic/gemma/web/controller/expression/experiment/DesignMatrixRowValueObject.java index 590280a130..ac9ab0d69b 100644 --- a/gemma-web/src/main/java/ubic/gemma/web/controller/expression/experiment/DesignMatrixRowValueObject.java +++ b/gemma-web/src/main/java/ubic/gemma/web/controller/expression/experiment/DesignMatrixRowValueObject.java @@ -1,8 +1,8 @@ /* * The Gemma project - * + * * Copyright (c) 2008 University of British Columbia - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -21,12 +21,12 @@ import org.apache.commons.lang3.StringUtils; import ubic.basecode.dataStructure.CountingMap; import ubic.gemma.core.analysis.preprocess.batcheffects.BatchInfoPopulationServiceImpl; -import ubic.gemma.model.common.description.Characteristic; import ubic.gemma.model.expression.bioAssay.BioAssay; import ubic.gemma.model.expression.biomaterial.BioMaterial; import ubic.gemma.model.expression.experiment.ExperimentalFactor; import ubic.gemma.model.expression.experiment.ExpressionExperiment; import ubic.gemma.model.expression.experiment.FactorValue; +import ubic.gemma.model.expression.experiment.FactorValueUtils; import ubic.gemma.persistence.util.FactorValueVector; import java.io.Serializable; @@ -130,39 +130,19 @@ private String getFactorValueString( Collection factorValues ) { StringBuilder buf = new StringBuilder(); for ( Iterator i = factorValues.iterator(); i.hasNext(); ) { FactorValue fv = i.next(); - buf.append( getFactorValueString( fv ) ); + if ( fv != null ) { + if ( fv.getMeasurement() != null ) { + buf.append( fv.getMeasurement().getValue() ); + } else { + buf.append( FactorValueUtils.getSummaryString( fv ) ); + } + } if ( i.hasNext() ) buf.append( ", " ); } return buf.toString(); } - private String getFactorValueString( FactorValue factorValue ) { - - // missing data. - if ( factorValue == null ) - return ""; - - StringBuilder buf = new StringBuilder(); - if ( !factorValue.getCharacteristics().isEmpty() ) { - for ( Iterator i = factorValue.getCharacteristics().iterator(); i.hasNext(); ) { - Characteristic characteristic = i.next(); - - /* - * Note we don't use toString here because it includes the category, uri, etc. - */ - buf.append( characteristic.getValue() ); - if ( i.hasNext() ) - buf.append( " " ); - } - } else if ( StringUtils.isNotBlank( factorValue.getValue() ) ) { - buf.append( factorValue.getValue() ); - } else if ( factorValue.getMeasurement() != null ) { - buf.append( factorValue.getMeasurement().getValue() ); - } - return buf.toString(); - } - public static final class Factory { /** diff --git a/gemma-web/src/main/java/ubic/gemma/web/controller/expression/experiment/ExperimentalDesignController.java b/gemma-web/src/main/java/ubic/gemma/web/controller/expression/experiment/ExperimentalDesignController.java index cc8f1fa5d5..24f6d95914 100644 --- a/gemma-web/src/main/java/ubic/gemma/web/controller/expression/experiment/ExperimentalDesignController.java +++ b/gemma-web/src/main/java/ubic/gemma/web/controller/expression/experiment/ExperimentalDesignController.java @@ -1,140 +1,820 @@ +/* + * The Gemma project + * + * Copyright (c) 2006 University of British Columbia + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ package ubic.gemma.web.controller.expression.experiment; +import gemma.gsec.SecurityService; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.servlet.ModelAndView; +import ubic.gemma.core.analysis.expression.diff.LinearModelAnalyzer; +import ubic.gemma.core.analysis.report.ExpressionExperimentReportService; +import ubic.gemma.core.expression.experiment.FactorValueDeletion; +import ubic.gemma.core.loader.expression.simple.ExperimentalDesignImporter; +import ubic.gemma.model.association.GOEvidenceCode; +import ubic.gemma.model.common.auditAndSecurity.eventType.ExperimentalDesignUpdatedEvent; import ubic.gemma.model.common.description.Characteristic; +import ubic.gemma.model.common.description.CharacteristicValueObject; +import ubic.gemma.model.expression.bioAssay.BioAssay; +import ubic.gemma.model.expression.biomaterial.BioMaterial; import ubic.gemma.model.expression.biomaterial.BioMaterialValueObject; -import ubic.gemma.model.expression.experiment.ExperimentalFactorValueObject; -import ubic.gemma.model.expression.experiment.FactorValueValueObject; +import ubic.gemma.model.expression.experiment.*; +import ubic.gemma.persistence.service.common.auditAndSecurity.AuditTrailService; +import ubic.gemma.persistence.service.expression.biomaterial.BioMaterialService; +import ubic.gemma.persistence.service.expression.experiment.ExperimentalDesignService; +import ubic.gemma.persistence.service.expression.experiment.ExperimentalFactorService; +import ubic.gemma.persistence.service.expression.experiment.ExpressionExperimentService; +import ubic.gemma.persistence.service.expression.experiment.FactorValueService; +import ubic.gemma.persistence.util.EntityUtils; +import ubic.gemma.web.controller.BaseController; import ubic.gemma.web.remote.EntityDelegator; +import ubic.gemma.web.util.AnchorTagUtil; +import ubic.gemma.web.util.EntityNotFoundException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.util.Collection; +import javax.servlet.ServletContext; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.*; /** - * Note: do not use parametrized collections as parameters for ajax methods in this class! Type information is lost - * during proxy creation so DWR can't figure out what type of collection the method should take. See bug 2756. Use - * arrays instead. + * Main entry point to editing and viewing experimental designs. Note: do not use parametrized collections as + * parameters for ajax methods in this class! Type information is lost during proxy creation so DWR can't figure out + * what type of collection the method should take. See bug 2756. Use arrays instead. + * + * @author keshav */ -@SuppressWarnings("unused") // Used in front end +@Controller @RequestMapping("/experimentalDesign") -public interface ExperimentalDesignController { +public class ExperimentalDesignController extends BaseController { - void createDesignFromFile( Long eeid, String filePath ); + @Autowired + private BioMaterialService bioMaterialService; + @Autowired + private ExperimentalDesignImporter experimentalDesignImporter; + @Autowired + private ExperimentalDesignService experimentalDesignService; + @Autowired + private ExperimentalFactorService experimentalFactorService; + @Autowired + private ExpressionExperimentService expressionExperimentService; + @Autowired + private ExpressionExperimentReportService experimentReportService; + @Autowired + private FactorValueDeletion factorValueDeletion; + @Autowired + private FactorValueService factorValueService; + @Autowired + private SecurityService securityService; + @Autowired + private AuditTrailService auditTrailService; + @Autowired + private ServletContext servletContext; - /** - * Creates a new ExperimentalFactor and adds it to the ExperimentalDesign specified by the EntityDelegator. - * - * @param e an EntityDelegator representing an ExperimentalDesign - * @param efvo an ExperimentalFactorValueObject representing the new ExperimentalFactor - */ - void createExperimentalFactor( EntityDelegator e, ExperimentalFactorValueObject efvo ); + public void createDesignFromFile( Long eeid, String filePath ) { + ExpressionExperiment ee = expressionExperimentService.loadAndThawOrFail( eeid, + EntityNotFoundException::new, "Could not access experiment with id=" + eeid ); - /** - * Creates a new FactorValue and adds it to the ExperimentalFactor specified by the EntityDelegator. The new - * FactorValue may have some initial Characteristics created to match any previously existing FactorValues for the - * same ExperimentalFactor. Note that this applies only to 'categorical' variables. For continuous variables, you - * merely set the value. - * - * @param e an EntityDelegator representing an ExperimentalFactor - */ - void createFactorValue( EntityDelegator e ); + if ( !ee.getExperimentalDesign().getExperimentalFactors().isEmpty() ) { + throw new IllegalArgumentException( "Cannot import an experimental design for an experiment that already has design data populated." ); + } - /** - * Creates a new Characteristic and adds it to the FactorValue specified by the EntityDelegator. - * - * @param e an EntityDelegator representing a FactorValue - */ - void createFactorValueCharacteristic( EntityDelegator e, Characteristic c ); + File f = new File( filePath ); - /** - * Deletes the specified ExperimentalFactors and removes them from the ExperimentalDesign specified by the - * EntityDelegator. - * - * @param e an EntityDelegator representing an ExperimentalDesign - * @param efIds a collection of ExperimentalFactor ids - */ - void deleteExperimentalFactors( EntityDelegator e, Long[] efIds ); + if ( !f.canRead() ) { + throw new IllegalArgumentException( "Cannot read from file:" + f ); + } - /** - * Deletes the specified Characteristics from their parent FactorValues. - * - * @param fvvos a collection of FactorValueValueObjects containing the Characteristics to remove - */ - void deleteFactorValueCharacteristics( FactorValueValueObject[] fvvos ); + try ( InputStream is = new FileInputStream( f ) ) { + // removed dry run code, validation and object creation is done before any commits to DB + // So if validation fails no rollback needed. However, this call is wrapped in a transaction + // as a fail safe. + experimentalDesignImporter.importDesign( ee, is ); + this.experimentReportService.evictFromCache( ee.getId() ); - /** - * Deletes the specified FactorValues and removes them from the ExperimentalFactor specified by the EntityDelegator. - * - * @param e an EntityDelegator representing an ExperimentalFactor - * @param fvIds a collection of FactorValue ids - */ - void deleteFactorValues( EntityDelegator e, Long[] fvIds ); + } catch ( IOException e ) { + throw new RuntimeException( "Failed to import the design: " + e.getMessage() ); + } - /** - * Returns BioMaterialValueObjects for each BioMaterial in the ExpressionExperiment specified by the - * EntityDelegator. - * - * @param e an EntityDelegator representing an ExpressionExperiment - * @return a collection of BioMaterialValueObjects - */ - Collection getBioMaterials( EntityDelegator e ); + } - /** - * Returns ExperimentalFactorValueObjects for each ExperimentalFactor in the ExperimentalDesign or - * ExpressionExperiment specified by the EntityDelegator. - * - * @param e an EntityDelegator representing an ExperimentalDesign OR an ExpressionExperiment - * @return a collection of ExperimentalFactorValueObjects - */ - Collection getExperimentalFactors( EntityDelegator e ); + public void createExperimentalFactor( EntityDelegator e, ExperimentalFactorValueObject efvo ) { + if ( e == null || e.getId() == null ) + return; + ExperimentalDesign ed = experimentalDesignService.loadWithExperimentalFactors( e.getId() ); - /** - * Returns FactorValueValueObjects for each FactorValue in the ExperimentalFactor specified by the EntityDelegator. - * There will be one row per FactorValue - * - * @param e an EntityDelegator representing an ExperimentalFactor - * @return a collection of FactorValueValueObjects - */ - Collection getFactorValues( EntityDelegator e ); + ExperimentalFactor ef = ExperimentalFactor.Factory.newInstance(); + ef.setType( FactorType.valueOf( efvo.getType() ) ); + ef.setExperimentalDesign( ed ); + ef.setName( efvo.getName() ); + ef.setDescription( efvo.getDescription() ); + ef.setCategory( this.createCategoryCharacteristic( efvo.getCategory(), efvo.getCategoryUri() ) ); - /** - * Returns FactorValueValueObjects for each Characteristic belonging to a FactorValue in the ExperimentalFactor - * specified by the EntityDelegator. There will be one row per Characteristic. - * - * @param e an EntityDelegator representing an ExperimentalFactor - * @return a collection of FactorValueValueObjects - */ - Collection getFactorValuesWithCharacteristics( EntityDelegator e ); + /* + * Note: this call should not be needed because of cascade behaviour. + */ + // experimentalFactorService.create( ef ); + if ( ed.getExperimentalFactors() == null ) + ed.setExperimentalFactors( new HashSet() ); + ed.getExperimentalFactors().add( ef ); - /** - * @param request with either 'eeid' (expression experiment id) or 'edid' (experimental design id) - * @param response response - * @return ModelAndView - */ - @RequestMapping("/showExperimentalDesign.html") - ModelAndView show( HttpServletRequest request, HttpServletResponse response ); + experimentalDesignService.update( ed ); - /** - * Updates the specified BioMaterials's factor values. This completely removes any pre-existing factor values. - * - * @param bmvos a collection of BioMaterialValueObjects containing the updated values - */ - void updateBioMaterials( BioMaterialValueObject[] bmvos ); + ExpressionExperiment ee = experimentalDesignService.getExpressionExperiment( ed ); - /** - * Updates the specified ExperimentalFactors. - * - * @param efvos a collection of ExperimentalFactorValueObjects containing the updated values - */ - void updateExperimentalFactors( ExperimentalFactorValueObject[] efvos ); + // this.auditTrailService.addUpdateEvent( ee, ExperimentalDesignEvent.class, + // "ExperimentalFactor added: " + efvo.getName(), efvo.toString() ); + this.experimentReportService.evictFromCache( ee.getId() ); + + } + + public void createFactorValue( EntityDelegator e ) { + if ( e == null || e.getId() == null ) + return; + ExperimentalFactor ef = experimentalFactorService.load( e.getId() ); + + if ( ef == null ) { + throw new EntityNotFoundException( + "Experimental factor with ID=" + e.getId() + " could not be accessed for editing" ); + } + + Set chars = new HashSet<>(); + for ( FactorValue fv : ef.getFactorValues() ) { + //noinspection LoopStatementThatDoesntLoop // No, but its an effective way of doing this + for ( Statement c : fv.getCharacteristics() ) { + chars.add( this.createTemplateStatement( c ) ); + break; + } + } + if ( chars.isEmpty() ) { + if ( ef.getCategory() == null ) { + throw new IllegalArgumentException( "You cannot create new factor values on a experimental factor that is not defined by a formal Category" ); + } + chars.add( this.createTemplateStatement( ef.getCategory() ) ); + } + + FactorValue fv = FactorValue.Factory.newInstance(); + fv.setExperimentalFactor( ef ); + fv.setCharacteristics( chars ); + + ExpressionExperiment ee = experimentalDesignService.getExpressionExperiment( ef.getExperimentalDesign() ); + + // this is just a placeholder factor value; use has to edit it. + expressionExperimentService.addFactorValue( ee, fv ); + } + + public void createFactorValueCharacteristic( EntityDelegator e, CharacteristicValueObject cvo ) { + if ( e == null || e.getId() == null ) + return; + FactorValue fv = factorValueService.load( e.getId() ); + + if ( fv == null ) { + throw new EntityNotFoundException( "No such factor value with id=" + e.getId() ); + } + + ExpressionExperiment ee = expressionExperimentService.findByFactorValue( fv ); + + if ( ee == null ) { + throw new EntityNotFoundException( "No such experiment with " + fv ); + } + + Statement c = factorValueService.createStatement( fv, statementFromVo( cvo ) ); + log.debug( String.format( "Created %s", c ) ); + + // this.auditTrailService.addUpdateEvent( ee, ExperimentalDesignEvent.class, + // "FactorValue characteristic added to: " + fv, c.toString() ); + this.experimentReportService.evictFromCache( ee.getId() ); + } + + private Statement statementFromVo( CharacteristicValueObject vo ) { + if ( StringUtils.isBlank( vo.getCategory() ) ) { + throw new IllegalArgumentException( "The category cannot be blank for " + vo ); + } + if ( StringUtils.isBlank( vo.getValue() ) ) { + throw new IllegalArgumentException( "The value cannot be blank for " + vo ); + } + Statement c = new Statement(); + c.setCategory( vo.getCategory() ); + c.setCategoryUri( StringUtils.stripToNull( vo.getCategoryUri() ) ); + c.setSubject( vo.getValue() ); + c.setSubjectUri( StringUtils.stripToNull( vo.getValueUri() ) ); + return c; + } + + public void deleteExperimentalFactors( EntityDelegator e, Long[] efIds ) { + + if ( e == null || e.getId() == null ) + return; + + Collection efCol = new LinkedList<>(); + Collections.addAll( efCol, efIds ); + + Collection toDelete = experimentalFactorService.load( efCol ); + + this.delete( toDelete ); + + } + + public void deleteFactorValueCharacteristics( FactorValueValueObject[] fvvos ) { + FactorValue[] fvs = new FactorValue[fvvos.length]; + Statement[] statements = new Statement[fvvos.length]; + for ( int i = 0; i < fvvos.length; i++ ) { + FactorValueValueObject fvvo = fvvos[i]; + if ( fvvo.getId() == null ) { + throw new IllegalArgumentException( "A factor value ID must be supplied." ); + } + if ( fvvo.getCharId() == null ) { + throw new IllegalArgumentException( "A characteristic ID must be supplied." ); + } + FactorValue fv = factorValueService.loadOrFail( fvvo.getId() ); + Statement c = fv.getCharacteristics().stream() + .filter( s -> s.getId().equals( fvvo.getCharId() ) ) + .findFirst() + .orElseThrow( () -> new EntityNotFoundException( String.format( "No statement with ID %d in FactorVlaue with ID %d", fvvo.getCharId(), fvvo.getId() ) ) ); + fvs[i] = fv; + statements[i] = c; + } + for ( int i = 0; i < fvvos.length; i++ ) { + factorValueService.removeStatement( fvs[i], statements[i] ); + } + } + + public void deleteFactorValues( EntityDelegator e, Long[] fvIds ) { + + if ( e == null || e.getId() == null ) + return; + Collection fvCol = new LinkedList<>(); + Collections.addAll( fvCol, fvIds ); + + for ( Long fvId : fvCol ) { + ExpressionExperiment ee = expressionExperimentService.findByFactorValue( fvId ); + this.experimentReportService.evictFromCache( ee.getId() ); + } + + factorValueDeletion.deleteFactorValues( fvCol ); + + } + + public Collection getBioMaterials( EntityDelegator e ) { + if ( e == null || e.getId() == null ) + return null; + ExpressionExperiment ee = expressionExperimentService.loadOrFail( e.getId() ); + ee = expressionExperimentService.thawLite( ee ); + Collection result = new HashSet<>(); + for ( BioAssay assay : ee.getBioAssays() ) { + BioMaterial sample = assay.getSampleUsed(); + BioMaterialValueObject bmvo = new BioMaterialValueObject( sample, assay ); + result.add( bmvo ); + + } + + filterCharacteristics( result ); + + return result; + } /** - * Updates the specified Characteristics. + * Filter the characteristicValues to those that we want to display in columns in the biomaterialvalue table. * - * @param fvvos a collection of FactorValueValueObjects containing the updated values */ - void updateFactorValueCharacteristics( FactorValueValueObject[] fvvos ); + private void filterCharacteristics( Collection result ) { + + int c = result.size(); + + // build map of categories to bmos. No category: can't use. + Map> map = new HashMap<>(); + for ( BioMaterialValueObject bmo : result ) { + for ( CharacteristicValueObject ch : bmo.getCharacteristics() ) { + + String category = ch.getCategory(); + String value = ch.getValue(); + if ( StringUtils.isBlank( category ) ) { + /* + Experimental: split on ":" or "=", use first part as the category. This should no longer be necessary + */ + if ( StringUtils.isNotBlank( value ) && value.matches( ".+[:=].+" ) ) { // note: GEO only allows ":" now but we have "=" in the db for older entries. + String[] split = value.split( "[:=]", 2 ); + category = StringUtils.strip( split[0] ); + } else { + continue; + } + } + + if ( !map.containsKey( category ) ) { + map.put( category, new HashSet() ); + } + map.get( category ).add( bmo ); + } + } + + /* + find ones that don't meet criteria for display e.g are constant across all samples + */ + Collection toremove = new HashSet<>(); + for ( String category : map.keySet() ) { + //log.info( ">>>>>>>>>> " + category + ", " + map.get( category ).size() + " items" ); + if ( map.get( category ).size() != result.size() ) { + // toremove.add( category ); // this isn't really worth it and hides useful information. + continue; + } + + // TODO add more exclusions; see also ExpresionExperimentDao.getAnnotationsByBioMaterials + if ( category.equals( "LabelCompound" ) || category.equals( "MaterialType" ) || category.equals( "molecular entity" ) ) { + toremove.add( category ); + continue; + } + + Collection vals = new HashSet<>(); + boolean keeper = false; + bms: + for ( BioMaterialValueObject bm : map.get( category ) ) { + // log.info( "inspecting " + bm ); + // Find the characteristic that had this category + for ( CharacteristicValueObject ch : bm.getCharacteristics() ) { + String mappedCategory = ch.getCategory(); + String mappedValue = ch.getValue(); + + if ( StringUtils.isBlank( mappedCategory ) ) { // this should no longer be needed + // redo split (will refactor later) + if ( StringUtils.isNotBlank( mappedValue ) && mappedValue.matches( ".+[:=].+" ) ) { + String[] split = mappedValue.split( "[:=]", 2 ); + mappedCategory = StringUtils.strip( split[0] ); + mappedValue = StringUtils.strip( split[1] ); // to show the trimmed up value. + } else { + continue bms; + } + } + + if ( mappedCategory.equals( category ) ) { + if ( !vals.contains( mappedValue ) ) { + if ( log.isDebugEnabled() ) + log.debug( category + " -> " + mappedValue ); + vals.add( mappedValue ); + } + + // populate this into the biomaterial + // log.info( category + " -> " + mappedValue ); + bm.getCharacteristicValues().put( mappedCategory, mappedValue ); + } + } + + // if ( vals.size() > 1 ) { + // if ( log.isDebugEnabled() ) + // log.debug( category + " -- Keeper with " + vals.size() + " values" ); + // + // keeper = true; + // } + } + + if ( vals.size() < 2 ) { // constant + toremove.add( category ); + } + } + + // finally, clean up the bmos. + for ( BioMaterialValueObject bmo : result ) { + for ( String lose : toremove ) { + bmo.getCharacteristicValues().remove( lose ); + } + } + + } + + public Collection getExperimentalFactors( EntityDelegator e ) { + if ( e == null || e.getId() == null ) + return null; + + Collection result = new HashSet<>(); + Long designId; + if ( e.holds( ExpressionExperiment.class ) ) { + ExpressionExperiment ee = this.expressionExperimentService.loadOrFail( e.getId() ); + designId = ee.getExperimentalDesign().getId(); + } else if ( e.holds( ExperimentalDesign.class ) ) { + designId = e.getId(); + } else { + throw new RuntimeException( "Don't know how to process a " + e.getClassDelegatingFor() ); + } + // ugly fix for bug 3746 + ExpressionExperiment ee = experimentalDesignService + .getExpressionExperiment( this.experimentalDesignService.loadOrFail( designId ) ); + ee = expressionExperimentService.thawLite( ee ); + ExperimentalDesign ed = ee.getExperimentalDesign(); + + for ( ExperimentalFactor factor : ed.getExperimentalFactors() ) { + result.add( new ExperimentalFactorValueObject( factor ) ); + } + + return result; + } + + public Collection getFactorValues( EntityDelegator e ) { + // FIXME I'm not sure why this keeps getting called with empty fields. + if ( e == null || e.getId() == null ) + return new HashSet<>(); + ExperimentalFactor ef = this.experimentalFactorService.load( e.getId() ); + if ( ef == null ) + return new HashSet<>(); + + Collection result = new HashSet<>(); + for ( FactorValue value : ef.getFactorValues() ) { + result.add( new FactorValueValueObject( value ) ); + } + return result; + } + + public Collection getFactorValuesWithCharacteristics( EntityDelegator e ) { + Collection result = new HashSet<>(); + if ( e == null || e.getId() == null ) { + return result; + } + ExperimentalFactor ef = this.experimentalFactorService.load( e.getId() ); + if ( ef == null ) { + return result; + } + + for ( FactorValue value : ef.getFactorValues() ) { + if ( !value.getCharacteristics().isEmpty() ) { + for ( Statement c : value.getCharacteristics() ) { + result.add( new FactorValueValueObject( value, c ) ); + } + } else { + result.add( new FactorValueValueObject( value ) ); + } + } + return result; + } + + @RequestMapping(value = "/showExperimentalDesign.html", params = { "edid" }, method = RequestMethod.GET) + public ModelAndView showById( @RequestParam("edid") Long edId ) { + ExperimentalDesign ed = experimentalDesignService.loadOrFail( edId, EntityNotFoundException::new ); + return show( experimentalDesignService.getExpressionExperiment( ed ) ); + } + + @RequestMapping(value = "/showExperimentalDesign.html", params = { "eeid" }, method = RequestMethod.GET) + public ModelAndView showByExperimentId( @RequestParam("eeid") Long eeId ) { + return show( expressionExperimentService.loadOrFail( eeId, EntityNotFoundException::new ) ); + } + + @RequestMapping(value = "/showExperimentalDesign.html", params = { "shortName" }, method = RequestMethod.GET) + public ModelAndView showByExperimentShortName( @RequestParam("shortName") String shortName ) { + ExpressionExperiment ee = expressionExperimentService.findByShortName( shortName ); + if ( ee == null ) { + throw new EntityNotFoundException( String.format( "No ExpressionExperiment with short name %s.", shortName ) ); + } + return show( ee ); + } + + private ModelAndView show( ExpressionExperiment ee ) { + ee = expressionExperimentService.thawLite( ee ); + // strip white spaces + String desc = ee.getDescription(); + ee.setDescription( StringUtils.strip( desc ) ); + RequestContextHolder.getRequestAttributes().setAttribute( "id", ee.getExperimentalDesign().getId(), RequestAttributes.SCOPE_REQUEST ); + return new ModelAndView( "experimentalDesign.detail" ) + .addObject( "taxonId", expressionExperimentService.getTaxon( ee ).getId() ) + .addObject( "hasPopulatedDesign", !ee.getExperimentalDesign().getExperimentalFactors().isEmpty() ) + .addObject( "experimentalDesign", ee.getExperimentalDesign() ) + .addObject( "expressionExperiment", ee ) + .addObject( "currentUserCanEdit", securityService.isEditable( ee ) ? "true" : "" ) + .addAllObjects( getNeedsAttentionDetails( ee ) ) + .addObject( "expressionExperimentUrl", AnchorTagUtil.getExpressionExperimentUrl( ee, servletContext ) ); + } + + private Map getNeedsAttentionDetails( ExpressionExperiment ee ) { + Map result = new HashMap<>(); + boolean needsAttention = ee.getExperimentalDesign().getExperimentalFactors().stream() + .flatMap( ef -> ef.getFactorValues().stream() ) + .anyMatch( FactorValue::getNeedsAttention ); + result.put( "needsAttention", needsAttention ); + try { + ExperimentalDesign randomEd = experimentalDesignService.getRandomExperimentalDesignThatNeedsAttention( ee.getExperimentalDesign() ); + result.put( "randomExperimentalDesignThatNeedsAttention", randomEd ); + if ( randomEd != null ) { + ExpressionExperiment randomEe = expressionExperimentService.findByDesign( randomEd ); + if ( randomEe != null ) { + result.put( "randomExperimentalDesignThatNeedsAttentionShortName", randomEe.getShortName() ); + } else { + log.warn( String.format( "%s does not belong to any experiment.", randomEd ) ); + result.put( "randomExperimentalDesignThatNeedsAttentionShortName", "" ); + } + } + } catch ( AccessDeniedException ignored ) { + } + return result; + } + + public void updateBioMaterials( BioMaterialValueObject[] bmvos ) { + + if ( bmvos == null || bmvos.length == 0 ) + return; + + Collection biomaterials = bioMaterialService.updateBioMaterials( Arrays.asList( bmvos ) ); + + if ( biomaterials.isEmpty() ) + return; + + BioMaterial bm = biomaterials.iterator().next(); + ExpressionExperiment ee = expressionExperimentService.findByBioMaterial( bm ); + if ( ee == null ) + throw new IllegalStateException( "No Experiment for biomaterial: " + bm ); + + ee = expressionExperimentService.thawLite( ee ); + for ( ExperimentalFactor ef : ee.getExperimentalDesign().getExperimentalFactors() ) { + if ( ef.getType().equals( FactorType.CONTINUOUS ) ) { + + /* + * Check for unused factorValues + */ + Collection usedFactorValues = new HashSet<>(); + LinearModelAnalyzer.populateFactorValuesFromBASet( ee, ef, usedFactorValues ); + + Collection toDelete = new HashSet<>(); + for ( FactorValue fv : ef.getFactorValues() ) { + if ( !usedFactorValues.contains( fv ) ) { + /* + * remove it. + */ + toDelete.add( fv ); + + } + } + + if ( !toDelete.isEmpty() ) { + log.info( "Deleting " + toDelete.size() + " unused factorvalues for " + ef ); + factorValueDeletion.deleteFactorValues( EntityUtils.getIds( toDelete ) ); + } + + } + } + StringBuilder details = new StringBuilder( "Updated bio materials:\n" ); + for ( BioMaterialValueObject vo : bmvos ) { + if ( vo == null ) { + continue; + } + BioMaterial ba = bioMaterialService.load( vo.getId() ); + if ( ba != null ) { + details.append( "id: " ).append( ba.getId() ).append( " - " ).append( ba.getName() ).append( "\n" ); + } + } + this.auditTrailService.addUpdateEvent( ee, ExperimentalDesignUpdatedEvent.class, + "BioMaterials updated (" + bmvos.length + " items)", details.toString() ); + this.experimentReportService.evictFromCache( ee.getId() ); + } + + public void updateExperimentalFactors( ExperimentalFactorValueObject[] efvos ) { + + if ( efvos == null || efvos.length == 0 ) + return; + + for ( ExperimentalFactorValueObject efvo : efvos ) { + ExperimentalFactor ef = experimentalFactorService.loadOrFail( efvo.getId() ); + ef.setName( efvo.getName() ); + ef.setDescription( efvo.getDescription() ); + + FactorType newType = efvo.getType() != null ? FactorType.valueOf( efvo.getType() ) : null; + if ( newType == null || !newType.equals( ef.getType() ) ) { + // we only allow this if there are no factors + if ( ef.getFactorValues().isEmpty() ) { + ef.setType( newType ); + } else { + throw new IllegalArgumentException( "You cannot change the 'type' of a factor once it has factor values. Delete the factor values first." ); + } + } + + Characteristic vc = ef.getCategory(); + + // can be null if this was imported from GEO etc. + if ( vc == null ) { + vc = Characteristic.Factory.newInstance(); + } + + // String originalCategoryUri = vc.getCategoryUri(); + + vc.setCategory( efvo.getCategory() ); + vc.setCategoryUri( StringUtils.stripToNull( efvo.getCategoryUri() ) ); + vc.setValue( efvo.getCategory() ); + vc.setValueUri( StringUtils.stripToNull( efvo.getCategoryUri() ) ); + + ef.setCategory( vc ); + + experimentalFactorService.update( ef ); + } + + ExperimentalFactor ef = experimentalFactorService.loadOrFail( efvos[0].getId() ); + ExpressionExperiment ee = expressionExperimentService.findByFactor( ef ); + if ( ee == null ) + throw new EntityNotFoundException( "No experiment for factor: " + ef ); + this.experimentReportService.evictFromCache( ee.getId() ); + } + + public void updateFactorValueCharacteristics( FactorValueValueObject[] fvvos ) { + + /* + * TODO: support Characteristic extensions (predicate-object) + */ + + if ( fvvos == null || fvvos.length == 0 ) + return; + + // validate the VOs + FactorValue[] fvs = new FactorValue[fvvos.length]; + Statement[] statements = new Statement[fvvos.length]; + for ( int i = 0; i < fvvos.length; i++ ) { + FactorValueValueObject fvvo = fvvos[i]; + if ( fvvo.getId() == null ) { + throw new IllegalArgumentException( "Factor value ID must be supplied" ); + } + if ( StringUtils.isBlank( fvvo.getCategory() ) ) { + throw new IllegalArgumentException( "A statement must have a category" ); + } + if ( StringUtils.isBlank( fvvo.getValue() ) ) { + throw new IllegalArgumentException( "A statement must have a subject." ); + } + if ( StringUtils.isBlank( fvvo.getPredicate() ) ^ StringUtils.isBlank( fvvo.getObject() ) ) { + throw new IllegalArgumentException( "Either provide both predicate and object or neither." ); + } + if ( StringUtils.isBlank( fvvo.getSecondPredicate() ) ^ StringUtils.isBlank( fvvo.getSecondObject() ) ) { + throw new IllegalArgumentException( "Either provide both second predicate and second object or neither." ); + } + FactorValue fv = null; + // reuse a previous FV, otherwise changes may be overwritten + for ( int j = 0; j < i; j++ ) { + if ( fvvo.getId().equals( fvs[j].getId() ) ) { + fv = fvs[j]; + } + } + if ( fv == null ) { + fv = this.factorValueService.loadOrFail( fvvo.getId(), EntityNotFoundException::new ); + } + Long charId = fvvo.getCharId(); // this is optional. Maybe we're actually adding a characteristic for the + Statement c; + if ( charId != null ) { + c = fv.getCharacteristics().stream() + .filter( s -> s.getId().equals( charId ) ) + .findFirst() + .orElseThrow( () -> new EntityNotFoundException( String.format( "No characteristic with ID %d in FactorValue with ID %d", charId, fvvo.getId() ) ) ); + } else { + c = Statement.Factory.newInstance(); + } + + // For related code see CharacteristicUpdateTaskImpl + + // preserve original data + if ( StringUtils.isBlank( c.getOriginalValue() ) ) { + c.setOriginalValue( c.getSubject() ); + } + + c.setCategory( fvvo.getCategory() ); + c.setCategoryUri( StringUtils.stripToNull( fvvo.getCategoryUri() ) ); + c.setSubject( fvvo.getValue() ); + c.setSubjectUri( StringUtils.stripToNull( fvvo.getValueUri() ) ); + c.setEvidenceCode( GOEvidenceCode.IC ); // characteristic has been manually updated + + if ( !StringUtils.isBlank( fvvo.getObject() ) ) { + c.setPredicate( fvvo.getPredicate() ); + c.setPredicateUri( fvvo.getPredicateUri() ); + c.setObject( fvvo.getObject() ); + c.setObjectUri( fvvo.getObjectUri() ); + } else { + c.setPredicate( null ); + c.setPredicateUri( null ); + c.setObject( null ); + c.setObjectUri( null ); + } + + if ( !StringUtils.isBlank( fvvo.getSecondObject() ) ) { + c.setSecondPredicate( fvvo.getSecondPredicate() ); + c.setSecondPredicateUri( fvvo.getSecondPredicateUri() ); + c.setSecondObject( fvvo.getSecondObject() ); + c.setSecondObjectUri( fvvo.getSecondObjectUri() ); + } else { + c.setSecondPredicate( null ); + c.setSecondPredicateUri( null ); + c.setSecondObject( null ); + c.setSecondObjectUri( null ); + } + + fvs[i] = fv; + statements[i] = c; + } + + // now save! + for ( int i = 0; i < fvs.length; i++ ) { + statements[i] = factorValueService.saveStatement( fvs[i], statements[i] ); + if ( fvs[i].getNeedsAttention() ) { + factorValueService.clearNeedsAttentionFlag( fvs[i], + "The dataset does not need attention and all of its factor values were fixed." ); + log.info( "Reverted needs attention flag for " + fvs[i] ); + } + } + + ExpressionExperiment ee = expressionExperimentService.findByFactorValue( fvs[0] ); + // this.auditTrailService.addUpdateEvent( ee, ExperimentalDesignEvent.class, + // "FactorValue characteristics updated", StringUtils.join( fvvos, "\n" ) ); + this.experimentReportService.evictFromCache( ee.getId() ); + } + + public void markFactorValuesAsNeedsAttention( Long[] fvvos, String note ) { + Set fvs = new HashSet<>( fvvos.length ); + int i = 0; + for ( Long fvo : fvvos ) { + FactorValue fv = factorValueService.loadOrFail( fvo, EntityNotFoundException::new, + String.format( "No FactorValue with ID %d", fvo ) ); + if ( fv.getNeedsAttention() ) { + if ( fvvos.length == 1 ) { + throw new IllegalArgumentException( String.format( "%s is already marked as needs attention.", fv ) ); + } else { + continue; // skip + } + } + fvs.add( fv ); + } + for ( FactorValue fv : fvs ) { + factorValueService.markAsNeedsAttention( fv, note ); + } + log.info( String.format( "Marked %d factor values as needs attention: %s", fvs.size(), note ) ); + } + + public void clearFactorValuesNeedsAttention( Long[] fvvos, String note ) { + Set fvs = new HashSet<>( fvvos.length ); + int i = 0; + for ( Long fvo : fvvos ) { + FactorValue fv = factorValueService.loadOrFail( fvo, EntityNotFoundException::new, + String.format( "No FactorValue with ID %d", fvo ) ); + if ( !fv.getNeedsAttention() ) { + if ( fvvos.length == 1 ) { + throw new IllegalArgumentException( String.format( "%s does not need attention.", fv ) ); + } else { + continue; // skip + } + } + fvs.add( fv ); + } + for ( FactorValue fv : fvs ) { + factorValueService.clearNeedsAttentionFlag( fv, note ); + } + log.info( String.format( "Cleared %d factor values' needs attention flags: %s", fvs.size(), note ) ); + } + + private Characteristic createCategoryCharacteristic( String category, String categoryUri ) { + Characteristic c; + if ( categoryUri != null ) { + Characteristic vc = Characteristic.Factory.newInstance(); + vc.setCategoryUri( StringUtils.stripToNull( categoryUri ) ); + vc.setValueUri( StringUtils.stripToNull( categoryUri ) ); + c = vc; + } else { + c = Characteristic.Factory.newInstance(); + } + c.setCategory( category ); + c.setValue( category ); + c.setEvidenceCode( GOEvidenceCode.IC ); // manually added characteristic + return c; + } + + private Statement createTemplateStatement( Characteristic source ) { + Statement template = Statement.Factory.newInstance(); + template.setCategory( source.getCategory() ); + template.setCategoryUri( source.getCategoryUri() ); + template.setEvidenceCode( GOEvidenceCode.IEA ); // automatically added characteristic + return template; + } + + private void delete( Collection toDelete ) { + for ( ExperimentalFactor factorRemove : toDelete ) { + experimentalFactorService.remove( factorRemove ); + } + + for ( ExperimentalFactor ef : toDelete ) { + ExpressionExperiment ee = expressionExperimentService.findByFactor( ef ); + + if ( ee != null ) { + this.experimentReportService.evictFromCache( ee.getId() ); + } + } + } -} \ No newline at end of file +} diff --git a/gemma-web/src/main/java/ubic/gemma/web/controller/expression/experiment/ExperimentalDesignControllerImpl.java b/gemma-web/src/main/java/ubic/gemma/web/controller/expression/experiment/ExperimentalDesignControllerImpl.java deleted file mode 100644 index ef78f8954e..0000000000 --- a/gemma-web/src/main/java/ubic/gemma/web/controller/expression/experiment/ExperimentalDesignControllerImpl.java +++ /dev/null @@ -1,769 +0,0 @@ -/* - * The Gemma project - * - * Copyright (c) 2006 University of British Columbia - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ -package ubic.gemma.web.controller.expression.experiment; - -import gemma.gsec.SecurityService; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.math.NumberUtils; -import org.directwebremoting.extend.AccessDeniedException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.servlet.ModelAndView; -import ubic.gemma.core.analysis.expression.diff.LinearModelAnalyzer; -import ubic.gemma.core.analysis.report.ExpressionExperimentReportService; -import ubic.gemma.core.expression.experiment.FactorValueDeletion; -import ubic.gemma.core.loader.expression.simple.ExperimentalDesignImporter; -import ubic.gemma.model.association.GOEvidenceCode; -import ubic.gemma.model.common.auditAndSecurity.eventType.ExperimentalDesignUpdatedEvent; -import ubic.gemma.model.common.description.Characteristic; -import ubic.gemma.model.expression.bioAssay.BioAssay; -import ubic.gemma.model.expression.biomaterial.BioMaterial; -import ubic.gemma.model.expression.biomaterial.BioMaterialValueObject; -import ubic.gemma.model.expression.experiment.*; -import ubic.gemma.model.genome.gene.phenotype.valueObject.CharacteristicValueObject; -import ubic.gemma.persistence.service.common.auditAndSecurity.AuditTrailService; -import ubic.gemma.persistence.service.common.description.CharacteristicService; -import ubic.gemma.persistence.service.expression.biomaterial.BioMaterialService; -import ubic.gemma.persistence.service.expression.experiment.ExperimentalDesignService; -import ubic.gemma.persistence.service.expression.experiment.ExperimentalFactorService; -import ubic.gemma.persistence.service.expression.experiment.ExpressionExperimentService; -import ubic.gemma.persistence.service.expression.experiment.FactorValueService; -import ubic.gemma.persistence.util.EntityUtils; -import ubic.gemma.web.controller.BaseController; -import ubic.gemma.web.remote.EntityDelegator; -import ubic.gemma.web.util.AnchorTagUtil; -import ubic.gemma.web.util.EntityNotFoundException; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.*; - -/** - * Main entry point to editing and viewing experimental designs. Note: do not use parametrized collections as - * parameters for ajax methods in this class! Type information is lost during proxy creation so DWR can't figure out - * what type of collection the method should take. See bug 2756. Use arrays instead. - * - * @author keshav - */ -@Controller -@RequestMapping("/experimentalDesign") -public class ExperimentalDesignControllerImpl extends BaseController implements ExperimentalDesignController { - - @Autowired - private BioMaterialService bioMaterialService; - @Autowired - private CharacteristicService characteristicService; - @Autowired - private ExperimentalDesignImporter experimentalDesignImporter; - @Autowired - private ExperimentalDesignService experimentalDesignService; - @Autowired - private ExperimentalFactorService experimentalFactorService; - @Autowired - private ExpressionExperimentService expressionExperimentService; - @Autowired - private ExpressionExperimentReportService experimentReportService; - @Autowired - private FactorValueDeletion factorValueDeletion; - @Autowired - private FactorValueService factorValueService; - @Autowired - private SecurityService securityService; - @Autowired - private AuditTrailService auditTrailService; - - @Override - public void createDesignFromFile( Long eeid, String filePath ) { - ExpressionExperiment ee = expressionExperimentService.loadAndThawOrFail( eeid, - EntityNotFoundException::new, "Could not access experiment with id=" + eeid ); - - if ( !ee.getExperimentalDesign().getExperimentalFactors().isEmpty() ) { - throw new IllegalArgumentException( "Cannot import an experimental design for an experiment that already has design data populated." ); - } - - File f = new File( filePath ); - - if ( !f.canRead() ) { - throw new IllegalArgumentException( "Cannot read from file:" + f ); - } - - try ( InputStream is = new FileInputStream( f ) ) { - // removed dry run code, validation and object creation is done before any commits to DB - // So if validation fails no rollback needed. However, this call is wrapped in a transaction - // as a fail safe. - experimentalDesignImporter.importDesign( ee, is ); - this.experimentReportService.evictFromCache( ee.getId() ); - - } catch ( IOException e ) { - throw new RuntimeException( "Failed to import the design: " + e.getMessage() ); - } - - } - - @Override - public void createExperimentalFactor( EntityDelegator e, ExperimentalFactorValueObject efvo ) { - if ( e == null || e.getId() == null ) - return; - ExperimentalDesign ed = experimentalDesignService.loadWithExperimentalFactors( e.getId() ); - - ExperimentalFactor ef = ExperimentalFactor.Factory.newInstance(); - ef.setType( FactorType.valueOf( efvo.getType() ) ); - ef.setExperimentalDesign( ed ); - ef.setName( efvo.getName() ); - ef.setDescription( efvo.getDescription() ); - ef.setCategory( this.createCategoryCharacteristic( efvo.getCategory(), efvo.getCategoryUri() ) ); - - /* - * Note: this call should not be needed because of cascade behaviour. - */ - // experimentalFactorService.create( ef ); - if ( ed.getExperimentalFactors() == null ) - ed.setExperimentalFactors( new HashSet() ); - ed.getExperimentalFactors().add( ef ); - - experimentalDesignService.update( ed ); - - ExpressionExperiment ee = experimentalDesignService.getExpressionExperiment( ed ); - - // this.auditTrailService.addUpdateEvent( ee, ExperimentalDesignEvent.class, - // "ExperimentalFactor added: " + efvo.getName(), efvo.toString() ); - this.experimentReportService.evictFromCache( ee.getId() ); - - } - - @Override - public void createFactorValue( EntityDelegator e ) { - if ( e == null || e.getId() == null ) - return; - ExperimentalFactor ef = experimentalFactorService.load( e.getId() ); - - if ( ef == null ) { - throw new EntityNotFoundException( - "Experimental factor with ID=" + e.getId() + " could not be accessed for editing" ); - } - - Set chars = new HashSet<>(); - for ( FactorValue fv : ef.getFactorValues() ) { - //noinspection LoopStatementThatDoesntLoop // No, but its an effective way of doing this - for ( Characteristic c : fv.getCharacteristics() ) { - chars.add( this.createTemplateCharacteristic( c ) ); - break; - } - } - if ( chars.isEmpty() ) { - if ( ef.getCategory() == null ) { - throw new IllegalArgumentException( "You cannot create new factor values on a experimental factor that is not defined by a formal Category" ); - } - chars.add( this.createTemplateCharacteristic( ef.getCategory() ) ); - } - - FactorValue fv = FactorValue.Factory.newInstance(); - fv.setExperimentalFactor( ef ); - fv.setCharacteristics( chars ); - - ExpressionExperiment ee = experimentalDesignService.getExpressionExperiment( ef.getExperimentalDesign() ); - - // this is just a placeholder factor value; use has to edit it. - expressionExperimentService.addFactorValue( ee, fv ); - } - - @Override - public void createFactorValueCharacteristic( EntityDelegator e, Characteristic c ) { - if ( e == null || e.getId() == null ) - return; - FactorValue fv = factorValueService.load( e.getId() ); - - if ( fv == null ) { - throw new EntityNotFoundException( "No such factor value with id=" + e.getId() ); - } - - if ( StringUtils.isBlank( c.getCategory() ) ) { - throw new IllegalArgumentException( "The category cannot be blank for " + c ); - } - - if ( fv.getCharacteristics() == null ) { - fv.setCharacteristics( new HashSet() ); - } - - fv.getCharacteristics().add( c ); - - factorValueService.update( fv ); - - ExpressionExperiment ee = expressionExperimentService.findByFactorValue( fv ); - // this.auditTrailService.addUpdateEvent( ee, ExperimentalDesignEvent.class, - // "FactorValue characteristic added to: " + fv, c.toString() ); - this.experimentReportService.evictFromCache( ee.getId() ); - } - - @Override - public void deleteExperimentalFactors( EntityDelegator e, Long[] efIds ) { - - if ( e == null || e.getId() == null ) - return; - - Collection efCol = new LinkedList<>(); - Collections.addAll( efCol, efIds ); - - Collection toDelete = experimentalFactorService.load( efCol ); - - this.delete( toDelete ); - - } - - @Override - public void deleteFactorValueCharacteristics( FactorValueValueObject[] fvvos ) { - for ( FactorValueValueObject fvvo : fvvos ) { - FactorValue fv = factorValueService.load( fvvo.getId() ); - - if ( fv == null ) { - log.warn( "No factorvalue with ID=" + fvvo.getId() ); - continue; - } - - Characteristic c = characteristicService.load( fvvo.getCharId() ); - - if ( c == null ) { - log.warn( "Characteristic ID is null for FactorValueValueObject with id=" + fvvo.getId() ); - continue; - } - - fv.getCharacteristics().remove( c ); - characteristicService.remove( c ); - factorValueService.update( fv ); - } - } - - @Override - public void deleteFactorValues( EntityDelegator e, Long[] fvIds ) { - - if ( e == null || e.getId() == null ) - return; - Collection fvCol = new LinkedList<>(); - Collections.addAll( fvCol, fvIds ); - - for ( Long fvId : fvCol ) { - ExpressionExperiment ee = expressionExperimentService.findByFactorValue( fvId ); - this.experimentReportService.evictFromCache( ee.getId() ); - } - - factorValueDeletion.deleteFactorValues( fvCol ); - - } - - @Override - public Collection getBioMaterials( EntityDelegator e ) { - if ( e == null || e.getId() == null ) - return null; - ExpressionExperiment ee = expressionExperimentService.loadOrFail( e.getId() ); - ee = expressionExperimentService.thawLite( ee ); - Collection result = new HashSet<>(); - for ( BioAssay assay : ee.getBioAssays() ) { - BioMaterial sample = assay.getSampleUsed(); - BioMaterialValueObject bmvo = new BioMaterialValueObject( sample, assay ); - result.add( bmvo ); - - } - - filterCharacteristics( result ); - - return result; - } - - /** - * Filter the characteristicValues to those that we want to display in columns in the biomaterialvalue table. - * - */ - private void filterCharacteristics( Collection result ) { - - int c = result.size(); - - // build map of categories to bmos. No category: can't use. - Map> map = new HashMap<>(); - for ( BioMaterialValueObject bmo : result ) { - for ( CharacteristicValueObject ch : bmo.getCharacteristics() ) { - - String category = ch.getCategory(); - String value = ch.getValue(); - if ( StringUtils.isBlank( category ) ) { - /* - Experimental: split on ":" or "=", use first part as the category. - */ - if ( StringUtils.isNotBlank( value ) && value.matches( ".+[:=].+" ) ) { // note: GEO only allows ":" now but we have "=" in the db for older entries. - String[] split = value.split( "[:=]", 2 ); - category = StringUtils.strip( split[0] ); - } else { - continue; - } - } - - if ( !map.containsKey( category ) ) { - map.put( category, new HashSet() ); - } - map.get( category ).add( bmo ); - } - } - - /* - find ones that don't have a value for each sample, or which have more values than samples, or which are constants - */ - Collection toremove = new HashSet<>(); - for ( String category : map.keySet() ) { - //log.info( ">>>>>>>>>> " + category + ", " + map.get( category ).size() + " items" ); - if ( map.get( category ).size() != result.size() ) { - toremove.add( category ); - continue; - } - - // TODO add more exclusions; see also ExpresionExperimentDao.getAnnotationsByBioMaterials - if ( category.equals( "LabelCompound" ) || category.equals( "MaterialType" ) || category.equals( "molecular entity" ) ) { - toremove.add( category ); - continue; - } - - Collection vals = new HashSet<>(); - boolean keeper = false; - bms: - for ( BioMaterialValueObject bm : map.get( category ) ) { - // log.info( "inspecting " + bm ); - // Find the characteristic that had this category - for ( CharacteristicValueObject ch : bm.getCharacteristics() ) { - String mappedCategory = ch.getCategory(); - String mappedValue = ch.getValue(); - - if ( StringUtils.isBlank( mappedCategory ) ) { - // redo split (will refactor later) - if ( StringUtils.isNotBlank( mappedValue ) && mappedValue.matches( ".+[:=].+" ) ) { - String[] split = mappedValue.split( "[:=]", 2 ); - mappedCategory = StringUtils.strip( split[0] ); - mappedValue = StringUtils.strip( split[1] ); // to show the trimmed up value. - } else { - continue bms; - } - } - - if ( mappedCategory.equals( category ) ) { - if ( !vals.contains( mappedValue ) ) { - if ( log.isDebugEnabled() ) - log.debug( category + " -> " + mappedValue ); - vals.add( mappedValue ); - } - - // populate this into the biomaterial - // log.info( category + " -> " + mappedValue ); - bm.getCharacteristicValues().put( mappedCategory, mappedValue ); - } - } - - // if ( vals.size() > 1 ) { - // if ( log.isDebugEnabled() ) - // log.debug( category + " -- Keeper with " + vals.size() + " values" ); - // - // keeper = true; - // } - } - - if ( vals.size() < 2 ) { // constant - toremove.add( category ); - } - } - - // finally, clean up the bmos. - for ( BioMaterialValueObject bmo : result ) { - for ( String lose : toremove ) { - bmo.getCharacteristicValues().remove( lose ); - } - } - - } - - @Override - public Collection getExperimentalFactors( EntityDelegator e ) { - if ( e == null || e.getId() == null ) - return null; - - Collection result = new HashSet<>(); - Long designId; - if ( e.getClassDelegatingFor().equalsIgnoreCase( "ExpressionExperiment" ) || e.getClassDelegatingFor() - .endsWith( ".ExpressionExperiment" ) ) { - ExpressionExperiment ee = this.expressionExperimentService.loadOrFail( e.getId() ); - designId = ee.getExperimentalDesign().getId(); - } else if ( e.getClassDelegatingFor().equalsIgnoreCase( "ExperimentalDesign" ) || e.getClassDelegatingFor() - .equalsIgnoreCase( "ExperimentalDesign" ) - || e.getClassDelegatingFor() - .endsWith( ".ExperimentalDesign" ) ) { - designId = e.getId(); - } else { - throw new RuntimeException( "Don't know how to process a " + e.getClassDelegatingFor() ); - } - // ugly fix for bug 3746 - ExpressionExperiment ee = experimentalDesignService - .getExpressionExperiment( this.experimentalDesignService.loadOrFail( designId ) ); - ee = expressionExperimentService.thawLite( ee ); - ExperimentalDesign ed = ee.getExperimentalDesign(); - - for ( ExperimentalFactor factor : ed.getExperimentalFactors() ) { - result.add( new ExperimentalFactorValueObject( factor ) ); - } - - return result; - } - - @Override - public Collection getFactorValues( EntityDelegator e ) { - // FIXME I'm not sure why this keeps getting called with empty fields. - if ( e == null || e.getId() == null ) - return new HashSet<>(); - ExperimentalFactor ef = this.experimentalFactorService.load( e.getId() ); - if ( ef == null ) - return new HashSet<>(); - - Collection result = new HashSet<>(); - for ( FactorValue value : ef.getFactorValues() ) { - Characteristic efCategory = value.getExperimentalFactor().getCategory(); - if ( efCategory == null ) { - efCategory = Characteristic.Factory.newInstance(); - efCategory.setValue( value.getExperimentalFactor().getName() ); - } - result.add( new FactorValueValueObject( value, efCategory ) ); - } - return result; - } - - @Override - public Collection getFactorValuesWithCharacteristics( EntityDelegator e ) { - Collection result = new HashSet<>(); - if ( e == null || e.getId() == null ) { - return result; - } - ExperimentalFactor ef = this.experimentalFactorService.load( e.getId() ); - if ( ef == null ) { - return result; - } - - for ( FactorValue value : ef.getFactorValues() ) { - if ( value.getCharacteristics().size() > 0 ) { - for ( Characteristic c : value.getCharacteristics() ) { - result.add( new FactorValueValueObject( value, c ) ); - } - } else { - // We just use the experimental factor's characteristic. - Characteristic category = value.getExperimentalFactor().getCategory(); - if ( category == null ) { - category = Characteristic.Factory.newInstance(); - category.setValue( value.getExperimentalFactor().getName() ); - } - result.add( new FactorValueValueObject( value ) ); - } - } - return result; - } - - @Override - @RequestMapping("/showExperimentalDesign.html") - public ModelAndView show( HttpServletRequest request, HttpServletResponse response ) { - - String idstr = request.getParameter( "eeid" ); - String edStr = request.getParameter( "edid" ); - if ( StringUtils.isBlank( idstr ) && StringUtils.isBlank( edStr ) ) { - throw new IllegalArgumentException( "Must supply 'eeid' or 'edid' parameter" ); - } - - Long designId; - ExpressionExperiment ee; - ExperimentalDesign experimentalDesign; - if ( StringUtils.isNotBlank( idstr ) ) { - try { - Long id = Long.parseLong( idstr ); - ee = expressionExperimentService.load( id ); - - if ( ee == null ) { - throw new EntityNotFoundException( "Expression experiment with id=" + id + " cannot be accessed" ); - } - - designId = ee.getExperimentalDesign().getId(); - experimentalDesign = experimentalDesignService.load( designId ); - if ( experimentalDesign == null ) { - throw new EntityNotFoundException( designId + " not found" ); - } - } catch ( NumberFormatException e ) { - throw new IllegalArgumentException( "eeid must be a number" ); - } - } else { - try { - designId = Long.parseLong( edStr ); - experimentalDesign = experimentalDesignService.load( designId ); - if ( experimentalDesign == null ) { - throw new EntityNotFoundException( designId + " not found" ); - } - ee = experimentalDesignService.getExpressionExperiment( experimentalDesign ); - - } catch ( NumberFormatException e ) { - throw new IllegalArgumentException( "edid must be a number" ); - } - } - - request.setAttribute( "id", designId ); - - ee = expressionExperimentService.thawLite( ee ); - - // strip white spaces - String desc = ee.getDescription(); - ee.setDescription( StringUtils.strip( desc ) ); - - ModelAndView mnv = new ModelAndView( "experimentalDesign.detail" ); - mnv.addObject( "taxonId", expressionExperimentService.getTaxon( ee ).getId() ); - mnv.addObject( "hasPopulatedDesign", ee.getExperimentalDesign().getExperimentalFactors().size() > 0 ); - mnv.addObject( "experimentalDesign", ee.getExperimentalDesign() ); - mnv.addObject( "expressionExperiment", ee ); - mnv.addObject( "currentUserCanEdit", securityService.isEditable( ee ) ? "true" : "" ); - mnv.addObject( "expressionExperimentUrl", AnchorTagUtil.getExpressionExperimentUrl( ee, request.getServletContext() ) ); - - return mnv; - } - - @Override - public void updateBioMaterials( BioMaterialValueObject[] bmvos ) { - - if ( bmvos == null || bmvos.length == 0 ) - return; - - Collection biomaterials = bioMaterialService.updateBioMaterials( Arrays.asList( bmvos ) ); - - if ( biomaterials.isEmpty() ) - return; - - BioMaterial bm = biomaterials.iterator().next(); - ExpressionExperiment ee = expressionExperimentService.findByBioMaterial( bm ); - if ( ee == null ) - throw new IllegalStateException( "No Experiment for biomaterial: " + bm ); - - ee = expressionExperimentService.thawLite( ee ); - for ( ExperimentalFactor ef : ee.getExperimentalDesign().getExperimentalFactors() ) { - if ( ef.getType().equals( FactorType.CONTINUOUS ) ) { - - /* - * Check for unused factorValues - */ - Collection usedFactorValues = new HashSet<>(); - LinearModelAnalyzer.populateFactorValuesFromBASet( ee, ef, usedFactorValues ); - - Collection toDelete = new HashSet<>(); - for ( FactorValue fv : ef.getFactorValues() ) { - if ( !usedFactorValues.contains( fv ) ) { - /* - * remove it. - */ - toDelete.add( fv ); - - } - } - - if ( !toDelete.isEmpty() ) { - log.info( "Deleting " + toDelete.size() + " unused factorvalues for " + ef ); - factorValueDeletion.deleteFactorValues( EntityUtils.getIds( toDelete ) ); - } - - } - } - StringBuilder details = new StringBuilder( "Updated bio materials:\n" ); - for ( BioMaterialValueObject vo : bmvos ) { - if ( vo == null ) { - continue; - } - BioMaterial ba = bioMaterialService.load( vo.getId() ); - if ( ba != null ) { - details.append( "id: " ).append( ba.getId() ).append( " - " ).append( ba.getName() ).append( "\n" ); - } - } - this.auditTrailService.addUpdateEvent( ee, ExperimentalDesignUpdatedEvent.class, - "BioMaterials updated (" + bmvos.length + " items)", details.toString() ); - this.experimentReportService.evictFromCache( ee.getId() ); - } - - @Override - public void updateExperimentalFactors( ExperimentalFactorValueObject[] efvos ) { - - if ( efvos == null || efvos.length == 0 ) - return; - - for ( ExperimentalFactorValueObject efvo : efvos ) { - ExperimentalFactor ef = experimentalFactorService.loadOrFail( efvo.getId() ); - ef.setName( efvo.getName() ); - ef.setDescription( efvo.getDescription() ); - - FactorType newType = efvo.getType() != null ? FactorType.valueOf( efvo.getType() ) : null; - if ( newType == null || !newType.equals( ef.getType() ) ) { - // we only allow this if there are no factors - if ( ef.getFactorValues().isEmpty() ) { - ef.setType( newType ); - } else { - throw new IllegalArgumentException( "You cannot change the 'type' of a factor once it has factor values. Delete the factor values first." ); - } - } - - Characteristic vc = ef.getCategory(); - - // can be null if this was imported from GEO etc. - if ( vc == null ) { - vc = Characteristic.Factory.newInstance(); - } - - // String originalCategoryUri = vc.getCategoryUri(); - - vc.setCategory( efvo.getCategory() ); - vc.setCategoryUri( StringUtils.stripToNull( efvo.getCategoryUri() ) ); - vc.setValue( efvo.getCategory() ); - vc.setValueUri( StringUtils.stripToNull( efvo.getCategoryUri() ) ); - - ef.setCategory( vc ); - - experimentalFactorService.update( ef ); - } - - ExperimentalFactor ef = experimentalFactorService.loadOrFail( efvos[0].getId() ); - ExpressionExperiment ee = expressionExperimentService.findByFactor( ef ); - if ( ee == null ) - throw new EntityNotFoundException( "No experiment for factor: " + ef ); - this.experimentReportService.evictFromCache( ee.getId() ); - } - - @Override - public void updateFactorValueCharacteristics( FactorValueValueObject[] fvvos ) { - - if ( fvvos == null || fvvos.length == 0 ) - return; - - for ( FactorValueValueObject fvvo : fvvos ) { - - Long fvID = fvvo.getId(); - - if ( fvID == null ) { - throw new IllegalArgumentException( "Factor value id must be supplied" ); - } - - FactorValue fv = this.factorValueService.loadOrFail( fvID, EntityNotFoundException::new, "Could not load factorvalue with id=" + fvID ); - - if ( !securityService.isEditable( fv ) ) { - /* - * We do this instead of the interceptor because Characteristics are not securable, and we really don't - * want them to be. - */ - throw new AccessDeniedException( "Access is denied" ); - } - - Long charId = fvvo.getCharId(); // this is optional. Maybe we're actually adding a characteristic for the - // first time. - - Characteristic c; - if ( charId != null ) { - - c = characteristicService.load( charId ); - - if ( c == null ) { - /* - * This shouldn't happen but just in case... - */ - throw new IllegalStateException( "No characteristic with id " + fvvo.getCharId() ); - } - - if ( !fv.getCharacteristics().contains( c ) ) { - throw new IllegalArgumentException( "Characteristic with id=" + charId + " does not belong to factorvalue with id=" + fvID ); - } - - } else { - - c = Characteristic.Factory.newInstance(); - - } - - // For related code see CharacteristicUpdateTaskImpl - - // preserve original data - if ( StringUtils.isBlank( c.getOriginalValue() ) ) { - c.setOriginalValue( c.getValue() ); - } - - c.setCategory( fvvo.getCategory() ); - c.setValue( fvvo.getValue() ); - c.setCategoryUri( StringUtils.stripToNull( fvvo.getCategoryUri() ) ); - c.setValueUri( StringUtils.stripToNull( fvvo.getValueUri() ) ); - - c.setEvidenceCode( GOEvidenceCode.IC ); // characteristic has been manually updated - - if ( c.getId() != null ) { - characteristicService.update( c ); - } else { - fv.getCharacteristics().add( c ); - factorValueService.update( fv ); // cascade - } - - } - - FactorValue fv = this.factorValueService.loadOrFail( fvvos[0].getId() ); - ExpressionExperiment ee = expressionExperimentService.findByFactorValue( fv ); - // this.auditTrailService.addUpdateEvent( ee, ExperimentalDesignEvent.class, - // "FactorValue characteristics updated", StringUtils.join( fvvos, "\n" ) ); - this.experimentReportService.evictFromCache( ee.getId() ); - - } - - private Characteristic createCategoryCharacteristic( String category, String categoryUri ) { - Characteristic c; - if ( categoryUri != null ) { - Characteristic vc = Characteristic.Factory.newInstance(); - vc.setCategoryUri( StringUtils.stripToNull( categoryUri ) ); - vc.setValueUri( StringUtils.stripToNull( categoryUri ) ); - c = vc; - } else { - c = Characteristic.Factory.newInstance(); - } - c.setCategory( category ); - c.setValue( category ); - c.setEvidenceCode( GOEvidenceCode.IC ); // manually added characteristic - return c; - } - - private Characteristic createTemplateCharacteristic( Characteristic source ) { - Characteristic template = Characteristic.Factory.newInstance(); - template.setCategory( source.getCategory() ); - template.setCategoryUri( source.getCategoryUri() ); - template.setEvidenceCode( GOEvidenceCode.IEA ); // automatically added characteristic - return template; - } - - private void delete( Collection toDelete ) { - for ( ExperimentalFactor factorRemove : toDelete ) { - experimentalFactorService.remove( factorRemove ); - } - - for ( ExperimentalFactor ef : toDelete ) { - ExpressionExperiment ee = expressionExperimentService.findByFactor( ef ); - - if ( ee != null ) { - this.experimentReportService.evictFromCache( ee.getId() ); - } - } - } - -} diff --git a/gemma-web/src/main/java/ubic/gemma/web/controller/expression/experiment/ExpressionExperimentController.java b/gemma-web/src/main/java/ubic/gemma/web/controller/expression/experiment/ExpressionExperimentController.java index c6ce82e0d4..16f2019494 100644 --- a/gemma-web/src/main/java/ubic/gemma/web/controller/expression/experiment/ExpressionExperimentController.java +++ b/gemma-web/src/main/java/ubic/gemma/web/controller/expression/experiment/ExpressionExperimentController.java @@ -314,7 +314,7 @@ public List getAllTaxonExperimentGroup( Long taxonId /** * AJAX */ - public Collection getAnnotation( EntityDelegator e ) { + public Collection getAnnotation( EntityDelegator e ) { if ( e == null || e.getId() == null ) return null; return expressionExperimentService.getAnnotationsById( e.getId() ); @@ -376,7 +376,7 @@ public String getDescription( Long id ) { /** * AJAX */ - public Collection getDesignMatrixRows( EntityDelegator e ) { + public Collection getDesignMatrixRows( EntityDelegator e ) { if ( e == null || e.getId() == null ) { throw new IllegalArgumentException( "A non-null experiment ID must be supplied." ); } @@ -389,7 +389,7 @@ public Collection getDesignMatrixRows( EntityDelegat * * @return a collection of factor value objects that represent the factors of a given experiment */ - public Collection getExperimentalFactors( EntityDelegator e ) { + public Collection getExperimentalFactors( EntityDelegator e ) { if ( e == null || e.getId() == null ) { throw new IllegalArgumentException( "A non-null experiment ID must be supplied." ); } @@ -413,7 +413,7 @@ public Collection getExperimentalFactors( EntityD * * @return A collection of factor value objects for the specified experimental factor */ - public Collection getFactorValues( EntityDelegator e ) { + public Collection getFactorValues( EntityDelegator e ) { if ( e == null || e.getId() == null ) { throw new IllegalArgumentException( "A non-null ExperimentalFactor ID must be supplied." ); } @@ -615,6 +615,7 @@ public void recalculateBatchConfound( Long id ) { public void recalculateBatchEffect( Long id ) { ExpressionExperiment ee = getExperimentById( id, false ); ee.setBatchEffect( expressionExperimentService.getBatchEffect( ee ) ); + ee.setBatchEffectStatistics( expressionExperimentService.getBatchEffectStatistics( ee ) ); expressionExperimentService.update( ee ); } @@ -1166,8 +1167,8 @@ private void setBatchInfo( ExpressionExperimentDetailsValueObject finalResult, E if ( hasBatchInformation ) { finalResult.setBatchConfound( expressionExperimentService.getBatchConfound( ee ) ); } - - finalResult.setBatchEffect( expressionExperimentService.getBatchEffect( ee ) ); + finalResult.setBatchEffect( expressionExperimentService.getBatchEffect( ee ).name() ); + finalResult.setBatchEffectStatistics( expressionExperimentService.getBatchEffectStatistics( ee ) ); } /** diff --git a/gemma-web/src/main/java/ubic/gemma/web/controller/expression/experiment/ExpressionExperimentFormController.java b/gemma-web/src/main/java/ubic/gemma/web/controller/expression/experiment/ExpressionExperimentFormController.java index 46a9f9bc6d..99b2dea2cb 100644 --- a/gemma-web/src/main/java/ubic/gemma/web/controller/expression/experiment/ExpressionExperimentFormController.java +++ b/gemma-web/src/main/java/ubic/gemma/web/controller/expression/experiment/ExpressionExperimentFormController.java @@ -204,7 +204,7 @@ protected Object formBackingObject( HttpServletRequest request ) { REPRESENTATIONS = Arrays.stream( PrimitiveType.values() ).map( Enum::name ).sorted().collect( Collectors.toList() ); @Override - protected Map referenceData( HttpServletRequest request ) { + protected Map referenceData( HttpServletRequest request ) { Map referenceData = new HashMap<>(); Collection edCol = externalDatabaseService.loadAll(); diff --git a/gemma-web/src/main/java/ubic/gemma/web/controller/expression/experiment/ExpressionExperimentQCController.java b/gemma-web/src/main/java/ubic/gemma/web/controller/expression/experiment/ExpressionExperimentQCController.java index 3eee6a65e9..e29dc21423 100644 --- a/gemma-web/src/main/java/ubic/gemma/web/controller/expression/experiment/ExpressionExperimentQCController.java +++ b/gemma-web/src/main/java/ubic/gemma/web/controller/expression/experiment/ExpressionExperimentQCController.java @@ -73,12 +73,12 @@ import ubic.gemma.core.datastructure.matrix.ExperimentalDesignWriter; import ubic.gemma.core.datastructure.matrix.ExpressionDataWriterUtils; import ubic.gemma.model.analysis.expression.coexpression.CoexpCorrelationDistribution; -import ubic.gemma.model.common.description.Characteristic; import ubic.gemma.model.expression.bioAssay.BioAssay; import ubic.gemma.model.expression.bioAssayData.MeanVarianceRelation; import ubic.gemma.model.expression.experiment.ExperimentalFactor; import ubic.gemma.model.expression.experiment.ExpressionExperiment; import ubic.gemma.model.expression.experiment.FactorValue; +import ubic.gemma.model.expression.experiment.FactorValueUtils; import ubic.gemma.persistence.service.analysis.expression.coexpression.CoexpressionAnalysisService; import ubic.gemma.persistence.service.analysis.expression.diff.DifferentialExpressionResultService; import ubic.gemma.persistence.service.analysis.expression.sampleCoexpression.SampleCoexpressionAnalysisService; @@ -490,18 +490,7 @@ private void getCategories( Map efIdMap, Long efId, Ma int maxCategoryLabelLength = 10; for ( FactorValue fv : ef.getFactorValues() ) { - String value = fv.getValue(); - if ( StringUtils.isBlank( value ) || value.equals( "null" ) ) { - for ( Characteristic c : fv.getCharacteristics() ) { - if ( StringUtils.isNotBlank( c.getValue() ) ) { - if ( StringUtils.isNotBlank( value ) ) { - value = value + "; " + c.getValue(); - } else { - value = c.getValue(); - } - } - } - } + String value = FactorValueUtils.getSummaryString( fv, "; " ); if ( StringUtils.isBlank( value ) ) { value = fv.toString() + "--??"; diff --git a/gemma-web/src/main/java/ubic/gemma/web/controller/genome/gene/GeneController.java b/gemma-web/src/main/java/ubic/gemma/web/controller/genome/gene/GeneController.java index 77ec628aa7..2d1a707577 100644 --- a/gemma-web/src/main/java/ubic/gemma/web/controller/genome/gene/GeneController.java +++ b/gemma-web/src/main/java/ubic/gemma/web/controller/genome/gene/GeneController.java @@ -40,7 +40,7 @@ import ubic.gemma.model.genome.gene.GeneSetValueObject; import ubic.gemma.model.genome.gene.GeneValueObject; import ubic.gemma.model.genome.gene.phenotype.EvidenceFilter; -import ubic.gemma.model.genome.gene.phenotype.valueObject.CharacteristicValueObject; +import ubic.gemma.model.common.description.CharacteristicValueObject; import ubic.gemma.model.genome.gene.phenotype.valueObject.EvidenceValueObject; import ubic.gemma.persistence.service.genome.taxon.TaxonService; import ubic.gemma.web.controller.BaseController; diff --git a/gemma-web/src/main/java/ubic/gemma/web/logging/SlackAppender.java b/gemma-web/src/main/java/ubic/gemma/web/logging/SlackAppender.java index b9a4d3c44b..630b4c9902 100644 --- a/gemma-web/src/main/java/ubic/gemma/web/logging/SlackAppender.java +++ b/gemma-web/src/main/java/ubic/gemma/web/logging/SlackAppender.java @@ -4,7 +4,6 @@ import com.slack.api.methods.SlackApiException; import com.slack.api.methods.request.chat.ChatPostMessageRequest; import com.slack.api.model.Attachment; -import lombok.SneakyThrows; import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.log4j.Appender; import org.apache.log4j.AppenderSkeleton; @@ -15,15 +14,30 @@ public class SlackAppender extends AppenderSkeleton implements Appender { + /** + * Constant to use when reporting errors to the {@link #errorHandler}. + */ + private static final int + ERROR_ON_POST_MESSAGE_CODE = 1000, + ERROR_ON_CLOSE_CODE = 1001; + private final Slack slackInstance; private String token; private String channel; + /** + * Used in log4j.properties via reflection. + */ + @SuppressWarnings("unused") public SlackAppender() { slackInstance = new Slack(); } + public SlackAppender( Slack slackInstance ) { + this.slackInstance = slackInstance; + } + @Override protected void append( LoggingEvent loggingEvent ) { if ( !isAsSevereAsThreshold( loggingEvent.getLevel() ) ) { @@ -36,18 +50,21 @@ protected void append( LoggingEvent loggingEvent ) { // attach a stacktrace if available if ( loggingEvent.getThrowableInformation() != null ) - request.attachments( Collections.singletonList( stacktraceAsAttachment( loggingEvent ) ) ); + request.attachments( Collections.singletonList( throwableAsAttachment( loggingEvent.getThrowableInformation().getThrowable() ) ) ); slackInstance.methods( token ).chatPostMessage( request.build() ); } catch ( IOException | SlackApiException e ) { - errorHandler.error( String.format( "Failed to send logging event to Slack channel %s.", channel ), e, 0, loggingEvent ); + errorHandler.error( String.format( "Failed to send logging event to Slack channel %s.", channel ), e, ERROR_ON_POST_MESSAGE_CODE, loggingEvent ); } } @Override - @SneakyThrows public void close() { - slackInstance.close(); + try { + slackInstance.close(); + } catch ( Exception e ) { + errorHandler.error( "Failed to close the Slack instance.", e, ERROR_ON_CLOSE_CODE ); + } } @Override @@ -63,10 +80,10 @@ public void setChannel( String channel ) { this.channel = channel; } - private static Attachment stacktraceAsAttachment( LoggingEvent loggingEvent ) { + private Attachment throwableAsAttachment( Throwable t ) { return Attachment.builder() - .title( ExceptionUtils.getMessage( loggingEvent.getThrowableInformation().getThrowable() ) ) - .text( ExceptionUtils.getStackTrace( loggingEvent.getThrowableInformation().getThrowable() ) ) + .title( ExceptionUtils.getMessage( t ) ) + .text( ExceptionUtils.getStackTrace( t ) ) .fallback( "This attachment normally contains an error stacktrace." ) .build(); } diff --git a/gemma-web/src/main/java/ubic/gemma/web/propertyeditor/SequenceTypePropertyEditor.java b/gemma-web/src/main/java/ubic/gemma/web/propertyeditor/SequenceTypePropertyEditor.java index d934aabdd5..4419aa8aef 100644 --- a/gemma-web/src/main/java/ubic/gemma/web/propertyeditor/SequenceTypePropertyEditor.java +++ b/gemma-web/src/main/java/ubic/gemma/web/propertyeditor/SequenceTypePropertyEditor.java @@ -1,8 +1,8 @@ /* * The Gemma project - * + * * Copyright (c) 2007 University of British Columbia - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -62,6 +62,8 @@ public String getAsText() { return "Other"; } else if ( type.equals( SequenceType.ORF ) ) { return "ORF"; + } else if ( type.equals( SequenceType.DUMMY ) ) { + return "DUMMY"; } else { return "[Unknown]"; } diff --git a/gemma-web/src/main/java/ubic/gemma/web/remote/AuditController.java b/gemma-web/src/main/java/ubic/gemma/web/remote/AuditController.java index e97173187a..cbf5e5eaed 100644 --- a/gemma-web/src/main/java/ubic/gemma/web/remote/AuditController.java +++ b/gemma-web/src/main/java/ubic/gemma/web/remote/AuditController.java @@ -61,7 +61,7 @@ public class AuditController { private ExpressionExperimentService expressionExperimentService; @SuppressWarnings("unchecked") - public void addAuditEvent( EntityDelegator e, String auditEventType, String comment, String detail ) { + public void addAuditEvent( EntityDelegator e, String auditEventType, String comment, String detail ) { Auditable entity = this.getAuditable( e ); if ( entity == null ) { AuditController.log.warn( "Couldn't find Auditable represented by " + e ); @@ -83,7 +83,7 @@ public void addAuditEvent( EntityDelegator e, String auditEventType, String comm } } - public Collection getEvents( EntityDelegator e ) { + public Collection getEvents( EntityDelegator e ) { Collection result = new HashSet<>(); Auditable entity = this.getAuditable( e ); @@ -109,7 +109,7 @@ public Collection getEvents( EntityDelegator e ) { return result; } - private Auditable getAuditable( EntityDelegator e ) { + private Auditable getAuditable( EntityDelegator e ) { if ( e == null || e.getId() == null ) return null; if ( e.getClassDelegatingFor() == null ) diff --git a/gemma-web/src/main/java/ubic/gemma/web/remote/EntityDelegator.java b/gemma-web/src/main/java/ubic/gemma/web/remote/EntityDelegator.java index 3774e80845..406c8705a1 100644 --- a/gemma-web/src/main/java/ubic/gemma/web/remote/EntityDelegator.java +++ b/gemma-web/src/main/java/ubic/gemma/web/remote/EntityDelegator.java @@ -1,8 +1,8 @@ /* * The Gemma project - * + * * Copyright (c) 2007 Columbia University - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at @@ -18,15 +18,15 @@ */ package ubic.gemma.web.remote; +import org.springframework.util.Assert; import ubic.gemma.model.common.Identifiable; /** * Bean to expose for remote access via AJAX, when all that is needed is the ID and a way to know what the class is. - * + * @param the type of entity being delegated * @author Paul - * */ -public class EntityDelegator { +public class EntityDelegator { private Long id; @@ -35,7 +35,8 @@ public class EntityDelegator { public EntityDelegator() { } - public EntityDelegator( Identifiable entity ) { + public EntityDelegator( T entity ) { + Assert.notNull( entity.getId(), "The entity being delegated must have a non-null ID." ); this.id = entity.getId(); this.classDelegatingFor = entity.getClass().getName(); } @@ -56,4 +57,15 @@ public void setId( Long id ) { this.id = id; } + /** + * Check if the entity delegator holds the given type. + */ + public boolean holds( Class type ) { + return type.getName().equals( this.classDelegatingFor ) || type.getSimpleName().equals( this.classDelegatingFor ); + } + + @Override + public String toString() { + return String.format( "%s Id=%d", classDelegatingFor, id ); + } } diff --git a/gemma-web/src/main/java/ubic/gemma/web/taglib/common/auditAndSecurity/ExceptionTag.java b/gemma-web/src/main/java/ubic/gemma/web/taglib/common/auditAndSecurity/ExceptionTag.java index 6555a79535..10bf7903d2 100644 --- a/gemma-web/src/main/java/ubic/gemma/web/taglib/common/auditAndSecurity/ExceptionTag.java +++ b/gemma-web/src/main/java/ubic/gemma/web/taglib/common/auditAndSecurity/ExceptionTag.java @@ -35,7 +35,8 @@ public class ExceptionTag extends TagSupport { private static final long serialVersionUID = 4323477499674966726L; - protected static Log log = LogFactory.getLog( ExceptionTag.class.getName() ); + private static final Log log = LogFactory.getLog( ExceptionTag.class.getName() ); + private Exception exception; private Boolean showStackTrace = true; @@ -46,26 +47,25 @@ public int doStartTag() { if ( this.exception == null ) { buf.append( "Error was not recovered" ); } else { - if ( showStackTrace ) { buf.append( "
" ); if ( exception.getStackTrace() != null ) { - buf.append( escapeHtml4( ExceptionUtils.getStackTrace( exception ) )); + buf.append( escapeHtml4( ExceptionUtils.getStackTrace( exception ) ) ); } else { buf.append( "There was no stack trace!" ); } buf.append( "
" ); } - // To make sure error doesn't get swallowed. - ExceptionTag.log.error( this.exception, this.exception ); } - pageContext.getOut().print( buf.toString() ); } catch ( Exception ex ) { /* * Avoid stack overflow... */ - ExceptionTag.log.error( "Exception tag threw an exception: " + ex.getMessage(), ex ); + ExceptionTag.log.error( "Exception tag threw an exception: " + ExceptionUtils.getRootCauseMessage( ex ), ex ); + if ( this.exception != null ) { + ExceptionTag.log.error( "The original exception was: " + ExceptionUtils.getRootCauseMessage( ex ), exception ); + } } return Tag.SKIP_BODY; } diff --git a/gemma-web/src/main/java/ubic/gemma/web/util/JsonUtil.java b/gemma-web/src/main/java/ubic/gemma/web/util/JsonUtil.java index e0b2d925a8..287ea43cdf 100644 --- a/gemma-web/src/main/java/ubic/gemma/web/util/JsonUtil.java +++ b/gemma-web/src/main/java/ubic/gemma/web/util/JsonUtil.java @@ -4,6 +4,7 @@ import org.json.JSONObject; import org.springframework.http.MediaType; import org.springframework.security.core.AuthenticationException; +import org.springframework.util.Assert; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @@ -15,18 +16,19 @@ public final class JsonUtil { public static void writeErrorToResponse( AuthenticationException e, HttpServletResponse response ) throws IOException { - JSONObject json = new JSONObject(); - json.put( "success", false ); - json.put( "message", ExceptionUtils.getRootCauseMessage( e ) ); - response.setStatus( HttpServletResponse.SC_UNAUTHORIZED ); - writeToResponse( json, response ); + writeErrorToResponse( HttpServletResponse.SC_UNAUTHORIZED, ExceptionUtils.getRootCauseMessage( e ), response ); } public static void writeErrorToResponse( Exception e, HttpServletResponse response ) throws IOException { + writeErrorToResponse( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ExceptionUtils.getRootCauseMessage( e ), response ); + } + + public static void writeErrorToResponse( int status, String message, HttpServletResponse response ) throws IOException { + Assert.isTrue( status >= 400 && status < 600, "Status code must be in 4xx or 5xx ranges." ); JSONObject json = new JSONObject(); json.put( "success", false ); - json.put( "message", ExceptionUtils.getRootCauseMessage( e ) ); - response.setStatus( HttpServletResponse.SC_INTERNAL_SERVER_ERROR ); + json.put( "message", message ); + response.setStatus( status ); writeToResponse( json, response ); } diff --git a/gemma-web/src/main/java/ubic/gemma/web/util/UnhandledExceptionResolver.java b/gemma-web/src/main/java/ubic/gemma/web/util/UnhandledExceptionResolver.java new file mode 100644 index 0000000000..90d6b3b242 --- /dev/null +++ b/gemma-web/src/main/java/ubic/gemma/web/util/UnhandledExceptionResolver.java @@ -0,0 +1,68 @@ +package ubic.gemma.web.util; + +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.web.servlet.HandlerExceptionResolver; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver; + +import javax.annotation.Nullable; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * Resolver used when no other resolver can intervene. + *

+ * This is essentially delegating work to a {@link SimpleMappingExceptionResolver} with the added benefit that the + * exception can be logged beforehand with a given error category. + * @author poirigui + * @see SimpleMappingExceptionResolver + */ +public class UnhandledExceptionResolver implements HandlerExceptionResolver { + + private final SimpleMappingExceptionResolver delegate = new SimpleMappingExceptionResolver() {{ + setDefaultStatusCode( 500 ); + }}; + + @Nullable + private Log errorLogger = null; + + @Override + public ModelAndView resolveException( HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex ) { + if ( this.errorLogger != null ) { + this.errorLogger.error( "An unhandled exception was intercepted: " + ExceptionUtils.getRootCauseMessage( ex ), ex ); + } + return delegate.resolveException( request, response, handler, ex ); + } + + /** + * Set the name of the logger to use for reporting unhandled exceptions. + *

+ * By default, no logging is done. + * @see org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver#setWarnLogCategory(String) + */ + public void setErrorCategory( @Nullable String loggerName ) { + this.errorLogger = LogFactory.getLog( loggerName ); + } + + /** + * Set the status code to use for reporting unhandled exception. + *

+ * Defaults to 500. + * @see SimpleMappingExceptionResolver#setDefaultStatusCode(int) + */ + public void setStatusCode( int statusCode ) { + this.delegate.setDefaultStatusCode( statusCode ); + } + + /** + * Set the view to use for reporting unhandled exception. + *

+ * Default is none. + * @see SimpleMappingExceptionResolver#setDefaultErrorView(String) + */ + public void setErrorView( String errorView ) { + this.delegate.setDefaultErrorView( errorView ); + } +} diff --git a/gemma-web/src/main/resources/messages.properties b/gemma-web/src/main/resources/messages.properties index 90f12558fe..9958733590 100644 --- a/gemma-web/src/main/resources/messages.properties +++ b/gemma-web/src/main/resources/messages.properties @@ -8,12 +8,14 @@ user.logout=Logout errorPage.title=Bad News errorPage.heading=There was an error processing your request -errorPage.info.missing=The cause of the error was not recovered 400.title=Bad Request 400.message=The request your performed was invalid. If you believe this is an error, please contact the webmaster. You might try returning to the home page. 404.title=Page Not Found 404.message=The page you requested was not found. You might try returning to the home page. +405.title=Method Not Allowed +406.title=Not Acceptable +415.title=Unsupported Media Type 403.title=Access Denied 403.message=You are not authorized to access this page. If you believe this is an error, please contact the webmaster. You might try returning to the home page. 503.title=Service Unavailable diff --git a/gemma-web/src/main/webapp/400.jsp b/gemma-web/src/main/webapp/400.jsp deleted file mode 100644 index ffc52359ea..0000000000 --- a/gemma-web/src/main/webapp/400.jsp +++ /dev/null @@ -1,7 +0,0 @@ -<%@ include file="/common/taglibs.jsp" %> - -<%@ page isErrorPage="true" %> - - - <%@ include file="/pages/error/400.jsp" %> - \ No newline at end of file diff --git a/gemma-web/src/main/webapp/403.jsp b/gemma-web/src/main/webapp/403.jsp deleted file mode 100644 index 05edb90353..0000000000 --- a/gemma-web/src/main/webapp/403.jsp +++ /dev/null @@ -1,7 +0,0 @@ -<%@ include file="/common/taglibs.jsp" %> - -<%@ page isErrorPage="true" %> - - - <%@ include file="/pages/error/403.jsp" %> - \ No newline at end of file diff --git a/gemma-web/src/main/webapp/404.jsp b/gemma-web/src/main/webapp/404.jsp deleted file mode 100644 index 57b37aead8..0000000000 --- a/gemma-web/src/main/webapp/404.jsp +++ /dev/null @@ -1,7 +0,0 @@ -<%@ include file="/common/taglibs.jsp" %> - -<%@ page isErrorPage="true" %> - - - <%@ include file="/pages/error/404.jsp" %> - \ No newline at end of file diff --git a/gemma-web/src/main/webapp/500.jsp b/gemma-web/src/main/webapp/500.jsp deleted file mode 100644 index ec9f7c8122..0000000000 --- a/gemma-web/src/main/webapp/500.jsp +++ /dev/null @@ -1,7 +0,0 @@ -<%@ include file="/common/taglibs.jsp" %> - -<%@ page isErrorPage="true" %> - - - <%@ include file="/pages/error/500.jsp" %> - \ No newline at end of file diff --git a/gemma-web/src/main/webapp/503.jsp b/gemma-web/src/main/webapp/503.jsp deleted file mode 100644 index 3ab99a478c..0000000000 --- a/gemma-web/src/main/webapp/503.jsp +++ /dev/null @@ -1,7 +0,0 @@ -<%@ include file="/common/taglibs.jsp" %> - -<%@ page isErrorPage="true" %> - - - <%@ include file="/pages/error/503.jsp" %> - \ No newline at end of file diff --git a/gemma-web/src/main/webapp/WEB-INF/gemma-servlet.xml b/gemma-web/src/main/webapp/WEB-INF/gemma-servlet.xml index a44f4d4d9c..bbf366ebce 100644 --- a/gemma-web/src/main/webapp/WEB-INF/gemma-servlet.xml +++ b/gemma-web/src/main/webapp/WEB-INF/gemma-servlet.xml @@ -94,23 +94,33 @@ - - - - - 400 - 403 - 404 - 500 - - - - - error/403 - error/404 - error/503 - error/400 - + + + + + + + 400 + 403 + 404 + 503 + + + + + error/403 + error/404 + error/503 + error/400 + + + + + + + + + @@ -425,7 +435,7 @@ + class="ubic.gemma.web.controller.expression.experiment.ExperimentalDesignController"> @@ -441,6 +451,8 @@ + + @@ -475,6 +487,7 @@ + @@ -736,7 +749,7 @@ class="ubic.gemma.model.genome.gene.phenotype.valueObject.BibliographicPhenotypesValueObject"/> - @@ -805,7 +818,7 @@ - + @@ -1001,13 +1014,19 @@ - + + + + + + + diff --git a/gemma-web/src/main/webapp/WEB-INF/web.xml b/gemma-web/src/main/webapp/WEB-INF/web.xml index 95c1a4eacc..782a42ac8c 100644 --- a/gemma-web/src/main/webapp/WEB-INF/web.xml +++ b/gemma-web/src/main/webapp/WEB-INF/web.xml @@ -304,28 +304,7 @@ - 400 - /400.jsp - - - 403 - /403.jsp - - - 404 - /404.jsp - - - 500 - /500.jsp - - - 502 - /500.jsp - - - 503 - /503.jsp + /error.jsp diff --git a/gemma-web/src/main/webapp/common/exception.jsp b/gemma-web/src/main/webapp/common/exception.jsp new file mode 100644 index 0000000000..6e5ebf8f48 --- /dev/null +++ b/gemma-web/src/main/webapp/common/exception.jsp @@ -0,0 +1,26 @@ +<%@ include file="/common/taglibs.jsp"%> + + + +


+

+ + + + + + +
+

+ + <%-- this is causing stackoverflow errors ... no idea why, since upgrading to spring 3.2 from 3.0.7 --%> + + +
+ + +
+

+
+ + \ No newline at end of file diff --git a/gemma-web/src/main/webapp/error.jsp b/gemma-web/src/main/webapp/error.jsp new file mode 100644 index 0000000000..f25709c7b2 --- /dev/null +++ b/gemma-web/src/main/webapp/error.jsp @@ -0,0 +1,45 @@ +<%@ include file="/common/taglibs.jsp" %> + + + +<%@ page isErrorPage="true" %> + + + + + <%@ include file="/pages/error/400.jsp" %> + + + <%@ include file="/pages/error/403.jsp" %> + + + <%@ include file="/pages/error/404.jsp" %> + + + <%@ include file="/pages/error/405.jsp" %> + + + <%@ include file="/pages/error/406.jsp" %> + + + <%@ include file="/pages/error/415.jsp" %> + + + <%@ include file="/pages/error/500.jsp" %> + + +
+

Unsupported Status Code

+

+ There is no error page configured for the status code . +

+
+ <%@ include file="/common/exception.jsp" %> +
+
+
+
\ No newline at end of file diff --git a/gemma-web/src/main/webapp/pages/error/400.jsp b/gemma-web/src/main/webapp/pages/error/400.jsp index ee96b8eb07..6dfd86fce7 100644 --- a/gemma-web/src/main/webapp/pages/error/400.jsp +++ b/gemma-web/src/main/webapp/pages/error/400.jsp @@ -2,13 +2,8 @@ <fmt:message key="400.title"/> - - - -

-

@@ -16,13 +11,5 @@

- -
- -

- - - - - + <%@ include file="/common/exception.jsp" %>
diff --git a/gemma-web/src/main/webapp/pages/error/403.jsp b/gemma-web/src/main/webapp/pages/error/403.jsp index c91b8e0e22..303c221631 100644 --- a/gemma-web/src/main/webapp/pages/error/403.jsp +++ b/gemma-web/src/main/webapp/pages/error/403.jsp @@ -18,18 +18,12 @@

-
- - + -

You do not have permission to access this page.

- -

${exception.message}

- -
+ <%@ include file="/common/exception.jsp" %> diff --git a/gemma-web/src/main/webapp/pages/error/404.jsp b/gemma-web/src/main/webapp/pages/error/404.jsp index d6d52274db..3101a1311b 100644 --- a/gemma-web/src/main/webapp/pages/error/404.jsp +++ b/gemma-web/src/main/webapp/pages/error/404.jsp @@ -15,8 +15,5 @@

- -
- -

+ <%@ include file="/common/exception.jsp" %> \ No newline at end of file diff --git a/gemma-web/src/main/webapp/pages/error/405.jsp b/gemma-web/src/main/webapp/pages/error/405.jsp new file mode 100644 index 0000000000..0133f02acb --- /dev/null +++ b/gemma-web/src/main/webapp/pages/error/405.jsp @@ -0,0 +1,8 @@ +<%@ include file="/common/taglibs.jsp" %> + +<fmt:message key="405.title"/> + +
+

+ <%@ include file="/common/exception.jsp" %> +
\ No newline at end of file diff --git a/gemma-web/src/main/webapp/pages/error/406.jsp b/gemma-web/src/main/webapp/pages/error/406.jsp new file mode 100644 index 0000000000..d49aadc7b6 --- /dev/null +++ b/gemma-web/src/main/webapp/pages/error/406.jsp @@ -0,0 +1,8 @@ +<%@ include file="/common/taglibs.jsp" %> + +<fmt:message key="406.title"/> + +
+

+ <%@ include file="/common/exception.jsp" %> +
\ No newline at end of file diff --git a/gemma-web/src/main/webapp/pages/error/415.jsp b/gemma-web/src/main/webapp/pages/error/415.jsp new file mode 100644 index 0000000000..46fabb7dfa --- /dev/null +++ b/gemma-web/src/main/webapp/pages/error/415.jsp @@ -0,0 +1,8 @@ +<%@ include file="/common/taglibs.jsp" %> + +<fmt:message key="415.title"/> + +
+

+ <%@ include file="/common/exception.jsp" %> +
\ No newline at end of file diff --git a/gemma-web/src/main/webapp/pages/error/500.jsp b/gemma-web/src/main/webapp/pages/error/500.jsp index 474d52b77d..e787feb956 100644 --- a/gemma-web/src/main/webapp/pages/error/500.jsp +++ b/gemma-web/src/main/webapp/pages/error/500.jsp @@ -1,7 +1,6 @@ <%@ include file="/common/taglibs.jsp" %> <fmt:message key="errorPage.title"/> -