24
24
import org .apache .commons .lang3 .StringUtils ;
25
25
import org .apache .commons .math3 .exception .NotStrictlyPositiveException ;
26
26
import org .hibernate .Hibernate ;
27
+ import org .hibernate .SessionFactory ;
27
28
import org .springframework .beans .factory .annotation .Autowired ;
28
29
import org .springframework .security .access .ConfigAttribute ;
29
30
import org .springframework .security .access .SecurityConfig ;
59
60
import ubic .gemma .model .expression .bioAssay .BioAssay ;
60
61
import ubic .gemma .model .expression .bioAssayData .*;
61
62
import ubic .gemma .model .expression .biomaterial .BioMaterial ;
63
+ import ubic .gemma .model .expression .designElement .CompositeSequence ;
62
64
import ubic .gemma .model .expression .experiment .*;
63
65
import ubic .gemma .model .genome .Gene ;
64
66
import ubic .gemma .model .genome .Taxon ;
69
71
import ubic .gemma .persistence .service .analysis .expression .pca .PrincipalComponentAnalysisService ;
70
72
import ubic .gemma .persistence .service .analysis .expression .sampleCoexpression .SampleCoexpressionAnalysisService ;
71
73
import ubic .gemma .persistence .service .common .auditAndSecurity .AuditEventService ;
74
+ import ubic .gemma .persistence .service .common .auditAndSecurity .AuditTrailService ;
72
75
import ubic .gemma .persistence .service .common .quantitationtype .QuantitationTypeService ;
73
76
import ubic .gemma .persistence .service .expression .bioAssayData .BioAssayDimensionService ;
74
77
import ubic .gemma .persistence .util .*;
@@ -101,6 +104,8 @@ public class ExpressionExperimentServiceImpl
101
104
@ Autowired
102
105
private AuditEventService auditEventService ;
103
106
@ Autowired
107
+ private AuditTrailService auditTrailService ;
108
+ @ Autowired
104
109
private BioAssayDimensionService bioAssayDimensionService ;
105
110
@ Autowired
106
111
private DifferentialExpressionAnalysisService differentialExpressionAnalysisService ;
@@ -1300,51 +1305,93 @@ public ExpressionExperiment replaceRawVectors( ExpressionExperiment ee,
1300
1305
@ Override
1301
1306
@ Transactional
1302
1307
public void addSingleCellDataVectors ( ExpressionExperiment ee , QuantitationType quantitationType , Collection <SingleCellExpressionDataVector > vectors ) {
1308
+ Assert .notNull ( ee .getId () );
1309
+ Assert .notNull ( quantitationType .getId (), "The quantitation type must be persistent." );
1303
1310
Assert .isTrue ( !ee .getQuantitationTypes ().contains ( quantitationType ),
1304
- ee + " already have vectors for the quantitation type: " + quantitationType );
1305
- Assert .isTrue ( !vectors .isEmpty (), "At least one single-cell vector has to be supplied." );
1306
- Assert .isTrue ( vectors .stream ().allMatch ( v -> v .getQuantitationType ().equals ( quantitationType ) ),
1307
- "All vectors must have the quantitation type: " + quantitationType );
1308
- Assert .isTrue ( vectors .stream ().map ( SingleCellExpressionDataVector ::getSingleCellDimension ).distinct ().count () == 1 ,
1309
- "All vectors must share the same dimension." );
1310
- validateSingleCellDimension ( ee , vectors .iterator ().next ().getSingleCellDimension () );
1311
- ExpressionExperiment finalEe = ee ;
1312
- Assert .isTrue ( vectors .stream ().allMatch ( v -> v .getExpressionExperiment () == null || v .getExpressionExperiment ().equals ( finalEe ) ),
1313
- "Some of the vectors belong to other expression experiments." );
1314
- ee = ensureInSession ( ee );
1311
+ String .format ( "%s already have vectors for the quantitation type: %s; use replaceSingleCellDataVectors() to replace existing vectors." ,
1312
+ ee , quantitationType ) );
1313
+ validateSingleCellDataVectors ( ee , quantitationType , vectors );
1314
+ if ( vectors .iterator ().next ().getSingleCellDimension ().getId () == null ) {
1315
+ log .info ( "Creating a new single-cell dimension for " + ee );
1316
+ expressionExperimentDao .createSingleCellDimension ( ee , vectors .iterator ().next ().getSingleCellDimension () );
1317
+ }
1315
1318
for ( SingleCellExpressionDataVector v : vectors ) {
1316
1319
v .setExpressionExperiment ( ee );
1317
1320
}
1321
+ int previousSize = ee .getSingleCellExpressionDataVectors ().size ();
1322
+ log .info ( String .format ( "Adding %d single-cell vectors to %s for %s" , vectors .size (), ee , quantitationType ) );
1318
1323
ee .getSingleCellExpressionDataVectors ().addAll ( vectors );
1324
+ int numVectorsAdded = ee .getSingleCellExpressionDataVectors ().size () - previousSize ;
1319
1325
// make all other single-cell QTs non-preferred
1320
1326
if ( quantitationType .getIsPreferred () ) {
1321
- ee .getQuantitationTypes ().forEach ( q -> q .setIsPreferred ( false ) );
1327
+ for ( QuantitationType qt : ee .getQuantitationTypes () ) {
1328
+ if ( qt .getIsPreferred () ) {
1329
+ log .info ( "Setting " + qt + " to non-preferred since we're adding a new set of preferred vectors to " + ee );
1330
+ qt .setIsPreferred ( false );
1331
+ break ; // there is at most 1 set of preferred vectors
1332
+ }
1333
+ }
1322
1334
}
1323
1335
ee .getQuantitationTypes ().add ( quantitationType );
1324
1336
update ( ee ); // will take care of creating vectors
1337
+ auditTrailService .addUpdateEvent ( ee , DataAddedEvent .class ,
1338
+ String .format ( "Added %d vectors for %s" , numVectorsAdded , quantitationType ) );
1325
1339
}
1326
1340
1327
1341
@ Override
1328
1342
@ Transactional
1329
1343
public void replaceSingleCellDataVectors ( ExpressionExperiment ee , QuantitationType quantitationType , Collection <SingleCellExpressionDataVector > vectors ) {
1344
+ Assert .notNull ( ee .getId () );
1345
+ Assert .notNull ( quantitationType .getId (), "The quantitation type must be persistent." );
1330
1346
Assert .isTrue ( ee .getQuantitationTypes ().contains ( quantitationType ),
1331
- ee + " does not have the quantitation type: " + quantitationType );
1332
- Assert .isTrue ( !vectors .isEmpty (), "At least one single-cell vector has to be supplied; use removeSingleCelLDataVectors() to remove vectors instead." );
1333
- Assert .isTrue ( vectors .stream ().allMatch ( v -> v .getQuantitationType ().equals ( quantitationType ) ),
1334
- "All vectors must have the quantitation type: " + quantitationType );
1335
- Assert .isTrue ( vectors .stream ().map ( SingleCellExpressionDataVector ::getSingleCellDimension ).distinct ().count () == 1 ,
1336
- "All vectors must share the same dimension." );
1337
- validateSingleCellDimension ( ee , vectors .iterator ().next ().getSingleCellDimension () );
1338
- ExpressionExperiment finalEe = ee ;
1339
- Assert .isTrue ( vectors .stream ().allMatch ( v -> v .getExpressionExperiment () == null || v .getExpressionExperiment ().equals ( finalEe ) ),
1340
- "Some of the vectors belong to other expression experiments." );
1341
- ee = ensureInSession ( ee );
1342
- ee .getSingleCellExpressionDataVectors ().removeIf ( v -> v .getQuantitationType ().equals ( quantitationType ) );
1347
+ String .format ( "%s does not have the quantitation type: %s; use addSingleCellDataVectors() to add new vectors instead." ,
1348
+ ee , quantitationType ) );
1349
+ validateSingleCellDataVectors ( ee , quantitationType , vectors );
1350
+ boolean scdCreated = false ;
1351
+ if ( vectors .iterator ().next ().getSingleCellDimension ().getId () == null ) {
1352
+ log .info ( "Creating a new single-cell dimension for " + ee );
1353
+ expressionExperimentDao .createSingleCellDimension ( ee , vectors .iterator ().next ().getSingleCellDimension () );
1354
+ scdCreated = true ;
1355
+ }
1356
+ Set <SingleCellExpressionDataVector > vectorsToBeReplaced = ee .getSingleCellExpressionDataVectors ().stream ()
1357
+ .filter ( v -> v .getQuantitationType ().equals ( quantitationType ) ).collect ( Collectors .toSet () );
1343
1358
for ( SingleCellExpressionDataVector v : vectors ) {
1344
1359
v .setExpressionExperiment ( ee );
1345
1360
}
1361
+ int previousSize = ee .getSingleCellExpressionDataVectors ().size ();
1362
+ if ( !vectorsToBeReplaced .isEmpty () ) {
1363
+ // if the SCD was created, we do not need to check additional vectors for removing the existing one
1364
+ removeSingleCellVectorsAndDimensionIfNecessary ( ee , vectorsToBeReplaced , scdCreated ? null : vectors );
1365
+ } else {
1366
+ log .warn ( "No vectors with the quantitation type: " + quantitationType );
1367
+ }
1368
+ int numVectorsRemoved = ee .getSingleCellExpressionDataVectors ().size () - previousSize ;
1369
+ log .info ( String .format ( "Adding %d single-cell vectors to %s for %s" , vectors .size (), ee , quantitationType ) );
1346
1370
ee .getSingleCellExpressionDataVectors ().addAll ( vectors );
1371
+ int numVectorsAdded = ee .getSingleCellExpressionDataVectors ().size () - ( previousSize - numVectorsRemoved );
1347
1372
update ( ee );
1373
+ auditTrailService .addUpdateEvent ( ee , DataReplacedEvent .class ,
1374
+ String .format ( "Replaced %d vectors with %d vectors for %s." , numVectorsRemoved , numVectorsAdded , quantitationType ) );
1375
+ }
1376
+
1377
+ private void validateSingleCellDataVectors ( ExpressionExperiment ee , QuantitationType quantitationType , Collection <SingleCellExpressionDataVector > vectors ) {
1378
+ Assert .notNull ( quantitationType .getId (), "The quantitation type must be persistent." );
1379
+ Assert .isTrue ( !vectors .isEmpty (), "At least one single-cell vector has to be supplied; use removeSingleCellDataVectors() to remove vectors instead." );
1380
+ Assert .isTrue ( vectors .stream ().allMatch ( v -> v .getExpressionExperiment () == null || v .getExpressionExperiment ().equals ( ee ) ),
1381
+ "Some of the vectors belong to other expression experiments." );
1382
+ Assert .isTrue ( vectors .stream ().allMatch ( v -> v .getQuantitationType () == quantitationType ),
1383
+ "All vectors must have the same quantitation type: " + quantitationType );
1384
+ Assert .isTrue ( vectors .stream ().allMatch ( v -> v .getDesignElement () != null && v .getDesignElement ().getId () != null ),
1385
+ "All vectors must have a persistent design element." );
1386
+ // TODO: allow vectors from multiple platforms
1387
+ CompositeSequence element = vectors .iterator ().next ().getDesignElement ();
1388
+ ArrayDesign platform = element .getArrayDesign ();
1389
+ Assert .isTrue ( vectors .stream ().allMatch ( v -> v .getDesignElement ().getArrayDesign ().equals ( platform ) ),
1390
+ "All vectors must have a persistent design element from the same platform." );
1391
+ SingleCellDimension singleCellDimension = vectors .iterator ().next ().getSingleCellDimension ();
1392
+ validateSingleCellDimension ( ee , singleCellDimension );
1393
+ Assert .isTrue ( vectors .stream ().allMatch ( v -> v .getSingleCellDimension () == singleCellDimension ),
1394
+ "All vectors must share the same dimension: " + singleCellDimension );
1348
1395
}
1349
1396
1350
1397
/**
@@ -1354,24 +1401,80 @@ private void validateSingleCellDimension( ExpressionExperiment ee, SingleCellDim
1354
1401
Assert .isTrue ( scbad .getCellIds ().size () == scbad .getNumberOfCells (),
1355
1402
"The number of cell IDs must match the number of cells." );
1356
1403
if ( scbad .getCellTypes () != null ) {
1357
- Assert .notNull ( scbad .getNumberOfCellTypes () );
1358
- Assert .isTrue ( scbad .getCellTypes ().stream ().distinct ().count () == scbad .getNumberOfCellTypes (),
1359
- "The number of cell types must match the number of distinct values the cellTypes collection." );
1404
+ Assert .notNull ( scbad .getNumberOfCellTypeLabels () );
1405
+ Assert .notNull ( scbad .getCellTypeLabels () );
1406
+ Assert .isTrue ( scbad .getCellTypes ().length == scbad .getCellIds ().size (),
1407
+ "The number of cell types must match the number of cell IDs." );
1408
+ Assert .isTrue ( scbad .getCellTypeLabels ().size () == scbad .getNumberOfCellTypeLabels (),
1409
+ "The number of cell types must match the number of values the cellTypeLabels collection." );
1360
1410
} else {
1361
- Assert .isNull ( scbad .getNumberOfCellTypes (), "There is no cell types assigned, the number of cell types must be null." );
1411
+ Assert .isNull ( scbad .getCellTypeLabels () );
1412
+ Assert .isNull ( scbad .getNumberOfCellTypeLabels (), "There is no cell types assigned, the number of cell types must be null." );
1362
1413
}
1363
1414
Assert .isTrue ( ee .getBioAssays ().containsAll ( scbad .getBioAssays () ), "Not all supplied BioAssays belong to " + ee );
1364
- validateSparseRangeArray ( scbad .getBioAssays (), scbad .getBioAssaysOffset (), scbad .getNumberOfCells () );
1415
+ validateSparseRangeArray ( scbad .getBioAssays (), scbad .getBioAssaysOffset (), scbad .getNumberOfCells () );
1365
1416
}
1366
1417
1367
1418
@ Override
1368
1419
@ Transactional
1369
1420
public void removeSingleCellDataVectors ( ExpressionExperiment ee , QuantitationType quantitationType ) {
1421
+ Assert .notNull ( ee .getId () );
1422
+ Assert .notNull ( quantitationType .getId () );
1370
1423
Assert .isTrue ( ee .getQuantitationTypes ().contains ( quantitationType ) );
1371
- ee = ensureInSession ( ee );
1372
- ee .getSingleCellExpressionDataVectors ().removeIf ( v -> v .getQuantitationType ().equals ( quantitationType ) );
1424
+ Set <SingleCellExpressionDataVector > vectors = ee .getSingleCellExpressionDataVectors ().stream ()
1425
+ .filter ( v -> v .getQuantitationType ().equals ( quantitationType ) ).collect ( Collectors .toSet () );
1426
+ if ( !vectors .isEmpty () ) {
1427
+ removeSingleCellVectorsAndDimensionIfNecessary ( ee , vectors , null );
1428
+ } else {
1429
+ log .warn ( "No vectors with the quantitation type: " + quantitationType );
1430
+ }
1373
1431
ee .getQuantitationTypes ().remove ( quantitationType );
1374
1432
update ( ee );
1433
+ auditTrailService .addUpdateEvent ( ee , DataRemovedEvent .class ,
1434
+ String .format ( "Removed %d vectors for %s." , vectors .size (), quantitationType ) );
1435
+ }
1436
+
1437
+ /**
1438
+ * @deprecated do not use this, it's only meant as a workaround for deleting single-cell vectors
1439
+ */
1440
+ @ Autowired
1441
+ @ Deprecated
1442
+ private SessionFactory sessionFactory ;
1443
+
1444
+ /**
1445
+ * Remove the given single-cell vectors and their corresponding single-cell dimension if necessary.
1446
+ * @param ee the experiment to remove the vectors from.
1447
+ * @param additionalVectors additional vectors to check if the single-cell dimension is still in use (i.e. vectors that are in the process of being added).
1448
+ * @return true if the vectors were removed, false otherwise.
1449
+ */
1450
+ private void removeSingleCellVectorsAndDimensionIfNecessary ( ExpressionExperiment ee ,
1451
+ Collection <SingleCellExpressionDataVector > vectors ,
1452
+ @ Nullable Collection <SingleCellExpressionDataVector > additionalVectors ) {
1453
+ log .info ( String .format ( "Removing %d single-cell vectors for %s..." , vectors .size (), ee ) );
1454
+ ee .getSingleCellExpressionDataVectors ().removeAll ( vectors );
1455
+ // FIXME: flushing shouldn't be necessary here, but Hibernate does appear to cascade vectors removal prior to removing the SCD or QT...
1456
+ sessionFactory .getCurrentSession ().flush ();
1457
+ // check if SCD is still in use else remove it
1458
+ SingleCellDimension scd = vectors .iterator ().next ().getSingleCellDimension ();
1459
+ boolean scdStillUsed = false ;
1460
+ for ( SingleCellExpressionDataVector v : ee .getSingleCellExpressionDataVectors () ) {
1461
+ if ( v .getSingleCellDimension ().equals ( scd ) ) {
1462
+ scdStillUsed = true ;
1463
+ break ;
1464
+ }
1465
+ }
1466
+ if ( !scdStillUsed && additionalVectors != null ) {
1467
+ for ( SingleCellExpressionDataVector v : additionalVectors ) {
1468
+ if ( v .getSingleCellDimension ().equals ( scd ) ) {
1469
+ scdStillUsed = true ;
1470
+ break ;
1471
+ }
1472
+ }
1473
+ }
1474
+ if ( !scdStillUsed ) {
1475
+ log .info ( "Removing unused single-cell dimension " + scd + " for " + ee );
1476
+ expressionExperimentDao .deleteSingleCellDimension ( ee , scd );
1477
+ }
1375
1478
}
1376
1479
1377
1480
/**
0 commit comments