package elw.dao;
import base.pattern.Result;
import com.google.common.io.ByteStreams;
import com.google.common.io.InputSupplier;
import elw.dao.ctx.*;
import elw.dao.rest.*;
import elw.vo.*;
import elw.vo.Score;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import org.akraievoy.couch.CouchDao;
import org.akraievoy.couch.Squab;
import static elw.vo.IdNamed._.*;
import static elw.vo.IdNamed._.resolve;
/**
* Default implementation of data queries.
*/
public class QueriesImpl implements Queries {
private static final Logger log = LoggerFactory.getLogger(QueriesImpl.class);
private final Object courseResolutionMonitor = new Object();
private CouchDao metaDao;
public void setMetaDao(CouchDao metaDao) {
this.metaDao = metaDao;
}
private CouchDao userDao;
public void setUserDao(CouchDao userDao) {
this.userDao = userDao;
}
private CouchDao attachmentDao;
public void setAttachmentDao(CouchDao attachmentDao) {
this.attachmentDao = attachmentDao;
}
private CouchDao solutionDao;
public void setSolutionDao(CouchDao solutionDao) {
this.solutionDao = solutionDao;
}
private CouchDao authDao;
public void setAuthDao(CouchDao authDao) {
this.authDao = authDao;
}
public QueriesImpl() {
}
public void invalidateCaches() {
for (CouchDao dao : new CouchDao[] {metaDao, userDao, attachmentDao, solutionDao, authDao}) {
dao.invalidateCaches();
}
}
public QueriesSecure secure(final Auth auth) {
final QueriesSecure queriesSecure = new QueriesSecure(this, auth);
queriesSecure.precacheVisible();
return queriesSecure;
}
public RestEnrollmentSummary restScores(
final String enrId,
final Collection<String> studentIds
) {
final Enrollment enr = enrollmentSome(enrId);
if (enr == null) {
return null;
}
final Group group = group(enr.getGroupId());
final Collection<String> studIds =
new ArrayList<String>(group.getStudents().keySet());
if (studentIds != null) {
studIds.retainAll(studentIds);
}
final Course course = course(enr.getCourseId());
final RestEnrollmentSummary enrSummary = new RestEnrollmentSummary();
final CtxEnrollment ctxEnr = new CtxEnrollment(enr, course, group);
for (CtxStudent ctxStudent : ctxEnr.students) {
final RestStudentSummary studentSummary =
new RestStudentSummary();
for (CtxTask ctxTask : ctxStudent.tasks) {
final RestTaskSummary taskSummary =
new RestTaskSummary();
for (CtxSlot ctxSlot : ctxTask.slots) {
if (ctxSlot.slot.getScoreWeight() <= 0) {
continue;
}
final List<Solution> solutions =
solutions(ctxSlot);
final RestSlotSummary slotSummary =
RestSlotSummary.create(ctxSlot, solutions);
taskSummary.register(ctxSlot, slotSummary);
}
studentSummary.register(ctxTask, taskSummary);
}
enrSummary.register(ctxStudent, studentSummary);
}
return enrSummary;
}
// LATER migrate all enrollment queries to this method
public RestEnrollment restEnrollment(
final String enrId,
final String sourceAddress
) {
final Enrollment enrRaw =
userDao.findSome(Enrollment.class, null, null, enrId);
if (enrRaw == null) {
return null;
}
final Enrollment enr = markEnrollment(enrRaw);
final CtxEnrollment ctxEnrollment = new CtxEnrollment(
enr,
course(enr.getCourseId()),
group(enr.getGroupId())
);
final RestEnrollment restEnr =
RestEnrollment.create(
ctxEnrollment,
sourceAddress
);
return restEnr;
}
public Map<String, RestSolution> restSolutions(
final String enrId,
final SolutionFilter filter
) {
final Enrollment enrRaw = enrollmentSome(enrId);
if (enrRaw == null) {
return null;
}
final Enrollment enr = markEnrollment(enrRaw);
final Group group = group(enr.getGroupId());
final Course course = course(enr.getCourseId());
final SortedMap<String, RestSolution> solutionsRes =
new TreeMap<String, RestSolution>();
final CtxEnrollment ctxEnr = new CtxEnrollment(enr, course, group);
for (CtxStudent ctxStudent : ctxEnr.students) {
if (!filter.preAllows(ctxStudent)) {
continue;
}
for (CtxTask ctxTask : ctxStudent.tasks) {
for (CtxSlot ctxSlot : ctxTask.slots) {
final List<Solution> solutions =
solutions(ctxSlot);
for (Solution solution : solutions) {
final CtxSolution ctxSolution =
ctxSlot.solution(solution);
if (filter.allows(ctxSolution)) {
solutionsRes.put(
solution.getCouchId(),
RestSolution.create(ctxSolution, true)
);
}
}
}
}
}
return solutionsRes;
}
public CtxResolutionState resolveSlot(
final String enrollmentId,
final String couchId,
final StudentFilter studentFilter
) {
final Enrollment enrRaw = enrollmentSome(enrollmentId);
if (enrRaw == null) {
return CtxResolutionState.failed(
couchId, "enrollment "+enrollmentId+" not found"
);
}
final Enrollment enr = markEnrollment(enrRaw);
final Squab.Path path;
try {
path = Squab.Path.fromId(couchId);
} catch (IllegalStateException ise) {
return CtxResolutionState.failed(
couchId, "not valid"
);
}
final String prefix = Solution.class.getSimpleName();
if (path.len() != 11 || !prefix.equals(path.elem(0))) {
return CtxResolutionState.failed(
couchId, "has extra/missing entries"
);
}
if (!enr.getGroupId().equals(path.elem(1))) {
return CtxResolutionState.failed(
couchId, path, "refers to incorrect group"
);
}
final Group group = group(enr.getGroupId());
if (group == null) {
return CtxResolutionState.failed(
couchId, path, "group not found"
);
}
if (!enr.getCourseId().equals(path.elem(3))) {
return CtxResolutionState.failed(
couchId, path, "refers to incorrect course"
);
}
final Course course = course(enr.getCourseId());
if (course == null) {
return CtxResolutionState.failed(
couchId, path, "course not found"
);
}
final CtxEnrollment ctxEnrollment =
new CtxEnrollment(enr, course, group);
final Student student = group.getStudents().get(path.elem(2));
if (student == null) {
return CtxResolutionState.failed(
couchId, path, "student not found"
);
}
if (studentFilter != null && !studentFilter.allows(student)) {
return CtxResolutionState.failed(
couchId, path, "student access denied"
);
}
final CtxStudent ctxStudent =
ctxEnrollment.student(student);
final String indexKey = path.elem(4);
final CtxTask ctxTask = ctxStudent.task(indexKey);
if (ctxTask.tType == null) {
return CtxResolutionState.failed(
couchId, path, "taskType not found"
);
}
if (!ctxTask.tType.getId().equals(path.elem(5))) {
return CtxResolutionState.failed(
couchId, path, "taskType id mismatch"
);
}
if (ctxTask.task == null) {
return CtxResolutionState.failed(
couchId, path, "task not found"
);
}
if (!ctxTask.task.getId().equals(path.elem(6))) {
return CtxResolutionState.failed(
couchId, path, "task id mismatch"
);
}
if (ctxTask.ver == null) {
return CtxResolutionState.failed(
couchId, path, "version not found"
);
}
final CtxTask ctxTaskOverride;
final String versionElem = path.elem(7);
if (!ctxTask.ver.getId().equals(versionElem)) {
final Version sharedVersion =
ctxTask.task.getVersions().get(versionElem);
if (!sharedVersion.isShared()) {
return CtxResolutionState.failed(
couchId, path, "version id mismatch"
);
}
ctxTaskOverride = ctxTask.overrideToShared(sharedVersion);
} else {
ctxTaskOverride = ctxTask;
}
final FileSlot fileSlot = ctxTask.tType.getFileSlots().get(path.elem(8));
if (fileSlot == null) {
return CtxResolutionState.failed(
couchId, path, "file slot not found"
);
}
final CtxSlot ctxSlot = ctxTaskOverride.slot(fileSlot);
return new CtxResolutionState(path, ctxSlot);
}
// FIXME proceed with upload processing
public CtxSolution resolveSolution(
final String enrollmentId,
final String solutionCouchId,
final StudentFilter studentFilter
) {
final CtxResolutionState state =
resolveSlot(enrollmentId, solutionCouchId, studentFilter);
if (!state.complete()) {
// everything is already reported in logs, we just bail
return null;
}
final String solutionId = state.path.elem(9);
final long solutionStamp = Squab.Stamped.parse(state.path.elem(10));
final Solution solutionCouch = solutionDao.findByStamp(
solutionStamp,
Solution.class,
state.ctxSlot.group.getId(),
state.ctxSlot.student.getId(),
state.ctxSlot.course.getId(),
state.ctxSlot.indexEntry.getId(),
state.ctxSlot.tType.getId(),
state.ctxSlot.task.getId(),
state.ctxSlot.ver.getId(),
state.ctxSlot.slot.getId(),
solutionId
);
// FIXME this somehow got duplicated
if (solutionCouch != null) {
final Score score = score(state.ctxSlot.solution(solutionCouch));
solutionCouch.setScore(score);
}
final Solution solution = Nav.resolveFileType(
solutionCouch,
state.ctxSlot.course.getFileTypes()
);
return state.ctxSlot.solution(solution);
}
public RestSolution restSolution(
final String enrollmentId,
final String solId,
final StudentFilter studentFilter
) {
final CtxSolution ctxSolution = resolveSolution(
enrollmentId, solId, studentFilter
);
return RestSolution.create(ctxSolution, true);
}
public boolean createSolution(
final CtxSlot ctxSlot,
final Solution solution,
final String contentType,
final InputSupplier<InputStream> inputSupplier
) {
solution.setupPathElems(ctxSlot.pathForSolution());
// there's no need to store full file type object,
// it's easily resolved on-load
final TreeMap<String, FileType> emptyMap =
new TreeMap<String, FileType>();
emptyMap.put(IdNamed._.one(solution.getFileType()).getId(), null);
solution.setFileType(emptyMap);
final CouchDao.UpdateStatus createStatus =
solutionDao.createOrUpdate(solution);
solution.setCouchRev(createStatus.rev);
solution.setStamp(createStatus.stamp);
solutionDao.attachStream(
solution, FileBase.CONTENT,
contentType, inputSupplier
);
return true;
}
public Group group(final String groupId) {
final Group group = userDao.findOne(Group.class, groupId);
mark(group.getStudents());
return group;
}
public List<Attachment> attachments(CtxSlot ctxSlot) {
final List<Attachment> attachments = attachmentDao.findAll(
Attachment.class,
ctxSlot.course.getId(),
ctxSlot.tType.getId(),
ctxSlot.task.getId(),
ctxSlot.ver.getId(),
ctxSlot.slot.getId()
);
return Nav.resolveFileType(attachments, ctxSlot.course.getFileTypes());
}
public Attachment attachment(Ctx ctxVer, final String slotId, final String id) {
final Attachment attachment = attachmentDao.findLast(
Attachment.class,
ctxVer.getCourse().getId(),
ctxVer.getAssType().getId(),
ctxVer.getAss().getId(),
ctxVer.getVer().getId(),
slotId,
id
);
return Nav.resolveFileType(attachment, ctxVer.getCourse().getFileTypes());
}
public SortedMap<String, List<Solution>> solutions(Ctx ctx) {
final TreeMap<String, List<Solution>> slotIdToFiles =
new TreeMap<String, List<Solution>>();
for (FileSlot slot : ctx.getAssType().getFileSlots().values()) {
slotIdToFiles.put(slot.getId(), solutions(ctx.ctxSlot(slot)));
}
return slotIdToFiles;
}
public List<Solution> solutions(CtxSlot ctx) {
final List<Solution> solutions = solutionDao.findAll(
Solution.class,
ctx.group.getId(),
ctx.student.getId(),
ctx.course.getId(),
ctx.indexEntry.getId(),
ctx.tType.getId(),
ctx.task.getId(),
ctx.ver.getId(),
ctx.slot.getId()
);
for (Solution solution : solutions) {
setupScore(ctx, solution);
}
return Nav.resolveFileType(solutions, ctx.course.getFileTypes());
}
protected void setupScore(CtxSlot ctx, Solution solution) {
final CtxSolution ctxSolution = ctx.solution(solution);
// fetch last of previously fixed scores for a given solution
final Score fixedScore = score(ctxSolution);
final Score currentScore;
if (fixedScore == null) {
// okay, nothing yet set, compute preliminary one
currentScore = ctxSolution.preliminary();
} else {
currentScore = fixedScore;
}
solution.setScore(currentScore);
}
public Solution solution(CtxSlot ctx, String fileId) {
final Solution solution = solutionDao.findLast(
Solution.class,
ctx.group.getId(),
ctx.student.getId(),
ctx.course.getId(),
ctx.indexEntry.getId(),
ctx.tType.getId(),
ctx.task.getId(),
ctx.ver.getId(),
ctx.slot.getId(),
fileId
);
if (solution != null) {
setupScore(ctx, solution);
}
return Nav.resolveFileType(solution, ctx.course.getFileTypes());
}
public Result createFile(
final Ctx ctx,
final FileSlot slot,
final FileBase file,
final InputSupplier<? extends InputStream> inputSupplier,
final String contentType
) {
try {
final Squab.CouchFile couchFile = new Squab.CouchFile();
final byte[] content = ByteStreams.toByteArray(inputSupplier);
couchFile.setData(content);
couchFile.setContentType(contentType);
couchFile.setLength((long) content.length);
final TreeMap<String, Squab.CouchFile> couchFiles =
new TreeMap<String, Squab.CouchFile>();
couchFiles.put(FileBase.CONTENT, couchFile);
file.setCouchFiles(couchFiles);
file.setupPathElems(ctx, slot);
// there's no need to store full file type object,
// it's easily resolved on-load
final TreeMap<String, FileType> emptyMap =
new TreeMap<String, FileType>();
emptyMap.put(IdNamed._.one(file.getFileType()).getId(), null);
file.setFileType(emptyMap);
final CouchDao targetDao =
file instanceof Attachment ? attachmentDao : solutionDao;
targetDao.createOrUpdate(file, file.getStamp() == null);
return new Result("File stored successfully", true);
} catch (IOException e) {
log.warn("failed to store file", e);
return new Result("Failed to store file", false);
}
}
// LATER inline this
public List<? extends FileBase> files(
String scope, Ctx ctx, FileSlot slot
) {
if (scope.equals(Attachment.SCOPE)) {
return attachments(ctx.ctxSlot(slot));
}
return solutions(ctx.ctxSlot(slot));
}
public FileBase file(String scope, Ctx ctx, FileSlot slot, String id) {
if (scope.equals(Attachment.SCOPE)) {
return attachment(ctx, slot.getId(), id);
}
return solution(ctx.ctxSlot(slot), id);
}
public InputSupplier<InputStream> solutionInput(
final @Nonnull CtxSolution ctxSolution,
final @Nonnull String fileName
) {
return solutionDao.couchFileGet(
ctxSolution.solution.getCouchPath(),
fileName
);
}
public InputSupplier<InputStream> attachmentInput(
final @Nonnull CtxAttachment ctxAttachment,
final @Nonnull String fileName
) {
return attachmentDao.couchFileGet(
ctxAttachment.attachment.getCouchPath(),
fileName
);
}
public List<Course> courses() {
final List<Course> courses = new ArrayList<Course>();
final List<String> courseIds = courseIds();
for (String courseId : courseIds) {
courses.add(course(courseId));
}
return courses;
}
public List<String> courseIds() {
final List<List<String>> axes =
metaDao.axes(Course.class, (String) null);
final List<String> courseIds =
axes.get(1);
return courseIds;
}
public Course course(final String courseId) {
final Course course = metaDao.findOne(Course.class, courseId);
synchronized (courseResolutionMonitor) {
// oh, that one was already resolved and published, so we bail
if (course.isResolved()) {
return course;
}
// freshly loaded objects are not marked, hence
// not yet visible to http workers and safe to resolve without
// surprising any http workers unaware of our evilish design
final String templateId = course.getTemplate();
if (templateId != null && templateId.length() > 0) {
final Course template = course(templateId);
course.setCriterias(mark(extend(
template.getCriterias(),
course.getCriterias(),
new TreeMap<String, Criteria>()
)));
course.setFileTypes(mark(extend(
template.getFileTypes(),
course.getFileTypes(),
new TreeMap<String, FileType>()
)));
}
mark(course.getTaskTypes());
for (final TaskType tType : course.getTaskTypes().values()) {
mark(tType.getFileSlots());
for (final FileSlot fSlot : tType.getFileSlots().values()) {
fSlot.setFileTypes(mark(resolve(
new TreeMap<String, FileType>(fSlot.getFileTypes()),
course.getFileTypes()
)));
fSlot.setCriterias(mark(resolve(
new TreeMap<String, Criteria>(fSlot.getCriterias()),
course.getCriterias()
)));
}
tType.setTasks(mark(resolve(
new TreeMap<String, Task>(tType.getTasks()),
new IdNamed.Resolver<Task>() {
public Task resolve(String key) {
final Task task = metaDao.findOne(
Task.class,
course.getId(),
tType.getId(),
key
);
mark(task.getVersions());
return task;
}
}
)));
// LATER for each task iterate over versions and pull the files
}
course.setResolved(true);
return course;
}
}
public Admin adminSome(String login) {
return userDao.findSome(Admin.class, login);
}
public EmailAuth lastAuth(String email) {
return authDao.findLast(EmailAuth.class, email);
}
public EmailAuth createAuth(String email) {
final EmailAuth emailAuth = new EmailAuth();
emailAuth.setActivated(false);
emailAuth.setEmail(email);
@SuppressWarnings("UnusedDeclaration")
final CouchDao.UpdateStatus updateStatus =
authDao.createOrUpdate(emailAuth);
return emailAuth;
}
public void activateAuth(final EmailAuth emailAuth) {
emailAuth.setActivated(true);
authDao.update(emailAuth, false);
}
public List<Admin> admins() {
return userDao.findAll(Admin.class);
}
public List<String> groupIds() {
final List<List<String>> axes =
userDao.axes(Group.class, (String) null);
final List<String> groupIds =
axes.get(1);
return groupIds;
}
public List<Group> groups() {
final List<Group> groups = userDao.findAll(Group.class);
for (Group group : groups) {
mark(group.getStudents());
}
return groups;
}
public List<String> enrollmentIds() {
final List<List<String>> axes =
userDao.axes(Enrollment.class, null, null, null);
final List<String> enrIds =
axes.get(3);
return enrIds;
}
public List<Enrollment> enrollments() {
final List<Enrollment> allEnrs = userDao.findAll(Enrollment.class);
final ArrayList<Enrollment> result =
new ArrayList<Enrollment>();
for (Enrollment enrs : allEnrs) {
result.add(markEnrollment(enrs));
}
return result;
}
public Enrollment enrollmentSome(final String id) {
final Enrollment enrSome =
userDao.findSome(Enrollment.class, null, null, id);
return markEnrollment(enrSome);
}
protected static Enrollment markEnrollment(Enrollment enrSome) {
if (enrSome == null) {
return null;
}
final Enrollment enrClone;
try {
enrClone = enrSome.clone();
} catch (CloneNotSupportedException e) {
throw new IllegalStateException("clone() was working recently", e);
}
IdNamed._.mark(enrClone.getClasses());
IdNamed._.mark(enrClone.getIndex());
return enrClone;
}
public Enrollment enrollment(final String id) {
return markEnrollment(
userDao.findOne(Enrollment.class, null, null, id)
);
}
public List<Enrollment> enrollmentsForGroup(String groupId) {
final List<Enrollment> groupEnrs =
userDao.findAll(Enrollment.class, groupId);
final ArrayList<Enrollment> result = new ArrayList<Enrollment>();
for (Enrollment enr : groupEnrs) {
result.add(markEnrollment(enr));
}
return result;
}
public SortedMap<Long, Score> scores(Ctx ctx, FileSlot slot, Solution file) {
final SortedMap<Long, Score> stampToScore = solutionDao.findAllStamped(
Score.class,
ctx.getGroup().getId(),
ctx.getStudent().getId(),
ctx.getCourse().getId(),
ctx.getIndexEntry().getId(),
ctx.getAssType().getId(),
ctx.getAss().getId(),
ctx.getVer().getId(),
slot.getId(),
file.getId()
);
if (stampToScore.isEmpty()) {
final TreeMap<Long, Score> preliminary =
new TreeMap<Long, Score>();
preliminary.put(
Long.MAX_VALUE,
ctx.ctxSlot(slot).solution(file).preliminary()
);
return preliminary;
}
return stampToScore;
}
public Score score(CtxSolution ctx) {
final Score lastScore = solutionDao.findLast(
Score.class,
ctx.group.getId(),
ctx.student.getId(),
ctx.course.getId(),
ctx.indexEntry.getId(),
ctx.tType.getId(),
ctx.task.getId(),
ctx.ver.getId(),
ctx.slot.getId(),
ctx.solution.getId()
);
if (lastScore == null) {
return ctx.preliminary();
}
return lastScore;
}
public Score score(CtxSolution ctx, Long stamp) {
if (Long.MAX_VALUE == stamp) {
return ctx.preliminary();
}
return solutionDao.findByStamp(
stamp,
Score.class,
ctx.group.getId(),
ctx.student.getId(),
ctx.course.getId(),
ctx.indexEntry.getId(),
ctx.tType.getId(),
ctx.task.getId(),
ctx.ver.getId(),
ctx.slot.getId(),
ctx.solution.getId()
);
}
public long createScore(CtxSolution ctxSolution, Score score) {
score.setupPathElems(ctxSolution.pathForScore());
return solutionDao.createOrUpdate(score).stamp;
}
public void updateSolution(Solution solution) {
solutionDao.createOrUpdate(solution, false);
}
public String fileText(
FileBase file, final String attachment
) throws IOException {
if (file instanceof Solution) {
return solutionDao.fileText(file, attachment);
}
return attachmentDao.fileText(file, attachment);
}
public List<String> fileLines(
FileBase file, final String attachment
) throws IOException {
if (file instanceof Solution) {
return solutionDao.fileLines(file, attachment);
}
return attachmentDao.fileLines(file, attachment);
}
}