Java tutorial
/*! LICENSE * * Copyright (c) 2015, The Agile Factory SA and/or its affiliates. All rights * reserved. * * 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; version 2 of the License. * * 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 <http://www.gnu.org/licenses/>. */ package controllers.core; import java.util.ArrayList; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.inject.Inject; import com.avaje.ebean.Ebean; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectWriter; import be.objectify.deadbolt.java.actions.Group; import be.objectify.deadbolt.java.actions.Restrict; import constants.IMafConstants; import controllers.ControllersUtils; import dao.governance.LifeCycleMilestoneDao; import dao.pmo.ActorDao; import framework.security.ISecurityService; import framework.services.account.IAccountManagerPlugin; import framework.services.account.IPreferenceManagerPlugin; import framework.services.account.IUserAccount; import framework.services.configuration.II18nMessagesPlugin; import framework.services.notification.INotificationManagerPlugin; import framework.services.session.IUserSessionManagerPlugin; import framework.services.storage.IAttachmentManagerPlugin; import framework.utils.DefaultSelectableValueHolder; import framework.utils.DefaultSelectableValueHolderCollection; import framework.utils.FileAttachmentHelper; import framework.utils.Msg; import framework.utils.Pagination; import framework.utils.Table; import framework.utils.Utilities; import models.framework_models.account.NotificationCategory; import models.framework_models.account.NotificationCategory.Code; import models.framework_models.account.Principal; import models.framework_models.common.Attachment; import models.governance.LifeCycleMilestone; import models.governance.LifeCycleMilestoneInstance; import models.governance.LifeCycleMilestoneInstanceApprover; import models.governance.LifeCycleMilestoneInstanceStatusType; import models.pmo.Actor; import models.pmo.PortfolioEntry; import play.Configuration; import play.Logger; import play.data.Form; import play.mvc.Controller; import play.mvc.Result; import services.budgettracking.IBudgetTrackingService; import services.tableprovider.ITableProvider; import utils.form.ProcessMilestoneApprovalFormData; import utils.form.ProcessMilestoneDecisionFormData; import utils.table.MilestoneApprovalListView; import utils.table.MilestoneApproverListView; /** * The controller which allows to approve / decide a milestone instance. * * Approve a milestone simply represents a vote by a user and has no impact * about the governance (except the vote itself). * * Decide a milestone represents the final decision (also called final * approval). So the milestone is passed and possibly approved. The following * operations are done:<br/> * -the current budget is closed and assigned to the milestone, a new budget * (based on the existing) is created<br/> * -the current planning is frozen and a new one (based on the existing) is * created<br/> * -if the portfolio entry is a concept and the milestone is approved then the * flag is concept is settled to false * * @author Johann Kohler */ public class MilestoneApprovalController extends Controller { @Inject private IUserSessionManagerPlugin userSessionManagerPlugin; @Inject private IAccountManagerPlugin accountManagerPlugin; @Inject private IAttachmentManagerPlugin attachmentPluginManager; @Inject private ISecurityService securityService; @Inject private II18nMessagesPlugin i18nMessagesPlugin; @Inject private Configuration configuration; @Inject private IBudgetTrackingService budgetTrackingService; @Inject private INotificationManagerPlugin notificationManagerService; @Inject private ITableProvider tableProvider; @Inject private IPreferenceManagerPlugin preferenceManagerPlugin; private static Logger.ALogger log = Logger.of(MilestoneApprovalController.class); private static Form<ProcessMilestoneApprovalFormData> processMilestoneApprovalFormTemplate = Form .form(ProcessMilestoneApprovalFormData.class); private static Form<ProcessMilestoneDecisionFormData> processMilestoneDecisionFormTemplate = Form .form(ProcessMilestoneDecisionFormData.class); /** * The possible vote values. */ public static DefaultSelectableValueHolderCollection<Boolean> getVoteValues() { DefaultSelectableValueHolderCollection<Boolean> voteValues = new DefaultSelectableValueHolderCollection<Boolean>(); voteValues.add(new DefaultSelectableValueHolder<Boolean>(true, "<span class=\"a label label-success\">" + Msg.get("button.approve") + "</span>")); voteValues.add(new DefaultSelectableValueHolder<Boolean>(false, "<span class=\"b label label-danger\">" + Msg.get("button.reject") + "</span>")); return voteValues; } /** * Display the milestones' planning. */ @Restrict({ @Group(IMafConstants.MILESTONE_OVERVIEW_PERMISSION) }) public Result overview() { List<LifeCycleMilestoneInstance> milestoneInstances = LifeCycleMilestoneDao .getLCMilestoneInstancePublicPEAsList(); List<MilestoneInstanceEvent> events = new ArrayList<MilestoneInstanceEvent>(); for (LifeCycleMilestoneInstance milestoneInstance : milestoneInstances) { events.add(new MilestoneInstanceEvent(milestoneInstance)); } String source = ""; try { ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter(); source = ow.writeValueAsString(events); source = source.replaceAll("\"statusClass\"", "\"class\""); } catch (JsonProcessingException e) { return ControllersUtils.logAndReturnUnexpectedError(e, log, getConfiguration(), getI18nMessagesPlugin()); } return ok(views.html.core.milestoneapproval.overview.render(source)); } /** * Modal content for a milestone in the planning. * * @param milestoneInstanceId * the milestone instance id */ @Restrict({ @Group(IMafConstants.MILESTONE_OVERVIEW_PERMISSION) }) public Result overviewModal(Long milestoneInstanceId) { // get the milestone instance LifeCycleMilestoneInstance milestoneInstance = LifeCycleMilestoneDao .getLCMilestoneInstanceById(milestoneInstanceId); // construct the approvers table List<MilestoneApproverListView> milestoneApproverListView = new ArrayList<MilestoneApproverListView>(); for (LifeCycleMilestoneInstanceApprover lifeCycleMilestoneInstanceApprover : milestoneInstance.lifeCycleMilestoneInstanceApprovers) { milestoneApproverListView.add(new MilestoneApproverListView(lifeCycleMilestoneInstanceApprover)); } Table<MilestoneApproverListView> filledTable = this.getTableProvider().get().milestoneApprover.templateTable .fill(milestoneApproverListView); return ok(views.html.core.milestoneapproval.overview_modal.render(milestoneInstance, filledTable)); } /** * Display the list of life cycle milestone instance for which an * vote/decision is needed. * * @param page * the current page */ @Restrict({ @Group(IMafConstants.MILESTONE_APPROVAL_PERMISSION), @Group(IMafConstants.MILESTONE_DECIDE_PERMISSION) }) public Result list(Integer page) { // get the current user IUserAccount userAccount; try { userAccount = getAccountManagerPlugin() .getUserAccountFromUid(getUserSessionManagerPlugin().getUserSessionId(ctx())); } catch (Exception e) { return ControllersUtils.logAndReturnUnexpectedError(e, log, getConfiguration(), getI18nMessagesPlugin()); } // get the current actor Actor actor = ActorDao.getActorByUid(userAccount.getUid()); // get the milestone instances required for a vote/decision Pagination<LifeCycleMilestoneInstance> pagination; if (getSecurityService().restrict(IMafConstants.MILESTONE_DECIDE_PERMISSION, userAccount)) { pagination = LifeCycleMilestoneDao .getLCMilestoneInstanceAsPagination(this.getPreferenceManagerPlugin()); } else { // if the sign in user hasn't the permission // MILESTONE_DECIDE_PERMISSION and is not related to an actor, then // the list of milestone to approve doesn't make sense if (actor == null) { return redirect(controllers.dashboard.routes.DashboardController.index(0, false)); } pagination = LifeCycleMilestoneDao .getLCMilestoneInstanceAsPaginationByApprover(this.getPreferenceManagerPlugin(), actor.id); } pagination.setCurrentPage(page); List<MilestoneApprovalListView> milestoneApprovalListView = new ArrayList<MilestoneApprovalListView>(); for (LifeCycleMilestoneInstance lifeCycleMilestoneInstance : pagination.getListOfObjects()) { milestoneApprovalListView.add(new MilestoneApprovalListView(lifeCycleMilestoneInstance)); } Table<MilestoneApprovalListView> filledTable = this.getTableProvider().get().milestoneApproval.templateTable .fill(milestoneApprovalListView); return ok(views.html.core.milestoneapproval.milestone_approval_list.render(filledTable, pagination)); } /** * Display the page to vote/decide for a milestone instance. This page * contains the details of the milestone (and its portfolio entry), the * approvers with their vote, the panel to vote (if approver), the panel to * decide (if decider). * * @param milestoneInstanceId * the milestone instance id */ @Restrict({ @Group(IMafConstants.MILESTONE_APPROVAL_PERMISSION), @Group(IMafConstants.MILESTONE_DECIDE_PERMISSION) }) public Result process(Long milestoneInstanceId) { // get the milestone instance LifeCycleMilestoneInstance milestoneInstance = LifeCycleMilestoneDao .getLCMilestoneInstanceById(milestoneInstanceId); // check the milestone instance is not already passed (decided) if (milestoneInstance.isPassed == true) { Utilities.sendInfoFlashMessage(Msg.get("core.milestone.approval.process.alreadydecided")); return redirect(controllers.core.routes.MilestoneApprovalController.list(0)); } // get the current user IUserAccount userAccount; try { userAccount = getAccountManagerPlugin() .getUserAccountFromUid(getUserSessionManagerPlugin().getUserSessionId(ctx())); } catch (Exception e) { return ControllersUtils.logAndReturnUnexpectedError(e, log, getConfiguration(), getI18nMessagesPlugin()); } // get the current actor Actor actor = ActorDao.getActorByUid(userAccount.getUid()); // if exists, get the approver instance LifeCycleMilestoneInstanceApprover approverInstance = null; if (actor != null) { approverInstance = LifeCycleMilestoneDao .getLCMilestoneInstanceApproverByActorAndLCMilestoneInstance(actor.id, milestoneInstance.id); } // if the user hasn't the permission MILESTONE_DECIDE_PERMISSION, then // he must be an approver of the milestone instance if (!getSecurityService().restrict(IMafConstants.MILESTONE_DECIDE_PERMISSION, userAccount)) { if (approverInstance == null) { return forbidden(views.html.error.access_forbidden.render("")); } else { // check the approver has not already vote if (approverInstance.hasApproved != null) { Utilities.sendInfoFlashMessage(Msg.get("core.milestone.approval.process.alreadyvoted")); return redirect(controllers.core.routes.MilestoneApprovalController.list(0)); } } } /* * construct the current vote table */ // initiate the list view List<MilestoneApproverListView> milestoneApproverListView = new ArrayList<MilestoneApproverListView>(); // add the approvers with vote if exists for (LifeCycleMilestoneInstanceApprover lifeCycleMilestoneInstanceApprover : milestoneInstance.lifeCycleMilestoneInstanceApprovers) { milestoneApproverListView.add(new MilestoneApproverListView(lifeCycleMilestoneInstanceApprover)); } // hide the date column Set<String> hideColumnsForApprover = new HashSet<String>(); hideColumnsForApprover.add("approvalDate"); // fill the table Table<MilestoneApproverListView> filledTable = this.getTableProvider().get().milestoneApprover.templateTable .fill(milestoneApproverListView, hideColumnsForApprover); /* * end of table construction */ // get the portfolio entry PortfolioEntry portfolioEntry = milestoneInstance.lifeCycleInstance.portfolioEntry; // get the milestone instances status List<String> status = LifeCycleMilestoneDao.getLCMilestoneAsStatusByPEAndLCMilestone(portfolioEntry.id, milestoneInstance.lifeCycleMilestone.id); // if exists, get the description document List<Attachment> attachments = FileAttachmentHelper.getFileAttachmentsForDisplay( LifeCycleMilestoneInstance.class, milestoneInstance.id, getAttachmentPluginManager(), getUserSessionManagerPlugin()); Attachment descriptionDocument = null; if (attachments != null && attachments.size() > 0) { descriptionDocument = attachments.get(0); } // construct the approval (vote) form Form<ProcessMilestoneApprovalFormData> processMilestoneApprovalForm = null; if (approverInstance != null) { processMilestoneApprovalForm = processMilestoneApprovalFormTemplate .fill(new ProcessMilestoneApprovalFormData(approverInstance)); } // construct the decision form Form<ProcessMilestoneDecisionFormData> processMilestoneDecisionForm = null; if (getSecurityService().restrict(IMafConstants.MILESTONE_DECIDE_PERMISSION, userAccount)) { processMilestoneDecisionForm = processMilestoneDecisionFormTemplate .fill(new ProcessMilestoneDecisionFormData(milestoneInstance)); } return ok(views.html.core.milestoneapproval.milestone_approval_process.render(milestoneInstance, descriptionDocument, approverInstance, filledTable, portfolioEntry, status, processMilestoneApprovalForm, processMilestoneDecisionForm, LifeCycleMilestoneDao.getLCMilestoneInstanceStatusTypeActiveAsVH())); } /** * Process a vote for a milestone instance. */ @Restrict({ @Group(IMafConstants.MILESTONE_APPROVAL_PERMISSION), @Group(IMafConstants.MILESTONE_DECIDE_PERMISSION) }) public Result vote() { Form<ProcessMilestoneApprovalFormData> boundForm = processMilestoneApprovalFormTemplate.bindFromRequest(); // get the approver instance Long approverInstanceId = Long.valueOf(boundForm.data().get("approverInstanceId")); LifeCycleMilestoneInstanceApprover approverInstance = LifeCycleMilestoneDao .getLCMilestoneInstanceApproverById(approverInstanceId); if (boundForm.hasErrors()) { // this case is not possible return redirect(controllers.core.routes.MilestoneApprovalController .process(approverInstance.lifeCycleMilestoneInstance.id)); } // get the form values ProcessMilestoneApprovalFormData processMilestoneApprovalFormData = boundForm.get(); // register the vote processMilestoneApprovalFormData.fill(approverInstance); approverInstance.save(); // if last vote, send notification to all deciders (has the permission // MILESTONE_DECIDE_PERMISSION) Long milestoneInstanceId = approverInstance.lifeCycleMilestoneInstance.id; if (LifeCycleMilestoneDao.hasLCMilestoneInstanceAllApproversVoted(milestoneInstanceId)) { PortfolioEntry portfolioEntry = approverInstance.lifeCycleMilestoneInstance.lifeCycleInstance.portfolioEntry; String url = controllers.core.routes.MilestoneApprovalController.process(milestoneInstanceId).url(); List<Principal> principals = Principal.find.where().eq("deleted", false) .eq("systemLevelRoles.isEnabled", true) .eq("systemLevelRoles.systemLevelRoleType.deleted", false) .eq("systemLevelRoles.systemLevelRoleType.selectable", true) .eq("systemLevelRoles.systemLevelRoleType.systemPermissions.deleted", false) .eq("systemLevelRoles.systemLevelRoleType.systemPermissions.selectable", true) .eq("systemLevelRoles.systemLevelRoleType.systemPermissions.name", IMafConstants.MILESTONE_DECIDE_PERMISSION) .findList(); for (Principal principal : principals) { ActorDao.sendNotification(getNotificationManagerService(), getI18nMessagesPlugin(), principal.uid, NotificationCategory.getByCode(Code.APPROVAL), url, "core.milestone.approval.process.panel.vote.decisionrequired.notification.title", "core.milestone.approval.process.panel.vote.decisionrequired.notification.message", portfolioEntry.getName()); } } // success message Utilities.sendSuccessFlashMessage(Msg.get("core.milestone.approval.process.panel.vote.successfull")); return redirect(controllers.core.routes.MilestoneApprovalController.list(0)); } /** * Process a decision. */ @Restrict({ @Group(IMafConstants.MILESTONE_DECIDE_PERMISSION) }) public Result decide() { Form<ProcessMilestoneDecisionFormData> boundForm = processMilestoneDecisionFormTemplate.bindFromRequest(); // get the milestone instance Long milestoneInstanceId = Long.valueOf(boundForm.data().get("milestoneInstanceId")); LifeCycleMilestoneInstance milestoneInstance = LifeCycleMilestoneDao .getLCMilestoneInstanceById(milestoneInstanceId); if (boundForm.hasErrors()) { // this case is possible only if the user empty the date return redirect(controllers.core.routes.MilestoneApprovalController.process(milestoneInstance.id)); } // get the form values ProcessMilestoneDecisionFormData processMilestoneDecisionFormData = boundForm.get(); Ebean.beginTransaction(); try { LifeCycleMilestoneInstanceStatusType status = LifeCycleMilestoneDao .getLCMilestoneInstanceStatusTypeById( processMilestoneDecisionFormData.lifeCycleMilestoneInstanceStatusType); milestoneInstance = LifeCycleMilestoneDao.doPassed(milestoneInstance.id, status, processMilestoneDecisionFormData.comments, this.getBudgetTrackingService()); // save the passed date milestoneInstance.passedDate = Utilities.getDateFormat(null) .parse(processMilestoneDecisionFormData.passedDate); milestoneInstance.save(); // set the current actor as approver (if exists) Actor actor = ActorDao.getActorByUid(getUserSessionManagerPlugin().getUserSessionId(ctx())); if (actor != null) { milestoneInstance.approver = actor; milestoneInstance.save(); } Ebean.commitTransaction(); // notification PortfolioEntry portfolioEntry = milestoneInstance.lifeCycleInstance.portfolioEntry; String url = controllers.core.routes.PortfolioEntryGovernanceController.index(portfolioEntry.id).url(); if (status.isApproved) { ActorDao.sendNotification(getNotificationManagerService(), getI18nMessagesPlugin(), portfolioEntry.manager, NotificationCategory.getByCode(Code.APPROVAL), url, "core.milestone.approval.process.panel.decide.approve.notification.title", "core.milestone.approval.process.panel.decide.approve.notification.message", portfolioEntry.getName()); } else { ActorDao.sendNotification(getNotificationManagerService(), getI18nMessagesPlugin(), portfolioEntry.manager, NotificationCategory.getByCode(Code.APPROVAL), url, "core.milestone.approval.process.panel.decide.reject.notification.title", "core.milestone.approval.process.panel.decide.reject.notification.message", portfolioEntry.getName()); } } catch (Exception e) { Ebean.rollbackTransaction(); return ControllersUtils.logAndReturnUnexpectedError(e, log, getConfiguration(), getI18nMessagesPlugin()); } // success message Utilities.sendSuccessFlashMessage(Msg.get("core.milestone.approval.process.panel.decide.successfull")); return redirect(controllers.core.routes.MilestoneApprovalController.list(0)); } /** * Delete the milestone instance. * * @param milestoneInstanceId * the milestone instance id */ @Restrict({ @Group(IMafConstants.MILESTONE_DECIDE_PERMISSION) }) public Result delete(Long milestoneInstanceId) { LifeCycleMilestoneInstance milestoneInstance = LifeCycleMilestoneDao .getLCMilestoneInstanceById(milestoneInstanceId); milestoneInstance.doDelete(); Utilities .sendSuccessFlashMessage(Msg.get("core.milestone.approval.process.panel.decide.delete.successful")); return redirect(controllers.core.routes.MilestoneApprovalController.list(0)); } /** * A milestone instance event represents an entry (for the display) in the * milestones' planning. * * @author Johann Kohler */ public static class MilestoneInstanceEvent { /** * The milestone instance id. */ public Long id; /** * The title, that is composed by:<br/> * "PE governanceId - PE name: milestone short name - status". If * governanceId is null, simply "PE name: ..." */ public String title; /** * The modal URL. */ public String url; /** * The URL to the governance page of the corresponding portfolio entry. */ public String goToUrl; /** * The CSS class for the status.<br/> * event-success: passed<br/> * event-important: rejected<br/> * event-warning: pending<br/> * event-info: unknown */ public String statusClass; /** * The start date. */ public Date start; /** * The end date. */ public Date end; /** * Construct an event thanks a milestone instance in the DB. * * @param milestoneInstance * the milestone instance */ public MilestoneInstanceEvent(LifeCycleMilestoneInstance milestoneInstance) { PortfolioEntry portfolioEntry = milestoneInstance.lifeCycleInstance.portfolioEntry; LifeCycleMilestone milestone = milestoneInstance.lifeCycleMilestone; // compute the status String statusLabel = null; String statusClass = null; switch (milestoneInstance.getStatus()) { case APPROVED: statusLabel = milestoneInstance.lifeCycleMilestoneInstanceStatusType.getName(); statusClass = "event-success"; break; case PENDING: statusLabel = Msg.get( "object.life_cycle_milestone_instance.status." + milestoneInstance.getStatus() + ".label"); statusClass = "event-warning"; break; case REJECTED: statusLabel = milestoneInstance.lifeCycleMilestoneInstanceStatusType.getName(); statusClass = "event-important"; break; case UNKNOWN: statusLabel = Msg.get( "object.life_cycle_milestone_instance.status." + milestoneInstance.getStatus() + ".label"); statusClass = "event-info"; break; } this.id = milestoneInstance.id; this.title = portfolioEntry.getName() + " - " + milestone.getShortName() + " - " + statusLabel; this.url = controllers.core.routes.MilestoneApprovalController.overviewModal(milestoneInstance.id) .url(); this.goToUrl = controllers.core.routes.PortfolioEntryGovernanceController .viewMilestone(portfolioEntry.id, milestoneInstance.lifeCycleMilestone.id).url(); this.statusClass = statusClass; this.start = milestoneInstance.passedDate; this.end = milestoneInstance.passedDate; } } /** * Get the use session manager service. */ private IUserSessionManagerPlugin getUserSessionManagerPlugin() { return userSessionManagerPlugin; } /** * Get the account manager service. */ private IAccountManagerPlugin getAccountManagerPlugin() { return accountManagerPlugin; } /** * Get the attachment plugin service. */ private IAttachmentManagerPlugin getAttachmentPluginManager() { return attachmentPluginManager; } /** * Get the security service. */ private ISecurityService getSecurityService() { return securityService; } /** * Get the i18n messages service. */ private II18nMessagesPlugin getI18nMessagesPlugin() { return i18nMessagesPlugin; } /** * Get the Play configuration service. */ private Configuration getConfiguration() { return configuration; } /** * Get the budget tracking service. */ private IBudgetTrackingService getBudgetTrackingService() { return this.budgetTrackingService; } /** * Get the notification manager service. */ private INotificationManagerPlugin getNotificationManagerService() { return this.notificationManagerService; } /** * Get the table provider. */ private ITableProvider getTableProvider() { return this.tableProvider; } /** * Get the preference manager service. */ private IPreferenceManagerPlugin getPreferenceManagerPlugin() { return this.preferenceManagerPlugin; } }