/*
* ELW : e-learning workspace
* Copyright (C) 2010 Anton Kraievoy
*
* This program 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.
*
* This program 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 elw.web;
import elw.dao.Auth;
import elw.dao.Ctx;
import elw.dao.Queries;
import elw.miniweb.Message;
import elw.miniweb.ViewJackson;
import elw.vo.*;
import elw.web.core.Core;
import elw.web.core.IndexRow;
import elw.web.core.LogFilter;
import elw.web.core.W;
import org.apache.commons.fileupload.FileUploadException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.Class;
import java.util.*;
@Controller
@RequestMapping("/s/**/*")
public class StudentController extends ControllerElw {
private static final Logger log = LoggerFactory.getLogger(StudentController.class);
private final Queries queries;
private final StudentCodeValidator studentCodeValidator;
public StudentController(
Queries queries,
Core core,
ElwServerConfig elwServerConfig,
StudentCodeValidator studentCodeValidator0
) {
super(core, elwServerConfig);
this.queries = queries;
this.studentCodeValidator = studentCodeValidator0;
}
protected HashMap<String, Object> auth(
final HttpServletRequest req,
final HttpServletResponse resp,
final boolean page,
final boolean verified
) throws IOException {
final HashMap<String, Object> model =
super.auth(req, resp, page, verified);
if (model == null) {
return null;
}
final Auth auth = auth(model);
final Group group = auth.getGroup();
final Student student = auth.getStudent();
final Admin admin = auth.getAdmin();
Ctx ctx = Ctx.fromString(req.getParameter(R_CTX));
ctx.resolve(queries);
if (ctx.getGroup() == null) {
ctx = ctx.extendGroup(group);
}
if (ctx.getStudent() == null) {
ctx = ctx.extendStudent(student);
}
if (!ctx.resolved(Ctx.STATE_GS)) {
resp.sendError(
HttpServletResponse.SC_BAD_REQUEST,
"context path problem, please check the logs"
);
return null;
}
if (admin == null) {
if (!ctx.getGroup().getId().equals(group.getId())) {
resp.sendError(
HttpServletResponse.SC_FORBIDDEN,
"context path refers to another group"
);
return null;
}
if (!ctx.getStudent().getId().equals(student.getId())) {
resp.sendError(
HttpServletResponse.SC_FORBIDDEN,
"context path refers to another student"
);
return null;
}
}
model.put(R_CTX, ctx);
// LATER nobody knows what's this and whether it's needed at all
model.put(
"expandTriggers",
req.getSession().getAttribute("viewToExpandTriggers")
);
return model;
}
@RequestMapping(value = "Index", method = RequestMethod.GET)
public void do_Index(final HttpServletRequest req, final HttpServletResponse resp) throws IOException {
resp.sendRedirect("index");
}
@RequestMapping(
value = "SessionMessage",
method = RequestMethod.GET
)
public ModelAndView do_SessionMessageGet(
final HttpServletRequest req,
final HttpServletResponse resp
) throws IOException {
return new ModelAndView(ViewJackson.data(Message.getMessages(req)));
}
@RequestMapping(
value = "SessionMessage/{stamp}",
method = RequestMethod.DELETE
)
public ModelAndView do_SessionMessageDelete(
final HttpServletRequest req,
final HttpServletResponse resp,
@PathVariable("stamp") final String stamp
) throws IOException {
Message.delete(req, stamp);
return null;
}
@RequestMapping(value = "courses", method = RequestMethod.GET)
public void do_courses(final HttpServletRequest req, final HttpServletResponse resp) throws IOException {
resp.sendRedirect("index");
}
@RequestMapping(value = "course", method = RequestMethod.GET)
public void do_course(final HttpServletRequest req, final HttpServletResponse resp) throws IOException {
resp.sendRedirect("course");
}
@RequestMapping(value = "logout", method = RequestMethod.GET)
public ModelAndView do_logout(final HttpServletRequest req, final HttpServletResponse resp) throws IOException {
final Auth auth = auth(req);
// that was impersonated session
if (auth != null && auth.isAdm()) {
auth.setStudent(null);
auth.setGroup(null);
} else {
req.getSession(true).invalidate();
}
resp.sendRedirect("index");
return null;
}
@RequestMapping(value = "index", method = RequestMethod.GET)
public ModelAndView do_index(final HttpServletRequest req, final HttpServletResponse resp) throws IOException {
final HashMap<String, Object> model = auth(req, resp, true, false);
if (model == null) {
return null;
}
final Ctx ctx = (Ctx) model.get(R_CTX);
return new ModelAndView("s/index", model);
}
@RequestMapping(value = "rest/index", method = RequestMethod.GET)
public ModelAndView do_restIndex(final HttpServletRequest req, final HttpServletResponse resp) throws IOException {
final HashMap<String, Object> model = auth(req, resp, false, false);
if (model == null) {
return null;
}
final Ctx ctx = (Ctx) model.get(R_CTX);
final List<Enrollment> enrolls = queries.enrollmentsForGroup(ctx.getGroup().getId());
final List<IndexRow> indexData = core.index(enrolls);
return new ModelAndView(ViewJackson.success(indexData));
}
@RequestMapping(value = "tasks", method = RequestMethod.GET)
public ModelAndView do_tasks(final HttpServletRequest req, final HttpServletResponse resp) throws IOException {
return wmECG(req, resp, true, false, new WebMethodCtx() {
public ModelAndView handleCtx() throws IOException {
W.storeFilter(req, model);
return new ModelAndView("s/tasks", model);
}
});
}
@RequestMapping(value = "rest/tasks", method = RequestMethod.GET)
public ModelAndView do_restTasks(final HttpServletRequest req, final HttpServletResponse resp) throws IOException {
return wmECG(req, resp, false, false, new WebMethodCtx() {
public ModelAndView handleCtx() throws IOException {
final Format format = (Format) model.get(FormatTool.MODEL_KEY);
final List<Object[]> logData = core.tasks(ctx, new LogFilter(), format, false);
return new ModelAndView(ViewJackson.success(logData));
}
});
}
@RequestMapping(value = "log", method = RequestMethod.GET)
public ModelAndView do_log(final HttpServletRequest req, final HttpServletResponse resp) throws IOException {
final HashMap<String, Object> model = auth(req, resp, true, false);
if (model == null) {
return null;
}
final Ctx ctx = (Ctx) model.get(R_CTX);
if (!ctx.resolved(Ctx.STATE_ECG)) {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Path problem, please check the logs");
return null;
}
W.storeFilter(req, model);
return new ModelAndView("s/log", model);
}
@RequestMapping(value = "rest/log", method = RequestMethod.GET)
public ModelAndView do_restLog(final HttpServletRequest req, final HttpServletResponse resp) throws IOException {
final HashMap<String, Object> model = auth(req, resp, false, false);
if (model == null) {
return null;
}
final Ctx ctx = (Ctx) model.get(R_CTX);
if (!ctx.resolved(Ctx.STATE_ECG)) {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Path problem, please check the logs");
return null;
}
final Format format = (Format) model.get(FormatTool.MODEL_KEY);
final List<Object[]> logData = core.log(
ctx, format, W.parseFilter(req), false
);
return new ModelAndView(ViewJackson.success(logData));
}
@RequestMapping(value = "list", method = RequestMethod.GET)
public ModelAndView do_list(final HttpServletRequest req, final HttpServletResponse resp) throws IOException {
final HashMap<String, Object> model =
auth(req, resp, true, false);
if (model == null) {
return null;
}
final Ctx ctx = (Ctx) model.get(R_CTX);
if (!ctx.resolved(Ctx.STATE_EGSCIV)) {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "context path problem, please check the logs");
return null;
}
final Map<String, Map<String, String[]>> slotToScopeToFileId =
new HashMap<String, Map<String, String[]>>();
for (FileSlot slot : ctx.getAssType().getFileSlots().values()) {
final HashMap<String, String[]> scopeToFileId = new HashMap<String, String[]>();
slotToScopeToFileId.put(slot.getId(), scopeToFileId);
for (String scope : FileBase.SCOPES) {
final List<? extends FileBase> fileEntries =
queries.files(scope, ctx, slot);
final String[] fileIds = new String[fileEntries.size()];
for (int i = 0, filesLength = fileEntries.size(); i < filesLength; i++) {
fileIds[i] = fileEntries.get(i).getId();
}
scopeToFileId.put(scope, fileIds);
}
}
return new ModelAndView(ViewJackson.success(slotToScopeToFileId));
}
@RequestMapping(value = "dl/*.*", method = RequestMethod.GET)
public ModelAndView do_dl(final HttpServletRequest req, final HttpServletResponse resp) throws IOException {
return wmFile(req, resp, null, false, true, new WebMethodFile() {
@Override
protected ModelAndView handleFile(String scope, FileSlot slot) throws IOException {
if (accessDenied(resp, ctx, scope, slot, false)) {
return null;
}
final String fileId = req.getParameter("fId");
if (fileId == null || fileId.trim().length() == 0) {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "no fileId (fId) defined");
return null;
}
final FileBase entry = queries.file(scope, ctx, slot, fileId);
if (entry == null) {
resp.sendError(HttpServletResponse.SC_NOT_FOUND, "no file found");
return null;
}
retrieveFile(entry, slot, queries);
return null;
}
});
}
@RequestMapping(value = "edit", method = RequestMethod.GET)
public ModelAndView do_edit(final HttpServletRequest req, final HttpServletResponse resp) throws IOException {
return wmFile(req, resp, Solution.SCOPE, true, false, new WebMethodFile() {
@Override
protected ModelAndView handleFile(String scope, FileSlot slot) throws IOException {
if (accessDenied(resp, ctx, scope, slot, true)) {
return null;
}
final String customEditName = IdNamed._.one(slot.getFileTypes()).getEditor();
if (customEditName == null || customEditName.trim().length() == 0) {
resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "editor not set for slot " + slot.getId());
return null;
}
Editor editor;
try {
editor = (Editor) Class.forName(customEditName).newInstance();
} catch (InstantiationException e) {
log.error("failed for ctx " + ctx + " and slotId " + slot.getId(), e);
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, String.valueOf(e));
return null;
} catch (IllegalAccessException e) {
log.error("failed for ctx " + ctx + " and slotId " + slot.getId(), e);
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, String.valueOf(e));
return null;
} catch (ClassNotFoundException e) {
log.error("failed for ctx " + ctx + " and slotId " + slot, e);
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, String.valueOf(e));
return null;
}
model.put("slot", slot);
model.put("editor", editor.render(req, resp, ctx, slot));
return new ModelAndView("s/edit", model);
}
});
}
@RequestMapping(value = "ul", method = {RequestMethod.GET})
public ModelAndView do_ul(final HttpServletRequest req, final HttpServletResponse resp) throws IOException, FileUploadException {
return wmFile(req, resp, Solution.SCOPE, true, false, new WebMethodFile() {
@Override
protected ModelAndView handleFile(String scope, FileSlot slot) throws IOException {
model.put("slot", slot);
return new ModelAndView("s/ul", model);
}
});
}
@RequestMapping(value = "ul", method = {RequestMethod.POST, RequestMethod.PUT})
public ModelAndView do_ulPost(final HttpServletRequest req, final HttpServletResponse resp) throws IOException, FileUploadException {
return wmFile(req, resp, Solution.SCOPE, false, true, new WebMethodFile() {
@Override
protected ModelAndView handleFile(String scope, FileSlot slot) throws IOException {
if (accessDenied(resp, ctx, scope, slot, true)) {
return null;
}
final String refreshUri = core.getUri().logOpenPendingEAV(ctx);
final String failureUri = core.getUri().upload(ctx, scope, slot.getId());
final ModelAndView nullViewAfterRedirect = storeFile(
slot, refreshUri, failureUri, ctx.getStudent().getName(),
queries, new Solution()
);
studentCodeValidator.workPending(ctx.getEnr().getId());
return nullViewAfterRedirect;
}
});
}
private boolean accessDenied(
HttpServletResponse resp, Ctx ctx, String scope, FileSlot slot, boolean checkWrite
) throws IOException {
if (!ctx.cFrom().isStarted()) {
resp.sendError(HttpServletResponse.SC_FORBIDDEN, "task not yet open");
return true;
}
if (Solution.SCOPE.equals(scope)) {
final SortedMap<String, List<Solution>> filesStud = queries.solutions(ctx);
if (!ctx.checkRead(slot, filesStud)) {
resp.sendError(HttpServletResponse.SC_FORBIDDEN, "not readable yet");
return true;
}
if (checkWrite && !ctx.checkWrite(slot, filesStud)) {
resp.sendError(HttpServletResponse.SC_FORBIDDEN, "not writable yet");
return true;
}
}
return false;
}
}