/*
* 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.List;
import java.util.UUID;
import net.sf.mpxj.reader.ProjectReader;
import net.sf.mpxj.reader.ProjectReaderUtility;
import org.apache.commons.lang3.Validate;
import org.joda.time.LocalDate;
import org.libreplan.business.calendars.daos.IBaseCalendarDAO;
import org.libreplan.business.calendars.entities.BaseCalendar;
import org.libreplan.business.common.IAdHocTransactionService;
import org.libreplan.business.common.daos.IConfigurationDAO;
import org.libreplan.business.common.daos.IEntitySequenceDAO;
import org.libreplan.business.common.entities.EntityNameEnum;
import org.libreplan.business.common.exceptions.ValidationException;
import org.libreplan.business.orders.daos.IOrderDAO;
import org.libreplan.business.orders.entities.Order;
import org.libreplan.business.orders.entities.OrderElement;
import org.libreplan.business.orders.entities.OrderLine;
import org.libreplan.business.orders.entities.OrderLineGroup;
import org.libreplan.business.orders.entities.TaskSource;
import org.libreplan.business.planner.daos.IDependencyDAO;
import org.libreplan.business.planner.daos.ITaskElementDAO;
import org.libreplan.business.planner.daos.ITaskSourceDAO;
import org.libreplan.business.planner.entities.Dependency;
import org.libreplan.business.planner.entities.Dependency.Type;
import org.libreplan.business.planner.entities.Task;
import org.libreplan.business.planner.entities.TaskElement;
import org.libreplan.business.planner.entities.TaskGroup;
import org.libreplan.business.planner.entities.TaskMilestone;
import org.libreplan.business.scenarios.IScenarioManager;
import org.libreplan.business.scenarios.entities.OrderVersion;
import org.libreplan.business.scenarios.entities.Scenario;
import org.libreplan.business.workingday.IntraDayDate;
import org.libreplan.importers.DependencyDTO.TypeOfDependencyDTO;
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 some external project files into Libreplan using MPXJ.
*
* @author Alba Carro PĂ©rez <alba.carro@gmail.com>
* @author Vova Perebykivskyi <vova@libreplan-enterprise.com>>
*/
@Component
@Scope(BeanDefinition.SCOPE_SINGLETON)
public class OrderImporterMPXJ implements IOrderImporter {
@Autowired
private IBaseCalendarDAO baseCalendarDAO;
@Autowired
private IEntitySequenceDAO entitySequenceDAO;
@Autowired
protected IAdHocTransactionService transactionService;
@Autowired
private IConfigurationDAO configurationDAO;
@Autowired
private IOrderDAO orderDAO;
@Autowired
private IDependencyDAO dependencyDAO;
@Autowired
private ITaskElementDAO taskDAO;
@Autowired
private ITaskSourceDAO taskSourceDAO;
@Autowired
private IScenarioManager scenarioManager;
/**
* Makes a {@link OrderDTO} from a InputStream.
*
* Uses the filename in order to get the specific ProjectReader for each kind of file (.mpp, .planner, etc).
*
* @param file
* InputStream to extract data from.
* @param filename
* String with the name of the original file of the InputStream.
* @return ImportData with the data that we want to import.
*/
@Override
public OrderDTO getImportData(InputStream file, String filename) {
try {
ProjectReader reader = ProjectReaderUtility.getProjectReader(filename);
return MPXJProjectFileConverter.convert(reader.read(file), filename);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
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 {@link Order} from a {@link OrderDTO}.
*
* @param project
* ImportData to extract data from.
* @return Order with all the data that we want.
*/
@Override
@Transactional(readOnly = true)
public Order convertImportDataToOrder(OrderDTO project, boolean importCalendar) {
String code = getCode(EntityNameEnum.ORDER);
Scenario current = scenarioManager.getCurrent();
OrderVersion orderVersion = OrderVersion.createInitialVersion(current);
Validate.notNull(orderVersion);
OrderElement orderElement;
orderElement = Order.createUnvalidated(code);
orderElement.setCodeAutogenerated(true);
((Order) orderElement).setVersionForScenario(current, orderVersion);
((Order) orderElement).setDependenciesConstraintsHavePriority(true);
BaseCalendar calendar = configurationDAO.getConfiguration()
.getDefaultCalendar();
if ( importCalendar & project.calendarName != null ) {
((Order) orderElement).setCalendar(findBaseCalendar(project.calendarName));
} else {
((Order) orderElement).setCalendar(calendar);
}
orderElement.useSchedulingDataFor(orderVersion);
List<OrderElement> children = new ArrayList<>();
for (OrderElementDTO task : project.tasks) {
children.add(convertImportTaskToOrderElement(orderVersion, task));
}
for (OrderElement child : children) {
((OrderLineGroup) orderElement).add(child);
}
orderElement.setName(project.name + ": " + project.hashCode());
orderElement.setCode(code);
orderElement.setInitDate(project.startDate);
orderElement.setDeadline(project.deadline);
((Order) orderElement).calculateAndSetTotalHours();
project.order = (Order) orderElement;
((Order) orderElement).generateOrderElementCodes(entitySequenceDAO
.getNumberOfDigitsCode(EntityNameEnum.ORDER));
return (Order) orderElement;
}
/**
* Private method.
*
* It makes a {@link OrderElement} from a {@link OrderElementDTO}
*
* @param task
* ImportTask to extract data from.
* @param orderVersion
* Number of version.
* @return OrderElement OrderElement that represent the data.
*/
private OrderElement convertImportTaskToOrderElement(OrderVersion orderVersion, OrderElementDTO task) {
Validate.notNull(orderVersion);
OrderElement orderElement;
if ( task.children.size() == 0 ) {
orderElement = OrderLine
.createUnvalidatedWithUnfixedPercentage(UUID.randomUUID().toString(), task.totalHours);
if ( !orderElement.getHoursGroups().isEmpty() ) {
orderElement.getHoursGroups().get(0).setCode(UUID.randomUUID().toString());
}
} else {
orderElement = OrderLineGroup.createUnvalidated(UUID.randomUUID().toString());
orderElement.useSchedulingDataFor(orderVersion);
}
List<OrderElement> children = new ArrayList<>();
for (OrderElementDTO childrenTask : task.children) {
children.add(convertImportTaskToOrderElement(orderVersion, childrenTask));
}
for (OrderElement child : children) {
((OrderLineGroup) orderElement).add(child);
}
orderElement.setName(task.name);
orderElement.setDeadline(task.deadline);
task.orderElement = orderElement;
return orderElement;
}
/**
* Creates a {@link TaskGroup} from a {@link OrderDTO}.
*
* @param project
* ImportData to extract data from
*
* @return TaskGroup TaskGroup with the data extracted.
*/
@Override
@Transactional
public TaskGroup createTask(OrderDTO project, boolean importCalendar) {
Order order = project.order;
TaskSource taskSource = TaskSource.createForGroup(order.getCurrentSchedulingDataForVersion());
TaskGroup taskGroup = taskSource.createTaskGroupWithoutDatesInitializedAndLinkItToTaskSource();
BaseCalendar calendar = configurationDAO.getConfiguration().getDefaultCalendar();
taskGroup.setCalendar(calendar);
List<TaskElement> taskElements = new ArrayList<>();
for (OrderElementDTO importTask : project.tasks) {
taskElements.add(createTask(importTask, importCalendar));
}
for (MilestoneDTO milestone : project.milestones) {
taskElements.add(createTaskMilestone(milestone));
}
for (TaskElement taskElement : taskElements) {
taskGroup.addTaskElement(taskElement);
}
return taskGroup;
}
/**
* Private method.
*
* It makes a {@link TaskMilestone} from a {@link MilestoneDTO}
*
* @param milestone
* MilestoneDTO to extract data from.
*
* @return TaskElement TaskElement that represent the data.
*/
@Transactional
private TaskElement createTaskMilestone(MilestoneDTO milestone) {
TaskElement taskMilestone = TaskMilestone.create(milestone.startDate);
taskMilestone.setName(milestone.name);
setPositionConstraint((TaskMilestone) taskMilestone, milestone);
milestone.taskElement = taskMilestone;
return taskMilestone;
}
/**
* It makes a {@link TaskElement} from a {@link OrderElementDTO}.
*
* @param task
* ImportTask to extract data from.
*
* @return TaskElement TaskElement that represent the data.
*/
private TaskElement createTask(OrderElementDTO task, boolean importCalendar) {
OrderElement orderElement = task.orderElement;
TaskElement taskElement;
TaskSource taskSource;
if (task.children.size() == 0) {
taskSource = TaskSource
.create(orderElement.getCurrentSchedulingDataForVersion(), orderElement.getHoursGroups());
taskElement = taskSource.createTaskWithoutDatesInitializedAndLinkItToTaskSource();
if (importCalendar && task.calendarName != null) {
taskElement.setCalendar(findBaseCalendar(task.calendarName));
}
setPositionConstraint((Task) taskElement, task);
} else {
taskSource = TaskSource.createForGroup(orderElement.getCurrentSchedulingDataForVersion());
taskElement = taskSource.createTaskGroupWithoutDatesInitializedAndLinkItToTaskSource();
List<TaskElement> taskElements = new ArrayList<>();
for (OrderElementDTO importTask : task.children) {
taskElements.add(createTask(importTask, importCalendar));
}
if ( task.milestones != null )
for (MilestoneDTO milestone : task.milestones) {
taskElements.add(createTaskMilestone(milestone));
}
for (TaskElement childTaskElement : taskElements) {
((TaskGroup) taskElement).addTaskElement(childTaskElement);
}
}
taskElement.setStartDate(task.startDate);
taskElement.setEndDate(task.endDate);
task.taskElement = taskElement;
return taskElement;
}
/**
* Sets the proper constraint to and a {@link Task}.
*
* @param importTask
* OrderElementDTO to extract data from.
* @param task
* Task to set data on.
*/
private void setPositionConstraint(Task task, OrderElementDTO importTask) {
switch (importTask.constraint) {
case AS_SOON_AS_POSSIBLE:
task.getPositionConstraint().asSoonAsPossible();
return;
case AS_LATE_AS_POSSIBLE:
task.getPositionConstraint().asLateAsPossible();
return;
case START_IN_FIXED_DATE:
task.setIntraDayStartDate(IntraDayDate.startOfDay(LocalDate.fromDateFields(importTask.constraintDate)));
Task.convertOnStartInFixedDate(task);
return;
case START_NOT_EARLIER_THAN:
task.getPositionConstraint().notEarlierThan(
IntraDayDate.startOfDay(LocalDate.fromDateFields(importTask.constraintDate)));
return;
case FINISH_NOT_LATER_THAN:
task.getPositionConstraint().finishNotLaterThan(
IntraDayDate.startOfDay(LocalDate.fromDateFields(importTask.constraintDate)));
return;
default: return;
}
}
/**
* Sets the proper constraint to and a {@link TaskMiletone}
*
* @param milestone
* MilestoneDTO to extract data from.
* @param taskMilestone
* TaskMilestone to set data on.
*/
private void setPositionConstraint(TaskMilestone taskMilestone, MilestoneDTO milestone) {
switch (milestone.constraint) {
case AS_SOON_AS_POSSIBLE:
taskMilestone.getPositionConstraint().asSoonAsPossible();
return;
case AS_LATE_AS_POSSIBLE:
taskMilestone.getPositionConstraint().asLateAsPossible();
return;
case START_IN_FIXED_DATE:
taskMilestone.getPositionConstraint().notEarlierThan(
IntraDayDate.startOfDay(LocalDate.fromDateFields(milestone.constraintDate)));
return;
case START_NOT_EARLIER_THAN:
taskMilestone.getPositionConstraint().notEarlierThan(
IntraDayDate.startOfDay(LocalDate.fromDateFields(milestone.constraintDate)));
return;
case FINISH_NOT_LATER_THAN:
taskMilestone.getPositionConstraint().finishNotLaterThan(
IntraDayDate.startOfDay(LocalDate.fromDateFields(milestone.constraintDate)));
return;
default: return;
}
}
/**
* Saves an {@link Order} which has all the data that we want to store in the database.
* Also save all the related {@link TaskElement} and its {@link TaskSource}.
*
* @param order
* Order with the data.
* @param taskGroup
* TaskGroup with the data. It also contains the link to the TaskSources.
* @param dependencies
*/
@Override
@Transactional
public void storeOrder(final Order order, final TaskGroup taskGroup, final List<Dependency> dependencies) {
final List<TaskSource> taskSources = new ArrayList<>();
taskSources.add(taskGroup.getTaskSource());
for (TaskElement taskElement : taskGroup.getAllChildren()) {
if ( !taskElement.isMilestone() ) {
taskSources.add(taskElement.getTaskSource());
}
}
orderDAO.save(order);
taskDAO.save(taskGroup);
for (TaskSource taskSource : taskSources) {
taskSource.validate();
taskSourceDAO.save(taskSource);
}
for(Dependency dependency: dependencies){
dependencyDAO.save(dependency);
}
}
/**
* Creates a list of {@link Dependency} from a {@link OrderDTO}.
*
* @param importData
* ImportData to extract data from
*
* @return {@link List<Dependency>} with the data extracted.
*/
@Override
public List<Dependency> createDependencies(OrderDTO importData) {
List<Dependency> dependencies = new ArrayList<>();
for(DependencyDTO dependencyDTO: importData.dependencies){
TaskElement origin = null;
TaskElement destination = null;
if ( dependencyDTO.origin != null && dependencyDTO.origin.getTaskAssociated() != null )
origin = dependencyDTO.origin.getTaskAssociated();
if ( dependencyDTO.destination != null && dependencyDTO.destination.getTaskAssociated() != null )
destination = dependencyDTO.destination.getTaskAssociated();
if ( origin != null && destination != null ){
dependencies.add(Dependency.create(origin, destination, toLPType(dependencyDTO.type)));
}
}
return dependencies;
}
/**
* Return the equivalent {@link Type} of a {@link TypeOfDependencyDTO}.
*
* @param type
* TypeOfDependencyDTO to extract data from.
* @return Type equivalent type.
*/
private Type toLPType(TypeOfDependencyDTO type) {
switch (type) {
case END_START:
return Type.END_START;
case START_START:
return Type.START_START;
case END_END:
return Type.END_END;
case START_END:
return Type.START_END;
default:
return null;
}
}
/**
* Return the {@link BaseCalendar} with the same name as the string given.
*
* @param name
* String with the name that we want to find.
* @return BaseCalendar Calendar.
*/
private BaseCalendar findBaseCalendar(String name) {
List<BaseCalendar> baseCalendars = baseCalendarDAO.findByName(name);
BaseCalendar calendar;
for (BaseCalendar baseCalendar : baseCalendars) {
if ( baseCalendar.getName().equals(name) ) {
calendar = baseCalendar;
return calendar;
}
}
throw new ValidationException(_("Linked calendar not found"));
}
}