Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • arsnova/arsnova-backend
  • pcvl72/arsnova-backend
  • tksl38/arsnova-backend
3 results
Show changes
Showing
with 1232 additions and 3011 deletions
/*
* This file is part of ARSnova Backend.
* Copyright (C) 2012-2015 The ARSnova Team
* Copyright (C) 2012-2019 The ARSnova Team and Contributors
*
* ARSnova Backend is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
......@@ -15,142 +15,158 @@
* 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 de.thm.arsnova.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
package de.thm.arsnova.controller.v2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import de.thm.arsnova.services.IQuestionService;
import de.thm.arsnova.controller.AbstractController;
import de.thm.arsnova.service.CommentService;
import de.thm.arsnova.service.ContentService;
import de.thm.arsnova.web.DeprecatedApi;
@Controller
/**
* This controller forwards requests from deprecated URLs to their new controller, where the requests are handled.
*/
@Controller("v2LegacyController")
@RequestMapping("/v2")
public class LegacyController extends AbstractController {
public static final Logger LOGGER = LoggerFactory.getLogger(LegacyController.class);
@Autowired
private ContentService contentService;
@Autowired
private IQuestionService questionService;
private CommentService commentService;
/* specific routes */
@DeprecatedApi
@RequestMapping(value = "/session/mysessions", method = RequestMethod.GET)
@GetMapping("/session/mysessions")
public String redirectSessionMy() {
return "forward:/session/?ownedonly=true";
return "forward:/v2/session/?ownedonly=true";
}
@DeprecatedApi
@RequestMapping(value = "/session/visitedsessions", method = RequestMethod.GET)
@GetMapping("/session/visitedsessions")
public String redirectSessionVisited() {
return "forward:/session/?visitedonly=true";
return "forward:/v2/session/?visitedonly=true";
}
@DeprecatedApi
@RequestMapping(value = "/session/{sessionKey}/question")
public String redirectQuestionByLecturer(@PathVariable final String sessionKey) {
return String.format("forward:/lecturerquestion/?sessionkey=%s", sessionKey);
@RequestMapping(value = "/session/{shortId}/question")
public String redirectQuestionByLecturer(@PathVariable final String shortId) {
return String.format("forward:/v2/lecturerquestion/?sessionkey=%s", shortId);
}
@DeprecatedApi
@RequestMapping(value = "/session/{sessionKey}/skillquestions", method = RequestMethod.GET)
public String redirectQuestionByLecturerList(@PathVariable final String sessionKey) {
return String.format("forward:/lecturerquestion/?sessionkey=%s", sessionKey);
@GetMapping("/session/{shortId}/skillquestions")
public String redirectQuestionByLecturerList(@PathVariable final String shortId) {
return String.format("forward:/v2/lecturerquestion/?sessionkey=%s", shortId);
}
@DeprecatedApi
@RequestMapping(value = "/session/{sessionKey}/skillquestioncount", method = RequestMethod.GET)
public String redirectQuestionByLecturerCount(@PathVariable final String sessionKey) {
return String.format("forward:/lecturerquestion/count?sessionkey=%s", sessionKey);
@GetMapping("/session/{shortId}/skillquestioncount")
public String redirectQuestionByLecturerCount(@PathVariable final String shortId) {
return String.format("forward:/v2/lecturerquestion/count?sessionkey=%s", shortId);
}
@DeprecatedApi
@RequestMapping(value = "/session/{sessionKey}/answercount", method = RequestMethod.GET)
public String redirectQuestionByLecturerAnswerCount(@PathVariable final String sessionKey) {
return String.format("forward:/lecturerquestion/answercount?sessionkey=%s", sessionKey);
@GetMapping("/session/{shortId}/answercount")
public String redirectQuestionByLecturerAnswerCount(@PathVariable final String shortId) {
return String.format("forward:/v2/lecturerquestion/answercount?sessionkey=%s", shortId);
}
@DeprecatedApi
@RequestMapping(value = "/session/{sessionKey}/unanswered", method = RequestMethod.GET)
public String redirectQuestionByLecturerUnnsweredCount(@PathVariable final String sessionKey) {
return String.format("forward:/lecturerquestion/answercount?sessionkey=%s", sessionKey);
@GetMapping("/session/{shortId}/unanswered")
public String redirectQuestionByLecturerUnnsweredCount(@PathVariable final String shortId) {
return String.format("forward:/v2/lecturerquestion/answercount?sessionkey=%s", shortId);
}
@DeprecatedApi
@RequestMapping(value = "/session/{sessionKey}/myanswers", method = RequestMethod.GET)
public String redirectQuestionByLecturerMyAnswers(@PathVariable final String sessionKey) {
return String.format("forward:/lecturerquestion/myanswers?sessionkey=%s", sessionKey);
@GetMapping("/session/{shortId}/myanswers")
public String redirectQuestionByLecturerMyAnswers(@PathVariable final String shortId) {
return String.format("forward:/v2/lecturerquestion/myanswers?sessionkey=%s", shortId);
}
@DeprecatedApi
@RequestMapping(value = "/session/{sessionKey}/interposed")
public String redirectQuestionByAudience(@PathVariable final String sessionKey) {
return String.format("forward:/audiencequestion/?sessionkey=%s", sessionKey);
@RequestMapping(value = "/session/{shortId}/interposed")
public String redirectQuestionByAudience(@PathVariable final String shortId) {
return String.format("forward:/v2/audiencequestion/?sessionkey=%s", shortId);
}
@DeprecatedApi
@RequestMapping(value = "/session/{sessionKey}/interposed", method = RequestMethod.DELETE)
@DeleteMapping("/session/{shortId}/interposed")
@ResponseBody
public void deleteAllInterposedQuestions(@PathVariable final String sessionKey) {
questionService.deleteAllInterposedQuestions(sessionKey);
public void deleteAllInterposedQuestions(@PathVariable final String shortId) {
commentService.deleteByRoomId(shortId);
}
@DeprecatedApi
@GetMapping("/session/{shortId}/interposedcount")
public String redirectQuestionByAudienceCount(@PathVariable final String shortId) {
return String.format("forward:/v2/audiencequestion/count?sessionkey=%s", shortId);
}
@DeprecatedApi
@GetMapping("/session/{shortId}/interposedreadingcount")
public String redirectQuestionByAudienceReadCount(@PathVariable final String shortId) {
return String.format("forward:/v2/audiencequestion/readcount?sessionkey=%s", shortId);
}
@DeprecatedApi
@RequestMapping(value = "/session/{sessionKey}/interposedcount", method = RequestMethod.GET)
public String redirectQuestionByAudienceCount(@PathVariable final String sessionKey) {
return String.format("forward:/audiencequestion/count?sessionkey=%s", sessionKey);
@GetMapping(value = { "/whoami", "/whoami.json" })
public String redirectWhoami() {
return "forward:/v2/auth/whoami";
}
@DeprecatedApi
@RequestMapping(value = "/session/{sessionKey}/interposedreadingcount", method = RequestMethod.GET)
public String redirectQuestionByAudienceReadCount(@PathVariable final String sessionKey) {
return String.format("forward:/audiencequestion/readcount?sessionkey=%s", sessionKey);
@PostMapping(value = "/doLogin")
public String redirectLogin() {
return "forward:/v2/auth/login";
}
/* generalized routes */
@DeprecatedApi
@RequestMapping(value = { "/session/{sessionKey}/question/{arg1}", "/session/{sessionKey}/questions/{arg1}" })
@RequestMapping(value = { "/session/{shortId}/question/{arg1}", "/session/{shortId}/questions/{arg1}" })
public String redirectQuestionByLecturerWithOneArgument(
@PathVariable final String sessionKey,
@PathVariable final String arg1
) {
return String.format("forward:/lecturerquestion/%s/?sessionkey=%s", arg1, sessionKey);
@PathVariable final String shortId,
@PathVariable final String arg1) {
return String.format("forward:/v2/lecturerquestion/%s/?sessionkey=%s", arg1, shortId);
}
@DeprecatedApi
@RequestMapping(
value = { "/session/{sessionKey}/question/{arg1}/{arg2}", "/session/{sessionKey}/questions/{arg1}/{arg2}" }
value = { "/session/{shortId}/question/{arg1}/{arg2}", "/session/{shortId}/questions/{arg1}/{arg2}" }
)
public String redirectQuestionByLecturerWithTwoArguments(
@PathVariable final String sessionKey,
@PathVariable final String shortId,
@PathVariable final String arg1,
@PathVariable final String arg2
) {
return String.format("forward:/lecturerquestion/%s/%s/?sessionkey=%s", arg1, arg2, sessionKey);
@PathVariable final String arg2) {
return String.format("forward:/v2/lecturerquestion/%s/%s/?sessionkey=%s", arg1, arg2, shortId);
}
@DeprecatedApi
@RequestMapping(value = "/session/{sessionKey}/interposed/{arg1}")
@RequestMapping(value = "/session/{shortId}/interposed/{arg1}")
public String redirectQuestionByAudienceWithOneArgument(
@PathVariable final String sessionKey,
@PathVariable final String arg1
) {
return String.format("forward:/audiencequestion/%s/?sessionkey=%s", arg1, sessionKey);
@PathVariable final String shortId,
@PathVariable final String arg1) {
return String.format("forward:/v2/audiencequestion/%s/?sessionkey=%s", arg1, shortId);
}
@DeprecatedApi
@RequestMapping(value = "/session/{sessionKey}/interposed/{arg1}/{arg2}")
@RequestMapping(value = "/session/{shortId}/interposed/{arg1}/{arg2}")
public String redirectQuestionByAudienceWithTwoArguments(
@PathVariable final String sessionKey,
@PathVariable final String shortId,
@PathVariable final String arg1,
@PathVariable final String arg2
) {
return String.format("forward:/audiencequestion/%s/%s/?sessionkey=%s", arg1, arg2, sessionKey);
@PathVariable final String arg2) {
return String.format("forward:/v2/audiencequestion/%s/%s/?sessionkey=%s", arg1, arg2, shortId);
}
}
/*
* This file is part of ARSnova Backend.
* Copyright (C) 2012-2019 The ARSnova Team and Contributors
*
* ARSnova Backend is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ARSnova Backend is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* 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 de.thm.arsnova.controller.v2;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import de.thm.arsnova.controller.AbstractController;
import de.thm.arsnova.model.UserProfile;
import de.thm.arsnova.model.migration.FromV2Migrator;
import de.thm.arsnova.model.migration.ToV2Migrator;
import de.thm.arsnova.model.migration.v2.Motd;
import de.thm.arsnova.model.migration.v2.MotdList;
import de.thm.arsnova.security.User;
import de.thm.arsnova.service.MotdService;
import de.thm.arsnova.service.RoomService;
import de.thm.arsnova.service.UserService;
import de.thm.arsnova.web.exceptions.ForbiddenException;
@RestController("v2MotdController")
@RequestMapping("/v2/motd")
@Api(value = "/motd", description = "Message of the Day API")
public class MotdController extends AbstractController {
@Autowired
private MotdService motdService;
@Autowired
private RoomService roomService;
@Autowired
private UserService userService;
@Autowired
private ToV2Migrator toV2Migrator;
@Autowired
private FromV2Migrator fromV2Migrator;
@ApiOperation(value = "get messages. if adminview=false,"
+ " only messages with startdate<clientdate<enddate are returned")
@GetMapping("/")
@ApiResponses(value = {
@ApiResponse(code = 204, message = HTML_STATUS_204),
@ApiResponse(code = 501, message = HTML_STATUS_501)
})
public List<Motd> getMotd(
@ApiParam(value = "clientdate", required = false)
@RequestParam(value = "clientdate", defaultValue = "")
final String clientdate,
@ApiParam(value = "adminview", required = false)
@RequestParam(value = "adminview", defaultValue = "false")
final Boolean adminview,
@ApiParam(value = "audience", required = false)
@RequestParam(value = "audience", defaultValue = "all")
final String audience,
@ApiParam(value = "sessionkey", required = false)
@RequestParam(value = "sessionkey", required = false)
final String roomShortId) {
final List<de.thm.arsnova.model.Motd> motds;
final Date date = new Date(System.currentTimeMillis());
if (!clientdate.isEmpty()) {
date.setTime(Long.parseLong(clientdate));
}
String roomId = "";
if (roomShortId != null) {
roomId = roomService.getIdByShortId(roomShortId);
}
if (adminview) {
motds = roomShortId != null
? motdService.getAllRoomMotds(roomId)
: motdService.getAdminMotds();
} else {
motds = roomShortId != null
? motdService.getCurrentRoomMotds(date, roomId)
: motdService.getCurrentMotds(date, audience);
}
return motds.stream().map(toV2Migrator::migrate).collect(Collectors.toList());
}
@ApiOperation(value = "create a new message of the day", nickname = "createMotd")
@ApiResponses(value = {
@ApiResponse(code = 201, message = HTML_STATUS_201),
@ApiResponse(code = 503, message = HTML_STATUS_503)
})
@PostMapping("/")
@ResponseStatus(HttpStatus.CREATED)
public Motd postNewMotd(
@ApiParam(value = "current motd", required = true) @RequestBody final Motd motd,
final HttpServletResponse response) {
final de.thm.arsnova.model.Motd motdV3 = fromV2Migrator.migrate(motd);
final String roomId = roomService.getIdByShortId(motd.getSessionkey());
if (de.thm.arsnova.model.Motd.Audience.ROOM == motdV3.getAudience() && roomId != null) {
motdService.save(roomId, motdV3);
} else {
motdService.save(motdV3);
}
return toV2Migrator.migrate(motdV3);
}
@ApiOperation(value = "update a message of the day", nickname = "updateMotd")
@PutMapping("/{motdId}")
public Motd updateMotd(
@ApiParam(value = "motdkey from current motd", required = true) @PathVariable final String motdId,
@ApiParam(value = "current motd", required = true) @RequestBody final Motd motd) {
final de.thm.arsnova.model.Motd motdV3 = fromV2Migrator.migrate(motd);
final String roomId = roomService.getIdByShortId(motd.getSessionkey());
if (motdV3.getAudience() == de.thm.arsnova.model.Motd.Audience.ROOM && roomId != null) {
motdService.update(roomId, motdV3);
} else {
motdService.update(motdV3);
}
return toV2Migrator.migrate(motdV3);
}
@ApiOperation(value = "deletes a message of the day", nickname = "deleteMotd")
@DeleteMapping("/{motdId}")
public void deleteMotd(
@ApiParam(value = "Motd-key from the message that shall be deleted", required = true)
@PathVariable
final String motdId) {
final de.thm.arsnova.model.Motd motd = motdService.get(motdId);
motdService.delete(motd);
}
@GetMapping("/userlist")
public MotdList getAcknowledgedIds(@AuthenticationPrincipal final User user, @RequestParam final String username) {
if (user == null || !user.getUsername().equals(username)) {
throw new ForbiddenException();
}
final UserProfile profile = userService.get(user.getId());
return toV2Migrator.migrateMotdList(profile);
}
@PutMapping("/userlist")
public void putAcknowledgedIds(@AuthenticationPrincipal final User user, @RequestBody final MotdList motdList) {
if (user == null || !user.getUsername().equals(motdList.getUsername())) {
throw new ForbiddenException();
}
final UserProfile profile = userService.get(user.getId());
profile.setAcknowledgedMotds(fromV2Migrator.migrate(motdList));
userService.update(profile);
}
}
/*
* This file is part of ARSnova Backend.
* Copyright (C) 2012-2019 The ARSnova Team and Contributors
*
* ARSnova Backend is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ARSnova Backend is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* 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 de.thm.arsnova.controller.v2;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import de.thm.arsnova.controller.PaginationController;
import de.thm.arsnova.model.migration.FromV2Migrator;
import de.thm.arsnova.model.migration.ToV2Migrator;
import de.thm.arsnova.model.migration.v2.Room;
import de.thm.arsnova.model.migration.v2.RoomFeature;
import de.thm.arsnova.model.migration.v2.RoomInfo;
import de.thm.arsnova.model.transport.ImportExportContainer;
import de.thm.arsnova.model.transport.ScoreStatistics;
import de.thm.arsnova.service.RoomService;
import de.thm.arsnova.service.RoomServiceImpl;
import de.thm.arsnova.service.RoomServiceImpl.RoomNameComparator;
import de.thm.arsnova.service.RoomServiceImpl.RoomShortNameComparator;
import de.thm.arsnova.service.UserService;
import de.thm.arsnova.web.DeprecatedApi;
import de.thm.arsnova.web.Pagination;
import de.thm.arsnova.web.exceptions.NotImplementedException;
import de.thm.arsnova.web.exceptions.UnauthorizedException;
/**
* Handles requests related to ARSnova Rooms.
*/
@RestController("v2RoomController")
@RequestMapping("/v2/session")
@Api(value = "/session", description = "Room (Session) API")
public class RoomController extends PaginationController {
@Autowired
private RoomService roomService;
@Autowired
private UserService userService;
@Autowired
private ToV2Migrator toV2Migrator;
@Autowired
private FromV2Migrator fromV2Migrator;
@ApiOperation(value = "join a Room",
nickname = "joinRoom")
@DeprecatedApi
@Deprecated
@GetMapping("/{shortId}")
public Room joinRoom(
@ApiParam(value = "Room-Key from current Room", required = true)
@PathVariable final String shortId,
@ApiParam(value = "Adminflag", required = false)
@RequestParam(value = "admin", defaultValue = "false")
final boolean admin) {
if (admin) {
return toV2Migrator.migrate(roomService.getForAdmin(shortId));
} else {
return toV2Migrator.migrate(roomService.getByShortId(shortId));
}
}
@ApiOperation(value = "deletes a Room",
nickname = "deleteRoom")
@DeleteMapping("/{shortId}")
public void deleteRoom(
@ApiParam(value = "Room-Key from current Room", required = true)
@PathVariable
final String shortId) {
final de.thm.arsnova.model.Room room = roomService.getByShortId(shortId);
roomService.delete(room);
}
@ApiOperation(value = "count active users",
nickname = "countActiveUsers")
@DeprecatedApi
@Deprecated
@GetMapping(value = "/{shortId}/activeusercount", produces = MediaType.TEXT_PLAIN_VALUE)
public String countActiveUsers(
@ApiParam(value = "Room-Key from current Room", required = true) @PathVariable final String shortId) {
return String.valueOf(roomService.activeUsers(roomService.getIdByShortId(shortId)));
}
@ApiOperation(value = "Creates a new Room and returns the Room's data",
nickname = "postNewRoom")
@ApiResponses(value = {
@ApiResponse(code = 201, message = HTML_STATUS_201),
@ApiResponse(code = 503, message = HTML_STATUS_503)
})
@PostMapping("/")
@ResponseStatus(HttpStatus.CREATED)
public Room postNewRoom(
@ApiParam(value = "current Room", required = true)
@RequestBody
final Room room,
final HttpServletResponse response) {
/* FIXME: migrate LMS course support
if (room != null && room.isCourseSession()) {
final List<Course> courses = new ArrayList<>();
final Course course = new Course();
course.setId(room.getCourseId());
courses.add(course);
final int sessionCount = roomService.countSessionsByCourses(courses);
if (sessionCount > 0) {
final String appendix = " (" + (sessionCount + 1) + ")";
room.setName(room.getName() + appendix);
room.setAbbreviation(room.getAbbreviation() + appendix);
}
}
*/
return toV2Migrator.migrate(roomService.create(fromV2Migrator.migrate(room)));
}
@ApiOperation(value = "updates a Room",
nickname = "postNewRoom")
@PutMapping("/{shortId}")
public Room updateRoom(
@ApiParam(value = "Room-Key from current Room", required = true) @PathVariable final String shortId,
@ApiParam(value = "current room", required = true) @RequestBody final Room room) {
return toV2Migrator.migrate(roomService.update(fromV2Migrator.migrate(room)));
}
@ApiOperation(value = "change the Room creator (owner)", nickname = "changeRoomCreator")
@RequestMapping(value = "/{shortId}/changecreator", method = RequestMethod.PUT)
public Room changeRoomCreator(
@ApiParam(value = "Room-key from current Room", required = true) @PathVariable final String shortId,
@ApiParam(value = "new Room creator", required = true) @RequestBody final String newCreator) {
return toV2Migrator.migrate(roomService.updateCreator(roomService.getIdByShortId(shortId), newCreator));
}
@ApiOperation(value = "Retrieves a list of Rooms",
nickname = "getRooms")
@ApiResponses(value = {
@ApiResponse(code = 204, message = HTML_STATUS_204),
@ApiResponse(code = 501, message = HTML_STATUS_501)
})
@GetMapping("/")
@Pagination
public List<Room> getRooms(
@ApiParam(value = "ownedOnly", required = true)
@RequestParam(value = "ownedonly", defaultValue = "false")
final boolean ownedOnly,
@ApiParam(value = "visitedOnly", required = true)
@RequestParam(value = "visitedonly", defaultValue = "false")
final boolean visitedOnly,
@ApiParam(value = "sortby", required = true)
@RequestParam(value = "sortby", defaultValue = "name")
final String sortby,
@ApiParam(value = "for a given username. admin rights needed", required = false)
@RequestParam(value = "username", defaultValue = "")
final String username,
final HttpServletResponse response) {
final List<de.thm.arsnova.model.Room> rooms;
if (!"".equals(username)) {
final String userId = userService.getByUsername(username).getId();
try {
if (ownedOnly && !visitedOnly) {
rooms = roomService.getUserRooms(userId);
} else if (visitedOnly && !ownedOnly) {
rooms = roomService.getUserRoomHistory(username);
} else {
response.setStatus(HttpStatus.NOT_IMPLEMENTED.value());
return null;
}
} catch (final AccessDeniedException e) {
throw new UnauthorizedException();
}
} else {
/* TODO implement all parameter combinations, implement use of user parameter */
try {
if (ownedOnly && !visitedOnly) {
rooms = roomService.getMyRooms(offset, limit);
} else if (visitedOnly && !ownedOnly) {
rooms = roomService.getMyRoomHistory(offset, limit);
} else {
response.setStatus(HttpStatus.NOT_IMPLEMENTED.value());
return null;
}
} catch (final AccessDeniedException e) {
throw new UnauthorizedException();
}
}
if (rooms == null || rooms.isEmpty()) {
response.setStatus(HttpServletResponse.SC_NO_CONTENT);
return null;
}
if ("shortname".equals(sortby)) {
Collections.sort(rooms, new RoomShortNameComparator());
} else {
Collections.sort(rooms, new RoomServiceImpl.RoomNameComparator());
}
return rooms.stream().map(toV2Migrator::migrate).collect(Collectors.toList());
}
/**
* Returns a list of my own Rooms with only the necessary information like name, keyword, or counters.
*/
@ApiOperation(value = "Retrieves a Room",
nickname = "getMyRooms")
@ApiResponses(value = {
@ApiResponse(code = 204, message = HTML_STATUS_204)
})
@GetMapping(value = "/", params = "statusonly=true")
@Pagination
public List<RoomInfo> getMyRooms(
@ApiParam(value = "visitedOnly", required = true)
@RequestParam(value = "visitedonly", defaultValue = "false")
final boolean visitedOnly,
@ApiParam(value = "sort by", required = false)
@RequestParam(value = "sortby", defaultValue = "name")
final String sortby,
final HttpServletResponse response) {
final List<de.thm.arsnova.model.Room> rooms;
if (!visitedOnly) {
rooms = roomService.getMyRoomsInfo(offset, limit);
} else {
rooms = roomService.getMyRoomHistoryInfo(offset, limit);
}
if (rooms == null || rooms.isEmpty()) {
response.setStatus(HttpServletResponse.SC_NO_CONTENT);
return null;
}
if ("shortname".equals(sortby)) {
Collections.sort(rooms, new RoomShortNameComparator());
} else {
Collections.sort(rooms, new RoomNameComparator());
}
return rooms.stream().map(toV2Migrator::migrateStats).collect(Collectors.toList());
}
@ApiOperation(value = "Retrieves all public pool Rooms for the current user",
nickname = "getMyPublicPoolRooms")
@ApiResponses(value = {
@ApiResponse(code = 204, message = HTML_STATUS_204)
})
@GetMapping(value = "/publicpool", params = "statusonly=true")
public List<RoomInfo> getMyPublicPoolRooms(
final HttpServletResponse response) {
final List<de.thm.arsnova.model.Room> rooms = roomService.getMyPublicPoolRoomsInfo();
if (rooms == null || rooms.isEmpty()) {
response.setStatus(HttpServletResponse.SC_NO_CONTENT);
return null;
}
return rooms.stream().map(toV2Migrator::migrateStats).collect(Collectors.toList());
}
@ApiOperation(value = "Retrieves all public pool Rooms",
nickname = "getMyPublicPoolRooms")
@ApiResponses(value = {
@ApiResponse(code = 204, message = HTML_STATUS_204)
})
@GetMapping("/publicpool")
public List<Room> getPublicPoolRooms(
final HttpServletResponse response) {
final List<de.thm.arsnova.model.Room> rooms = roomService.getPublicPoolRoomsInfo();
if (rooms == null || rooms.isEmpty()) {
response.setStatus(HttpServletResponse.SC_NO_CONTENT);
return null;
}
return rooms.stream().map(toV2Migrator::migrate).collect(Collectors.toList());
}
@ApiOperation(value = "imports a Room",
nickname = "importRoom")
@PostMapping("/import")
public Room importRoom(
@ApiParam(value = "current Room", required = true) @RequestBody final ImportExportContainer room,
final HttpServletResponse response) {
return toV2Migrator.migrate(roomService.importRooms(room));
}
@ApiOperation(value = "export Rooms", nickname = "exportRoom")
@GetMapping("/export")
public List<ImportExportContainer> getExport(
@ApiParam(value = "Room-Key", required = true)
@RequestParam(value = "sessionkey", defaultValue = "")
final List<String> shortIds,
@ApiParam(value = "wether statistics shall be exported", required = true)
@RequestParam(value = "withAnswerStatistics", defaultValue = "false")
final Boolean withAnswerStatistics,
@ApiParam(value = "wether comments shall be exported", required = true)
@RequestParam(value = "withFeedbackQuestions", defaultValue = "false")
final Boolean withFeedbackQuestions,
final HttpServletResponse response) throws IOException {
final List<ImportExportContainer> rooms = new ArrayList<>();
ImportExportContainer temp;
for (final String shortId : shortIds) {
final String id = roomService.getIdByShortId(shortId);
roomService.setActive(id, false);
temp = roomService.exportRoom(id, withAnswerStatistics, withFeedbackQuestions);
if (temp != null) {
rooms.add(temp);
}
roomService.setActive(id, true);
}
return rooms;
}
@ApiOperation(value = "copy a Rooms to the public pool if enabled")
@PostMapping("/{shortId}/copytopublicpool")
public Room copyToPublicPool(
@ApiParam(value = "Room-Key from current Room", required = true)
@PathVariable
final String shortId,
@ApiParam(value = "public pool attributes for Room", required = true)
@RequestBody
final ImportExportContainer.PublicPool publicPool)
throws IOException {
final String id = roomService.getIdByShortId(shortId);
roomService.setActive(id, false);
final de.thm.arsnova.model.Room roomInfo = roomService.copyRoomToPublicPool(shortId, publicPool);
roomService.setActive(id, true);
return toV2Migrator.migrate(roomInfo);
}
@ApiOperation(value = "copy a Room from the public pool if enabled")
@PostMapping("/{shortId}/copyfrompublicpool")
public Room copyFromPublicPool(
@ApiParam(value = "Short ID of the Room", required = true) @PathVariable final String shortId,
@ApiParam(value = "custom attributes for Room", required = true) @RequestBody final Room sessionAttributes) {
throw new NotImplementedException();
}
@ApiOperation(value = "Locks or unlocks a Room",
nickname = "lockRoom")
@ApiResponses(value = {
@ApiResponse(code = 404, message = HTML_STATUS_404)
})
@PostMapping("/{shortId}/lock")
public Room lockRoom(
@ApiParam(value = "Room-Key from current Room", required = true) @PathVariable final String shortId,
@ApiParam(value = "lock", required = true) @RequestParam(required = false) final Boolean lock,
final HttpServletResponse response) throws IOException {
if (lock != null) {
return toV2Migrator.migrate(roomService.setActive(roomService.getIdByShortId(shortId), lock));
}
response.setStatus(HttpStatus.NOT_FOUND.value());
return null;
}
@ApiOperation(value = "retrieves a value for the score",
nickname = "getLearningProgress")
@GetMapping("/{shortId}/learningprogress")
public ScoreStatistics getLearningProgress(
@ApiParam(value = "Room-Key from current Room", required = true)
@PathVariable
final String shortId,
@ApiParam(value = "type", required = false)
@RequestParam(value = "type", defaultValue = "questions")
final String type,
@ApiParam(value = "question variant", required = false)
@RequestParam(value = "questionVariant", required = false)
final String questionVariant,
final HttpServletResponse response) {
return roomService.getLearningProgress(roomService.getIdByShortId(shortId), type, questionVariant);
}
@ApiOperation(value = "retrieves a value for the learning progress for the current user",
nickname = "getMyLearningProgress")
@GetMapping("/{shortId}/mylearningprogress")
public ScoreStatistics getMyLearningProgress(
@ApiParam(value = "Room-Key from current Room", required = true) @PathVariable final String shortId,
@RequestParam(value = "type", defaultValue = "questions") final String type,
@RequestParam(value = "questionVariant", required = false) final String questionVariant,
final HttpServletResponse response) {
return roomService.getMyLearningProgress(roomService.getIdByShortId(shortId), type, questionVariant);
}
@ApiOperation(value = "retrieves all Room features",
nickname = "getRoomFeatures")
@GetMapping("/{shortId}/features")
public RoomFeature getRoomFeatures(
@ApiParam(value = "Room-Key from current Room", required = true) @PathVariable final String shortId,
final HttpServletResponse response) {
final de.thm.arsnova.model.Room room = roomService.getByShortId(shortId);
return toV2Migrator.migrate(room.getSettings());
}
@PutMapping("/{shortId}/features")
@ApiOperation(value = "change all Room features",
nickname = "changeRoomFeatures")
public RoomFeature changeRoomFeatures(
@ApiParam(value = "Room-Key from current Room", required = true) @PathVariable final String shortId,
@ApiParam(value = "Room feature", required = true) @RequestBody final RoomFeature features,
final HttpServletResponse response) {
final de.thm.arsnova.model.Room room = roomService.getByShortId(shortId);
room.setSettings(fromV2Migrator.migrate(features));
roomService.update(room);
return toV2Migrator.migrate(room.getSettings());
}
@PostMapping(value = "/{shortId}/lockfeedbackinput", produces = MediaType.TEXT_PLAIN_VALUE)
@ApiOperation(value = "locks input of user live feedback",
nickname = "lockFeedbackInput")
public String lockFeedbackInput(
@ApiParam(value = "Room-Key from current Room", required = true) @PathVariable final String shortId,
@ApiParam(value = "lock", required = true) @RequestParam(required = true) final Boolean lock,
final HttpServletResponse response) throws IOException {
return String.valueOf(roomService.lockFeedbackInput(roomService.getIdByShortId(shortId), lock));
}
@PostMapping(value = "/{shortId}/flipflashcards", produces = MediaType.TEXT_PLAIN_VALUE)
@ApiOperation(value = "flip all flashcards in Room",
nickname = "lockFeedbackInput")
public String flipFlashcards(
@ApiParam(value = "Room-Key from current Room", required = true) @PathVariable final String shortId,
@ApiParam(value = "flip", required = true) @RequestParam(required = true) final Boolean flip,
final HttpServletResponse response) {
return String.valueOf(roomService.flipFlashcards(roomService.getIdByShortId(shortId), flip));
}
/* internal redirections */
@RequestMapping(value = "/{shortId}/lecturerquestion")
public String redirectLecturerQuestion(
@PathVariable final String shortId,
final HttpServletResponse response) {
response.addHeader(X_FORWARDED, "1");
return String.format("forward:/lecturerquestion/?sessionkey=%s", shortId);
}
@RequestMapping(value = "/{shortId}/lecturerquestion/{arg1}")
public String redirectLecturerQuestionWithOneArgument(
@PathVariable final String shortId,
@PathVariable final String arg1,
final HttpServletResponse response) {
response.addHeader(X_FORWARDED, "1");
return String.format("forward:/lecturerquestion/%s/?sessionkey=%s", arg1, shortId);
}
@RequestMapping(value = "/{shortId}/lecturerquestion/{arg1}/{arg2}")
public String redirectLecturerQuestionWithTwoArguments(
@PathVariable final String shortId,
@PathVariable final String arg1,
@PathVariable final String arg2,
final HttpServletResponse response) {
response.addHeader(X_FORWARDED, "1");
return String.format("forward:/lecturerquestion/%s/%s/?sessionkey=%s", arg1, arg2, shortId);
}
@RequestMapping(value = "/{shortId}/lecturerquestion/{arg1}/{arg2}/{arg3}")
public String redirectLecturerQuestionWithThreeArguments(
@PathVariable final String shortId,
@PathVariable final String arg1,
@PathVariable final String arg2,
@PathVariable final String arg3,
final HttpServletResponse response) {
response.addHeader(X_FORWARDED, "1");
return String.format("forward:/lecturerquestion/%s/%s/%s/?sessionkey=%s", arg1, arg2, arg3, shortId);
}
@RequestMapping(value = "/{shortId}/audiencequestion")
public String redirectAudienceQuestion(
@PathVariable final String shortId,
final HttpServletResponse response) {
response.addHeader(X_FORWARDED, "1");
return String.format("forward:/audiencequestion/?sessionkey=%s", shortId);
}
@RequestMapping(value = "/{shortId}/audiencequestion/{arg1}")
public String redirectAudienceQuestionWithOneArgument(
@PathVariable final String shortId,
@PathVariable final String arg1,
final HttpServletResponse response) {
response.addHeader(X_FORWARDED, "1");
return String.format("forward:/audiencequestion/%s/?sessionkey=%s", arg1, shortId);
}
}
/*
* This file is part of ARSnova Backend.
* Copyright (C) 2012-2015 The ARSnova Team
* Copyright (C) 2012-2019 The ARSnova Team and Contributors
*
* ARSnova Backend is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
......@@ -15,70 +15,82 @@
* 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 de.thm.arsnova.controller;
package de.thm.arsnova.controller.v2;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import java.util.Map;
import java.util.UUID;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import de.thm.arsnova.entities.User;
import de.thm.arsnova.services.IUserService;
import de.thm.arsnova.services.UserSessionService;
import de.thm.arsnova.socket.ARSnovaSocket;
import de.thm.arsnova.controller.AbstractController;
import de.thm.arsnova.security.User;
import de.thm.arsnova.service.UserService;
import de.thm.arsnova.websocket.ArsnovaSocketioServer;
@RestController
@RequestMapping("/socket")
/**
* Initiates the socket communication.
*/
@RestController("v2SocketController")
@RequestMapping("/v2/socket")
@Api(value = "/socket", description = "WebSocket Initialization API")
public class SocketController extends AbstractController {
@Autowired
private IUserService userService;
private UserService userService;
@Autowired
private UserSessionService userSessionService;
private ArsnovaSocketioServer server;
@Autowired
private ARSnovaSocket server;
private static final Logger LOGGER = LoggerFactory.getLogger(SocketController.class);
private static final Logger logger = LoggerFactory.getLogger(SocketController.class);
@RequestMapping(method = RequestMethod.POST, value = "/assign")
public void authorize(@RequestBody final Map<String, String> sessionMap, final HttpServletResponse response) {
String socketid = sessionMap.get("session");
@ApiOperation(value = "requested to assign Websocket session",
nickname = "authorize")
@ApiResponses(value = {
@ApiResponse(code = 204, message = HTML_STATUS_204),
@ApiResponse(code = 400, message = HTML_STATUS_400),
@ApiResponse(code = 403, message = HTML_STATUS_403)
})
@PostMapping("/assign")
public void authorize(
@ApiParam(value = "sessionMap", required = true) @RequestBody final Map<String, String> sessionMap,
@ApiParam(value = "response", required = true) final HttpServletResponse response) {
final String socketid = sessionMap.get("session");
if (null == socketid) {
LOGGER.debug("Expected property 'session' missing", socketid);
logger.debug("Expected property 'session' missing.");
response.setStatus(HttpStatus.BAD_REQUEST.value());
return;
}
User u = userService.getCurrentUser();
if (null == u) {
LOGGER.debug("Client {} requested to assign Websocket session but has not authenticated", socketid);
final User user = userService.getCurrentUser();
if (null == user) {
logger.debug("Client {} requested to assign Websocket session but has not authenticated.", socketid);
response.setStatus(HttpStatus.FORBIDDEN.value());
return;
}
userService.putUser2SocketId(UUID.fromString(socketid), u);
userSessionService.setSocketId(UUID.fromString(socketid));
userService.putUserIdToSocketId(UUID.fromString(socketid), user.getId());
response.setStatus(HttpStatus.NO_CONTENT.value());
}
@RequestMapping(value = "/url", method = RequestMethod.GET)
@ApiOperation(value = "retrieves a socket url",
nickname = "getSocketUrl")
@GetMapping(value = "/url", produces = MediaType.TEXT_PLAIN_VALUE)
public String getSocketUrl(final HttpServletRequest request) {
StringBuilder url = new StringBuilder();
url.append(server.isUseSSL() ? "https://" : "http://");
url.append(request.getServerName() + ":" + server.getPortNumber());
return url.toString();
return (server.isUseSsl() ? "https://" : "http://") + request.getServerName() + ":" + server.getPortNumber();
}
}
/*
* This file is part of ARSnova Backend.
* Copyright (C) 2012-2015 The ARSnova Team
* Copyright (C) 2012-2019 The ARSnova Team and Contributors
*
* ARSnova Backend is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
......@@ -15,46 +15,67 @@
* 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 de.thm.arsnova.controller;
package de.thm.arsnova.controller.v2;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import de.thm.arsnova.entities.Statistics;
import de.thm.arsnova.services.IStatisticsService;
import de.thm.arsnova.controller.AbstractController;
import de.thm.arsnova.model.Statistics;
import de.thm.arsnova.service.StatisticsService;
import de.thm.arsnova.web.CacheControl;
import de.thm.arsnova.web.DeprecatedApi;
@RestController
/**
* Allows retrieval of several statistics such as the number of active users.
*/
@RestController("v2StatisticsController")
@Api(value = "/statistics", description = "Statistics API")
@RequestMapping("/v2/statistics")
public class StatisticsController extends AbstractController {
@Autowired
private IStatisticsService statisticsService;
private StatisticsService statisticsService;
@RequestMapping(method = RequestMethod.GET, value = "/statistics")
@ApiOperation(value = "Retrieves global statistics",
nickname = "getStatistics")
@GetMapping("/")
@CacheControl(maxAge = 60, policy = CacheControl.Policy.PUBLIC)
public Statistics getStatistics() {
return statisticsService.getStatistics();
}
@ApiOperation(value = "Retrieves the amount of all active users",
nickname = "countActiveUsers")
@DeprecatedApi
@RequestMapping(method = RequestMethod.GET, value = "/statistics/activeusercount", produces = "text/plain")
@Deprecated
@GetMapping(value = "/activeusercount", produces = MediaType.TEXT_PLAIN_VALUE)
public String countActiveUsers() {
return Integer.toString(statisticsService.getStatistics().getActiveUsers());
return String.valueOf(statisticsService.getStatistics().getActiveUsers());
}
@ApiOperation(value = "Retrieves the amount of all currently logged in users",
nickname = "countLoggedInUsers")
@DeprecatedApi
@RequestMapping(method = RequestMethod.GET, value = "/statistics/loggedinusercount", produces = "text/plain")
@Deprecated
@GetMapping(value = "/loggedinusercount", produces = MediaType.TEXT_PLAIN_VALUE)
public String countLoggedInUsers() {
return Integer.toString(statisticsService.getStatistics().getLoggedinUsers());
return String.valueOf(statisticsService.getStatistics().getLoggedinUsers());
}
@ApiOperation(value = "Retrieves the total amount of all sessions",
nickname = "countSessions")
@DeprecatedApi
@RequestMapping(method = RequestMethod.GET, value = "/statistics/sessioncount", produces = "text/plain")
@Deprecated
@GetMapping(value = "/sessioncount", produces = MediaType.TEXT_PLAIN_VALUE)
public String countSessions() {
return Integer.toString(statisticsService.getStatistics().getOpenSessions()
return String.valueOf(statisticsService.getStatistics().getOpenSessions()
+ statisticsService.getStatistics().getClosedSessions());
}
}
/*
* This file is part of ARSnova Backend.
* Copyright (C) 2012-2019 The ARSnova Team and Contributors
*
* ARSnova Backend is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ARSnova Backend is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* 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 de.thm.arsnova.controller.v2;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import de.thm.arsnova.controller.AbstractController;
import de.thm.arsnova.model.UserProfile;
import de.thm.arsnova.service.UserService;
/**
* Handles requests related to ARSnova's own user registration and login process.
*/
@Controller("v2UserController")
@RequestMapping("/v2/user")
public class UserController extends AbstractController {
@Autowired
private DaoAuthenticationProvider daoProvider;
@Autowired
private UserService userService;
@PostMapping(value = "/register")
public void register(@RequestParam final String username,
@RequestParam final String password,
final HttpServletRequest request, final HttpServletResponse response) {
if (null != userService.create(username, password)) {
return;
}
/* TODO: Improve error handling: send reason to client */
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
}
@PostMapping(value = "/{username}/activate")
public void activate(
@PathVariable final String username,
@RequestParam final String key,
final HttpServletRequest request,
final HttpServletResponse response) {
final UserProfile userProfile = userService.getByUsername(username);
if (userProfile == null || !userService.activateAccount(userProfile.getId(), key, request.getRemoteAddr())) {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
}
}
@DeleteMapping(value = "/{username}/")
public void activate(
@PathVariable final String username,
final HttpServletRequest request,
final HttpServletResponse response) {
if (null == userService.deleteByUsername(username)) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
}
}
@PostMapping(value = "/{username}/resetpassword")
public void resetPassword(
@PathVariable final String username,
@RequestParam(required = false) final String key,
@RequestParam(required = false) final String password,
final HttpServletRequest request,
final HttpServletResponse response) {
final UserProfile userProfile = userService.getByUsername(username);
if (null == userProfile) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
if (null != key) {
if (!userService.resetPassword(userProfile, key, password)) {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
}
} else {
userService.initiatePasswordReset(username);
}
}
}
package de.thm.arsnova.controller.v2;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller("v2WelcomeController")
@RequestMapping("/v2")
public class WelcomeController {
@GetMapping(value = "/")
public String forwardHome() {
return "forward:/";
}
}
/*
* This file is part of ARSnova Backend.
* Copyright (C) 2012-2015 The ARSnova Team
*
* ARSnova Backend is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ARSnova Backend is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* 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 de.thm.arsnova.dao;
import java.io.IOException;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import net.sf.ezmorph.Morpher;
import net.sf.ezmorph.MorpherRegistry;
import net.sf.ezmorph.bean.BeanMorpher;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import net.sf.json.util.JSONUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
import com.fourspaces.couchdb.Database;
import com.fourspaces.couchdb.Document;
import com.fourspaces.couchdb.View;
import com.fourspaces.couchdb.ViewResults;
import de.thm.arsnova.connector.model.Course;
import de.thm.arsnova.domain.CourseScore;
import de.thm.arsnova.entities.Answer;
import de.thm.arsnova.entities.DbUser;
import de.thm.arsnova.entities.InterposedQuestion;
import de.thm.arsnova.entities.InterposedReadingCount;
import de.thm.arsnova.entities.LoggedIn;
import de.thm.arsnova.entities.PossibleAnswer;
import de.thm.arsnova.entities.Question;
import de.thm.arsnova.entities.Session;
import de.thm.arsnova.entities.SessionInfo;
import de.thm.arsnova.entities.Statistics;
import de.thm.arsnova.entities.User;
import de.thm.arsnova.entities.VisitedSession;
import de.thm.arsnova.entities.SortOrder;
import de.thm.arsnova.entities.transport.AnswerQueueElement;
import de.thm.arsnova.entities.transport.ImportExportSession;
import de.thm.arsnova.entities.transport.ImportExportSession.ImportExportQuestion;
import de.thm.arsnova.events.NewAnswerEvent;
import de.thm.arsnova.exceptions.NotFoundException;
import de.thm.arsnova.services.ISessionService;
@Component("databaseDao")
public class CouchDBDao implements IDatabaseDao, ApplicationEventPublisherAware {
@Autowired
private ISessionService sessionService;
private String databaseHost;
private int databasePort;
private String databaseName;
private Database database;
private ApplicationEventPublisher publisher;
private final Queue<AbstractMap.SimpleEntry<Document, AnswerQueueElement>> answerQueue = new ConcurrentLinkedQueue<AbstractMap.SimpleEntry<Document, AnswerQueueElement>>();
public static final Logger LOGGER = LoggerFactory.getLogger(CouchDBDao.class);
@Value("${couchdb.host}")
public void setDatabaseHost(final String newDatabaseHost) {
databaseHost = newDatabaseHost;
}
@Value("${couchdb.port}")
public void setDatabasePort(final String newDatabasePort) {
databasePort = Integer.parseInt(newDatabasePort);
}
@Value("${couchdb.name}")
public void setDatabaseName(final String newDatabaseName) {
databaseName = newDatabaseName;
}
public void setSessionService(final ISessionService service) {
sessionService = service;
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
@Override
public List<Session> getMySessions(final User user) {
final NovaView view = new NovaView("session/by_creator");
view.setStartKeyArray(user.getUsername());
view.setEndKeyArray(user.getUsername(), "{}");
final ViewResults sessions = getDatabase().view(view);
final List<Session> result = new ArrayList<Session>();
for (final Document d : sessions.getResults()) {
final Session session = (Session) JSONObject.toBean(
d.getJSONObject().getJSONObject("value"),
Session.class
);
session.setCreator(d.getJSONObject().getJSONArray("key").getString(0));
session.setName(d.getJSONObject().getJSONArray("key").getString(1));
session.set_id(d.getId());
result.add(session);
}
return result;
}
@Override
public List<Session> getPublicPoolSessions() {
final NovaView view = new NovaView("session/public_pool_by_subject");
final ViewResults sessions = getDatabase().view(view);
final List<Session> result = new ArrayList<Session>();
for (final Document d : sessions.getResults()) {
final Session session = (Session) JSONObject.toBean(
d.getJSONObject().getJSONObject("value"),
Session.class
);
session.set_id(d.getId());
result.add(session);
}
return result;
}
@Override
public List<SessionInfo> getPublicPoolSessionsInfo() {
final List<Session> sessions = this.getPublicPoolSessions();
return getInfosForSessions(sessions);
}
@Override
public List<Session> getMyPublicPoolSessions(final User user) {
final NovaView view = new NovaView("session/public_pool_by_creator");
view.setStartKeyArray(user.getUsername());
view.setEndKeyArray(user.getUsername(), "{}");
final ViewResults sessions = getDatabase().view(view);
final List<Session> result = new ArrayList<Session>();
for (final Document d : sessions.getResults()) {
final Session session = (Session) JSONObject.toBean(
d.getJSONObject().getJSONObject("value"),
Session.class
);
session.setCreator(d.getJSONObject().getJSONArray("key").getString(0));
session.setName(d.getJSONObject().getJSONArray("key").getString(1));
session.set_id(d.getId());
result.add(session);
}
return result;
}
@Override
public List<SessionInfo> getMyPublicPoolSessionsInfo(final User user) {
final List<Session> sessions = this.getMyPublicPoolSessions(user);
if (sessions.isEmpty()) {
return new ArrayList<SessionInfo>();
}
return getInfosForSessions(sessions);
}
@Override
public List<SessionInfo> getMySessionsInfo(final User user) {
final List<Session> sessions = this.getMySessions(user);
if (sessions.isEmpty()) {
return new ArrayList<SessionInfo>();
}
return getInfosForSessions(sessions);
}
private List<SessionInfo> getInfosForSessions(final List<Session> sessions) {
final ExtendedView questionCountView = new ExtendedView("skill_question/count_by_session");
final ExtendedView answerCountView = new ExtendedView("skill_question/count_answers_by_session");
final ExtendedView interposedCountView = new ExtendedView("interposed_question/count_by_session");
final ExtendedView unredInterposedCountView = new ExtendedView("interposed_question/count_by_session_reading");
interposedCountView.setSessionIdKeys(sessions);
interposedCountView.setGroup(true);
questionCountView.setSessionIdKeys(sessions);
questionCountView.setGroup(true);
answerCountView.setSessionIdKeys(sessions);
answerCountView.setGroup(true);
List<String> unredInterposedQueryKeys = new ArrayList<String>();
for (Session s : sessions) {
unredInterposedQueryKeys.add("[\"" + s.get_id() + "\",\"unread\"]");
}
unredInterposedCountView.setKeys(unredInterposedQueryKeys);
unredInterposedCountView.setGroup(true);
return getSessionInfoData(sessions, questionCountView, answerCountView, interposedCountView, unredInterposedCountView);
}
private List<SessionInfo> getInfosForVisitedSessions(final List<Session> sessions, final User user) {
final ExtendedView answeredQuestionsView = new ExtendedView("answer/by_user");
final ExtendedView questionIdsView = new ExtendedView("skill_question/by_session_only_id_for_all");
questionIdsView.setSessionIdKeys(sessions);
List<String> answeredQuestionQueryKeys = new ArrayList<String>();
for (Session s : sessions) {
answeredQuestionQueryKeys.add("[\"" + user.getUsername() + "\",\"" + s.get_id() + "\"]");
}
answeredQuestionsView.setKeys(answeredQuestionQueryKeys);
return getVisitedSessionInfoData(sessions, answeredQuestionsView, questionIdsView);
}
private List<SessionInfo> getVisitedSessionInfoData(List<Session> sessions,
ExtendedView answeredQuestionsView, ExtendedView questionIdsView) {
final Map<String, Set<String>> answeredQuestionsMap = new HashMap<String, Set<String>>();
final Map<String, Set<String>> questionIdMap = new HashMap<String, Set<String>>();
final ViewResults answeredQuestionsViewResults = getDatabase().view(answeredQuestionsView);
final ViewResults questionIdsViewResults = getDatabase().view(questionIdsView);
// Maps a session ID to a set of question IDs of answered questions of that session
for (final Document d : answeredQuestionsViewResults.getResults()) {
final String sessionId = d.getJSONArray("key").getString(1);
final String questionId = d.getString("value");
Set<String> questionIdsInSession = answeredQuestionsMap.get(sessionId);
if (questionIdsInSession == null) {
questionIdsInSession = new HashSet<String>();
}
questionIdsInSession.add(questionId);
answeredQuestionsMap.put(sessionId, questionIdsInSession);
}
// Maps a session ID to a set of question IDs of that session
for (final Document d : questionIdsViewResults.getResults()) {
final String sessionId = d.getString("key");
final String questionId = d.getId();
Set<String> questionIdsInSession = questionIdMap.get(sessionId);
if (questionIdsInSession == null) {
questionIdsInSession = new HashSet<String>();
}
questionIdsInSession.add(questionId);
questionIdMap.put(sessionId, questionIdsInSession);
}
// For each session, count the question IDs that are not yet answered
Map<String, Integer> unansweredQuestionsCountMap = new HashMap<String, Integer>();
for (final Session s : sessions) {
if (!questionIdMap.containsKey(s.get_id())) {
continue;
}
// Note: create a copy of the first set so that we don't modify the contents in the original set
Set<String> questionIdsInSession = new HashSet<String>(questionIdMap.get(s.get_id()));
Set<String> answeredQuestionIdsInSession = answeredQuestionsMap.get(s.get_id());
if (answeredQuestionIdsInSession == null) {
answeredQuestionIdsInSession = new HashSet<String>();
}
questionIdsInSession.removeAll(answeredQuestionIdsInSession);
unansweredQuestionsCountMap.put(s.get_id(), questionIdsInSession.size());
}
List<SessionInfo> sessionInfos = new ArrayList<SessionInfo>();
for (Session session : sessions) {
int numUnanswered = 0;
if (unansweredQuestionsCountMap.containsKey(session.get_id())) {
numUnanswered = unansweredQuestionsCountMap.get(session.get_id());
}
SessionInfo info = new SessionInfo(session);
info.setNumUnanswered(numUnanswered);
sessionInfos.add(info);
}
return sessionInfos;
}
private List<SessionInfo> getSessionInfoData(final List<Session> sessions,
final ExtendedView questionCountView,
final ExtendedView answerCountView,
final ExtendedView interposedCountView,
final ExtendedView unredInterposedCountView) {
final ViewResults questionCountViewResults = getDatabase().view(questionCountView);
final ViewResults answerCountViewResults = getDatabase().view(answerCountView);
final ViewResults interposedCountViewResults = getDatabase().view(interposedCountView);
final ViewResults unredInterposedCountViewResults = getDatabase().view(unredInterposedCountView);
Map<String, Integer> questionCountMap = new HashMap<String, Integer>();
for (final Document d : questionCountViewResults.getResults()) {
questionCountMap.put(d.getString("key"), d.getInt("value"));
}
Map<String, Integer> answerCountMap = new HashMap<String, Integer>();
for (final Document d : answerCountViewResults.getResults()) {
answerCountMap.put(d.getString("key"), d.getInt("value"));
}
Map<String, Integer> interposedCountMap = new HashMap<String, Integer>();
for (final Document d : interposedCountViewResults.getResults()) {
interposedCountMap.put(d.getString("key"), d.getInt("value"));
}
Map<String, Integer> unredInterposedCountMap = new HashMap<String, Integer>();
for (final Document d : unredInterposedCountViewResults.getResults()) {
unredInterposedCountMap.put(d.getJSONArray("key").getString(0), d.getInt("value"));
}
List<SessionInfo> sessionInfos = new ArrayList<SessionInfo>();
for (Session session : sessions) {
int numQuestions = 0;
int numAnswers = 0;
int numInterposed = 0;
int numUnredInterposed = 0;
if (questionCountMap.containsKey(session.get_id())) {
numQuestions = questionCountMap.get(session.get_id());
}
if (answerCountMap.containsKey(session.get_id())) {
numAnswers = answerCountMap.get(session.get_id());
}
if (interposedCountMap.containsKey(session.get_id())) {
numInterposed = interposedCountMap.get(session.get_id());
}
if (unredInterposedCountMap.containsKey(session.get_id())) {
numUnredInterposed = unredInterposedCountMap.get(session.get_id());
}
SessionInfo info = new SessionInfo(session);
info.setNumQuestions(numQuestions);
info.setNumAnswers(numAnswers);
info.setNumInterposed(numInterposed);
info.setNumUnredInterposed(numUnredInterposed);
sessionInfos.add(info);
}
return sessionInfos;
}
/**
* @deprecated The decision to load data depending on the user should be made by a service class, not this
* database class. Please use getSkillQuestionsForUsers or getSkillQuestionsForTeachers as this will enable
* caching.
*/
@Deprecated
@Override
public List<Question> getSkillQuestions(final User user, final Session session) {
String viewName;
if (session.getCreator().equals(user.getUsername())) {
viewName = "skill_question/by_session_sorted_by_subject_and_text";
} else {
viewName = "skill_question/by_session_for_all_full";
}
return getQuestions(new NovaView(viewName), session);
}
@Cacheable("skillquestions")
@Override
public List<Question> getSkillQuestionsForUsers(final Session session) {
String viewName = "skill_question/by_session_for_all_full";
return getQuestions(new NovaView(viewName), session);
}
@Cacheable("skillquestions")
@Override
public List<Question> getSkillQuestionsForTeachers(final Session session) {
String viewName = "skill_question/by_session_sorted_by_subject_and_text";
return getQuestions(new NovaView(viewName), session);
}
@Override
public int getSkillQuestionCount(final Session session) {
return getQuestionCount(new NovaView("skill_question/count_by_session"), session);
}
@Override
@Cacheable("sessions")
public Session getSessionFromKeyword(final String keyword) {
final NovaView view = new NovaView("session/by_keyword");
view.setKey(keyword);
final ViewResults results = getDatabase().view(view);
if (results.getJSONArray("rows").optJSONObject(0) == null) {
throw new NotFoundException();
}
return (Session) JSONObject.toBean(
results.getJSONArray("rows").optJSONObject(0).optJSONObject("value"),
Session.class
);
}
@Override
@Cacheable("sessions")
public Session getSessionFromId(final String sessionId) {
final NovaView view = new NovaView("session/by_id");
view.setKey(sessionId);
final ViewResults sessions = getDatabase().view(view);
if (sessions.getJSONArray("rows").optJSONObject(0) == null) {
return null;
}
return (Session) JSONObject.toBean(
sessions.getJSONArray("rows").optJSONObject(0).optJSONObject("value"),
Session.class
);
}
@Override
public Session saveSession(final User user, final Session session) {
final Document sessionDocument = new Document();
sessionDocument.put("type", "session");
sessionDocument.put("name", session.getName());
sessionDocument.put("shortName", session.getShortName());
sessionDocument.put("keyword", sessionService.generateKeyword());
sessionDocument.put("creator", user.getUsername());
sessionDocument.put("active", true);
sessionDocument.put("courseType", session.getCourseType());
sessionDocument.put("courseId", session.getCourseId());
sessionDocument.put("creationTime", session.getCreationTime());
sessionDocument.put("learningProgressType", session.getLearningProgressType());
sessionDocument.put("ppAuthorName", session.getPpAuthorName());
sessionDocument.put("ppAuthorMail", session.getPpAuthorMail());
sessionDocument.put("ppUniversity", session.getPpUniversity());
sessionDocument.put("ppLogo", session.getPpLogo());
sessionDocument.put("ppSubject", session.getPpSubject());
sessionDocument.put("ppLicense", session.getPpLicense());
sessionDocument.put("ppDescription", session.getPpDescription());
sessionDocument.put("ppFaculty", session.getPpFaculty());
sessionDocument.put("ppLevel", session.getPpLevel());
sessionDocument.put("sessionType", session.getSessionType());
try {
database.saveDocument(sessionDocument);
} catch (final IOException e) {
return null;
}
// session caching is done by loading the created session
return getSessionFromKeyword(sessionDocument.getString("keyword"));
}
@Override
@Transactional(isolation = Isolation.READ_COMMITTED)
public boolean sessionKeyAvailable(final String keyword) {
final View view = new View("session/by_keyword");
final ViewResults results = getDatabase().view(view);
return !results.containsKey(keyword);
}
private String getSessionKeyword(final String internalSessionId) throws IOException {
final Document document = getDatabase().getDocument(internalSessionId);
if (document.has("keyword")) {
return (String) document.get("keyword");
}
LOGGER.error("No session found for internal id: {}", internalSessionId);
return null;
}
private Database getDatabase() {
if (database == null) {
try {
final com.fourspaces.couchdb.Session session = new com.fourspaces.couchdb.Session(
databaseHost,
databasePort
);
database = session.getDatabase(databaseName);
} catch (final Exception e) {
LOGGER.error(
"Cannot connect to CouchDB database '" + databaseName
+ "' on host '" + databaseHost
+ "' using port " + databasePort
);
}
}
return database;
}
@CachePut(value = "questions", key="#question")
@Override
public Question saveQuestion(final Session session, final Question question) {
final Document q = toQuestionDocument(session, question);
try {
database.saveDocument(q);
question.set_id(q.getId());
question.set_rev(q.getRev());
return question;
} catch (final IOException e) {
LOGGER.error("Could not save question {}", question);
}
return null;
}
private Document toQuestionDocument(final Session session, final Question question) {
final Document q = new Document();
q.put("type", "skill_question");
q.put("questionType", question.getQuestionType());
q.put("questionVariant", question.getQuestionVariant());
q.put("sessionId", session.get_id());
q.put("subject", question.getSubject());
q.put("text", question.getText());
q.put("active", question.isActive());
q.put("number", 0); // TODO: This number is now unused. A clean up is necessary.
q.put("releasedFor", question.getReleasedFor());
q.put("possibleAnswers", question.getPossibleAnswers());
q.put("noCorrect", question.isNoCorrect());
q.put("piRound", question.getPiRound());
q.put("showStatistic", question.isShowStatistic());
q.put("showAnswer", question.isShowAnswer());
q.put("abstention", question.isAbstention());
q.put("image", question.getImage());
q.put("fcImage", question.getFcImage());
q.put("gridSize", question.getGridSize());
q.put("offsetX", question.getOffsetX());
q.put("offsetY", question.getOffsetY());
q.put("zoomLvl", question.getZoomLvl());
q.put("gridOffsetX", question.getGridOffsetX());
q.put("gridOffsetY", question.getGridOffsetY());
q.put("gridZoomLvl", question.getGridZoomLvl());
q.put("gridSizeX", question.getGridSizeX());
q.put("gridSizeY", question.getGridSizeY());
q.put("gridIsHidden", question.getGridIsHidden());
q.put("imgRotation", question.getImgRotation());
q.put("toggleFieldsLeft", question.getToggleFieldsLeft());
q.put("numClickableFields", question.getNumClickableFields());
q.put("thresholdCorrectAnswers", question.getThresholdCorrectAnswers());
q.put("cvIsColored", question.getCvIsColored());
q.put("gridLineColor", question.getGridLineColor());
q.put("numberOfDots", question.getNumberOfDots());
q.put("gridType", question.getGridType());
q.put("scaleFactor", question.getScaleFactor());
q.put("gridScaleFactor", question.getGridScaleFactor());
q.put("timestamp", question.getTimestamp());
return q;
}
@Caching(evict = {@CacheEvict(value = "skillquestions", allEntries = true),
@CacheEvict(value = "lecturequestions", allEntries = true),
@CacheEvict(value = "preparationquestions", allEntries = true),
@CacheEvict(value = "flashcardquestions", allEntries = true) },
put = {@CachePut("questions")})
@Override
public Question updateQuestion(final Question question) {
try {
final Document q = database.getDocument(question.get_id());
q.put("subject", question.getSubject());
q.put("text", question.getText());
q.put("active", question.isActive());
q.put("releasedFor", question.getReleasedFor());
q.put("possibleAnswers", question.getPossibleAnswers());
q.put("noCorrect", question.isNoCorrect());
q.put("piRound", question.getPiRound());
q.put("showStatistic", question.isShowStatistic());
q.put("showAnswer", question.isShowAnswer());
q.put("abstention", question.isAbstention());
q.put("image", question.getImage());
q.put("fcImage", question.getFcImage());
q.put("gridSize", question.getGridSize());
q.put("offsetX", question.getOffsetX());
q.put("offsetY", question.getOffsetY());
q.put("zoomLvl", question.getZoomLvl());
q.put("gridOffsetX", question.getGridOffsetX());
q.put("gridOffsetY", question.getGridOffsetY());
q.put("gridZoomLvl", question.getGridZoomLvl());
q.put("gridSizeX", question.getGridSizeX());
q.put("gridSizeY", question.getGridSizeY());
q.put("gridIsHidden", question.getGridIsHidden());
q.put("imgRotation", question.getImgRotation());
q.put("toggleFieldsLeft", question.getToggleFieldsLeft());
q.put("numClickableFields", question.getNumClickableFields());
q.put("thresholdCorrectAnswers", question.getThresholdCorrectAnswers());
q.put("cvIsColored", question.getCvIsColored());
q.put("gridLineColor", question.getGridLineColor());
q.put("numberOfDots", question.getNumberOfDots());
q.put("gridType", question.getGridType());
q.put("scaleFactor", question.getScaleFactor());
q.put("gridScaleFactor", question.getGridScaleFactor());
database.saveDocument(q);
question.set_rev(q.getRev());
return question;
} catch (final IOException e) {
LOGGER.error("Could not update question {}", question);
}
return null;
}
@Override
public InterposedQuestion saveQuestion(final Session session, final InterposedQuestion question, User user) {
final Document q = new Document();
q.put("type", "interposed_question");
q.put("sessionId", session.get_id());
q.put("subject", question.getSubject());
q.put("text", question.getText());
if (question.getTimestamp() != 0) {
q.put("timestamp", question.getTimestamp());
} else {
q.put("timestamp", System.currentTimeMillis());
}
q.put("read", false);
q.put("creator", user.getUsername());
try {
database.saveDocument(q);
question.set_id(q.getId());
question.set_rev(q.getRev());
return question;
} catch (final IOException e) {
LOGGER.error("Could not save interposed question {}", question);
}
return null;
}
@Cacheable("questions")
@Override
public Question getQuestion(final String id) {
try {
final Document q = getDatabase().getDocument(id);
final Question question = (Question) JSONObject.toBean(q.getJSONObject(), Question.class);
final JSONArray possibleAnswers = q.getJSONObject().getJSONArray("possibleAnswers");
@SuppressWarnings("unchecked")
final Collection<PossibleAnswer> answers = JSONArray.toCollection(possibleAnswers, PossibleAnswer.class);
question.setPossibleAnswers(new ArrayList<PossibleAnswer>(answers));
question.setSessionKeyword(getSessionKeyword(question.getSessionId()));
return question;
} catch (final IOException e) {
LOGGER.error("Could not get question with id {}", id);
}
return null;
}
@Override
public LoggedIn registerAsOnlineUser(final User user, final Session session) {
try {
final NovaView view = new NovaView("logged_in/all");
view.setKey(user.getUsername());
final ViewResults results = getDatabase().view(view);
LoggedIn loggedIn = new LoggedIn();
if (results.getJSONArray("rows").optJSONObject(0) != null) {
final JSONObject json = results.getJSONArray("rows").optJSONObject(0).optJSONObject("value");
loggedIn = (LoggedIn) JSONObject.toBean(json, LoggedIn.class);
final JSONArray vs = json.optJSONArray("visitedSessions");
if (vs != null) {
@SuppressWarnings("unchecked")
final Collection<VisitedSession> visitedSessions = JSONArray.toCollection(vs, VisitedSession.class);
loggedIn.setVisitedSessions(new ArrayList<VisitedSession>(visitedSessions));
}
/* Do not clutter CouchDB. Only update once every 3 hours per session. */
if (loggedIn.getSessionId().equals(session.get_id()) && loggedIn.getTimestamp() > System.currentTimeMillis() - 3 * 3600000) {
return loggedIn;
}
}
loggedIn.setUser(user.getUsername());
loggedIn.setSessionId(session.get_id());
loggedIn.addVisitedSession(session);
loggedIn.updateTimestamp();
final JSONObject json = JSONObject.fromObject(loggedIn);
final Document doc = new Document(json);
if (doc.getId().isEmpty()) {
// If this is a new user without a logged_in document, we have
// to remove the following
// pre-filled fields. Otherwise, CouchDB will take these empty
// fields as genuine
// identifiers, and will throw errors afterwards.
doc.remove("_id");
doc.remove("_rev");
}
getDatabase().saveDocument(doc);
final LoggedIn l = (LoggedIn) JSONObject.toBean(doc.getJSONObject(), LoggedIn.class);
final JSONArray vs = doc.getJSONObject().optJSONArray("visitedSessions");
if (vs != null) {
@SuppressWarnings("unchecked")
final Collection<VisitedSession> visitedSessions = JSONArray.toCollection(vs, VisitedSession.class);
l.setVisitedSessions(new ArrayList<VisitedSession>(visitedSessions));
}
return l;
} catch (final IOException e) {
return null;
}
}
@Override
@CachePut(value = "sessions")
public Session updateSessionOwnerActivity(final Session session) {
try {
/* Do not clutter CouchDB. Only update once every 3 hours. */
if (session.getLastOwnerActivity() > System.currentTimeMillis() - 3 * 3600000) {
return session;
}
session.setLastOwnerActivity(System.currentTimeMillis());
final JSONObject json = JSONObject.fromObject(session);
getDatabase().saveDocument(new Document(json));
return session;
} catch (final IOException e) {
LOGGER.error("Failed to update lastOwnerActivity for Session {}", session);
return session;
}
}
@Override
public List<String> getQuestionIds(final Session session, final User user) {
NovaView view = new NovaView("skill_question/by_session_only_id_for_all");
view.setKey(session.get_id());
return collectQuestionIds(view);
}
@Caching(evict = { @CacheEvict("questions"),
@CacheEvict(value = "skillquestions", allEntries = true),
@CacheEvict(value = "lecturequestions", allEntries = true),
@CacheEvict(value = "preparationquestions", allEntries = true),
@CacheEvict(value = "flashcardquestions", allEntries = true) })
@Override
public void deleteQuestionWithAnswers(final Question question) {
try {
deleteAnswers(question);
deleteDocument(question.get_id());
} catch (final IOException e) {
LOGGER.error("IOException: Could not delete question {}", question.get_id());
}
}
@Caching(evict = { @CacheEvict("questions"),
@CacheEvict(value = "skillquestions", allEntries = true),
@CacheEvict(value = "lecturequestions", allEntries = true),
@CacheEvict(value = "preparationquestions", allEntries = true),
@CacheEvict(value = "flashcardquestions", allEntries = true) })
@Override
public void deleteAllQuestionsWithAnswers(final Session session) {
final NovaView view = new NovaView("skill_question/by_session");
deleteAllQuestionDocumentsWithAnswers(session, view);
}
private void deleteAllQuestionDocumentsWithAnswers(final Session session, final NovaView view) {
view.setStartKeyArray(session.get_id());
view.setEndKey(session.get_id(), "{}");
final ViewResults results = getDatabase().view(view);
List<Question> questions = new ArrayList<Question>();
for (final Document d : results.getResults()) {
final Question q = new Question();
q.set_id(d.getId());
q.set_rev(d.getJSONObject("value").getString("_rev"));
questions.add(q);
}
deleteAllAnswersWithQuestions(questions);
}
private void deleteDocument(final String documentId) throws IOException {
final Document d = getDatabase().getDocument(documentId);
getDatabase().deleteDocument(d);
}
@CacheEvict("answers")
@Override
public void deleteAnswers(final Question question) {
try {
final NovaView view = new NovaView("answer/cleanup");
view.setKey(question.get_id());
view.setIncludeDocs(true);
final ViewResults results = getDatabase().view(view);
List<Document> answersToDelete = new ArrayList<Document>();
for (final Document a : results.getResults()) {
final Document d = new Document(a.getJSONObject("doc"));
d.put("_deleted", true);
answersToDelete.add(d);
}
database.bulkSaveDocuments(answersToDelete.toArray(new Document[answersToDelete.size()]));
} catch (final IOException e) {
LOGGER.error("IOException: Could not delete answers for question {}", question.get_id());
}
}
@Override
public List<String> getUnAnsweredQuestionIds(final Session session, final User user) {
final NovaView view = new NovaView("answer/by_user");
view.setKey(user.getUsername(), session.get_id());
return collectUnansweredQuestionIds(getQuestionIds(session, user), view);
}
@Override
public Answer getMyAnswer(final User me, final String questionId, final int piRound) {
final NovaView view = new NovaView("answer/by_question_and_user_and_piround");
if (2 == piRound) {
view.setKey(questionId, me.getUsername(), "2");
} else {
/* needed for legacy questions whose piRound property has not been set */
view.setStartKey(questionId, me.getUsername());
view.setEndKey(questionId, me.getUsername(), "1");
}
final ViewResults results = getDatabase().view(view);
if (results.getResults().isEmpty()) {
return null;
}
return (Answer) JSONObject.toBean(
results.getJSONArray("rows").optJSONObject(0).optJSONObject("value"),
Answer.class
);
}
@Override
public List<Answer> getAnswers(final Question question, final int piRound) {
final String questionId = question.get_id();
final NovaView view = new NovaView("skill_question/count_answers_by_question_and_piround");
if (2 == piRound) {
view.setStartKey(questionId, "2");
view.setEndKey(questionId, "2", "{}");
} else {
/* needed for legacy questions whose piRound property has not been set */
view.setStartKeyArray(questionId);
view.setEndKeyArray(questionId, "1", "{}");
}
view.setGroup(true);
final ViewResults results = getDatabase().view(view);
final int abstentionCount = getAbstentionAnswerCount(questionId);
final List<Answer> answers = new ArrayList<Answer>();
for (final Document d : results.getResults()) {
final Answer a = new Answer();
a.setAnswerCount(d.getInt("value"));
a.setAbstentionCount(abstentionCount);
a.setQuestionId(d.getJSONObject().getJSONArray("key").getString(0));
a.setPiRound(piRound);
final String answerText = d.getJSONObject().getJSONArray("key").getString(2);
a.setAnswerText("null".equals(answerText) ? null : answerText);
answers.add(a);
}
return answers;
}
@Cacheable("answers")
@Override
public List<Answer> getAnswers(final Question question) {
return this.getAnswers(question, question.getPiRound());
}
@Override
public int getAbstentionAnswerCount(final String questionId) {
final NovaView view = new NovaView("skill_question/count_abstention_answers_by_question");
view.setKey(questionId);
view.setGroup(true);
final ViewResults results = getDatabase().view(view);
if (results.getResults().size() == 0) {
return 0;
}
return results.getJSONArray("rows").optJSONObject(0).optInt("value");
}
@Override
public int getAnswerCount(final Question question, final int piRound) {
final NovaView view = new NovaView("skill_question/count_total_answers_by_question_and_piround");
view.setGroup(true);
view.setStartKey(question.get_id(), String.valueOf(piRound));
view.setEndKey(question.get_id(), String.valueOf(piRound), "{}");
final ViewResults results = getDatabase().view(view);
if (results.getResults().size() == 0) {
return 0;
}
return results.getJSONArray("rows").optJSONObject(0).optInt("value");
}
private boolean isEmptyResults(final ViewResults results) {
return results == null || results.getResults().isEmpty() || results.getJSONArray("rows").size() == 0;
}
@Override
public List<Answer> getFreetextAnswers(final String questionId) {
final List<Answer> answers = new ArrayList<Answer>();
final NovaView view = new NovaView("skill_question/freetext_answers_full");
view.setKey(questionId);
final ViewResults results = getDatabase().view(view);
if (results.getResults().isEmpty()) {
return answers;
}
for (final Document d : results.getResults()) {
final Answer a = (Answer) JSONObject.toBean(d.getJSONObject().getJSONObject("value"), Answer.class);
a.setQuestionId(questionId);
answers.add(a);
}
return answers;
}
@Override
public List<Answer> getMyAnswers(final User me, final Session s) {
final NovaView view = new NovaView("answer/by_user_and_session_full");
view.setKey(me.getUsername(), s.get_id());
final ViewResults results = getDatabase().view(view);
final List<Answer> answers = new ArrayList<Answer>();
if (results == null || results.getResults() == null || results.getResults().isEmpty()) {
return answers;
}
for (final Document d : results.getResults()) {
final Answer a = (Answer) JSONObject.toBean(d.getJSONObject().getJSONObject("value"), Answer.class);
a.set_id(d.getId());
a.set_rev(d.getRev());
a.setUser(me.getUsername());
a.setSessionId(s.get_id());
answers.add(a);
}
return answers;
}
@Override
public int getTotalAnswerCount(final String sessionKey) {
final Session s = getSessionFromKeyword(sessionKey);
if (s == null) {
throw new NotFoundException();
}
final NovaView view = new NovaView("skill_question/count_answers_by_session");
view.setKey(s.get_id());
final ViewResults results = getDatabase().view(view);
if (results.getResults().size() == 0) {
return 0;
}
return results.getJSONArray("rows").optJSONObject(0).optInt("value");
}
@Override
public int getInterposedCount(final String sessionKey) {
final Session s = getSessionFromKeyword(sessionKey);
if (s == null) {
throw new NotFoundException();
}
final NovaView view = new NovaView("interposed_question/count_by_session");
view.setKey(s.get_id());
view.setGroup(true);
final ViewResults results = getDatabase().view(view);
if (results.size() == 0 || results.getResults().size() == 0) {
return 0;
}
return results.getJSONArray("rows").optJSONObject(0).optInt("value");
}
@Override
public InterposedReadingCount getInterposedReadingCount(final Session session) {
final NovaView view = new NovaView("interposed_question/count_by_session_reading");
view.setStartKeyArray(session.get_id());
view.setEndKeyArray(session.get_id(), "{}");
view.setGroup(true);
return getInterposedReadingCount(view);
}
@Override
public InterposedReadingCount getInterposedReadingCount(final Session session, final User user) {
final NovaView view = new NovaView("interposed_question/count_by_session_reading_for_creator");
view.setStartKeyArray(session.get_id(), user.getUsername());
view.setEndKeyArray(session.get_id(), user.getUsername(), "{}");
view.setGroup(true);
return getInterposedReadingCount(view);
}
private InterposedReadingCount getInterposedReadingCount(final NovaView view) {
final ViewResults results = getDatabase().view(view);
if (results.size() == 0 || results.getResults().size() == 0) {
return new InterposedReadingCount();
}
// A complete result looks like this. Note that the second row is optional, and that the first one may be
// 'unread' or 'read', i.e., results may be switched around or only one result may be present.
// count = {"rows":[
// {"key":["cecebabb21b096e592d81f9c1322b877","Guestc9350cf4a3","read"],"value":1},
// {"key":["cecebabb21b096e592d81f9c1322b877","Guestc9350cf4a3","unread"],"value":1}
// ]}
int read = 0, unread = 0;
String type = "";
final JSONObject fst = results.getJSONArray("rows").getJSONObject(0);
final JSONObject snd = results.getJSONArray("rows").optJSONObject(1);
final JSONArray fstkey = fst.getJSONArray("key");
if (fstkey.size() == 2) {
type = fstkey.getString(1);
} else if (fstkey.size() == 3) {
type = fstkey.getString(2);
}
if (type.equals("read")) {
read = fst.optInt("value");
} else if (type.equals("unread")) {
unread = fst.optInt("value");
}
if (snd != null) {
final JSONArray sndkey = snd.getJSONArray("key");
if (sndkey.size() == 2) {
type = sndkey.getString(1);
} else {
type = sndkey.getString(2);
}
if (type.equals("read")) {
read = snd.optInt("value");
} else if (type.equals("unread")) {
unread = snd.optInt("value");
}
}
return new InterposedReadingCount(read, unread);
}
@Override
public List<InterposedQuestion> getInterposedQuestions(final Session session) {
final NovaView view = new NovaView("interposed_question/by_session_full");
view.setKey(session.get_id());
final ViewResults questions = getDatabase().view(view);
if (questions == null || questions.isEmpty()) {
return null;
}
return createInterposedList(session, questions);
}
@Override
public List<InterposedQuestion> getInterposedQuestions(final Session session, final User user) {
final NovaView view = new NovaView("interposed_question/by_session_and_creator");
view.setKey(session.get_id(), user.getUsername());
final ViewResults questions = getDatabase().view(view);
if (questions == null || questions.isEmpty()) {
return null;
}
return createInterposedList(session, questions);
}
private List<InterposedQuestion> createInterposedList(
final Session session, final ViewResults questions) {
final List<InterposedQuestion> result = new ArrayList<InterposedQuestion>();
for (final Document document : questions.getResults()) {
final InterposedQuestion question = (InterposedQuestion) JSONObject.toBean(
document.getJSONObject().getJSONObject("value"),
InterposedQuestion.class
);
question.setSessionId(session.getKeyword());
question.set_id(document.getId());
result.add(question);
}
return result;
}
public InterposedQuestion getInterposedQuestion(final String sessionKey, final String documentId) {
try {
final Document document = getDatabase().getDocument(documentId);
if (document == null) {
LOGGER.error("Document is NULL");
return null;
}
return (InterposedQuestion) JSONObject.toBean(document.getJSONObject(), InterposedQuestion.class);
} catch (final IOException e) {
LOGGER.error("Error while retrieving interposed question", e);
}
return null;
}
@Override
public Statistics getStatistics() {
final Statistics stats = new Statistics();
try {
final View statsView = new View("statistics/statistics");
final View creatorView = new View("statistics/unique_session_creators");
statsView.setGroup(true);
creatorView.setGroup(true);
final ViewResults statsResults = getDatabase().view(statsView);
final ViewResults creatorResults = getDatabase().view(creatorView);
if (!isEmptyResults(statsResults)) {
final JSONArray rows = statsResults.getJSONArray("rows");
for (int i = 0; i < rows.size(); i++) {
final JSONObject row = rows.getJSONObject(i);
final int value = row.getInt("value");
switch (row.getString("key")) {
case "openSessions":
stats.setOpenSessions(stats.getOpenSessions() + value);
break;
case "closedSessions":
stats.setClosedSessions(stats.getClosedSessions() + value);
break;
case "answers":
stats.setAnswers(stats.getAnswers() + value);
break;
case "lectureQuestions":
stats.setLectureQuestions(stats.getLectureQuestions() + value);
break;
case "preparationQuestions":
stats.setPreparationQuestions(stats.getPreparationQuestions() + value);
break;
case "interposedQuestions":
stats.setInterposedQuestions(stats.getInterposedQuestions() + value);
break;
case "conceptQuestions":
stats.setConceptQuestions(stats.getConceptQuestions() + value);
break;
}
}
}
if (!isEmptyResults(creatorResults)) {
final JSONArray rows = creatorResults.getJSONArray("rows");
Set<String> creators = new HashSet<String>();
for (int i = 0; i < rows.size(); i++) {
final JSONObject row = rows.getJSONObject(i);
creators.add(row.getString("key"));
}
stats.setCreators(creators.size());
}
return stats;
} catch (final Exception e) {
LOGGER.error("Error while retrieving session count", e);
}
return stats;
}
@Override
public InterposedQuestion getInterposedQuestion(final String questionId) {
try {
final Document document = getDatabase().getDocument(questionId);
final InterposedQuestion question = (InterposedQuestion) JSONObject.toBean(document.getJSONObject(),
InterposedQuestion.class);
question.setSessionId(getSessionKeyword(question.getSessionId()));
return question;
} catch (final IOException e) {
LOGGER.error("Could not load interposed question {}", questionId);
}
return null;
}
@Override
public void markInterposedQuestionAsRead(final InterposedQuestion question) {
try {
question.setRead(true);
final Document document = getDatabase().getDocument(question.get_id());
document.put("read", question.isRead());
getDatabase().saveDocument(document);
} catch (final IOException e) {
LOGGER.error("Coulg not mark interposed question as read {}", question.get_id());
}
}
@Override
public List<Session> getMyVisitedSessions(final User user) {
final NovaView view = new NovaView("logged_in/visited_sessions_by_user");
view.setKey(user.getUsername());
final ViewResults sessions = getDatabase().view(view);
final List<Session> allSessions = new ArrayList<Session>();
for (final Document d : sessions.getResults()) {
// Not all users have visited sessions
if (d.getJSONObject().optJSONArray("value") != null) {
@SuppressWarnings("unchecked")
final Collection<Session> visitedSessions = JSONArray.toCollection(
d.getJSONObject().getJSONArray("value"),
Session.class
);
allSessions.addAll(visitedSessions);
}
}
// Filter sessions that don't exist anymore, also filter my own sessions
final List<Session> result = new ArrayList<Session>();
final List<Session> filteredSessions = new ArrayList<Session>();
for (final Session s : allSessions) {
try {
final Session session = getSessionFromKeyword(s.getKeyword());
if (session != null && !session.isCreator(user)) {
result.add(session);
} else {
filteredSessions.add(s);
}
} catch (final NotFoundException e) {
filteredSessions.add(s);
}
}
if (filteredSessions.isEmpty()) {
return result;
}
// Update document to remove sessions that don't exist anymore
try {
List<VisitedSession> visitedSessions = new ArrayList<VisitedSession>();
for (final Session s : result) {
visitedSessions.add(new VisitedSession(s));
}
final LoggedIn loggedIn = new LoggedIn();
final Document loggedInDocument = getDatabase().getDocument(sessions.getResults().get(0).getString("id"));
loggedIn.setSessionId(loggedInDocument.getString("sessionId"));
loggedIn.setUser(user.getUsername());
loggedIn.setTimestamp(loggedInDocument.getLong("timestamp"));
loggedIn.setType(loggedInDocument.getString("type"));
loggedIn.setVisitedSessions(visitedSessions);
loggedIn.set_id(loggedInDocument.getId());
loggedIn.set_rev(loggedInDocument.getRev());
final JSONObject json = JSONObject.fromObject(loggedIn);
final Document doc = new Document(json);
getDatabase().saveDocument(doc);
} catch (IOException e) {
LOGGER.error("Could not clean up logged_in document of {}", user.getUsername());
}
return result;
}
@Override
public List<SessionInfo> getMyVisitedSessionsInfo(final User user) {
List<Session> sessions = this.getMyVisitedSessions(user);
if (sessions.isEmpty()) {
return new ArrayList<SessionInfo>();
}
return this.getInfosForVisitedSessions(sessions, user);
}
@CacheEvict(value = "answers", allEntries = true)
@Override
public Answer saveAnswer(final Answer answer, final User user, final Question question, final Session session) {
final Document a = new Document();
a.put("type", "skill_question_answer");
a.put("sessionId", answer.getSessionId());
a.put("questionId", answer.getQuestionId());
a.put("answerSubject", answer.getAnswerSubject());
a.put("questionVariant", answer.getQuestionVariant());
a.put("questionValue", answer.getQuestionValue());
a.put("answerText", answer.getAnswerText());
a.put("timestamp", answer.getTimestamp());
a.put("user", user.getUsername());
a.put("piRound", answer.getPiRound());
a.put("abstention", answer.isAbstention());
AnswerQueueElement answerQueueElement = new AnswerQueueElement(session, question, answer, user);
this.answerQueue.offer(new AbstractMap.SimpleEntry<Document, AnswerQueueElement>(a, answerQueueElement));
return answer;
}
@Scheduled(fixedDelay = 5000)
public void flushAnswerQueue() {
final Map<Document, Answer> map = new HashMap<Document, Answer>();
final List<Document> answerList = new ArrayList<Document>();
final List<AnswerQueueElement> elements = new ArrayList<AnswerQueueElement>();
AbstractMap.SimpleEntry<Document, AnswerQueueElement> entry;
while ((entry = this.answerQueue.poll()) != null) {
final Document doc = entry.getKey();
final Answer answer = entry.getValue().getAnswer();
map.put(doc, answer);
answerList.add(doc);
elements.add(entry.getValue());
}
if (answerList.isEmpty()) {
// no need to send an empty bulk request. ;-)
return;
}
try {
getDatabase().bulkSaveDocuments(answerList.toArray(new Document[answerList.size()]));
for (Document d : answerList) {
final Answer answer = map.get(d);
answer.set_id(d.getId());
answer.set_rev(d.getRev());
}
// Send NewAnswerEvents ...
for (AnswerQueueElement e : elements) {
this.publisher.publishEvent(new NewAnswerEvent(this, e.getSession(), e.getAnswer(), e.getUser(), e.getQuestion()));
}
} catch (IOException e) {
LOGGER.error("Could not bulk save answers from queue");
}
}
@CacheEvict(value = "answers", allEntries = true)
@Override
public Answer updateAnswer(final Answer answer) {
try {
final Document a = database.getDocument(answer.get_id());
a.put("answerSubject", answer.getAnswerSubject());
a.put("answerText", answer.getAnswerText());
a.put("timestamp", answer.getTimestamp());
a.put("abstention", answer.isAbstention());
a.put("questionValue", answer.getQuestionValue());
database.saveDocument(a);
answer.set_rev(a.getRev());
return answer;
} catch (final IOException e) {
LOGGER.error("Could not save answer {}", answer);
}
return null;
}
@CacheEvict(value = "answers", allEntries = true)
@Override
public void deleteAnswer(final String answerId) {
try {
database.deleteDocument(database.getDocument(answerId));
} catch (final IOException e) {
LOGGER.error("Could not delete answer {} because of {}", answerId, e.getMessage());
}
}
@Override
public void deleteInterposedQuestion(final InterposedQuestion question) {
try {
deleteDocument(question.get_id());
} catch (final IOException e) {
LOGGER.error("Could not delete interposed question {} because of {}", question.get_id(), e.getMessage());
}
}
@Override
public List<Session> getCourseSessions(final List<Course> courses) {
final ExtendedView view = new ExtendedView("session/by_courseid");
view.setCourseIdKeys(courses);
final ViewResults sessions = getDatabase().view(view);
final List<Session> result = new ArrayList<Session>();
for (final Document d : sessions.getResults()) {
final Session session = (Session) JSONObject.toBean(
d.getJSONObject().getJSONObject("value"),
Session.class
);
result.add(session);
}
return result;
}
private static class ExtendedView extends NovaView {
public ExtendedView(final String fullname) {
super(fullname);
}
public void setCourseIdKeys(final List<Course> courses) {
List<String> courseIds = new ArrayList<String>();
for (Course c : courses) {
courseIds.add(c.getId());
}
setKeys(courseIds);
}
public void setSessionIdKeys(final List<Session> sessions) {
List<String> sessionIds = new ArrayList<String>();
for (Session s : sessions) {
sessionIds.add(s.get_id());
}
setKeys(sessionIds);
}
}
@Override
@CachePut(value = "sessions")
public Session updateSession(final Session session) {
try {
final Document s = database.getDocument(session.get_id());
s.put("name", session.getName());
s.put("shortName", session.getShortName());
s.put("active", session.isActive());
s.put("learningProgressType", session.getLearningProgressType());
database.saveDocument(s);
session.set_rev(s.getRev());
return session;
} catch (final IOException e) {
LOGGER.error("Could not lock session {}", session);
}
return null;
}
@Override
@CacheEvict("sessions")
public void deleteSession(final Session session) {
try {
deleteDocument(session.get_id());
} catch (final IOException e) {
LOGGER.error("Could not delete session {}", session);
}
}
@Cacheable("lecturequestions")
@Override
public List<Question> getLectureQuestionsForUsers(final Session session) {
String viewName = "skill_question/lecture_question_by_session_for_all";
return getQuestions(new NovaView(viewName), session);
}
@Override
public List<Question> getLectureQuestionsForTeachers(final Session session) {
String viewName = "skill_question/lecture_question_by_session";
return getQuestions(new NovaView(viewName), session);
}
@Cacheable("flashcardquestions")
@Override
public List<Question> getFlashcardsForUsers(final Session session) {
String viewName = "skill_question/flashcard_by_session_for_all";
return getQuestions(new NovaView(viewName), session);
}
@Override
public List<Question> getFlashcardsForTeachers(final Session session) {
String viewName = "skill_question/flashcard_by_session";
return getQuestions(new NovaView(viewName), session);
}
@Cacheable("preparationquestions")
@Override
public List<Question> getPreparationQuestionsForUsers(final Session session) {
String viewName = "skill_question/preparation_question_by_session_for_all";
return getQuestions(new NovaView(viewName), session);
}
@Override
public List<Question> getPreparationQuestionsForTeachers(final Session session) {
String viewName = "skill_question/preparation_question_by_session";
return getQuestions(new NovaView(viewName), session);
}
private List<Question> getQuestions(final NovaView view, final Session session) {
view.setStartKeyArray(session.get_id());
view.setEndKeyArray(session.get_id(), "{}");
final ViewResults questions = getDatabase().view(view);
if (questions == null || questions.isEmpty()) {
return null;
}
final List<Question> result = new ArrayList<Question>();
final MorpherRegistry morpherRegistry = JSONUtils.getMorpherRegistry();
final Morpher dynaMorpher = new BeanMorpher(PossibleAnswer.class, morpherRegistry);
morpherRegistry.registerMorpher(dynaMorpher);
for (final Document document : questions.getResults()) {
final Question question = (Question) JSONObject.toBean(
document.getJSONObject().getJSONObject("value"),
Question.class
);
@SuppressWarnings("unchecked")
final
Collection<PossibleAnswer> answers = JSONArray.toCollection(
document.getJSONObject().getJSONObject("value").getJSONArray("possibleAnswers"),
PossibleAnswer.class
);
question.setPossibleAnswers(new ArrayList<PossibleAnswer>(answers));
question.setSessionKeyword(session.getKeyword());
if (!"freetext".equals(question.getQuestionType()) && 0 == question.getPiRound()) {
/* needed for legacy questions whose piRound property has not been set */
question.setPiRound(1);
}
result.add(question);
}
return result;
}
@Override
public int getLectureQuestionCount(final Session session) {
return getQuestionCount(new NovaView("skill_question/lecture_question_count_by_session"), session);
}
@Override
public int getFlashcardCount(final Session session) {
return getQuestionCount(new NovaView("skill_question/flashcard_count_by_session"), session);
}
@Override
public int getPreparationQuestionCount(final Session session) {
return getQuestionCount(new NovaView("skill_question/preparation_question_count_by_session"), session);
}
private int getQuestionCount(final NovaView view, final Session session) {
view.setKey(session.get_id());
final ViewResults results = getDatabase().view(view);
if (results.getJSONArray("rows").optJSONObject(0) == null) {
return 0;
}
return results.getJSONArray("rows").optJSONObject(0).optInt("value");
}
@Override
public int countLectureQuestionAnswers(final Session session) {
return countQuestionVariantAnswers(session, "lecture");
}
@Override
public int countPreparationQuestionAnswers(final Session session) {
return countQuestionVariantAnswers(session, "preparation");
}
private int countQuestionVariantAnswers(final Session session, final String variant) {
final NovaView view = new NovaView("skill_question/count_answers_by_session_and_question_variant");
view.setKey(session.get_id(), variant);
final ViewResults results = getDatabase().view(view);
if (results.getResults().size() == 0) {
return 0;
}
return results.getJSONArray("rows").optJSONObject(0).optInt("value");
}
@Caching(evict = { @CacheEvict(value = "questions", allEntries = true),
@CacheEvict(value = "skillquestions", allEntries = true),
@CacheEvict("lecturequestions"),
@CacheEvict(value = "answers", allEntries = true)})
@Override
public void deleteAllLectureQuestionsWithAnswers(final Session session) {
final NovaView view = new NovaView("skill_question/lecture_question_by_session");
deleteAllQuestionDocumentsWithAnswers(session, view);
}
@Caching(evict = { @CacheEvict(value = "questions", allEntries = true),
@CacheEvict(value = "skillquestions", allEntries = true),
@CacheEvict("flashcardquestions"),
@CacheEvict(value = "answers", allEntries = true)})
@Override
public void deleteAllFlashcardsWithAnswers(final Session session) {
final NovaView view = new NovaView("skill_question/flashcard_by_session");
deleteAllQuestionDocumentsWithAnswers(session, view);
}
@Caching(evict = { @CacheEvict(value = "questions", allEntries = true),
@CacheEvict(value = "skillquestions", allEntries = true),
@CacheEvict("preparationquestions"),
@CacheEvict(value = "answers", allEntries = true)})
@Override
public void deleteAllPreparationQuestionsWithAnswers(final Session session) {
final NovaView view = new NovaView("skill_question/preparation_question_by_session");
deleteAllQuestionDocumentsWithAnswers(session, view);
}
@Override
public List<String> getUnAnsweredLectureQuestionIds(final Session session, final User user) {
final NovaView view = new NovaView("answer/variant_by_user");
view.setKey(user.getUsername(), session.get_id(), "lecture");
return collectUnansweredQuestionIds(getLectureQuestionIds(session), view);
}
private List<String> getLectureQuestionIds(final Session session) {
NovaView view = new NovaView("skill_question/lecture_question_ids_by_session_for_all");
view.setStartKeyArray(session.get_id());
view.setEndKeyArray(session.get_id(), "{}");
return collectQuestionIds(view);
}
@Override
public List<String> getUnAnsweredPreparationQuestionIds(final Session session, final User user) {
final NovaView view = new NovaView("answer/variant_by_user");
view.setKey(user.getUsername(), session.get_id(), "preparation");
return collectUnansweredQuestionIds(getPreparationQuestionIds(session), view);
}
private List<String> getPreparationQuestionIds(final Session session) {
NovaView view = new NovaView("skill_question/preparation_question_ids_by_session_for_all");
view.setStartKeyArray(session.get_id());
view.setEndKeyArray(session.get_id(), "{}");
return collectQuestionIds(view);
}
private List<String> collectUnansweredQuestionIds(
final List<String> questions,
final NovaView view
) {
final ViewResults answeredQuestions = getDatabase().view(view);
final List<String> answered = new ArrayList<String>();
for (final Document d : answeredQuestions.getResults()) {
answered.add(d.getString("value"));
}
final List<String> unanswered = new ArrayList<String>();
for (final String questionId : questions) {
if (!answered.contains(questionId)) {
unanswered.add(questionId);
}
}
return unanswered;
}
private List<String> collectQuestionIds(final NovaView view) {
final ViewResults results = getDatabase().view(view);
if (results.getResults().size() == 0) {
return new ArrayList<String>();
}
final List<String> ids = new ArrayList<String>();
for (final Document d : results.getResults()) {
ids.add(d.getId());
}
return ids;
}
@Override
public void deleteAllInterposedQuestions(final Session session) {
final NovaView view = new NovaView("interposed_question/by_session");
view.setKey(session.get_id());
final ViewResults questions = getDatabase().view(view);
deleteAllInterposedQuestions(session, questions);
}
@Override
public void deleteAllInterposedQuestions(final Session session, final User user) {
final NovaView view = new NovaView("interposed_question/by_session_and_creator");
view.setKey(session.get_id(), user.getUsername());
final ViewResults questions = getDatabase().view(view);
deleteAllInterposedQuestions(session, questions);
}
private void deleteAllInterposedQuestions(final Session session, final ViewResults questions) {
if (questions == null || questions.isEmpty()) {
return;
}
for (final Document document : questions.getResults()) {
try {
deleteDocument(document.getId());
} catch (final IOException e) {
LOGGER.error("Could not delete all interposed questions {}", session);
}
}
}
@Override
public void publishAllQuestions(final Session session, final boolean publish) {
final List<Question> questions = getQuestions(new NovaView("skill_question/by_session"), session);
publishQuestions(session, publish, questions);
}
@Caching(evict = { @CacheEvict(value = "questions", allEntries = true),
@CacheEvict(value = "skillquestions", allEntries = true),
@CacheEvict(value = "lecturequestions", allEntries = true),
@CacheEvict(value = "preparationquestions", allEntries = true),
@CacheEvict(value = "flashcardquestions", allEntries = true) })
@Override
public void publishQuestions(final Session session, final boolean publish, List<Question> questions) {
for (final Question q : questions) {
q.setActive(publish);
}
final List<Document> documents = new ArrayList<Document>();
for (final Question q : questions) {
final Document d = toQuestionDocument(session, q);
d.setId(q.get_id());
d.setRev(q.get_rev());
documents.add(d);
}
try {
database.bulkSaveDocuments(documents.toArray(new Document[documents.size()]));
} catch (final IOException e) {
LOGGER.error("Could not bulk publish all questions: {}", e.getMessage());
}
}
@CacheEvict(value = "answers", allEntries = true)
@Override
public void deleteAllQuestionsAnswers(final Session session) {
final List<Question> questions = getQuestions(new NovaView("skill_question/by_session"), session);
deleteAllAnswersForQuestions(questions);
}
@CacheEvict(value = "answers", allEntries = true)
@Override
public void deleteAllPreparationAnswers(final Session session) {
final List<Question> questions = getQuestions(new NovaView("skill_question/preparation_question_by_session"), session);
deleteAllAnswersForQuestions(questions);
}
@CacheEvict(value = "answers", allEntries = true)
@Override
public void deleteAllLectureAnswers(final Session session) {
final List<Question> questions = getQuestions(new NovaView("skill_question/lecture_question_by_session"), session);
deleteAllAnswersForQuestions(questions);
}
private boolean deleteAllAnswersForQuestions(List<Question> questions) {
List<String> questionIds = new ArrayList<String>();
for (Question q : questions) {
questionIds.add(q.get_id());
}
final NovaView bulkView = new NovaView("answer/cleanup");
bulkView.setKeys(questionIds);
bulkView.setIncludeDocs(true);
final List<Document> result = getDatabase().view(bulkView).getResults();
final List<Document> allAnswers = new ArrayList<Document>();
for (Document a : result) {
final Document d = new Document(a.getJSONObject("doc"));
d.put("_deleted", true);
allAnswers.add(d);
}
try {
getDatabase().bulkSaveDocuments(allAnswers.toArray(new Document[allAnswers.size()]));
} catch (IOException e) {
LOGGER.error("Could not bulk delete answers: {}", e.getMessage());
return false;
}
return true;
}
private boolean deleteAllAnswersWithQuestions(List<Question> questions) {
List<String> questionIds = new ArrayList<String>();
final List<Document> allQuestions = new ArrayList<Document>();
for (Question q : questions) {
final Document d = new Document();
d.put("_id", q.get_id());
d.put("_rev", q.get_rev());
d.put("_deleted", true);
questionIds.add(q.get_id());
allQuestions.add(d);
}
final NovaView bulkView = new NovaView("answer/cleanup");
bulkView.setKeys(questionIds);
bulkView.setIncludeDocs(true);
final List<Document> result = getDatabase().view(bulkView).getResults();
final List<Document> allAnswers = new ArrayList<Document>();
for (Document a : result) {
final Document d = new Document(a.getJSONObject("doc"));
d.put("_deleted", true);
allAnswers.add(d);
}
try {
List<Document> deleteList = new ArrayList<Document>(allAnswers);
deleteList.addAll(allQuestions);
getDatabase().bulkSaveDocuments(deleteList.toArray(new Document[deleteList.size()]));
} catch (IOException e) {
LOGGER.error("Could not bulk delete questions and answers: {}", e.getMessage());
return false;
}
return true;
}
@Cacheable("learningprogress")
@Override
public CourseScore getLearningProgress(final Session session) {
final NovaView maximumValueView = new NovaView("learning_progress/maximum_value_of_question");
final NovaView answerSumView = new NovaView("learning_progress/question_value_achieved_for_user");
maximumValueView.setStartKeyArray(session.get_id());
maximumValueView.setEndKeyArray(session.get_id(), "{}");
answerSumView.setStartKeyArray(session.get_id());
answerSumView.setEndKeyArray(session.get_id(), "{}");
final List<Document> maximumValueResult = getDatabase().view(maximumValueView).getResults();
final List<Document> answerSumResult = getDatabase().view(answerSumView).getResults();
CourseScore courseScore = new CourseScore();
// no results found
if (maximumValueResult.isEmpty() && answerSumResult.isEmpty()) {
return courseScore;
}
// collect mapping (questionId -> max value)
for (Document d : maximumValueResult) {
String questionId = d.getJSONArray("key").getString(1);
int questionScore = d.getInt("value");
courseScore.add(questionId, questionScore);
}
// collect mapping (questionId -> (user -> value))
for (Document d : answerSumResult) {
String username = d.getJSONArray("key").getString(1);
JSONObject value = d.getJSONObject("value");
String questionId = value.getString("questionId");
int userscore = value.getInt("score");
courseScore.add(questionId, username, userscore);
}
return courseScore;
}
@Override
public DbUser createOrUpdateUser(DbUser user) {
try {
String id = user.getId();
String rev = user.getRev();
Document d = new Document();
if (null != id) {
d = database.getDocument(id, rev);
}
d.put("type", "userdetails");
d.put("username", user.getUsername());
d.put("password", user.getPassword());
d.put("activationKey", user.getActivationKey());
d.put("passwordResetKey", user.getPasswordResetKey());
d.put("passwordResetTime", user.getPasswordResetTime());
d.put("creation", user.getCreation());
d.put("lastLogin", user.getLastLogin());
database.saveDocument(d, id);
user.setId(d.getId());
user.setRev(d.getRev());
return user;
} catch (IOException e) {
LOGGER.error("Could not save user {}", user);
}
return null;
}
@Override
public DbUser getUser(String username) {
NovaView view = new NovaView("user/all");
view.setKey(username);
ViewResults results = this.getDatabase().view(view);
if (results.getJSONArray("rows").optJSONObject(0) == null) {
return null;
}
return (DbUser) JSONObject.toBean(
results.getJSONArray("rows").optJSONObject(0).optJSONObject("value"),
DbUser.class
);
}
@Override
public boolean deleteUser(DbUser dbUser) {
try {
this.deleteDocument(dbUser.getId());
return true;
} catch (IOException e) {
LOGGER.error("Could not delete user {}", dbUser.getId());
}
return false;
}
@Override
public SessionInfo importSession(User user, ImportExportSession importSession) {
final Session session = this.saveSession(user, importSession.generateSessionEntity(user));
List<Document> questions = new ArrayList<Document>();
// We need to remember which answers belong to which question.
// The answers need a questionId, so we first store the questions to get the IDs.
// Then we update the answer objects and store them as well.
Map<Document, ImportExportQuestion> mapping = new HashMap<Document, ImportExportQuestion>();
// Later, generate all answer documents
List<Document> answers = new ArrayList<Document>();
// We can then push answers together with interposed questions in one large bulk request
List<Document> interposedQuestions = new ArrayList<Document>();
try {
// add session id to all questions and generate documents
for (ImportExportQuestion question : importSession.getQuestions()) {
Document doc = toQuestionDocument(session, question);
question.setSessionId(session.get_id());
questions.add(doc);
mapping.put(doc, question);
}
database.bulkSaveDocuments(questions.toArray(new Document[questions.size()]));
// bulk import answers together with interposed questions
for (Entry<Document, ImportExportQuestion> entry : mapping.entrySet()) {
final Document doc = entry.getKey();
final ImportExportQuestion question = entry.getValue();
question.set_id(doc.getId());
question.set_rev(doc.getRev());
for (de.thm.arsnova.entities.transport.Answer answer : question.getAnswers()) {
final Answer a = answer.generateAnswerEntity(user, question);
final Document answerDoc = new Document();
answerDoc.put("type", "skill_question_answer");
answerDoc.put("sessionId", a.getSessionId());
answerDoc.put("questionId", a.getQuestionId());
answerDoc.put("answerSubject", a.getAnswerSubject());
answerDoc.put("questionVariant", a.getQuestionVariant());
answerDoc.put("questionValue", a.getQuestionValue());
answerDoc.put("answerText", a.getAnswerText());
answerDoc.put("timestamp", a.getTimestamp());
answerDoc.put("piRound", a.getPiRound());
answerDoc.put("abstention", a.isAbstention());
// we do not store the user's name
answerDoc.put("user", "");
answers.add(answerDoc);
}
}
for (de.thm.arsnova.entities.transport.InterposedQuestion i : importSession.getFeedbackQuestions()) {
final Document q = new Document();
q.put("type", "interposed_question");
q.put("sessionId", session.get_id());
q.put("subject", i.getSubject());
q.put("text", i.getText());
q.put("timestamp", i.getTimestamp());
q.put("read", i.isRead());
// we do not store the creator's name
q.put("creator", "");
interposedQuestions.add(q);
}
List<Document> documents = new ArrayList<Document>(answers);
documents.addAll(interposedQuestions);
database.bulkSaveDocuments(documents.toArray(new Document[documents.size()]));
} catch (IOException e) {
LOGGER.error("Could not import this session: {}", e.getMessage());
// Something went wrong, delete this session since we do not want a partial import
this.deleteSession(session);
return null;
}
// Calculate some statistics...
int unreadInterposed = 0;
for (de.thm.arsnova.entities.transport.InterposedQuestion i : importSession.getFeedbackQuestions()) {
if (!i.isRead()) {
unreadInterposed++;
}
}
int numUnanswered = 0;
for (ImportExportQuestion question : importSession.getQuestions()) {
if (question.getAnswers().size() == 0) {
numUnanswered++;
}
}
final SessionInfo info = new SessionInfo(session);
info.setNumQuestions(questions.size());
info.setNumUnanswered(numUnanswered);
info.setNumAnswers(answers.size());
info.setNumInterposed(interposedQuestions.size());
info.setNumUnredInterposed(unreadInterposed);
return info;
}
@Override
public List<String> getSubjects(Session session, String questionVariant) {
String viewString = "";
if ("lecture".equals(questionVariant)) {
viewString = "skill_question/lecture_question_subjects_by_session";
}
else {
viewString = "skill_question/preparation_question_subjects_by_session";
}
NovaView view = new NovaView(viewString);
view.setKey(session.get_id());
ViewResults results = this.getDatabase().view(view);
if (results.getJSONArray("rows").optJSONObject(0) == null) {
return null;
}
Set<String> uniqueSubjects = new HashSet();
for (final Document d : results.getResults()) {
uniqueSubjects.add(d.getString("value"));
}
List<String> uniqueSubjectsList = new ArrayList(uniqueSubjects);
return uniqueSubjectsList;
}
@Override
public List<String> getQuestionIdsBySubject(Session session, String questionVariant, String subject) {
String viewString = "";
if ("lecture".equals(questionVariant)) {
viewString = "skill_question/lecture_question_ids_by_session_and_subject";
}
else {
viewString = "skill_question/preparation_question_ids_by_session_and_subject";
}
NovaView view = new NovaView(viewString);
view.setKey(session.get_id(), subject);
ViewResults results = this.getDatabase().view(view);
if (results.getJSONArray("rows").optJSONObject(0) == null) {
return null;
}
List<String> qids = new ArrayList();
for (final Document d : results.getResults()) {
final String s = d.getString("value");
qids.add(s);
}
return qids;
}
@Override
public SortOrder getSortOrder(String sessionId, String questionVariant, String subject) {
String viewString = "";
if ("preparation".equals(questionVariant)) {
viewString = "sort_order/preparation_question_sort_order_by_sessionid_and_subject";
}
else if ("lecture".equals(questionVariant)) {
viewString = "sort_order/lecture_question_sort_order_by_sessionid_and_subject";
}
NovaView view = new NovaView(viewString);
view.setKey(sessionId, subject);
ViewResults results = this.getDatabase().view(view);
if (results.getResults().isEmpty()) {
return null;
}
SortOrder sortOrder = new SortOrder();
for (final Document d : results.getResults()) {
sortOrder.set_id(d.getJSONObject("value").getString("_id"));
sortOrder.set_rev(d.getJSONObject("value").getString("_rev"));
sortOrder.setSessionId(d.getJSONObject("value").getString("sessionId"));
sortOrder.setSubject(d.getJSONObject("value").getString("subject"));
sortOrder.setSortType(d.getJSONObject("value").getString("sortType"));
sortOrder.setQuestionVariant(d.getJSONObject("value").getString("questionVariant"));
List<String> sort = new ArrayList<String>();
JSONArray json = d.getJSONObject("value").getJSONArray("sortOrder");
int len = json.size();
for (int i=0; i<len; i++) {
sort.add(json.getString(i));
}
sortOrder.setSortOrder(sort);
}
return sortOrder;
}
@Override
public SortOrder createOrUpdateSortOrder(SortOrder sortOrder) {
try {
SortOrder oldSortOrder = getSortOrder(sortOrder.getSessionId(), sortOrder.getQuestionVariant(), sortOrder.getSubject());
Document d = new Document();
String id = "";
String rev = "";
if (oldSortOrder != null) {
id = oldSortOrder.get_id();
rev = oldSortOrder.get_rev();
d = database.getDocument(id, rev);
}
d.put("type", "sort_order");
d.put("sessionId", sortOrder.getSessionId());
d.put("sortType", sortOrder.getSortType());
d.put("questionVariant", sortOrder.getQuestionVariant());
d.put("subject", sortOrder.getSubject());
d.put("sortOrder", sortOrder.getSortOrder());
database.saveDocument(d, id);
sortOrder.set_id(d.getId());
sortOrder.set_rev(d.getRev());
return sortOrder;
} catch (IOException e) {
LOGGER.error("Could not save sort {}", sortOrder);
}
return null;
}
}
/*
* This file is part of ARSnova Backend.
* Copyright (C) 2012-2015 The ARSnova Team
*
* ARSnova Backend is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ARSnova Backend is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* 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 de.thm.arsnova.dao;
import java.util.List;
import de.thm.arsnova.connector.model.Course;
import de.thm.arsnova.domain.CourseScore;
import de.thm.arsnova.entities.Answer;
import de.thm.arsnova.entities.DbUser;
import de.thm.arsnova.entities.InterposedQuestion;
import de.thm.arsnova.entities.InterposedReadingCount;
import de.thm.arsnova.entities.LoggedIn;
import de.thm.arsnova.entities.Question;
import de.thm.arsnova.entities.Session;
import de.thm.arsnova.entities.SessionInfo;
import de.thm.arsnova.entities.Statistics;
import de.thm.arsnova.entities.User;
import de.thm.arsnova.entities.SortOrder;
import de.thm.arsnova.entities.transport.ImportExportSession;
public interface IDatabaseDao {
Session getSessionFromKeyword(String keyword);
List<Session> getMySessions(User user);
List<Session> getPublicPoolSessions();
List<Session> getMyPublicPoolSessions(User user);
Session saveSession(User user, Session session);
boolean sessionKeyAvailable(String keyword);
Question saveQuestion(Session session, Question question);
InterposedQuestion saveQuestion(Session session, InterposedQuestion question, User user);
Question getQuestion(String id);
/**
* @deprecated Use getSkillQuestionsForUsers or getSkillQuestionsForTeachers
* @param user
* @param session
* @return
*/
@Deprecated
List<Question> getSkillQuestions(User user, Session session);
List<Question> getSkillQuestionsForUsers(Session session);
List<Question> getSkillQuestionsForTeachers(Session session);
int getSkillQuestionCount(Session session);
LoggedIn registerAsOnlineUser(User u, Session s);
Session updateSessionOwnerActivity(Session session);
List<String> getQuestionIds(Session session, User user);
void deleteQuestionWithAnswers(Question question);
void deleteAllQuestionsWithAnswers(Session session);
List<String> getUnAnsweredQuestionIds(Session session, User user);
Answer getMyAnswer(User me, String questionId, int piRound);
List<Answer> getAnswers(Question question, int piRound);
List<Answer> getAnswers(Question question);
int getAnswerCount(Question question, int piRound);
int getAbstentionAnswerCount(String questionId);
List<Answer> getFreetextAnswers(String questionId);
List<Answer> getMyAnswers(User me, Session session);
int getTotalAnswerCount(String sessionKey);
int getInterposedCount(String sessionKey);
InterposedReadingCount getInterposedReadingCount(Session session);
InterposedReadingCount getInterposedReadingCount(Session session, User user);
List<InterposedQuestion> getInterposedQuestions(Session session);
List<InterposedQuestion> getInterposedQuestions(Session session, User user);
InterposedQuestion getInterposedQuestion(String questionId);
void markInterposedQuestionAsRead(InterposedQuestion question);
List<Session> getMyVisitedSessions(User user);
Question updateQuestion(Question question);
void deleteAnswers(Question question);
Answer saveAnswer(Answer answer, User user, Question question, Session session);
Answer updateAnswer(Answer answer);
Session getSessionFromId(String sessionId);
void deleteAnswer(String answerId);
void deleteInterposedQuestion(InterposedQuestion question);
List<Session> getCourseSessions(List<Course> courses);
Session updateSession(Session session);
void deleteSession(Session session);
List<Question> getLectureQuestionsForUsers(Session session);
List<Question> getLectureQuestionsForTeachers(Session session);
List<Question> getFlashcardsForUsers(Session session);
List<Question> getFlashcardsForTeachers(Session session);
List<Question> getPreparationQuestionsForUsers(Session session);
List<Question> getPreparationQuestionsForTeachers(Session session);
int getLectureQuestionCount(Session session);
int getFlashcardCount(Session session);
int getPreparationQuestionCount(Session session);
int countLectureQuestionAnswers(Session session);
int countPreparationQuestionAnswers(Session session);
void deleteAllLectureQuestionsWithAnswers(Session session);
void deleteAllFlashcardsWithAnswers(Session session);
void deleteAllPreparationQuestionsWithAnswers(Session session);
List<String> getUnAnsweredLectureQuestionIds(Session session, User user);
List<String> getUnAnsweredPreparationQuestionIds(Session session, User user);
void deleteAllInterposedQuestions(Session session);
void deleteAllInterposedQuestions(Session session, User user);
void publishQuestions(Session session, boolean publish, List<Question> questions);
void publishAllQuestions(Session session, boolean publish);
void deleteAllQuestionsAnswers(Session session);
DbUser createOrUpdateUser(DbUser user);
DbUser getUser(String username);
boolean deleteUser(DbUser dbUser);
CourseScore getLearningProgress(Session session);
List<SessionInfo> getMySessionsInfo(User user);
List<SessionInfo> getPublicPoolSessionsInfo();
List<SessionInfo> getMyPublicPoolSessionsInfo(final User user);
List<SessionInfo> getMyVisitedSessionsInfo(User currentUser);
void deleteAllPreparationAnswers(Session session);
void deleteAllLectureAnswers(Session session);
SessionInfo importSession(User user, ImportExportSession importSession);
Statistics getStatistics();
List<String> getSubjects(Session session, String questionVariant);
List<String> getQuestionIdsBySubject(Session session, String questionVariant, String subject);
SortOrder createOrUpdateSortOrder(SortOrder sortOrder);
SortOrder getSortOrder(String sessionkey, String questionVariant, String subject);
}
/*
* This file is part of ARSnova Backend.
* Copyright (C) 2012-2015 The ARSnova Team
*
* ARSnova Backend is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ARSnova Backend is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* 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 de.thm.arsnova.dao;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang.StringUtils;
import com.fourspaces.couchdb.View;
public class NovaView extends View {
public enum StaleMode {
NONE, OK, UPDATE_AFTER
}
protected String keys;
protected StaleMode stale = StaleMode.NONE;
protected boolean includeDocs = false;
public boolean isIncludeDocs() {
return includeDocs;
}
public void setIncludeDocs(boolean includeDocs) {
this.includeDocs = includeDocs;
}
public NovaView(final String fullname) {
super(fullname);
}
@Override
public void setStartKey(final String key) {
startKey = quote(key);
}
public void setStartKeyArray(final String key) {
if (isNumber(key)) {
startKey = encode("[" + key + "]");
} else {
startKey = encode("[\"" + key + "\"]");
}
}
public void setStartKeyArray(final String... keys) {
this.setStartKey(keys);
}
@Override
public void setEndKey(final String key) {
endKey = quote(key);
}
public void setEndKeyArray(final String key) {
if (isNumber(key)) {
endKey = encode("[" + key + "]");
} else {
endKey = encode("[\"" + key + "\"]");
}
}
public void setEndKeyArray(final String... keys) {
this.setEndKey(keys);
}
public void setStartKey(final String... keys) {
startKey = toJsonArray(keys);
}
public void setEndKey(final String... keys) {
endKey = toJsonArray(keys);
}
@Override
public void setKey(final String key) {
this.key = quote(key);
}
public void setKey(final String... keys) {
key = toJsonArray(keys);
}
public void setKeys(List<String> keys) {
this.keys = toJsonArray(keys.toArray(new String[keys.size()]));
}
public void setStale(StaleMode stale) {
this.stale = stale;
}
@Override
public String getQueryString() {
final String tempQuery = super.getQueryString();
final StringBuilder query = new StringBuilder();
if (tempQuery != null) {
query.append(tempQuery);
}
if (keys != null) {
if (query.length() > 0) {
query.append("&");
}
query.append("keys=" + keys);
}
if (stale != null && stale != StaleMode.NONE) {
if (query.length() > 0) {
query.append("&");
}
if (stale == StaleMode.OK) {
query.append("stale=ok");
} else if (stale == StaleMode.UPDATE_AFTER) {
query.append("stale=update_after");
}
}
if (includeDocs != false) {
if (query.length() > 0) {
query.append("&");
}
query.append("include_docs=true");
}
if (query.length() == 0) {
return null;
}
return query.toString();
}
private String toJsonArray(final String[] strs) {
final List<String> strings = new ArrayList<String>();
for (final String string : strs) {
if (isNumber(string) || isPlaceholder(string) || isArray(string)) {
strings.add(string);
} else {
strings.add("\"" + string + "\"");
}
}
return encode("[" + StringUtils.join(strings, ",") + "]");
}
private String quote(final String string) {
return encode("\"" + string + "\"");
}
private boolean isNumber(final String string) {
return string.matches("^[0-9]+$");
}
private boolean isPlaceholder(final String string) {
return string.equals("{}");
}
private boolean isArray(final String string) {
return string.startsWith("[") && string.endsWith("]");
}
private String encode(final String string) {
try {
return URLEncoder.encode(string, "UTF-8");
} catch (final UnsupportedEncodingException e) {
// Since we're using 'UTF-8', this should Exception should never occur.
return "";
}
}
}
package de.thm.arsnova.dao;
/*
* This file is part of ARSnova Backend.
* Copyright (C) 2012-2015 The ARSnova Team
*
* ARSnova Backend is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ARSnova Backend is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* 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 de.thm.arsnova.domain;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Component;
import de.thm.arsnova.dao.IDatabaseDao;
import de.thm.arsnova.events.ChangeLearningProgress;
import de.thm.arsnova.events.DeleteAllLectureAnswersEvent;
import de.thm.arsnova.events.DeleteAllPreparationAnswersEvent;
import de.thm.arsnova.events.DeleteAllQuestionsAnswersEvent;
import de.thm.arsnova.events.DeleteAnswerEvent;
import de.thm.arsnova.events.DeleteFeedbackForSessionsEvent;
import de.thm.arsnova.events.DeleteInterposedQuestionEvent;
import de.thm.arsnova.events.DeleteQuestionEvent;
import de.thm.arsnova.events.NewAnswerEvent;
import de.thm.arsnova.events.NewFeedbackEvent;
import de.thm.arsnova.events.NewInterposedQuestionEvent;
import de.thm.arsnova.events.NewQuestionEvent;
import de.thm.arsnova.events.NovaEventVisitor;
import de.thm.arsnova.events.PiRoundDelayedStartEvent;
import de.thm.arsnova.events.PiRoundEndEvent;
import de.thm.arsnova.events.StatusSessionEvent;
@Component
public class LearningProgressFactory implements NovaEventVisitor, ILearningProgressFactory, ApplicationEventPublisherAware {
@Autowired
private IDatabaseDao databaseDao;
private ApplicationEventPublisher publisher;
@Override
public LearningProgress createFromType(String progressType) {
if (progressType.equals("questions")) {
return new QuestionBasedLearningProgress(databaseDao);
} else {
return new PointBasedLearningProgress(databaseDao);
}
}
@Override
public void visit(NewInterposedQuestionEvent event) {}
@Override
public void visit(DeleteInterposedQuestionEvent deleteInterposedQuestionEvent) {}
@CacheEvict(value = "learningprogress", key = "#event.Session")
@Override
public void visit(NewQuestionEvent event) {
this.publisher.publishEvent(new ChangeLearningProgress(this, event.getSession()));
}
@CacheEvict(value = "learningprogress", key = "#event.Session")
@Override
public void visit(NewAnswerEvent event) {
this.publisher.publishEvent(new ChangeLearningProgress(this, event.getSession()));
}
@CacheEvict(value = "learningprogress", key = "#event.Session")
@Override
public void visit(DeleteAnswerEvent event) {
this.publisher.publishEvent(new ChangeLearningProgress(this, event.getSession()));
}
@CacheEvict(value = "learningprogress", key = "#event.Session")
@Override
public void visit(DeleteQuestionEvent event) {
this.publisher.publishEvent(new ChangeLearningProgress(this, event.getSession()));
}
@CacheEvict(value = "learningprogress", key = "#event.Session")
@Override
public void visit(DeleteAllQuestionsAnswersEvent event) {
this.publisher.publishEvent(new ChangeLearningProgress(this, event.getSession()));
}
@CacheEvict(value = "learningprogress", key = "#event.Session")
@Override
public void visit(DeleteAllPreparationAnswersEvent event) {
this.publisher.publishEvent(new ChangeLearningProgress(this, event.getSession()));
}
@CacheEvict(value = "learningprogress", key = "#event.Session")
@Override
public void visit(DeleteAllLectureAnswersEvent event) {
this.publisher.publishEvent(new ChangeLearningProgress(this, event.getSession()));
}
@Override
public void visit(NewFeedbackEvent newFeedbackEvent) {}
@Override
public void visit(DeleteFeedbackForSessionsEvent deleteFeedbackEvent) {}
@Override
public void visit(StatusSessionEvent statusSessionEvent) {}
@Override
public void visit(ChangeLearningProgress changeLearningProgress) {}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
@Override
public void visit(PiRoundDelayedStartEvent piRoundDelayedStartEvent) {}
@Override
public void visit(PiRoundEndEvent piRoundEndEvent) {}
}
package de.thm.arsnova.domain;
\ No newline at end of file
/*
* This file is part of ARSnova Backend.
* Copyright (C) 2012-2015 The ARSnova Team
*
* ARSnova Backend is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ARSnova Backend is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* 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 de.thm.arsnova.entities;
import java.util.List;
public class SortOrder {
private String sessionId;
private String sortType;
private String questionVariant;
private String subject;
private List<String> sortOrder;
private String _id;
private String _rev;
public void setSessionId(final String sessionId) {
this.sessionId = sessionId;
}
public String getSessionId() {
return this.sessionId;
}
public void setSortType(final String sortType) {
this.sortType = sortType;
}
public String getSortType() {
return this.sortType;
}
public void setQuestionVariant(final String questionVariant) {
this.questionVariant = questionVariant;
}
public String getQuestionVariant() {
return this.questionVariant;
}
public void setSubject(final String subject) {
this.subject = subject;
}
public String getSubject() {
return this.subject;
}
public void setSortOrder(final List<String> sortOrder) {
this.sortOrder = sortOrder;
}
public List<String> getSortOrder() {
return this.sortOrder;
}
public void set_id(final String id) {
_id = id;
}
public String get_id() {
return _id;
}
public void set_rev(final String rev) {
_rev = rev;
}
public String get_rev() {
return _rev;
}
}
package de.thm.arsnova.entities;
/*
* This file is part of ARSnova Backend.
* Copyright (C) 2012-2015 The ARSnova Team
*
* ARSnova Backend is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ARSnova Backend is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* 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 de.thm.arsnova.entities.transport;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonInclude;
import de.thm.arsnova.entities.Question;
import de.thm.arsnova.entities.User;
@JsonInclude(JsonInclude.Include.NON_DEFAULT)
public class Answer {
private String answerSubject;
private String answerText;
private boolean abstention;
public String getAnswerText() {
return answerText;
}
public void setAnswerText(String answerText) {
this.answerText = answerText;
}
public String getAnswerSubject() {
return answerSubject;
}
public void setAnswerSubject(String answerSubject) {
this.answerSubject = answerSubject;
}
public boolean isAbstention() {
return abstention;
}
public void setAbstention(boolean abstention) {
this.abstention = abstention;
}
public de.thm.arsnova.entities.Answer generateAnswerEntity(final User user, final Question question) {
// rewrite all fields so that no manipulated data gets written
// only answerText, answerSubject, and abstention are allowed
de.thm.arsnova.entities.Answer theAnswer = new de.thm.arsnova.entities.Answer();
theAnswer.setAnswerSubject(this.getAnswerSubject());
theAnswer.setAnswerText(this.getAnswerText());
theAnswer.setSessionId(question.getSessionId());
theAnswer.setUser(user.getUsername());
theAnswer.setQuestionId(question.get_id());
theAnswer.setTimestamp(new Date().getTime());
theAnswer.setQuestionVariant(question.getQuestionVariant());
theAnswer.setAbstention(this.isAbstention());
// calculate learning progress value after all properties are set
theAnswer.setQuestionValue(question.calculateValue(theAnswer));
if ("freetext".equals(question.getQuestionType())) {
theAnswer.setPiRound(0);
} else {
theAnswer.setPiRound(question.getPiRound());
}
return theAnswer;
}
}
/*
* This file is part of ARSnova Backend.
* Copyright (C) 2012-2015 The ARSnova Team
*
* ARSnova Backend is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ARSnova Backend is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* 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 de.thm.arsnova.entities.transport;
import java.util.Date;
import java.util.List;
import de.thm.arsnova.entities.Question;
import de.thm.arsnova.entities.Session;
import de.thm.arsnova.entities.User;
public class ImportExportSession {
private ImportExportSesssion session;
private List<ImportExportQuestion> questions;
private List<InterposedQuestion> feedbackQuestions;
public ImportExportSesssion getSession() {
return session;
}
public void setSession(ImportExportSesssion session) {
this.session = session;
}
public List<ImportExportQuestion> getQuestions() {
return questions;
}
public void setQuestions(List<ImportExportQuestion> questions) {
this.questions = questions;
}
public List<InterposedQuestion> getFeedbackQuestions() {
return feedbackQuestions;
}
public void setFeedbackQuestions(List<InterposedQuestion> feedbackQuestions) {
this.feedbackQuestions = feedbackQuestions;
}
public Session generateSessionEntity(User user) {
final Session s = new Session();
// import fields
s.setActive(session.isActive());
s.setName(session.getName());
s.setShortName(session.getShortName());
// public pool
if (session.getPublicPool() != null) {
// overwrite name and shortname
s.setName(session.getPublicPool().getName());
s.setShortName(session.getPublicPool().getShortName());
// set pool fields
s.setPpAuthorMail(session.getPublicPool().getPpAuthorMail());
s.setPpAuthorName(session.getPublicPool().getPpAuthorName());
s.setPpDescription(session.getPublicPool().getPpDescription());
s.setPpFaculty(session.getPublicPool().getPpFaculty());
s.setPpLevel(session.getPublicPool().getPpLevel());
s.setPpLicense(session.getPublicPool().getPpLicense());
s.setPpLogo(session.getPublicPool().getPpLogo());
s.setPpSubject(session.getPublicPool().getPpSubject());
s.setPpUniversity(session.getPublicPool().getPpUniversity());
// mark as public pool session
s.setSessionType("public_pool");
}
// other fields
s.setType("session");
s.setCreator(user.getUsername());
s.setCreationTime(new Date().getTime());
return s;
}
public static class ImportExportQuestion extends Question {
private List<Answer> answers;
public List<Answer> getAnswers() {
return answers;
}
public void setAnswers(List<Answer> answers) {
this.answers = answers;
}
}
public static class ImportExportSesssion {
private String name;
private String shortName;
private boolean active;
private PublicPool publicPool;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getShortName() {
return shortName;
}
public void setShortName(String shortName) {
this.shortName = shortName;
}
public boolean isActive() {
return active;
}
public void setActive(boolean active) {
this.active = active;
}
public PublicPool getPublicPool() {
return publicPool;
}
public void setPublicPool(PublicPool publicPool) {
this.publicPool = publicPool;
}
}
public static class PublicPool {
private String ppAuthorName;
private String ppAuthorMail;
private String ppUniversity;
private String ppLogo;
private String ppSubject;
private String ppLicense;
private String ppLevel;
private String ppDescription;
private String ppFaculty;
private String name;
private String shortName;
public String getPpAuthorName() {
return ppAuthorName;
}
public void setPpAuthorName(String ppAuthorName) {
this.ppAuthorName = ppAuthorName;
}
public String getPpAuthorMail() {
return ppAuthorMail;
}
public void setPpAuthorMail(String ppAuthorMail) {
this.ppAuthorMail = ppAuthorMail;
}
public String getPpUniversity() {
return ppUniversity;
}
public void setPpUniversity(String ppUniversity) {
this.ppUniversity = ppUniversity;
}
public String getPpLogo() {
return ppLogo;
}
public void setPpLogo(String ppLogo) {
this.ppLogo = ppLogo;
}
public String getPpSubject() {
return ppSubject;
}
public void setPpSubject(String ppSubject) {
this.ppSubject = ppSubject;
}
public String getPpLicense() {
return ppLicense;
}
public void setPpLicense(String ppLicense) {
this.ppLicense = ppLicense;
}
public String getPpLevel() {
return ppLevel;
}
public void setPpLevel(String ppLevel) {
this.ppLevel = ppLevel;
}
public String getPpDescription() {
return ppDescription;
}
public void setPpDescription(String ppDescription) {
this.ppDescription = ppDescription;
}
public String getPpFaculty() {
return ppFaculty;
}
public void setPpFaculty(String ppFaculty) {
this.ppFaculty = ppFaculty;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getShortName() {
return shortName;
}
public void setShortName(String shortName) {
this.shortName = shortName;
}
}
}
package de.thm.arsnova.entities.transport;
/*
* This file is part of ARSnova Backend.
* Copyright (C) 2012-2019 The ARSnova Team and Contributors
*
* ARSnova Backend is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ARSnova Backend is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* 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 de.thm.arsnova.event;
import de.thm.arsnova.model.Entity;
public class AfterCreationEvent<E extends Entity> extends CrudEvent<E> {
public AfterCreationEvent(final Object source, final E entity) {
super(source, entity);
}
}
/*
* This file is part of ARSnova Backend.
* Copyright (C) 2012-2019 The ARSnova Team and Contributors
*
* ARSnova Backend is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ARSnova Backend is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* 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 de.thm.arsnova.event;
import de.thm.arsnova.model.Entity;
public class AfterDeletionEvent<E extends Entity> extends CrudEvent<E> {
public AfterDeletionEvent(final Object source, final E entity) {
super(source, entity);
}
}