diff --git a/deep_sequencing_unit/.classpath b/deep_sequencing_unit/.classpath index 5d146a2f5be0637210a81eebacb7a32ba8d74414..e3ed3cc42fc79c41dfd9903a3d39f330e08497e3 100644 --- a/deep_sequencing_unit/.classpath +++ b/deep_sequencing_unit/.classpath @@ -18,5 +18,7 @@ <classpathentry kind="lib" path="/libraries/jaxb/jaxb-api.jar" sourcepath="/libraries/jaxb/jaxb-api-src.zip"/> <classpathentry kind="lib" path="/libraries/jaxb/jaxb-impl.jar" sourcepath="/libraries/jaxb/jaxb-impl.src.zip"/> <classpathentry kind="lib" path="/libraries/jaxb/jsr173_1.0_api.jar"/> + <classpathentry kind="lib" path="/libraries/activation/activation.jar"/> + <classpathentry kind="lib" path="/libraries/restrictionchecker/restrictions.jar"/> <classpathentry kind="output" path="targets/classes"/> </classpath> diff --git a/deep_sequencing_unit/source/java/ch/ethz/bsse/cisd/dsu/dss/plugins/DataSetToSOFT.java b/deep_sequencing_unit/source/java/ch/ethz/bsse/cisd/dsu/dss/plugins/DataSetToSOFT.java index a31a7ed0764a9d4e0c0efefbcea557aa087484f4..ab5d32d186f15ffbbd3458d96a3e63b0caaa8fc3 100644 --- a/deep_sequencing_unit/source/java/ch/ethz/bsse/cisd/dsu/dss/plugins/DataSetToSOFT.java +++ b/deep_sequencing_unit/source/java/ch/ethz/bsse/cisd/dsu/dss/plugins/DataSetToSOFT.java @@ -30,6 +30,7 @@ import javax.mail.util.ByteArrayDataSource; import org.apache.commons.io.FileUtils; import org.apache.log4j.Logger; +import ch.rinn.restrictions.Private; import ch.systemsx.cisd.base.exceptions.CheckedExceptionTunnel; import ch.systemsx.cisd.common.exceptions.Status; import ch.systemsx.cisd.common.logging.LogCategory; @@ -58,21 +59,23 @@ import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.SampleIdentifierFa */ public class DataSetToSOFT implements IProcessingPluginTask { - private static final String EXTERNAL_SAMPLE_NAME_PROPERTY = "EXTERNAL_SAMPLE_NAME"; + @Private static final String EXTERNAL_SAMPLE_NAME_PROPERTY = "EXTERNAL_SAMPLE_NAME"; private static final String EMPTY = "<<<NEED_TO_BE_FILLED>>>"; - private static final Template SOFT_FILE_NAME_TEMPLATE = new Template( + @Private + static final Template SOFT_FILE_NAME_TEMPLATE = new Template( "${flow-lane}_${external-sample-name}_SOFT.txt"); - private static final Template E_MAIL_SUBJECT_TEMPLATE = new Template( - "SOFT file for ${external-sample-name}"); - - private static final Template E_MAIL_CONTENT_TEMPLATE = - new Template("Dear User\n\n" - + "Enclosed you will find the SOFT file for ${external-sample-name}.\n" - + "Flow lane: ${flow-lane}\nData Set: ${data-set}"); - + @Private + static final Template E_MAIL_SUBJECT_TEMPLATE = new Template( + "SOFT file for '${external-sample-name}'"); + + @Private + static final Template E_MAIL_CONTENT_TEMPLATE = new Template("Dear User\n\n" + + "Enclosed you will find the SOFT file for '${external-sample-name}'.\n" + + "Flow lane: ${flow-lane}\nData Set: ${data-set}"); + private static final class SOFTBuilder { private final StringBuilder builder = new StringBuilder(); @@ -130,18 +133,26 @@ public class DataSetToSOFT implements IProcessingPluginTask return null; } - private static final Logger operationLog = LogFactory.getLogger(LogCategory.OPERATION, DataSetToSOFT.class); private static final long serialVersionUID = 1L; + private final File storeRoot; - private Map<String, String> translation; + private final Map<String, String> translation; + + private IEncapsulatedOpenBISService service; public DataSetToSOFT(Properties properties, File storeRoot) + { + this(properties, storeRoot, null); + } + + DataSetToSOFT(Properties properties, File storeRoot, IEncapsulatedOpenBISService service) { this.storeRoot = storeRoot; + this.service = service; translation = new HashMap<String, String>(); translation.put("GENOMIC_DNA", "genomic"); translation.put("FRAGMENTED_GENOMIC_DNA", "genomic"); @@ -160,7 +171,6 @@ public class DataSetToSOFT implements IProcessingPluginTask DataSetProcessingContext context) { EMailAddress address = new EMailAddress(context.getUserEmailOrNull()); - IEncapsulatedOpenBISService service = ServiceProvider.getOpenBISService(); ProcessingStatus status = new ProcessingStatus(); for (DatasetDescription datasetDescription : datasets) { @@ -171,7 +181,7 @@ public class DataSetToSOFT implements IProcessingPluginTask { operationLog.info("Create SOFT file for data set " + dataSetCode); } - ExternalData srfDataSet = service.tryGetDataSet(dataSetCode); + ExternalData srfDataSet = getService().tryGetDataSet(dataSetCode); Sample flowLaneSample = getFlowLaneSample(srfDataSet); Sample flowCellSample = getFlowCellSample(flowLaneSample); Sample sequencingSample = getSequencingSample(flowLaneSample); @@ -194,7 +204,7 @@ public class DataSetToSOFT implements IProcessingPluginTask softBuilder.addSampleProperty("biomaterial_provider", sequencingSample, "CONTACT_PERSON_NAME"); softBuilder.addSampleProperty("molecule", sequencingSample, "SEQUENCING_APPLICATION"); softBuilder.addSampleProperty("extract_protocol", sequencingSample, "SAMPLE_EXTRACT_PROTOCOL"); - softBuilder.addSampleProperty("data_processing", sequencingSample, "AMPLE_DATA_PROCESSING"); + softBuilder.addSampleProperty("data_processing", sequencingSample, "SAMPLE_DATA_PROCESSING"); softBuilder.addSampleProperty("library_strategy", sequencingSample, "SEQUENCING_APPLICATION"); softBuilder.addSampleProperty("library_source", sequencingSample, "SAMPLE_KIND", translation); softBuilder.addSampleProperty("library_selection", sequencingSample, "SAMPLE_KIND"); @@ -238,7 +248,8 @@ public class DataSetToSOFT implements IProcessingPluginTask private String createSoftFileName(Sample sequencingSample, Sample flowLaneSample) { Template template = SOFT_FILE_NAME_TEMPLATE.createFreshCopy(); - bindExternalSampleName(template, sequencingSample); + String externalSampleName = tryToGetProperty(sequencingSample, EXTERNAL_SAMPLE_NAME_PROPERTY); + template.bind("external-sample-name", externalSampleName.replace(' ', '_')); template.bind("flow-lane", flowLaneSample.getCode().replace(':', '-')); return template.createText(); } @@ -291,22 +302,19 @@ public class DataSetToSOFT implements IProcessingPluginTask private Sample getFlowLaneSample(ExternalData dataSet) { - IEncapsulatedOpenBISService service = ServiceProvider.getOpenBISService(); SampleIdentifier identifier = SampleIdentifierFactory.parse(dataSet.getSampleIdentifier()); - return service.tryGetSampleWithExperiment(identifier); + return getService().tryGetSampleWithExperiment(identifier); } private Sample getFlowCellSample(Sample flowLaneSample) { - IEncapsulatedOpenBISService service = ServiceProvider.getOpenBISService(); SampleIdentifier identifier = SampleIdentifierFactory.parse(flowLaneSample.getContainer().getIdentifier()); - return service.tryGetSampleWithExperiment(identifier); + return getService().tryGetSampleWithExperiment(identifier); } private Sample getSequencingSample(Sample flowLaneSample) { - IEncapsulatedOpenBISService service = ServiceProvider.getOpenBISService(); - List<Sample> parents = service.listSamples(ListSampleCriteria.createForChild(new TechId(flowLaneSample.getId()))); + List<Sample> parents = getService().listSamples(ListSampleCriteria.createForChild(new TechId(flowLaneSample.getId()))); return parents.get(0); } @@ -321,5 +329,13 @@ public class DataSetToSOFT implements IProcessingPluginTask } } + private IEncapsulatedOpenBISService getService() + { + if (service == null) + { + service = ServiceProvider.getOpenBISService(); + } + return service; + } } diff --git a/deep_sequencing_unit/sourceTest/java/ch/ethz/bsse/cisd/dsu/dss/plugins/DataSetToSOFTTest.java b/deep_sequencing_unit/sourceTest/java/ch/ethz/bsse/cisd/dsu/dss/plugins/DataSetToSOFTTest.java new file mode 100644 index 0000000000000000000000000000000000000000..0233f85afb1bc6acfecf1e608cf617ce62f569d6 --- /dev/null +++ b/deep_sequencing_unit/sourceTest/java/ch/ethz/bsse/cisd/dsu/dss/plugins/DataSetToSOFTTest.java @@ -0,0 +1,255 @@ +/* + * Copyright 2010 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.ethz.bsse.cisd.dsu.dss.plugins; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Properties; + +import javax.activation.DataHandler; +import javax.activation.DataSource; + +import org.apache.commons.io.IOUtils; +import org.apache.log4j.Level; +import org.hamcrest.core.IsNull; +import org.jmock.Expectations; +import org.jmock.Mockery; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import ch.rinn.restrictions.Friend; +import ch.systemsx.cisd.base.tests.AbstractFileSystemTestCase; +import ch.systemsx.cisd.common.filesystem.FileUtilities; +import ch.systemsx.cisd.common.logging.BufferedAppender; +import ch.systemsx.cisd.common.mail.EMailAddress; +import ch.systemsx.cisd.common.mail.IMailClient; +import ch.systemsx.cisd.common.test.RecordingMatcher; +import ch.systemsx.cisd.common.utilities.Template; +import ch.systemsx.cisd.openbis.dss.generic.server.plugins.tasks.IProcessingPluginTask; +import ch.systemsx.cisd.openbis.dss.generic.server.plugins.tasks.ProcessingStatus; +import ch.systemsx.cisd.openbis.dss.generic.shared.DataSetProcessingContext; +import ch.systemsx.cisd.openbis.dss.generic.shared.IEncapsulatedOpenBISService; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.EntityProperty; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ExternalData; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.IEntityProperty; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ListSampleCriteria; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.PropertyType; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Sample; +import ch.systemsx.cisd.openbis.generic.shared.dto.DatasetDescription; +import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.SampleIdentifierFactory; + +/** + * + * + * @author Franz-Josef Elmer + */ +@Friend(toClasses=DataSetToSOFT.class) +public class DataSetToSOFTTest extends AbstractFileSystemTestCase +{ + private static final String USER_EMAIL = "user@ho.me"; + private static final String SRF_FILE_NAME = "sample.srf"; + private static final String SRF_FILE_CONTENT = "hello world!"; + private static final String DATASET_CODE = "ds-1"; + private static final String FLOW_LANE_SAMPLE_CODE = "flow-lane:1"; + + private BufferedAppender logRecorder; + private Mockery context; + private IEncapsulatedOpenBISService service; + private IMailClient mailClient; + private DataSetProcessingContext dataSetProcessingContext; + private IProcessingPluginTask processingPlugin; + + @BeforeMethod + public void beforeMethod() + { + logRecorder = new BufferedAppender("%-5p %c - %m%n", Level.DEBUG); + context = new Mockery(); + service = context.mock(IEncapsulatedOpenBISService.class); + mailClient = context.mock(IMailClient.class); + dataSetProcessingContext = + new DataSetProcessingContext(new HashMap<String, String>(), mailClient, USER_EMAIL); + File testFolder = new File(workingDirectory, "test"); + testFolder.mkdirs(); + File sampleSrfFile = new File(testFolder, SRF_FILE_NAME); + FileUtilities.writeToFile(sampleSrfFile, SRF_FILE_CONTENT); + processingPlugin = new DataSetToSOFT(new Properties(), workingDirectory, service); + } + + @AfterMethod + public void afterMethod() + { + logRecorder.reset(); + // To following line of code should also be called at the end of each test method. + // Otherwise one do not known which test failed. + context.assertIsSatisfied(); + } + + @Test + public void test() throws IOException + { + DatasetDescription datasetDescription = new DatasetDescription(); + datasetDescription.setDatasetCode(DATASET_CODE); + datasetDescription.setDataSetLocation("."); + List<DatasetDescription> dataSets = Arrays.asList(datasetDescription); + final Sample flowLaneSample = sample(42, FLOW_LANE_SAMPLE_CODE); + flowLaneSample.setCode(FLOW_LANE_SAMPLE_CODE); + prepareGetDataSet(DATASET_CODE, flowLaneSample); + Sample flowCellSample = sample(43, "flow-cell"); + addProperty(flowCellSample, "GENOME_ANALYZER", "my-analyzer"); + final Sample sequencingSample = sample(44, "sequencing"); + addProperty(sequencingSample, DataSetToSOFT.EXTERNAL_SAMPLE_NAME_PROPERTY, "my sample"); + addProperty(sequencingSample, "SAMPLE_SOURCE_NAME", "source"); + addProperty(sequencingSample, "NCBI_ORGANISM_TAXONOMY", "organism"); + addProperty(sequencingSample, "CONTACT_PERSON_NAME", "person"); + addProperty(sequencingSample, "SEQUENCING_APPLICATION", "application"); + addProperty(sequencingSample, "SAMPLE_EXTRACT_PROTOCOL", "protocol"); + addProperty(sequencingSample, "SAMPLE_DATA_PROCESSING", "processing"); + addProperty(sequencingSample, "SAMPLE_KIND", "CHIP"); + flowLaneSample.setContainer(flowCellSample); + prepareGetSample(flowLaneSample); + prepareGetSample(flowCellSample); + final RecordingMatcher<ListSampleCriteria> listSampleCriteriaMatcher = new RecordingMatcher<ListSampleCriteria>(); + final RecordingMatcher<String> subjectMatcher = new RecordingMatcher<String>(); + final RecordingMatcher<String> contentMatcher = new RecordingMatcher<String>(); + final RecordingMatcher<String> fileNameMatcher = new RecordingMatcher<String>(); + final RecordingMatcher<DataHandler> attachmentMatcher = new RecordingMatcher<DataHandler>(); + final RecordingMatcher<EMailAddress[]> addressesMatcher = new RecordingMatcher<EMailAddress[]>(); + context.checking(new Expectations() + { + { + one(service).listSamples(with(listSampleCriteriaMatcher)); + will(returnValue(Arrays.asList(sequencingSample))); + + one(mailClient).sendEmailMessageWithAttachment(with(subjectMatcher), + with(contentMatcher), with(fileNameMatcher), with(attachmentMatcher), + with(new IsNull<EMailAddress>()), with(new IsNull<EMailAddress>()), + with(addressesMatcher)); + } + }); + + ProcessingStatus status = processingPlugin.process(dataSets, dataSetProcessingContext); + + assertEquals(0, status.getErrorStatuses().size()); + assertEquals(flowLaneSample.getId(), listSampleCriteriaMatcher.getRecordedObjects().get(0).getChildSampleId().getId()); + Template template = initTemplate(DataSetToSOFT.E_MAIL_SUBJECT_TEMPLATE); + assertEquals("[" + template.createText() + "]", subjectMatcher.getRecordedObjects().toString()); + Template content = initTemplate(DataSetToSOFT.E_MAIL_CONTENT_TEMPLATE); + content.bind("flow-lane", flowLaneSample.getCode()); + content.bind("data-set", DATASET_CODE); + assertEquals("[" + content.createText() + "]", contentMatcher.getRecordedObjects().toString()); + Template fileNameTemplate = DataSetToSOFT.SOFT_FILE_NAME_TEMPLATE.createFreshCopy(); + fileNameTemplate.bind("external-sample-name", "my_sample"); + fileNameTemplate.bind("flow-lane", flowLaneSample.getCode().replace(':', '-')); + assertEquals("[" + fileNameTemplate.createText() + "]", fileNameMatcher.getRecordedObjects().toString()); + List<EMailAddress[]> emailAddresses = addressesMatcher.getRecordedObjects(); + assertEquals(USER_EMAIL, emailAddresses.get(0)[0].tryGetEmailAddress()); + DataSource dataSource = attachmentMatcher.getRecordedObjects().get(0).getDataSource(); + String attachmentContent = getContent(dataSource); + assertEquals("^SAMPLE = my sample\n" + + "!Sample_type = SRA\n" + + "!Sample_title = my sample\n" + + "!Sample_source_name = source\n" + + "!Sample_organism = organism\n" + + "!Sample_characteristics = <<<NEED_TO_BE_FILLED>>>\n" + + "!Sample_biomaterial_provider = person\n" + + "!Sample_molecule = application\n" + + "!Sample_extract_protocol = protocol\n" + + "!Sample_data_processing = processing\n" + + "!Sample_library_strategy = application\n" + + "!Sample_library_source = genomic\n" + + "!Sample_library_selection = CHIP\n" + + "!Sample_instrument_model = my-analyzer\n" + + "!Sample_raw_file_1 = sample.srf\n" + + "!Sample_raw_file_type_1 = srf\n" + + "!Sample_file_checksum_1 = fc3ff98e8c6a0d3087d515c0473f8677\n", attachmentContent); + context.assertIsSatisfied(); + } + + String getContent(DataSource dataSource) throws IOException + { + StringBuilder builder = new StringBuilder(); + @SuppressWarnings("rawtypes") + List fileLines = IOUtils.readLines(dataSource.getInputStream()); + for (Object object : fileLines) + { + builder.append(object).append('\n'); + } + return builder.toString(); + } + + Template initTemplate(Template originalTemplate) + { + Template template = originalTemplate.createFreshCopy(); + template.bind("external-sample-name", "my sample"); + return template; + } + + private void prepareGetDataSet(final String dataSetCode, final Sample flowLaneSample) + { + context.checking(new Expectations() + { + { + one(service).tryGetDataSet(dataSetCode); + ExternalData dataSet = new ExternalData(); + dataSet.setCode(dataSetCode); + dataSet.setSample(flowLaneSample); + will(returnValue(dataSet)); + } + }); + } + + private void prepareGetSample(final Sample sample) + { + context.checking(new Expectations() + { + { + one(service).tryGetSampleWithExperiment(SampleIdentifierFactory.parse(sample.getIdentifier())); + will(returnValue(sample)); + } + }); + } + + private void addProperty(Sample sample, String propertyTypeCode, String value) + { + List<IEntityProperty> properties = sample.getProperties(); + if (properties == null) + { + properties = new ArrayList<IEntityProperty>(); + sample.setProperties(properties); + } + EntityProperty property = new EntityProperty(); + PropertyType propertyType = new PropertyType(); + propertyType.setCode(propertyTypeCode); + property.setPropertyType(propertyType); + property.setValue(value); + properties.add(property); + } + + private Sample sample(long id, String identifier) + { + Sample sample = new Sample(); + sample.setId(id); + sample.setIdentifier(identifier); + return sample; + } +} +