/*
* Copyright (c) 2017 OBiBa. All rights reserved.
*
* This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.obiba.magma.js.validation;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import org.junit.After;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.obiba.core.util.FileUtil;
import org.obiba.magma.Datasource;
import org.obiba.magma.DatasourceFactory;
import org.obiba.magma.MagmaEngine;
import org.obiba.magma.NoSuchVariableException;
import org.obiba.magma.Value;
import org.obiba.magma.ValueSet;
import org.obiba.magma.ValueTable;
import org.obiba.magma.ValueTableWriter;
import org.obiba.magma.Variable;
import org.obiba.magma.VariableValueSourceWrapper;
import org.obiba.magma.datasource.fs.FsDatasource;
import org.obiba.magma.datasource.generated.GeneratedValueTable;
import org.obiba.magma.test.EmbeddedMongoProcessWrapper;
import org.obiba.magma.datasource.mongodb.MongoDBDatasourceFactory;
import org.obiba.magma.js.AbstractJsTest;
import org.obiba.magma.js.JavascriptVariableValueSource;
import org.obiba.magma.js.views.VariablesClause;
import org.obiba.magma.support.DatasourceCopier;
import org.obiba.magma.support.Initialisables;
import org.obiba.magma.type.IntegerType;
import org.obiba.magma.views.DefaultViewManagerImpl;
import org.obiba.magma.views.MemoryViewPersistenceStrategy;
import org.obiba.magma.views.View;
import org.obiba.magma.views.ViewManager;
import org.obiba.magma.xstream.MagmaXStreamExtension;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Stopwatch;
import com.google.common.collect.Lists;
import com.thoughtworks.xstream.XStream;
import static org.fest.assertions.api.Assertions.assertThat;
import static org.junit.Assert.fail;
import static org.obiba.magma.Variable.Builder.newVariable;
@Ignore
@SuppressWarnings({ "PMD.NcssMethodCount", "OverlyLongMethod", "OverlyCoupledClass" })
public class VariableScriptValidatorTest extends AbstractJsTest {
private static final Logger log = LoggerFactory.getLogger(VariableScriptValidatorTest.class);
private static final String MONGO_DB_TEST = "magma-test";
private static final String DATASOURCE = "ds";
private static final String TABLE = "table";
private static final String VARIABLE_AGE = "age";
private static final String VARIABLE_WEIGHT = "weight";
private static final String VARIABLE_HEIGHT = "height";
private ViewManager viewManager;
private EmbeddedMongoProcessWrapper mongo;
private String mongoDbUrl;
@Before
@Override
public void before() {
super.before();
// run test only if MongoDB is running
Assume.assumeTrue(setupMongoDB());
viewManager = new DefaultViewManagerImpl(new MemoryViewPersistenceStrategy());
}
@After
@Override
public void after() {
super.after();
mongo.stop();
}
@Override
protected MagmaEngine newEngine() {
MagmaEngine magmaEngine = super.newEngine();
magmaEngine.extend(new MagmaXStreamExtension());
return magmaEngine;
}
private boolean setupMongoDB() {
try {
mongo = new EmbeddedMongoProcessWrapper();
mongo.start();
mongoDbUrl = "mongodb://" +mongo.getServerSocketAddress() + '/' + MONGO_DB_TEST;
return true;
} catch(Exception e) {
return false;
}
}
private Datasource getTestDatasource() throws IOException {
DatasourceFactory factory = new MongoDBDatasourceFactory(DATASOURCE, mongoDbUrl);
Datasource datasource = factory.create();
List<Variable> variables = Lists.newArrayList( //
newVariable(VARIABLE_AGE, IntegerType.get(), PARTICIPANT).addAttribute("min", "25").addAttribute("max", "90")
.build(), //
newVariable(VARIABLE_WEIGHT, IntegerType.get(), PARTICIPANT).unit("kg").addAttribute("min", "50")
.addAttribute("max", "120").build(), //
newVariable(VARIABLE_HEIGHT, IntegerType.get(), PARTICIPANT).unit("cm").addAttribute("min", "150")
.addAttribute("max", "200").build());
ValueTable generatedValueTable = new GeneratedValueTable(datasource, variables, 10);
Datasource viewAwareDatasource = viewManager.decorate(datasource);
MagmaEngine.get().addDatasource(viewAwareDatasource);
DatasourceCopier.Builder.newCopier().build().copy(generatedValueTable, TABLE, datasource);
return viewAwareDatasource;
}
@Test
public void test_parse_script() throws Exception {
Set<VariableScriptValidator.VariableRefCall> calls = VariableScriptValidator.parseScript(
"$('datasource1:table1.var1') / ($group(\"table1.var2\") * $this('var3') + $this('var3')) + 10 + $var('var4')");
assertThat(calls).isNotNull();
assertThat(calls).hasSize(3);
assertThat(calls).contains(new VariableScriptValidator.VariableRefCall("$", "datasource1:table1.var1"));
assertThat(calls).contains(new VariableScriptValidator.VariableRefCall("$this", "var3"));
assertThat(calls).contains(new VariableScriptValidator.VariableRefCall("$var", "var4"));
}
@Test
public void test_validate_bmi_script() throws Exception {
Datasource datasource = getTestDatasource();
ValueTable table = datasource.getValueTable(TABLE);
Variable weight_in_kg = createIntVariable("weight_in_kg", "$('ds.table:weight')");
Variable height_in_cm = createIntVariable("height_in_cm", "$('ds.table:height')");
Variable height_in_m = createDecimalVariable("height_in_m", "$this('height_in_cm') / 100");
Variable weight_in_lbs = createIntVariable("weight_in_lbs", "$this('weight_in_kg') * 2.20462");
Variable height_in_inches = createIntVariable("height_in_inches", "$this('height_in_cm') * 0.393701");
Variable bmi_metric = createDecimalVariable("bmi_metric",
"$this('weight_in_kg') / ($this('height_in_m') * $this('height_in_m'))");
Variable bmi = createDecimalVariable("bmi",
"$this('weight_in_lbs') / ($this('height_in_inches') * $this('height_in_inches')) * 703");
Collection<Variable> variables = Lists
.newArrayList(weight_in_kg, height_in_cm, height_in_m, bmi_metric, weight_in_lbs, height_in_inches, bmi);
View viewTemplate = View.Builder.newView("view", table).list(new VariablesClause()).build();
try(ValueTableWriter.VariableWriter variableWriter = viewTemplate.getListClause().createWriter()) {
for(Variable variable : variables) {
variableWriter.writeVariable(variable);
}
}
viewManager.addView(DATASOURCE, viewTemplate, null, null);
View view = viewManager.getView(DATASOURCE, "view");
validateJavascriptValueSource(view, "bmi_metric");
validateJavascriptValueSource(view, "bmi");
}
@Test
public void test_validate_with_circular_dependencies() throws Exception {
Datasource datasource = getTestDatasource();
ValueTable table = datasource.getValueTable(TABLE);
Variable varA = createIntVariable("A", "$('ds.view:B') + $('ds.view:F') + $('ds.view:C')");
Variable varB = createIntVariable("B", "$('ds.view:F')");
Variable varC = createIntVariable("C", "$('ds.view:D') * 10");
Variable varD = createIntVariable("D", "$('ds.view:E') + 5");
Variable varE = createIntVariable("E", "$('ds.view:A')");
Variable varF = createIntVariable("F", "10");
View viewTemplate = View.Builder.newView("view", table).list(new VariablesClause()).build();
try(ValueTableWriter.VariableWriter variableWriter = viewTemplate.getListClause().createWriter()) {
variableWriter.writeVariable(varA);
variableWriter.writeVariable(varB);
variableWriter.writeVariable(varC);
variableWriter.writeVariable(varD);
variableWriter.writeVariable(varE);
variableWriter.writeVariable(varF);
}
viewManager.addView(DATASOURCE, viewTemplate, null, null);
View view = viewManager.getView(DATASOURCE, "view");
try {
validateJavascriptValueSource(view, "A");
fail("Should throw CircularVariableDependencyRuntimeException");
} catch(CircularVariableDependencyException e) {
assertThat(e.getVariableRef()).isEqualTo("ds.view:A");
}
}
@Test
public void test_validate() throws Exception {
Datasource datasource = getTestDatasource();
ValueTable table = datasource.getValueTable(TABLE);
Variable varA = createIntVariable("A", "$('ds.view:B') + $('ds.view:C')");
Variable varB = createIntVariable("B", "10");
Variable varC = createIntVariable("C", "$('ds.view:D') * $('ds.view:D')");
Variable varD = createIntVariable("D", "$('ds.view:E') + 5");
Variable varE = createIntVariable("E", "$('ds.view:B')");
View viewTemplate = View.Builder.newView("view", table).list(new VariablesClause()).build();
try(ValueTableWriter.VariableWriter variableWriter = viewTemplate.getListClause().createWriter()) {
variableWriter.writeVariable(varA);
variableWriter.writeVariable(varB);
variableWriter.writeVariable(varC);
variableWriter.writeVariable(varD);
variableWriter.writeVariable(varE);
}
viewManager.addView(DATASOURCE, viewTemplate, null, null);
View view = viewManager.getView(DATASOURCE, "view");
validateJavascriptValueSource(view, "A");
}
@Test
public void test_validate_with_self_reference() throws Exception {
Datasource datasource = getTestDatasource();
ValueTable table = datasource.getValueTable(TABLE);
Variable circular = createIntVariable("circular", "$('ds.view:circular')");
View viewTemplate = View.Builder.newView("view", table).list(new VariablesClause()).build();
try(ValueTableWriter.VariableWriter variableWriter = viewTemplate.getListClause().createWriter()) {
variableWriter.writeVariable(circular);
}
viewManager.addView(DATASOURCE, viewTemplate, null, null);
View view = viewManager.getView(DATASOURCE, "view");
try {
validateJavascriptValueSource(view, "circular");
fail("Should throw CircularVariableDependencyRuntimeException");
} catch(CircularVariableDependencyException e) {
assertThat(e.getVariableRef()).isEqualTo("ds.view:circular");
}
}
@Test
public void test_validate_with_missing_variable() throws Exception {
Datasource datasource = getTestDatasource();
ValueTable table = datasource.getValueTable(TABLE);
Variable var = createIntVariable("var", "$('non-existing')");
View viewTemplate = View.Builder.newView("view", table).list(new VariablesClause()).build();
try(ValueTableWriter.VariableWriter variableWriter = viewTemplate.getListClause().createWriter()) {
variableWriter.writeVariable(var);
}
viewManager.addView(DATASOURCE, viewTemplate, null, null);
View view = viewManager.getView(DATASOURCE, "view");
try {
validateJavascriptValueSource(view, "var");
fail("Should throw NoSuchVariableException");
} catch(NoSuchVariableException e) {
assertThat(e.getName()).isEqualTo("non-existing");
}
try {
for(ValueSet valueSet : view.getValueSets()) {
view.getValue(var, valueSet).getValue();
}
} catch(NoSuchVariableException e) {
assertThat(e.getName()).isEqualTo("non-existing");
}
try {
for(Value value : view.getVariableValueSource("var").asVectorSource()
.getValues(new TreeSet<>(view.getVariableEntities()))) {
value.getValue();
}
} catch(NoSuchVariableException e) {
assertThat(e.getName()).isEqualTo("non-existing");
}
}
@Test
public void test_FNAC() throws Exception {
DatasourceFactory factory = new MongoDBDatasourceFactory(DATASOURCE, mongoDbUrl);
Datasource datasource = factory.create();
Datasource viewAwareDatasource = viewManager.decorate(datasource);
MagmaEngine.get().addDatasource(viewAwareDatasource);
Datasource fsDatasource = new FsDatasource("fs", FileUtil.getFileFromResource("FNAC.zip"));
Initialisables.initialise(fsDatasource);
DatasourceCopier.Builder.newCopier().build().copy(fsDatasource, datasource);
XStream xstream = MagmaEngine.get().getExtension(MagmaXStreamExtension.class).getXStreamFactory().createXStream();
View viewTemplate = (View) xstream.fromXML(FileUtil.getFileFromResource("HOP.xml"));
viewManager.addView(DATASOURCE, viewTemplate, null, null);
View view = viewManager.getView(DATASOURCE, "HOP");
Stopwatch stopwatch = Stopwatch.createUnstarted();
for(Variable variable : view.getVariables()) {
stopwatch.reset().start();
validateJavascriptValueSource(view, variable.getName());
log.debug("Validate {} script in {}", variable.getName(), stopwatch);
}
}
private void validateJavascriptValueSource(ValueTable view, String variableName) {
((JavascriptVariableValueSource) ((VariableValueSourceWrapper) view.getVariableValueSource(variableName))
.getWrapped()).validateScript();
}
}