GitLab wird am Montag, den 31. Januar, zwischen 08:00 und 12:00 Uhr wegen wichtigen Wartungsarbeiten nicht zur Verfügung stehen.

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

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