package info.interactivesystems.gamificationengine.entities.task; import info.interactivesystems.gamificationengine.api.exeption.ApiError; import info.interactivesystems.gamificationengine.dao.GoalDAO; import info.interactivesystems.gamificationengine.dao.PlayerGroupDAO; import info.interactivesystems.gamificationengine.dao.RuleDAO; import info.interactivesystems.gamificationengine.entities.Organisation; import info.interactivesystems.gamificationengine.entities.Player; import info.interactivesystems.gamificationengine.entities.PlayerGroup; import info.interactivesystems.gamificationengine.entities.Role; import info.interactivesystems.gamificationengine.entities.goal.FinishedGoal; import info.interactivesystems.gamificationengine.entities.goal.Goal; import info.interactivesystems.gamificationengine.entities.goal.TaskRule; import info.interactivesystems.gamificationengine.entities.rewards.Points; import info.interactivesystems.gamificationengine.entities.rewards.Reward; import java.io.Serializable; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; import javax.persistence.CascadeType; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.ManyToMany; import javax.persistence.ManyToOne; import javax.persistence.OneToMany; import javax.validation.constraints.NotNull; import javax.ws.rs.core.Response; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; /** * The Super Class for different types of Task. * * A Task is the basic module and represents for example a specific activity. By its creation * the roles were assigned which indicate who is allowed to fulfil this task. To complete the * task only one of these roles is needed. One or more tasks can be assigned to a goal, so * depending on the rule of the goal some additional tasks may also have to be completed to * fulfill the goal so the player can earn the associated rewards. If the task is tradeable * it can be offered in the marketplace, so that another player can do it and gets the reward * of it. */ @Entity @JsonIgnoreProperties({ "belongsTo", "finishedTasks" }) public class Task implements Serializable { private static final long serialVersionUID = 8925734998433033594L; private static final Logger LOGGER = LoggerFactory.getLogger(Task.class); @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private int id; @NotNull @ManyToOne private Organisation belongsTo; @NotNull private String taskName; private String description; @ManyToMany(cascade = CascadeType.PERSIST, fetch = FetchType.EAGER) private List<Role> allowedFor; @OneToMany(cascade = {CascadeType.PERSIST, CascadeType.REMOVE}, fetch = FetchType.EAGER, mappedBy="task") private List<FinishedTask> finishedTasks; private boolean tradeable; /** * Gets the id of the task. * * @return int value of the id */ public int getId() { return id; } /** * Sets the id of the task * * @param id * the id of the task */ public void setId(int id) { this.id = id; } /** * Gets the organisation a task belongs to. * * @return The organisation object the task belongs to. */ public Organisation getBelongsTo() { return belongsTo; } /** * Sets the organisation a task belongs to so it can be completed * by its employees. * * @param belongsTo * The organisation a task belongs to. */ public void setBelongsTo(Organisation belongsTo) { this.belongsTo = belongsTo; } /** * Gets the name of a task. * * @return The name of the task as String. */ public String getTaskName() { return taskName; } /** * Sets the name of the task. * * @param taskName * The name of the task as String. */ public void setTaskName(String taskName) { this.taskName = taskName; } /** * Gets the description of a task. * * @return The task's description as String. */ public String getDescription() { return description; } /** * Sets the description of a task. * * @param description * The description of the task as a String. */ public void setDescription(String description) { this.description = description; } /** * Gets all roles for which the task is allowed. A player need only one role * to complete the task. * * @return List of roles which are allowed to complete the task. */ public List<Role> getAllowedFor() { return allowedFor; } /** * Sets all roles for which the task is allowed. A player need only one role * to complete the task. * * @param allowedFor * List of roles which are allowed to complete the task. */ public void setAllowedFor(List<Role> allowedFor) { this.allowedFor = allowedFor; } /** * Checks if a task is tradeable. This means if a player is allowed to offer * it on the marketplace. * * @return The value it the task is tradeable as boolean. */ public boolean isTradeable() { return tradeable; } /** * If the tradeable field is set true a task as tradeable, so that it can be * offered on the marketplace, if it is set to false the task cannot be traded. * * @param tradable * The value (true/false) if the task is tradeable as boolean. */ public void setTradeable(boolean tradable) { this.tradeable = tradable; } /** * Checks if a task belongs to a specific organisation. If the task has the * same API key like the organisation the method returns true and the task * belongs to this organisation otherwise false is returned. * * @param organisation * The organisation which is tested. * @return The value if a task belongs to the specific organisation (true) or * not (false). */ public boolean belongsTo(Organisation organisation) { return getBelongsTo().getApiKey().equals(organisation.getApiKey()); } /** * If a player completed a task this method adds the task to the list of finished * tasks if the player is allowed to complete this task. This is tested by the * roles a player has and the roles which are assigned to the task. * Also the method tests if the player isn't deactivated because then she/he isn't * allowed to complete tasks. * * @param player * The player who completed the task. This parameter must not be null. * @param ruleDao * The rule DAO is required to access the created rules. * @param goalDao * The goal DAO is required to access created goals. * @param groupDao * The group DAO is required to access created groups. * @param finishedDate * DateTime when the task has been finished the date time is stored. If * the value is null the date is set to now. * @param apiKey * The API key of the organisation. */ public void completeTask(Player player, RuleDAO ruleDao, GoalDAO goalDao, PlayerGroupDAO groupDao, LocalDateTime finishedDate, String apiKey) { if (!player.isActive()) { throw new ApiError(Response.Status.FORBIDDEN, "Player is inactive!"); } Task task = this; // set tempFinishedGoals list to add this to the player at the end --> avoid transaction errors List<FinishedGoal> finishedPlayerGoalsList = new ArrayList<>(); List<Reward> recievedRewards = new ArrayList<>(); List<Role> matchingRoles = new ArrayList<>(); // set Timestamp if (finishedDate == null) { finishedDate = LocalDateTime.now(); } boolean pointsRecieved = false; FinishedTask fTask = new FinishedTask(); fTask.setTask(task); fTask.setFinishedDate(finishedDate); fTask.setPlayer(player); logPlayerDetails(player); // check if task can be completed by player playerIsAllowed(player, task, matchingRoles); List<FinishedTask> playerFinishedTasksList = player.getFinishedTasks(); playerFinishedTasksList.add(fTask); logTasks(player, playerFinishedTasksList); // search all rules which contain this task List<TaskRule> rules = ruleDao.getRulesByTask(task, apiKey); LOGGER.debug("Rule count: " + rules.size()); // for each rule... for (TaskRule rule : rules) { LOGGER.debug("Rule: " + rule.getName()); // get goals which contain this rule for (Goal goal : goalDao.getGoalsByRule(rule, apiKey)) { logGoalandRoleNames(goal, player); if (goal.getCanCompletedBy().size() > 0) { LOGGER.debug("Goal is restricted by roles"); matchingRoles = goal.getCanCompletedBy().stream().filter(r -> { if (player.getBelongsToRoles().contains(r)) { LOGGER.debug("Player has required Role to Complete Goal: " + r.getName()); return true; } else { return false; } }).collect(Collectors.toList()); if (matchingRoles.size() > 0) { LOGGER.debug("Roles match -> proceed"); } else { LOGGER.debug("Roles don't match -> goal can not be completed"); continue; } } else { LOGGER.debug("Goal is not restricted by roles"); } List<FinishedGoal> oldFinishedGoals = new ArrayList<>(); // check if goal is groupGoal if (!goal.isPlayerGroupGoal()) { oldFinishedGoals.addAll(player.getFinishedGoalsByGoal(goal)); // check if goal is completed FinishedGoal tempFinishedGoal = goal.checkGoal(player, null, oldFinishedGoals, playerFinishedTasksList, rule); if (tempFinishedGoal != null) { finishedPlayerGoalsList.add(tempFinishedGoal); } } else { // get all groups from player List<PlayerGroup> allGroups = groupDao.getAllGroups(apiKey); List<PlayerGroup> playerGroups = new ArrayList<>(); List<Role> matchingGroupRoles = new ArrayList<>(); for (PlayerGroup g : allGroups) { if (g.getPlayers().contains(player)) { playerGroups.add(g); } } // for each group for (PlayerGroup group : playerGroups) { // get finishedGoals List<FinishedGoal> groupFinishedGoals = group.getFinishedGoals(); // get finished tasks from all players List<FinishedTask> groupFinishedTasksList = new ArrayList<>(); for (Player p : group.getPlayers()) { groupFinishedTasksList.addAll(p.getFinishedTasks()); } //Test, if one player role of the group match with role of the goal if (goal.getCanCompletedBy().size() > 0) { LOGGER.debug("Pointsgoal is restricted by roles"); for(Player everyGroupPlayer : group.getPlayers()){ matchingGroupRoles.addAll(goal.getCanCompletedBy().stream().filter(r -> { if (everyGroupPlayer.getBelongsToRoles().contains(r)) { LOGGER.debug("Player has required Role to Complete Pointgoal: " + r.getName()); return true; } else { return false; } }).collect(Collectors.toList())); } if (matchingGroupRoles.size() > 0) { LOGGER.debug("Roles match for PointGoal -> proceed"); } else { LOGGER.debug("Roles don't match for Pointgoal -> Pointgoal can not be completed"); continue; } } else { LOGGER.debug("Pointgoal is not restricted by roles"); } // check if goal is completed and add it to finishedGoals of group FinishedGoal tempFinishedGoal = goal.checkGoal(null, group, groupFinishedGoals, groupFinishedTasksList, rule); if (tempFinishedGoal != null) { // add goal to finishedGoals list groupFinishedGoals.add(tempFinishedGoal); // add rewards to group for (Reward r : goal.getRewards()) { LOGGER.debug("Add Reward to group"); if(r instanceof Points){ ((Points) r).addReward(group, goalDao, ruleDao); } else { r.addReward(group, goalDao, ruleDao); } } //Control for (PlayerGroup gr : playerGroups) { LOGGER.debug("Group points are: " + gr.getPoints()); } } } } } } // proceed with fGoalsList LOGGER.debug("proceed with fGoalsList"); for (FinishedGoal fGoal : finishedPlayerGoalsList) { if(!fGoal.getGoal().isPlayerGroupGoal()){ fGoal.setPlayer(player); } // for each reward -> addReward for (Reward reward : fGoal.getGoal().getRewards()) { LOGGER.debug("Reward: " + reward.getId()); // only add point rewards if (reward instanceof Points) { LOGGER.debug("Reward: instanceof Points -> get points"); Points r = (Points) reward; LOGGER.debug("Reward Points: " + r.getAmount()); reward.addReward(player, goalDao, ruleDao); pointsRecieved = true; } else { // other awards will be added afterwards LOGGER.debug("Reward: NOT instanceof Points -> add to recievedRewards"); recievedRewards.add(reward); } } } LOGGER.debug("add finishedGoals to player"); // add Goals to finishedGaolsList player.addFinishedGoal(finishedPlayerGoalsList); LOGGER.debug("add Rewards to player"); // add Rewards to rewardList for (Reward reward : recievedRewards) { LOGGER.debug("Reward id : " + reward.getId()); reward.addReward(player, goalDao, ruleDao); } logPlayerDetails(player); } private void logPlayerDetails(Player player) { LOGGER.debug("Player Name: " + player.getNickname()); LOGGER.debug("Player Points: " + player.getPoints()); LOGGER.debug("Player Currency: " + player.getCoins()); LOGGER.debug("Player Tasks: " + player.getFinishedTasks().size()); LOGGER.debug("Player Goals: " + player.getFinishedGoals().size()); } private void logGoalandRoleNames(Goal goal, Player player) { LOGGER.debug("Goal: " + goal.getName()); // check if goal can be completed by player LOGGER.debug("Player Roles:"); for (Role r : player.getBelongsToRoles()) { LOGGER.debug("- " + r.getName()); } LOGGER.debug("Goal Roles:"); for (Role r : goal.getCanCompletedBy()) { LOGGER.debug("- " + r.getName()); } } private void logTasks(Player player, List<FinishedTask> playerFinishedTasksList) { LOGGER.debug("Player Tasks: " + player.getFinishedTasks().size()); LOGGER.debug("Player Tasks last item: " + player.getFinishedTasks().get(player.getFinishedTasks().size() - 1).getFinishedDate()); LOGGER.debug("Temp Tasks List: " + playerFinishedTasksList.size()); LOGGER.debug("Temp Tasks List last item: " + playerFinishedTasksList.get((playerFinishedTasksList.size() - 1)).getFinishedDate()); } public void playerIsAllowed(Player player, Task task, List<Role> matchingRoles){ LOGGER.debug("Player Roles:"); for (Role r : player.getBelongsToRoles()) { LOGGER.debug("- " + r.getName()); } LOGGER.debug("Task Roles:"); for (Role r : task.getAllowedFor()) { LOGGER.debug("- " + r.getName()); } if (task.getAllowedFor().size() > 0) { LOGGER.debug("Task is restricted by roles"); matchingRoles = task.getAllowedFor().stream().filter(r -> { if (player.getBelongsToRoles().contains(r)) { LOGGER.debug("Player has required Role to Complete Task: " + r.getName()); return true; } else { return false; } }).collect(Collectors.toList()); if (matchingRoles.size() > 0) { LOGGER.debug("Roles match -> proceed"); } else { LOGGER.debug("Roles don't match -> error"); throw new ApiError(Response.Status.FORBIDDEN, "Roles don't match!"); } } else { LOGGER.debug("Task is not restricted by roles"); } } }