From 5f35e12e599f37b17ef889d4b262b84c7e4de718 Mon Sep 17 00:00:00 2001 From: felmer <felmer> Date: Mon, 19 Dec 2011 15:53:34 +0000 Subject: [PATCH] LMS-2623 ImageSizeFeedingMaintenanceTask implemented and tested SVN: 24054 --- .../etl/ImageSizeFeedingMaintenanceTask.java | 201 +++++++++++ .../dss/etl/ImagingDatabaseVersionHolder.java | 2 +- .../ImageSizeFeedingMaintenanceTaskTest.java | 330 ++++++++++++++++++ 3 files changed, 532 insertions(+), 1 deletion(-) create mode 100644 screening/source/java/ch/systemsx/cisd/openbis/dss/etl/ImageSizeFeedingMaintenanceTask.java create mode 100644 screening/sourceTest/java/ch/systemsx/cisd/openbis/dss/etl/ImageSizeFeedingMaintenanceTaskTest.java diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/ImageSizeFeedingMaintenanceTask.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/ImageSizeFeedingMaintenanceTask.java new file mode 100644 index 00000000000..94396438eb5 --- /dev/null +++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/ImageSizeFeedingMaintenanceTask.java @@ -0,0 +1,201 @@ +/* + * Copyright 2011 ETH Zuerich, CISD + * + * 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 ch.systemsx.cisd.openbis.dss.etl; + +import java.awt.image.BufferedImage; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; +import java.util.regex.Pattern; + +import javax.sql.DataSource; + +import net.lemnik.eodsql.QueryTool; + +import org.apache.log4j.Logger; + +import ch.systemsx.cisd.common.io.hierarchical_content.api.IHierarchicalContent; +import ch.systemsx.cisd.common.logging.LogCategory; +import ch.systemsx.cisd.common.logging.LogFactory; +import ch.systemsx.cisd.common.maintenance.IDataStoreLockingMaintenanceTask; +import ch.systemsx.cisd.common.utilities.Counters; +import ch.systemsx.cisd.openbis.dss.etl.dataaccess.IImagingQueryDAO; +import ch.systemsx.cisd.openbis.dss.etl.dataaccess.ImagingDatasetLoader; +import ch.systemsx.cisd.openbis.dss.generic.shared.IEncapsulatedOpenBISService; +import ch.systemsx.cisd.openbis.dss.generic.shared.IHierarchicalContentProvider; +import ch.systemsx.cisd.openbis.dss.generic.shared.ServiceProvider; +import ch.systemsx.cisd.openbis.generic.shared.dto.SimpleDataSetInformationDTO; +import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgImageDatasetDTO; +import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgImageZoomLevelDTO; + +/** + * Task which feeds imageing database with images sizes of already registered data sets of type + * HCS_IMAGE*. + * + * @author Franz-Josef Elmer + */ +public class ImageSizeFeedingMaintenanceTask implements IDataStoreLockingMaintenanceTask +{ + private static final String THUMBNAIL = "thumbnail"; + + private static final String ORIGINAL = "original"; + + private static final Logger operationLog = LogFactory.getLogger(LogCategory.OPERATION, + ImageSizeFeedingMaintenanceTask.class); + + private static final int MAX_NUMBER_OF_EXCEPTIONS_SHOWN = 10; + private static final Pattern DATA_SET_TYPE_PATTERN = Pattern.compile("HCS_IMAGE.*"); + private IImagingQueryDAO dao; + private IEncapsulatedOpenBISService service; + private IHierarchicalContentProvider contentProvider; + + public ImageSizeFeedingMaintenanceTask() + { + } + + ImageSizeFeedingMaintenanceTask(IImagingQueryDAO dao, + IEncapsulatedOpenBISService service, IHierarchicalContentProvider contentProvider) + { + this.dao = dao; + this.service = service; + this.contentProvider = contentProvider; + } + + public boolean requiresDataStoreLock() + { + return true; + } + + public void setUp(String pluginName, Properties properties) + { + DataSource dataSource = ServiceProvider.getDataSourceProvider().getDataSource(properties); + dao = QueryTool.getQuery(dataSource, IImagingQueryDAO.class); + dao.listSpots(0L); // testing correct database set up + service = ServiceProvider.getOpenBISService(); + contentProvider = ServiceProvider.getHierarchicalContentProvider(); + } + + public void execute() + { + List<SimpleDataSetInformationDTO> dataSets = service.listDataSets(); + operationLog.info("Scan " + dataSets.size() + " data sets."); + List<String> exceptions = new ArrayList<String>(); + Counters<String> counters = new Counters<String>(); + for (SimpleDataSetInformationDTO dataSet : dataSets) + { + if (matchingType(dataSet)) + { + handleDataSet(dataSet.getDataSetCode(), exceptions, counters); + } + } + logExceptionsIfAny(exceptions); + operationLog.info(counters.getCountOf(ORIGINAL) + " original image sizes and " + + counters.getCountOf(THUMBNAIL) + + " thumbnail image sizes are added to the database."); + } + + private void handleDataSet(String dataSetCode, List<String> exceptions, + Counters<String> counters) + { + IHierarchicalContent content = null; + try + { + content = contentProvider.asContent(dataSetCode); + ImgImageDatasetDTO imageDataSet = dao.tryGetImageDatasetByPermId(dataSetCode); + if (imageDataSet != null) + { + long dataSetId = imageDataSet.getId(); + List<ImgImageZoomLevelDTO> zoomLevels = dao.listImageZoomLevels(dataSetId); + if (zoomLevels.isEmpty()) + { + IImagingDatasetLoader loader = + createImageLoader(dataSetCode, content); + AbsoluteImageReference originalImage = loader.tryFindAnyOriginalImage(); + String logEntryOriginal = addZoomLevel(dataSetCode, dataSetId, originalImage, true); + AbsoluteImageReference thumbnail = loader.tryFindAnyThumbnail(); + String logEntryThumbnail = addZoomLevel(dataSetCode, dataSetId, thumbnail, false); + dao.commit(); + if (logEntryOriginal != null) + { + operationLog.info(logEntryOriginal); + counters.count(ORIGINAL); + } + if (logEntryThumbnail != null) + { + operationLog.info(logEntryThumbnail); + counters.count(THUMBNAIL); + } + } + } + } catch (Exception ex) + { + dao.rollback(); + exceptions.add("Data set " + dataSetCode + ": " + ex.toString()); + } finally + { + if (content != null) + { + content.close(); + } + } + } + + protected IImagingDatasetLoader createImageLoader(String dataSetCode, + IHierarchicalContent content) + { + return ImagingDatasetLoader.tryCreate(dao, dataSetCode, content); + } + + private void logExceptionsIfAny(List<String> exceptions) + { + if (exceptions.isEmpty() == false) + { + boolean more = exceptions.size() > MAX_NUMBER_OF_EXCEPTIONS_SHOWN; + operationLog.error(exceptions.size() + + " exceptions occured" + + (more ? " (only the first " + MAX_NUMBER_OF_EXCEPTIONS_SHOWN + + " are logged):" : ":")); + for (int i = 0; i < Math.min(MAX_NUMBER_OF_EXCEPTIONS_SHOWN, exceptions.size()); i++) + { + operationLog.error(exceptions.get(i)); + } + } + } + + private String addZoomLevel(String dataSetCode, long dataSetId, AbsoluteImageReference image, + boolean original) + { + if (image == null) + { + return null; + } + BufferedImage unchangedImage = image.getUnchangedImage(); + int width = unchangedImage.getWidth(); + int height = unchangedImage.getHeight(); + dao.addImageZoomLevel(new ImgImageZoomLevelDTO(dataSetCode, original, "", width, height, + dataSetId)); + return (original ? "Original" : "Thumbnail") + " size " + width + "x" + height + + " added for data set " + dataSetCode; + } + + private boolean matchingType(SimpleDataSetInformationDTO dataSet) + { + String dataSetType = dataSet.getDataSetType(); + return DATA_SET_TYPE_PATTERN.matcher(dataSetType).matches(); + } + +} diff --git a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/ImagingDatabaseVersionHolder.java b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/ImagingDatabaseVersionHolder.java index 2894360e072..3dd5566db2d 100644 --- a/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/ImagingDatabaseVersionHolder.java +++ b/screening/source/java/ch/systemsx/cisd/openbis/dss/etl/ImagingDatabaseVersionHolder.java @@ -27,6 +27,6 @@ public class ImagingDatabaseVersionHolder implements IDatabaseVersionHolder { public String getDatabaseVersion() { - return "020"; // changed in S122 + return "021"; // changed in S122 } } diff --git a/screening/sourceTest/java/ch/systemsx/cisd/openbis/dss/etl/ImageSizeFeedingMaintenanceTaskTest.java b/screening/sourceTest/java/ch/systemsx/cisd/openbis/dss/etl/ImageSizeFeedingMaintenanceTaskTest.java new file mode 100644 index 00000000000..cbb4340c4f4 --- /dev/null +++ b/screening/sourceTest/java/ch/systemsx/cisd/openbis/dss/etl/ImageSizeFeedingMaintenanceTaskTest.java @@ -0,0 +1,330 @@ +/* + * Copyright 2011 ETH Zuerich, CISD + * + * 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 ch.systemsx.cisd.openbis.dss.etl; + +import java.awt.image.BufferedImage; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.jmock.Expectations; +import org.jmock.Mockery; +import org.testng.AssertJUnit; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import ch.systemsx.cisd.common.io.hierarchical_content.api.IHierarchicalContent; +import ch.systemsx.cisd.common.logging.BufferedAppender; +import ch.systemsx.cisd.common.test.RecordingMatcher; +import ch.systemsx.cisd.openbis.dss.etl.dataaccess.IImagingQueryDAO; +import ch.systemsx.cisd.openbis.dss.etl.dto.ImageTransfomationFactories; +import ch.systemsx.cisd.openbis.dss.generic.server.images.dto.RequestedImageSize; +import ch.systemsx.cisd.openbis.dss.generic.shared.IEncapsulatedOpenBISService; +import ch.systemsx.cisd.openbis.dss.generic.shared.IHierarchicalContentProvider; +import ch.systemsx.cisd.openbis.generic.shared.dto.SimpleDataSetInformationDTO; +import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgImageDatasetDTO; +import ch.systemsx.cisd.openbis.plugin.screening.shared.imaging.dataaccess.ImgImageZoomLevelDTO; + +/** + * + * + * @author Franz-Josef Elmer + */ +public class ImageSizeFeedingMaintenanceTaskTest extends AssertJUnit +{ + private static final long CONTAINER_ID = 42L; + + private static final class MockAbsoluteImageReference extends AbsoluteImageReference + { + private final int width; + private final int height; + + public MockAbsoluteImageReference(int width, int height) + { + super(null, null, null, null, new RequestedImageSize(null, false), null, + new ImageTransfomationFactories(), null); + this.width = width; + this.height = height; + } + + @Override + public BufferedImage getUnchangedImage() + { + if (width < 0) + { + throw new RuntimeException("Negative width: " + width); + } + return new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + } + } + + private BufferedAppender logRecorder; + private Mockery context; + private IImagingQueryDAO dao; + private IEncapsulatedOpenBISService service; + private IHierarchicalContentProvider contentProvider; + private ImageSizeFeedingMaintenanceTask maintenanceTask; + private IHierarchicalContent ds1Content; + private IHierarchicalContent ds2Content; + private IHierarchicalContent ds3Content; + private IImagingDatasetLoader imageLoader1; + private IImagingDatasetLoader imageLoader2; + private IImagingDatasetLoader imageLoader3; + + @BeforeMethod + public void setUp() + { + logRecorder = new BufferedAppender(); + context = new Mockery(); + dao = context.mock(IImagingQueryDAO.class); + service = context.mock(IEncapsulatedOpenBISService.class); + contentProvider = context.mock(IHierarchicalContentProvider.class); + ds1Content = context.mock(IHierarchicalContent.class, "ds1"); + ds2Content = context.mock(IHierarchicalContent.class, "ds2"); + ds3Content = context.mock(IHierarchicalContent.class, "ds3"); + imageLoader1 = context.mock(IImagingDatasetLoader.class, "ds1-loader"); + imageLoader2 = context.mock(IImagingDatasetLoader.class, "ds2-loader"); + imageLoader3 = context.mock(IImagingDatasetLoader.class, "ds3-loader"); + final Map<String, IImagingDatasetLoader> loaderMap = new HashMap<String, IImagingDatasetLoader>(); + loaderMap.put("ds1", imageLoader1); + loaderMap.put("ds2", imageLoader2); + loaderMap.put("ds3", imageLoader3); + maintenanceTask = new ImageSizeFeedingMaintenanceTask(dao, service, contentProvider) + { + @Override + protected IImagingDatasetLoader createImageLoader(String dataSetCode, + IHierarchicalContent content) + { + IImagingDatasetLoader loader = loaderMap.get(dataSetCode); + if (loader == null) + { + fail("No loader for data set " + dataSetCode); + } + return loader; + } + }; + assertEquals(true, maintenanceTask.requiresDataStoreLock()); + } + + @AfterMethod + public void tearDown() + { + logRecorder.reset(); + context.assertIsSatisfied(); + } + + @Test + public void testNonMatchingOrUnknownDataSets() + { + final SimpleDataSetInformationDTO ds0 = dataSet("ds0", "HCS_ANALYSIS"); + final SimpleDataSetInformationDTO ds1 = dataSet("ds1", "HCS_IMAGE_ANALYSIS"); + context.checking(new Expectations() + { + { + one(service).listDataSets(); + will(returnValue(Arrays.asList(ds0, ds1))); + + one(contentProvider).asContent(ds1.getDataSetCode()); + will(returnValue(ds1Content)); + + one(dao).tryGetImageDatasetByPermId(ds1.getDataSetCode()); + will(returnValue(null)); + + one(ds1Content).close(); + } + }); + + maintenanceTask.execute(); + + assertEquals("Scan 2 data sets.\n" + + "0 original image sizes and 0 thumbnail image sizes are added to the database.", + logRecorder.getLogContent()); + context.assertIsSatisfied(); + } + + @Test + public void testImageDataSets() + { + final SimpleDataSetInformationDTO ds1 = dataSet("ds1", "HCS_IMAGE_ALPHA"); + final SimpleDataSetInformationDTO ds2 = dataSet("ds2", "HCS_IMAGE_RAW"); + final SimpleDataSetInformationDTO ds3 = dataSet("ds3", "HCS_IMAGE"); + final RecordingMatcher<ImgImageZoomLevelDTO> zoomLevelRecorder = + new RecordingMatcher<ImgImageZoomLevelDTO>(); + context.checking(new Expectations() + { + { + one(service).listDataSets(); + will(returnValue(Arrays.asList(ds1, ds2, ds3))); + } + }); + prepareListZoomLevels(ds1, ds1Content, new ImgImageZoomLevelDTO("", true, "", 1, 2, 12)); + prepareListZoomLevels(ds2, ds2Content); + prepareForTryFindAnyOriginal(ds2, imageLoader2, new MockAbsoluteImageReference(144, 89)); + prepareForAddZoomLevel(zoomLevelRecorder); + prepareForTryFindAnyThumbnail(ds2, imageLoader2, null); + prepareForCommit(); + prepareListZoomLevels(ds3, ds3Content); + prepareForTryFindAnyOriginal(ds3, imageLoader3, null); + prepareForTryFindAnyThumbnail(ds3, imageLoader3, new MockAbsoluteImageReference(21, 34)); + prepareForAddZoomLevel(zoomLevelRecorder); + prepareForCommit(); + + maintenanceTask.execute(); + + List<ImgImageZoomLevelDTO> zoomLevels = zoomLevelRecorder.getRecordedObjects(); + assertEquals("[ImgImageZoomLevelDTO{physicalDatasetPermId=ds2,isOriginal=true," + + "containerDatasetId=99715,rootPath=,width=144,height=89,id=0}, " + + "ImgImageZoomLevelDTO{physicalDatasetPermId=ds3,isOriginal=false," + + "containerDatasetId=99716,rootPath=,width=21,height=34,id=0}]", + zoomLevels.toString()); + assertEquals("Scan 3 data sets.\n" + "Original size 144x89 added for data set ds2\n" + + "Thumbnail size 21x34 added for data set ds3\n" + + "1 original image sizes and 1 thumbnail image sizes are added to the database.", + logRecorder.getLogContent()); + context.assertIsSatisfied(); + } + + @Test + public void testExceptionHandling() + { + final SimpleDataSetInformationDTO ds1 = dataSet("ds1", "HCS_IMAGE_ALPHA"); + final SimpleDataSetInformationDTO ds2 = dataSet("ds2", "HCS_IMAGE_RAW"); + final RecordingMatcher<ImgImageZoomLevelDTO> zoomLevelRecorder = + new RecordingMatcher<ImgImageZoomLevelDTO>(); + context.checking(new Expectations() + { + { + one(service).listDataSets(); + will(returnValue(Arrays.asList(ds1, ds2))); + } + }); + prepareListZoomLevels(ds1, ds1Content); + prepareForTryFindAnyOriginal(ds1, imageLoader1, new MockAbsoluteImageReference(-1, 0)); + prepareForRollback(); + prepareListZoomLevels(ds2, ds2Content); + prepareForTryFindAnyOriginal(ds2, imageLoader2, new MockAbsoluteImageReference(1, 2)); + prepareForAddZoomLevel(zoomLevelRecorder); + prepareForTryFindAnyThumbnail(ds2, imageLoader2, new MockAbsoluteImageReference(-13, 0)); + prepareForRollback(); + + maintenanceTask.execute(); + + List<ImgImageZoomLevelDTO> zoomLevels = zoomLevelRecorder.getRecordedObjects(); + assertEquals("[ImgImageZoomLevelDTO{physicalDatasetPermId=ds2,isOriginal=true," + + "containerDatasetId=99715,rootPath=,width=1,height=2,id=0}]", + zoomLevels.toString()); + assertEquals("Scan 2 data sets.\n" + + "2 exceptions occured:\n" + + "Data set ds1: java.lang.RuntimeException: Negative width: -1\n" + + "Data set ds2: java.lang.RuntimeException: Negative width: -13\n" + + "0 original image sizes and 0 thumbnail image sizes are added to the database.", + logRecorder.getLogContent()); + context.assertIsSatisfied(); + } + + private void prepareListZoomLevels(final SimpleDataSetInformationDTO dataSet, + final IHierarchicalContent content, final ImgImageZoomLevelDTO... zoomLevels) + { + context.checking(new Expectations() + { + { + String dataSetCode = dataSet.getDataSetCode(); + one(contentProvider).asContent(dataSetCode); + will(returnValue(content)); + + one(dao).tryGetImageDatasetByPermId(dataSetCode); + ImgImageDatasetDTO imageDataSet = + new ImgImageDatasetDTO(dataSetCode, null, null, CONTAINER_ID, false, + null, null); + imageDataSet.setId(dataSetCode.hashCode()); + will(returnValue(imageDataSet)); + + one(dao).listImageZoomLevels(imageDataSet.getId()); + will(returnValue(Arrays + .asList(zoomLevels))); + + one(content).close(); + } + }); + } + + private void prepareForTryFindAnyOriginal(final SimpleDataSetInformationDTO dataSet, + final IImagingDatasetLoader loader, final AbsoluteImageReference originalImageOrNull) + { + context.checking(new Expectations() + { + { + one(loader).tryFindAnyOriginalImage(); + will(returnValue(originalImageOrNull)); + } + }); + } + + + private void prepareForTryFindAnyThumbnail(final SimpleDataSetInformationDTO dataSet, + final IImagingDatasetLoader loader, final AbsoluteImageReference thumbnailOrNull) + { + context.checking(new Expectations() + { + { + one(loader).tryFindAnyThumbnail(); + will(returnValue(thumbnailOrNull)); + } + }); + } + + private void prepareForAddZoomLevel( + final RecordingMatcher<ImgImageZoomLevelDTO> zoomLevelRecorder) + { + context.checking(new Expectations() + { + { + one(dao).addImageZoomLevel(with(zoomLevelRecorder)); + } + }); + } + + private void prepareForCommit() + { + context.checking(new Expectations() + { + { + one(dao).commit(); + } + }); + } + + private void prepareForRollback() + { + context.checking(new Expectations() + { + { + one(dao).rollback(); + } + }); + } + + private SimpleDataSetInformationDTO dataSet(String code, String type) + { + SimpleDataSetInformationDTO dataSet = new SimpleDataSetInformationDTO(); + dataSet.setDataSetCode(code); + dataSet.setDataSetType(type); + return dataSet; + } +} -- GitLab