Commit 0ae62670 authored by Daniel Gerhardt's avatar Daniel Gerhardt

Merge branch 'db-ektorp' into 'master'

Migration to Ektorp and refactoring of the database layer

A summary of the changes:
* CouchDB4J has been replaced by Ektorp.
* CouchDBDao has been split up into multiple repositories: One for each
  entity.
* Jackson is used instead of JSON-lib for POJO <-> JSON
  (de)serialization.
    * Jackson's `@JsonView`'s are used to mark which properties are
      (de)serialized for the API and/or CouchDB
    * A lot of persistence code could be simplified since serialization
      is handled automatically by the `ObjectMapper`.
* Entities and related Classes have been renamed:
    * `Content` <- `Question` / `SkillQuestion` / `LecturerQuestion`
    * `Comment` <- `FeedbackQuestion` / `AudienceQuestion`
* CouchDB design docs can now be automatically created by the backend if
  necessary. They are included as JavaScript code which is parsed and
  transformed to JSON at startup using Java's `ScriptEngine`.
* `MediaType`s for API versioning have been introduced:
    * `vnd.de.thm.arsnova.v2+json`: the format as used in ARSnova 2.x
    * `vnd.de.thm.arsnova.v3+json`:
        * ISO 8601 dates instead of timestamps
        * `id` instead of `_id`, `revision` instead of `_rev`
* `api.indent-response-body` can be set in configuration for pretty
  responses for API debugging.
* Empty (e.g. `null`) entity properties are ignored for serialization

