package org.sigmah.server.service; /* * #%L * Sigmah * %% * Copyright (C) 2010 - 2016 URD * %% * 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 * <http://www.gnu.org/licenses/gpl-3.0.html>. * #L% */ import java.util.*; import javax.persistence.NoResultException; import javax.persistence.Query; import com.google.inject.Inject; import com.google.inject.Singleton; import org.sigmah.client.ui.presenter.CreateProjectPresenter; import org.sigmah.client.ui.presenter.project.logframe.ProjectLogFramePresenter; import org.sigmah.server.dispatch.impl.UserDispatch.UserExecutionContext; import org.sigmah.server.domain.*; import org.sigmah.server.domain.calendar.PersonalCalendar; import org.sigmah.server.domain.element.BudgetElement; import org.sigmah.server.domain.element.BudgetRatioElement; import org.sigmah.server.domain.element.DefaultFlexibleElement; import org.sigmah.server.domain.element.FlexibleElement; import org.sigmah.server.domain.layout.LayoutConstraint; import org.sigmah.server.domain.layout.LayoutGroup; import org.sigmah.server.domain.logframe.LogFrame; import org.sigmah.server.domain.logframe.LogFrameGroup; import org.sigmah.server.domain.logframe.LogFrameModel; import org.sigmah.server.domain.profile.Profile; import org.sigmah.server.domain.reminder.MonitoredPointList; import org.sigmah.server.domain.reminder.ReminderList; import org.sigmah.server.domain.value.Value; import org.sigmah.server.handler.util.ProjectMapper; import org.sigmah.server.service.base.AbstractEntityService; import org.sigmah.server.service.util.PropertyMap; import org.sigmah.shared.dispatch.CommandException; import org.sigmah.shared.dto.ProjectDTO; import org.sigmah.shared.dto.ProjectFundingDTO; import org.sigmah.shared.dto.base.EntityDTO; import org.sigmah.shared.dto.referential.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * {@link Project} service. * * @author Alexander (v1.3) * @author Denis Colliot (dcolliot@ideia.fr) (v2.0) */ @Singleton public class ProjectService extends AbstractEntityService<Project, Integer, ProjectDTO> { /** * Logger. */ private static final Logger LOG = LoggerFactory.getLogger(ProjectService.class); /** * Project mapper. */ @Inject private ProjectMapper projectMapper; /** * Project funding service. */ @Inject private ProjectFundingService fundingService; /** * Service handling the update of Value objects. */ @Inject private ValueService valueService; /** * {@inheritDoc} */ @Override public Project create(final PropertyMap properties, final UserExecutionContext context) { if (LOG.isDebugEnabled()) { LOG.debug("Starting project creation for properties: {}.", properties); } final User user = context.getUser(); // Creates a new calendar PersonalCalendar calendar = new PersonalCalendar(); calendar.setName(properties.<String> get("calendarName")); em().persist(calendar); // Creates the new project Project project = new Project(); // Userdatabase attributes. project.setStartDate(new Date()); final User owner = em().getReference(User.class, user.getId()); project.setOwner(owner); // Manager. // The default manager is the owner of the project. project.setManager(owner); // Monitored points. project.setPointsList(new MonitoredPointList()); // Reminders. project.setRemindersList(new ReminderList()); OrgUnit orgunit = null; // No organizational unit for the testProjects if (properties.get(ProjectDTO.ORG_UNIT_ID) != null) { // Org unit. int orgUnitId = Integer.parseInt("" + properties.get(ProjectDTO.ORG_UNIT_ID)); orgunit = em().find(OrgUnit.class, orgUnitId); project.getPartners().add(orgunit); } // Country final Country country = getProjectCountry(orgunit); project.setCountry(country); // Amendment project.setAmendmentState(AmendmentState.DRAFT); project.setAmendmentVersion(1); project.setAmendmentRevision(1); if (LOG.isDebugEnabled()) { LOG.debug("[createProject] Selected country: " + country.getName() + "."); } // Considers name length constraints. final String name = properties.<String> get(ProjectDTO.NAME); if (name != null) { if (name.length() > 50) { project.setName(name.substring(0, 50)); } else { project.setName(name); } } else { project.setName("noname"); } // Considers name length constraints. final String fullName = properties.<String> get(ProjectDTO.FULL_NAME); if (fullName != null) { if (fullName.length() > 500) { project.setFullName(fullName.substring(0, 500)); } else { project.setFullName(fullName); } } else { project.setFullName(""); } project.setLastSchemaUpdate(new Date()); project.setCalendarId(calendar.getId()); // Project attributes. ProjectModel model = em().getReference(ProjectModel.class, properties.<Integer> get("modelId")); if (ProjectModelStatus.READY.equals(model.getStatus())) { model.setStatus(ProjectModelStatus.USED); } model = em().merge(model); project.setProjectModel(model); project.setLogFrame(null); // Let's add default team member profiles List<Profile> defaultTeamMemberProfiles = new ArrayList<>(model.getDefaultTeamMemberProfiles()); project.setTeamMemberProfiles(defaultTeamMemberProfiles); // Creates and adds phases. for (final PhaseModel phaseModel : model.getPhaseModels()) { final Phase phase = new Phase(); phase.setPhaseModel(phaseModel); project.addPhase(phase); if (LOG.isDebugEnabled()) { LOG.debug("[createProject] Creates and adds phase instance for model: " + phaseModel.getName() + "."); } // Searches the root phase. if (model.getRootPhaseModel() != null && phaseModel.getId().equals(model.getRootPhaseModel().getId())) { // Sets it. phase.setStartDate(new Date()); project.setCurrentPhase(phase); if (LOG.isDebugEnabled()) { LOG.debug("[createProject] Sets the first phase: " + phaseModel.getName() + "."); } } } // The model doesn't define a root phase, select the first declared // phase as the first one. if (model.getRootPhaseModel() == null) { if (LOG.isDebugEnabled()) { LOG.debug("[createProject] No root phase defined for this model, active the first phase by default."); } // Selects the first phase by default. final Phase phase = project.getPhases().get(0); // Sets it. phase.setStartDate(new Date()); project.setCurrentPhase(phase); if (LOG.isDebugEnabled()) { LOG.debug("[createProject] Sets the first phase: " + phase.getPhaseModel().getName() + "."); } } em().persist(project); if (LOG.isDebugEnabled()) { LOG.debug("[createProject] Project successfully created."); } // Updates the project with a default log frame. final LogFrame logFrame = createDefaultLogFrame(project); project.setLogFrame(logFrame); // Updates the project project = em().merge(project); // Create initials history tokens // BUGFIX #729 & #784 // NOTE: initial history token for budget value is created by the value service. createInitialHistoryTokens(project, null, user); // Create links (if requested) final ProjectDTO baseProject = properties.get(ProjectDTO.BASE_PROJECT); if(baseProject != null) { final CreateProjectPresenter.Mode mode = properties.get(ProjectDTO.CREATION_MODE); if(mode == CreateProjectPresenter.Mode.FUNDING_ANOTHER_PROJECT) { // Sets the funding parameters. final Map<String, Object> parameters = new HashMap<String, Object>(); parameters.put(ProjectFundingDTO.FUNDING_ID, project.getId()); parameters.put(ProjectFundingDTO.FUNDED_ID, baseProject.getId()); parameters.put(ProjectFundingDTO.PERCENTAGE, properties.get(ProjectDTO.AMOUNT)); final ProjectFunding funding = fundingService.create(new PropertyMap(parameters), context); if(project.getFunding() == null) { project.setFunding(new ArrayList<ProjectFunding>()); } project.getFunding().add(funding); } else if(mode == CreateProjectPresenter.Mode.FUNDED_BY_ANOTHER_PROJECT) { // Sets the funding parameters. final Map<String, Object> parameters = new HashMap<String, Object>(); parameters.put(ProjectFundingDTO.FUNDING_ID, baseProject.getId()); parameters.put(ProjectFundingDTO.FUNDED_ID, project.getId()); parameters.put(ProjectFundingDTO.PERCENTAGE, properties.get(ProjectDTO.AMOUNT)); final ProjectFunding funded = fundingService.create(new PropertyMap(parameters), context); if(project.getFunded() == null) { project.setFunded(new ArrayList<ProjectFunding>()); } project.getFunded().add(funded); } } // Create the budget values final Double budgetPlanned = properties.<Double> get(ProjectDTO.BUDGET); if (budgetPlanned != null) { createPlannedBudgetValue(budgetPlanned, model, project, user); } return project; } /** * Create the planned budget initial value. * * @param budgetPlanned * Planned budget. <code>null</code> value will result in a <code>NullPointerException</code>. * @param model * Project model to read. * @param project * Project to edit. * @param user * User creating the project. * @return The saved value. */ private String createPlannedBudgetValue(final Double budgetPlanned, final ProjectModel model, final Project project, final User user) { final BudgetRatioElement budgetRatioElement = model.getFirstElementOfType(BudgetRatioElement.class); if (budgetRatioElement == null || budgetRatioElement.getPlannedBudget() == null) { return null; } final String budgetValue = budgetPlanned.toString(); valueService.saveValue(budgetValue, new Date(), budgetRatioElement.getPlannedBudget(), project.getId(), null, user, "Initial budget"); return budgetValue; } /** * Creates a well-configured default log frame for the new project. * * @param project * The project. * @return The log frame. */ private LogFrame createDefaultLogFrame(Project project) { // Creates a new log frame (with a default model) final LogFrame logFrame = new LogFrame(); logFrame.setParentProject(project); // Default groups. final ArrayList<LogFrameGroup> groups = new ArrayList<LogFrameGroup>(); LogFrameGroup group = new LogFrameGroup(); group.setType(LogFrameGroupType.SPECIFIC_OBJECTIVE); group.setParentLogFrame(logFrame); group.setLabel(ProjectLogFramePresenter.DEFAULT_GROUP_LABEL); groups.add(group); group = new LogFrameGroup(); group.setType(LogFrameGroupType.EXPECTED_RESULT); group.setParentLogFrame(logFrame); group.setLabel(ProjectLogFramePresenter.DEFAULT_GROUP_LABEL); groups.add(group); group = new LogFrameGroup(); group.setType(LogFrameGroupType.ACTIVITY); group.setParentLogFrame(logFrame); group.setLabel(ProjectLogFramePresenter.DEFAULT_GROUP_LABEL); groups.add(group); group = new LogFrameGroup(); group.setType(LogFrameGroupType.PREREQUISITE); group.setParentLogFrame(logFrame); group.setLabel(ProjectLogFramePresenter.DEFAULT_GROUP_LABEL); groups.add(group); logFrame.setGroups(groups); // Links to the log frame model. ProjectModel projectModel = project.getProjectModel(); LogFrameModel logFrameModel = projectModel.getLogFrameModel(); if (logFrameModel == null) { logFrameModel = ProjectModelService.createDefaultLogFrameModel(projectModel); logFrameModel.setName("Auto-created default model at " + new Date()); em().persist(logFrameModel); projectModel.setLogFrameModel(logFrameModel); em().merge(projectModel); } logFrame.setLogFrameModel(logFrameModel); em().persist(logFrame); return logFrame; } /** * Creates an history token for each default flexible element. * * @param project * Created project. * @param budgetValue * Budget value (can be <code>null</code>). * @param user * User creating the project. */ private void createInitialHistoryTokens(final Project project, final String budgetValue, final User user) { for (final DefaultFlexibleElement element : getDefaultElements(project)) { final Integer elementId; final String value; if (element instanceof BudgetRatioElement) { // Created by value service in createPlannedBudgetValue. continue; } else { value = element.getValue(project); elementId = element.getId(); } if (value != null && elementId != null) { final HistoryToken historyToken = new HistoryToken(); historyToken.setDate(new Date()); historyToken.setElementId(elementId); historyToken.setProjectId(project.getId()); historyToken.setType(ValueEventChangeType.ADD); historyToken.setUser(user); historyToken.setValue(value); em().persist(historyToken); } } } /** * Find every default element contained in the model of the given project. * * @param project Project to search. * @return A set of every default flexible element. */ private Set<DefaultFlexibleElement> getDefaultElements(Project project) { final Set<DefaultFlexibleElement> defaultElements = new HashSet<>(); for(final LayoutGroup layoutGroup : project.getProjectModel().getProjectDetails().getLayout().getGroups()) { getDefaultElements(layoutGroup, defaultElements); } for(final PhaseModel phaseModel : project.getProjectModel().getPhaseModels()) { for(final LayoutGroup layoutGroup : phaseModel.getLayout().getGroups()) { getDefaultElements(layoutGroup, defaultElements); } } return defaultElements; } /** * Find every default flexible elements in the given layout group and add * them to the given set. * * @param layoutGroup Group tu search. * @param defaultElements Set to fill. */ private void getDefaultElements(LayoutGroup layoutGroup, Set<DefaultFlexibleElement> defaultElements) { for(final LayoutConstraint constraint : layoutGroup.getConstraints()) { if(constraint.getElement() instanceof DefaultFlexibleElement) { defaultElements.add((DefaultFlexibleElement) constraint.getElement()); } } } /** * Searches the country for the given org unit. * * @param orgUnit * The org unit. * @return The country */ private Country getProjectCountry(OrgUnit orgUnit) { if (orgUnit == null) { return getDefaultCountry(); } Country country = null; OrgUnit current = orgUnit; while (country == null || current != null) { if ((country = current.getOfficeLocationCountry()) != null) { return country; } else { current = current.getParentOrgUnit(); } // Root reached if (current == null) { break; } } return getDefaultCountry(); } /** * Gets the default country for all the application. * * @return The default country. */ private Country getDefaultCountry() { final Query q = em().createQuery("SELECT c FROM Country c WHERE c.name = :defaultName"); // FIXME France by default q.setParameter("defaultName", Country.DEFAULT_COUNTRY_NAME); try { return (Country) q.getSingleResult(); } catch (NoResultException e) { try { return (Country) em().createQuery("SELECT c FROM Country c").getResultList().get(0); } catch (Throwable e2) { throw new IllegalStateException("There is no country in database, unable to create a project.", e2); } } } /** * Find the budget element of the given model. * * @param model Model to search. * @return An instance of <code>BudgetElement</code> or <code>null</code> if * none was found. */ private BudgetElement getBudgetElement(ProjectModel model) { BudgetElement budgetElement = null; if (model.getProjectBanner().getLayout() != null) { for (LayoutGroup lg : model.getProjectBanner().getLayout().getGroups()) { for (LayoutConstraint lc : lg.getConstraints()) { if (lc.getElement() instanceof BudgetElement) { budgetElement = (BudgetElement) lc.getElement(); } } } } if (model.getProjectDetails().getLayout() != null) { for (LayoutGroup lg : model.getProjectDetails().getLayout().getGroups()) { for (LayoutConstraint lc : lg.getConstraints()) { if (lc.getElement() instanceof BudgetElement) { budgetElement = (BudgetElement) lc.getElement(); } } } } for (PhaseModel phase : model.getPhaseModels()) { for (LayoutGroup lg : phase.getLayout().getGroups()) { for (LayoutConstraint lc : lg.getConstraints()) { if (lc.getElement() instanceof BudgetElement) { budgetElement = (BudgetElement) lc.getElement(); } } } } return budgetElement; } /** * {@inheritDoc} */ @Override public Project update(final Integer entityId, final PropertyMap changes, final UserExecutionContext context) { for (Map.Entry<String, Object> entry : changes.entrySet()) { if ("fundingId".equals(entry.getKey())) { // Get the current project Project project = em().find(Project.class, entityId); // Get the project funding relation entity object ProjectFunding projectFunding = em().find(ProjectFunding.class, entry.getValue()); // Remove it from the current project project.getFunding().remove(projectFunding); // Save em().merge(project); em().remove(projectFunding); } else if ("fundedId".equals(entry.getKey())) { // Get the current project Project project = em().find(Project.class, entityId); // Get the project funding relation entity object ProjectFunding projectFunding = em().find(ProjectFunding.class, entry.getValue()); // Remove it from the current project project.getFunded().remove(projectFunding); // Save em().merge(project); em().remove(projectFunding); } else if ("dateDeleted".equals(entry.getKey())) { // Get the current project UserDatabase project = em().find(UserDatabase.class, entityId); // Mark the project in the state "deleted" (but don't delete it // really) project.delete(); final List<ProjectFunding> listfundingsToDelete = new ArrayList<ProjectFunding>(); // Saves all the projectFundings that need to be deleted // before deleting them from the deleted project if (project instanceof Project) { Project pr = (Project) project; listfundingsToDelete.addAll(pr.getFunded()); listfundingsToDelete.addAll(pr.getFunding()); ((Project) project).getFunded().clear(); ((Project) project).getFunding().clear(); } // Save em().merge(project); for (ProjectFunding pf : listfundingsToDelete) { em().remove(pf); } } } return em().find(Project.class, entityId); } /** * {@inheritDoc} */ @Override protected EntityDTO<?> handleMapping(final Project createdProject) throws CommandException { final ProjectDTO mappedProject = projectMapper.map(createdProject, false); mappedProject.setSpendBudget(0.0); mappedProject.setReceivedBudget(0.0); return mappedProject; } }