diff --git a/datastore_server/resource/test-data/JythonBasedAggregationServiceReportingPluginTest/script.py b/datastore_server/resource/test-data/JythonBasedAggregationServiceReportingPluginTest/script.py new file mode 100644 index 0000000000000000000000000000000000000000..5420b4e0c640482320fdc1bf1dc5751633974554 --- /dev/null +++ b/datastore_server/resource/test-data/JythonBasedAggregationServiceReportingPluginTest/script.py @@ -0,0 +1,40 @@ +from ch.systemsx.cisd.openbis.generic.shared.api.v1.dto import SearchCriteria +from ch.systemsx.cisd.openbis.generic.shared.api.v1.dto import SearchSubCriteria +from ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.SearchCriteria import MatchClause +from ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.SearchCriteria import MatchClauseAttribute + +EXPERIMENT = "Experiment" +CODE = "Data Set Code" +NUMBER_OF_FILES = "Number of Files" +NUMBER_OF_PROTEINS = "Number of Proteins" + +def countFiles(node): + sum = 1 + if node.isDirectory(): + for child in node.getChildNodes(): + sum = sum + countFiles(child) + return sum + +def getNumberOfProteins(dataSetCode): + result = queryService.select("protein-db", "select count(*) as count from proteins where data_set = ?{1}", [dataSetCode]) + return result[0].get("count") + +def aggregate(parameters, tableBuilder): + experimentCode = parameters.get('experiment-code') + searchCriteria = SearchCriteria() + subCriteria = SearchCriteria() + subCriteria.addMatchClause(MatchClause.createAttributeMatch(MatchClauseAttribute.CODE, experimentCode)) + searchCriteria.addSubCriteria(SearchSubCriteria.createExperimentCriteria(subCriteria)) + dataSets = searchService.searchForDataSets(searchCriteria) + tableBuilder.addHeader(EXPERIMENT) + tableBuilder.addHeader(CODE) + tableBuilder.addHeader(NUMBER_OF_FILES) + tableBuilder.addHeader(NUMBER_OF_PROTEINS) + for dataSet in dataSets: + dataSetCode = dataSet.getDataSetCode() + content = contentProvider.getContent(dataSetCode) + row = tableBuilder.addRow() + row.setCell(EXPERIMENT, dataSet.experiment.experimentIdentifier) + row.setCell(CODE, dataSetCode) + row.setCell(NUMBER_OF_FILES, countFiles(content.rootNode)) + row.setCell(NUMBER_OF_PROTEINS, getNumberOfProteins(dataSetCode)) diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/jython/PluginScriptRunnerFactory.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/jython/PluginScriptRunnerFactory.java index f2069906e55115a3b2d8718a710fd2b8961c33cd..17d480069e9e96f8f4dccd0d92790b7232b6e06b 100644 --- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/jython/PluginScriptRunnerFactory.java +++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/jython/PluginScriptRunnerFactory.java @@ -161,12 +161,12 @@ public class PluginScriptRunnerFactory implements IPluginScriptRunnerFactory return evaluator; } - private static ISearchService createSearchService() + protected ISearchService createSearchService() { return ServiceProvider.getSearchService(); } - private static IDataSourceQueryService createDataSourceQueryService() + protected IDataSourceQueryService createDataSourceQueryService() { return ServiceProvider.getDataSourceQueryService(); } diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/jython/Utils.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/jython/Utils.java index a41f45632b54457abd5b41d832dfb88cf828c306..3d3f3588b7649dcd9cade9f5c74451783787785a 100644 --- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/jython/Utils.java +++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/jython/Utils.java @@ -18,7 +18,6 @@ package ch.systemsx.cisd.openbis.dss.generic.server.plugins.jython; import org.apache.log4j.Logger; -import ch.systemsx.cisd.common.evaluator.EvaluatorException; import ch.systemsx.cisd.common.exceptions.UserFailureException; import ch.systemsx.cisd.common.utilities.ExceptionUtils; import ch.systemsx.cisd.openbis.generic.shared.basic.dto.TableModel; @@ -39,16 +38,6 @@ class Utils ISimpleTableModelBuilderAdaptor builder = SimpleTableModelBuilderAdaptor.create(); tableModelCreator.create(builder); return (TableModel) builder.getTableModel(); - } catch (EvaluatorException ex) - { - if (null != ex.getCause()) - { - notifyLog.error(createErrorMessage(scriptPath), ex.getCause()); - } else - { - notifyLog.error(createErrorMessage(scriptPath), ex); - } - throw createUserFailureException(ex); } catch (RuntimeException ex) { notifyLog.error(createErrorMessage(scriptPath), ex); @@ -64,7 +53,7 @@ class Utils private static UserFailureException createUserFailureException(RuntimeException ex) { return new UserFailureException("Chosen plugin failed to create a report: " - + ExceptionUtils.getEndOfChain(ex)); + + ExceptionUtils.getEndOfChain(ex), ex); } } diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/AbstractAggregationServiceReportingPlugin.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/AbstractAggregationServiceReportingPlugin.java index 19ab7023898edca899f54ff456d910e53840076a..1480bc984aa9950d0053c11dffb77ff18c363138 100644 --- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/AbstractAggregationServiceReportingPlugin.java +++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/standard/AbstractAggregationServiceReportingPlugin.java @@ -49,13 +49,19 @@ public abstract class AbstractAggregationServiceReportingPlugin extends Abstract public TableModel createReport(List<DatasetDescription> datasets, DataSetProcessingContext context) { - throw new IllegalArgumentException( - "The method createReport is not supported by AGGREGATION_TABLE_MODEL tasks"); + throw createException(); } public LinkModel createLink(DatasetDescription dataset) { - throw new IllegalArgumentException( - "The method createLink is not supported by AGGREGATION_TABLE_MODEL tasks"); + throw createException(); } + + private IllegalArgumentException createException() + { + return new IllegalArgumentException( + "The method createReport is not supported by " + getReportingPluginType() + + " tasks"); + } + } diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/tasks/AbstractPluginTaskFactory.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/tasks/AbstractPluginTaskFactory.java index a52649970aa87c221f7712f772a1fcce36ac21c5..327600f3857c9856061b537226293fad4d433ed5 100644 --- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/tasks/AbstractPluginTaskFactory.java +++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/tasks/AbstractPluginTaskFactory.java @@ -113,7 +113,6 @@ public abstract class AbstractPluginTaskFactory<T> servletPropertiesManager.addServletProperties(label, props); } String pluginKey = sectionProperties.getKey(); - String[] datasetCodes = extractDatasetCodes(pluginProperties); this.className = PropertyUtils.getMandatoryProperty(pluginProperties, CLASS_PROPERTY_NAME); this.instanceParameters = extractInstanceParameters(pluginProperties); @@ -124,11 +123,15 @@ public abstract class AbstractPluginTaskFactory<T> { ReportingPluginType type = ((IReportingPluginTask) pluginInstance).getReportingPluginType(); + String[] datasetCodes = + type == ReportingPluginType.AGGREGATION_TABLE_MODEL ? new String[0] + : extractDatasetCodes(pluginProperties); this.description = DatastoreServiceDescription.reporting(pluginKey, label, datasetCodes, datastoreCode, type); } else { + String[] datasetCodes = extractDatasetCodes(pluginProperties); this.description = DatastoreServiceDescription.processing(pluginKey, label, datasetCodes, datastoreCode); diff --git a/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/jython/JythonBasedAggregationServiceReportingPluginTest.java b/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/jython/JythonBasedAggregationServiceReportingPluginTest.java new file mode 100644 index 0000000000000000000000000000000000000000..492ab1853fb9b83d2426bf1a44f60ac66d18e943 --- /dev/null +++ b/datastore_server/sourceTest/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/jython/JythonBasedAggregationServiceReportingPluginTest.java @@ -0,0 +1,224 @@ +/* + * Copyright 2012 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.plugins.jython; + +import java.io.File; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +import net.lemnik.eodsql.DataSet; + +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.systemsx.cisd.base.tests.AbstractFileSystemTestCase; +import ch.systemsx.cisd.common.exceptions.UserFailureException; +import ch.systemsx.cisd.common.io.hierarchical_content.api.IHierarchicalContent; +import ch.systemsx.cisd.common.io.hierarchical_content.api.IHierarchicalContentNode; +import ch.systemsx.cisd.common.mail.IMailClient; +import ch.systemsx.cisd.common.test.RecordingMatcher; +import ch.systemsx.cisd.etlserver.registrator.api.v1.impl.DataSetImmutable; +import ch.systemsx.cisd.openbis.dss.generic.server.plugins.tasks.IReportingPluginTask; +import ch.systemsx.cisd.openbis.dss.generic.shared.DataSetProcessingContext; +import ch.systemsx.cisd.openbis.dss.generic.shared.IHierarchicalContentProvider; +import ch.systemsx.cisd.openbis.dss.generic.shared.api.internal.v1.IDataSourceQueryService; +import ch.systemsx.cisd.openbis.dss.generic.shared.api.internal.v1.ISearchService; +import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.SearchCriteria; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.TableModel; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.builders.DataSetBuilder; +import ch.systemsx.cisd.openbis.generic.shared.basic.dto.builders.ExperimentBuilder; + +/** + * + * + * @author Franz-Josef Elmer + */ +public class JythonBasedAggregationServiceReportingPluginTest extends AbstractFileSystemTestCase +{ + private Mockery context; + private ISearchService searchService; + private IDataSourceQueryService queryService; + private File store; + private File scriptFolder; + private DataSetProcessingContext processingContext; + private IMailClient mailClient; + private IHierarchicalContentProvider contentProvider; + private IHierarchicalContent content; + private IHierarchicalContentNode rootNode; + private DataSet<?> dbDataSet; + + @BeforeMethod + public void setUpTest() + { + context = new Mockery(); + searchService = context.mock(ISearchService.class); + queryService = context.mock(IDataSourceQueryService.class); + mailClient = context.mock(IMailClient.class); + contentProvider = context.mock(IHierarchicalContentProvider.class); + content = context.mock(IHierarchicalContent.class); + rootNode = context.mock(IHierarchicalContentNode.class); + dbDataSet = context.mock(DataSet.class); + processingContext = + new DataSetProcessingContext(contentProvider, null, new HashMap<String, String>(), + mailClient, "test-user"); + store = new File(workingDirectory, "store"); + store.mkdirs(); + scriptFolder = new File("resource/test-data/" + getClass().getSimpleName()); + } + + @AfterMethod + public void afterTest() + { + context.assertIsSatisfied(); + } + + @Test + public void testHappyCase() + { + final RecordingMatcher<SearchCriteria> searchCriteriaRecorder = + new RecordingMatcher<SearchCriteria>(); + context.checking(new Expectations() + { + { + one(searchService).searchForDataSets(with(searchCriteriaRecorder)); + will(returnValue(Arrays.asList(new DataSetImmutable(new DataSetBuilder() + .code("ds1") + .experiment( + new ExperimentBuilder().identifier("/A/B/ABC").getExperiment()) + .getDataSet(), null)))); + + one(contentProvider).asContent("ds1"); + will(returnValue(content)); + + one(content).getRootNode(); + will(returnValue(rootNode)); + + one(rootNode).isDirectory(); + will(returnValue(false)); + + one(content).close(); + + one(queryService).select("protein-db", + "select count(*) as count from proteins where data_set = ?{1}", "ds1"); + will(returnValue(dbDataSet)); + + one(dbDataSet).size(); + will(returnValue(1)); + + one(dbDataSet).get(0); + will(returnValue(new ParametersBuilder().parameter("count", 42L).getParameters())); + } + }); + Map<String, Object> parameters = + new ParametersBuilder().parameter("experiment-code", "ABC").getParameters(); + + IReportingPluginTask plugin = createPlugin("script.py"); + TableModel tableModel = plugin.createAggregationReport(parameters, processingContext); + + assertEquals("SearchCriteria[MATCH_ALL_CLAUSES,[]," + + "[SearchSubCriteria[EXPERIMENT,SearchCriteria[MATCH_ALL_CLAUSES," + + "[SearchCriteria.AttributeMatchClause[ATTRIBUTE,CODE,ABC,EQUALS]],[]]]]]", + searchCriteriaRecorder.recordedObject().toString()); + assertEquals("[Experiment, Data Set Code, Number of Files, Number of Proteins]", tableModel + .getHeader().toString()); + assertEquals("[/A/B/ABC, ds1, 1, 42]", tableModel.getRows().get(0).getValues().toString()); + assertEquals(1, tableModel.getRows().size()); + context.assertIsSatisfied(); + } + + + @Test + public void testSearchServiceThrowsException() + { + final RecordingMatcher<SearchCriteria> searchCriteriaRecorder = + new RecordingMatcher<SearchCriteria>(); + context.checking(new Expectations() + { + { + one(searchService).searchForDataSets(with(searchCriteriaRecorder)); + will(throwException(new IllegalArgumentException("Invalid"))); + } + }); + Map<String, Object> parameters = + new ParametersBuilder().getParameters(); + + IReportingPluginTask plugin = createPlugin("script.py"); + try + { + plugin.createAggregationReport(parameters, processingContext); + fail("UserFailureException expected"); + } catch (UserFailureException ex) + { + assertEquals("Chosen plugin failed to create a report: " + + "java.lang.IllegalArgumentException: Invalid", ex.getMessage()); + String message = ex.getCause().getMessage(); + String prefix = + "Error occured in line 28 of the script when evaluating 'aggregate({}, "; + assertEquals("Message '" + message + "' doesn't starts with '" + prefix + "'.", true, + message.startsWith(prefix)); + } + + assertEquals("SearchCriteria[MATCH_ALL_CLAUSES,[]," + + "[SearchSubCriteria[EXPERIMENT,SearchCriteria[MATCH_ALL_CLAUSES," + + "[SearchCriteria.AttributeMatchClause[ATTRIBUTE,CODE,<null>,EQUALS]],[]]]]]", + searchCriteriaRecorder.recordedObject().toString()); + context.assertIsSatisfied(); + } + + private static final class ParametersBuilder + { + private final Map<String, Object> parameters = new HashMap<String, Object>(); + + public ParametersBuilder parameter(String name, Object value) + { + parameters.put(name, value); + return this; + } + + public Map<String, Object> getParameters() + { + return parameters; + } + } + + private IReportingPluginTask createPlugin(String scriptFile) + { + return new JythonBasedAggregationServiceReportingPlugin(new Properties(), store, + new PluginScriptRunnerFactory(new File(scriptFolder, scriptFile).getPath()) + { + private static final long serialVersionUID = 1L; + + @Override + protected ISearchService createSearchService() + { + return searchService; + } + + @Override + protected IDataSourceQueryService createDataSourceQueryService() + { + return queryService; + } + }); + } + +}