/* * This file is part of LibrePlan * * Copyright (C) 2009-2010 Fundación para o Fomento da Calidade Industrial e * Desenvolvemento Tecnolóxico de Galicia * Copyright (C) 2010-2011 Igalia, S.L. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.libreplan.business.planner.daos; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.regex.Pattern; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.hibernate.Query; import org.hibernate.Session; import org.joda.time.LocalDate; import org.libreplan.business.common.daos.GenericDAOHibernate; import org.libreplan.business.planner.entities.GenericResourceAllocation; import org.libreplan.business.planner.entities.ResourceAllocation; import org.libreplan.business.planner.entities.SpecificResourceAllocation; import org.libreplan.business.resources.entities.Criterion; import org.libreplan.business.resources.entities.Resource; import org.libreplan.business.scenarios.entities.Scenario; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Repository; /** * DAO for {@link ResourceAllocation}. * * @author Manuel Rego Casasnovas <mrego@igalia.com> */ @Repository @Scope(BeanDefinition.SCOPE_SINGLETON) public class ResourceAllocationDAO extends GenericDAOHibernate<ResourceAllocation, Long> implements IResourceAllocationDAO { @Override public List<ResourceAllocation<?>> findAllocationsRelatedToAnyOf(Scenario onScenario, List<Resource> resources) { List<ResourceAllocation<?>> result = new ArrayList<>(); result.addAll(findSpecificAllocationsRelatedTo(onScenario, resources, null, null)); result.addAll(findGenericAllocationsFor(onScenario, resources, null, null)); return result; } @Override public List<ResourceAllocation<?>> findAllocationsRelatedToAnyOf(Scenario onScenario, List<Resource> resources, LocalDate intervalFilterStartDate, LocalDate intervalFilterEndDate) { List<ResourceAllocation<?>> result = new ArrayList<>(); result.addAll(findSpecificAllocationsRelatedTo(onScenario, resources, intervalFilterStartDate, intervalFilterEndDate)); result.addAll(findGenericAllocationsFor(onScenario, resources, intervalFilterStartDate, intervalFilterEndDate)); return result; } @SuppressWarnings("unchecked") private List<GenericResourceAllocation> findGenericAllocationsFor( final Scenario onScenario, final List<Resource> resources, final LocalDate intervalFilterStartDate, final LocalDate intervalFilterEndDate) { if (resources.isEmpty()) { return new ArrayList<>(); } QueryBuilder queryBuilder = new QueryBuilder() { @Override protected String getBaseQuery() { return "select distinct generic from GenericResourceAllocation generic " + "join generic.task task " + "join generic.genericDayAssignmentsContainers container " + "join container.dayAssignments dayAssignment"; } @Override protected String getBaseConditions() { return "where dayAssignment.resource in (:resources)"; } @Override protected void setBaseParameters(Query query) { query.setParameterList("resources", resources); } @Override protected IQueryPart[] getExtraParts() { return new IQueryPart[] { new DatesInterval("task", intervalFilterStartDate, intervalFilterEndDate), new OnScenario("task", onScenario) }; } }; return queryBuilder.build(getSession()).list(); } @Override @SuppressWarnings("unchecked") public List<SpecificResourceAllocation> findSpecificAllocationsRelatedTo( final Scenario onScenario, final List<Resource> resources, final LocalDate intervalFilterStartDate, final LocalDate intervalFilterEndDate) { if (resources.isEmpty()) { return new ArrayList<>(); } QueryBuilder queryBuilder = new QueryBuilder() { @Override protected String getBaseQuery() { return "select distinct specific from " + "SpecificResourceAllocation specific " + "join specific.task task"; } @Override protected String getBaseConditions() { return "where specific.resource in (:resources)"; } @Override protected void setBaseParameters(Query query) { query.setParameterList("resources", resources); } @Override protected IQueryPart[] getExtraParts() { return new IQueryPart[] { new DatesInterval("task", intervalFilterStartDate, intervalFilterEndDate), new OnScenario("task", onScenario) }; } }; return (List<SpecificResourceAllocation>) queryBuilder.build(getSession()).list(); } @Override public List<ResourceAllocation<?>> findAllocationsRelatedTo( Scenario onScenario, Resource resource, LocalDate intervalFilterStartDate, LocalDate intervalFilterEndDate) { return stripAllocationsWithoutAssignations(findAllocationsRelatedToAnyOf( onScenario, Collections.singletonList(resource), intervalFilterStartDate, intervalFilterEndDate)); } private <R extends ResourceAllocation<?>> List<R> stripAllocationsWithoutAssignations(List<R> allocations) { List<R> result = new ArrayList<>(); for (R eachAllocation : allocations) { if (eachAllocation.hasAssignments()) { result.add(eachAllocation); } } return result; } private Map<Criterion, List<GenericResourceAllocation>> stripAllocationsWithoutAssignations( Map<Criterion, List<GenericResourceAllocation>> map) { Map<Criterion, List<GenericResourceAllocation>> result = new HashMap<>(); for (Entry<Criterion, List<GenericResourceAllocation>> entry : map.entrySet()) { List<GenericResourceAllocation> valid = stripAllocationsWithoutAssignations(entry.getValue()); if (!valid.isEmpty()) { result.put(entry.getKey(), valid); } } return result; } @Override public Map<Criterion, List<GenericResourceAllocation>> findGenericAllocationsByCriterion( final Scenario onScenario, final Date intervalFilterStartDate, final Date intervalFilterEndDate) { QueryBuilder queryBuilder = new QueryBuilder() { @Override protected String getBaseQuery() { return "select generic, criterion " + "from GenericResourceAllocation as generic " + "join generic.criterions as criterion " + "join generic.task as task"; } @Override protected String getBaseConditions() { return ""; } @Override protected void setBaseParameters(Query query) { } @Override protected IQueryPart[] getExtraParts() { return new IQueryPart[] { new DatesInterval("task", intervalFilterStartDate, intervalFilterEndDate), new OnScenario("task", onScenario) }; } }; Query query = queryBuilder.build(getSession()); return toCriterionMapFrom(query); } @Override @SuppressWarnings("unchecked") public List<GenericResourceAllocation> findGenericAllocationsRelatedToCriterion( final Scenario onScenario, final Criterion criterion, final Date intervalFilterStartDate, final Date intervalFilterEndDate) { if (criterion == null) { return Collections.emptyList(); } QueryBuilder queryBuilder = new QueryBuilder() { @Override protected String getBaseQuery() { return "select generic " + "from GenericResourceAllocation as generic " + "join generic.task as task " + "join generic.criterions as criterion "; } @Override protected String getBaseConditions() { return "where criterion = :criterion "; } @Override protected void setBaseParameters(Query query) { query.setParameter("criterion", criterion); } @Override protected IQueryPart[] getExtraParts() { return new IQueryPart[] { new DatesInterval("task", intervalFilterStartDate, intervalFilterEndDate), new OnScenario("task", onScenario) }; } }; return queryBuilder.build(getSession()).list(); } @SuppressWarnings("unchecked") private Map<Criterion, List<GenericResourceAllocation>> toCriterionMapFrom(Query query){ return addParents(stripAllocationsWithoutAssignations(byCriterion(query.list()))); } private Map<Criterion, List<GenericResourceAllocation>> byCriterion( List<Object> results) { Map<Criterion, List<GenericResourceAllocation>> result = new HashMap<>(); for (Object row : results) { GenericResourceAllocation allocation = getAllocation(row); Criterion criterion = getCriterion(row); if (!result.containsKey(criterion)) { result.put(criterion, new ArrayList<>()); } result.get(criterion).add(allocation); } return result; } private GenericResourceAllocation getAllocation(Object row) { Object[] elements = (Object[]) row; return (GenericResourceAllocation) elements[0]; } private Criterion getCriterion(Object row) { Object[] elements = (Object[]) row; return (Criterion) elements[1]; } private Map<Criterion, List<GenericResourceAllocation>> addParents( Map<Criterion, List<GenericResourceAllocation>> byCriterion) { Map<Criterion, List<GenericResourceAllocation>> toBeMerged = new HashMap<>(); for (Entry<Criterion, List<GenericResourceAllocation>> each : byCriterion.entrySet()) { Criterion criterion = each.getKey(); for (Criterion parent : getParentsFrom(criterion)) { List<GenericResourceAllocation> childAllocations = each.getValue(); addToCriterion(toBeMerged, parent, childAllocations); } } return mergeTo(byCriterion, toBeMerged); } private void addToCriterion( Map<Criterion, List<GenericResourceAllocation>> map, Criterion criterion, List<GenericResourceAllocation> toAdd) { if (!map.containsKey(criterion)) { map.put(criterion, new ArrayList<>()); } map.get(criterion).addAll(toAdd); } private Map<Criterion, List<GenericResourceAllocation>> mergeTo( Map<Criterion, List<GenericResourceAllocation>> byCriterion, Map<Criterion, List<GenericResourceAllocation>> toMerge) { for (Entry<Criterion, List<GenericResourceAllocation>> each : toMerge.entrySet()) { addToCriterion(byCriterion, each.getKey(), each.getValue()); } return byCriterion; } private List<Criterion> getParentsFrom(Criterion criterion) { List<Criterion> result = new ArrayList<>(); Criterion current = criterion.getParent(); while (current != null) { result.add(current); current = current.getParent(); } return result; } @Override public List<SpecificResourceAllocation> findSpecificAllocationsRelatedTo( final Scenario onScenario, final Criterion criterion, final Date intervalFilterStartDate, final Date intervalFilterEndDate) { QueryBuilder builder = new QueryBuilder() { @Override protected String getBaseQuery() { return "select distinct s from SpecificResourceAllocation s " + "join s.resource r " + "join r.criterionSatisfactions satisfaction " + "join satisfaction.criterion c join s.task t"; } @Override protected String getBaseConditions() { return " where c = :criterion"; } @Override protected void setBaseParameters(Query query) { query.setParameter("criterion", criterion); } @Override protected IQueryPart[] getExtraParts() { return new IQueryPart[] { new DatesInterval("t", intervalFilterStartDate, intervalFilterEndDate), new OnScenario("t", onScenario) }; } }; Query query = builder.build(getSession()); @SuppressWarnings("unchecked") List<SpecificResourceAllocation> result = query.list(); return onlyAllocationsWithActiveCriterion( criterion, result, asLocalDate(intervalFilterStartDate), asLocalDate(intervalFilterEndDate)); } private static LocalDate asLocalDate(Date date) { if (date == null) { return null; } return LocalDate.fromDateFields(date); } private List<SpecificResourceAllocation> onlyAllocationsWithActiveCriterion( Criterion criterion, List<SpecificResourceAllocation> allocations, LocalDate startInclusive, LocalDate endExclusive) { List<SpecificResourceAllocation> result = new ArrayList<>(); for (SpecificResourceAllocation each : allocations) { if (each.interferesWith(criterion, startInclusive, endExclusive)) { result.add(each); } } return result; } public static abstract class QueryBuilder { private static Pattern wherePattern = Pattern.compile("WHERE", Pattern.CASE_INSENSITIVE); protected abstract String getBaseQuery(); protected abstract String getBaseConditions(); protected abstract void setBaseParameters(Query query); protected abstract IQueryPart[] getExtraParts(); public Query build(Session session) { final List<IQueryPart> extraParts = Arrays.asList(getExtraParts()); String queryString = getBaseQuery(); queryString = withExtraQueryParts(queryString, extraParts); queryString += " " + getBaseConditions(); queryString = withExtraWhereConditions(queryString, extraParts); Query query = session.createQuery(queryString); setBaseParameters(query); injectParameters(query, extraParts); return query; } private String withExtraQueryParts(String initialQuery, List<IQueryPart> extraParts) { StringBuilder result = new StringBuilder(initialQuery); for (IQueryPart each : extraParts) { result.append(" ").append(each.queryPart()); } return result.toString(); } private String withExtraWhereConditions(String initialQuery, List<IQueryPart> extraParts) { StringBuilder result = new StringBuilder(initialQuery); boolean alreadyHasWhere = hasWhere(initialQuery); if (!alreadyHasWhere) { result.append(" where "); } List<String> conditionsToAdd = notEmptyConditions(extraParts); for (int i = 0; i < conditionsToAdd.size(); i++) { if (alreadyHasWhere || i != 0) { result.append(" and "); } result.append(conditionsToAdd.get(i)); } return result.toString(); } private List<String> notEmptyConditions(List<IQueryPart> extraParts) { List<String> result = new ArrayList<>(); for (IQueryPart each : extraParts) { String toAdd = each.wherePart(); if (!StringUtils.isEmpty(toAdd)) { result.add(toAdd); } } return result; } private static boolean hasWhere(String initialQuery) { return wherePattern.matcher(initialQuery).find(); } private void injectParameters(Query query, List<IQueryPart> extraParts) { for (IQueryPart each : extraParts) { each.injectParameters(query); } } } public interface IQueryPart { String queryPart(); String wherePart(); void injectParameters(Query query); } public static class DatesInterval implements IQueryPart { private final LocalDate startInclusive; private final LocalDate endInclusive; private final String baseAlias; public DatesInterval(String baseAlias, Date startInclusive, Date endInclusive) { this(baseAlias, asLocal(startInclusive), asLocal(endInclusive)); } private static LocalDate asLocal(Date date) { if (date == null) { return null; } return LocalDate.fromDateFields(date); } public DatesInterval(String baseAlias, LocalDate startInclusive, LocalDate endInclusive) { this.baseAlias = baseAlias; this.startInclusive = startInclusive; this.endInclusive = endInclusive; } @Override public String queryPart() { return ""; } @Override public String wherePart() { String result = ""; if (startInclusive != null) { result += baseAlias + ".endDate.date >= :startInclusive"; } if (!result.isEmpty() && endInclusive != null) { result += " and "; } if (endInclusive != null) { result += baseAlias + ".startDate.date <= :endInclusive"; } return result; } @Override public void injectParameters(Query query) { if (startInclusive != null) { query.setParameter("startInclusive", startInclusive); } if (endInclusive != null) { query.setParameter("endInclusive", endInclusive); } } } public class OnScenario implements IQueryPart { private final Scenario onScenario; private final String taskAlias; private OnScenario(String taskAlias, Scenario onScenario) { Validate.notNull(taskAlias); Validate.notNull(onScenario); this.taskAlias = taskAlias; this.onScenario = onScenario; } @Override public String queryPart() { return "join " + taskAlias + ".taskSource.schedulingData as schedulingData " + "join schedulingData.orderElement as orderElement " + ", OrderVersion as version "; } @Override public String wherePart() { return "orderElement.schedulingDataForVersion[version] = schedulingData and version.ownerScenario = :scenario"; } @Override public void injectParameters(Query query) { query.setParameter("scenario", onScenario); } } }