Commit 68f96741 authored by Daniel Gerhardt's avatar Daniel Gerhardt

Make use of ScriptEngine for design doc creation

Java's ScriptEnging is used to transform design doc JS objects into
valid JSON. A custom CouchDbConnector has been implemented to handle
this transformation and create the design docs in the database at
startup.
parent d3211f7c
......@@ -26,13 +26,14 @@ import de.thm.arsnova.connector.client.ConnectorClientImpl;
import de.thm.arsnova.entities.serialization.CouchDbDocumentModule;
import de.thm.arsnova.entities.serialization.CouchDbObjectMapperFactory;
import de.thm.arsnova.entities.serialization.View;
import de.thm.arsnova.persistance.couchdb.InitializingCouchDbConnector;
import de.thm.arsnova.socket.ARSnovaSocket;
import de.thm.arsnova.socket.ARSnovaSocketIOServer;
import de.thm.arsnova.web.CacheControlInterceptorHandler;
import de.thm.arsnova.web.CorsFilter;
import de.thm.arsnova.web.DeprecatedApiInterceptorHandler;
import de.thm.arsnova.web.ResponseInterceptorHandler;
import org.ektorp.impl.StdCouchDbConnector;
import org.ektorp.CouchDbConnector;
import org.ektorp.impl.StdCouchDbInstance;
import org.ektorp.spring.HttpClientFactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
......@@ -246,8 +247,8 @@ public class AppConfig extends WebMvcConfigurerAdapter {
}
@Bean
public StdCouchDbConnector couchDbConnector() throws Exception {
return new StdCouchDbConnector(couchDbName, couchDbInstance(), new CouchDbObjectMapperFactory());
public CouchDbConnector couchDbConnector() throws Exception {
return new InitializingCouchDbConnector(couchDbName, couchDbInstance(), new CouchDbObjectMapperFactory());
}
@Bean
......
package de.thm.arsnova.persistance.couchdb;
import com.fasterxml.jackson.core.JsonProcessingException;
import org.ektorp.CouchDbInstance;
import org.ektorp.impl.ObjectMapperFactory;
import org.ektorp.impl.StdCouchDbConnector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.util.FileCopyUtils;
import javax.script.Bindings;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
public class InitializingCouchDbConnector extends StdCouchDbConnector implements InitializingBean, ResourceLoaderAware {
private static final Logger logger = LoggerFactory.getLogger(InitializingCouchDbConnector.class);
private final List<Bindings> docs = new ArrayList<>();
private ResourceLoader resourceLoader;
public InitializingCouchDbConnector(String databaseName, CouchDbInstance dbInstance) {
super(databaseName, dbInstance);
}
public InitializingCouchDbConnector(String databaseName, CouchDbInstance dbi, ObjectMapperFactory om) {
super(databaseName, dbi, om);
}
protected void loadDesignDocFiles() throws IOException, ScriptException {
final ScriptEngine engine = new ScriptEngineManager().getEngineByMimeType("application/javascript");
engine.eval(new InputStreamReader(new ClassPathResource("couchdb/jsToJson.js").getInputStream()));
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource[] resources = resolver.getResources("classpath:couchdb/*.design.js");
for (Resource resource : resources) {
logger.debug("Loading CouchDB design doc: {}", resource.getFilename());
String js = FileCopyUtils.copyToString(new InputStreamReader(resource.getInputStream()));
/* Reset designDoc before parsing a new one. */
engine.eval("var designDoc = null;" + js);
Bindings jsonObject = (Bindings) engine.eval("jsToJson(designDoc)");
docs.add(jsonObject);
}
}
protected void createDesignDocs() {
docs.forEach(doc -> {
if (logger.isDebugEnabled()) {
try {
logger.debug("Creating design doc:\n{}", objectMapper.writeValueAsString(doc));
} catch (JsonProcessingException e) {
logger.warn("Failed to serialize design doc.", e);
}
}
String rev = getCurrentRevision((String) doc.get("_id"));
if (rev == null) {
create(doc);
} else {
doc.put("_rev", rev);
update(doc);
}
});
}
@Override
public void afterPropertiesSet() throws Exception {
loadDesignDocFiles();
createDesignDocs();
}
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
}
/**
* Transforms JS map functions of the passed object to strings. JS functions are
* not valid JSON. Additionally, redundant indentation is removed from the
* function string.
*/
function jsToJson(designDoc) {
var views = designDoc.views;
Object.keys(views).forEach(function (viewName) {
views[viewName].map = views[viewName].map.toString().replace(/\n\t{3}(\t*)/g, function (m, p1) {
return "\n" + p1.replace(/\t/g, " ");
});
});
return designDoc;
}
......@@ -30,7 +30,7 @@ var designDoc = {
var value = 0, answers = [], positiveAnswers = [], score = 0;
if (doc.type === "skill_question" && ["school", "flashcard"].indexOf(doc.questionType) === -1) {
if ("freetext" === doc.questionType && !doc.fixedAnswer) { return; }
answers = doc.possibleAnswers.map(function(answer) { return answer.value || 0; });
answers = doc.possibleAnswers.map(function (answer) { return answer.value || 0; });
/* find the maximum value */
if (doc.fixedAnswer) { value = doc.rating; }
else { value = Math.max.apply(null, [0].concat(answers)); }
......@@ -38,9 +38,9 @@ var designDoc = {
if (doc.questionType === "vote" && value === 0) { return; }
/* special case for mc and grid questions: add up all positive answers. */
if (["grid", "mc"].indexOf(doc.questionType) !== -1) {
positiveAnswers = answers.filter(function(val) { return val >= 0; });
positiveAnswers = answers.filter(function (val) { return val >= 0; });
if (positiveAnswers.length > 0) {
value = positiveAnswers.reduce(function(prev, cur) { return prev + cur; }, 0);
value = positiveAnswers.reduce(function (prev, cur) { return prev + cur; }, 0);
}
}
emit([doc.sessionId, doc._id], {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment