Merge branch 'dev' into feature_guiOverhaul_M3

This commit is contained in:
gulerdav
2022-12-05 13:08:47 +01:00
committed by GitHub Enterprise
31 changed files with 798 additions and 197 deletions
@@ -1,23 +0,0 @@
package ch.zhaw.gartenverwaltung;
import ch.zhaw.gartenverwaltung.bootstrap.AppLoader;
import javafx.application.Application;
import javafx.stage.Stage;
import java.io.IOException;
public class HelloApplication extends Application {
@Override
public void start(Stage stage) throws IOException {
AppLoader appLoader = new AppLoader();
appLoader.loadSceneToStage("MainFXML.fxml", stage);
stage.setTitle("Gartenverwaltung");
stage.show();
}
public static void main(String[] args) {
launch();
}
}
@@ -0,0 +1,42 @@
package ch.zhaw.gartenverwaltung;
import ch.zhaw.gartenverwaltung.bootstrap.AppLoader;
import ch.zhaw.gartenverwaltung.backgroundtasks.BackgroundTasks;
import ch.zhaw.gartenverwaltung.io.CropList;
import ch.zhaw.gartenverwaltung.io.PlantList;
import ch.zhaw.gartenverwaltung.io.TaskList;
import ch.zhaw.gartenverwaltung.models.Garden;
import ch.zhaw.gartenverwaltung.types.Crop;
import javafx.application.Application;
import javafx.stage.Stage;
import java.io.IOException;
import java.util.Timer;
public class Main extends Application {
Timer backGroundTaskTimer = new Timer();
BackgroundTasks backgroundTasks;
@Override
public void start(Stage stage) throws IOException {
AppLoader appLoader = new AppLoader();
backgroundTasks = new BackgroundTasks((TaskList) appLoader.getAppDependency(TaskList.class),(CropList) appLoader.getAppDependency(CropList.class), (PlantList) appLoader.getAppDependency(PlantList.class));
// TODO reduce period
backGroundTaskTimer.scheduleAtFixedRate(backgroundTasks, 0, 1000);
appLoader.loadSceneToStage("MainFXML.fxml", stage);
stage.setTitle("Gartenverwaltung");
stage.show();
}
@Override
public void stop(){
backGroundTaskTimer.cancel();
}
public static void main(String[] args) {
launch();
}
}
@@ -1,13 +1,23 @@
package ch.zhaw.gartenverwaltung;
import ch.zhaw.gartenverwaltung.backgroundtasks.email.SmtpCredentials;
import ch.zhaw.gartenverwaltung.types.HardinessZone;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import java.util.List;
public class Settings {
private static final Settings instance;
private HardinessZone currentHardinessZone = HardinessZone.ZONE_8A;
private static Settings instance;
private final BooleanProperty showTutorial = new SimpleBooleanProperty(false);
private SmtpCredentials smtpCredentials = new SmtpCredentials("imap.gmail.com", "587", "pm3.hs22.it21b.win.team1@gmail.com", "pm3.hs22.it21b.win.team1@gmail.com", "bisefhhjtrrhtoqr", true);
// Gmail Address: pm3.hs22.it21b.win.team1@gmail.com
// Gmail Passwort: Gartenverwaltung.PM3.2022
// E-Mail Inbox: https://www.mailinator.com/v4/public/inboxes.jsp?to=pm3.hs22.it21b.win.team1
private String mailNotificationReceivers = "pm3.hs22.it21b.win.team1@mailinator.com";
private String mailNotificationSubjectTemplate = "Task %s is due!"; // {0} = Task Name
private String mailNotificationTextTemplate = "Dear user\nYour gardentask %s for plant %s is due at %tF. Don't forget to confirm in your application if the task is done.\nTask description:\n%s"; // {0} = Task Name, {1} = plantname, {2} = nextExecution, {3} = Task description
private String location = "";
@@ -48,4 +58,20 @@ public class Settings {
public String getLocation() {
return this.location;
}
public SmtpCredentials getSmtpCredentials() {
return smtpCredentials;
}
public String getMailNotificationReceivers() {
return mailNotificationReceivers;
}
public String getMailNotificationSubjectTemplate() {
return mailNotificationSubjectTemplate;
}
public String getMailNotificationTextTemplate() {
return mailNotificationTextTemplate;
}
}
@@ -0,0 +1,63 @@
package ch.zhaw.gartenverwaltung.backgroundtasks;
import ch.zhaw.gartenverwaltung.CropDetailController;
import ch.zhaw.gartenverwaltung.backgroundtasks.weather.WeatherGradenTaskPlanner;
import ch.zhaw.gartenverwaltung.io.CropList;
import ch.zhaw.gartenverwaltung.io.HardinessZoneNotSetException;
import ch.zhaw.gartenverwaltung.io.PlantList;
import ch.zhaw.gartenverwaltung.io.TaskList;
import ch.zhaw.gartenverwaltung.models.PlantNotFoundException;
import ch.zhaw.gartenverwaltung.types.Task;
import javax.mail.MessagingException;
import java.io.IOException;
import java.time.LocalDate;
import java.util.List;
import java.util.TimerTask;
import java.util.logging.Level;
import java.util.logging.Logger;
public class BackgroundTasks extends TimerTask {
private static final Logger LOG = Logger.getLogger(CropDetailController.class.getName());
private final TaskList taskList;
private final Notifier notifier;
private final WeatherGradenTaskPlanner weatherGardenTaskPlaner;
private void movePastTasks() throws IOException {
List<Task> taskList = this.taskList.getTaskList(LocalDate.MIN, LocalDate.now().minusDays(1));
taskList.forEach(task -> {
if (!task.isDone()) {
task.setNextExecution(LocalDate.now());
}
});
}
public BackgroundTasks(TaskList taskList, CropList cropList, PlantList plantList) {
this.taskList = taskList;
notifier = new Notifier(taskList, plantList, cropList);
weatherGardenTaskPlaner = new WeatherGradenTaskPlanner(taskList, plantList, cropList);
}
@Override
public void run() {
try {
movePastTasks();
} catch (IOException e) {
e.printStackTrace();
LOG.log(Level.SEVERE, "Could not execute Background Task: move past Tasks ", e.getCause());
}
try {
weatherGardenTaskPlaner.refreshTasks();
} catch (IOException | HardinessZoneNotSetException | PlantNotFoundException e) {
e.printStackTrace();
LOG.log(Level.SEVERE, "Could not execute Background Task: Refresh Tasks by WeatherGardenTaskPlaner ", e.getCause());
}
try {
notifier.sendNotifications();
} catch (IOException | MessagingException | HardinessZoneNotSetException e) {
e.printStackTrace();
LOG.log(Level.SEVERE, "Could not execute Background Task: send Notification for due Tasks", e.getCause());
}
}
}
@@ -0,0 +1,50 @@
package ch.zhaw.gartenverwaltung.backgroundtasks;
import ch.zhaw.gartenverwaltung.Settings;
import ch.zhaw.gartenverwaltung.backgroundtasks.email.EMailSender;
import ch.zhaw.gartenverwaltung.io.CropList;
import ch.zhaw.gartenverwaltung.io.HardinessZoneNotSetException;
import ch.zhaw.gartenverwaltung.io.PlantList;
import ch.zhaw.gartenverwaltung.io.TaskList;
import ch.zhaw.gartenverwaltung.types.Crop;
import ch.zhaw.gartenverwaltung.types.Task;
import javax.mail.MessagingException;
import java.io.IOException;
import java.time.LocalDate;
public class Notifier {
private final TaskList taskList;
private final CropList cropList;
private final PlantList plantList;
private final EMailSender eMailSender = new EMailSender();
public Notifier(TaskList taskList, PlantList plantList, CropList cropList) {
this.taskList = taskList;
this.cropList = cropList;
this.plantList = plantList;
}
private void sendNotification(Task task) throws IOException, MessagingException, HardinessZoneNotSetException {
String plantName = "unkown plant";
if(cropList.getCropById((task.getCropId())).isPresent()){
Crop crop = cropList.getCropById(task.getCropId()).get();
if(plantList.getPlantById(Settings.getInstance().getCurrentHardinessZone(), crop.getPlantId()).isPresent()) {
plantName = plantList.getPlantById(Settings.getInstance().getCurrentHardinessZone(), crop.getPlantId()).get().name();
}
}
String messageSubject = String.format(Settings.getInstance().getMailNotificationSubjectTemplate(), task.getName());
String messageText = String.format(Settings.getInstance().getMailNotificationTextTemplate(), task.getName(), plantName, task.getNextExecution(), task.getDescription());
eMailSender.sendMails(Settings.getInstance().getMailNotificationReceivers(), messageSubject, messageText);
}
public void sendNotifications() throws IOException, MessagingException, HardinessZoneNotSetException {
for (Task task : taskList.getTaskList(LocalDate.MIN, LocalDate.MAX)) {
if (task.getNextNotification() != null && task.getNextNotification().isBefore(LocalDate.now().minusDays(1))) {
sendNotification(task);
task.setNextNotification(LocalDate.now().plusDays(1));
taskList.saveTask(task);
}
}
}
}
@@ -0,0 +1,40 @@
package ch.zhaw.gartenverwaltung.backgroundtasks.email;
import ch.zhaw.gartenverwaltung.Settings;
import javax.mail.*;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
public class EMailSender {
public EMailSender(){
}
public void sendMails(String recipients, String subject, String text) throws MessagingException {
// TODO replace printMail with implementation
printMail(recipients, subject, text); // TODO Remove Printing E-Mail to console to test it
MimeMessage message = new MimeMessage(Settings.getInstance().getSmtpCredentials().getSession());
message.addHeader("Content-type", "text/HTML; charset=UTF-8");
message.addHeader("format", "flowed");
message.addHeader("Content-Transfer-Encoding", "8bit");
message.setFrom(new InternetAddress(Settings.getInstance().getSmtpCredentials().fromAddress()));
message.setReplyTo(InternetAddress.parse(Settings.getInstance().getSmtpCredentials().fromAddress(), false));
message.setSubject(subject);
message.setText(text);
message.setSentDate(new Date());
message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(recipients, false));
Transport.send(message);
}
private void printMail(String receiver, String subject, String text){
System.out.printf("\nSending E-Mail:\nTo: %s\nSubject: %s\nMessage:\n%s\n", receiver, subject, text);
}
}
@@ -0,0 +1,41 @@
package ch.zhaw.gartenverwaltung.backgroundtasks.email;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import java.net.Authenticator;
import java.util.Properties;
public record SmtpCredentials(
String host,
String port,
String fromAddress,
String username,
String password,
boolean startTLS
) {
private Properties getProperties() {
Properties properties = new Properties();
properties.put("mail.smtp.host", host);
properties.put("mail.smtp.port", port);
properties.put("mail.smtp.auth", "true");
properties.put("mail.smtp.starttls.enable", startTLS ? "true" : "false");
return properties;
}
private javax.mail.Authenticator getAuthenticator() {
return new javax.mail.Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(username, password);
}
};
}
public Session getSession() {
return Session.getInstance(getProperties(), getAuthenticator());
}
}
@@ -0,0 +1,5 @@
package ch.zhaw.gartenverwaltung.backgroundtasks.weather;
public enum SevereWeather {
FROST,SNOW,HAIL,NO_SEVERE_WEATHER,RAIN
}
@@ -0,0 +1,127 @@
package ch.zhaw.gartenverwaltung.backgroundtasks.weather;
import ch.zhaw.gartenverwaltung.io.CropList;
import ch.zhaw.gartenverwaltung.io.HardinessZoneNotSetException;
import ch.zhaw.gartenverwaltung.io.PlantList;
import ch.zhaw.gartenverwaltung.io.TaskList;
import ch.zhaw.gartenverwaltung.models.PlantNotFoundException;
import ch.zhaw.gartenverwaltung.types.*;
import java.io.IOException;
import java.time.LocalDate;
import java.util.List;
/**
* The WeatherGardenTaskPlanner creates Tasks based on weather events and the rain amount from the last days
*
*/
public class WeatherGradenTaskPlanner {
private final TaskList taskList;
private final PlantList plantList;
private final CropList cropList;
WeatherService weatherService;
private final LocalDate dateSevereWeather = LocalDate.of(2022,12,15);
public WeatherGradenTaskPlanner(TaskList taskList, PlantList plantList, CropList cropList) {
this.taskList = taskList;
this.plantList = plantList;
this.cropList = cropList;
weatherService = new WeatherService();
}
/**
* Method to refresh watering tasks and severe weather tasks
* @throws IOException If the database cannot be accessed
* @throws HardinessZoneNotSetException If the hardiness zone is not available
* @throws PlantNotFoundException if the plant is not available for the watering task
*/
public void refreshTasks() throws IOException, HardinessZoneNotSetException, PlantNotFoundException {
getSevereWeatherEvents();
getRainAmount();
}
private void getSevereWeatherEvents() throws IOException {
SevereWeather actualWeather = weatherService.causeSevereWeather(1);
if (SevereWeather.HAIL.equals(actualWeather)) {
createPreHailTask();
} else if (SevereWeather.FROST.equals(actualWeather)) {
createPreFrostTask();
} else if (SevereWeather.SNOW.equals(actualWeather)) {
createPreSnowTask();
}
}
private void getRainAmount() throws IOException, HardinessZoneNotSetException, PlantNotFoundException {
int rainAmount = weatherService.causeRainAmount(3);
adjustWateringTask(rainAmount);
}
/**
* Method to create a PreHailTask
* @throws IOException If the database cannot be accessed
*/
private void createPreHailTask() throws IOException {
Task preHailTask = new Task("Hail",
"During a summer Thunderstorm it could hail heavily. THe Hail could damage the crops. To prevent damage cover the plants with a strong tarpaulin",
dateSevereWeather.minusDays(1L),dateSevereWeather.plusDays(1L));
taskList.saveTask(preHailTask);
}
/**
* Method to create a PreFrosttask
* @throws IOException If the database cannot be accessed
*/
private void createPreFrostTask() throws IOException {
Task preFrostTask = new Task("Frost",
"The temperatur falls below zero degrees, cover especially the root with wool",
dateSevereWeather.minusDays(1L),dateSevereWeather.plusDays(1L));
taskList.saveTask(preFrostTask);
}
/**
* Method to create a PreSnowTask
* @throws IOException If the database cannot be accessed
*/
private void createPreSnowTask() throws IOException {
Task preSnowTask = new Task("Snow",
"The weather brings little snowfall. Cover your crops",
dateSevereWeather.minusDays(1L),dateSevereWeather.plusDays(1L));
taskList.saveTask(preSnowTask);
}
/**
* Method to adjust the water plant tasks
* @param rainAmount Amount of rain from the last 7 days
* @throws IOException If the database cannot be accessed
* @throws HardinessZoneNotSetException If the hardiness zone is not available
* @throws PlantNotFoundException if the plant is not available for the watering task
*/
private void adjustWateringTask(int rainAmount) throws HardinessZoneNotSetException, IOException, PlantNotFoundException {
for (Crop crop : cropList.getCrops()) {
Plant plant = plantList.getPlantById(HardinessZone.ZONE_8A,crop.getPlantId()).orElseThrow(PlantNotFoundException::new);
if(plant.wateringCycle().litersPerSqM() < rainAmount){
adjustNextExecutionOfWateringTasks(taskList.getTaskList(LocalDate.now(), LocalDate.now().plusDays(7)));
}
}
}
/**
* Method to set next execution date of the water plant tasks
* @param cropTaskList List with tasks from crops
* @throws IOException If the database cannot be accessed
* @throws HardinessZoneNotSetException If the hardiness zone is not available
* @throws PlantNotFoundException if the plant is not available for the watering task
*/
private void adjustNextExecutionOfWateringTasks(List<Task> cropTaskList){
for(Task task : cropTaskList){
if(task.getName().equals("water plant")){
task.setNextExecution(task.getNextExecution().plusDays(task.getInterval().orElse(1)));
}
}
}
}
@@ -0,0 +1,32 @@
package ch.zhaw.gartenverwaltung.backgroundtasks.weather;
/**
* The WeatherService is a class to cause weather events for the WeatherGardenTaskPlanner
*
*/
public class WeatherService {
private static final int NO_RAIN = 0;
private static final int LITTLE_RAIN = 15;
private static final int RAIN = 25;
private static final int HEAVY_RAIN = 50;
public SevereWeather causeSevereWeather(int randomWeather) {
return switch (randomWeather) {
case 1 -> SevereWeather.HAIL;
case 2 -> SevereWeather.SNOW;
case 3 -> SevereWeather.FROST;
default -> null;
};
}
public int causeRainAmount(int randomRainAmount) {
return switch (randomRainAmount) {
case 1 -> NO_RAIN;
case 2 -> LITTLE_RAIN;
case 3 -> RAIN;
case 4 -> HEAVY_RAIN;
default -> -1;
};
}
}
@@ -1,12 +1,7 @@
package ch.zhaw.gartenverwaltung.bootstrap;
import ch.zhaw.gartenverwaltung.HelloApplication;
import ch.zhaw.gartenverwaltung.io.CropList;
import ch.zhaw.gartenverwaltung.io.JsonCropList;
import ch.zhaw.gartenverwaltung.io.JsonPlantList;
import ch.zhaw.gartenverwaltung.io.JsonTaskList;
import ch.zhaw.gartenverwaltung.io.PlantList;
import ch.zhaw.gartenverwaltung.io.TaskList;
import ch.zhaw.gartenverwaltung.Main;
import ch.zhaw.gartenverwaltung.io.*;
import ch.zhaw.gartenverwaltung.models.Garden;
import ch.zhaw.gartenverwaltung.models.GardenSchedule;
import ch.zhaw.gartenverwaltung.models.PlantListModel;
@@ -82,7 +77,7 @@ public class AppLoader {
* @throws IOException if the file could not be loaded
*/
public Object loadSceneToStage(String fxmlFile, Stage appendee) throws IOException {
FXMLLoader loader = new FXMLLoader(Objects.requireNonNull(HelloApplication.class.getResource(fxmlFile)));
FXMLLoader loader = new FXMLLoader(Objects.requireNonNull(Main.class.getResource(fxmlFile)));
Pane root = loader.load();
Scene scene = new Scene(root);
String css = Objects.requireNonNull(this.getClass().getResource("styles.css")).toExternalForm();
@@ -104,7 +99,7 @@ public class AppLoader {
* @throws IOException if the file could not be loaded
*/
public Object loadPaneToDialog(String fxmlFile, DialogPane appendee) throws IOException {
FXMLLoader loader = new FXMLLoader(Objects.requireNonNull(HelloApplication.class.getResource(fxmlFile)));
FXMLLoader loader = new FXMLLoader(Objects.requireNonNull(Main.class.getResource(fxmlFile)));
appendee.setContent(loader.load());
Object controller = loader.getController();
annotationInject(controller);
@@ -119,7 +114,7 @@ public class AppLoader {
* @throws IOException if the file could not be loaded
*/
public void loadAndCacheFxml(String fxmlFile) throws IOException {
FXMLLoader loader = new FXMLLoader(Objects.requireNonNull(HelloApplication.class.getResource(fxmlFile)));
FXMLLoader loader = new FXMLLoader(Objects.requireNonNull(Main.class.getResource(fxmlFile)));
Pane pane = loader.load();
panes.put(fxmlFile, pane);
annotationInject(loader.getController());
@@ -157,7 +152,7 @@ public class AppLoader {
});
}
private Object getAppDependency(Class<?> type) {
public Object getAppDependency(Class<?> type) {
return dependencies.get(type.getSimpleName());
}
}
@@ -21,13 +21,9 @@ public class PlantImageDeserializer extends JsonDeserializer<Image> {
@Override
public Image deserialize(JsonParser parser, DeserializationContext context) throws IOException {
Image result = null;
URL imageUrl = PlantList.class.getResource(String.format("images/%s", parser.getText()));
if (imageUrl != null) {
try (InputStream is = new FileInputStream(new File(imageUrl.toURI()))) {
result = new Image(is);
} catch (IllegalArgumentException | URISyntaxException e) {
throw new IOException(String.format("Cannot find Image \"%s\"\n", imageUrl.getFile()));
}
InputStream is = PlantList.class.getResourceAsStream(String.format("images/%s", parser.getText()));
if (is != null) {
result = new Image(is);
}
return result;
}
@@ -9,7 +9,6 @@ import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;
public class GardenSchedule {
@@ -49,6 +48,10 @@ public class GardenSchedule {
public void planTasksForCrop(Crop crop) throws PlantNotFoundException, HardinessZoneNotSetException, IOException {
Plant plant = plantList.getPlantById(Settings.getInstance().getCurrentHardinessZone(), crop.getPlantId()).orElseThrow(PlantNotFoundException::new);
int growPhaseGroup = plant.getGrowphaseGroupForDate(crop.getStartDate());
addTask(new Task("watering Task", "pour water over the plant circa : "+ plant.wateringCycle().litersPerSqM() +" per square meter",
crop.getStartDate(), crop.getStartDate().plusDays(plant.timeToHarvest(0)),
plant.wateringCycle().interval(), crop.getCropId().orElse(0L)));
for (GrowthPhase growthPhase : plant.lifecycleForGroup(growPhaseGroup)) {
for (TaskTemplate taskTemplate : growthPhase.taskTemplates()) {
addTask(taskTemplate.generateTask(crop.getStartDate(), crop.getCropId().orElse(0L)));
@@ -1,5 +1,8 @@
package ch.zhaw.gartenverwaltung.types;
import ch.zhaw.gartenverwaltung.io.JsonPlantList;
import ch.zhaw.gartenverwaltung.io.PlantList;
import java.time.LocalDate;
import java.util.Objects;
import java.util.Optional;
@@ -12,7 +12,6 @@ public record GrowthPhase(
MonthDay startDate,
MonthDay endDate,
int group,
WateringCycle wateringCycle,
@JsonDeserialize(using = GrowthPhaseTypeDeserializer.class) GrowthPhaseType type,
@JsonDeserialize(using = HardinessZoneDeserializer.class) HardinessZone zone,
List<TaskTemplate> taskTemplates) {
@@ -18,6 +18,7 @@ public record Plant(
int light,
String soil,
List<Pest> pests,
WateringCycle wateringCycle,
List<GrowthPhase> lifecycle) {
/**
@@ -41,6 +41,16 @@ public class Task {
this.cropId = cropId;
}
/**
* Constructor for weather events
*/
public Task(String name, String description, LocalDate startDate, LocalDate endDate) {
this.name = name;
this.description = description;
this.startDate = startDate;
this.endDate = endDate;
}
public Task(String name, String description, LocalDate startDate, LocalDate endDate, int interval, long cropId) {
this.name = name;
this.description = description;
@@ -72,8 +82,10 @@ public class Task {
public void done(){
if(interval != null && interval != 0 && !nextExecution.plusDays(interval).isAfter(endDate)){
nextExecution = nextExecution.plusDays(interval);
nextNotification = nextExecution;
} else {
nextExecution = null;
nextNotification = null;
}
}