package org.sigmah.server.handler.util; /* * #%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 com.google.inject.Inject; import java.util.ArrayList; import java.util.List; import java.util.Map; import javax.persistence.NoResultException; import javax.persistence.TypedQuery; import org.sigmah.client.util.ClientUtils; import org.sigmah.server.dao.base.EntityManagerProvider; import org.sigmah.server.domain.Phase; import org.sigmah.server.domain.Project; import org.sigmah.server.domain.User; import org.sigmah.server.domain.element.FilesListElement; import org.sigmah.server.domain.value.File; import org.sigmah.server.domain.value.Value; import org.sigmah.server.i18n.I18nServer; import org.sigmah.shared.Language; import org.sigmah.shared.dispatch.UpdateConflictException; import org.sigmah.shared.dto.referential.AmendmentState; import org.sigmah.shared.dto.referential.GlobalPermissionEnum; import org.sigmah.shared.dto.value.FileUploadUtils; import org.sigmah.shared.util.ValueResultUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Utility class that centralize search for conflicts. * * @author Raphaƫl Calabro (rcalabro@ideia.fr) */ public class Conflicts extends EntityManagerProvider { private static final Logger LOGGER = LoggerFactory.getLogger(Conflicts.class); @Inject private I18nServer i18nServer; /** * Identify if the phase containing the given element is closed. * * @param elementId * Identifier of a flexible element. * @param projectId * Identifier of the project. * @return <code>true</code> if the parent phase is closed, * <code>false</code> if the parent phase is opened. */ public boolean isParentPhaseClosed(int elementId, int projectId) { // Retrieves the parent phase only if it exists and has been closed. final TypedQuery<Phase> query = em().createQuery("SELECT p FROM " + "Phase p " + "JOIN p.phaseModel.layout.groups as lg " + "JOIN lg.constraints as lc " + "WHERE p.endDate is not null " + "AND :projectId = p.parentProject.id " + "AND :elementId = lc.element.id", Phase.class); query.setParameter("projectId", projectId); query.setParameter("elementId", elementId); return !query.getResultList().isEmpty(); } // File conflicts /** * Find if a conflict will happen when adding a file. * * @param properties * Properties of the file flexible element. * @param language * Language of the message. * @param user * User. * @throws UpdateConflictException If a conflict has been detected. */ public void searchForFileAddConflicts(final Map<String, String> properties, final Language language, final User user) throws UpdateConflictException { // Element. final int elementId = ClientUtils.asInt(properties.get(FileUploadUtils.DOCUMENT_FLEXIBLE_ELEMENT), -1); final FilesListElement filesListElement = elementId != -1 ? em().find(FilesListElement.class, elementId) : null; // Project. final int projectId = ClientUtils.asInt(properties.get(FileUploadUtils.DOCUMENT_PROJECT), 0); final Project project = em().find(Project.class, projectId); if (project != null && !Handlers.isGranted(user.getOrgUnitsWithProfiles(), project.getOrgUnit(), GlobalPermissionEnum.MODIFY_LOCKED_CONTENT)) { if(project.getCloseDate() != null) { final String fileName = ValueResultUtils.normalizeFileName(properties.get(FileUploadUtils.DOCUMENT_NAME)); throw new UpdateConflictException(project.toContainerInformation(), true, i18nServer.t(language, "conflictAddingFileToAClosedProject", fileName, filesListElement.getLabel())); } if(isParentPhaseClosed(elementId, projectId)) { final String fileName = ValueResultUtils.normalizeFileName(properties.get(FileUploadUtils.DOCUMENT_NAME)); throw new UpdateConflictException(project.toContainerInformation(), true, i18nServer.t(language, "conflictAddingFileToAClosedPhase", fileName, filesListElement.getLabel())); } if(filesListElement != null && filesListElement.isAmendable() && project.getAmendmentState() == AmendmentState.LOCKED) { final String fileName = ValueResultUtils.normalizeFileName(properties.get(FileUploadUtils.DOCUMENT_NAME)); throw new UpdateConflictException(project.toContainerInformation(), true, i18nServer.t(language, "conflictAddingFileToALockedField", fileName, filesListElement.getLabel())); } } final boolean isAdd = ClientUtils.asInt(properties.get(FileUploadUtils.DOCUMENT_ID)) == null; // For add operation, checking if adding a file does not break the limit. if (isAdd && filesListElement != null && filesListElement.getLimit() != null) { // Retrieving the current value final TypedQuery<Value> valueQuery = em().createQuery("SELECT v FROM Value v WHERE v.containerId = :projectId and v.element.id = :elementId", Value.class); valueQuery.setParameter("projectId", projectId); valueQuery.setParameter("elementId", elementId); Value currentValue = null; try { currentValue = valueQuery.getSingleResult(); } catch (NoResultException nre) { // No current value } if (currentValue != null) { final TypedQuery<File> fileQuery = em().createQuery("SELECT f FROM File f WHERE f.id IN (:idsList)", File.class); fileQuery.setParameter("idsList", ValueResultUtils.splitValuesAsInteger(currentValue.getValue())); final List<File> files = fileQuery.getResultList(); if (files.size() >= filesListElement.getLimit()) { final String fileName = ValueResultUtils.normalizeFileName(properties.get(FileUploadUtils.DOCUMENT_NAME)); throw new UpdateConflictException(project.toContainerInformation(), true, i18nServer.t(language, "conflictAddingFileToAFullFileField", fileName, filesListElement.getLabel())); } } } } /** * Find if a conflict will happen when deleting a file. * * @param file * File to delete. * @param language * Language of the message. * @param user * User. * @throws UpdateConflictException If a conflict has been detected. */ public void searchForFileDeleteConflicts(final File file, final Language language, final User user) throws UpdateConflictException { final Project project = getParentProjectOfFile(file); if (Handlers.isGranted(user.getOrgUnitsWithProfiles(), project.getOrgUnit(), GlobalPermissionEnum.MODIFY_LOCKED_CONTENT)) { return; } final FilesListElement filesListElement = getParentFilesListElement(file); if (filesListElement != null && project != null) { if(project.getCloseDate() != null) { throw new UpdateConflictException(project.toContainerInformation(), i18nServer.t(language, "conflictRemovingFileFromAClosedProject", filesListElement.getLabel())); } else if(isParentPhaseClosed(filesListElement.getId(), project.getId())) { throw new UpdateConflictException(project.toContainerInformation(), i18nServer.t(language, "conflictRemovingFileFromAClosedPhase", filesListElement.getLabel())); } else if(project.getAmendmentState() == AmendmentState.LOCKED && filesListElement.isAmendable()) { throw new UpdateConflictException(project.toContainerInformation(), i18nServer.t(language, "conflictRemovingFileFromALockedField", filesListElement.getLabel())); } } } // File search method. /** * Find the project containing the given file. * * @param file * File contained in a file flexible element. * @return The parent project. */ public Project getParentProjectOfFile(final File file) { final TypedQuery<Project> phaseQuery = em().createQuery("SELECT p FROM " + "Value v, " + "Project p " + "WHERE v.containerId = p.id " + "AND (v.value = :fileId OR v.value like :fileIdLeft OR v.value like :fileIdRight OR v.value like :fileIdCenter)", Project.class); final TypedQuery<Project> detailsQuery = em().createQuery("SELECT p FROM " + "Value v, " + "Project p " + "WHERE v.containerId = p.id " + "AND (v.value = :fileId OR v.value like :fileIdLeft OR v.value like :fileIdRight OR v.value like :fileIdCenter)", Project.class); setFileQueryParameters(phaseQuery, file.getId()); setFileQueryParameters(detailsQuery, file.getId()); final ArrayList<Project> projects = new ArrayList<>(); projects.addAll(phaseQuery.getResultList()); projects.addAll(detailsQuery.getResultList()); if(!projects.isEmpty()) { if(projects.size() > 1) { LOGGER.warn("{} projects have been found while searching for the parent of file {}.", projects.size(), file.getId()); } return projects.get(0); } return null; } /** * Find the FilesListElement containing the given file. * * @param file * File contained in a file flexible element. * @return The parent flexible element. */ public FilesListElement getParentFilesListElement(final File file) { final TypedQuery<FilesListElement> query = em().createQuery("SELECT fle FROM " + "Value v, " + "FilesListElement fle " + "WHERE v.element = fle " + "AND (v.value = :fileId OR v.value like :fileIdLeft OR v.value like :fileIdRight OR v.value like :fileIdCenter)", FilesListElement.class); setFileQueryParameters(query, file.getId()); final List<FilesListElement> elements = query.getResultList(); if(!elements.isEmpty()) { if(elements.size() > 1) { LOGGER.warn("{} elements have been found while searching for the parent of file {}.", elements.size(), file.getId()); } return elements.get(0); } else { return null; } } /** * Affect the parameters of the given query to search for the given file. * * @param query * Query to configure. * @param fileId * Identifier of the file. */ private void setFileQueryParameters(TypedQuery<?> query, Integer fileId) { query.setParameter("fileId", fileId.toString()); query.setParameter("fileIdLeft", '%' + ValueResultUtils.DEFAULT_VALUE_SEPARATOR + fileId); query.setParameter("fileIdRight", fileId + ValueResultUtils.DEFAULT_VALUE_SEPARATOR + '%'); query.setParameter("fileIdCenter", '%' + ValueResultUtils.DEFAULT_VALUE_SEPARATOR + fileId + ValueResultUtils.DEFAULT_VALUE_SEPARATOR + '%'); } }