diff --git a/.gitignore b/.gitignore index 82d4685..e69de29 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +0,0 @@ -/server/target/ -/client/target/ -/ale-api/target/ -/server/derby.log -/server/activemq-data/ -/server/db/ -/server/nbproject/ \ No newline at end of file diff --git a/ale-api/nb-configuration.xml b/LECTurE-API/nb-configuration.xml similarity index 100% rename from ale-api/nb-configuration.xml rename to LECTurE-API/nb-configuration.xml diff --git a/LECTurE-API/pom.xml b/LECTurE-API/pom.xml new file mode 100644 index 0000000..e6796c7 --- /dev/null +++ b/LECTurE-API/pom.xml @@ -0,0 +1,26 @@ + + + 4.0.0 + it.cnr.istc + LECTurE-API + 1.0 + jar + + + javax.json.bind + javax.json.bind-api + 1.0 + + + javax.json + javax.json-api + 1.1 + jar + + + + UTF-8 + 1.8 + 1.8 + + \ No newline at end of file diff --git a/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/Credentials.java b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/Credentials.java new file mode 100644 index 0000000..d5fbd10 --- /dev/null +++ b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/Credentials.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2018 Riccardo De Benedictis + * + * 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 . + */ +package it.cnr.istc.lecture.api; + +/** + * + * @author Riccardo De Benedictis + */ +public class Credentials { + + public String email; + public String password; + + public Credentials() { + } + + public Credentials(String email, String password) { + this.email = email; + this.password = password; + } +} diff --git a/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/EventListAdapter.java b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/EventListAdapter.java new file mode 100644 index 0000000..e57b008 --- /dev/null +++ b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/EventListAdapter.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2018 Riccardo De Benedictis + * + * 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 . + */ +package it.cnr.istc.lecture.api; + +import it.cnr.istc.lecture.api.messages.Event; +import it.cnr.istc.lecture.api.messages.EventAdapter; +import java.util.ArrayList; +import javax.json.Json; +import javax.json.JsonArray; +import javax.json.JsonArrayBuilder; +import javax.json.JsonValue; +import javax.json.bind.adapter.JsonbAdapter; + +/** + * + * @author Riccardo De Benedictis + */ +public class EventListAdapter implements JsonbAdapter, JsonArray> { + + private static final EventAdapter EVENT_ADAPTER = new EventAdapter(); + + @Override + public JsonArray adaptToJson(ArrayList obj) throws Exception { + JsonArrayBuilder es_builder = Json.createArrayBuilder(); + for (Event event : obj) { + es_builder.add(Json.createObjectBuilder(EVENT_ADAPTER.adaptToJson(event))); + } + return es_builder.build(); + } + + @Override + public ArrayList adaptFromJson(JsonArray obj) throws Exception { + ArrayList es = new ArrayList<>(obj.size()); + for (JsonValue e_value : obj) { + es.add(EVENT_ADAPTER.adaptFromJson(e_value.asJsonObject())); + } + return es; + } +} diff --git a/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/InitResponse.java b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/InitResponse.java new file mode 100644 index 0000000..0227c70 --- /dev/null +++ b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/InitResponse.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2018 Riccardo De Benedictis + * + * 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 . + */ +package it.cnr.istc.lecture.api; + +import it.cnr.istc.lecture.api.model.LessonModel; +import java.util.ArrayList; +import java.util.Collection; + +/** + * + * @author Riccardo De Benedictis + */ +public class InitResponse { + + public User user; + /** + * The lessons followed as a student. + */ + public ArrayList following_lessons; + /** + * The followed teachers. + */ + public ArrayList teachers; + /** + * The associated lesson models. + */ + public ArrayList models; + /** + * The lessons followed as a teacher. + */ + public ArrayList teaching_lessons; + /** + * The followed students. + */ + public ArrayList students; + + public InitResponse() { + } + + public InitResponse(User user, Collection following_lessons, Collection teachers, Collection models, Collection teaching_lessons, Collection students) { + this.user = user; + this.following_lessons = new ArrayList<>(following_lessons); + this.teachers = new ArrayList<>(teachers); + this.models = new ArrayList<>(models); + this.teaching_lessons = new ArrayList<>(teaching_lessons); + this.students = new ArrayList<>(students); + } +} diff --git a/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/Lesson.java b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/Lesson.java new file mode 100644 index 0000000..88d7f0e --- /dev/null +++ b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/Lesson.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2018 Riccardo De Benedictis + * + * 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 . + */ +package it.cnr.istc.lecture.api; + +import it.cnr.istc.lecture.api.messages.Event; +import it.cnr.istc.lecture.api.messages.Token; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import javax.json.bind.annotation.JsonbTypeAdapter; + +/** + * + * @author Riccardo De Benedictis + */ +public class Lesson { + + public long id; + public long teacher_id; + public String name; + public LessonState state; + public long time; + public Long model; + public HashMap roles; + @JsonbTypeAdapter(EventListAdapter.class) + public ArrayList events; + public ArrayList tokens; + + public Lesson() { + } + + public Lesson(long id, long teacher_id, String name, LessonState state, long time, Long model, Map roles, Collection events, Collection tokens) { + this.id = id; + this.teacher_id = teacher_id; + this.name = name; + this.state = state; + this.time = time; + this.model = model; + this.roles = new HashMap<>(roles); + if (events != null) { + this.events = new ArrayList<>(events); + } + if (tokens != null) { + this.tokens = new ArrayList<>(tokens); + } + } + + public enum LessonState { + Running, Paused, Stopped + } +} diff --git a/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/NewLessonRequest.java b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/NewLessonRequest.java new file mode 100644 index 0000000..d398355 --- /dev/null +++ b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/NewLessonRequest.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2018 Riccardo De Benedictis + * + * 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 . + */ +package it.cnr.istc.lecture.api; + +import it.cnr.istc.lecture.api.model.LessonModel; +import java.util.HashMap; +import java.util.Map; + +/** + * + * @author Riccardo De Benedictis + */ +public class NewLessonRequest { + + public long teacher_id; + public String lesson_name; + public Long lesson_model_id; + public LessonModel model; + public HashMap roles; + + public NewLessonRequest() { + } + + public NewLessonRequest(long teacher_id, String lesson_name, Long lesson_model_id, Map roles) { + this.teacher_id = teacher_id; + this.lesson_name = lesson_name; + this.lesson_model_id = lesson_model_id; + this.roles = new HashMap<>(roles); + } + + public NewLessonRequest(long teacher_id, String lesson_name, LessonModel model, Map roles) { + this.teacher_id = teacher_id; + this.lesson_name = lesson_name; + this.model = model; + this.roles = new HashMap<>(roles); + } +} diff --git a/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/NewUserRequest.java b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/NewUserRequest.java new file mode 100644 index 0000000..0e30deb --- /dev/null +++ b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/NewUserRequest.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2018 Riccardo De Benedictis + * + * 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 . + */ +package it.cnr.istc.lecture.api; + +/** + * + * @author Riccardo De Benedictis + */ +public class NewUserRequest { + + public String email; + public String password; + public String first_name; + public String last_name; + + public NewUserRequest() { + } + + public NewUserRequest(String email, String password, String first_name, String last_name) { + this.email = email; + this.password = password; + this.first_name = first_name; + this.last_name = last_name; + } +} diff --git a/ale-api/src/main/java/it/cnr/istc/ale/api/LessonState.java b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/Parameter.java similarity index 75% rename from ale-api/src/main/java/it/cnr/istc/ale/api/LessonState.java rename to LECTurE-API/src/main/java/it/cnr/istc/lecture/api/Parameter.java index cbc0971..6edbfb6 100644 --- a/ale-api/src/main/java/it/cnr/istc/ale/api/LessonState.java +++ b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/Parameter.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 sydde + * Copyright (C) 2018 Riccardo De Benedictis * * 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 @@ -14,12 +14,16 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package it.cnr.istc.ale.api; +package it.cnr.istc.lecture.api; + +import java.util.Map; /** * * @author Riccardo De Benedictis */ -public enum LessonState { - Running, Paused, Stopped +public class Parameter { + + public String name; + public Map properties; } diff --git a/server/src/main/java/it/cnr/istc/ale/server/solver/AnswerContext.java b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/User.java similarity index 52% rename from server/src/main/java/it/cnr/istc/ale/server/solver/AnswerContext.java rename to LECTurE-API/src/main/java/it/cnr/istc/lecture/api/User.java index 52a698c..516f937 100644 --- a/server/src/main/java/it/cnr/istc/ale/server/solver/AnswerContext.java +++ b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/User.java @@ -14,36 +14,32 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package it.cnr.istc.ale.server.solver; +package it.cnr.istc.lecture.api; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; +import java.util.Map; /** * * @author Riccardo De Benedictis */ -public class AnswerContext { +public class User { - private final SolverToken question; - private final int answer; - final Collection tokens = new ArrayList<>(); + public long id; + public String email; + public String first_name; + public String last_name; + public Map par_types; + public Map> par_values; - AnswerContext(SolverToken question, int answer) { - this.question = question; - this.answer = answer; + public User() { } - public SolverToken getQuestion() { - return question; - } - - public int getAnswer() { - return answer; - } - - public Collection getTokens() { - return Collections.unmodifiableCollection(tokens); + public User(long id, String email, String firstName, String lastName, Map par_types, Map> par_values) { + this.id = id; + this.email = email; + this.first_name = firstName; + this.last_name = lastName; + this.par_types = par_types; + this.par_values = par_values; } } diff --git a/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/messages/Answer.java b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/messages/Answer.java new file mode 100644 index 0000000..33f1f64 --- /dev/null +++ b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/messages/Answer.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2017 Riccardo De Benedictis + * + * 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 . + */ +package it.cnr.istc.lecture.api.messages; + +/** + * This message is intended to inform a teacher that a student has answered a + * question. Since the question was dispatched to a specific student, it is + * possible to retrieve the student who gave the answer by the id of the + * question. + * + * @author Riccardo De Benedictis + */ +public class Answer extends Message { + + public long lessonId; + public int questionId; + public int answer; + + public Answer() { + } + + public Answer(long lessonId, int questionId, int answer) { + super(MessageType.Answer); + this.lessonId = lessonId; + this.questionId = questionId; + this.answer = answer; + } +} diff --git a/ale-api/src/main/java/it/cnr/istc/ale/api/messages/HideEvent.java b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/messages/Event.java similarity index 55% rename from ale-api/src/main/java/it/cnr/istc/ale/api/messages/HideEvent.java rename to LECTurE-API/src/main/java/it/cnr/istc/lecture/api/messages/Event.java index 5b585e2..8cc0bd5 100644 --- a/ale-api/src/main/java/it/cnr/istc/ale/api/messages/HideEvent.java +++ b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/messages/Event.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 Riccardo De Benedictis + * Copyright (C) 2017 Riccardo De Benedictis * * 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 @@ -14,31 +14,33 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package it.cnr.istc.ale.api.messages; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; +package it.cnr.istc.lecture.api.messages; /** * * @author Riccardo De Benedictis */ -public class HideEvent extends Message { +public abstract class Event extends Message { - private final long lesson_id; - private final long event_id; + public EventType event_type; + public long lesson_id; + public int event_id; + public String role; + public long time; - @JsonCreator - public HideEvent(@JsonProperty("lessonId") long lesson_id, @JsonProperty("eventId") long event_id) { - this.lesson_id = lesson_id; - this.event_id = event_id; + public Event() { } - public long getLessonId() { - return lesson_id; + public Event(EventType event_type, long lesson_id, int event_id, String role, long time) { + super(MessageType.Event); + this.event_type = event_type; + this.lesson_id = lesson_id; + this.event_id = event_id; + this.role = role; + this.time = time; } - public long getEventId() { - return event_id; + public enum EventType { + TextEvent, QuestionEvent, URLEvent } } diff --git a/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/messages/EventAdapter.java b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/messages/EventAdapter.java new file mode 100644 index 0000000..2769906 --- /dev/null +++ b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/messages/EventAdapter.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2018 Riccardo De Benedictis + * + * 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 . + */ +package it.cnr.istc.lecture.api.messages; + +import java.util.ArrayList; +import java.util.List; +import javax.json.Json; +import javax.json.JsonArray; +import javax.json.JsonArrayBuilder; +import javax.json.JsonObject; +import javax.json.JsonObjectBuilder; +import javax.json.JsonValue; +import javax.json.bind.adapter.JsonbAdapter; + +/** + * + * @author Riccardo De Benedictis + */ +public class EventAdapter implements JsonbAdapter { + + @Override + public JsonObject adaptToJson(Event obj) throws Exception { + JsonObjectBuilder c_object = Json.createObjectBuilder(); + c_object.add("message_type", obj.message_type.name()); + c_object.add("event_type", obj.event_type.name()); + c_object.add("lesson_id", obj.lesson_id); + c_object.add("event_id", obj.event_id); + c_object.add("role", obj.role); + c_object.add("time", obj.time); + switch (obj.event_type) { + case TextEvent: + c_object.add("content", ((TextEvent) obj).content); + break; + case QuestionEvent: + c_object.add("question", ((QuestionEvent) obj).question); + JsonArrayBuilder answers_builder = Json.createArrayBuilder(); + for (String answer : ((QuestionEvent) obj).answers) { + answers_builder.add(answer); + } + c_object.add("answers", answers_builder); + if (((QuestionEvent) obj).answer != null) { + c_object.add("answer", ((QuestionEvent) obj).answer); + } + break; + case URLEvent: + c_object.add("content", ((URLEvent) obj).content); + c_object.add("url", ((URLEvent) obj).url); + break; + default: + throw new AssertionError(obj.event_type.name()); + } + return c_object.build(); + } + + @Override + public Event adaptFromJson(JsonObject obj) throws Exception { + long lesson_id = obj.getJsonNumber("lesson_id").longValue(); + int event_id = obj.getInt("event_id"); + String role = obj.getString("role"); + long time = obj.getJsonNumber("time").longValue(); + switch (Event.EventType.valueOf(obj.getString("event_type"))) { + case TextEvent: + return new TextEvent(lesson_id, event_id, role, time, obj.getString("content")); + case QuestionEvent: + String question = obj.getString("question"); + JsonArray answers_array = obj.getJsonArray("answers"); + List answers = new ArrayList<>(answers_array.size()); + for (JsonValue answer_value : answers_array) { + answers.add(answer_value.toString()); + } + return new QuestionEvent(lesson_id, event_id, role, time, question, answers, obj.containsKey("answer") ? obj.getInt("answer") : null); + case URLEvent: + return new URLEvent(lesson_id, event_id, role, time, obj.getString("content"), obj.getString("url")); + default: + throw new AssertionError(Event.EventType.valueOf(obj.getString("event_type")).name()); + } + } +} diff --git a/server/src/main/java/it/cnr/istc/ale/server/solver/TemporalNetworkListener.java b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/messages/HideEvent.java similarity index 67% rename from server/src/main/java/it/cnr/istc/ale/server/solver/TemporalNetworkListener.java rename to LECTurE-API/src/main/java/it/cnr/istc/lecture/api/messages/HideEvent.java index f1c603a..c0ed012 100644 --- a/server/src/main/java/it/cnr/istc/ale/server/solver/TemporalNetworkListener.java +++ b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/messages/HideEvent.java @@ -14,13 +14,23 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package it.cnr.istc.ale.server.solver; +package it.cnr.istc.lecture.api.messages; /** * * @author Riccardo De Benedictis */ -public interface TemporalNetworkListener { +public class HideEvent extends Message { - public void newValue(final int var); + public long lesson_id; + public long event_id; + + public HideEvent() { + } + + public HideEvent(long lesson_id, long event_id) { + super(MessageType.HideEvent); + this.lesson_id = lesson_id; + this.event_id = event_id; + } } diff --git a/ale-api/src/main/java/it/cnr/istc/ale/api/messages/LostLesson.java b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/messages/LostLesson.java similarity index 70% rename from ale-api/src/main/java/it/cnr/istc/ale/api/messages/LostLesson.java rename to LECTurE-API/src/main/java/it/cnr/istc/lecture/api/messages/LostLesson.java index a06b17b..2960ece 100644 --- a/ale-api/src/main/java/it/cnr/istc/ale/api/messages/LostLesson.java +++ b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/messages/LostLesson.java @@ -14,10 +14,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package it.cnr.istc.ale.api.messages; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; +package it.cnr.istc.lecture.api.messages; /** * @@ -25,14 +22,13 @@ */ public class LostLesson extends Message { - private final long lesson_id; + public long lesson_id; - @JsonCreator - public LostLesson(@JsonProperty("lessonId") long lesson_id) { - this.lesson_id = lesson_id; + public LostLesson() { } - public long getLessonId() { - return lesson_id; + public LostLesson(long lesson_id) { + super(MessageType.LostLesson); + this.lesson_id = lesson_id; } } diff --git a/ale-api/src/main/java/it/cnr/istc/ale/api/messages/LostParameter.java b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/messages/LostParameter.java similarity index 71% rename from ale-api/src/main/java/it/cnr/istc/ale/api/messages/LostParameter.java rename to LECTurE-API/src/main/java/it/cnr/istc/lecture/api/messages/LostParameter.java index 62fb095..713c6e5 100644 --- a/ale-api/src/main/java/it/cnr/istc/ale/api/messages/LostParameter.java +++ b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/messages/LostParameter.java @@ -14,10 +14,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package it.cnr.istc.ale.api.messages; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; +package it.cnr.istc.lecture.api.messages; /** * @@ -25,14 +22,13 @@ */ public class LostParameter extends Message { - private final String name; + public String name; - @JsonCreator - public LostParameter(@JsonProperty("name") String name) { - this.name = name; + public LostParameter() { } - public String getName() { - return name; + public LostParameter(String name) { + super(MessageType.LostParameter); + this.name = name; } } diff --git a/ale-api/src/main/java/it/cnr/istc/ale/api/messages/LostStudent.java b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/messages/LostStudent.java similarity index 67% rename from ale-api/src/main/java/it/cnr/istc/ale/api/messages/LostStudent.java rename to LECTurE-API/src/main/java/it/cnr/istc/lecture/api/messages/LostStudent.java index c15776e..13c20ff 100644 --- a/ale-api/src/main/java/it/cnr/istc/ale/api/messages/LostStudent.java +++ b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/messages/LostStudent.java @@ -14,10 +14,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package it.cnr.istc.ale.api.messages; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; +package it.cnr.istc.lecture.api.messages; /** * @@ -25,14 +22,13 @@ */ public class LostStudent extends Message { - private final long studentId; + public long student_id; - @JsonCreator - public LostStudent(@JsonProperty("studentId") long studentId) { - this.studentId = studentId; + public LostStudent() { } - public long getStudentId() { - return studentId; + public LostStudent(long studentId) { + super(MessageType.LostStudent); + this.student_id = studentId; } } diff --git a/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/messages/Message.java b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/messages/Message.java new file mode 100644 index 0000000..aeddd09 --- /dev/null +++ b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/messages/Message.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2017 Riccardo De Benedictis + * + * 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 . + */ +package it.cnr.istc.lecture.api.messages; + +/** + * + * @author Riccardo De Benedictis + */ +public class Message { + + public MessageType message_type; + + public Message() { + } + + public Message(MessageType type) { + this.message_type = type; + } + + public enum MessageType { + NewParameter, LostParameter, NewStudent, LostStudent, NewLesson, LostLesson, Token, TokenUpdate, RemoveToken, Event, HideEvent, Answer + } +} diff --git a/ale-api/src/main/java/it/cnr/istc/ale/api/messages/NewLesson.java b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/messages/NewLesson.java similarity index 69% rename from ale-api/src/main/java/it/cnr/istc/ale/api/messages/NewLesson.java rename to LECTurE-API/src/main/java/it/cnr/istc/lecture/api/messages/NewLesson.java index 354be3b..4bdfd0b 100644 --- a/ale-api/src/main/java/it/cnr/istc/ale/api/messages/NewLesson.java +++ b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/messages/NewLesson.java @@ -14,11 +14,9 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package it.cnr.istc.ale.api.messages; +package it.cnr.istc.lecture.api.messages; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import it.cnr.istc.ale.api.Lesson; +import it.cnr.istc.lecture.api.Lesson; /** * @@ -26,14 +24,13 @@ */ public class NewLesson extends Message { - private final Lesson lesson; + public Lesson lesson; - @JsonCreator - public NewLesson(@JsonProperty("lesson") Lesson lesson) { - this.lesson = lesson; + public NewLesson() { } - public Lesson getLesson() { - return lesson; + public NewLesson(Lesson lesson) { + super(MessageType.NewLesson); + this.lesson = lesson; } } diff --git a/ale-api/src/main/java/it/cnr/istc/ale/api/messages/NewParameter.java b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/messages/NewParameter.java similarity index 67% rename from ale-api/src/main/java/it/cnr/istc/ale/api/messages/NewParameter.java rename to LECTurE-API/src/main/java/it/cnr/istc/lecture/api/messages/NewParameter.java index 4e2459a..b50b6f9 100644 --- a/ale-api/src/main/java/it/cnr/istc/ale/api/messages/NewParameter.java +++ b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/messages/NewParameter.java @@ -14,11 +14,9 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package it.cnr.istc.ale.api.messages; +package it.cnr.istc.lecture.api.messages; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import it.cnr.istc.ale.api.Parameter; +import it.cnr.istc.lecture.api.Parameter; /** * @@ -26,14 +24,13 @@ */ public class NewParameter extends Message { - private final Parameter parameter; + public Parameter parameter; - @JsonCreator - public NewParameter(@JsonProperty("parameter") Parameter parameter) { - this.parameter = parameter; + public NewParameter() { } - public Parameter getParameter() { - return parameter; + public NewParameter(Parameter parameter) { + super(MessageType.NewParameter); + this.parameter = parameter; } } diff --git a/ale-api/src/main/java/it/cnr/istc/ale/api/messages/NewStudent.java b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/messages/NewStudent.java similarity index 68% rename from ale-api/src/main/java/it/cnr/istc/ale/api/messages/NewStudent.java rename to LECTurE-API/src/main/java/it/cnr/istc/lecture/api/messages/NewStudent.java index 27f5858..b3b6db0 100644 --- a/ale-api/src/main/java/it/cnr/istc/ale/api/messages/NewStudent.java +++ b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/messages/NewStudent.java @@ -14,10 +14,9 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package it.cnr.istc.ale.api.messages; +package it.cnr.istc.lecture.api.messages; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; +import it.cnr.istc.lecture.api.User; /** * @@ -25,14 +24,13 @@ */ public class NewStudent extends Message { - private final long studentId; + public User student; - @JsonCreator - public NewStudent(@JsonProperty("studentId") long studentId) { - this.studentId = studentId; + public NewStudent() { } - public long getStudentId() { - return studentId; + public NewStudent(User student) { + super(MessageType.NewStudent); + this.student = student; } } diff --git a/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/messages/QuestionEvent.java b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/messages/QuestionEvent.java new file mode 100644 index 0000000..a049fd0 --- /dev/null +++ b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/messages/QuestionEvent.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2017 Riccardo De Benedictis + * + * 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 . + */ +package it.cnr.istc.lecture.api.messages; + +import java.util.List; + +/** + * + * @author Riccardo De Benedictis + */ +public class QuestionEvent extends Event { + + public String question; + public List answers; + public Integer answer; + + public QuestionEvent() { + } + + public QuestionEvent(long lesson_id, int event_id, String role, long time, String question, List answers, Integer answer) { + super(EventType.QuestionEvent, lesson_id, event_id, role, time); + this.question = question; + this.answers = answers; + this.answer = answer; + } +} diff --git a/ale-api/src/main/java/it/cnr/istc/ale/api/messages/RemoveToken.java b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/messages/RemoveToken.java similarity index 63% rename from ale-api/src/main/java/it/cnr/istc/ale/api/messages/RemoveToken.java rename to LECTurE-API/src/main/java/it/cnr/istc/lecture/api/messages/RemoveToken.java index b1885ca..53830fe 100644 --- a/ale-api/src/main/java/it/cnr/istc/ale/api/messages/RemoveToken.java +++ b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/messages/RemoveToken.java @@ -14,10 +14,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package it.cnr.istc.ale.api.messages; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; +package it.cnr.istc.lecture.api.messages; /** * @@ -25,20 +22,15 @@ */ public class RemoveToken extends Message { - private final long lesson_id; - private final long event_id; + public long lesson_id; + public long event_id; - @JsonCreator - public RemoveToken(@JsonProperty("lessonId") long lesson_id, @JsonProperty("eventId") long event_id) { - this.lesson_id = lesson_id; - this.event_id = event_id; + public RemoveToken() { } - public long getLessonId() { - return lesson_id; - } - - public long getEventId() { - return event_id; + public RemoveToken(long lesson_id, long event_id) { + super(MessageType.RemoveToken); + this.lesson_id = lesson_id; + this.event_id = event_id; } } diff --git a/ale-api/src/main/java/it/cnr/istc/ale/api/messages/TextEvent.java b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/messages/TextEvent.java similarity index 65% rename from ale-api/src/main/java/it/cnr/istc/ale/api/messages/TextEvent.java rename to LECTurE-API/src/main/java/it/cnr/istc/lecture/api/messages/TextEvent.java index 64980c8..6e43044 100644 --- a/ale-api/src/main/java/it/cnr/istc/ale/api/messages/TextEvent.java +++ b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/messages/TextEvent.java @@ -14,10 +14,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package it.cnr.istc.ale.api.messages; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; +package it.cnr.istc.lecture.api.messages; /** * @@ -25,15 +22,13 @@ */ public class TextEvent extends Event { - private final String content; + public String content; - @JsonCreator - public TextEvent(@JsonProperty("lessonId") long lesson_id, @JsonProperty("id") int id, @JsonProperty("content") String content) { - super(lesson_id, id); - this.content = content; + public TextEvent() { } - public String getContent() { - return content; + public TextEvent(long lesson_id, int event_id, String role, long time, String content) { + super(EventType.TextEvent, lesson_id, event_id, role, time); + this.content = content; } } diff --git a/ale-api/src/main/java/it/cnr/istc/ale/api/messages/TokenUpdate.java b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/messages/Token.java similarity index 57% rename from ale-api/src/main/java/it/cnr/istc/ale/api/messages/TokenUpdate.java rename to LECTurE-API/src/main/java/it/cnr/istc/lecture/api/messages/Token.java index c24c4df..15d95eb 100644 --- a/ale-api/src/main/java/it/cnr/istc/ale/api/messages/TokenUpdate.java +++ b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/messages/Token.java @@ -14,37 +14,35 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package it.cnr.istc.ale.api.messages; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; +package it.cnr.istc.lecture.api.messages; /** * * @author Riccardo De Benedictis */ -public class TokenUpdate extends Message { +public class Token extends Message { + + public long lesson_id; + public int id; + public Integer cause; + public Long min; + public Long max; + public long time; + public String refEvent; + public Integer question; - private final long lesson_id; - private final int id; - private final long time; + public Token() { + } - @JsonCreator - public TokenUpdate(@JsonProperty("lessonId") long lesson_id, @JsonProperty("id") int id, @JsonProperty("time") long time) { + public Token(long lesson_id, int id, Integer cause, Long min, Long max, long time, String refEvent, Integer question) { + super(MessageType.Token); this.lesson_id = lesson_id; this.id = id; + this.cause = cause; + this.min = min; + this.max = max; this.time = time; - } - - public long getLessonId() { - return lesson_id; - } - - public int getId() { - return id; - } - - public long getTime() { - return time; + this.refEvent = refEvent; + this.question = question; } } diff --git a/ale-api/src/main/java/it/cnr/istc/ale/api/messages/Event.java b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/messages/TokenUpdate.java similarity index 62% rename from ale-api/src/main/java/it/cnr/istc/ale/api/messages/Event.java rename to LECTurE-API/src/main/java/it/cnr/istc/lecture/api/messages/TokenUpdate.java index 85274aa..57a5f91 100644 --- a/ale-api/src/main/java/it/cnr/istc/ale/api/messages/Event.java +++ b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/messages/TokenUpdate.java @@ -14,31 +14,28 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package it.cnr.istc.ale.api.messages; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; +package it.cnr.istc.lecture.api.messages; /** * * @author Riccardo De Benedictis */ -public abstract class Event extends Message { +public class TokenUpdate extends Message { - private final long lesson_id; - private final int id; + public long lesson_id; + public int id; + public Long min, max; + public long time; - @JsonCreator - public Event(@JsonProperty("lessonId") long lesson_id, @JsonProperty("id") int id) { - this.lesson_id = lesson_id; - this.id = id; + public TokenUpdate() { } - public long getLessonId() { - return lesson_id; - } - - public int getId() { - return id; + public TokenUpdate(long lesson_id, int id, Long min, Long max, long val) { + super(MessageType.TokenUpdate); + this.lesson_id = lesson_id; + this.id = id; + this.min = min; + this.max = max; + this.time = val; } } diff --git a/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/messages/URLEvent.java b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/messages/URLEvent.java new file mode 100644 index 0000000..24ff252 --- /dev/null +++ b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/messages/URLEvent.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2017 Riccardo De Benedictis + * + * 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 . + */ +package it.cnr.istc.lecture.api.messages; + +/** + * + * @author Riccardo De Benedictis + */ +public class URLEvent extends Event { + + public String content; + public String url; + + public URLEvent() { + } + + public URLEvent(long lesson_id, int event_id, String role, long time, String content, String url) { + super(EventType.URLEvent, lesson_id, event_id, role, time); + this.content = content; + this.url = url; + } +} diff --git a/ale-api/src/main/java/it/cnr/istc/ale/api/model/AndCondition.java b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/model/AndCondition.java similarity index 60% rename from ale-api/src/main/java/it/cnr/istc/ale/api/model/AndCondition.java rename to LECTurE-API/src/main/java/it/cnr/istc/lecture/api/model/AndCondition.java index 02c88e0..63f7281 100644 --- a/ale-api/src/main/java/it/cnr/istc/ale/api/model/AndCondition.java +++ b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/model/AndCondition.java @@ -14,27 +14,29 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package it.cnr.istc.ale.api.model; +package it.cnr.istc.lecture.api.model; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; -import java.util.Collections; /** * * @author Riccardo De Benedictis */ -public class AndCondition { +public class AndCondition extends Condition { - private final Collection conditions; + public ArrayList conditions; - @JsonCreator - public AndCondition(@JsonProperty("conditions") Collection conditions) { - this.conditions = conditions; + public AndCondition() { } - public Collection getConditions() { - return Collections.unmodifiableCollection(conditions); + public AndCondition(Condition... conditions) { + this(Arrays.asList(conditions)); + } + + public AndCondition(Collection conditions) { + super(ConditionType.AndCondition); + this.conditions = new ArrayList<>(conditions); } } diff --git a/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/model/Condition.java b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/model/Condition.java new file mode 100644 index 0000000..01a2b9f --- /dev/null +++ b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/model/Condition.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2017 Riccardo De Benedictis + * + * 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 . + */ +package it.cnr.istc.lecture.api.model; + +/** + * + * @author Riccardo De Benedictis + */ +public abstract class Condition { + + public ConditionType type; + + public Condition() { + } + + public Condition(ConditionType type) { + this.type = type; + } + + public enum ConditionType { + AndCondition, OrCondition, NotCondition, NumericCondition, NominalCondition + } +} diff --git a/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/model/ConditionAdapter.java b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/model/ConditionAdapter.java new file mode 100644 index 0000000..2de29ba --- /dev/null +++ b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/model/ConditionAdapter.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2018 Riccardo De Benedictis + * + * 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 . + */ +package it.cnr.istc.lecture.api.model; + +import java.util.ArrayList; +import java.util.Collection; +import javax.json.Json; +import javax.json.JsonArray; +import javax.json.JsonArrayBuilder; +import javax.json.JsonObject; +import javax.json.JsonObjectBuilder; +import javax.json.JsonValue; +import javax.json.bind.adapter.JsonbAdapter; + +/** + * + * @author Riccardo De Benedictis + */ +public class ConditionAdapter implements JsonbAdapter { + + private static final ConditionAdapter CONDITION_ADAPTER = new ConditionAdapter(); + + @Override + public JsonObject adaptToJson(Condition obj) throws Exception { + JsonObjectBuilder c_object = Json.createObjectBuilder(); + c_object.add("type", obj.type.name()); + switch (obj.type) { + case AndCondition: + JsonArrayBuilder and_condition_builder = Json.createArrayBuilder(); + for (Condition condition : ((AndCondition) obj).conditions) { + and_condition_builder.add(Json.createObjectBuilder(CONDITION_ADAPTER.adaptToJson(condition))); + } + c_object.add("conditions", and_condition_builder); + break; + case OrCondition: + JsonArrayBuilder or_condition_builder = Json.createArrayBuilder(); + for (Condition condition : ((OrCondition) obj).conditions) { + or_condition_builder.add(Json.createObjectBuilder(CONDITION_ADAPTER.adaptToJson(condition))); + } + c_object.add("conditions", or_condition_builder); + break; + case NotCondition: + c_object.add("condition", Json.createObjectBuilder(CONDITION_ADAPTER.adaptToJson(((NotCondition) obj).condition))); + break; + case NumericCondition: + c_object.add("numeric_condition_type", ((NumericCondition) obj).numeric_condition_type.name()); + c_object.add("variable", ((NumericCondition) obj).variable); + c_object.add("value", ((NumericCondition) obj).value); + break; + case NominalCondition: + c_object.add("variable", ((NominalCondition) obj).variable); + c_object.add("value", ((NominalCondition) obj).value); + break; + default: + throw new AssertionError(obj.type.name()); + } + return c_object.build(); + } + + @Override + public Condition adaptFromJson(JsonObject obj) throws Exception { + switch (Condition.ConditionType.valueOf(obj.getString("type"))) { + case AndCondition: + JsonArray and_conditions_array = obj.getJsonArray("conditions"); + Collection and_conditions = new ArrayList<>(and_conditions_array.size()); + for (JsonValue cond_value : and_conditions_array) { + and_conditions.add(CONDITION_ADAPTER.adaptFromJson(cond_value.asJsonObject())); + } + return new AndCondition(and_conditions); + case OrCondition: + JsonArray or_conditions_array = obj.getJsonArray("conditions"); + Collection or_conditions = new ArrayList<>(or_conditions_array.size()); + for (JsonValue cond_value : or_conditions_array) { + or_conditions.add(CONDITION_ADAPTER.adaptFromJson(cond_value.asJsonObject())); + } + return new OrCondition(or_conditions); + case NotCondition: + return new NotCondition(CONDITION_ADAPTER.adaptFromJson(obj.getJsonObject("condition"))); + case NumericCondition: + return new NumericCondition(NumericCondition.NumericConditionType.valueOf(obj.getString("numeric_condition_type")), obj.getString("variable"), obj.getJsonNumber("value").doubleValue()); + case NominalCondition: + return new NominalCondition(obj.getString("variable"), obj.getString("value")); + default: + throw new AssertionError(Condition.ConditionType.valueOf(obj.getString("type")).name()); + } + } +} diff --git a/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/model/EventTemplate.java b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/model/EventTemplate.java new file mode 100644 index 0000000..88476a5 --- /dev/null +++ b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/model/EventTemplate.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2017 Riccardo De Benedictis + * + * 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 . + */ +package it.cnr.istc.lecture.api.model; + +import java.util.ArrayList; +import java.util.Collection; +import javax.json.bind.annotation.JsonbTypeAdapter; + +/** + * + * @author Riccardo De Benedictis + */ +public class EventTemplate { + + public EventTemplateType type; + public String name; + public String role; + @JsonbTypeAdapter(ConditionAdapter.class) + public Condition trigger_condition; + @JsonbTypeAdapter(ConditionAdapter.class) + public Condition execution_condition; + public ArrayList ids; + public ArrayList relations; + + public EventTemplate() { + } + + public EventTemplate(EventTemplateType type, String name, String role, Condition trigger_condition, Condition execution_condition, Collection ids, Collection relations) { + this.type = type; + this.name = name; + this.role = role; + this.trigger_condition = trigger_condition; + this.execution_condition = execution_condition; + this.ids = new ArrayList<>(ids); + this.relations = new ArrayList<>(relations); + } + + public enum EventTemplateType { + EventTemplate, TextEventTemplate, URLEventTemplate, QuestionEventTemplate + } +} diff --git a/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/model/EventTemplateListAdapter.java b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/model/EventTemplateListAdapter.java new file mode 100644 index 0000000..1d61b10 --- /dev/null +++ b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/model/EventTemplateListAdapter.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2018 Riccardo De Benedictis + * + * 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 . + */ +package it.cnr.istc.lecture.api.model; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; +import javax.json.Json; +import javax.json.JsonArray; +import javax.json.JsonArrayBuilder; +import javax.json.JsonObject; +import javax.json.JsonObjectBuilder; +import javax.json.JsonValue; +import javax.json.bind.adapter.JsonbAdapter; + +/** + * + * @author Riccardo De Benedictis + */ +public class EventTemplateListAdapter implements JsonbAdapter, JsonArray> { + + private static final ConditionAdapter CONDITION_ADAPTER = new ConditionAdapter(); + + @Override + public JsonArray adaptToJson(ArrayList obj) throws Exception { + JsonArrayBuilder ets_builder = Json.createArrayBuilder(); + for (EventTemplate et : obj) { + JsonObjectBuilder et_object = Json.createObjectBuilder(); + et_object.add("type", et.type.name()); + et_object.add("name", et.name); + et_object.add("role", et.role); + if (et.trigger_condition != null) { + et_object.add("trigger_condition", Json.createObjectBuilder(CONDITION_ADAPTER.adaptToJson(et.trigger_condition))); + } + if (et.execution_condition != null) { + et_object.add("execution_condition", Json.createObjectBuilder(CONDITION_ADAPTER.adaptToJson(et.execution_condition))); + } + JsonArrayBuilder ids_builder = Json.createArrayBuilder(); + for (String id : et.ids) { + ids_builder.add(id); + } + et_object.add("ids", ids_builder); + JsonArrayBuilder rels_builder = Json.createArrayBuilder(); + for (Relation rel : et.relations) { + JsonObjectBuilder rel_builder = Json.createObjectBuilder(); + rel_builder.add("from", rel.from); + rel_builder.add("to", rel.to); + if (rel.lb != Double.NEGATIVE_INFINITY) { + rel_builder.add("lb", rel.lb); + } + if (rel.ub != Double.POSITIVE_INFINITY) { + rel_builder.add("ub", rel.ub); + } + rel_builder.add("unit", rel.unit.name()); + } + et_object.add("relations", rels_builder); + switch (et.type) { + case EventTemplate: + break; + case TextEventTemplate: + et_object.add("content", ((TextEventTemplate) et).content); + break; + case URLEventTemplate: + et_object.add("content", ((URLEventTemplate) et).content); + et_object.add("url", ((URLEventTemplate) et).url); + break; + case QuestionEventTemplate: + et_object.add("question", ((QuestionEventTemplate) et).question); + JsonArrayBuilder ans_builder = Json.createArrayBuilder(); + for (QuestionEventTemplate.Answer answer : ((QuestionEventTemplate) et).answers) { + ans_builder.add(Json.createObjectBuilder().add("answer", answer.answer).add("event", answer.event)); + } + et_object.add("answers", ans_builder); + break; + default: + throw new AssertionError(et.type.name()); + } + ets_builder.add(et_object); + } + return ets_builder.build(); + } + + @Override + public ArrayList adaptFromJson(JsonArray obj) throws Exception { + ArrayList ets = new ArrayList<>(obj.size()); + for (JsonValue et_value : obj) { + JsonObject et_object = et_value.asJsonObject(); + String name = et_object.getString("name"); + String role = et_object.getString("role"); + Condition trigger_condition = et_object.containsKey("trigger_condition") && !et_object.isNull("trigger_condition") ? CONDITION_ADAPTER.adaptFromJson(et_object.getJsonObject("trigger_condition")) : null; + Condition execution_condition = et_object.containsKey("execution_condition") && !et_object.isNull("execution_condition") ? CONDITION_ADAPTER.adaptFromJson(et_object.getJsonObject("execution_condition")) : null; + List ids = new ArrayList<>(et_object.getJsonArray("ids").size()); + for (JsonValue id : et_object.getJsonArray("ids")) { + ids.add(id.toString()); + } + List relations = new ArrayList<>(et_object.getJsonArray("relations").size()); + for (JsonValue rel_val : et_object.getJsonArray("relations")) { + JsonObject rel_obj = rel_val.asJsonObject(); + relations.add(new Relation(rel_obj.getString("from"), rel_obj.getString("to"), rel_obj.containsKey("lb") ? rel_obj.getJsonNumber("lb").longValue() : null, rel_obj.containsKey("ub") ? rel_obj.getJsonNumber("ub").longValue() : null, TimeUnit.valueOf(rel_obj.getString("unit")))); + } + switch (EventTemplate.EventTemplateType.valueOf(et_object.getString("type"))) { + case EventTemplate: + ets.add(new EventTemplate(EventTemplate.EventTemplateType.EventTemplate, name, role, trigger_condition, execution_condition, ids, relations)); + break; + case TextEventTemplate: + ets.add(new TextEventTemplate(name, role, trigger_condition, execution_condition, ids, relations, et_object.getString("content"))); + break; + case URLEventTemplate: + ets.add(new URLEventTemplate(name, role, trigger_condition, execution_condition, ids, relations, et_object.getString("content"), et_object.getString("url"))); + break; + case QuestionEventTemplate: + JsonArray answers_array = et_object.getJsonArray("answers"); + List answers = new ArrayList<>(answers_array.size()); + for (JsonValue ans_value : answers_array) { + JsonObject ans_onject = ans_value.asJsonObject(); + answers.add(new QuestionEventTemplate.Answer(ans_onject.getString("answer"), ans_onject.getString("event"))); + } + ets.add(new QuestionEventTemplate(name, role, trigger_condition, execution_condition, ids, relations, et_object.getString("question"), answers)); + break; + default: + throw new AssertionError(EventTemplate.EventTemplateType.valueOf(et_object.getString("type")).name()); + } + } + return ets; + } +} diff --git a/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/model/LessonModel.java b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/model/LessonModel.java new file mode 100644 index 0000000..de92ec1 --- /dev/null +++ b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/model/LessonModel.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2017 Riccardo De Benedictis + * + * 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 . + */ +package it.cnr.istc.lecture.api.model; + +import java.util.ArrayList; +import java.util.Collection; +import javax.json.bind.annotation.JsonbTypeAdapter; + +/** + * + * @author Riccardo De Benedictis + */ +public class LessonModel { + + public Long id; + public String name; + public ArrayList roles; + @JsonbTypeAdapter(EventTemplateListAdapter.class) + public ArrayList events; + public ArrayList ids; + public ArrayList relations; + + public LessonModel() { + } + + public LessonModel(long id, String name, Collection roles, Collection events, Collection ids, Collection relations) { + this.id = id; + this.name = name; + this.roles = new ArrayList<>(roles); + this.events = new ArrayList<>(events); + this.ids = new ArrayList<>(ids); + this.relations = new ArrayList<>(relations); + } +} diff --git a/ale-api/src/main/java/it/cnr/istc/ale/api/model/NominalCondition.java b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/model/NominalCondition.java similarity index 64% rename from ale-api/src/main/java/it/cnr/istc/ale/api/model/NominalCondition.java rename to LECTurE-API/src/main/java/it/cnr/istc/lecture/api/model/NominalCondition.java index fcb330c..65f8a8b 100644 --- a/ale-api/src/main/java/it/cnr/istc/ale/api/model/NominalCondition.java +++ b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/model/NominalCondition.java @@ -14,10 +14,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package it.cnr.istc.ale.api.model; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; +package it.cnr.istc.lecture.api.model; /** * @@ -25,20 +22,15 @@ */ public class NominalCondition extends Condition { - private final String variable; - private final String value; + public String variable; + public String value; - @JsonCreator - public NominalCondition(@JsonProperty("variable") String variable, @JsonProperty("value") String value) { - this.variable = variable; - this.value = value; + public NominalCondition() { } - public String getVariable() { - return variable; - } - - public String getValue() { - return value; + public NominalCondition(String variable, String value) { + super(ConditionType.NominalCondition); + this.variable = variable; + this.value = value; } } diff --git a/ale-api/src/main/java/it/cnr/istc/ale/api/model/NotCondition.java b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/model/NotCondition.java similarity index 70% rename from ale-api/src/main/java/it/cnr/istc/ale/api/model/NotCondition.java rename to LECTurE-API/src/main/java/it/cnr/istc/lecture/api/model/NotCondition.java index cfd76de..c9f3212 100644 --- a/ale-api/src/main/java/it/cnr/istc/ale/api/model/NotCondition.java +++ b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/model/NotCondition.java @@ -14,10 +14,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package it.cnr.istc.ale.api.model; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; +package it.cnr.istc.lecture.api.model; /** * @@ -25,14 +22,13 @@ */ public class NotCondition extends Condition { - private final Condition condition; + public Condition condition; - @JsonCreator - public NotCondition(@JsonProperty("condition") Condition condition) { - this.condition = condition; + public NotCondition() { } - public Condition getCondition() { - return condition; + public NotCondition(Condition condition) { + super(ConditionType.NotCondition); + this.condition = condition; } } diff --git a/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/model/NumericCondition.java b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/model/NumericCondition.java new file mode 100644 index 0000000..c380f29 --- /dev/null +++ b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/model/NumericCondition.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2017 Riccardo De Benedictis + * + * 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 . + */ +package it.cnr.istc.lecture.api.model; + +/** + * + * @author Riccardo De Benedictis + */ +public class NumericCondition extends Condition { + + public NumericConditionType numeric_condition_type; + public String variable; + public double value; + + public NumericCondition() { + } + + public NumericCondition(NumericConditionType type, String variable, double value) { + super(ConditionType.NumericCondition); + this.numeric_condition_type = type; + this.variable = variable; + this.value = value; + } + + public enum NumericConditionType { + GEq, Eq, LEq + } +} diff --git a/ale-api/src/main/java/it/cnr/istc/ale/api/model/OrCondition.java b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/model/OrCondition.java similarity index 65% rename from ale-api/src/main/java/it/cnr/istc/ale/api/model/OrCondition.java rename to LECTurE-API/src/main/java/it/cnr/istc/lecture/api/model/OrCondition.java index 29c14c0..7c2af13 100644 --- a/ale-api/src/main/java/it/cnr/istc/ale/api/model/OrCondition.java +++ b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/model/OrCondition.java @@ -14,11 +14,11 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package it.cnr.istc.ale.api.model; +package it.cnr.istc.lecture.api.model; -import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; -import java.util.Collections; /** * @@ -26,13 +26,17 @@ */ public class OrCondition extends Condition { - private final Collection conditions; + public ArrayList conditions; - public OrCondition(@JsonProperty("conditions") Collection conditions) { - this.conditions = conditions; + public OrCondition() { } - public Collection getConditions() { - return Collections.unmodifiableCollection(conditions); + public OrCondition(Condition... conditions) { + this(Arrays.asList(conditions)); + } + + public OrCondition(Collection conditions) { + super(ConditionType.OrCondition); + this.conditions = new ArrayList<>(conditions); } } diff --git a/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/model/QuestionEventTemplate.java b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/model/QuestionEventTemplate.java new file mode 100644 index 0000000..4cabef6 --- /dev/null +++ b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/model/QuestionEventTemplate.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2017 Riccardo De Benedictis + * + * 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 . + */ +package it.cnr.istc.lecture.api.model; + +import java.util.ArrayList; +import java.util.Collection; + +/** + * + * @author Riccardo De Benedictis + */ +public class QuestionEventTemplate extends EventTemplate { + + public String question; + public ArrayList answers; + + public QuestionEventTemplate() { + } + + public QuestionEventTemplate(String name, String role, Condition trigger_condition, Condition execution_condition, Collection events, Collection relations, String question, Collection answers) { + super(EventTemplateType.QuestionEventTemplate, name, role, trigger_condition, execution_condition, events, relations); + this.question = question; + this.answers = new ArrayList<>(answers); + } + + public static class Answer { + + public String answer; + public String event; + + public Answer() { + } + + public Answer(String answer, String event) { + this.answer = answer; + this.event = event; + } + } +} diff --git a/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/model/Relation.java b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/model/Relation.java new file mode 100644 index 0000000..4f90453 --- /dev/null +++ b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/model/Relation.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2017 Riccardo De Benedictis + * + * 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 . + */ +package it.cnr.istc.lecture.api.model; + +import java.util.concurrent.TimeUnit; + +/** + * + * @author Riccardo De Benedictis + */ +public class Relation { + + public String from; + public String to; + public Long lb; + public Long ub; + public TimeUnit unit; + + public Relation() { + } + + public Relation(String from, String to, Long lb, Long ub, TimeUnit unit) { + this.from = from; + this.to = to; + this.lb = lb; + this.ub = ub; + this.unit = unit; + } +} diff --git a/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/model/TextEventTemplate.java b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/model/TextEventTemplate.java new file mode 100644 index 0000000..dfe3025 --- /dev/null +++ b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/model/TextEventTemplate.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2017 Riccardo De Benedictis + * + * 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 . + */ +package it.cnr.istc.lecture.api.model; + +import java.util.Collection; + +/** + * + * @author Riccardo De Benedictis + */ +public class TextEventTemplate extends EventTemplate { + + public String content; + + public TextEventTemplate() { + } + + public TextEventTemplate(String name, String role, Condition trigger_condition, Condition execution_condition, Collection events, Collection relations, String content) { + super(EventTemplateType.TextEventTemplate, name, role, trigger_condition, execution_condition, events, relations); + this.content = content; + } +} diff --git a/ale-api/src/main/java/it/cnr/istc/ale/api/messages/URLEvent.java b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/model/URLEventTemplate.java similarity index 58% rename from ale-api/src/main/java/it/cnr/istc/ale/api/messages/URLEvent.java rename to LECTurE-API/src/main/java/it/cnr/istc/lecture/api/model/URLEventTemplate.java index b714f19..3e402fe 100644 --- a/ale-api/src/main/java/it/cnr/istc/ale/api/messages/URLEvent.java +++ b/LECTurE-API/src/main/java/it/cnr/istc/lecture/api/model/URLEventTemplate.java @@ -14,32 +14,25 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package it.cnr.istc.ale.api.messages; +package it.cnr.istc.lecture.api.model; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Collection; /** * * @author Riccardo De Benedictis */ -public class URLEvent extends Event { +public class URLEventTemplate extends EventTemplate { - private final String content; - private final String url; + public String content; + public String url; - @JsonCreator - public URLEvent(@JsonProperty("lessonId") long lesson_id, @JsonProperty("id") int id, @JsonProperty("content") String content, @JsonProperty("url") String url) { - super(lesson_id, id); - this.content = content; - this.url = url; - } - - public String getContent() { - return content; + public URLEventTemplate() { } - public String getUrl() { - return url; + public URLEventTemplate(String name, String role, Condition trigger_condition, Condition execution_condition, Collection events, Collection relations, String content, String url) { + super(EventTemplateType.URLEventTemplate, name, role, trigger_condition, execution_condition, events, relations); + this.content = content; + this.url = url; } } diff --git a/ale-api/lessons/lesson_01.json b/LECTurE-API/src/main/resources/lesson_01.json similarity index 71% rename from ale-api/lessons/lesson_01.json rename to LECTurE-API/src/main/resources/lesson_01.json index e6330bc..d7d175d 100644 --- a/ale-api/lessons/lesson_01.json +++ b/LECTurE-API/src/main/resources/lesson_01.json @@ -1,135 +1,135 @@ -{ - "name": "Lesson one", - "roles": [ - "Police officer", - "Firefighter" - ], - "model": [ - { - "type": "text", - "name": "police_0", - "role": "Police officer", - "triggerCondition": null, - "executionCondition": null, - "events": [], - "relations": [], - "content": "Hi police officer!\nThis is your first text event." - }, - { - "type": "question", - "name": "police_1", - "role": "Police officer", - "triggerCondition": null, - "executionCondition": null, - "events": [], - "relations": [], - "question": "This is your first question! Which is your answer?", - "answers": [ - { - "answer": "First choice", - "event": "police_2" - }, - { - "answer": "Second choice", - "event": "police_2" - } - ] - }, - { - "type": "text", - "name": "police_2", - "role": "Police officer", - "triggerCondition": null, - "executionCondition": null, - "events": [], - "relations": [], - "content": "This is your second event." - }, - { - "type": "text", - "name": "fire_0", - "role": "Firefighter", - "triggerCondition": null, - "executionCondition": null, - "events": [], - "relations": [], - "content": "Hi firefighter!\nThis is your first text event." - }, - { - "type": "url", - "name": "fire_1", - "role": "Firefighter", - "triggerCondition": null, - "executionCondition": null, - "events": [], - "relations": [], - "content": "Hi firefighter!\nThis is your first cruciverba.", - "url": "http://www.cruciverbaonline.it/index.php" - }, - { - "type": "text", - "name": "fire_2", - "role": "Firefighter", - "triggerCondition": null, - "executionCondition": null, - "events": [], - "relations": [], - "content": "This is your second event." - }, - { - "type": "url", - "name": "fire_3", - "role": "Firefighter", - "triggerCondition": null, - "executionCondition": null, - "events": [], - "relations": [], - "content": "Hi firefighter!\nThis is your first info about Colosseum.", - "url": "https://it.wikipedia.org/wiki/Colosseo" - } - ], - "events": [ - "police_0", - "police_1", - "fire_0", - "fire_1", - "fire_3" - ], - "relations": [ - { - "from": "this", - "to": "police_0", - "lb": 6, - "ub": 10, - "unit": "SECONDS" - }, - { - "from": "police_0", - "to": "police_1", - "lb": 4, - "ub": 10, - "unit": "SECONDS" - }, - { - "from": "this", - "to": "fire_0", - "lb": 5, - "ub": 10, - "unit": "SECONDS" - }, - { - "from": "fire_0", - "to": "fire_1", - "lb": 5, - "ub": 10, - "unit": "SECONDS" - }, - { - "from": "fire_1", - "to": "fire_3", - "lb": 5, - "ub": 10, - "unit": "SECONDS" - } - ] +{ + "name": "Lesson one", + "roles": [ + "Police officer", + "Firefighter" + ], + "events": [ + { + "type": "TextEventTemplate", + "name": "police_0", + "role": "Police officer", + "trigger_condition": null, + "execution_condition": null, + "ids": [], + "relations": [], + "content": "Hi police officer!\nThis is your first text event." + }, + { + "type": "QuestionEventTemplate", + "name": "police_1", + "role": "Police officer", + "trigger_condition": null, + "execution_condition": null, + "ids": [], + "relations": [], + "question": "This is your first question! Which is your answer?", + "answers": [ + { + "answer": "First choice", + "event": "police_2" + }, + { + "answer": "Second choice", + "event": "police_2" + } + ] + }, + { + "type": "TextEventTemplate", + "name": "police_2", + "role": "Police officer", + "trigger_condition": null, + "execution_condition": null, + "ids": [], + "relations": [], + "content": "This is your second event." + }, + { + "type": "TextEventTemplate", + "name": "fire_0", + "role": "Firefighter", + "trigger_condition": null, + "execution_condition": null, + "ids": [], + "relations": [], + "content": "Hi firefighter!\nThis is your first text event." + }, + { + "type": "URLEventTemplate", + "name": "fire_1", + "role": "Firefighter", + "trigger_condition": null, + "execution_condition": null, + "ids": [], + "relations": [], + "content": "Hi firefighter!\nThis is your first cruciverba.", + "url": "http://www.cruciverbaonline.it/index.php" + }, + { + "type": "TextEventTemplate", + "name": "fire_2", + "role": "Firefighter", + "trigger_condition": null, + "execution_condition": null, + "ids": [], + "relations": [], + "content": "This is your second event." + }, + { + "type": "URLEventTemplate", + "name": "fire_3", + "role": "Firefighter", + "trigger_condition": null, + "execution_condition": null, + "ids": [], + "relations": [], + "content": "Hi firefighter!\nThis is your first info about Colosseum.", + "url": "https://it.wikipedia.org/wiki/Colosseo" + } + ], + "ids": [ + "police_0", + "police_1", + "fire_0", + "fire_1", + "fire_3" + ], + "relations": [ + { + "from": "this", + "to": "police_0", + "lb": 6, + "ub": 10, + "unit": "SECONDS" + }, + { + "from": "police_0", + "to": "police_1", + "lb": 4, + "ub": 10, + "unit": "SECONDS" + }, + { + "from": "this", + "to": "fire_0", + "lb": 5, + "ub": 10, + "unit": "SECONDS" + }, + { + "from": "fire_0", + "to": "fire_1", + "lb": 5, + "ub": 10, + "unit": "SECONDS" + }, + { + "from": "fire_1", + "to": "fire_3", + "lb": 5, + "ub": 10, + "unit": "SECONDS" + } + ] } \ No newline at end of file diff --git a/client/nb-configuration.xml b/LECTurE-DesktopApp/nb-configuration.xml similarity index 92% rename from client/nb-configuration.xml rename to LECTurE-DesktopApp/nb-configuration.xml index 2e44fc1..17ac18b 100644 --- a/client/nb-configuration.xml +++ b/LECTurE-DesktopApp/nb-configuration.xml @@ -13,6 +13,7 @@ That way multiple projects can share the same settings (useful for formatting rules for example). Any value defined here will override the pom.xml file value but is only applicable to the current project. --> + none gpl30 diff --git a/LECTurE-DesktopApp/nbactions.xml b/LECTurE-DesktopApp/nbactions.xml new file mode 100644 index 0000000..557c649 --- /dev/null +++ b/LECTurE-DesktopApp/nbactions.xml @@ -0,0 +1,36 @@ + + + + run + + clean + package + org.codehaus.mojo:exec-maven-plugin:1.2.1:exec + + + -jar "${project.build.directory}/${project.build.finalName}.jar" + + + + debug + + clean + package + org.codehaus.mojo:exec-maven-plugin:1.2.1:exec + + + -Xdebug -Xrunjdwp:transport=dt_socket,server=n,address=${jpda.address} -Dglass.disableGrab=true -jar "${project.build.directory}/${project.build.finalName}.jar" + true + + + + CUSTOM-Run simple + Run simple + + org.codehaus.mojo:exec-maven-plugin:1.2.1:exec + + + -jar "${project.build.directory}/${project.build.finalName}.jar" + + + diff --git a/LECTurE-DesktopApp/pom.xml b/LECTurE-DesktopApp/pom.xml new file mode 100644 index 0000000..71ad523 --- /dev/null +++ b/LECTurE-DesktopApp/pom.xml @@ -0,0 +1,148 @@ + + + 4.0.0 + + it.cnr.istc + LECTurE-DesktopApp + 1.0 + jar + + LECTurE-DesktopApp + + + UTF-8 + it.cnr.istc.lecture.desktopapp.MainApp + + + + ISTC - CNR + + + + + org.glassfish.jersey.core + jersey-client + 2.26 + + + org.glassfish.jersey.inject + jersey-hk2 + 2.26 + + + org.glassfish.jersey.media + jersey-media-json-binding + 2.26 + + + org.eclipse.paho + org.eclipse.paho.client.mqttv3 + 1.2.0 + + + org.controlsfx + controlsfx + 8.40.14 + + + org.jfree + jfreechart-fx + 1.0.1 + + + ${project.groupId} + LECTurE-API + ${project.version} + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + 2.6 + + + unpack-dependencies + package + + unpack-dependencies + + + system + META-INF/*.SF + META-INF/*.DSA + META-INF/*.RSA + junit,org.mockito,org.hamcrest + ${project.build.directory}/classes + + + + + + org.codehaus.mojo + exec-maven-plugin + 1.2.1 + + + unpack-dependencies + + package + + exec + + + ${java.home}/../bin/javapackager + + -createjar + -nocss2bin + -appclass + ${mainClass} + -srcdir + ${project.build.directory}/classes + -outdir + ${project.build.directory} + -outfile + ${project.build.finalName}.jar + + + + + default-cli + + exec + + + ${java.home}/bin/java + ${runfx.args} + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.8 + 1.8 + + ${sun.boot.class.path}${path.separator}${java.home}/lib/jfxrt.jar + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.16 + + + ${java.home}/lib/jfxrt.jar + + + + + + + diff --git a/client/src/main/java/it/cnr/istc/ale/client/AddLessonDialog.java b/LECTurE-DesktopApp/src/main/java/it/cnr/istc/lecture/desktopapp/AddLessonDialog.java similarity index 63% rename from client/src/main/java/it/cnr/istc/ale/client/AddLessonDialog.java rename to LECTurE-DesktopApp/src/main/java/it/cnr/istc/lecture/desktopapp/AddLessonDialog.java index 18ef2d7..ae9cdb9 100644 --- a/client/src/main/java/it/cnr/istc/ale/client/AddLessonDialog.java +++ b/LECTurE-DesktopApp/src/main/java/it/cnr/istc/lecture/desktopapp/AddLessonDialog.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 Riccardo De Benedictis + * Copyright (C) 2018 ISTC - CNR * * 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 @@ -14,12 +14,12 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package it.cnr.istc.ale.client; +package it.cnr.istc.lecture.desktopapp; -import it.cnr.istc.ale.api.User; -import it.cnr.istc.ale.api.model.LessonModel; -import it.cnr.istc.ale.client.context.Context; +import it.cnr.istc.lecture.api.User; +import it.cnr.istc.lecture.api.model.LessonModel; import java.io.File; +import java.io.FileInputStream; import java.io.IOException; import java.util.Collections; import java.util.Map; @@ -31,14 +31,18 @@ import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; +import javafx.beans.value.ObservableValue; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.event.ActionEvent; import javafx.scene.control.Button; import javafx.scene.control.ButtonBar; import javafx.scene.control.ButtonType; +import javafx.scene.control.ComboBox; import javafx.scene.control.Dialog; import javafx.scene.control.Label; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; import javafx.scene.control.TextField; @@ -48,8 +52,12 @@ import static javafx.scene.layout.GridPane.setHgrow; import static javafx.scene.layout.GridPane.setVgrow; import javafx.scene.layout.Priority; +import javafx.scene.layout.Region; import javafx.stage.FileChooser; +import javafx.stage.Stage; import javafx.util.StringConverter; +import org.controlsfx.glyphfont.FontAwesome; +import org.controlsfx.glyphfont.Glyph; /** * @@ -59,13 +67,13 @@ public class AddLessonDialog extends Dialog { private static final FileChooser FILE_CHOOSER = new FileChooser(); private final GridPane grid = new GridPane(); - private final TextField lesson_type_name = new TextField(); + private final ComboBox lesson_types = new ComboBox<>(); private final TextField lesson_name = new TextField(); - private final Button open_button = new Button("Open"); + private final Button open_button = new Button("", new Glyph("FontAwesome", FontAwesome.Glyph.FILE_CODE_ALT)); private final ObservableList roles = FXCollections.observableArrayList(); private final TableView roles_table_view = new TableView<>(roles); private final TableColumn role_column = new TableColumn<>("Role"); - private final TableColumn student_column = new TableColumn<>("Student"); + private final TableColumn student_column = new TableColumn<>("Student"); private final ButtonType add_button = new ButtonType("Add", ButtonBar.ButtonData.OK_DONE); private LessonModel lesson_model; @@ -73,7 +81,7 @@ public class AddLessonDialog extends Dialog { public AddLessonDialog() { grid.setHgap(10); grid.setVgap(10); - setHgrow(lesson_type_name, Priority.ALWAYS); + setHgrow(lesson_types, Priority.ALWAYS); setHgrow(lesson_name, Priority.ALWAYS); setVgrow(roles_table_view, Priority.ALWAYS); setHgrow(roles_table_view, Priority.ALWAYS); @@ -81,9 +89,41 @@ public AddLessonDialog() { setTitle("Add lesson"); grid.add(new Label("Lesson type:"), 0, 0); - lesson_type_name.setPromptText("Lesson type"); - lesson_type_name.setEditable(false); - grid.add(lesson_type_name, 1, 0); + lesson_types.setPromptText("Lesson type"); + lesson_types.setEditable(false); + lesson_types.setItems(Context.getContext().modelsProperty()); + lesson_types.valueProperty().addListener((ObservableValue observable, LessonModel oldValue, LessonModel newValue) -> { + lesson_model = newValue; + roles.clear(); + if (newValue != null) { + lesson_model.roles.forEach(role -> roles.add(new StudentRole(role, null))); + } + getDialogPane().lookupButton(add_button).disableProperty().unbind(); + getDialogPane().lookupButton(add_button).disableProperty().bind(lesson_types.valueProperty().isNull().or(lesson_name.textProperty().isEmpty()).or(new StudentRoleBinding())); + }); + lesson_types.setCellFactory((ListView param) -> new ListCell() { + @Override + public void updateItem(LessonModel item, boolean empty) { + super.updateItem(item, empty); + if (empty || item == null) { + setText(null); + } else { + setText(item.name); + } + } + }); + lesson_types.setConverter(new StringConverter() { + @Override + public String toString(LessonModel object) { + return object.name; + } + + @Override + public LessonModel fromString(String string) { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + }); + grid.add(lesson_types, 1, 0); grid.add(open_button, 2, 0); grid.add(new Label("Lesson name:"), 0, 1); lesson_name.setPromptText("Lesson name"); @@ -96,23 +136,23 @@ public AddLessonDialog() { roles_table_view.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); role_column.setCellValueFactory(new PropertyValueFactory<>("role")); student_column.setCellValueFactory(new PropertyValueFactory<>("student")); - student_column.setCellFactory(ComboBoxTableCell.forTableColumn(new StringConverter() { + student_column.setCellFactory(ComboBoxTableCell.forTableColumn(new StringConverter() { @Override - public String toString(User user) { - if (user == null) { + public String toString(StudentContext std_ctx) { + if (std_ctx == null) { return "Select one"; } else { - return user.getLastName() + " " + user.getFirstName(); + return std_ctx.getStudent().first_name + " " + std_ctx.getStudent().last_name; } } @Override - public User fromString(String string) { + public StudentContext fromString(String string) { throw new UnsupportedOperationException("Not supported yet."); } - }, Context.getContext().getTeachingContext().getStudents())); + }, Context.getContext().studentsProperty())); student_column.setEditable(true); - student_column.setOnEditCommit((TableColumn.CellEditEvent event) -> { + student_column.setOnEditCommit((TableColumn.CellEditEvent event) -> { roles.get(event.getTablePosition().getRow()).student.set(event.getNewValue()); }); @@ -129,12 +169,7 @@ public User fromString(String string) { File lesson_file = FILE_CHOOSER.showOpenDialog(Context.getContext().getStage()); if (lesson_file != null) { try { - lesson_model = Context.MAPPER.readValue(lesson_file, LessonModel.class); - lesson_type_name.setText(lesson_model.getName()); - lesson_model.getRoles().forEach(role -> roles.add(new StudentRole(role, null))); - - getDialogPane().lookupButton(add_button).disableProperty().unbind(); - getDialogPane().lookupButton(add_button).disableProperty().bind(lesson_type_name.textProperty().isEmpty().or(lesson_name.textProperty().isEmpty()).or(new StudentRoleBinding())); + lesson_types.setValue(Context.JSONB.fromJson(new FileInputStream(lesson_file), LessonModel.class)); } catch (IOException ex) { Logger.getLogger(AddLessonDialog.class.getName()).log(Level.SEVERE, null, ex); } @@ -143,6 +178,8 @@ public User fromString(String string) { getDialogPane().getButtonTypes().add(add_button); getDialogPane().lookupButton(add_button).disableProperty().set(true); + getDialogPane().setMinHeight(Region.USE_PREF_SIZE); + ((Stage) getDialogPane().getScene().getWindow()).getIcons().addAll(Context.getContext().getStage().getIcons()); setResultConverter((ButtonType param) -> param == add_button ? new AddLessonResult(lesson_model, lesson_name.getText(), roles.stream().collect(Collectors.toMap(StudentRole::getRole, StudentRole::getStudentId))) : null); } @@ -161,9 +198,9 @@ protected boolean computeValue() { public static class StudentRole { public final StringProperty role; - public final ObjectProperty student; + public final ObjectProperty student; - public StudentRole(String name, User student) { + public StudentRole(String name, StudentContext student) { this.role = new SimpleStringProperty(name); this.student = new SimpleObjectProperty<>(student); } @@ -176,11 +213,11 @@ public StringProperty roleProperty() { return role; } - public Long getStudentId() { - return student.get().getId(); + public long getStudentId() { + return student.get().getStudent().id; } - public ObjectProperty studentProperty() { + public ObjectProperty studentProperty() { return student; } } diff --git a/client/src/main/java/it/cnr/istc/ale/client/AddTeachersDialog.java b/LECTurE-DesktopApp/src/main/java/it/cnr/istc/lecture/desktopapp/AddTeachersDialog.java similarity index 87% rename from client/src/main/java/it/cnr/istc/ale/client/AddTeachersDialog.java rename to LECTurE-DesktopApp/src/main/java/it/cnr/istc/lecture/desktopapp/AddTeachersDialog.java index 950c45e..eced142 100644 --- a/client/src/main/java/it/cnr/istc/ale/client/AddTeachersDialog.java +++ b/LECTurE-DesktopApp/src/main/java/it/cnr/istc/lecture/desktopapp/AddTeachersDialog.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 Riccardo De Benedictis + * Copyright (C) 2018 ISTC - CNR * * 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 @@ -14,10 +14,9 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package it.cnr.istc.ale.client; +package it.cnr.istc.lecture.desktopapp; -import it.cnr.istc.ale.api.User; -import it.cnr.istc.ale.client.context.Context; +import it.cnr.istc.lecture.api.User; import javafx.beans.binding.Bindings; import javafx.collections.FXCollections; import javafx.collections.ObservableList; @@ -32,6 +31,8 @@ import javafx.scene.control.TextField; import javafx.scene.input.MouseEvent; import javafx.scene.layout.GridPane; +import javafx.scene.layout.Region; +import javafx.stage.Stage; /** * @@ -65,7 +66,7 @@ protected void updateItem(User user, boolean empty) { if (empty) { setText(null); } else { - setText(user.getLastName() + " " + user.getFirstName()); + setText(user.first_name + " " + user.last_name); } } }); @@ -82,6 +83,8 @@ protected void updateItem(User user, boolean empty) { getDialogPane().getButtonTypes().add(add_button); getDialogPane().lookupButton(add_button).disableProperty().bind(Bindings.isEmpty(found_users_list_view.selectionModelProperty().get().getSelectedItems())); + getDialogPane().setMinHeight(Region.USE_PREF_SIZE); + ((Stage) getDialogPane().getScene().getWindow()).getIcons().addAll(Context.getContext().getStage().getIcons()); setResultConverter((ButtonType param) -> param == add_button ? found_users_list_view.getSelectionModel().getSelectedItems().toArray(new User[found_users_list_view.getSelectionModel().getSelectedItems().size()]) : null); } } diff --git a/LECTurE-DesktopApp/src/main/java/it/cnr/istc/lecture/desktopapp/Context.java b/LECTurE-DesktopApp/src/main/java/it/cnr/istc/lecture/desktopapp/Context.java new file mode 100644 index 0000000..67be507 --- /dev/null +++ b/LECTurE-DesktopApp/src/main/java/it/cnr/istc/lecture/desktopapp/Context.java @@ -0,0 +1,707 @@ +/* + * Copyright (C) 2018 ISTC - CNR + * + * 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 . + */ +package it.cnr.istc.lecture.desktopapp; + +import it.cnr.istc.lecture.api.Credentials; +import it.cnr.istc.lecture.api.InitResponse; +import it.cnr.istc.lecture.api.Lesson; +import it.cnr.istc.lecture.api.Lesson.LessonState; +import it.cnr.istc.lecture.api.NewLessonRequest; +import it.cnr.istc.lecture.api.NewUserRequest; +import it.cnr.istc.lecture.api.Parameter; +import it.cnr.istc.lecture.api.User; +import it.cnr.istc.lecture.api.messages.Event; +import it.cnr.istc.lecture.api.messages.HideEvent; +import it.cnr.istc.lecture.api.messages.LostLesson; +import it.cnr.istc.lecture.api.messages.LostParameter; +import it.cnr.istc.lecture.api.messages.Message; +import it.cnr.istc.lecture.api.messages.EventAdapter; +import it.cnr.istc.lecture.api.messages.LostStudent; +import it.cnr.istc.lecture.api.messages.NewLesson; +import it.cnr.istc.lecture.api.messages.NewParameter; +import it.cnr.istc.lecture.api.messages.NewStudent; +import it.cnr.istc.lecture.api.messages.QuestionEvent; +import it.cnr.istc.lecture.api.messages.RemoveToken; +import it.cnr.istc.lecture.api.messages.Token; +import it.cnr.istc.lecture.api.messages.TokenUpdate; +import it.cnr.istc.lecture.api.model.LessonModel; +import it.cnr.istc.lecture.desktopapp.TeachingLessonContext.TokenRow; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; +import javafx.application.Platform; +import javafx.beans.Observable; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import javafx.beans.value.ObservableValue; +import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.stage.Stage; +import javax.json.bind.Jsonb; +import javax.json.bind.JsonbBuilder; +import javax.json.bind.JsonbConfig; +import javax.ws.rs.client.Client; +import javax.ws.rs.client.ClientBuilder; +import javax.ws.rs.client.Entity; +import javax.ws.rs.client.WebTarget; +import javax.ws.rs.core.Form; +import javax.ws.rs.core.GenericType; +import javax.ws.rs.core.MediaType; +import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; +import org.eclipse.paho.client.mqttv3.MqttCallback; +import org.eclipse.paho.client.mqttv3.MqttClient; +import org.eclipse.paho.client.mqttv3.MqttConnectOptions; +import org.eclipse.paho.client.mqttv3.MqttException; +import org.eclipse.paho.client.mqttv3.MqttMessage; +import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; + +/** + * + * @author Riccardo De Benedictis + */ +public class Context { + + private static final Logger LOG = Logger.getLogger(Context.class.getName()); + public static final Jsonb JSONB = JsonbBuilder.create(new JsonbConfig().withAdapters(new EventAdapter())); + private static Context ctx; + + public static Context getContext() { + if (ctx == null) { + ctx = new Context(); + } + return ctx; + } + private final Properties properties = new Properties(); + private final Client client = ClientBuilder.newClient(); + private final WebTarget target; + private MqttClient mqtt; + private Stage stage; + /** + * The current user. + */ + private final ObjectProperty user = new SimpleObjectProperty<>(); + /** + * The current user's parameter types. + */ + private final ObservableList par_types = FXCollections.observableArrayList(); + private final Map id_par_types = new HashMap<>(); + /** + * The current user's parameter values. + */ + private final Map> par_vals = new HashMap<>(); + /** + * The current user's parameter values as a list, to be displayed on tables. + * Notice that each parameter can aggregate more than a single value. + */ + private final ObservableList par_values = FXCollections.observableArrayList(); + /** + * The received events. + */ + private final ObservableList events = FXCollections.observableArrayList(); + /** + * The lessons followed as a student. + */ + private final ObservableList following_lessons = FXCollections.observableArrayList(l_ctx -> new Observable[]{l_ctx.stateProperty()}); + private final Map id_following_lessons = new HashMap<>(); + /** + * The followed teachers. + */ + private final ObservableList teachers = FXCollections.observableArrayList(tch_ctx -> new Observable[]{tch_ctx.onlineProperty()}); + private final Map id_teachers = new HashMap<>(); + /** + * The lesson models associated to the teacher. + */ + private final ObservableList models = FXCollections.observableArrayList(); + /** + * The lessons followed as a teacher. + */ + private final ObservableList teaching_lessons = FXCollections.observableArrayList(l_ctx -> new Observable[]{l_ctx.stateProperty()}); + private final Map id_teaching_lessons = new HashMap<>(); + /** + * The following students. + */ + private final ObservableList students = FXCollections.observableArrayList(std_ctx -> new Observable[]{std_ctx.onlineProperty()}); + private final Map id_students = new HashMap<>(); + + private Context() { + try { + properties.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("config.properties")); + } catch (IOException ex) { + LOG.log(Level.SEVERE, null, ex); + } + this.target = client.target("http://" + properties.getProperty("host", "localhost") + ":" + properties.getProperty("service-port", "8080")).path("LECTurE-WebApp").path("LECTurE"); + user.addListener((ObservableValue observable, User oldValue, User newValue) -> { + if (oldValue != null) { + // we clear the current data.. + try { + par_values.clear(); + par_vals.clear(); + for (Parameter par : par_types) { + // we broadcast the lost of a parameter.. + mqtt.publish(oldValue.id + "/output", JSONB.toJson(new LostParameter(par.name)).getBytes(), 1, false); + } + par_types.clear(); + events.clear(); + for (FollowingLessonContext l_ctx : following_lessons) { + // we unsubscribe from the lesson's time and state.. + mqtt.unsubscribe(oldValue.id + "/input/lesson-" + l_ctx.getLesson().id + "/time"); + mqtt.unsubscribe(oldValue.id + "/input/lesson-" + l_ctx.getLesson().id + "/state"); + } + following_lessons.clear(); + teachers.clear(); + models.clear(); + for (TeachingLessonContext l_ctx : teaching_lessons) { + l_ctx.tokensProperty().clear(); + // we unsubscribe from the lesson's time and state.. + mqtt.unsubscribe(oldValue.id + "/input/lesson-" + l_ctx.getLesson().id + "/time"); + mqtt.unsubscribe(oldValue.id + "/input/lesson-" + l_ctx.getLesson().id + "/state"); + } + teaching_lessons.clear(); + students.clear(); + mqtt.disconnect(); + mqtt.close(); + } catch (MqttException ex) { + LOG.log(Level.SEVERE, null, ex); + } + } + if (newValue != null) { + // we set up a new user.. + try { + mqtt = new MqttClient("tcp://" + properties.getProperty("host", "localhost") + ":" + properties.getProperty("mqtt-port", "1883"), String.valueOf(newValue.id), new MemoryPersistence()); + mqtt.setCallback(new MqttCallback() { + @Override + public void connectionLost(Throwable cause) { + LOG.log(Level.SEVERE, null, cause); + user.set(null); + } + + @Override + public void messageArrived(String topic, MqttMessage message) throws Exception { + LOG.log(Level.WARNING, "message arrived: {0} - {1}", new Object[]{topic, message}); + } + + @Override + public void deliveryComplete(IMqttDeliveryToken token) { + } + }); + + MqttConnectOptions options = new MqttConnectOptions(); + options.setCleanSession(true); + options.setAutomaticReconnect(true); + mqtt.connect(options); + + mqtt.subscribe(newValue.id + "/input", (String topic, MqttMessage message) -> { + LOG.log(Level.INFO, "message arrived: {0} - {1}", new Object[]{topic, message}); + Message m = JSONB.fromJson(new String(message.getPayload()), Message.class); + switch (m.message_type) { + case NewStudent: + // a new student is following this user.. + NewStudent new_student = JSONB.fromJson(new String(message.getPayload()), NewStudent.class); + Platform.runLater(() -> students.add(new StudentContext(new_student.student))); + break; + case LostStudent: + // a student is not following this user anymore.. + LostStudent lost_student = JSONB.fromJson(new String(message.getPayload()), LostStudent.class); + Platform.runLater(() -> students.remove(id_students.get(lost_student.student_id))); + break; + case NewLesson: + // a teacher has created a new lesson for this student.. + NewLesson new_lesson = JSONB.fromJson(new String(message.getPayload()), NewLesson.class); + Platform.runLater(() -> following_lessons.add(new FollowingLessonContext(new_lesson.lesson))); + break; + case LostLesson: + // a teacher has removed a new lesson for this student.. + LostLesson lost_lesson = JSONB.fromJson(new String(message.getPayload()), LostLesson.class); + Platform.runLater(() -> following_lessons.remove(id_following_lessons.get(lost_lesson.lesson_id))); + break; + case Token: + // a new token has been created for a teaching lesson.. + Token token = JSONB.fromJson(new String(message.getPayload()), Token.class); + Platform.runLater(() -> id_teaching_lessons.get(token.lesson_id).tokensProperty().add(new TeachingLessonContext.TokenRow(token.id, id_teaching_lessons.get(token.lesson_id).timeProperty(), token.min, token.max, token.time, token.refEvent))); + break; + case TokenUpdate: + // a token of a teaching lesson has been updated.. + TokenUpdate token_update = JSONB.fromJson(new String(message.getPayload()), TokenUpdate.class); + Platform.runLater(() -> { + id_teaching_lessons.get(token_update.lesson_id).getToken(token_update.id).timeProperty().setValue(token_update.time); + id_teaching_lessons.get(token_update.lesson_id).getToken(token_update.id).minProperty().setValue(token_update.min); + id_teaching_lessons.get(token_update.lesson_id).getToken(token_update.id).maxProperty().setValue(token_update.max); + }); + break; + case RemoveToken: + // a token of a teaching lesson has been removed.. + RemoveToken remove_token = JSONB.fromJson(new String(message.getPayload()), RemoveToken.class); + Platform.runLater(() -> id_teaching_lessons.get(remove_token.lesson_id).tokensProperty().remove(id_teaching_lessons.get(remove_token.lesson_id).getToken((int) remove_token.event_id))); + break; + case Event: + // a new event has been created for a following lesson.. + Event event = JSONB.fromJson(new String(message.getPayload()), Event.class); + Platform.runLater(() -> id_following_lessons.get(event.lesson_id).eventsProperty().add(event)); + break; + case HideEvent: + // an event has been removed for a following lesson.. + HideEvent hide_event = JSONB.fromJson(new String(message.getPayload()), HideEvent.class); + Platform.runLater(() -> id_following_lessons.get(hide_event.lesson_id).eventsProperty().removeIf(e -> e.event_id == hide_event.event_id)); + break; + case Answer: + break; + default: + throw new AssertionError(m.message_type.name()); + } + }); + + for (Parameter par : newValue.par_types.values()) { + par_types.add(par); + // we broadcast the existence of a new parameter.. + mqtt.publish(newValue.id + "/output", JSONB.toJson(new NewParameter(par)).getBytes(), 1, false); + } + for (Map.Entry> par_val : newValue.par_values.entrySet()) { + Map c_vals = new HashMap<>(); + for (Map.Entry val : par_val.getValue().entrySet()) { + ParameterValue val_prop = new ParameterValue(par_val.getKey() + "." + val.getKey(), val.getValue()); + c_vals.put(val.getKey(), val_prop); + par_values.add(val_prop); + } + par_vals.put(par_val.getKey(), c_vals); + // we broadcast the the new value of the parameter.. + mqtt.publish(newValue.id + "/output/" + par_val.getKey(), JSONB.toJson(par_val.getValue()).getBytes(), 1, true); + } + } catch (MqttException ex) { + LOG.log(Level.SEVERE, null, ex); + } + } + }); + + following_lessons.addListener((ListChangeListener.Change c) -> { + while (c.next()) { + for (FollowingLessonContext flc : c.getAddedSubList()) { + id_following_lessons.put(flc.getLesson().id, flc); + flc.eventsProperty().addAll(flc.getLesson().events); + try { + // we subscribe to the lesson's time.. + mqtt.subscribe(flc.getLesson().teacher_id + "/input/lesson-" + flc.getLesson().id + "/time", (String topic, MqttMessage message) -> { + Platform.runLater(() -> flc.timeProperty().setValue(Long.parseLong(new String(message.getPayload())))); + }); + // we subscribe to the lesson's state.. + mqtt.subscribe(flc.getLesson().teacher_id + "/input/lesson-" + flc.getLesson().id + "/state", (String topic, MqttMessage message) -> { + Platform.runLater(() -> flc.stateProperty().setValue(LessonState.valueOf(new String(message.getPayload())))); + }); + } catch (MqttException ex) { + LOG.log(Level.SEVERE, null, ex); + } + } + for (FollowingLessonContext flc : c.getRemoved()) { + id_following_lessons.remove(flc.getLesson().id); + events.removeAll(flc.eventsProperty()); + flc.eventsProperty().clear(); + if (user.isNotNull().get()) { + try { + // we subscribe from the lesson's time and state.. + mqtt.unsubscribe(user.get().id + "/input/lesson-" + flc.getLesson().id + "/time"); + mqtt.unsubscribe(user.get().id + "/input/lesson-" + flc.getLesson().id + "/state"); + } catch (MqttException ex) { + LOG.log(Level.SEVERE, null, ex); + } + } + } + } + }); + + teachers.addListener((ListChangeListener.Change c) -> { + while (c.next()) { + for (TeacherContext tch_ctx : c.getAddedSubList()) { + try { + mqtt.subscribe(tch_ctx.getTeacher().id + "/output/on-line", (String topic, MqttMessage message) -> Platform.runLater(() -> tch_ctx.onlineProperty().set(Boolean.parseBoolean(new String(message.getPayload()))))); + } catch (MqttException ex) { + LOG.log(Level.SEVERE, null, ex); + } + id_teachers.put(tch_ctx.getTeacher().id, tch_ctx); + } + for (TeacherContext tch_ctx : c.getRemoved()) { + try { + mqtt.unsubscribe(tch_ctx.getTeacher().id + "/output/on-line"); + } catch (MqttException ex) { + LOG.log(Level.SEVERE, null, ex); + } + id_teachers.remove(tch_ctx.getTeacher().id); + } + } + }); + + teaching_lessons.addListener((ListChangeListener.Change c) -> { + while (c.next()) { + for (TeachingLessonContext tlc : c.getAddedSubList()) { + id_teaching_lessons.put(tlc.getLesson().id, tlc); + try { + // we subscribe to the lesson's time.. + mqtt.subscribe(user.get().id + "/input/lesson-" + tlc.getLesson().id + "/time", (String topic, MqttMessage message) -> { + Platform.runLater(() -> tlc.timeProperty().setValue(Long.parseLong(new String(message.getPayload())))); + }); + // we subscribe to the lesson's state.. + mqtt.subscribe(user.get().id + "/input/lesson-" + tlc.getLesson().id + "/state", (String topic, MqttMessage message) -> { + Platform.runLater(() -> tlc.stateProperty().setValue(LessonState.valueOf(new String(message.getPayload())))); + }); + } catch (MqttException ex) { + LOG.log(Level.SEVERE, null, ex); + } + } + for (TeachingLessonContext tlc : c.getRemoved()) { + id_teaching_lessons.remove(tlc.getLesson().id); + if (user.isNotNull().get()) { + try { + // we unsubscribe from the lesson's time and state.. + mqtt.unsubscribe(user.get().id + "/input/lesson-" + tlc.getLesson().id + "/time"); + mqtt.unsubscribe(user.get().id + "/input/lesson-" + tlc.getLesson().id + "/state"); + } catch (MqttException ex) { + LOG.log(Level.SEVERE, null, ex); + } + } + } + } + }); + + students.addListener((ListChangeListener.Change c) -> { + while (c.next()) { + for (StudentContext std_ctx : c.getAddedSubList()) { + long student_id = std_ctx.getStudent().id; + try { + // we subscribe to be notified whether the student gets online/offline.. + mqtt.subscribe(student_id + "/output/on-line", (String topic, MqttMessage message) -> Platform.runLater(() -> std_ctx.onlineProperty().set(Boolean.parseBoolean(new String(message.getPayload()))))); + // we subscribe/unsubscribe to the student's added/removed parameters.. + std_ctx.parameterTypesProperty().addListener((ListChangeListener.Change c1) -> { + while (c1.next()) { + // we subscribe to the new user's parameters.. + for (Parameter par : c1.getAddedSubList()) { + try { + mqtt.subscribe(std_ctx.getStudent().id + "/output/" + par.name, (String topic, MqttMessage message) -> { + Map c_par_vals = JSONB.fromJson(new String(message.getPayload()), new HashMap() { + }.getClass().getGenericSuperclass()); + Platform.runLater(() -> std_ctx.setParameterValue(par.name, c_par_vals)); + }); + } catch (MqttException ex) { + LOG.log(Level.SEVERE, null, ex); + } + } + // we unsubscribe from the removed parameters.. + for (Parameter par : c1.getRemoved()) { + try { + mqtt.unsubscribe(student_id + "/output/" + par.name); + } catch (MqttException ex) { + LOG.log(Level.SEVERE, null, ex); + } + } + } + }); + if (std_ctx.isOnline()) { + // we add the current student's parameters.. + // notice that in case the student is offline, the parameters will be added by the subscription to the student's output.. + std_ctx.parameterTypesProperty().addAll(std_ctx.getStudent().par_types.values()); + } + // we subscribe to the student's output.. + mqtt.subscribe(std_ctx.getStudent().id + "/output", (String topic, MqttMessage message) -> { + Message m = JSONB.fromJson(new String(message.getPayload()), Message.class); + switch (m.message_type) { + case NewParameter: + NewParameter new_parameter = JSONB.fromJson(new String(message.getPayload()), NewParameter.class); + Platform.runLater(() -> std_ctx.parameterTypesProperty().add(new_parameter.parameter)); + break; + case LostParameter: + LostParameter lost_parameter = JSONB.fromJson(new String(message.getPayload()), LostParameter.class); + Platform.runLater(() -> std_ctx.parameterTypesProperty().remove(std_ctx.getParameter(lost_parameter.name))); + break; + case Answer: + break; + default: + throw new AssertionError(m.message_type.name()); + } + }); + for (Map.Entry> par_val : std_ctx.getStudent().par_values.entrySet()) { + std_ctx.setParameterValue(par_val.getKey(), par_val.getValue()); + } + } catch (MqttException ex) { + LOG.log(Level.SEVERE, null, ex); + } + id_students.put(std_ctx.getStudent().id, std_ctx); + } + for (StudentContext std_ctx : c.getRemoved()) { + try { + mqtt.unsubscribe(std_ctx.getStudent().id + "/output/on-line"); + std_ctx.parameterTypesProperty().clear(); + } catch (MqttException ex) { + LOG.log(Level.SEVERE, null, ex); + } + id_students.remove(std_ctx.getStudent().id); + } + } + }); + + par_types.addListener((ListChangeListener.Change c) -> { + while (c.next()) { + for (Parameter par : c.getAddedSubList()) { + id_par_types.put(par.name, par); + } + for (Parameter par : c.getRemoved()) { + id_par_types.remove(par.name); + } + } + }); + } + + public Stage getStage() { + return stage; + } + + public void setStage(Stage stage) { + this.stage = stage; + } + + public User getUser() { + return user.get(); + } + + public void setUser(User user) { + this.user.set(user); + } + + public ObjectProperty userProperty() { + return user; + } + + public ObservableList parametersProperty() { + return par_values; + } + + public void setParameterValue(String par_name, String sub_par, String value) { + par_vals.get(par_name).get(sub_par).value.set(value); + Map val = new HashMap<>(); + for (Map.Entry v_val : par_vals.get(par_name).entrySet()) { + val.put(v_val.getKey(), v_val.getValue().value.get()); + } + try { + mqtt.publish(user.get().id + "/output/" + par_name, JSONB.toJson(val).getBytes(), 1, true); + } catch (MqttException ex) { + LOG.log(Level.SEVERE, null, ex); + } + } + + public ObservableList eventsProperty() { + return events; + } + + public ObservableList followingLessonsProperty() { + return following_lessons; + } + + public void answerQuestion(QuestionEvent event, int answer) { + Form form = new Form(); + form.param("lesson_id", Long.toString(event.lesson_id)); + form.param("question_id", Long.toString(event.event_id)); + form.param("answer_id", Long.toString(answer)); + target.path("answer_question").request().put(Entity.form(form)); + } + + public void addTeacher(User teacher) { + Form form = new Form(); + form.param("student_id", Long.toString(user.get().id)); + form.param("teacher_id", Long.toString(teacher.id)); + target.path("add_teacher").request(MediaType.APPLICATION_JSON).put(Entity.form(form)); + teachers.add(new TeacherContext(teacher)); + } + + public void removeTeacher(TeacherContext tch_ctx) { + Form form = new Form(); + form.param("student_id", Long.toString(user.get().id)); + form.param("teacher_id", Long.toString(tch_ctx.getTeacher().id)); + target.path("remove_teacher").request(MediaType.APPLICATION_JSON).put(Entity.form(form)); + teachers.remove(tch_ctx); + } + + public ObservableList teachersProperty() { + return teachers; + } + + public ObservableList modelsProperty() { + return models; + } + + public void addLesson(String lesson_name, LessonModel model, Map roles) { + // we create a new lesson.. + NewLessonRequest new_lesson; + Lesson lesson; + if (model.id != null) { + new_lesson = new NewLessonRequest(user.get().id, lesson_name, model.id, roles); + lesson = target.path("new_lesson_by_model_id").request(MediaType.APPLICATION_JSON).post(Entity.json(new_lesson), Lesson.class); + } else { + new_lesson = new NewLessonRequest(user.get().id, lesson_name, model, roles); + lesson = target.path("new_lesson_by_model").request(MediaType.APPLICATION_JSON).post(Entity.json(new_lesson), Lesson.class); + } + teaching_lessons.add(new TeachingLessonContext(lesson, model)); + + // we solve the new lesson.. + Form form = new Form(); + form.param("lesson_id", Long.toString(lesson.id)); + target.path("solve_lesson").request().put(Entity.form(form)); + } + + public void removeLesson(TeachingLessonContext l_ctx) { + target.path("lessons").path(Long.toString(l_ctx.getLesson().id)).request().delete(); + teaching_lessons.remove(l_ctx); + } + + public ObservableList teachingLessonsProperty() { + return teaching_lessons; + } + + public void setTime(Lesson lesson, TokenRow row, long time) { + Form form = new Form(); + form.param("lesson_id", Long.toString(lesson.id)); + form.param("token_id", Integer.toString(row.getId())); + form.param("time", Long.toString(time)); + target.path("set_time").request().put(Entity.form(form)); + } + + public void play(Lesson lesson) { + Form form = new Form(); + form.param("lesson_id", Long.toString(lesson.id)); + target.path("play").request().put(Entity.form(form)); + } + + public void pause(Lesson lesson) { + Form form = new Form(); + form.param("lesson_id", Long.toString(lesson.id)); + target.path("pause").request().put(Entity.form(form)); + } + + public void stop(Lesson lesson) { + Form form = new Form(); + form.param("lesson_id", Long.toString(lesson.id)); + target.path("stop").request().put(Entity.form(form)); + } + + public void goTo(Lesson lesson, long time) { + Form form = new Form(); + form.param("lesson_id", Long.toString(lesson.id)); + form.param("time", Long.toString(time)); + target.path("go_to").request().put(Entity.form(form)); + } + + public StudentContext getStudentContext(long id) { + return id_students.get(id); + } + + public ObservableList studentsProperty() { + return students; + } + + public void login(String email, String password) { + Credentials credentials = new Credentials(email, password); + InitResponse init = target.path("login").request(MediaType.APPLICATION_JSON).post(Entity.json(credentials), InitResponse.class); + init.user.par_types = load_pars(); + init.user.par_values = load_par_vals(); + user.set(init.user); + + // we add the following lessons.. + init.following_lessons.forEach(l -> following_lessons.add(new FollowingLessonContext(l))); + + // we add the teachers.. + init.teachers.forEach(t -> teachers.add(new TeacherContext(t))); + + // we add the available models.. + models.addAll(init.models); + + // we add the teaching lessons.. + init.teaching_lessons.forEach(l -> teaching_lessons.add(new TeachingLessonContext(l, init.models.stream().filter(m -> m.id.equals(l.model)).findAny().get()))); + + // we add the students.. + init.students.forEach(s -> students.add(new StudentContext(s))); + } + + public void logout() { + user.set(null); + } + + public void newUser(String email, String password, String first_name, String last_name) { + NewUserRequest new_user = new NewUserRequest(email, password, first_name, last_name); + User u = target.path("new_user").request(MediaType.APPLICATION_JSON).post(Entity.json(new_user), User.class); + u.par_types = load_pars(); + u.par_values = load_par_vals(); + user.set(u); + } + + public Collection findUsers(String search_string) { + return target.path("find_users").path(search_string).request(MediaType.APPLICATION_JSON).get(new GenericType>() { + }); + } + + private static Map load_pars() { + Collection pars = JSONB.fromJson(Context.class.getResourceAsStream("/parameters/types.json"), new ArrayList() { + }.getClass().getGenericSuperclass()); + return pars.stream().collect(Collectors.toMap(p -> p.name, p -> p)); + } + + private static Map> load_par_vals() { + Map> par_vals = JSONB.fromJson(Context.class.getResourceAsStream("/parameters/values.json"), new HashMap>() { + }.getClass().getGenericSuperclass()); + return par_vals; + } + + public static class ParameterValue { + + private final StringProperty name; + private final StringProperty value; + private final ObservableList updates = FXCollections.observableArrayList(); + + ParameterValue(String name, String value) { + this.name = new SimpleStringProperty(name); + this.value = new SimpleStringProperty(value); + this.updates.add(new ParUpdate(System.currentTimeMillis(), value)); + this.value.addListener((ObservableValue observable, String oldValue, String newValue) -> updates.add(new ParUpdate(System.currentTimeMillis(), newValue))); + } + + public StringProperty nameProperty() { + return name; + } + + public StringProperty valueProperty() { + return value; + } + + public ObservableList updatesProperty() { + return updates; + } + } + + public static class ParUpdate { + + public final long time; + public final String new_value; + + public ParUpdate(long time, String new_value) { + this.time = time; + this.new_value = new_value; + } + } +} diff --git a/LECTurE-DesktopApp/src/main/java/it/cnr/istc/lecture/desktopapp/FollowingLessonContext.java b/LECTurE-DesktopApp/src/main/java/it/cnr/istc/lecture/desktopapp/FollowingLessonContext.java new file mode 100644 index 0000000..9aa33b0 --- /dev/null +++ b/LECTurE-DesktopApp/src/main/java/it/cnr/istc/lecture/desktopapp/FollowingLessonContext.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2018 ISTC - CNR + * + * 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 . + */ +package it.cnr.istc.lecture.desktopapp; + +import it.cnr.istc.lecture.api.Lesson; +import it.cnr.istc.lecture.api.Lesson.LessonState; +import it.cnr.istc.lecture.api.messages.Event; +import javafx.beans.property.LongProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleLongProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; + +/** + * + * @author Riccardo De Benedictis + */ +public class FollowingLessonContext { + + private final Lesson lesson; + private final ObjectProperty state = new SimpleObjectProperty<>(LessonState.Stopped); + private final LongProperty time = new SimpleLongProperty(0); + private final ObservableList events = FXCollections.observableArrayList(); + + FollowingLessonContext(Lesson lesson) { + this.lesson = lesson; + events.addListener((ListChangeListener.Change c) -> { + while (c.next()) { + Context.getContext().eventsProperty().addAll(c.getAddedSubList()); + Context.getContext().eventsProperty().removeAll(c.getRemoved()); + } + }); + } + + public Lesson getLesson() { + return lesson; + } + + public ObjectProperty stateProperty() { + return state; + } + + public LongProperty timeProperty() { + return time; + } + + public ObservableList eventsProperty() { + return events; + } +} diff --git a/LECTurE-DesktopApp/src/main/java/it/cnr/istc/lecture/desktopapp/LessonController.java b/LECTurE-DesktopApp/src/main/java/it/cnr/istc/lecture/desktopapp/LessonController.java new file mode 100644 index 0000000..632d1d8 --- /dev/null +++ b/LECTurE-DesktopApp/src/main/java/it/cnr/istc/lecture/desktopapp/LessonController.java @@ -0,0 +1,451 @@ +/* + * Copyright (C) 2018 ISTC - CNR + * + * 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 . + */ +package it.cnr.istc.lecture.desktopapp; + +import it.cnr.istc.lecture.api.Lesson.LessonState; +import it.cnr.istc.lecture.api.model.EventTemplate; +import it.cnr.istc.lecture.api.model.QuestionEventTemplate; +import it.cnr.istc.lecture.api.model.TextEventTemplate; +import it.cnr.istc.lecture.api.model.URLEventTemplate; +import it.cnr.istc.lecture.desktopapp.TeachingLessonContext.TokenRow; +import java.net.URL; +import java.text.FieldPosition; +import java.text.NumberFormat; +import java.text.ParsePosition; +import java.util.ArrayList; +import java.util.List; +import java.util.ResourceBundle; +import javafx.animation.Interpolator; +import javafx.animation.KeyFrame; +import javafx.animation.KeyValue; +import javafx.animation.Timeline; +import javafx.beans.binding.Bindings; +import javafx.beans.property.LongProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleLongProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.collections.ListChangeListener; +import javafx.collections.transformation.SortedList; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.fxml.Initializable; +import javafx.scene.control.Button; +import javafx.scene.control.ContextMenu; +import javafx.scene.control.MenuItem; +import javafx.scene.control.SeparatorMenuItem; +import javafx.scene.control.TableCell; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableRow; +import javafx.scene.control.TableView; +import javafx.scene.control.TextField; +import javafx.scene.control.cell.PropertyValueFactory; +import javafx.scene.control.cell.TextFieldTableCell; +import javafx.scene.layout.StackPane; +import javafx.scene.paint.Color; +import javafx.util.Duration; +import javafx.util.StringConverter; +import org.controlsfx.glyphfont.FontAwesome; +import org.controlsfx.glyphfont.Glyph; +import org.jfree.chart.JFreeChart; +import org.jfree.chart.axis.NumberAxis; +import org.jfree.chart.fx.ChartViewer; +import org.jfree.chart.plot.ValueMarker; +import org.jfree.chart.plot.XYPlot; +import org.jfree.chart.renderer.xy.XYShapeRenderer; +import org.jfree.chart.ui.RectangleAnchor; +import org.jfree.chart.ui.TextAnchor; +import org.jfree.chart.util.DefaultShadowGenerator; +import org.jfree.data.xy.XYDataItem; +import org.jfree.data.xy.XYSeries; +import org.jfree.data.xy.XYSeriesCollection; + +/** + * + * @author Riccardo De Benedictis + */ +public class LessonController implements Initializable { + + @FXML + private TextField lesson_name; + @FXML + private Button play_button; + @FXML + private Button pause_button; + @FXML + private Button stop_button; + @FXML + private TextField time; + @FXML + private StackPane lesson_chart_pane; + @FXML + private TableView tokens_table_view; + @FXML + private TableColumn time_column; + @FXML + private TableColumn min_column; + @FXML + private TableColumn max_column; + @FXML + private TableColumn id_column; + @FXML + private TableColumn role_column; + @FXML + private TableColumn subject_column; + private final ObjectProperty l_ctx = new SimpleObjectProperty<>(); + private final TokenXYSeries tokens = new TokenXYSeries("Tokens"); + private final ValueMarker t_now = new ValueMarker(0); + private final LongProperty t_now_property = new SimpleLongProperty(0); + private final ChangeListener TIME_LISTENER = (ObservableValue observable, Number oldValue, Number newValue) -> { + time.setText(TIME_STRING_CONVERTER.toString(newValue.longValue())); + final Timeline tl = new Timeline(); + final KeyValue kv = new KeyValue(t_now_property, newValue.longValue(), Interpolator.EASE_BOTH); + final KeyFrame kf = new KeyFrame(Duration.millis(500), kv); + tl.getKeyFrames().add(kf); + tl.play(); + }; + private final ChangeListener STATE_LISTENER = (ObservableValue observable, LessonState oldValue, LessonState newValue) -> { + if (newValue != null) { + switch (newValue) { + case Running: + play_button.setDisable(true); + pause_button.setDisable(false); + stop_button.setDisable(false); + break; + case Paused: + play_button.setDisable(false); + pause_button.setDisable(true); + stop_button.setDisable(false); + break; + case Stopped: + play_button.setDisable(false); + pause_button.setDisable(true); + stop_button.setDisable(true); + break; + default: + throw new AssertionError(newValue.name()); + } + } + }; + private static final StringConverter TIME_STRING_CONVERTER = new TimeStringConverter(); + private final ChangeListener TOKENS_TIME_CHANGE_LISTENER = (ObservableValue observable, Number oldValue, Number newValue) -> { + List items = new ArrayList<>(tokens.getItemCount()); + for (int i = 0; i < tokens.getItemCount(); i++) { + if (((TokenXYDataItem) tokens.getDataItem(i)).t.timeProperty() == observable) { + long x = ((TokenXYDataItem) tokens.getDataItem(i)).t.getTime(); + long y = 1; + for (int j = 0; j < tokens.getItemCount(); j++) { + if (tokens.getDataItem(j).getXValue() == x) { + y++; + } + } + items.add(new TokenXYDataItem(x, y, ((TokenXYDataItem) tokens.getDataItem(i)).t)); + } else { + items.add(tokens.getDataItem(i)); + } + } + tokens.clear(); + for (XYDataItem item : items) { + tokens.add(item); + } + }; + private final ListChangeListener TOKENS_CHANGE_LISTENER = (ListChangeListener.Change c) -> { + while (c.next()) { + c.getAddedSubList().forEach(tk_row -> { + long x = tk_row.getTime(); + long y = 1; + for (int i = 0; i < tokens.getItemCount(); i++) { + if (tokens.getDataItem(i).getXValue() == x) { + y++; + } + } + tokens.add(new TokenXYDataItem(x, y, tk_row)); + tk_row.timeProperty().addListener(TOKENS_TIME_CHANGE_LISTENER); + }); + c.getRemoved().forEach(tk_row -> { + tk_row.timeProperty().removeListener(TOKENS_TIME_CHANGE_LISTENER); + for (int i = 0; i < tokens.getItemCount(); i++) { + if (((TokenXYDataItem) tokens.getDataItem(i)).t == tk_row) { + tokens.remove(i); + } + break; + } + }); + } + }; + + @Override + public void initialize(URL location, ResourceBundle resources) { + l_ctx.addListener((ObservableValue observable, TeachingLessonContext oldValue, TeachingLessonContext newValue) -> { + if (oldValue != null) { + oldValue.timeProperty().removeListener(TIME_LISTENER); + oldValue.stateProperty().removeListener(STATE_LISTENER); + oldValue.tokensProperty().removeListener(TOKENS_CHANGE_LISTENER); + } + tokens.clear(); + if (newValue != null) { + lesson_name.setText(newValue.getLesson().name); + STATE_LISTENER.changed(newValue.stateProperty(), oldValue != null ? oldValue.stateProperty().getValue() : null, newValue.stateProperty().getValue()); + newValue.stateProperty().addListener(STATE_LISTENER); + TIME_LISTENER.changed(newValue.timeProperty(), oldValue != null ? oldValue.timeProperty().getValue() : null, newValue.timeProperty().getValue()); + newValue.timeProperty().addListener(TIME_LISTENER); + newValue.tokensProperty().forEach(tk_row -> { + long x = tk_row.getTime(); + long y = 1; + for (int i = 0; i < tokens.getItemCount(); i++) { + if (tokens.getDataItem(i).getXValue() == x) { + y++; + } + } + tokens.add(new TokenXYDataItem(x, y, tk_row)); + tk_row.timeProperty().addListener(TOKENS_TIME_CHANGE_LISTENER); + }); + newValue.tokensProperty().addListener(TOKENS_CHANGE_LISTENER); + tokens_table_view.setItems(new SortedList<>(newValue.tokensProperty(), (TokenRow r0, TokenRow r1) -> Long.compare(r0.getTime(), r1.getTime()))); + } else { + lesson_name.setText(null); + play_button.setDisable(true); + pause_button.setDisable(true); + stop_button.setDisable(true); + time.setText(null); + } + }); + + play_button.setGraphic(new Glyph("FontAwesome", FontAwesome.Glyph.PLAY).color(Color.INDIGO)); + play_button.setOnAction((ActionEvent event) -> Context.getContext().play(l_ctx.get().getLesson())); + pause_button.setGraphic(new Glyph("FontAwesome", FontAwesome.Glyph.PAUSE).color(Color.INDIGO)); + pause_button.setOnAction((ActionEvent event) -> Context.getContext().pause(l_ctx.get().getLesson())); + stop_button.setGraphic(new Glyph("FontAwesome", FontAwesome.Glyph.STOP).color(Color.INDIGO)); + stop_button.setOnAction((ActionEvent event) -> Context.getContext().stop(l_ctx.get().getLesson())); + + XYSeriesCollection series_collection = new XYSeriesCollection(); + series_collection.addSeries(tokens); + + XYShapeRenderer renderer = new XYShapeRenderer(); + renderer.setSeriesPaint(0, java.awt.Color.blue); + NumberAxis range_axis = new NumberAxis(""); + range_axis.setVisible(false); + range_axis.setUpperMargin(0.4); + NumberAxis domain_axis = new NumberAxis(""); + domain_axis.setNumberFormatOverride(new NumberFormat() { + @Override + public StringBuffer format(double number, StringBuffer toAppendTo, FieldPosition pos) { + return format((long) number, toAppendTo, pos); + } + + @Override + public StringBuffer format(long number, StringBuffer toAppendTo, FieldPosition pos) { + return toAppendTo.append(TIME_STRING_CONVERTER.toString(number)); + } + + @Override + public Number parse(String source, ParsePosition parsePosition) { + throw new UnsupportedOperationException("Not supported yet."); + } + }); + XYPlot plot = new XYPlot(series_collection, domain_axis, range_axis, renderer); + plot.setShadowGenerator(new DefaultShadowGenerator(5, java.awt.Color.black, 1, 2, -45)); + plot.setNoDataMessage("No data"); + plot.setRangeGridlinesVisible(false); + t_now.setLabel("now"); + t_now.setLabelAnchor(RectangleAnchor.TOP_LEFT); + t_now.setLabelTextAnchor(TextAnchor.TOP_RIGHT); + plot.addDomainMarker(t_now); + + JFreeChart chart = new JFreeChart(null, JFreeChart.DEFAULT_TITLE_FONT, plot, false); + chart.setBackgroundPaint(java.awt.Color.WHITE); + + t_now_property.addListener((ObservableValue observable, Number oldValue, Number newValue) -> t_now.setValue(newValue.longValue())); + + lesson_chart_pane.getChildren().add(new ChartViewer(chart)); + + time_column.setCellValueFactory(new PropertyValueFactory<>("time")); + time_column.setEditable(true); + time_column.setCellFactory((TableColumn param) -> new TimeTextFieldTableCell()); + time_column.setOnEditCommit((TableColumn.CellEditEvent event) -> { + Context.getContext().setTime(l_ctx.get().getLesson(), event.getRowValue(), event.getNewValue()); + }); + min_column.setCellValueFactory(new PropertyValueFactory<>("min")); + min_column.setCellFactory((TableColumn param) -> new TimeTextFieldTableCell()); + max_column.setCellValueFactory(new PropertyValueFactory<>("max")); + max_column.setCellFactory((TableColumn param) -> new TimeTextFieldTableCell()); + + id_column.setCellValueFactory(new PropertyValueFactory<>("name")); + id_column.setCellFactory((TableColumn param) -> new TableCell() { + @Override + protected void updateItem(String item, boolean empty) { + super.updateItem(item, empty); + if (empty) { + setText(null); + styleProperty().unbind(); + setStyle(""); + } else { + setText(item); + + TokenRow row = getTableView().getItems().get(getIndex()); + styleProperty().bind(Bindings.createStringBinding(() -> { + if (row.getExecuted()) { + return "-fx-font-weight: bold;"; + } else { + return ""; + } + }, row.executedProperty())); + } + } + }); + role_column.setCellValueFactory(new PropertyValueFactory<>("name")); + role_column.setCellFactory((TableColumn param) -> new TableCell() { + @Override + protected void updateItem(String item, boolean empty) { + super.updateItem(item, empty); + if (empty) { + setText(null); + styleProperty().unbind(); + setStyle(""); + } else { + EventTemplate et = l_ctx.get().getModel().events.get(getIndex()); + StudentContext student_ctx = Context.getContext().getStudentContext(l_ctx.get().getLesson().roles.get(et.role)); + setText(et.role + " (" + student_ctx.getStudent().first_name + " " + student_ctx.getStudent().last_name + ")"); + + TokenRow row = getTableView().getItems().get(getIndex()); + styleProperty().bind(Bindings.createStringBinding(() -> { + if (row.getExecuted()) { + return "-fx-font-weight: bold;"; + } else { + return ""; + } + }, row.executedProperty())); + } + } + }); + subject_column.setCellValueFactory(new PropertyValueFactory<>("name")); + subject_column.setCellFactory((TableColumn param) -> new TableCell() { + @Override + protected void updateItem(String item, boolean empty) { + super.updateItem(item, empty); + if (empty) { + setText(null); + styleProperty().unbind(); + setStyle(""); + } else { + EventTemplate et = l_ctx.get().getModel().events.get(getIndex()); + if (et instanceof TextEventTemplate) { + setText(((TextEventTemplate) et).content); + } else if (et instanceof URLEventTemplate) { + setText(((URLEventTemplate) et).url); + } else if (et instanceof QuestionEventTemplate) { + setText(((QuestionEventTemplate) et).question); + } + + TokenRow row = getTableView().getItems().get(getIndex()); + styleProperty().bind(Bindings.createStringBinding(() -> { + if (row.getExecuted()) { + return "-fx-font-weight: bold;"; + } else { + return ""; + } + }, row.executedProperty())); + } + } + }); + + tokens_table_view.setRowFactory((TableView param) -> { + TableRow row = new TableRow<>(); + row.itemProperty().addListener((ObservableValue observable, TokenRow oldValue, TokenRow newValue) -> { + if (newValue == null) { + row.setContextMenu(null); + } else { + row.setContextMenu(new NavigateContextMenu(row)); + } + }); + return row; + }); + } + + public ObjectProperty teachingLessonContextProperty() { + return l_ctx; + } + + private class TokenXYSeries extends XYSeries { + + TokenXYSeries(Comparable key) { + super(key); + } + + public void add(double x, double y, TokenRow t) { + super.add(new TokenXYDataItem(x, y, t)); + } + } + + private class TokenXYDataItem extends XYDataItem { + + private final TokenRow t; + + TokenXYDataItem(double x, double y, TokenRow t) { + super(x, y); + this.t = t; + } + } + + private static class TimeTextFieldTableCell extends TextFieldTableCell { + + public TimeTextFieldTableCell() { + super(TIME_STRING_CONVERTER); + } + + @Override + public void updateItem(Long item, boolean empty) { + super.updateItem(item, empty); + if (empty) { + setText(null); + editableProperty().unbind(); + styleProperty().unbind(); + setStyle(""); + } else { + setText(TIME_STRING_CONVERTER.toString(item)); + + TokenRow row = getTableView().getItems().get(getIndex()); + editableProperty().bind(row.executedProperty().not()); + styleProperty().bind(Bindings.createStringBinding(() -> { + if (row.getExecuted()) { + return "-fx-font-weight: bold;"; + } else { + return ""; + } + }, row.executedProperty())); + } + } + } + + private class NavigateContextMenu extends ContextMenu { + + private final MenuItem go_to = new MenuItem("Go to", new Glyph("FontAwesome", FontAwesome.Glyph.SHARE)); + private final MenuItem edit = new MenuItem("Edit time", new Glyph("FontAwesome", FontAwesome.Glyph.EDIT)); + + public NavigateContextMenu(TableRow row) { + go_to.setOnAction((ActionEvent event) -> { + Context.getContext().goTo(l_ctx.get().getLesson(), row.getItem().getTime()); + }); + edit.setOnAction((ActionEvent event) -> { + tokens_table_view.edit(row.getIndex(), tokens_table_view.getColumns().get(0)); + }); + edit.disableProperty().bind(row.getItem().executedProperty()); + getItems().addAll(go_to, new SeparatorMenuItem(), edit); + } + } +} diff --git a/client/src/main/java/it/cnr/istc/ale/client/LoginDialog.java b/LECTurE-DesktopApp/src/main/java/it/cnr/istc/lecture/desktopapp/LoginDialog.java similarity index 84% rename from client/src/main/java/it/cnr/istc/ale/client/LoginDialog.java rename to LECTurE-DesktopApp/src/main/java/it/cnr/istc/lecture/desktopapp/LoginDialog.java index a69de27..6184f89 100644 --- a/client/src/main/java/it/cnr/istc/ale/client/LoginDialog.java +++ b/LECTurE-DesktopApp/src/main/java/it/cnr/istc/lecture/desktopapp/LoginDialog.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 Riccardo De Benedictis + * Copyright (C) 2018 ISTC - CNR * * 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 @@ -14,16 +14,18 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package it.cnr.istc.ale.client; +package it.cnr.istc.lecture.desktopapp; import javafx.beans.binding.Bindings; -import javafx.scene.control.ButtonBar.ButtonData; +import javafx.scene.control.ButtonBar; import javafx.scene.control.ButtonType; import javafx.scene.control.Dialog; import javafx.scene.control.Label; import javafx.scene.control.PasswordField; import javafx.scene.control.TextField; import javafx.scene.layout.GridPane; +import javafx.scene.layout.Region; +import javafx.stage.Stage; /** * @@ -34,7 +36,7 @@ public class LoginDialog extends Dialog { private final GridPane grid = new GridPane(); private final TextField email_field = new TextField(); private final PasswordField password_field = new PasswordField(); - private final ButtonType login_button = new ButtonType("Login", ButtonData.OK_DONE); + private final ButtonType login_button = new ButtonType("Login", ButtonBar.ButtonData.OK_DONE); public LoginDialog() { setTitle("Login"); @@ -51,6 +53,8 @@ public LoginDialog() { getDialogPane().getButtonTypes().add(login_button); getDialogPane().lookupButton(login_button).disableProperty().bind(Bindings.or(email_field.textProperty().isEmpty(), password_field.textProperty().isEmpty())); + getDialogPane().setMinHeight(Region.USE_PREF_SIZE); + ((Stage) getDialogPane().getScene().getWindow()).getIcons().addAll(Context.getContext().getStage().getIcons()); setResultConverter((ButtonType param) -> param == login_button ? new LoginResult(email_field.getText(), password_field.getText()) : null); } diff --git a/LECTurE-DesktopApp/src/main/java/it/cnr/istc/lecture/desktopapp/MainApp.java b/LECTurE-DesktopApp/src/main/java/it/cnr/istc/lecture/desktopapp/MainApp.java new file mode 100644 index 0000000..3d50387 --- /dev/null +++ b/LECTurE-DesktopApp/src/main/java/it/cnr/istc/lecture/desktopapp/MainApp.java @@ -0,0 +1,42 @@ +package it.cnr.istc.lecture.desktopapp; + +import javafx.application.Application; +import static javafx.application.Application.launch; +import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.image.Image; +import javafx.stage.Stage; + +public class MainApp extends Application { + + @Override + public void start(Stage stage) throws Exception { + Context.getContext().setStage(stage); + Parent root = FXMLLoader.load(getClass().getResource("/fxml/Main.fxml")); + + Scene scene = new Scene(root); + scene.getStylesheets().add("/styles/bootstrap3.css"); + + stage.getIcons().addAll(new Image(getClass().getResourceAsStream("/fxml/icon_16x16.png")), new Image(getClass().getResourceAsStream("/fxml/icon_32x32.png"))); + stage.setScene(scene); + stage.show(); + } + + @Override + public void stop() throws Exception { + Context.getContext().logout(); + } + + /** + * The main() method is ignored in correctly deployed JavaFX application. + * main() serves only as fallback in case the application can not be + * launched through deployment artifacts, e.g., in IDEs with limited FX + * support. NetBeans ignores main(). + * + * @param args the command line arguments + */ + public static void main(String[] args) { + launch(args); + } +} diff --git a/LECTurE-DesktopApp/src/main/java/it/cnr/istc/lecture/desktopapp/MainController.java b/LECTurE-DesktopApp/src/main/java/it/cnr/istc/lecture/desktopapp/MainController.java new file mode 100644 index 0000000..37bba19 --- /dev/null +++ b/LECTurE-DesktopApp/src/main/java/it/cnr/istc/lecture/desktopapp/MainController.java @@ -0,0 +1,445 @@ +/* + * Copyright (C) 2018 ISTC - CNR + * + * 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 . + */ +package it.cnr.istc.lecture.desktopapp; + +import it.cnr.istc.lecture.api.User; +import it.cnr.istc.lecture.api.messages.Event; +import it.cnr.istc.lecture.api.messages.QuestionEvent; +import it.cnr.istc.lecture.api.messages.TextEvent; +import it.cnr.istc.lecture.api.messages.URLEvent; +import it.cnr.istc.lecture.desktopapp.Context.ParameterValue; +import java.io.IOException; +import java.net.URL; +import java.util.ResourceBundle; +import java.util.logging.Level; +import java.util.logging.Logger; +import javafx.application.Platform; +import javafx.beans.binding.Bindings; +import javafx.beans.property.ObjectProperty; +import javafx.beans.value.ObservableValue; +import javafx.collections.ListChangeListener; +import javafx.collections.transformation.SortedList; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.fxml.Initializable; +import javafx.scene.control.Accordion; +import javafx.scene.control.Alert; +import javafx.scene.control.Alert.AlertType; +import javafx.scene.control.Button; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.control.MenuItem; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; +import javafx.scene.control.cell.PropertyValueFactory; +import javafx.scene.control.cell.TextFieldTableCell; +import javafx.scene.layout.Pane; +import javafx.scene.layout.StackPane; +import javafx.scene.paint.Color; +import javafx.stage.Stage; +import org.controlsfx.control.Notifications; +import org.controlsfx.glyphfont.FontAwesome; +import org.controlsfx.glyphfont.Glyph; + +/** + * + * @author Riccardo De Benedictis + */ +public class MainController implements Initializable { + + @FXML + private MenuItem login; + @FXML + private MenuItem logout; + @FXML + private MenuItem new_user; + @FXML + private Accordion learn_accord; + @FXML + private ListView events; + @FXML + private ListView following_lessons; + @FXML + private ListView teachers; + @FXML + private Button add_teachers_button; + @FXML + private Button remove_teachers_button; + private Pane text_event_pane; + private TextEventController text_event_controller; + private Pane question_event_pane; + private QuestionEventController question_event_controller; + private Pane url_event_pane; + private URLEventController url_event_controller; + @FXML + private StackPane learning_pane; + @FXML + private Accordion teach_accord; + @FXML + private ListView teaching_lessons; + @FXML + private Button add_lesson_button; + @FXML + private Button remove_lessons_button; + @FXML + private StackPane teaching_pane; + private Pane lesson_pane; + private LessonController lesson_controller; + private Pane student_pane; + private StudentController student_controller; + @FXML + private ListView students; + @FXML + private TableView parameters; + @FXML + private TableColumn par_names; + @FXML + private TableColumn par_vals; + + @Override + public void initialize(URL location, ResourceBundle resources) { + Stage stage = Context.getContext().getStage(); + stage.setTitle("LECTurE (Learning Environment CiTtà Educante)"); + + ObjectProperty user = Context.getContext().userProperty(); + user.addListener((ObservableValue observable, User oldValue, User newValue) -> { + if (newValue != null) { + stage.setTitle("LECTurE (Learning Environment CiTtà Educante) - " + newValue.first_name); + } else { + stage.setTitle("LECTurE (Learning Environment CiTtà Educante)"); + learning_pane.getChildren().clear(); + teaching_pane.getChildren().clear(); + } + }); + + login.disableProperty().bind(user.isNotNull()); + new_user.disableProperty().bind(user.isNotNull()); + logout.disableProperty().bind(user.isNull()); + + learn_accord.setExpandedPane(learn_accord.getPanes().get(0)); + + events.setItems(new SortedList<>(Context.getContext().eventsProperty(), (Event e0, Event e1) -> Long.compare(e0.time, e1.time))); + events.setCellFactory((ListView param) -> new ListCell() { + @Override + protected void updateItem(Event event, boolean empty) { + super.updateItem(event, empty); + if (empty) { + setText(null); + setGraphic(null); + } else { + if (event instanceof TextEvent) { + setText(((TextEvent) event).content); + setGraphic(new Glyph("FontAwesome", FontAwesome.Glyph.INFO)); + } else if (event instanceof URLEvent) { + setText(((URLEvent) event).content); + setGraphic(new Glyph("FontAwesome", FontAwesome.Glyph.INFO)); + } else if (event instanceof QuestionEvent) { + setText(((QuestionEvent) event).question); + setGraphic(new Glyph("FontAwesome", FontAwesome.Glyph.QUESTION)); + } + } + } + }); + Context.getContext().eventsProperty().addListener(new ListChangeListener() { + @Override + public void onChanged(ListChangeListener.Change c) { + while (c.next()) { + for (Event event : c.getAddedSubList()) { + switch (event.event_type) { + case TextEvent: + Platform.runLater(() -> Notifications.create().title("Event").text(((TextEvent) event).content).show()); + break; + case QuestionEvent: + Platform.runLater(() -> Notifications.create().title("Question").text(((QuestionEvent) event).question).show()); + break; + case URLEvent: + Platform.runLater(() -> Notifications.create().title("Event").text(((URLEvent) event).content).show()); + break; + default: + throw new AssertionError(event.event_type.name()); + } + } + } + } + }); + + following_lessons.setItems(Context.getContext().followingLessonsProperty()); + following_lessons.setCellFactory((ListView param) -> new ListCell() { + @Override + protected void updateItem(FollowingLessonContext l_ctx, boolean empty) { + super.updateItem(l_ctx, empty); + if (empty) { + setText(null); + setGraphic(null); + } else { + setText(l_ctx.getLesson().name); + switch (l_ctx.stateProperty().get()) { + case Running: + setGraphic(new Glyph("FontAwesome", FontAwesome.Glyph.PLAY).color(Color.INDIGO)); + break; + case Paused: + setGraphic(new Glyph("FontAwesome", FontAwesome.Glyph.PAUSE).color(Color.INDIGO)); + break; + case Stopped: + setGraphic(new Glyph("FontAwesome", FontAwesome.Glyph.STOP).color(Color.INDIGO)); + break; + default: + throw new AssertionError(l_ctx.stateProperty().get().name()); + } + } + } + }); + + teachers.setItems(Context.getContext().teachersProperty()); + teachers.setCellFactory((ListView param) -> new ListCell() { + @Override + protected void updateItem(TeacherContext tch_ctx, boolean empty) { + super.updateItem(tch_ctx, empty); + if (empty) { + setText(null); + setGraphic(null); + } else { + setText(tch_ctx.getTeacher().first_name + " " + tch_ctx.getTeacher().last_name); + if (tch_ctx.isOnline()) { + setStyle("-fx-text-fill: black;"); + setGraphic(new Glyph("FontAwesome", FontAwesome.Glyph.LINK)); + } else { + setStyle("-fx-text-fill: gray;"); + setGraphic(new Glyph("FontAwesome", FontAwesome.Glyph.UNLINK)); + } + } + } + }); + add_teachers_button.graphicProperty().set(new Glyph("FontAwesome", FontAwesome.Glyph.PLUS)); + add_teachers_button.disableProperty().bind(user.isNull()); + remove_teachers_button.graphicProperty().set(new Glyph("FontAwesome", FontAwesome.Glyph.MINUS)); + remove_teachers_button.disableProperty().bind(Bindings.isEmpty(teachers.selectionModelProperty().get().getSelectedItems())); + + try { + FXMLLoader text_event_pane_loader = new FXMLLoader(getClass().getResource("/fxml/TextEvent.fxml")); + text_event_pane = text_event_pane_loader.load(); + text_event_controller = text_event_pane_loader.getController(); + FXMLLoader question_event_pane_loader = new FXMLLoader(getClass().getResource("/fxml/QuestionEvent.fxml")); + question_event_pane = question_event_pane_loader.load(); + question_event_controller = question_event_pane_loader.getController(); + FXMLLoader url_event_pane_loader = new FXMLLoader(getClass().getResource("/fxml/URLEvent.fxml")); + url_event_pane = url_event_pane_loader.load(); + url_event_controller = url_event_pane_loader.getController(); + } catch (IOException ex) { + Logger.getLogger(MainController.class.getName()).log(Level.SEVERE, null, ex); + } + events.getSelectionModel().selectedItemProperty().addListener((ObservableValue observable, Event oldValue, Event newValue) -> { + if (newValue != null) { + Pane c_pane = null; + switch (newValue.event_type) { + case TextEvent: + c_pane = text_event_pane; + text_event_controller.eventProperty().set((TextEvent) newValue); + break; + case QuestionEvent: + c_pane = question_event_pane; + question_event_controller.eventProperty().set((QuestionEvent) newValue); + break; + case URLEvent: + c_pane = url_event_pane; + url_event_controller.eventProperty().set((URLEvent) newValue); + break; + default: + throw new AssertionError(newValue.event_type.name()); + } + if (learning_pane.getChildren().isEmpty()) { + learning_pane.getChildren().add(c_pane); + } else if (learning_pane.getChildren().get(0) != c_pane) { + learning_pane.getChildren().set(0, c_pane); + } + } else { + text_event_controller.eventProperty().set(null); + url_event_controller.eventProperty().set(null); + } + }); + + teach_accord.setExpandedPane(teach_accord.getPanes().get(0)); + teaching_lessons.setItems(Context.getContext().teachingLessonsProperty()); + teaching_lessons.setCellFactory((ListView param) -> new ListCell() { + @Override + protected void updateItem(TeachingLessonContext l_ctx, boolean empty) { + super.updateItem(l_ctx, empty); + if (empty) { + setText(null); + setGraphic(null); + } else { + setText(l_ctx.getLesson().name); + switch (l_ctx.stateProperty().get()) { + case Running: + setGraphic(new Glyph("FontAwesome", FontAwesome.Glyph.PLAY).color(Color.INDIGO)); + break; + case Paused: + setGraphic(new Glyph("FontAwesome", FontAwesome.Glyph.PAUSE).color(Color.INDIGO)); + break; + case Stopped: + setGraphic(new Glyph("FontAwesome", FontAwesome.Glyph.STOP).color(Color.INDIGO)); + break; + default: + throw new AssertionError(l_ctx.stateProperty().get().name()); + } + } + } + }); + add_lesson_button.graphicProperty().set(new Glyph("FontAwesome", FontAwesome.Glyph.PLUS)); + add_lesson_button.disableProperty().bind(user.isNull()); + remove_lessons_button.graphicProperty().set(new Glyph("FontAwesome", FontAwesome.Glyph.MINUS)); + remove_lessons_button.disableProperty().bind(Bindings.isEmpty(teaching_lessons.selectionModelProperty().get().getSelectedItems())); + + try { + FXMLLoader lesson_pane_loader = new FXMLLoader(getClass().getResource("/fxml/Lesson.fxml")); + lesson_pane = lesson_pane_loader.load(); + lesson_controller = lesson_pane_loader.getController(); + } catch (IOException ex) { + Logger.getLogger(MainController.class.getName()).log(Level.SEVERE, null, ex); + } + + teaching_lessons.getSelectionModel().selectedItemProperty().addListener((ObservableValue observable, TeachingLessonContext oldValue, TeachingLessonContext newValue) -> { + lesson_controller.teachingLessonContextProperty().set(newValue); + if (newValue != null) { + if (teaching_pane.getChildren().isEmpty()) { + teaching_pane.getChildren().add(lesson_pane); + } else if (teaching_pane.getChildren().get(0) != lesson_pane) { + teaching_pane.getChildren().set(0, lesson_pane); + } + } + }); + + students.setItems(Context.getContext().studentsProperty()); + students.setCellFactory((ListView param) -> new ListCell() { + @Override + protected void updateItem(StudentContext std_ctx, boolean empty) { + super.updateItem(std_ctx, empty); + if (empty) { + setText(null); + setGraphic(null); + } else { + setText(std_ctx.getStudent().first_name + " " + std_ctx.getStudent().last_name); + if (std_ctx.isOnline()) { + setStyle("-fx-text-fill: black;"); + setGraphic(new Glyph("FontAwesome", FontAwesome.Glyph.LINK)); + } else { + setStyle("-fx-text-fill: gray;"); + setGraphic(new Glyph("FontAwesome", FontAwesome.Glyph.UNLINK)); + } + } + } + }); + + try { + FXMLLoader student_pane_loader = new FXMLLoader(getClass().getResource("/fxml/Student.fxml")); + student_pane = student_pane_loader.load(); + student_controller = student_pane_loader.getController(); + } catch (IOException ex) { + Logger.getLogger(MainController.class.getName()).log(Level.SEVERE, null, ex); + } + + students.getSelectionModel().selectedItemProperty().addListener((ObservableValue observable, StudentContext oldValue, StudentContext newValue) -> { + student_controller.studentContextProperty().set(newValue); + if (newValue != null) { + if (teaching_pane.getChildren().isEmpty()) { + teaching_pane.getChildren().add(student_pane); + } else if (teaching_pane.getChildren().get(0) != student_pane) { + teaching_pane.getChildren().set(0, student_pane); + } + } + }); + + parameters.setItems(Context.getContext().parametersProperty()); + par_names.setCellValueFactory(new PropertyValueFactory<>("name")); + par_vals.setCellValueFactory(new PropertyValueFactory<>("value")); + par_vals.setCellFactory(TextFieldTableCell.forTableColumn()); + par_vals.setOnEditCommit((TableColumn.CellEditEvent event) -> { + ParameterValue par_value = (ParameterValue) event.getTableView().getItems().get(event.getTablePosition().getRow()); + String[] par_name = par_value.nameProperty().get().split("\\."); + Context.getContext().setParameterValue(par_name[0], par_name[1], event.getNewValue()); + }); + } + + @FXML + private void login(ActionEvent event) { + LoginDialog login_dialog = new LoginDialog(); + login_dialog.getDialogPane().getStylesheets().addAll(Context.getContext().getStage().getScene().getStylesheets()); + login_dialog.showAndWait().ifPresent(user -> { + try { + Context.getContext().login(user.getEmail(), user.getPassword()); + } catch (Exception e) { + Alert alert = new Alert(AlertType.ERROR); + alert.getDialogPane().getStylesheets().addAll(Context.getContext().getStage().getScene().getStylesheets()); + alert.setTitle("Exception"); + alert.setHeaderText(e.getMessage()); + alert.showAndWait(); + } + }); + } + + @FXML + private void logout(ActionEvent event) { + Context.getContext().logout(); + } + + @FXML + private void new_user(ActionEvent event) { + NewUserDialog new_user_dialog = new NewUserDialog(); + new_user_dialog.getDialogPane().getStylesheets().addAll(Context.getContext().getStage().getScene().getStylesheets()); + new_user_dialog.showAndWait().ifPresent(user -> { + try { + Context.getContext().newUser(user.getEmail(), user.getPassword(), user.getFirstName(), user.getLastName()); + } catch (Exception e) { + Alert alert = new Alert(AlertType.ERROR); + alert.getDialogPane().getStylesheets().addAll(Context.getContext().getStage().getScene().getStylesheets()); + alert.setTitle("Exception"); + alert.setHeaderText(e.getMessage()); + alert.showAndWait(); + } + }); + } + + @FXML + private void exit(ActionEvent event) { + Platform.exit(); + } + + @FXML + private void add_teachers(ActionEvent event) { + new AddTeachersDialog().showAndWait().ifPresent(teachers_to_add -> { + for (User teacher : teachers_to_add) { + Context.getContext().addTeacher(teacher); + } + }); + } + + @FXML + private void remove_selected_teachers(ActionEvent event) { + teachers.selectionModelProperty().get().getSelectedItems().forEach(tch_ctx -> Context.getContext().removeTeacher(tch_ctx)); + } + + @FXML + private void add_lesson(ActionEvent event) { + new AddLessonDialog().showAndWait().ifPresent(new_lesson -> Context.getContext().addLesson(new_lesson.getLessonName(), new_lesson.getModel(), new_lesson.getRoles())); + } + + @FXML + private void remove_selected_lessons(ActionEvent event) { + teaching_lessons.selectionModelProperty().get().getSelectedItems().forEach(l_ctx -> Context.getContext().removeLesson(l_ctx)); + } +} diff --git a/client/src/main/java/it/cnr/istc/ale/client/NewUserDialog.java b/LECTurE-DesktopApp/src/main/java/it/cnr/istc/lecture/desktopapp/NewUserDialog.java similarity index 89% rename from client/src/main/java/it/cnr/istc/ale/client/NewUserDialog.java rename to LECTurE-DesktopApp/src/main/java/it/cnr/istc/lecture/desktopapp/NewUserDialog.java index fd36bf2..1d02486 100644 --- a/client/src/main/java/it/cnr/istc/ale/client/NewUserDialog.java +++ b/LECTurE-DesktopApp/src/main/java/it/cnr/istc/lecture/desktopapp/NewUserDialog.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 Riccardo De Benedictis + * Copyright (C) 2018 ISTC - CNR * * 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 @@ -14,7 +14,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package it.cnr.istc.ale.client; +package it.cnr.istc.lecture.desktopapp; import javafx.scene.control.ButtonBar.ButtonData; import javafx.scene.control.ButtonType; @@ -23,6 +23,8 @@ import javafx.scene.control.PasswordField; import javafx.scene.control.TextField; import javafx.scene.layout.GridPane; +import javafx.scene.layout.Region; +import javafx.stage.Stage; /** * @@ -58,6 +60,8 @@ public NewUserDialog() { getDialogPane().getButtonTypes().add(create_button); getDialogPane().lookupButton(create_button).disableProperty().bind(email_field.textProperty().isEmpty().or(password_field.textProperty().isEmpty()).or(first_name_field.textProperty().isEmpty()).or(last_name_field.textProperty().isEmpty())); + getDialogPane().setMinHeight(Region.USE_PREF_SIZE); + ((Stage) getDialogPane().getScene().getWindow()).getIcons().addAll(Context.getContext().getStage().getIcons()); setResultConverter((ButtonType param) -> param == create_button ? new NewUserResult(email_field.getText(), password_field.getText(), first_name_field.getText(), last_name_field.getText()) : null); } diff --git a/LECTurE-DesktopApp/src/main/java/it/cnr/istc/lecture/desktopapp/QuestionEventController.java b/LECTurE-DesktopApp/src/main/java/it/cnr/istc/lecture/desktopapp/QuestionEventController.java new file mode 100644 index 0000000..9e211cf --- /dev/null +++ b/LECTurE-DesktopApp/src/main/java/it/cnr/istc/lecture/desktopapp/QuestionEventController.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2018 ISTC - CNR + * + * 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 . + */ +package it.cnr.istc.lecture.desktopapp; + +import it.cnr.istc.lecture.api.messages.QuestionEvent; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; +import java.util.ResourceBundle; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.value.ObservableValue; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.fxml.Initializable; +import javafx.scene.control.Button; +import javafx.scene.control.RadioButton; +import javafx.scene.control.ToggleGroup; +import javafx.scene.layout.GridPane; +import static javafx.scene.layout.GridPane.setHgrow; +import javafx.scene.layout.Priority; +import javafx.scene.web.WebView; + +/** + * + * @author Riccardo De Benedictis + */ +public class QuestionEventController implements Initializable { + + @FXML + private GridPane root; + @FXML + private WebView web_view; + private final ObjectProperty event = new SimpleObjectProperty<>(); + private final ToggleGroup group = new ToggleGroup(); + private final List answer_buttons = new ArrayList<>(); + private final Button send_answer = new Button("Send"); + + @Override + public void initialize(URL location, ResourceBundle resources) { + event.addListener((ObservableValue observable, QuestionEvent oldValue, QuestionEvent newValue) -> { + group.getToggles().clear(); + root.getChildren().remove(send_answer); + root.getChildren().removeAll(answer_buttons); + answer_buttons.clear(); + if (newValue != null) { + web_view.getEngine().loadContent(newValue.question); + for (int i = 0; i < newValue.answers.size(); i++) { + RadioButton ans = new RadioButton(newValue.answers.get(i)); + ans.setToggleGroup(group); + setHgrow(ans, Priority.ALWAYS); + if (newValue.answer != null) { + ans.disableProperty().set(true); + } + answer_buttons.add(ans); + root.add(ans, 0, i + 1); + } + if (newValue.answer != null) { + group.selectToggle(group.getToggles().get(newValue.answer)); + } else { + send_answer.disableProperty().bind(group.selectedToggleProperty().isNull()); + root.add(send_answer, 0, newValue.answers.size() + 1); + } + } else { + web_view.getEngine().loadContent(""); + } + }); + + send_answer.setOnAction((ActionEvent event1) -> { + Context.getContext().answerQuestion(event.get(), group.getToggles().indexOf(group.getSelectedToggle())); + send_answer.disableProperty().unbind(); + send_answer.disableProperty().set(true); + answer_buttons.forEach(btn -> btn.disableProperty().set(true)); + }); + } + + public ObjectProperty eventProperty() { + return event; + } +} diff --git a/LECTurE-DesktopApp/src/main/java/it/cnr/istc/lecture/desktopapp/StudentContext.java b/LECTurE-DesktopApp/src/main/java/it/cnr/istc/lecture/desktopapp/StudentContext.java new file mode 100644 index 0000000..bddaf76 --- /dev/null +++ b/LECTurE-DesktopApp/src/main/java/it/cnr/istc/lecture/desktopapp/StudentContext.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2018 ISTC - CNR + * + * 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 . + */ +package it.cnr.istc.lecture.desktopapp; + +import it.cnr.istc.lecture.api.Parameter; +import it.cnr.istc.lecture.api.User; +import java.util.HashMap; +import java.util.Map; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; + +/** + * + * @author Riccardo De Benedictis + */ +public class StudentContext { + + private final User student; + private final BooleanProperty on_line = new SimpleBooleanProperty(); + /** + * The current student's parameter types. + */ + private final ObservableList par_types = FXCollections.observableArrayList(); + private final Map id_par_types = new HashMap<>(); + /** + * The current student's parameter values. + */ + private final Map> par_vals = new HashMap<>(); + /** + * The current student's parameter values as a list, to be displayed on + * tables. Notice that each parameter can aggregate more than a single + * value. + */ + private final ObservableList par_values = FXCollections.observableArrayList(); + + StudentContext(User student) { + this.student = student; + par_types.addListener((ListChangeListener.Change c) -> { + while (c.next()) { + for (Parameter par : c.getAddedSubList()) { + id_par_types.put(par.name, par); + } + for (Parameter par : c.getRemoved()) { + id_par_types.remove(par.name); + } + } + }); + } + + public User getStudent() { + return student; + } + + public boolean isOnline() { + return on_line.get(); + } + + public BooleanProperty onlineProperty() { + return on_line; + } + + public ObservableList parameterTypesProperty() { + return par_types; + } + + public Parameter getParameter(String par_name) { + return id_par_types.get(par_name); + } + + public void setParameterValue(String par_name, Map values) { + Map c_vals = par_vals.computeIfAbsent(par_name, name -> new HashMap<>()); + for (Map.Entry val : values.entrySet()) { + if (c_vals.containsKey(val.getKey())) { + c_vals.get(val.getKey()).valueProperty().set(val.getValue()); + } else { + Context.ParameterValue val_prop = new Context.ParameterValue(par_name + "." + val.getKey(), val.getValue()); + c_vals.put(val.getKey(), val_prop); + par_values.add(val_prop); + } + } + } + + public ObservableList parametersProperty() { + return par_values; + } +} diff --git a/LECTurE-DesktopApp/src/main/java/it/cnr/istc/lecture/desktopapp/StudentController.java b/LECTurE-DesktopApp/src/main/java/it/cnr/istc/lecture/desktopapp/StudentController.java new file mode 100644 index 0000000..907afab --- /dev/null +++ b/LECTurE-DesktopApp/src/main/java/it/cnr/istc/lecture/desktopapp/StudentController.java @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2018 ISTC - CNR + * + * 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 . + */ +package it.cnr.istc.lecture.desktopapp; + +import it.cnr.istc.lecture.api.Parameter; +import it.cnr.istc.lecture.desktopapp.Context.ParameterValue; +import java.net.URL; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.ResourceBundle; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.value.ObservableValue; +import javafx.collections.ListChangeListener; +import javafx.fxml.FXML; +import javafx.fxml.Initializable; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; +import javafx.scene.control.TextField; +import javafx.scene.control.cell.PropertyValueFactory; +import javafx.scene.layout.StackPane; +import javafx.util.StringConverter; +import org.jfree.chart.JFreeChart; +import org.jfree.chart.axis.DateAxis; +import org.jfree.chart.axis.NumberAxis; +import org.jfree.chart.fx.ChartViewer; +import org.jfree.chart.plot.CombinedDomainXYPlot; +import org.jfree.chart.plot.XYPlot; +import org.jfree.chart.renderer.xy.StandardXYItemRenderer; +import org.jfree.chart.renderer.xy.XYItemRenderer; +import org.jfree.data.xy.XYDataItem; +import org.jfree.data.xy.XYSeries; +import org.jfree.data.xy.XYSeriesCollection; + +/** + * + * @author Riccardo De Benedictis + */ +public class StudentController implements Initializable { + + @FXML + private TextField first_name; + @FXML + private TextField last_name; + @FXML + private TableView parameters_table_view; + @FXML + private TableColumn name_column; + @FXML + private TableColumn value_column; + @FXML + private StackPane student_chart_pane; + private final ObjectProperty std_ctx = new SimpleObjectProperty<>(); + private final Map std_ctxs = new IdentityHashMap<>(); + private static final StringConverter TIME_STRING_CONVERTER = new TimeStringConverter(); + + @Override + public void initialize(URL location, ResourceBundle resources) { + name_column.setCellValueFactory(new PropertyValueFactory<>("name")); + value_column.setCellValueFactory(new PropertyValueFactory<>("value")); + + std_ctx.addListener((ObservableValue observable, StudentContext oldValue, StudentContext newValue) -> { + student_chart_pane.getChildren().clear(); + if (newValue != null) { + first_name.setText(newValue.getStudent().first_name); + last_name.setText(newValue.getStudent().last_name); + parameters_table_view.setItems(newValue.parametersProperty()); + student_chart_pane.getChildren().add(std_ctxs.computeIfAbsent(newValue, ctx -> new StudentChartContext(ctx)).viewer); + } else { + first_name.setText(null); + last_name.setText(null); + parameters_table_view.setItems(null); + } + }); + } + + public ObjectProperty studentContextProperty() { + return std_ctx; + } + + private class StudentChartContext { + + private final Map par_collections = new HashMap<>(); + private final Map par_plots = new HashMap<>(); + private final Map>> par_updates_listener = new HashMap<>(); + private final ChartViewer viewer; + + private StudentChartContext(StudentContext ctx) { + DateAxis domain_axis = new DateAxis(""); + CombinedDomainXYPlot plot = new CombinedDomainXYPlot(domain_axis); + plot.setGap(10.0); + + JFreeChart chart = new JFreeChart(null, JFreeChart.DEFAULT_TITLE_FONT, plot, false); + chart.setBackgroundPaint(java.awt.Color.WHITE); + this.viewer = new ChartViewer(chart); + + for (Parameter par : ctx.parameterTypesProperty()) { + XYSeriesCollection collection = new XYSeriesCollection(); + par_collections.put(par.name, collection); + par.properties.entrySet().forEach(c_par -> collection.addSeries(new XYSeries(c_par.getKey()))); + NumberAxis range_axis = new NumberAxis(par.name); + range_axis.setLabelAngle(1.5708); + final XYItemRenderer renderer = new StandardXYItemRenderer(); + XYPlot c_plot = new XYPlot(collection, null, range_axis, renderer); + par_plots.put(par.name, c_plot); + plot.add(c_plot, 1); + + par_updates_listener.put(par.name, new HashMap<>()); + for (ParameterValue par_v : ctx.parametersProperty()) { + String[] par_name = par_v.nameProperty().get().split("\\."); + if (par_updates_listener.containsKey(par_name[0]) && !par_updates_listener.get(par_name[0]).containsKey(par_name[1])) { + XYSeries series = par_collections.get(par_name[0]).getSeries(par_name[1]); + switch (ctx.getParameter(par_name[0]).properties.get(par_name[1])) { + case "numeric": + par_v.updatesProperty().forEach(update -> series.add(new XYDataItem(update.time, Double.parseDouble(update.new_value)))); + par_updates_listener.get(par_name[0]).put(par_name[1], new NumericParUpdatesListChangeListener(par_v, series)); + break; + default: + throw new AssertionError(par_name[0] + " " + ctx.getParameter(par_name[0]).properties.get(par_name[1])); + } + } + } + } + + ctx.parameterTypesProperty().addListener((ListChangeListener.Change c) -> { + while (c.next()) { + for (Parameter par : c.getAddedSubList()) { + XYSeriesCollection collection = new XYSeriesCollection(); + par_collections.put(par.name, collection); + par.properties.entrySet().forEach(c_par -> collection.addSeries(new XYSeries(c_par.getKey()))); + NumberAxis range_axis = new NumberAxis(par.name); + range_axis.setLabelAngle(1.5708); + final XYItemRenderer renderer = new StandardXYItemRenderer(); + XYPlot c_plot = new XYPlot(collection, null, range_axis, renderer); + par_plots.put(par.name, c_plot); + plot.add(c_plot, 1); + + par_updates_listener.put(par.name, new HashMap<>()); + for (ParameterValue par_v : ctx.parametersProperty()) { + String[] par_name = par_v.nameProperty().get().split("\\."); + if (par_updates_listener.containsKey(par_name[0]) && !par_updates_listener.get(par_name[0]).containsKey(par_name[1])) { + XYSeries series = par_collections.get(par_name[0]).getSeries(par_name[1]); + switch (ctx.getParameter(par_name[0]).properties.get(par_name[1])) { + case "numeric": + par_v.updatesProperty().forEach(update -> series.add(new XYDataItem(update.time, Double.parseDouble(update.new_value)))); + par_updates_listener.get(par_name[0]).put(par_name[1], new NumericParUpdatesListChangeListener(par_v, series)); + break; + default: + throw new AssertionError(par_name[0] + " " + ctx.getParameter(par_name[0]).properties.get(par_name[1])); + } + } + } + } + for (Parameter par : c.getRemoved()) { + par_collections.remove(par.name); + XYPlot c_plot = par_plots.remove(par.name); + plot.remove(c_plot); + + Map> updates = par_updates_listener.remove(par.name); + for (ListChangeListener update : updates.values()) { + if (update instanceof NumericParUpdatesListChangeListener) { + NumericParUpdatesListChangeListener listener = (NumericParUpdatesListChangeListener) update; + listener.par_v.updatesProperty().removeListener(listener); + } + } + } + } + }); + } + } + + private static class NumericParUpdatesListChangeListener implements ListChangeListener { + + final ParameterValue par_v; + final XYSeries series; + + NumericParUpdatesListChangeListener(ParameterValue par_v, XYSeries series) { + this.par_v = par_v; + this.series = series; + this.par_v.updatesProperty().addListener(this); + } + + @Override + public void onChanged(Change c) { + while (c.next()) { + c.getAddedSubList().forEach(update -> series.add(new XYDataItem(update.time, Double.parseDouble(update.new_value)))); + } + } + } +} diff --git a/client/src/main/java/it/cnr/istc/ale/client/context/ConnectionContext.java b/LECTurE-DesktopApp/src/main/java/it/cnr/istc/lecture/desktopapp/TeacherContext.java similarity index 54% rename from client/src/main/java/it/cnr/istc/ale/client/context/ConnectionContext.java rename to LECTurE-DesktopApp/src/main/java/it/cnr/istc/lecture/desktopapp/TeacherContext.java index be613af..4895a8d 100644 --- a/client/src/main/java/it/cnr/istc/ale/client/context/ConnectionContext.java +++ b/LECTurE-DesktopApp/src/main/java/it/cnr/istc/lecture/desktopapp/TeacherContext.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 Riccardo De Benedictis + * Copyright (C) 2018 ISTC - CNR * * 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 @@ -14,31 +14,34 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package it.cnr.istc.ale.client.context; +package it.cnr.istc.lecture.desktopapp; -import it.cnr.istc.ale.api.User; -import java.util.HashMap; -import java.util.Map; +import it.cnr.istc.lecture.api.User; import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; /** * * @author Riccardo De Benedictis */ -public class ConnectionContext { +public class TeacherContext { - private final Context ctx; - /** - * For each user, a boolean property representing whether the user is online - * or not. - */ - final Map online_users = new HashMap<>(); + private final User teacher; + private final BooleanProperty on_line = new SimpleBooleanProperty(); - ConnectionContext(Context ctx) { - this.ctx = ctx; + TeacherContext(User teacher) { + this.teacher = teacher; } - public BooleanProperty isOnline(User user) { - return online_users.get(user.getId()); + public User getTeacher() { + return teacher; + } + + public boolean isOnline() { + return on_line.get(); + } + + public BooleanProperty onlineProperty() { + return on_line; } } diff --git a/LECTurE-DesktopApp/src/main/java/it/cnr/istc/lecture/desktopapp/TeachingLessonContext.java b/LECTurE-DesktopApp/src/main/java/it/cnr/istc/lecture/desktopapp/TeachingLessonContext.java new file mode 100644 index 0000000..81e1b33 --- /dev/null +++ b/LECTurE-DesktopApp/src/main/java/it/cnr/istc/lecture/desktopapp/TeachingLessonContext.java @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2018 ISTC - CNR + * + * 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 . + */ +package it.cnr.istc.lecture.desktopapp; + +import it.cnr.istc.lecture.api.Lesson; +import it.cnr.istc.lecture.api.Lesson.LessonState; +import it.cnr.istc.lecture.api.model.LessonModel; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; +import javafx.beans.Observable; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.LongProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleLongProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; + +/** + * + * @author Riccardo De Benedictis + */ +public class TeachingLessonContext { + + private final Lesson lesson; + private final LessonModel model; + private final ObjectProperty state = new SimpleObjectProperty<>(LessonState.Stopped); + private final LongProperty time = new SimpleLongProperty(0); + private final ObservableList tokens = FXCollections.observableArrayList((TokenRow tk) -> new Observable[]{tk.timeProperty()}); + private final Map id_tokens = new HashMap<>(); + + TeachingLessonContext(Lesson lesson, LessonModel model) { + this.lesson = lesson; + this.model = model; + tokens.addListener((ListChangeListener.Change c) -> { + while (c.next()) { + c.getAddedSubList().forEach((tk) -> id_tokens.put(tk.getId(), tk)); + c.getRemoved().forEach((tk) -> id_tokens.remove(tk.getId())); + } + }); + if (lesson.tokens != null) { + tokens.addAll(lesson.tokens.stream().map(token -> new TeachingLessonContext.TokenRow(token.id, time, token.min, token.max, token.time, token.refEvent)).collect(Collectors.toList())); + } + } + + public Lesson getLesson() { + return lesson; + } + + public LessonModel getModel() { + return model; + } + + public ObjectProperty stateProperty() { + return state; + } + + public LongProperty timeProperty() { + return time; + } + + public ObservableList tokensProperty() { + return tokens; + } + + public TokenRow getToken(final int id) { + return id_tokens.get(id); + } + + public static class TokenRow { + + private final int token_id; + private final BooleanProperty executed; + private final LongProperty min; + private final LongProperty max; + private final LongProperty time; + private final StringProperty name; + + public TokenRow(int token_id, LongProperty lesson_time, Long min, Long max, long time, String name) { + this.token_id = token_id; + this.executed = new SimpleBooleanProperty(false); + this.min = min != null ? new SimpleLongProperty(min) : new SimpleLongProperty(); + this.max = max != null ? new SimpleLongProperty(max) : new SimpleLongProperty(); + this.time = new SimpleLongProperty(time); + this.name = new SimpleStringProperty(name); + executed.bind(lesson_time.greaterThanOrEqualTo(this.time)); + } + + public int getId() { + return token_id; + } + + public boolean getExecuted() { + return executed.get(); + } + + public BooleanProperty executedProperty() { + return executed; + } + + public long getMin() { + return min.get(); + } + + public LongProperty minProperty() { + return min; + } + + public long getMax() { + return max.get(); + } + + public LongProperty maxProperty() { + return max; + } + + public long getTime() { + return time.get(); + } + + public LongProperty timeProperty() { + return time; + } + + public String getName() { + return name.get(); + } + + public StringProperty nameProperty() { + return name; + } + } +} diff --git a/LECTurE-DesktopApp/src/main/java/it/cnr/istc/lecture/desktopapp/TextEventController.java b/LECTurE-DesktopApp/src/main/java/it/cnr/istc/lecture/desktopapp/TextEventController.java new file mode 100644 index 0000000..a3e502d --- /dev/null +++ b/LECTurE-DesktopApp/src/main/java/it/cnr/istc/lecture/desktopapp/TextEventController.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2018 ISTC - CNR + * + * 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 . + */ +package it.cnr.istc.lecture.desktopapp; + +import it.cnr.istc.lecture.api.messages.TextEvent; +import java.net.URL; +import java.util.ResourceBundle; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.value.ObservableValue; +import javafx.fxml.FXML; +import javafx.fxml.Initializable; +import javafx.scene.web.WebView; + +/** + * + * @author Riccardo De Benedictis + */ +public class TextEventController implements Initializable { + + @FXML + private WebView web_view; + private final ObjectProperty event = new SimpleObjectProperty<>(); + + @Override + public void initialize(URL location, ResourceBundle resources) { + event.addListener((ObservableValue observable, TextEvent oldValue, TextEvent newValue) -> { + if (newValue != null) { + web_view.getEngine().loadContent(newValue.content); + } else { + web_view.getEngine().loadContent(""); + } + }); + } + + public ObjectProperty eventProperty() { + return event; + } +} diff --git a/client/src/main/java/it/cnr/istc/ale/client/TimeStringConverter.java b/LECTurE-DesktopApp/src/main/java/it/cnr/istc/lecture/desktopapp/TimeStringConverter.java similarity index 90% rename from client/src/main/java/it/cnr/istc/ale/client/TimeStringConverter.java rename to LECTurE-DesktopApp/src/main/java/it/cnr/istc/lecture/desktopapp/TimeStringConverter.java index 575a3c0..7a7c49f 100644 --- a/client/src/main/java/it/cnr/istc/ale/client/TimeStringConverter.java +++ b/LECTurE-DesktopApp/src/main/java/it/cnr/istc/lecture/desktopapp/TimeStringConverter.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 ricde + * Copyright (C) 2018 ISTC - CNR * * 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 @@ -14,18 +14,21 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package it.cnr.istc.ale.client; +package it.cnr.istc.lecture.desktopapp; import javafx.util.StringConverter; /** * - * @author ricde + * @author Riccardo De Benedictis */ public class TimeStringConverter extends StringConverter { @Override public String toString(Long time) { + if (time == null) { + return ""; + } long second = (time / 1000) % 60; long minute = (time / (1000 * 60)) % 60; long hour = (time / (1000 * 60 * 60)) % 24; diff --git a/LECTurE-DesktopApp/src/main/java/it/cnr/istc/lecture/desktopapp/URLEventController.java b/LECTurE-DesktopApp/src/main/java/it/cnr/istc/lecture/desktopapp/URLEventController.java new file mode 100644 index 0000000..392e36b --- /dev/null +++ b/LECTurE-DesktopApp/src/main/java/it/cnr/istc/lecture/desktopapp/URLEventController.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2018 ISTC - CNR + * + * 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 . + */ +package it.cnr.istc.lecture.desktopapp; + +import it.cnr.istc.lecture.api.messages.URLEvent; +import java.net.URL; +import java.util.ResourceBundle; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.value.ObservableValue; +import javafx.fxml.FXML; +import javafx.fxml.Initializable; +import javafx.scene.web.WebView; + +/** + * + * @author Riccardo De Benedictis + */ +public class URLEventController implements Initializable { + + @FXML + private WebView web_view; + private final ObjectProperty event = new SimpleObjectProperty<>(); + + @Override + public void initialize(URL location, ResourceBundle resources) { + event.addListener((ObservableValue observable, URLEvent oldValue, URLEvent newValue) -> { + if (newValue != null) { + web_view.getEngine().load(newValue.url); + } else { + web_view.getEngine().loadContent(""); + } + }); + } + + public ObjectProperty eventProperty() { + return event; + } +} diff --git a/LECTurE-DesktopApp/src/main/resources/config.properties b/LECTurE-DesktopApp/src/main/resources/config.properties new file mode 100644 index 0000000..aa406a8 --- /dev/null +++ b/LECTurE-DesktopApp/src/main/resources/config.properties @@ -0,0 +1,18 @@ +# Copyright (C) 2018 ISTC - CNR +# +# 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 . + +lecture-host=localhost +service-port=8080 +mqtt-port=1883 \ No newline at end of file diff --git a/LECTurE-DesktopApp/src/main/resources/fxml/Lesson.fxml b/LECTurE-DesktopApp/src/main/resources/fxml/Lesson.fxml new file mode 100644 index 0000000..40a5362 --- /dev/null +++ b/LECTurE-DesktopApp/src/main/resources/fxml/Lesson.fxml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +