/*
* This file is part of LibrePlan
*
* Copyright (C) 2012 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.importers;
import static org.libreplan.web.I18nHelper._;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Objects;
import net.sf.mpxj.ProjectFile;
import net.sf.mpxj.reader.ProjectReader;
import net.sf.mpxj.reader.ProjectReaderUtility;
import org.joda.time.LocalDate;
import org.libreplan.business.calendars.daos.IBaseCalendarDAO;
import org.libreplan.business.calendars.daos.ICalendarExceptionTypeDAO;
import org.libreplan.business.calendars.entities.BaseCalendar;
import org.libreplan.business.calendars.entities.CalendarData;
import org.libreplan.business.calendars.entities.CalendarException;
import org.libreplan.business.calendars.entities.CalendarExceptionType;
import org.libreplan.business.calendars.entities.Capacity;
import org.libreplan.business.common.daos.IEntitySequenceDAO;
import org.libreplan.business.common.entities.EntityNameEnum;
import org.libreplan.business.common.exceptions.InstanceNotFoundException;
import org.libreplan.business.common.exceptions.ValidationException;
import org.libreplan.business.workingday.EffortDuration;
import org.libreplan.importers.CalendarDayHoursDTO.CalendarTypeDayDTO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
/**
* Has all the methods needed to successfully import calendar data of external project files into Libreplan using MPXJ.
*
* @author Alba Carro PĂ©rez <alba.carro@gmail.com>
*/
@Component
@Scope(BeanDefinition.SCOPE_SINGLETON)
@Transactional
public class CalendarImporterMPXJ implements ICalendarImporter {
private static ProjectFile projectFile = null;
@Autowired
private IBaseCalendarDAO baseCalendarDAO;
@Autowired
private IEntitySequenceDAO entitySequenceDAO;
@Autowired
private ICalendarExceptionTypeDAO calendarExceptionTypeDAO;
/**
* Makes a list of {@link CalendarDTO} from a InputStream.
*
* @param file
* InputStream to extract data from.
* @return List<CalendarDTO> with the calendar data that we want to import.
*/
@Override
public List<CalendarDTO> getCalendarDTOs(InputStream file, String filename) {
try {
ProjectReader reader = ProjectReaderUtility.getProjectReader(filename);
// In case that orders are imported too
projectFile = reader.read(file);
return MPXJProjectFileConverter.convertCalendars(projectFile);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Makes a {@link OrderDTO} from a InputStream.
*
* Uses the ProjectReader of the class. It must be created before.
*
* @param filename
* String with the name of the original file of the InputStream.
* @return OrderDTO with the data that we want to import.
*/
@Override
public OrderDTO getOrderDTO(String filename) {
try {
return MPXJProjectFileConverter.convert(projectFile, filename);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Makes a list of {@link BaseCalendar} from a list of {@link CalendarDTO}.
*
* @param calendarDTOs
* List of CalendarDTO to extract data from.
* @return List<BaseCalendar> with all the calendars that we want.
* @throws InstanceNotFoundException, ValidationException
*/
@Override
public List<BaseCalendar> getBaseCalendars(List<CalendarDTO> calendarDTOs) throws InstanceNotFoundException {
List<BaseCalendar> baseCalendars = new ArrayList<>();
for (CalendarDTO calendarDTO : calendarDTOs) {
if (calendarDTO.parent == null) {
baseCalendars.add(toBaseCalendar(calendarDTO, null));
} else {
BaseCalendar parent = findBaseCalendarParent(baseCalendars,
calendarDTO.parent);
if (parent != null) {
baseCalendars.add(toBaseCalendar(calendarDTO, parent));
} else {
throw new ValidationException("Parent calendar not found");
}
}
}
return baseCalendars;
}
/**
* Search for a {@link BaseCalendar} that has this name.
*
* @param baseCalendars
* List<BaseCalendar> to search into.
* @param name
* Search condition
* @return BaseCalendar with the name equal to the search condition.
*/
private BaseCalendar findBaseCalendarParent(
List<BaseCalendar> baseCalendars, String name) {
for (BaseCalendar baseCalendar: baseCalendars){
if (Objects.equals(baseCalendar.getName(), name)){
return baseCalendar;
}
}
return null;
}
/**
* Makes a {@link BaseCalendar} from a {@link CalendarDTO}.
*
* @param calendarDTO
* CalendarDTO to extract data from.
* @return BaseCalendar with the calendar that we want.
* @throws InstanceNotFoundException, ValidationException
*/
private BaseCalendar toBaseCalendar(CalendarDTO calendarDTO, BaseCalendar parent) throws InstanceNotFoundException {
String code = getCode(EntityNameEnum.CALENDAR);
String name = validateName(calendarDTO.name);
Set<CalendarException> calendarExceptions = getCalendarExceptions(calendarDTO.calendarExceptions);
List<CalendarData> calendarData = getCalendarData(calendarDTO.calendarWeeks, parent);
BaseCalendar baseCalendar = BaseCalendar.createUnvalidated(code, name, parent, calendarExceptions, calendarData);
baseCalendar.setCodeAutogenerated(true);
baseCalendar.setName(name);
if (parent != null) {
baseCalendar.setParent(parent);
}
baseCalendar.generateCalendarExceptionCodes(entitySequenceDAO.getNumberOfDigitsCode(EntityNameEnum.CALENDAR));
return baseCalendar;
}
/**
* Calculate the next code for the entity.
*
* @param entity
* EntityNameEnum Entity
* @return String new code.
*/
private String getCode(EntityNameEnum entity) {
String code = entitySequenceDAO.getNextEntityCode(entity);
if (code == null) {
throw new ConcurrentModificationException(
"Could not retrieve Code. Please, try again later");
}
return code;
}
/**
* Makes a list of {@link CalendarData} from a list of {@link CalendarWeekDTO}.
*
* @param calendarWeeks
* List of CalendarWeekDTO to extract data from.
* @param parent
* BaseCalendar parent of all the calendarWeeks.
* @return List<CalendarData> with all the CalendarData that we want.
*/
private List<CalendarData> getCalendarData(List<CalendarWeekDTO> calendarWeeks, BaseCalendar parent) {
List<CalendarData> calendarData = new ArrayList<>();
for (CalendarWeekDTO calendarWeekDTO : calendarWeeks) {
calendarData.add(toCalendarData(calendarWeekDTO, parent));
}
return calendarData;
}
/**
* Makes a list of {@link CalendarException} from a list of {@link CalendarExceptionDTO}.
*
* @param calendarExceptionDTOs
* List of CalendarExceptionDTO to extract data from.
* @return List<CalendarException> with all the CalendarException that we want.
* @throws InstanceNotFoundException
*/
private Set<CalendarException> getCalendarExceptions(
List<CalendarExceptionDTO> calendarExceptionDTOs) throws InstanceNotFoundException {
Set<CalendarException> calendarExceptions = new HashSet<>();
for (CalendarExceptionDTO calendarExceptionDTO : calendarExceptionDTOs) {
calendarExceptions.add(toCalendarException(calendarExceptionDTO));
}
return calendarExceptions;
}
/**
* Makes a {@link CalendarException} from a {@link CalendarExceptionDTO}.
*
* @param calendarExceptionDTO
* CalendarExceptionDTO to extract data from.
* @return CalendarException with the CalendarException that we want.
* @throws InstanceNotFoundException
*/
private CalendarException toCalendarException(CalendarExceptionDTO calendarExceptionDTO)
throws InstanceNotFoundException {
LocalDate date = null;
if (calendarExceptionDTO.date != null) {
date = LocalDate.fromDateFields(calendarExceptionDTO.date);
}
CalendarExceptionType calendarExceptionType;
if (calendarExceptionDTO.working) {
calendarExceptionType = calendarExceptionTypeDAO.findUniqueByName("WORKING_DAY");
} else {
calendarExceptionType = calendarExceptionTypeDAO.findUniqueByName("NOT_WORKING_DAY");
}
return CalendarException.create(
date,
EffortDuration.hours(calendarExceptionDTO.hours).plus(EffortDuration.minutes(calendarExceptionDTO.minutes)),
calendarExceptionType);
}
/**
* Validate if a calendar name is not in use.
* If it is throws an exception.
* It not return the same name.
*
* @param name
* String with the name to validate.
* @return String with the valid name.
* @throws ValidationException
*/
@Transactional
private String validateName(String name) {
List<BaseCalendar> calendars = baseCalendarDAO.findByName(name);
if (calendars.isEmpty()) {
return name;
} else {
throw new ValidationException(_("Calendar name already in use"));
}
}
/**
* Makes a {@link CalendarData} from a {@link CalendarWeekDTO}.
*
* @param workingWeek
* CalendarWeekDTO to extract data from.
* @param parent
* BaseCalendar parent of this workingWeek
* @return CalendarData with the CalendarData that we want.
*/
private CalendarData toCalendarData(CalendarWeekDTO workingWeek, BaseCalendar parent) {
LocalDate expiringDate = null;
if (workingWeek.endDate != null) {
expiringDate = LocalDate.fromDateFields(workingWeek.endDate);
}
if (workingWeek.startDate != null) {
expiringDate = LocalDate.fromDateFields(workingWeek.startDate);
}
CalendarData calendarData = CalendarData.create();
calendarData.setExpiringDate(expiringDate);
if (parent != null) {
calendarData.setParent(parent);
}
calendarData.setCodeAutogenerated(true);
Map<Integer, Capacity> capacitiesPerDays = getCapacitiesPerDays(workingWeek.hoursPerDays);
try {
calendarData.updateCapacitiesPerDay(capacitiesPerDays);
} catch (IllegalArgumentException e) {
throw new ValidationException(e.getMessage());
}
return calendarData;
}
/**
* Makes a map of capacities with a list of {@link CalendarDayHoursDTO}.
*
* @param hoursPerDays
* List<CalendarDayHoursDTO> to extract data from.
* @return Map<Integer, Capacity> with the data that we want.
* @throws ValidationException
*/
private Map<Integer, Capacity> getCapacitiesPerDays(List<CalendarDayHoursDTO> hoursPerDays) {
Map<Integer, Capacity> result = new HashMap<>();
if (hoursPerDays != null) {
for (CalendarDayHoursDTO hoursPerDayDTO : hoursPerDays) {
try {
if (hoursPerDayDTO.type == CalendarTypeDayDTO.DEFAULT) {
continue;
}
Integer day = CalendarData.Days.valueOf(hoursPerDayDTO.day.toString()).ordinal();
Capacity capacity = Capacity.zero();
if (hoursPerDayDTO.type == CalendarTypeDayDTO.WORKING) {
capacity = Capacity.create(
EffortDuration.hours(hoursPerDayDTO.hours)).overAssignableWithoutLimit();
} else if (hoursPerDayDTO.type == CalendarTypeDayDTO.NOT_WORKING) {
capacity = Capacity.create(EffortDuration.hours(hoursPerDayDTO.hours));
}
result.put(day, capacity);
} catch (IllegalArgumentException e) {
throw new ValidationException("a day is not valid");
} catch (NullPointerException e) {
throw new ValidationException("a day is null");
}
}
}
return result;
}
/**
* Saves a list of {@link BaseCalendar} that has all the calendar data that we want to store in the database.
*
* @param baseCalendars
*/
@Override
@Transactional
public void storeBaseCalendars(List<BaseCalendar> baseCalendars) {
for (BaseCalendar baseCalendar : baseCalendars) {
baseCalendarDAO.save(baseCalendar);
}
}
}