package info.interactivesystems.gamificationengine.api; import info.interactivesystems.gamificationengine.api.exeption.ApiError; import info.interactivesystems.gamificationengine.api.exeption.Notification; import info.interactivesystems.gamificationengine.api.validation.ValidApiKey; import info.interactivesystems.gamificationengine.api.validation.ValidListOfDigitsOrNull; import info.interactivesystems.gamificationengine.api.validation.ValidPositiveDigit; import info.interactivesystems.gamificationengine.dao.GoalDAO; import info.interactivesystems.gamificationengine.dao.MarketPlaceDAO; import info.interactivesystems.gamificationengine.dao.OrganisationDAO; import info.interactivesystems.gamificationengine.dao.PlayerDAO; import info.interactivesystems.gamificationengine.dao.PlayerGroupDAO; import info.interactivesystems.gamificationengine.dao.RoleDAO; import info.interactivesystems.gamificationengine.dao.RuleDAO; import info.interactivesystems.gamificationengine.dao.TaskDAO; import info.interactivesystems.gamificationengine.entities.Organisation; import info.interactivesystems.gamificationengine.entities.Player; import info.interactivesystems.gamificationengine.entities.Role; import info.interactivesystems.gamificationengine.entities.goal.GoalRule; import info.interactivesystems.gamificationengine.entities.goal.TaskRule; import info.interactivesystems.gamificationengine.entities.marketPlace.MarketPlace; import info.interactivesystems.gamificationengine.entities.marketPlace.Offer; import info.interactivesystems.gamificationengine.entities.task.Task; import info.interactivesystems.gamificationengine.utils.LocalDateTimeUtil; import info.interactivesystems.gamificationengine.utils.OfferMarketPlace; import info.interactivesystems.gamificationengine.utils.StringUtils; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; import javax.ejb.Stateless; import javax.inject.Inject; import javax.validation.constraints.NotNull; import javax.ws.rs.DELETE; import javax.ws.rs.DefaultValue; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.webcohesion.enunciate.metadata.rs.TypeHint; /** * A Task is the basic module and represents for example a specific activity. For a creation * of a task, the roles are needed 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. * * When a player has completed a task, it will be added to the player’s list of finished * tasks. At the same time the date and time is also stored when this request was sent and the * task was officially be done. If the task is the last one to fulfill a goal, the goal is also * added to the player’s list of finished goals and the player will obtain all its associated * rewards. * It is possible to query all tasks which are associated with a particular organisation or with * the help of the associated id one specific task. When a task was created it is possible to * change the task’s name, description and roles of players who are allowed to fulfil this task. * Furthermore a task can be set tradeable or not at a later point of time. */ @Path("/task") @Stateless @Produces(MediaType.APPLICATION_JSON) public class TaskApi { private static final Logger LOGGER = LoggerFactory.getLogger(TaskApi.class); @Inject OrganisationDAO organisationDao; @Inject TaskDAO taskDao; @Inject PlayerDAO playerDao; @Inject PlayerGroupDAO groupDao; @Inject RuleDAO ruleDao; @Inject GoalDAO goalDao; @Inject RoleDAO roleDao; @Inject MarketPlaceDAO marketPlDao; /** * Creates a new task and so the method generates the task-id. The organisation's API key * is mandatory otherwise a warning with the hint for a non valid API key is returned. * By the creation values for its name, a short description what have to done and the roles * who are allowed to complete the task. * It is checked, if the id of the roles are positive numbers otherwise a message for the * invalid number is returned. * * @param name * The name of the task. This parameter is required. * @param description * Optional a short description can be set. This can be for example explain what * a player has to do to complete the task. * @param tradeable * This field specifies whether the task is tradeable or not. The default value is * set to not tradeable (false). * @param roleIds * Optionally a list of role ids separated by commas which are allowed to fulfil the * task. * @param apiKey * The valid query parameter API key affiliated to one specific organisation, * to which this task belongs to. * @return Response of Task in JSON. */ @POST @Path("/") @TypeHint(Task.class) public Response createNewTask(@QueryParam("name") @NotNull String name, @QueryParam("description") String description, @QueryParam("tradeable") @DefaultValue("false") String tradeable, @QueryParam("roleIds")@DefaultValue("null")@ValidListOfDigitsOrNull(message = "The role ids must be null or a valid list of numbers") String roleIds, @QueryParam("apiKey") @ValidApiKey String apiKey) { LOGGER.debug("createNewTask called"); Organisation organisation = organisationDao.getOrganisationByApiKey(apiKey); List<Role> roles = new ArrayList<>(); if(!roleIds.equals("null")){ String[] roleIdList = roleIds.split(","); for (String roleIdString : roleIdList) { Role role = roleDao.getRole(ValidateUtils.requireGreaterThanZero(roleIdString), apiKey); if (role != null) { roles.add(role); } } } Task task = new Task(); task.setTaskName(name); task.setDescription(description); task.setBelongsTo(organisation); task.setAllowedFor(roles); task.setTradeable(Boolean.parseBoolean(tradeable)); taskDao.insertTask(task); // Notification note = new Notification().of("New Task created."); // note.addError("New Task created."); return ResponseSurrogate.created(task, new Notification().of("New Task created.")); } /** * Returns a list of all tasks associated with the passed API key. If the key is not * valid an analogous message is returned. * * @param apiKey * The valid query parameter API key affiliated to one specific organisation, * to which this task belongs to. * @return Response of Task in JSON. */ @GET @Path("/*") @TypeHint(Task[].class) public Response getTasks(@QueryParam("apiKey") @ValidApiKey String apiKey) { List<Task> tasks = taskDao.getTasks(apiKey); for (Task t : tasks) { LOGGER.debug("Task: " + t.getTaskName()); for (Role r : t.getAllowedFor()) { LOGGER.debug("Role: " + r.getId()); } } return ResponseSurrogate.of(tasks); } /** * Returns a list of all tasks which are tradeable and associated with the passed API key. If the key is not * valid an analogous message is returned. * * @param apiKey * The valid query parameter API key affiliated to one specific organisation, * to which these tasks belong to. * @return Response of all tradeable tasks in JSON. */ @GET @Path("/tradeable/*") @TypeHint(Task[].class) public Response getTradeableTasks(@QueryParam("apiKey") @ValidApiKey String apiKey) { List<Task> tasks = taskDao.getTasks(apiKey).stream().filter(t -> t.isTradeable()==true).collect(Collectors.toList()); return ResponseSurrogate.of(tasks); } /** * Returns the task associated with the passed id and API key. If the API key is not * valid an analogous message is returned. It is also checked, if the player id is a * positive number otherwise a message for an invalid number is returned. * * @param id * Required path parameter as integer which uniquely identify the Task. * @param apiKey * The valid query parameter API key affiliated to one specific organisation, * to which this task belongs to. * @return Response of Task in JSON. */ @GET @Path("/{id}") @TypeHint(Task.class) public Response getTask(@PathParam("id") @NotNull @ValidPositiveDigit(message = "The task id must be a valid number") String id, @QueryParam("apiKey") @ValidApiKey String apiKey) { int taskId = ValidateUtils.requireGreaterThanZero(id); Task task = taskDao.getTask(taskId, apiKey); ValidateUtils.requireNotNull(taskId, task); return ResponseSurrogate.of(task); } /** * This method completes a task with the assigned id and associated API key. The player-id * represents the player who has completed the task. The task is added to the list of * finished tasks of this player. Thereby the task becomes a finished task object and the * time and date is also stored when the task was officially be done. * * @param id * Required integer which uniquely identify the Task. * @param playerId * The if ot the player who has completed the task. This parameter is required. * @param finishedDate * Optionally the local tate time can be passed when the task was finished. If the * value is null, the finshedDate is set to the time and date when the query was * sent. * @param apiKey * The valid query parameter API key affiliated to one specific organisation, * to which this task belongs to. * @return Response of Task in JSON. */ @POST @Path("/{id}/complete/{playerId}") @TypeHint(Task.class) public Response completeTask(@PathParam("id") @NotNull @ValidPositiveDigit(message = "The task id must be a valid number") String id, @PathParam("playerId") @NotNull @ValidPositiveDigit(message = "The player id must be a valid number") String playerId, @QueryParam("finishedDate") String finishedDate, @QueryParam("apiKey") @ValidApiKey String apiKey) { // find player by id and organisation LOGGER.debug("Get Player"); int pId = ValidateUtils.requireGreaterThanZero(playerId); Player player = playerDao.getPlayer(pId, apiKey); ValidateUtils.requireNotNull(pId, player); // find task by id and organisation int taskId = ValidateUtils.requireGreaterThanZero(id); Task task = taskDao.getTask(taskId, apiKey); ValidateUtils.requireNotNull(taskId, task); LOGGER.debug("TaskName: " + task.getTaskName()); if (finishedDate == null || "".equals(finishedDate)) { LOGGER.debug("No Date passed."); task.completeTask(player, ruleDao, goalDao, groupDao, null, apiKey); } else { LOGGER.debug("Date passed: " + finishedDate); LocalDateTime dateTime = LocalDateTimeUtil.formatDateAndTime(finishedDate); task.completeTask(player, ruleDao, goalDao, groupDao, dateTime, apiKey); } List<OfferMarketPlace> taskOffers = MarketPlace.getAllOfferMarketPlaces(marketPlDao, task, apiKey); if(!taskOffers.isEmpty()){ MarketPlace.completeAssociatedOffers(taskOffers, player, marketPlDao, playerDao, apiKey); } return ResponseSurrogate.created(task); } /** * With this method the fields of a Task can be changed. For this the id of the * task, the API key of the specific organisation, the name of the field and the new * value are needed. * To modify the name or the description the new String has to be passed with the * attribute field. A new list of roles can be passed when their ids are separated by * commas. Also the task can be set tradeable or not by passing the value true or false. * If the API key is not valid an analogous message is returned. It is also checked, if * the ids are a positive number otherwise a message for an invalid number is returned. * * @param id * The id of the task that should be changed. This parameter is required. * @param attribute * The name of the attribute which should be modified. This parameter is required. * The following names of attributes can be used to change the associated field: * "taskName", "description", "tradeable" and "roles". * @param value * The new value of the attribute. This parameter is required. * @param apiKey * The valid query parameter API key affiliated to one specific organisation, * to which this task belongs to. * @return Response of Task in JSON. */ @PUT @Path("/{id}/attributes") @TypeHint(Task.class) public Response changeTaskAttributes(@PathParam("id") @NotNull @ValidPositiveDigit String id, @QueryParam("attribute") @NotNull String attribute, @QueryParam("value") @NotNull String value, @QueryParam("apiKey") @ValidApiKey String apiKey) { LOGGER.debug("change Attribute of Task"); int taskId = ValidateUtils.requireGreaterThanZero(id); Task task = taskDao.getTask(taskId, apiKey); ValidateUtils.requireNotNull(taskId, task); if ("null".equals(value)) { value = null; } // not changeable: id -> generated & belongsTo; switch (attribute) { case "taskName": task.setTaskName(value); break; case "description": task.setDescription(value); break; case "tradeable": task.setTradeable(Boolean.parseBoolean(value)); break; case "roles": changeRoles(value, task, apiKey); break; default: break; } taskDao.insertTask(task); return ResponseSurrogate.updated(task); } /** * This method converts the string of role ids which are transfered to a list of roles. * These roles are then set as the new list of roles a player can have to fulfil this task. * * @param value * The new values of roles as String separated by commas. This parameter is * required. * @param task * The task whose field of roles will be modified. This parameter should be not * null because this method is called by a method which checks the given id if a * group exists. * @param apiKey * The valid query parameter API key affiliated to one specific organisation, * to which this task belongs to. */ private void changeRoles(@NotNull String value, Task task, String apiKey) { if(value != null){ String commaSeparatedList = StringUtils.validateAsListOfDigits(value); List<Integer> ids = StringUtils.stringArrayToIntegerList(commaSeparatedList); List<Role> roles = roleDao.getRoles(ids, apiKey); task.setAllowedFor(roles); }else task.setAllowedFor(null); } /** * Removes the task with the assigned id and associated API key from data base. But only if * the task is not associated to a goal rule or is an offer on the marketplace. Then first these * elements have to deleted. * * Consider that if a task is deleted all finished tasks that contains this task are also deleted! * So these finished tasks are also removed of the player's list who has completed it. * It is checked, if the passed id is a positive number otherwise a message for an invalid number * is returned. If the API key is not valid an analogous message is returned. * * @param id * Required integer which uniquely identify the Task. * @param apiKey * The valid query parameter API key affiliated to one specific organisation, * to which this task belongs to. * @return Response of Task in JSON. */ @DELETE @Path("/{id}") @TypeHint(Task.class) public Response deleteTask(@PathParam("id") @NotNull @ValidPositiveDigit(message = "The task id must be a valid number") String id, @QueryParam("apiKey") @ValidApiKey String apiKey) { int taskId = ValidateUtils.requireGreaterThanZero(id); Task task = taskDao.getTask(taskId, apiKey); ValidateUtils.requireNotNull(taskId, task); List<TaskRule> rules = ruleDao.getRulesByTask(task, apiKey); if(!rules.isEmpty()){ GoalRule.checkRulesForTask(rules); } List<Offer> offers = marketPlDao.getOffersByTask(task, apiKey); if(!offers.isEmpty()){ Offer.checkOffersForTask(offers); } task = taskDao.deleteTask(taskId, apiKey); return ResponseSurrogate.deleted(task); } /** * This method returns only the requested tasks. This can be used, when more than one task is needed but not * all tasks. * * @param idsOfTask * The ids of the requestes tasks. These are passed as a comma separated list. * @param apiKey * The valid query parameter API key affiliated to one specific organisation, * to which these tasks belong to. * @return Response of specific tasks in JSON. */ @GET @Path("/specificTasks") @TypeHint(Task[].class) public Response getSpecificTasks( @QueryParam("taskIds") @DefaultValue("null") @ValidListOfDigitsOrNull String idsOfTask, @QueryParam("apiKey") @ValidApiKey String apiKey){ List<Task> tasks = new ArrayList<>(); if(!idsOfTask.equals("null")){ List<Integer> taskIds = StringUtils.stringArrayToIntegerList(idsOfTask); tasks = taskDao.getTasksWithId(taskIds, apiKey); List<Integer> taskIds1 = new ArrayList<>(taskIds); taskIds1.removeAll(tasks.stream().map(Task::getId).collect(Collectors.toList())); if (!taskIds1.isEmpty()) { throw new ApiError(Response.Status.FORBIDDEN, "Creation failed, task ids don't exist " + taskIds1); } } return ResponseSurrogate.of(tasks); } /** * Gets all tasks that are not done for at least one time. * * @param apiKey * The valid query parameter API key affiliated to the specific organisation, * to which these task belong to. * @return Response of the requested tasks in JSON. */ @GET @Path("/notDone") @TypeHint(Task[].class) public Response getTasksNotDone(@QueryParam("apiKey") @ValidApiKey String apiKey){ List<Task> tasks = taskDao.getTasksToDo(apiKey); return ResponseSurrogate.of(tasks); } /** * Gets all tradeable tasks that are not done for at least one time. * * @param apiKey * The valid query parameter API key affiliated to the specific organisation, * to which these task belong to. * @return Response of the requested tasks in JSON. */ @GET @Path("/tradeableNotDone") @TypeHint(Task[].class) public Response getTradeableTasksNotDone(@QueryParam("apiKey") @ValidApiKey String apiKey){ List<Task> tasks = taskDao.getTradeableTasksToDo(apiKey); return ResponseSurrogate.of(tasks); } }