See merge request !68
parents d47c41ff d54b3263
......@@ -121,6 +121,12 @@
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
</dependency>
<!-- While commons-logging is not a required dependency, AJC fails without it. -->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<scope>provided</scope>
</dependency>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
......@@ -186,9 +192,14 @@
<artifactId>log4j</artifactId>
</dependency>
<dependency>
<groupId>de.thm.couchdb4j</groupId>
<artifactId>couchdb4j</artifactId>
<version>0.8-SNAPSHOT</version>
<groupId>org.ektorp</groupId>
<artifactId>org.ektorp</artifactId>
<version>1.4.4</version>
</dependency>
<dependency>
<groupId>org.ektorp</groupId>
<artifactId>org.ektorp.spring</artifactId>
<version>1.4.4</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
......
......@@ -30,11 +30,11 @@ public class CacheBuster implements ICacheBuster, NovaEventVisitor {
@CacheEvict(value = "statistics", allEntries = true)
@Override
public void visit(NewInterposedQuestionEvent event) { }
public void visit(NewCommentEvent event) { }
@CacheEvict(value = "statistics", allEntries = true)
@Override
public void visit(DeleteInterposedQuestionEvent event) { }
public void visit(DeleteCommentEvent event) { }
@Override
public void visit(NewQuestionEvent event) { }
......@@ -51,7 +51,7 @@ public class CacheBuster implements ICacheBuster, NovaEventVisitor {
@Override
public void visit(LockQuestionsEvent lockQuestionsEvent) { }
@CacheEvict(value = "answers", key = "#event.Question")
@CacheEvict(value = "answers", key = "#event.content")
@Override
public void visit(NewAnswerEvent event) { }
......
......@@ -18,16 +18,27 @@
package de.thm.arsnova.config;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import de.thm.arsnova.ImageUtils;
import de.thm.arsnova.connector.client.ConnectorClient;
import de.thm.arsnova.connector.client.ConnectorClientImpl;
import de.thm.arsnova.entities.*;
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.*;
import de.thm.arsnova.persistance.couchdb.*;
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.CouchDbConnector;
import org.ektorp.impl.StdCouchDbInstance;
import org.ektorp.spring.HttpClientFactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.PropertiesFactoryBean;
......@@ -60,6 +71,7 @@ import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
......@@ -114,8 +126,8 @@ public class AppConfig extends WebMvcConfigurerAdapter {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(stringMessageConverter());
converters.add(defaultJsonMessageConverter());
converters.add(apiV2JsonMessageConverter());
converters.add(defaultJsonMessageConverter());
//converters.add(new MappingJackson2XmlHttpMessageConverter(builder.createXmlMapper(true).build()));
}
......@@ -163,6 +175,8 @@ public class AppConfig extends WebMvcConfigurerAdapter {
@Bean
public StringHttpMessageConverter stringMessageConverter() {
StringHttpMessageConverter messageConverter = new StringHttpMessageConverter();
messageConverter.setDefaultCharset(Charset.forName("UTF-8"));
messageConverter.setWriteAcceptCharset(false);
List<MediaType> mediaTypes = new ArrayList<>();
mediaTypes.add(MediaType.TEXT_PLAIN);
messageConverter.setSupportedMediaTypes(mediaTypes);
......@@ -178,7 +192,9 @@ public class AppConfig extends WebMvcConfigurerAdapter {
.defaultViewInclusion(false)
.indentOutput(apiIndent)
.simpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(builder.build());
ObjectMapper mapper = builder.build();
mapper.setConfig(mapper.getSerializationConfig().withView(View.Public.class));
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(mapper);
List<MediaType> mediaTypes = new ArrayList<>();
mediaTypes.add(API_V3_MEDIA_TYPE);
mediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
......@@ -195,8 +211,11 @@ public class AppConfig extends WebMvcConfigurerAdapter {
.defaultViewInclusion(false)
.indentOutput(apiIndent)
.featuresToEnable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.featuresToEnable(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS);
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(builder.build());
.featuresToEnable(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS)
.modules(new CouchDbDocumentModule());
ObjectMapper mapper = builder.build();
mapper.setConfig(mapper.getSerializationConfig().withView(View.Public.class));
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(mapper);
List<MediaType> mediaTypes = new ArrayList<>();
mediaTypes.add(API_V2_MEDIA_TYPE);
mediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
......
package de.thm.arsnova.config;
import de.thm.arsnova.entities.Answer;
import de.thm.arsnova.entities.Comment;
import de.thm.arsnova.entities.Content;
import de.thm.arsnova.entities.DbUser;
import de.thm.arsnova.entities.LogEntry;
import de.thm.arsnova.entities.Motd;
import de.thm.arsnova.entities.MotdList;
import de.thm.arsnova.entities.Session;
import de.thm.arsnova.entities.VisitedSession;
import de.thm.arsnova.entities.serialization.CouchDbObjectMapperFactory;
import de.thm.arsnova.persistance.*;
import de.thm.arsnova.persistance.couchdb.*;
import org.ektorp.CouchDbConnector;
import org.ektorp.impl.StdCouchDbInstance;
import org.ektorp.spring.HttpClientFactoryBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
@Configuration
@Profile("!test")
public class PersistanceConfig {
@Value("${couchdb.name}") private String couchDbName;
@Value("${couchdb.host}") private String couchDbHost;
@Value("${couchdb.port}") private int couchDbPort;
@Bean
public CouchDbConnector couchDbConnector() throws Exception {
return new InitializingCouchDbConnector(couchDbName, couchDbInstance(), new CouchDbObjectMapperFactory());
}
@Bean
public StdCouchDbInstance couchDbInstance() throws Exception {
return new StdCouchDbInstance(couchDbHttpClientFactory().getObject());
}
@Bean
public HttpClientFactoryBean couchDbHttpClientFactory() throws Exception {
final HttpClientFactoryBean factory = new HttpClientFactoryBean();
factory.setHost(couchDbHost);
factory.setPort(couchDbPort);
return factory;
}
@Bean
public LogEntryRepository logEntryRepository() throws Exception {
return new CouchDbLogEntryRepository(LogEntry.class, couchDbConnector(), false);
}
@Bean
public UserRepository userRepository() throws Exception {
return new CouchDbUserRepository(DbUser.class, couchDbConnector(), false);
}
@Bean
public SessionRepository sessionRepository() throws Exception {
return new CouchDbSessionRepository(Session.class, couchDbConnector(), false);
}
@Bean
public CommentRepository commentRepository() throws Exception {
return new CouchDbCommentRepository(Comment.class, couchDbConnector(), false);
}
@Bean
public ContentRepository contentRepository() throws Exception {
return new CouchDbContentRepository(Content.class, couchDbConnector(), false);
}
@Bean
public AnswerRepository answerRepository() throws Exception {
return new CouchDbAnswerRepository(Answer.class, couchDbConnector(), false);
}
@Bean
public MotdRepository motdRepository() throws Exception {
return new CouchDbMotdRepository(Motd.class, couchDbConnector(), false);
}
@Bean
public MotdListRepository motdListRepository() throws Exception {
return new CouchDbMotdListRepository(MotdList.class, couchDbConnector(), false);
}
@Bean
public VisitedSessionRepository visitedSessionRepository() throws Exception {
return new CouchDbVisitedSessionRepository(VisitedSession.class, couchDbConnector(), false);
}
@Bean
public StatisticsRepository statisticsRepository() throws Exception {
return new CouchDbStatisticsRepository(Object.class, couchDbConnector(), false);
}
@Bean
public SessionStatisticsRepository sessionStatisticsRepository() throws Exception {
return new CouchDbSessionStatisticsRepository(Object.class, couchDbConnector(), false);
}
}
......@@ -17,10 +17,10 @@
*/
package de.thm.arsnova.controller;
import de.thm.arsnova.entities.InterposedReadingCount;
import de.thm.arsnova.entities.transport.InterposedQuestion;
import de.thm.arsnova.entities.CommentReadingCount;
import de.thm.arsnova.entities.transport.Comment;
import de.thm.arsnova.exceptions.BadRequestException;
import de.thm.arsnova.services.IQuestionService;
import de.thm.arsnova.services.IContentService;
import de.thm.arsnova.web.DeprecatedApi;
import de.thm.arsnova.web.Pagination;
import io.swagger.annotations.Api;
......@@ -41,50 +41,50 @@ import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* Handles requests related to audience questions, which are also called interposed or feedback questions.
* Handles requests related to comments.
*/
@RestController
@RequestMapping("/audiencequestion")
@Api(value = "/audiencequestion", description = "the Audience Question API")
public class AudienceQuestionController extends PaginationController {
@Api(value = "/audiencequestion", description = "the Audience Content API")
public class CommentController extends PaginationController {
@Autowired
private IQuestionService questionService;
private IContentService contentService;
@ApiOperation(value = "Count all the questions in current session",
@ApiOperation(value = "Count all the comments in current session",
nickname = "getAudienceQuestionCount")
@RequestMapping(value = "/count", method = RequestMethod.GET)
@DeprecatedApi
@Deprecated
public int getInterposedCount(@ApiParam(value = "Session-Key from current session", required = true) @RequestParam final String sessionkey) {
return questionService.getInterposedCount(sessionkey);
return contentService.getInterposedCount(sessionkey);
}
@ApiOperation(value = "count all unread interposed questions",
@ApiOperation(value = "count all unread comments",
nickname = "getUnreadInterposedCount")
@RequestMapping(value = "/readcount", method = RequestMethod.GET)
@DeprecatedApi
@Deprecated
public InterposedReadingCount getUnreadInterposedCount(@ApiParam(value = "Session-Key from current session", required = true) @RequestParam("sessionkey") final String sessionkey, String user) {
return questionService.getInterposedReadingCount(sessionkey, user);
public CommentReadingCount getUnreadInterposedCount(@ApiParam(value = "Session-Key from current session", required = true) @RequestParam("sessionkey") final String sessionkey, String user) {
return contentService.getInterposedReadingCount(sessionkey, user);
}
@ApiOperation(value = "Retrieves all Interposed Questions for a Session",
@ApiOperation(value = "Retrieves all Comments for a Session",
nickname = "getInterposedQuestions")
@RequestMapping(value = "/", method = RequestMethod.GET)
@Pagination
public List<InterposedQuestion> getInterposedQuestions(@ApiParam(value = "Session-Key from current session", required = true) @RequestParam final String sessionkey) {
return InterposedQuestion.fromList(questionService.getInterposedQuestions(sessionkey, offset, limit));
public List<Comment> getInterposedQuestions(@ApiParam(value = "Session-Key from current session", required = true) @RequestParam final String sessionkey) {
return Comment.fromList(contentService.getInterposedQuestions(sessionkey, offset, limit));
}
@ApiOperation(value = "Retrieves an InterposedQuestion",
@ApiOperation(value = "Retrieves an Comment",
nickname = "getInterposedQuestion")
@RequestMapping(value = "/{questionId}", method = RequestMethod.GET)
public InterposedQuestion getInterposedQuestion(@ApiParam(value = "ID of the question that needs to be deleted", required = true) @PathVariable final String questionId) {
return new InterposedQuestion(questionService.readInterposedQuestion(questionId));
public Comment getInterposedQuestion(@ApiParam(value = "ID of the Comment that needs to be deleted", required = true) @PathVariable final String questionId) {
return new Comment(contentService.readInterposedQuestion(questionId));
}
@ApiOperation(value = "Creates a new Interposed Question for a Session and returns the InterposedQuestion's data",
@ApiOperation(value = "Creates a new Comment for a Session and returns the Comment's data",
nickname = "postInterposedQuestion")
@ApiResponses(value = {
@ApiResponse(code = 400, message = HTML_STATUS_400)
......@@ -93,19 +93,19 @@ public class AudienceQuestionController extends PaginationController {
@ResponseStatus(HttpStatus.CREATED)
public void postInterposedQuestion(
@ApiParam(value = "Session-Key from current session", required = true) @RequestParam final String sessionkey,
@ApiParam(value = "the body from the new question", required = true) @RequestBody final de.thm.arsnova.entities.InterposedQuestion question
@ApiParam(value = "the body from the new comment", required = true) @RequestBody final de.thm.arsnova.entities.Comment comment
) {
if (questionService.saveQuestion(question)) {
if (contentService.saveQuestion(comment)) {
return;
}
throw new BadRequestException();
}
@ApiOperation(value = "Deletes an InterposedQuestion",
@ApiOperation(value = "Deletes a Comment",
nickname = "deleteInterposedQuestion")
@RequestMapping(value = "/{questionId}", method = RequestMethod.DELETE)
public void deleteInterposedQuestion(@ApiParam(value = "ID of the question that needs to be deleted", required = true) @PathVariable final String questionId) {
questionService.deleteInterposedQuestion(questionId);
public void deleteInterposedQuestion(@ApiParam(value = "ID of the comment that needs to be deleted", required = true) @PathVariable final String questionId) {
contentService.deleteInterposedQuestion(questionId);
}
}
......@@ -19,12 +19,12 @@ package de.thm.arsnova.controller;
import de.thm.arsnova.PaginationListDecorator;
import de.thm.arsnova.entities.Answer;
import de.thm.arsnova.entities.Question;
import de.thm.arsnova.entities.Content;
import de.thm.arsnova.exceptions.BadRequestException;
import de.thm.arsnova.exceptions.ForbiddenException;
import de.thm.arsnova.exceptions.NoContentException;
import de.thm.arsnova.exceptions.NotFoundException;
import de.thm.arsnova.services.IQuestionService;
import de.thm.arsnova.services.IContentService;
import de.thm.arsnova.web.DeprecatedApi;
import de.thm.arsnova.web.Pagination;
import io.swagger.annotations.Api;
......@@ -52,9 +52,9 @@ import java.util.List;
@RestController
@RequestMapping("/lecturerquestion")
@Api(value = "/lecturerquestion", description = "Operations for Lecture Questions")
public class LecturerQuestionController extends PaginationController {
public class ContentController extends PaginationController {
@Autowired
private IQuestionService questionService;
private IContentService contentService;
@ApiOperation(value = "Get question with provided question Id",
nickname = "getQuestion")
......@@ -62,56 +62,56 @@ public class LecturerQuestionController extends PaginationController {
@ApiResponse(code = 404, message = HTML_STATUS_404)
})
@RequestMapping(value = "/{questionId}", method = RequestMethod.GET)
public Question getQuestion(@PathVariable final String questionId) {
final Question question = questionService.getQuestion(questionId);
if (question != null) {
return question;
public Content getQuestion(@PathVariable final String questionId) {
final Content content = contentService.getQuestion(questionId);
if (content != null) {
return content;
}
throw new NotFoundException();
}
@ApiOperation(value = "Post provided question",
@ApiOperation(value = "Post provided content",
nickname = "postQuestion")
@ApiResponses(value = {
@ApiResponse(code = 400, message = HTML_STATUS_400)
})
@RequestMapping(value = "/", method = RequestMethod.POST)
@ResponseStatus(HttpStatus.CREATED)
public Question postQuestion(@RequestBody final Question question) {
if (questionService.saveQuestion(question) != null) {
return question;
public Content postQuestion(@RequestBody final Content content) {
if (contentService.saveQuestion(content) != null) {
return content;
}
throw new BadRequestException();
}
@ApiOperation(value = "Post provided questions", nickname = "bulkPostQuestions")
@ApiOperation(value = "Post provided contents", nickname = "bulkPostQuestions")
@ApiResponses(value = {
@ApiResponse(code = 400, message = HTML_STATUS_400)
})
@RequestMapping(value = "/bulk", method = RequestMethod.POST)
@ResponseStatus(HttpStatus.CREATED)
public List<Question> bulkPostQuestions(@RequestBody final List<Question> questions) {
for (final Question question : questions) {
if (questionService.saveQuestion(question) == null) {
public List<Content> bulkPostQuestions(@RequestBody final List<Content> contents) {
for (final Content content : contents) {
if (contentService.saveQuestion(content) == null) {
throw new BadRequestException();
}
}
return questions;
return contents;
}
@ApiOperation(value = "Update the question, identified by provided id, with the provided question in the Request Body",
@ApiOperation(value = "Update the content, identified by provided id, with the provided content in the Request Body",
nickname = "updateQuestion")
@ApiResponses(value = {
@ApiResponse(code = 400, message = HTML_STATUS_400)
})
@RequestMapping(value = "/{questionId}", method = RequestMethod.PUT)
public Question updateQuestion(
public Content updateQuestion(
@PathVariable final String questionId,
@RequestBody final Question question
@RequestBody final Content content
) {
try {
return questionService.update(question);
return contentService.update(content);
} catch (final Exception e) {
throw new BadRequestException();
}
......@@ -126,9 +126,9 @@ public class LecturerQuestionController extends PaginationController {
) {
if (fcImage) {
return questionService.getQuestionFcImage(questionId);
return contentService.getQuestionFcImage(questionId);
} else {
return questionService.getQuestionImage(questionId);
return contentService.getQuestionImage(questionId);
}
}
......@@ -139,9 +139,9 @@ public class LecturerQuestionController extends PaginationController {
) {
if (time == 0) {
questionService.startNewPiRound(questionId, null);
contentService.startNewPiRound(questionId, null);
} else {
questionService.startNewPiRoundDelayed(questionId, time);
contentService.startNewPiRoundDelayed(questionId, time);
}
}
......@@ -151,7 +151,7 @@ public class LecturerQuestionController extends PaginationController {
public void cancelPiRound(
@PathVariable final String questionId
) {
questionService.cancelPiRoundChange(questionId);
contentService.cancelPiRoundChange(questionId);
}
@RequestMapping(value = "/{questionId}/resetpiroundstate", method = RequestMethod.POST)
......@@ -160,7 +160,7 @@ public class LecturerQuestionController extends PaginationController {
public void resetPiQuestion(
@PathVariable final String questionId
) {
questionService.resetPiRoundState(questionId);
contentService.resetPiRoundState(questionId);
}
@ApiOperation(value = "Set voting admission on question, identified by provided id",
......@@ -176,7 +176,7 @@ public class LecturerQuestionController extends PaginationController {
disable = disableVote;
}
questionService.setVotingAdmission(questionId, disable);
contentService.setVotingAdmission(questionId, disable);
}
@ApiOperation(value = "Set voting admission for all questions",
......@@ -189,35 +189,35 @@ public class LecturerQuestionController extends PaginationController {
@RequestParam(value = "preparationquestionsonly", defaultValue = "false", required = false) final boolean preparationQuestionsOnly
) {
boolean disable = false;
List<Question> questions;
List<Content> contents;
if (disableVote != null) {
disable = disableVote;
}
if (lectureQuestionsOnly) {
questions = questionService.getLectureQuestions(sessionkey);
questionService.setVotingAdmissions(sessionkey, disable, questions);
contents = contentService.getLectureQuestions(sessionkey);
contentService.setVotingAdmissions(sessionkey, disable, contents);
} else if (preparationQuestionsOnly) {
questions = questionService.getPreparationQuestions(sessionkey);
questionService.setVotingAdmissions(sessionkey, disable, questions);
contents = contentService.getPreparationQuestions(sessionkey);
contentService.setVotingAdmissions(sessionkey, disable, contents);
} else {
questionService.setVotingAdmissionForAllQuestions(sessionkey, disable);
contentService.setVotingAdmissionForAllQuestions(sessionkey, disable);
}
}
@ApiOperation(value = "Publish a question, identified by provided id and question in Request Body.",
@ApiOperation(value = "Publish a content, identified by provided id and content in Request Body.",
nickname = "publishQuestion")
@RequestMapping(value = "/{questionId}/publish", method = RequestMethod.POST)
public void publishQuestion(
@PathVariable final String questionId,
@RequestParam(required = false) final Boolean publish,
@RequestBody final Question question
@RequestBody final Content content
) {
if (publish != null) {
question.setActive(publish);
content.setActive(publish);
}
questionService.update(question);
contentService.update(content);
}
@ApiOperation(value = "Publish all questions",
......@@ -230,52 +230,52 @@ public class LecturerQuestionController extends PaginationController {
@RequestParam(value = "preparationquestionsonly", defaultValue = "false", required = false) final boolean preparationQuestionsOnly
) {
boolean p = publish == null || publish;
List<Question> questions;
List<Content> contents;
if (lectureQuestionsOnly) {
questions = questionService.getLectureQuestions(sessionkey);
questionService.publishQuestions(sessionkey, p, questions);
contents = contentService.getLectureQuestions(sessionkey);
contentService.publishQuestions(sessionkey, p, contents);
} else if (preparationQuestionsOnly) {
questions = questionService.getPreparationQuestions(sessionkey);
questionService.publishQuestions(sessionkey, p, questions);
contents = contentService.getPreparationQuestions(sessionkey);
contentService.publishQuestions(sessionkey, p, contents);
} else {