diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/DataSetProcessingContext.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/DataSetProcessingContext.java new file mode 100644 index 0000000000000000000000000000000000000000..f03705d427ce839e1c0d71df186e118f72ee1512 --- /dev/null +++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/DataSetProcessingContext.java @@ -0,0 +1,62 @@ +/* + * 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.systemsx.cisd.openbis.dss.generic.server; + +import java.util.Map; + +import ch.systemsx.cisd.common.mail.IMailClient; +import ch.systemsx.cisd.openbis.dss.generic.server.plugins.tasks.IProcessingPluginTask; + +/** + * Context for processing data sets by a {@link IProcessingPluginTask}. + * + * @author Franz-Josef Elmer + */ +public class DataSetProcessingContext +{ + private final Map<String, String> parameterBindings; + + private final IMailClient mailClient; + + private final String userEmailOrNull; + + /** + * Creates an instance for specified parameter bindings, e-mail client, and user e-mail address. + */ + public DataSetProcessingContext(Map<String, String> parameterBindings, IMailClient mailClient, + String userEmailOrNull) + { + this.parameterBindings = parameterBindings; + this.mailClient = mailClient; + this.userEmailOrNull = userEmailOrNull; + } + + public final Map<String, String> getParameterBindings() + { + return parameterBindings; + } + + public final IMailClient getMailClient() + { + return mailClient; + } + + public final String getUserEmailOrNull() + { + return userEmailOrNull; + } +} diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/DataStoreService.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/DataStoreService.java index d39fdd8c8df26631d486be081abd54430690b5d9..d7a9c5c53a113a6af2df71f13789538638475862 100644 --- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/DataStoreService.java +++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/DataStoreService.java @@ -288,7 +288,7 @@ public class DataStoreService extends AbstractServiceWithLogger<IDataStoreServic } public ProcessingStatus process(List<DatasetDescription> datasets, - Map<String, String> parameterBindings) + DataSetProcessingContext context) { if (archive) { diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ProcessDatasetsCommand.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ProcessDatasetsCommand.java index 24b23dbb54a3a19ec30809cf7d4bcc100ab3a53f..510b939538b935d2e4152ef3785859664c45ab2b 100644 --- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ProcessDatasetsCommand.java +++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/ProcessDatasetsCommand.java @@ -21,11 +21,16 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import javax.activation.DataHandler; + import org.apache.log4j.Logger; +import ch.systemsx.cisd.common.exceptions.EnvironmentFailureException; import ch.systemsx.cisd.common.exceptions.Status; import ch.systemsx.cisd.common.logging.LogCategory; import ch.systemsx.cisd.common.logging.LogFactory; +import ch.systemsx.cisd.common.mail.EMailAddress; +import ch.systemsx.cisd.common.mail.From; import ch.systemsx.cisd.common.mail.IMailClient; import ch.systemsx.cisd.common.mail.MailClient; import ch.systemsx.cisd.common.mail.MailClientParameters; @@ -57,28 +62,96 @@ public class ProcessDatasetsCommand implements IDataSetCommand private final DatastoreServiceDescription serviceDescription; - private final MailClientParameters mailClientParameters; + private final IMailClient mailClient; public ProcessDatasetsCommand(IProcessingPluginTask task, List<DatasetDescription> datasets, Map<String, String> parameterBindings, String userEmailOrNull, DatastoreServiceDescription serviceDescription, MailClientParameters mailClientParameters) + { + this(task, datasets, parameterBindings, userEmailOrNull, serviceDescription, new MailClient(mailClientParameters)); + } + + ProcessDatasetsCommand(IProcessingPluginTask task, List<DatasetDescription> datasets, + Map<String, String> parameterBindings, String userEmailOrNull, + DatastoreServiceDescription serviceDescription, + IMailClient mailClient) { this.task = task; this.datasets = datasets; this.parameterBindings = parameterBindings; this.userEmailOrNull = userEmailOrNull; this.serviceDescription = serviceDescription; - this.mailClientParameters = mailClientParameters; + this.mailClient = mailClient; + } + + private static final class ProxyMailClient implements IMailClient + { + private final IMailClient mailClient; + + private boolean mailSent; + + ProxyMailClient(IMailClient mailClient){ + this.mailClient = mailClient; + } + + boolean hasMailSent() + { + return mailSent; + } + + public void sendMessage(String subject, String content, String replyToOrNull, + From fromOrNull, String... recipients) throws EnvironmentFailureException + { + mailClient.sendMessage(subject, content, replyToOrNull, fromOrNull, recipients); + mailSent = true; + } + + public void sendEmailMessage(String subject, String content, EMailAddress replyToOrNull, + EMailAddress fromOrNull, EMailAddress... recipients) + throws EnvironmentFailureException + { + mailClient.sendEmailMessage(subject, content, replyToOrNull, fromOrNull, recipients); + mailSent = true; + } + + @SuppressWarnings("deprecation") + public void sendMessageWithAttachment(String subject, String content, String filename, + DataHandler attachmentContent, String replyToOrNull, From fromOrNull, + String... recipients) throws EnvironmentFailureException + { + mailClient.sendMessageWithAttachment(subject, content, filename, attachmentContent, + replyToOrNull, fromOrNull, recipients); + mailSent = true; + } + + public void sendEmailMessageWithAttachment(String subject, String content, String filename, + DataHandler attachmentContent, EMailAddress replyToOrNull, EMailAddress fromOrNull, + EMailAddress... recipients) throws EnvironmentFailureException + { + mailClient.sendEmailMessageWithAttachment(subject, content, filename, + attachmentContent, replyToOrNull, fromOrNull, recipients); + mailSent = true; + } + + public void sendTestEmail() + { + mailClient.sendTestEmail(); + } + } public void execute(File store) { String errorMessageOrNull = null; ProcessingStatus processingStatusOrNull = null; + ProxyMailClient proxyMailClient = new ProxyMailClient(mailClient); try { - processingStatusOrNull = task.process(datasets, parameterBindings); + DataSetProcessingContext context = + new DataSetProcessingContext(parameterBindings, proxyMailClient, + userEmailOrNull); + processingStatusOrNull = task.process(datasets, context); } catch (RuntimeException e) { // exception message should be readable for users @@ -101,7 +174,10 @@ public class ProcessDatasetsCommand implements IDataSetCommand } return; } - createContentAndSendMessage(errorMessageOrNull, processingStatusOrNull); + if (proxyMailClient.hasMailSent() == false || errorMessageOrNull != null) + { + createContentAndSendMessage(errorMessageOrNull, processingStatusOrNull); + } } } @@ -134,7 +210,6 @@ public class ProcessDatasetsCommand implements IDataSetCommand private void sendMessage(String subject, String content, String recipient) { - final IMailClient mailClient = new MailClient(mailClientParameters); mailClient.sendMessage(subject, content, null, null, recipient); } diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/demo/DemoProcessingPlugin.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/demo/DemoProcessingPlugin.java index bd3607465485dfb20eec27329e3d77eb89ed14c3..23bcaa5629f865be54a348bc5b25a6c8c6c41629 100644 --- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/demo/DemoProcessingPlugin.java +++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/demo/DemoProcessingPlugin.java @@ -18,9 +18,9 @@ package ch.systemsx.cisd.openbis.dss.generic.server.plugins.demo; import java.io.File; import java.util.List; -import java.util.Map; import java.util.Properties; +import ch.systemsx.cisd.openbis.dss.generic.server.DataSetProcessingContext; 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.generic.shared.dto.DatasetDescription; @@ -39,7 +39,7 @@ public class DemoProcessingPlugin implements IProcessingPluginTask } public ProcessingStatus process(List<DatasetDescription> datasets, - Map<String, String> parameterBindings) + DataSetProcessingContext context) { System.out.println("Processing of the following datasets has been requested: " + datasets); System.out.println("sleeping for 2 sec"); diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/AbstractDropboxProcessingPlugin.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/AbstractDropboxProcessingPlugin.java index 2fc836e0e3c68a329300c9665d4214fc7d01a276..1c72e76d82a847b42b03cdee2a98bb898de7b286 100644 --- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/AbstractDropboxProcessingPlugin.java +++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/AbstractDropboxProcessingPlugin.java @@ -27,6 +27,7 @@ import ch.systemsx.cisd.common.exceptions.Status; import ch.systemsx.cisd.common.filesystem.FileUtilities; import ch.systemsx.cisd.common.logging.LogCategory; import ch.systemsx.cisd.common.logging.LogFactory; +import ch.systemsx.cisd.openbis.dss.generic.server.DataSetProcessingContext; 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.IPostRegistrationDatasetHandler; @@ -68,12 +69,12 @@ abstract public class AbstractDropboxProcessingPlugin extends AbstractDatastoreP } public ProcessingStatus process(List<DatasetDescription> datasets, - Map<String, String> parameterBindings) + DataSetProcessingContext context) { final ProcessingStatus result = new ProcessingStatus(); for (DatasetDescription dataset : datasets) { - Status status = processDataset(dataset, parameterBindings); + Status status = processDataset(dataset, context.getParameterBindings()); result.addDatasetStatus(dataset, status); } return result; diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/tasks/IProcessingPluginTask.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/tasks/IProcessingPluginTask.java index a0fa2356c92d249c232f29918d756b32a19f2a2b..8f9873e0e01b6f3989b60198e468895c9d76aa4a 100644 --- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/tasks/IProcessingPluginTask.java +++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/tasks/IProcessingPluginTask.java @@ -18,9 +18,8 @@ package ch.systemsx.cisd.openbis.dss.generic.server.plugins.tasks; import java.io.Serializable; import java.util.List; -import java.util.Map; -import ch.systemsx.cisd.openbis.generic.shared.Constants; +import ch.systemsx.cisd.openbis.dss.generic.server.DataSetProcessingContext; import ch.systemsx.cisd.openbis.generic.shared.dto.DatasetDescription; /** @@ -34,12 +33,12 @@ public interface IProcessingPluginTask extends Serializable /** * Processes asynchronously the specified datasets with specified parameter bindings. * - * @param parameterBindings Contains at least the parameter {@link Constants#USER_PARAMETER} with - * the ID of the user who initiated processing. + * @param context Processing context which contains parameter bindings, mail-client, and user + * e-mail address. * @returns {@link ProcessingStatus} of the finished processing with statuses of processing for * all scheduled data sets or null if processing succeeded for all datasets and no * additional information is provided. */ - ProcessingStatus process(List<DatasetDescription> datasets, Map<String, String> parameterBindings); + ProcessingStatus process(List<DatasetDescription> datasets, DataSetProcessingContext context); } diff --git a/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/ProcessDatasetsCommandTest.java b/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/ProcessDatasetsCommandTest.java new file mode 100644 index 0000000000000000000000000000000000000000..8d0e2ac08d4a3dc91e7ad4d693fa2b63340fd898 --- /dev/null +++ b/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/ProcessDatasetsCommandTest.java @@ -0,0 +1,222 @@ +/* + * 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.systemsx.cisd.openbis.dss.generic.server; + +import java.io.IOException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; +import org.hamcrest.core.IsAnything; +import org.hamcrest.core.IsNull; +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.exceptions.Status; +import ch.systemsx.cisd.common.mail.From; +import ch.systemsx.cisd.common.mail.IMailClient; +import ch.systemsx.cisd.common.test.RecordingMatcher; +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.generic.shared.basic.dto.DatastoreServiceDescription; +import ch.systemsx.cisd.openbis.generic.shared.dto.DatasetDescription; + +/** + * + * + * @author Franz-Josef Elmer + */ +public class ProcessDatasetsCommandTest extends AssertJUnit +{ + private static final String EXAMPLE_TASK_LABEL = "My task"; + private static final String E_MAIL = "my@e.mail"; + private static final String MESSAGE = "hello"; + + private Mockery context; + private IProcessingPluginTask task; + private IMailClient mailClient; + private DatasetDescription ds1; + private DatasetDescription ds2; + private ProcessDatasetsCommand command; + private List<DatasetDescription> dataSets; + private Map<String, String> parameterBindings; + private RecordingMatcher<String> subjectRecorder; + private RecordingMatcher<String> contentRecorder; + + @BeforeMethod + public void beforeMethod() throws IOException + { + context = new Mockery(); + task = context.mock(IProcessingPluginTask.class); + mailClient = context.mock(IMailClient.class); + ds1 = new DatasetDescription(); + ds1.setDatasetCode("ds1"); + ds2 = new DatasetDescription(); + ds2.setDatasetCode("ds2"); + parameterBindings = new HashMap<String, String>(); + dataSets = Arrays.<DatasetDescription> asList(ds1, ds2); + command = new ProcessDatasetsCommand(task, dataSets, + parameterBindings, E_MAIL, new DatastoreServiceDescription("MY_TASK", EXAMPLE_TASK_LABEL, + new String[0], "DSS1"), mailClient); + subjectRecorder = new RecordingMatcher<String>(); + contentRecorder = new RecordingMatcher<String>(); + context.checking(new Expectations() + { + { + allowing(mailClient).sendMessage(with(subjectRecorder), with(contentRecorder), + with(new IsNull<String>()), with(new IsNull<From>()), + with(new IsAnything<String[]>())); + } + }); + } + + @AfterMethod + public void afterMethod() + { + // 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 testTaskPartiallyProcessedSuccessfully() + { + context.checking(new Expectations() + { + { + one(task).process(with(dataSets), + with(createDataSetProcessingContext(null))); + ProcessingStatus status = new ProcessingStatus(); + status.addDatasetStatus(ds1, Status.OK); + status.addDatasetStatus(ds2, Status.createError(true, "Oops!")); + will(returnValue(status)); + } + }); + command.execute(null); + + assertEquals("['" + EXAMPLE_TASK_LABEL + "' processing finished]", subjectRecorder + .getRecordedObjects().toString()); + assertEquals("[This is an automatically generated report from the completed processing " + + "of data sets in openBIS.\n" + + "- number of successfully processed data sets: 1. Datasets: ds1\n" + + "- processing of 1 data set(s) failed because: Oops!. Datasets: ds2\n" + "]", + contentRecorder.getRecordedObjects().toString()); + context.assertIsSatisfied(); + } + + @Test + public void testTaskWhichThrowsException() + { + context.checking(new Expectations() + { + { + one(task).process(with(dataSets), with(createDataSetProcessingContext(null))); + will(throwException(new IllegalStateException("illegal state!"))); + } + }); + try + { + command.execute(null); + fail("IllegalStateException expected."); + } catch (IllegalStateException e) + { + assertEquals("illegal state!", e.getMessage()); + } + + assertEquals("['" + EXAMPLE_TASK_LABEL + "' processing failed]", subjectRecorder + .getRecordedObjects().toString()); + assertEquals("['My task' processing failed on 2 data set(s): \nds1,ds2\n\n" + + "Error message:\nillegal state!]", contentRecorder.getRecordedObjects() + .toString()); + context.assertIsSatisfied(); + } + + @Test + public void testProcessingTaskSendsEMail() + { + context.checking(new Expectations() + { + { + one(task) + .process(with(dataSets), with(createDataSetProcessingContext(MESSAGE))); + } + }); + command.execute(null); + + assertEquals("[null]", subjectRecorder.getRecordedObjects().toString()); + assertEquals("[hello]", contentRecorder.getRecordedObjects().toString()); + context.assertIsSatisfied(); + } + + @Test + public void testProcessingTaskSendsEMailAndFails() + { + context.checking(new Expectations() + { + { + one(task) + .process(with(dataSets), with(createDataSetProcessingContext(MESSAGE))); + will(throwException(new RuntimeException())); + } + }); + try + { + command.execute(null); + fail("RuntimeException expected"); + } catch (RuntimeException e) + { + assertEquals(null, e.getMessage()); + } + + assertEquals("[null, 'My task' processing failed]", subjectRecorder.getRecordedObjects() + .toString()); + assertEquals("[hello, 'My task' processing failed on 2 data set(s): \nds1,ds2\n\n" + + "Error message:\n]", contentRecorder.getRecordedObjects().toString()); + context.assertIsSatisfied(); + } + + private BaseMatcher<DataSetProcessingContext> createDataSetProcessingContext( + final String eMailMessageOrNull) + { + return new BaseMatcher<DataSetProcessingContext>() + { + public boolean matches(Object item) + { + DataSetProcessingContext processingContext = (DataSetProcessingContext) item; + assertEquals(parameterBindings, processingContext.getParameterBindings()); + assertEquals(E_MAIL, processingContext.getUserEmailOrNull()); + IMailClient client = processingContext.getMailClient(); + if (eMailMessageOrNull != null) + { + client.sendMessage(null, eMailMessageOrNull, null, null); + } + return true; + } + + public void describeTo(Description description) + { + } + }; + } +} diff --git a/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/DataSetCopierForUsersTest.java b/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/DataSetCopierForUsersTest.java index 0a3b8158076a8bde444f675f04a73d2e4b6ced7d..96e3b038321ddc89f4bc5dba0883912e744f7031 100644 --- a/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/DataSetCopierForUsersTest.java +++ b/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/DataSetCopierForUsersTest.java @@ -24,6 +24,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Properties; import org.jmock.Expectations; @@ -38,6 +39,7 @@ import ch.systemsx.cisd.base.tests.AbstractFileSystemTestCase; import ch.systemsx.cisd.common.exceptions.Status; import ch.systemsx.cisd.common.filesystem.IPathCopier; import ch.systemsx.cisd.common.filesystem.ssh.ISshCommandExecutor; +import ch.systemsx.cisd.openbis.dss.generic.server.DataSetProcessingContext; import ch.systemsx.cisd.openbis.dss.generic.server.plugins.tasks.ProcessingStatus; import ch.systemsx.cisd.openbis.generic.shared.Constants; import ch.systemsx.cisd.openbis.generic.shared.dto.DatasetDescription; @@ -74,7 +76,7 @@ public class DataSetCopierForUsersTest extends AbstractFileSystemTestCase private File dsData; - private HashMap<String, String> parameterBindings; + private DataSetProcessingContext dataSetProcessingContext; @BeforeMethod public void beforeMethod() throws IOException @@ -105,8 +107,9 @@ public class DataSetCopierForUsersTest extends AbstractFileSystemTestCase ds1Folder.mkdirs(); dsData = new File(ds1Folder, "data.txt"); dsData.createNewFile(); - parameterBindings = new HashMap<String, String>(); + Map<String, String> parameterBindings = new HashMap<String, String>(); parameterBindings.put(Constants.USER_PARAMETER, USER_ID); + dataSetProcessingContext = new DataSetProcessingContext(parameterBindings, null, null); } @AfterMethod @@ -134,7 +137,7 @@ public class DataSetCopierForUsersTest extends AbstractFileSystemTestCase new DataSetCopierForUsers(properties, storeRoot, pathFactory, sshFactory); ProcessingStatus processingStatus = - dataSetCopier.process(Arrays.asList(ds), parameterBindings); + dataSetCopier.process(Arrays.asList(ds), dataSetProcessingContext); assertNoErrors(processingStatus); assertSuccessful(processingStatus, ds); @@ -158,7 +161,7 @@ public class DataSetCopierForUsersTest extends AbstractFileSystemTestCase new DataSetCopierForUsers(properties, storeRoot, pathFactory, sshFactory); ProcessingStatus processingStatus = - dataSetCopier.process(Arrays.asList(ds), parameterBindings); + dataSetCopier.process(Arrays.asList(ds), dataSetProcessingContext); assertNoErrors(processingStatus); assertSuccessful(processingStatus, ds);