diff --git a/libreplan-business/src/main/java/org/libreplan/business/common/Registry.java b/libreplan-business/src/main/java/org/libreplan/business/common/Registry.java index 82cbf840f..441e6d264 100644 --- a/libreplan-business/src/main/java/org/libreplan/business/common/Registry.java +++ b/libreplan-business/src/main/java/org/libreplan/business/common/Registry.java @@ -28,7 +28,9 @@ import org.libreplan.business.calendars.daos.ICalendarDataDAO; import org.libreplan.business.calendars.daos.ICalendarExceptionDAO; import org.libreplan.business.calendars.daos.ICalendarExceptionTypeDAO; import org.libreplan.business.common.daos.IConfigurationDAO; +import org.libreplan.business.common.daos.IConnectorDAO; import org.libreplan.business.common.daos.IEntitySequenceDAO; +import org.libreplan.business.common.daos.IJobSchedulerConfigurationDAO; import org.libreplan.business.costcategories.daos.ICostCategoryDAO; import org.libreplan.business.costcategories.daos.IHourCostDAO; import org.libreplan.business.costcategories.daos.IResourcesCostCategoryAssignmentDAO; @@ -44,6 +46,7 @@ import org.libreplan.business.materials.daos.IUnitTypeDAO; import org.libreplan.business.orders.daos.IHoursGroupDAO; import org.libreplan.business.orders.daos.IOrderDAO; import org.libreplan.business.orders.daos.IOrderElementDAO; +import org.libreplan.business.orders.daos.IOrderSyncInfoDAO; import org.libreplan.business.planner.daos.ITaskElementDAO; import org.libreplan.business.qualityforms.daos.IQualityFormDAO; import org.libreplan.business.resources.daos.ICriterionDAO; @@ -203,6 +206,15 @@ public class Registry { @Autowired private IOrderAuthorizationDAO orderAuthorizationDAO; + @Autowired + private IConnectorDAO connectorDAO; + + @Autowired + private IOrderSyncInfoDAO orderSyncInfoDAO; + + @Autowired + private IJobSchedulerConfigurationDAO jobSchedulerConfigurationDAO; + @Autowired private IAdHocTransactionService transactionServiceDAO; @@ -379,4 +391,15 @@ public class Registry { return getInstance().orderAuthorizationDAO; } + public static IConnectorDAO getConnectorDAO() { + return getInstance().connectorDAO; + } + + public static IOrderSyncInfoDAO getOrderSyncInfoDAO() { + return getInstance().orderSyncInfoDAO; + } + + public static IJobSchedulerConfigurationDAO getJobSchedulerConfigurationDAO() { + return getInstance().jobSchedulerConfigurationDAO; + } } diff --git a/libreplan-business/src/main/java/org/libreplan/business/common/daos/ConnectorDAO.java b/libreplan-business/src/main/java/org/libreplan/business/common/daos/ConnectorDAO.java new file mode 100644 index 000000000..884b13879 --- /dev/null +++ b/libreplan-business/src/main/java/org/libreplan/business/common/daos/ConnectorDAO.java @@ -0,0 +1,75 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 St. Antoniusziekenhuis + * + * 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 . + */ + +package org.libreplan.business.common.daos; + +import java.util.List; + +import org.hibernate.Criteria; +import org.hibernate.criterion.Restrictions; +import org.libreplan.business.common.entities.Connector; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +/** + * DAO for {@link Connector} entity. + * + * @author Miciele Ghiorghis + * @author Manuel Rego Casasnovas + */ +@Repository +@Scope(BeanDefinition.SCOPE_SINGLETON) +public class ConnectorDAO extends GenericDAOHibernate + implements IConnectorDAO { + + @Override + @Transactional(readOnly = true) + public List getAll() { + return list(Connector.class); + } + + @Override + @Transactional(readOnly = true) + public Connector findUniqueByName(String name) { + Criteria c = getSession().createCriteria(Connector.class).add( + Restrictions.eq("name", name)); + return (Connector) c.uniqueResult(); + } + + @Override + @Transactional(readOnly = true, propagation = Propagation.REQUIRES_NEW) + public boolean existsByNameAnotherTransaction(Connector connector) { + return existsOtherConnectorByName(connector); + } + + private boolean existsOtherConnectorByName(Connector connector) { + Connector found = findUniqueByName(connector.getName()); + return found != null && found != connector; + } + + @Override + @Transactional(readOnly = true, propagation = Propagation.REQUIRES_NEW) + public Connector findUniqueByNameAnotherTransaction(String name) { + return findUniqueByName(name); + } + +} diff --git a/libreplan-business/src/main/java/org/libreplan/business/common/daos/IConnectorDAO.java b/libreplan-business/src/main/java/org/libreplan/business/common/daos/IConnectorDAO.java new file mode 100644 index 000000000..82ab2119c --- /dev/null +++ b/libreplan-business/src/main/java/org/libreplan/business/common/daos/IConnectorDAO.java @@ -0,0 +1,42 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 St. Antoniusziekenhuis + * + * 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 . + */ + +package org.libreplan.business.common.daos; + +import java.util.List; + +import org.libreplan.business.common.entities.Connector; + +/** + * Contract for {@link Conn} + * + * @author Miciele Ghiorghis + * @author Manuel Rego Casasnovas + */ +public interface IConnectorDAO extends IGenericDAO { + + List getAll(); + + Connector findUniqueByName(String name); + + boolean existsByNameAnotherTransaction(Connector connector); + + Connector findUniqueByNameAnotherTransaction(String name); + +} diff --git a/libreplan-business/src/main/java/org/libreplan/business/common/daos/IJobSchedulerConfigurationDAO.java b/libreplan-business/src/main/java/org/libreplan/business/common/daos/IJobSchedulerConfigurationDAO.java new file mode 100644 index 000000000..4f1bfe23a --- /dev/null +++ b/libreplan-business/src/main/java/org/libreplan/business/common/daos/IJobSchedulerConfigurationDAO.java @@ -0,0 +1,80 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 St. Antoniusziekenhuis + * + * 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 . + */ + +package org.libreplan.business.common.daos; + +import java.util.List; + +import org.libreplan.business.common.entities.JobSchedulerConfiguration; + +/** + * Contract for {@link JobSchedulerConfigurationDAO} + * + * @author Miciele Ghiorghis + */ +public interface IJobSchedulerConfigurationDAO extends + IGenericDAO { + + /** + * Returns all {@link JobSchedulerConfiguration} + */ + List getAll(); + + /** + * Searches and returns {@link JobSchedulerConfiguration} for the given + * connectorName + * + * @param connectorName + * the name of the connector + */ + List findByConnectorName(String connectorName); + + /** + * Searches and returns {@link JobSchedulerConfiguration} for the given + * jobGroup and jobName + * + * @param jobGroup + * @param jobName + */ + JobSchedulerConfiguration findByJobGroupAndJobName(String jobGroup, + String jobName); + + /** + * Returns true if there exists other @{link JobSchedulerConfiguration} with + * the same {@link JobSchedulerConfiguration#getJobGroup()} and + * {@link JobSchedulerConfiguration#getJobName() + * + * @param jobSchedulerConfiguration + * the {@link JobSchedulerConfiguration} + */ + boolean existsByJobGroupAndJobNameAnotherTransaction( + JobSchedulerConfiguration jobSchedulerConfiguration); + + /** + * Returns unique {@link JobSchedulerConfiguration} for the specified + * JobGroup and JobName + * + * @param jobGroup + * the jobGroup + * @param jobName + * the jobName + */ + JobSchedulerConfiguration findUniqueByJobGroupAndJobNameAnotherTransaction( + String jobGroup, String jobName); +} diff --git a/libreplan-business/src/main/java/org/libreplan/business/common/daos/JobSchedulerConfigurationDAO.java b/libreplan-business/src/main/java/org/libreplan/business/common/daos/JobSchedulerConfigurationDAO.java new file mode 100644 index 000000000..f07ad1fce --- /dev/null +++ b/libreplan-business/src/main/java/org/libreplan/business/common/daos/JobSchedulerConfigurationDAO.java @@ -0,0 +1,100 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 St. Antoniusziekenhuis + * + * 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 . + */ + +package org.libreplan.business.common.daos; + +import java.util.List; + +import org.hibernate.Criteria; +import org.hibernate.criterion.Restrictions; +import org.libreplan.business.common.entities.JobSchedulerConfiguration; +import org.libreplan.business.orders.entities.OrderSyncInfo; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +/** + * DAO for {@link JobSchedulerConfiguration} + * + * @author Miciele Ghiorghis + */ +@Repository +@Scope(BeanDefinition.SCOPE_SINGLETON) +public class JobSchedulerConfigurationDAO extends + GenericDAOHibernate implements + IJobSchedulerConfigurationDAO { + + @Override + @Transactional(readOnly = true) + public List getAll() { + return list(JobSchedulerConfiguration.class); + } + + @Override + @Transactional(readOnly = true) + public JobSchedulerConfiguration findByJobGroupAndJobName(String jobGroup, + String jobName) { + return (JobSchedulerConfiguration) getSession() + .createCriteria(JobSchedulerConfiguration.class) + .add(Restrictions.eq("jobGroup", jobGroup)) + .add(Restrictions.eq("jobName", jobName)).uniqueResult(); + } + + @Override + @Transactional(readOnly = true) + public List findByConnectorName( + String connectorName) { + Criteria c = getSession().createCriteria( + JobSchedulerConfiguration.class).add( + Restrictions.eq("connectorName", connectorName)); + return ((List) c.list()); + } + + @Override + @Transactional(readOnly = true, propagation = Propagation.REQUIRES_NEW) + public boolean existsByJobGroupAndJobNameAnotherTransaction( + JobSchedulerConfiguration jobSchedulerConfiguration) { + return existsOtherJobByGroupAndName(jobSchedulerConfiguration); + } + + /** + * Returns true if other {@link JobSchedulerConfiguration} which is the same + * as the given {@link OrderSyncInfo} already exists + * + * @param jobSchedulerConfiguration + * the {@link JobSchedulerConfiguration} + */ + private boolean existsOtherJobByGroupAndName( + JobSchedulerConfiguration jobSchedulerConfiguration) { + JobSchedulerConfiguration found = findByJobGroupAndJobName( + jobSchedulerConfiguration.getJobGroup(), + jobSchedulerConfiguration.getJobName()); + return found != null && found != jobSchedulerConfiguration; + } + + @Override + @Transactional(readOnly = true, propagation = Propagation.REQUIRES_NEW) + public JobSchedulerConfiguration findUniqueByJobGroupAndJobNameAnotherTransaction( + String jobGroup, String jobName) { + return findByJobGroupAndJobName(jobGroup, jobName); + } + +} diff --git a/libreplan-business/src/main/java/org/libreplan/business/common/entities/Configuration.java b/libreplan-business/src/main/java/org/libreplan/business/common/entities/Configuration.java index caf2a1b02..2f05f98d4 100644 --- a/libreplan-business/src/main/java/org/libreplan/business/common/entities/Configuration.java +++ b/libreplan-business/src/main/java/org/libreplan/business/common/entities/Configuration.java @@ -104,8 +104,6 @@ public class Configuration extends BaseEntity { private Boolean generateCodeForExpenseSheets = true; - private JiraConfiguration jiraConfiguration; - /** * Currency code according to ISO-4217 (3 letters) */ @@ -505,12 +503,4 @@ public class Configuration extends BaseEntity { this.secondsPlanningWarning = secondsPlanningWarning; } - public JiraConfiguration getJiraConfiguration() { - return jiraConfiguration; - } - - public void setJiraConfiguration(JiraConfiguration jiraConfiguration) { - this.jiraConfiguration = jiraConfiguration; - } - } diff --git a/libreplan-business/src/main/java/org/libreplan/business/common/entities/ConfigurationBootstrap.java b/libreplan-business/src/main/java/org/libreplan/business/common/entities/ConfigurationBootstrap.java index 7f9f41c0b..633b4b85f 100644 --- a/libreplan-business/src/main/java/org/libreplan/business/common/entities/ConfigurationBootstrap.java +++ b/libreplan-business/src/main/java/org/libreplan/business/common/entities/ConfigurationBootstrap.java @@ -81,13 +81,6 @@ public class ConfigurationBootstrap implements IConfigurationBootstrap { } configuration.setLdapConfiguration(ldapConfiguration); - JiraConfiguration jiraConfiguration = configuration - .getJiraConfiguration(); - if (jiraConfiguration == null) { - jiraConfiguration = JiraConfiguration.create(); - } - configuration.setJiraConfiguration(jiraConfiguration); - configurationDAO.save(configuration); } diff --git a/libreplan-business/src/main/java/org/libreplan/business/common/entities/ConfigurationTypeOfWorkHoursBootstrap.java b/libreplan-business/src/main/java/org/libreplan/business/common/entities/ConfigurationTypeOfWorkHoursBootstrap.java index 90cfee44b..c3404b920 100644 --- a/libreplan-business/src/main/java/org/libreplan/business/common/entities/ConfigurationTypeOfWorkHoursBootstrap.java +++ b/libreplan-business/src/main/java/org/libreplan/business/common/entities/ConfigurationTypeOfWorkHoursBootstrap.java @@ -75,8 +75,6 @@ public class ConfigurationTypeOfWorkHoursBootstrap implements } configuration.setPersonalTimesheetsTypeOfWorkHours(typeOfWorkHours); - configuration.getJiraConfiguration().setJiraConnectorTypeOfWorkHours( - typeOfWorkHours); configurationDAO.save(configuration); } diff --git a/libreplan-business/src/main/java/org/libreplan/business/common/entities/Connector.java b/libreplan-business/src/main/java/org/libreplan/business/common/entities/Connector.java new file mode 100644 index 000000000..e57458358 --- /dev/null +++ b/libreplan-business/src/main/java/org/libreplan/business/common/entities/Connector.java @@ -0,0 +1,148 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 St. Antoniusziekenhuis + * + * 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 . + */ + +package org.libreplan.business.common.entities; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang.StringUtils; +import org.hibernate.validator.AssertTrue; +import org.hibernate.validator.NotEmpty; +import org.hibernate.validator.Valid; +import org.libreplan.business.common.BaseEntity; +import org.libreplan.business.common.Registry; +import org.libreplan.business.common.daos.IConnectorDAO; + +/** + * Connector entity, represents a connector in order that LibrePlan interchange + * some data with other application. + * + * A connector is identified by a name and it has a list of pairs + * key-value in order to store the configuration parameters of the connector. + * + * This entity should be used to create new connectors in LibrePlan. + * + * @author Miciele Ghiorghis + * @author Manuel Rego Casasnovas + */ +public class Connector extends BaseEntity { + + public static Connector create(String name) { + return create(new Connector(name)); + } + + private String name; + + private List properties = new ArrayList(); + + /** + * Constructor for Hibernate. Do not use! + */ + protected Connector() { + } + + private Connector(String name) { + this.name = name; + } + + @NotEmpty(message = "name not specified") + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Valid + public List getProperties() { + return Collections.unmodifiableList(properties); + } + + public void setProperties(List properties) { + this.properties = properties; + } + + public void addProperty(ConnectorProperty property) { + properties.add(property); + } + + public Map getPropertiesAsMap() { + Map map = new HashMap(); + for (ConnectorProperty property : properties) { + map.put(property.getKey(), property.getValue()); + } + return map; + } + + @AssertTrue(message = "connector name is already being used") + public boolean checkConstraintUniqueConnectorName() { + if (StringUtils.isBlank(name)) { + return true; + } + + IConnectorDAO connectorDAO = Registry.getConnectorDAO(); + if (isNewObject()) { + return !connectorDAO.existsByNameAnotherTransaction(this); + } else { + Connector found = connectorDAO + .findUniqueByNameAnotherTransaction(name); + return found == null || found.getId().equals(getId()); + } + + } + + public boolean isActivated() { + return getPropertiesAsMap() + .get(PredefinedConnectorProperties.ACTIVATED).equalsIgnoreCase( + "Y"); + } + + /** + * Check if connector's connections values are valid + * + * @return true if connection values are valid + */ + public boolean areConnectionValuesValid() { + String serverUrl = getPropertiesAsMap().get( + PredefinedConnectorProperties.SERVER_URL); + try { + new URL(serverUrl); + } catch (MalformedURLException e) { + return false; + } + + if (StringUtils.isBlank(getPropertiesAsMap().get( + PredefinedConnectorProperties.USERNAME))) { + return false; + } + + if (StringUtils.isBlank(getPropertiesAsMap().get( + PredefinedConnectorProperties.PASSWORD))) { + return false; + } + return true; + } +} diff --git a/libreplan-business/src/main/java/org/libreplan/business/common/entities/ConnectorBootstrap.java b/libreplan-business/src/main/java/org/libreplan/business/common/entities/ConnectorBootstrap.java new file mode 100644 index 000000000..a47ff19be --- /dev/null +++ b/libreplan-business/src/main/java/org/libreplan/business/common/entities/ConnectorBootstrap.java @@ -0,0 +1,57 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 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 . + */ + +package org.libreplan.business.common.entities; + +import org.libreplan.business.common.daos.IConnectorDAO; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +/** + * Creates the LibrePlan {@link Connector Connectors} with its configuration + * properties and default values. + * + * @author Manuel Rego Casasnovas + */ +@Component +@Scope("singleton") +public class ConnectorBootstrap implements IConnectorBootstrap { + + @Autowired + private IConnectorDAO connectorDAO; + + @Override + @Transactional + public void loadRequiredData() { + for (PredefinedConnectors predefinedConnector : PredefinedConnectors + .values()) { + String name = predefinedConnector.getName(); + + Connector connector = connectorDAO.findUniqueByName(name); + if (connector == null) { + connector = Connector.create(name); + connector.setProperties(predefinedConnector.getProperties()); + connectorDAO.save(connector); + } + } + } + +} diff --git a/libreplan-business/src/main/java/org/libreplan/business/common/entities/ConnectorException.java b/libreplan-business/src/main/java/org/libreplan/business/common/entities/ConnectorException.java new file mode 100644 index 000000000..062666893 --- /dev/null +++ b/libreplan-business/src/main/java/org/libreplan/business/common/entities/ConnectorException.java @@ -0,0 +1,33 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 St. Antoniusziekenhuis + * + * 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 . + */ + +package org.libreplan.business.common.entities; + +/** + * Exception to ecapsulate connector(values) exceptions + * + * @author Miciele Ghiorghis + */ +public class ConnectorException extends Exception { + + public ConnectorException(String message) { + super(message); + } + +} diff --git a/libreplan-business/src/main/java/org/libreplan/business/common/entities/ConnectorProperty.java b/libreplan-business/src/main/java/org/libreplan/business/common/entities/ConnectorProperty.java new file mode 100644 index 000000000..7cb6b79d7 --- /dev/null +++ b/libreplan-business/src/main/java/org/libreplan/business/common/entities/ConnectorProperty.java @@ -0,0 +1,64 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 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 . + */ + +package org.libreplan.business.common.entities; + +import org.hibernate.validator.NotEmpty; + +/** + * This class is intended to work as a Hibernate component. It's formed by two + * attributes, the key and the value of the property. It represents the + * different configuration parameters of a {@link Connector}. + * + * @author Manuel Rego Casasnovas + */ +public class ConnectorProperty { + + public static ConnectorProperty create(String key, String value) { + return new ConnectorProperty(key, value); + } + + private String key; + private String value; + + /** + * Default constructor for Hibernate. Do not use! + */ + protected ConnectorProperty() { + } + + private ConnectorProperty(String key, String value) { + this.key = key; + this.value = value; + } + + @NotEmpty(message = "property key not specified") + public String getKey() { + return key; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + +} diff --git a/libreplan-business/src/main/java/org/libreplan/business/common/entities/IConnectorBootstrap.java b/libreplan-business/src/main/java/org/libreplan/business/common/entities/IConnectorBootstrap.java new file mode 100644 index 000000000..d70373b32 --- /dev/null +++ b/libreplan-business/src/main/java/org/libreplan/business/common/entities/IConnectorBootstrap.java @@ -0,0 +1,33 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 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 . + */ + +package org.libreplan.business.common.entities; + +import org.libreplan.business.IDataBootstrap; + +/** + * Contract for {@link ConnectorBootstrap}. + * + * @author Manuel Rego Casasnovas + */ +public interface IConnectorBootstrap extends IDataBootstrap { + + void loadRequiredData(); + +} diff --git a/libreplan-business/src/main/java/org/libreplan/business/common/entities/JiraConfiguration.java b/libreplan-business/src/main/java/org/libreplan/business/common/entities/JiraConfiguration.java deleted file mode 100755 index f92b504ea..000000000 --- a/libreplan-business/src/main/java/org/libreplan/business/common/entities/JiraConfiguration.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * This file is part of LibrePlan - * - * Copyright (C) 2013 St. Antoniusziekenhuis - * - * 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 . - */ - -package org.libreplan.business.common.entities; - -import org.libreplan.business.common.BaseEntity; -import org.libreplan.business.costcategories.entities.TypeOfWorkHours; - -/** - * JiraConfiguration entity - * - * @author Miciele Ghiorghis - */ -public class JiraConfiguration extends BaseEntity { - - /** - * Code prefix for different entities integrated with JIRA. - */ - public static final String CODE_PREFIX = "JIRA-"; - - public static JiraConfiguration create() { - return create(new JiraConfiguration()); - } - - private boolean jiraActivated; - - private String jiraUrl; - - /** - * Stores one of the next 2 options: - *
    - *
  • A comma-separated list of labels
  • - *
  • A URL that will return a comma-separated list of labels
  • - *
- */ - private String jiraLabels; - - private String jiraUserId; - - private String jiraPassword; - - private TypeOfWorkHours jiraConnectorTypeOfWorkHours; - - /** - * Constructor for Hibernate. Do not use! - */ - protected JiraConfiguration() { - } - - public boolean isJiraActivated() { - return jiraActivated; - } - - public void setJiraActivated(boolean jiraActivated) { - this.jiraActivated = jiraActivated; - } - - public String getJiraUrl() { - return jiraUrl; - } - - public void setJiraUrl(String jiraUrl) { - this.jiraUrl = jiraUrl; - } - - public String getJiraLabels() { - return jiraLabels; - } - - public void setJiraLabels(String jiraLabels) { - this.jiraLabels = jiraLabels; - } - - public String getJiraUserId() { - return jiraUserId; - } - - public void setJiraUserId(String jiraUserId) { - this.jiraUserId = jiraUserId; - } - - public String getJiraPassword() { - return jiraPassword; - } - - public void setJiraPassword(String jiraPassword) { - this.jiraPassword = jiraPassword; - } - - public TypeOfWorkHours getJiraConnectorTypeOfWorkHours() { - return jiraConnectorTypeOfWorkHours; - } - - public void setJiraConnectorTypeOfWorkHours(TypeOfWorkHours typeOfWorkHours) { - jiraConnectorTypeOfWorkHours = typeOfWorkHours; - } - -} diff --git a/libreplan-business/src/main/java/org/libreplan/business/common/entities/JobClassNameEnum.java b/libreplan-business/src/main/java/org/libreplan/business/common/entities/JobClassNameEnum.java new file mode 100644 index 000000000..038d7c413 --- /dev/null +++ b/libreplan-business/src/main/java/org/libreplan/business/common/entities/JobClassNameEnum.java @@ -0,0 +1,51 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 St. Antoniusziekenhuis + * + * 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 . + */ + +package org.libreplan.business.common.entities; + + +/** + * Defines the job class package and name to be used as data type in + * {@link JobSchedulerConfiguration} + * + * @author Miciele Ghiorghis + */ +public enum JobClassNameEnum { + + IMPORT_ROSTER_FROM_TIM_JOB("org.libreplan.importers", "ImportRosterFromTimJob"), + EXPORT_TIMESHEET_TO_TIM_JOB("org.libreplan.importers","ExportTimesheetToTimJob"), + SYNC_ORDERELEMENTS_WITH_JIRA_ISSUES_JOB("org.libreplan.importers","JiraOrderElementSynchronizerJob"); + + private String packageName; + private String name; + + private JobClassNameEnum(String packageName, String name) { + this.packageName = packageName; + this.name = name; + } + + public String getPackageName() { + return packageName; + } + + public String getName() { + return name; + } + +} diff --git a/libreplan-business/src/main/java/org/libreplan/business/common/entities/JobSchedulerConfiguration.java b/libreplan-business/src/main/java/org/libreplan/business/common/entities/JobSchedulerConfiguration.java new file mode 100644 index 000000000..d9f970d8a --- /dev/null +++ b/libreplan-business/src/main/java/org/libreplan/business/common/entities/JobSchedulerConfiguration.java @@ -0,0 +1,140 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 St. Antoniusziekenhuis + * + * 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 . + */ + +package org.libreplan.business.common.entities; + +import org.apache.commons.lang.StringUtils; +import org.hibernate.validator.AssertTrue; +import org.hibernate.validator.NotNull; +import org.libreplan.business.common.BaseEntity; +import org.libreplan.business.common.IHumanIdentifiable; +import org.libreplan.business.common.Registry; +import org.libreplan.business.common.daos.IJobSchedulerConfigurationDAO; + +/** + * JobSchedulerConfiguration entity, represents parameters for the jobs to be + * scheduled. This entity is used by the SchedulerManager to + * schedule jobs and in UI to show the scheduler status. + * + * The jobGroup and jobName together forms a job key + * and non of the fields must be null. Moreover it should contain a valid + * cronExpression + * + * @author Miciele Ghiorghis + */ +public class JobSchedulerConfiguration extends BaseEntity implements + IHumanIdentifiable { + + public static JobSchedulerConfiguration create() { + return create(new JobSchedulerConfiguration()); + } + + /** + * Constructor for Hibernate. Do not use! + */ + protected JobSchedulerConfiguration() { + } + + private String jobGroup; + + private String jobName; + + private String cronExpression; + + private JobClassNameEnum jobClassName; + + private boolean schedule; + + private String connectorName; + + @NotNull(message = "job group not specified") + public String getJobGroup() { + return jobGroup; + } + + public void setJobGroup(String jobGroup) { + this.jobGroup = jobGroup; + } + + @NotNull(message = "job name not specified") + public String getJobName() { + return jobName; + } + + public void setJobName(String jobName) { + this.jobName = jobName; + } + + @NotNull(message = "cron expression not specified") + public String getCronExpression() { + return cronExpression; + } + + public void setCronExpression(String cronExpression) { + this.cronExpression = cronExpression; + } + + @NotNull(message = "job class name not specified") + public JobClassNameEnum getJobClassName() { + return jobClassName; + } + + public void setJobClassName(JobClassNameEnum jobClassName) { + this.jobClassName = jobClassName; + } + + public boolean isSchedule() { + return schedule; + } + + public void setSchedule(boolean schedule) { + this.schedule = schedule; + } + + public String getConnectorName() { + return connectorName; + } + + public void setConnectorName(String connectorName) { + this.connectorName = connectorName; + } + + @Override + public String getHumanId() { + return jobGroup == null ? "" : jobGroup; + } + + @AssertTrue(message = "job group and name are already being used") + public boolean checkConstraintUniqueJobGroupAndName() { + if (StringUtils.isBlank(jobGroup) && StringUtils.isBlank(jobName)) { + return true; + } + IJobSchedulerConfigurationDAO jobSchedulerConfigurationDAO = Registry + .getJobSchedulerConfigurationDAO(); + if (isNewObject()) { + return !jobSchedulerConfigurationDAO + .existsByJobGroupAndJobNameAnotherTransaction(this); + } else { + JobSchedulerConfiguration found = jobSchedulerConfigurationDAO + .findUniqueByJobGroupAndJobNameAnotherTransaction(jobGroup, + jobName); + return found == null || found.getId().equals(getId()); + } + } +} diff --git a/libreplan-business/src/main/java/org/libreplan/business/common/entities/PredefinedConnectorProperties.java b/libreplan-business/src/main/java/org/libreplan/business/common/entities/PredefinedConnectorProperties.java new file mode 100644 index 000000000..ef0b84200 --- /dev/null +++ b/libreplan-business/src/main/java/org/libreplan/business/common/entities/PredefinedConnectorProperties.java @@ -0,0 +1,54 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 St. Antoniusziekenhuis + * + * 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 . + */ + +package org.libreplan.business.common.entities; + +import static org.libreplan.business.i18n.I18nHelper._; + +/** + * Simply class to keep constants of {@link ConnectorProperty properties} for + * LibrePlan {@link Connector connectors}. + * + * @author Miciele Ghiorghis + * @author Manuel Rego Casasnovas + */ +public class PredefinedConnectorProperties { + + // Generic + public static String ACTIVATED = _("Activated"); + public static String SERVER_URL = _("Server URL"); + public static String USERNAME = _("Username"); + public static String PASSWORD = _("Password"); + + // Specific for Tim + public static String TIM_NR_DAYS_TIMESHEET = _("Number of days timesheet to Tim"); + public static String TIM_NR_DAYS_ROSTER = _("Number of days roster from Tim"); + public static String TIM_PRODUCTIVITY_FACTOR = _("Productivity factor"); + public static String TIM_DEPARTAMENTS_IMPORT_ROSTER = _("Department IDs to import toster"); + + // Specific for JIRA + public static String JIRA_LABELS = _("JIRA labels: comma-separated list of labels or URL"); + public static String JIRA_HOURS_TYPE = _("Hours type"); + + /** + * Code prefix for different entities integrated with JIRA. + */ + public static final String JIRA_CODE_PREFIX = "JIRA-"; + +} diff --git a/libreplan-business/src/main/java/org/libreplan/business/common/entities/PredefinedConnectors.java b/libreplan-business/src/main/java/org/libreplan/business/common/entities/PredefinedConnectors.java new file mode 100644 index 000000000..b51adc8a1 --- /dev/null +++ b/libreplan-business/src/main/java/org/libreplan/business/common/entities/PredefinedConnectors.java @@ -0,0 +1,72 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 St. Antoniusziekenhuis + * + * 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 . + */ + +package org.libreplan.business.common.entities; + +import java.util.Arrays; +import java.util.List; + +/** + * Defines the LibrePlan {@link Connector Connectors} together with its + * configuration properties. + * + * @author Miciele Ghiorghis + * @author Manuel Rego Casasnovas + */ +public enum PredefinedConnectors { + + TIM("Tim", + ConnectorProperty.create(PredefinedConnectorProperties.ACTIVATED, "N"), + ConnectorProperty.create(PredefinedConnectorProperties.SERVER_URL, ""), + ConnectorProperty.create(PredefinedConnectorProperties.USERNAME, ""), + ConnectorProperty.create(PredefinedConnectorProperties.PASSWORD, ""), + ConnectorProperty.create(PredefinedConnectorProperties.TIM_NR_DAYS_TIMESHEET, "7"), + ConnectorProperty.create(PredefinedConnectorProperties.TIM_NR_DAYS_ROSTER, "90"), + ConnectorProperty.create(PredefinedConnectorProperties.TIM_PRODUCTIVITY_FACTOR, "100"), + ConnectorProperty.create( + PredefinedConnectorProperties.TIM_DEPARTAMENTS_IMPORT_ROSTER, + "0")), + JIRA("Jira", + ConnectorProperty.create(PredefinedConnectorProperties.ACTIVATED, "N"), + ConnectorProperty.create(PredefinedConnectorProperties.SERVER_URL, ""), + ConnectorProperty.create(PredefinedConnectorProperties.USERNAME, ""), + ConnectorProperty.create(PredefinedConnectorProperties.PASSWORD, ""), + ConnectorProperty + .create(PredefinedConnectorProperties.JIRA_LABELS, ""), + ConnectorProperty.create( + PredefinedConnectorProperties.JIRA_HOURS_TYPE, "Default")); + + private String name; + private List properties; + + private PredefinedConnectors(String name, + ConnectorProperty... properties) { + this.name = name; + this.properties = Arrays.asList(properties); + } + + public String getName() { + return name; + } + + public List getProperties() { + return properties; + } + +} diff --git a/libreplan-business/src/main/java/org/libreplan/business/costcategories/daos/TypeOfWorkHoursDAO.java b/libreplan-business/src/main/java/org/libreplan/business/costcategories/daos/TypeOfWorkHoursDAO.java index 59573128c..b98e9f1b5 100644 --- a/libreplan-business/src/main/java/org/libreplan/business/costcategories/daos/TypeOfWorkHoursDAO.java +++ b/libreplan-business/src/main/java/org/libreplan/business/costcategories/daos/TypeOfWorkHoursDAO.java @@ -29,8 +29,12 @@ import org.hibernate.Criteria; import org.hibernate.criterion.Order; import org.hibernate.criterion.Restrictions; import org.libreplan.business.common.daos.IConfigurationDAO; +import org.libreplan.business.common.daos.IConnectorDAO; import org.libreplan.business.common.daos.IntegrationEntityDAO; import org.libreplan.business.common.entities.Configuration; +import org.libreplan.business.common.entities.Connector; +import org.libreplan.business.common.entities.PredefinedConnectorProperties; +import org.libreplan.business.common.entities.PredefinedConnectors; import org.libreplan.business.common.exceptions.InstanceNotFoundException; import org.libreplan.business.common.exceptions.ValidationException; import org.libreplan.business.costcategories.entities.HourCost; @@ -56,6 +60,9 @@ public class TypeOfWorkHoursDAO extends IntegrationEntityDAO @Autowired private IConfigurationDAO configurationDAO; + @Autowired + private IConnectorDAO connectorDAO; + @Override public TypeOfWorkHours findUniqueByCode(TypeOfWorkHours typeOfWorkHours) throws InstanceNotFoundException { @@ -194,13 +201,17 @@ public class TypeOfWorkHoursDAO extends IntegrationEntityDAO } private void checkIsJiraConnectorTypeOfWorkHours(TypeOfWorkHours type) { - Configuration configuration = configurationDAO.getConfiguration(); - if (configuration.getJiraConfiguration() - .getJiraConnectorTypeOfWorkHours().getId().equals(type.getId())) { - throw ValidationException - .invalidValue( - "Cannot delete the type of work hours. It is configured as type of work hours for JIRA connector.", - type); + Connector connector = connectorDAO + .findUniqueByName(PredefinedConnectors.JIRA.getName()); + if (connector != null) { + String name = connector.getPropertiesAsMap().get( + PredefinedConnectorProperties.JIRA_HOURS_TYPE); + if (name.equals(type.getName())) { + throw ValidationException + .invalidValue( + "Cannot delete the type of work hours. It is configured as type of work hours for JIRA connector.", + type); + } } } diff --git a/libreplan-business/src/main/java/org/libreplan/business/costcategories/entities/TypeOfWorkHours.java b/libreplan-business/src/main/java/org/libreplan/business/costcategories/entities/TypeOfWorkHours.java index f26d72b22..0f09a66c1 100644 --- a/libreplan-business/src/main/java/org/libreplan/business/costcategories/entities/TypeOfWorkHours.java +++ b/libreplan-business/src/main/java/org/libreplan/business/costcategories/entities/TypeOfWorkHours.java @@ -30,6 +30,9 @@ import org.hibernate.validator.NotNull; import org.libreplan.business.common.IHumanIdentifiable; import org.libreplan.business.common.IntegrationEntity; import org.libreplan.business.common.Registry; +import org.libreplan.business.common.entities.Connector; +import org.libreplan.business.common.entities.PredefinedConnectorProperties; +import org.libreplan.business.common.entities.PredefinedConnectors; import org.libreplan.business.common.exceptions.InstanceNotFoundException; import org.libreplan.business.costcategories.daos.ITypeOfWorkHoursDAO; @@ -168,11 +171,13 @@ public class TypeOfWorkHours extends IntegrationEntity implements IHumanIdentifi @AssertTrue(message = "type of work hours for JIRA connector cannot be disabled") public boolean checkJiraConnectorTypeOfWorkHoursNotDisabled() { if (!isNewObject() && !getEnabled()) { - TypeOfWorkHours typeOfWorkHours = Registry.getConfigurationDAO() - .getConfiguration().getJiraConfiguration() - .getJiraConnectorTypeOfWorkHours(); - if (typeOfWorkHours.getId().equals(getId())) { - return false; + Connector connector = Registry.getConnectorDAO().findUniqueByName( + PredefinedConnectors.JIRA.getName()); + if (connector != null) { + if (this.name.equals(connector.getPropertiesAsMap().get( + PredefinedConnectorProperties.JIRA_HOURS_TYPE))) { + return false; + } } } diff --git a/libreplan-business/src/main/java/org/libreplan/business/orders/daos/IOrderSyncInfoDAO.java b/libreplan-business/src/main/java/org/libreplan/business/orders/daos/IOrderSyncInfoDAO.java new file mode 100644 index 000000000..d81d7078f --- /dev/null +++ b/libreplan-business/src/main/java/org/libreplan/business/orders/daos/IOrderSyncInfoDAO.java @@ -0,0 +1,111 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 St. Antoniusziekenhuis + * + * 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 . + */ + +package org.libreplan.business.orders.daos; + +import java.util.List; + +import org.libreplan.business.common.daos.IGenericDAO; +import org.libreplan.business.orders.entities.Order; +import org.libreplan.business.orders.entities.OrderSyncInfo; + +/** + * Contract for {@link OrderSyncInfoDAO} + * + * @author Miciele Ghiorghis + */ +public interface IOrderSyncInfoDAO extends IGenericDAO { + + /** + * Search last synchronized info for the specified + * {@link Order} and connectorName + * + * @param order + * the order to search for + * @param connectorName + * the connector name + * + * @return Last synchronized info + */ + OrderSyncInfo findLastSynchronizedInfoByOrderAndConnectorName(Order order, + String connectorName); + + /** + * Search last synchronized infos for the specified + * {@link Order} and connectorName + * + * @param order + * the order to search for + * @param connectorName + * the connector name + * @return list of last synchronized infos + */ + List findLastSynchronizedInfosByOrderAndConnectorName( + Order order, String connectorName); + + /** + * Searches and returns {@link OrderSyncInfo} for the specified + * key and connectorName + * + * @param key + * the unique key with in connector id + * @param order + * the order + * @param connectorName + * the connector name + */ + OrderSyncInfo findByKeyOrderAndConnectorName(String key, Order order, + String connectorName); + + /** + * Finds the {@link OrderSyncInfo}s for the specified + * connectorName + * + * @param connectorName + * the connector name + * @return a list of OrderSyncInfo if found and null if not + */ + List findByConnectorName(String connectorName); + + /** + * Returns true if there exists other {@link OrderSyncInfo} with the same + * {@link OrderSyncInfo#getKey()}, + * {@link OrderSyncInfo#getOrder()} and + * {@link OrderSyncInfo#getConnectorName()} + * + * @param orderSyncInfo + * the {@link OrderSyncInfo} + */ + boolean existsByKeyOrderAndConnectorNameAnotherTransaction( + OrderSyncInfo orderSyncInfo); + + /** + * Returns unique {@link OrderSyncInfo} for the specified key, + * order and connectorName + * + * @param key + * the key + * @param order + * an order + * @param connectorName + * the name of the connector + */ + OrderSyncInfo findUniqueByKeyOrderAndConnectorNameAnotherTransaction( + String key, Order order, String connectorName); +} diff --git a/libreplan-business/src/main/java/org/libreplan/business/orders/daos/OrderDAO.java b/libreplan-business/src/main/java/org/libreplan/business/orders/daos/OrderDAO.java index 9de3542a0..f8e563dc9 100644 --- a/libreplan-business/src/main/java/org/libreplan/business/orders/daos/OrderDAO.java +++ b/libreplan-business/src/main/java/org/libreplan/business/orders/daos/OrderDAO.java @@ -442,12 +442,16 @@ public class OrderDAO extends IntegrationEntityDAO implements } else { String strQuery = "SELECT oa.order.id " + "FROM OrderAuthorization oa " - + "WHERE oa.user = :user " - + "OR oa.profile IN (:profiles) "; + + "WHERE oa.user = :user "; + if (!user.getProfiles().isEmpty()) { + strQuery += "OR oa.profile IN (:profiles) "; + } Query query = getSession().createQuery(strQuery); query.setParameter("user", user); - query.setParameterList("profiles", user.getProfiles()); + if (!user.getProfiles().isEmpty()) { + query.setParameterList("profiles", user.getProfiles()); + } return query.list(); } diff --git a/libreplan-business/src/main/java/org/libreplan/business/orders/daos/OrderSyncInfoDAO.java b/libreplan-business/src/main/java/org/libreplan/business/orders/daos/OrderSyncInfoDAO.java new file mode 100644 index 000000000..eb4c916bf --- /dev/null +++ b/libreplan-business/src/main/java/org/libreplan/business/orders/daos/OrderSyncInfoDAO.java @@ -0,0 +1,113 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 St. Antoniusziekenhuis + * + * 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 . + */ + +package org.libreplan.business.orders.daos; + +import java.util.List; + +import org.hibernate.Criteria; +import org.hibernate.criterion.Restrictions; +import org.libreplan.business.common.daos.GenericDAOHibernate; +import org.libreplan.business.orders.entities.Order; +import org.libreplan.business.orders.entities.OrderSyncInfo; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +/** + * DAO for {@link OrderSyncInfo} + * + * @author Miciele Ghiorghis + */ +@Repository +@Scope(BeanDefinition.SCOPE_SINGLETON) +public class OrderSyncInfoDAO extends GenericDAOHibernate + implements IOrderSyncInfoDAO { + + @Override + public OrderSyncInfo findLastSynchronizedInfoByOrderAndConnectorName( + Order order, String connectorName) { + List orderSyncInfoList = findLastSynchronizedInfosByOrderAndConnectorName( + order, connectorName); + if (orderSyncInfoList == null || orderSyncInfoList.isEmpty()) { + return null; + } + return orderSyncInfoList.get(0); + } + + @Override + @SuppressWarnings("unchecked") + public List findLastSynchronizedInfosByOrderAndConnectorName( + Order order, String connectorName) { + Criteria criteria = getSession().createCriteria(OrderSyncInfo.class); + criteria.add(Restrictions.eq("order", order)); + criteria.add(Restrictions.eq("connectorName", connectorName)); + criteria.addOrder(org.hibernate.criterion.Order.desc("lastSyncDate")); + return criteria.list(); + } + + @Override + public OrderSyncInfo findByKeyOrderAndConnectorName(String key, + Order order, String connectorName) { + Criteria criteria = getSession().createCriteria(OrderSyncInfo.class); + criteria.add(Restrictions.eq("key", key)); + criteria.add(Restrictions.eq("order", order)); + criteria.add(Restrictions.eq("connectorName", connectorName)); + return (OrderSyncInfo) criteria.uniqueResult(); + } + + @Override + public List findByConnectorName(String connectorName) { + Criteria criteria = getSession().createCriteria(OrderSyncInfo.class); + criteria.add(Restrictions.eq("connectorName", connectorName)); + return criteria.list(); + } + + @Override + @Transactional(readOnly = true, propagation = Propagation.REQUIRES_NEW) + public boolean existsByKeyOrderAndConnectorNameAnotherTransaction( + OrderSyncInfo orderSyncInfo) { + return existsOtherOrderSyncInfoByKeyOrderAndConnectorName(orderSyncInfo); + } + + /** + * Returns true if other {@link OrderSyncInfo} which is the same + * as the given {@link OrderSyncInfo} already exists + * + * @param orderSyncInfo + * the {@link OrderSyncInfo} + */ + private boolean existsOtherOrderSyncInfoByKeyOrderAndConnectorName( + OrderSyncInfo orderSyncInfo) { + OrderSyncInfo found = findByKeyOrderAndConnectorName( + orderSyncInfo.getKey(), orderSyncInfo.getOrder(), + orderSyncInfo.getConnectorName()); + return found != null && found != orderSyncInfo; + } + + @Override + @Transactional(readOnly = true, propagation = Propagation.REQUIRES_NEW) + public OrderSyncInfo findUniqueByKeyOrderAndConnectorNameAnotherTransaction( + String key, Order order, String connectorName) { + return findByKeyOrderAndConnectorName(key, order, connectorName); + } + +} diff --git a/libreplan-business/src/main/java/org/libreplan/business/orders/entities/Order.java b/libreplan-business/src/main/java/org/libreplan/business/orders/entities/Order.java index c2436a5a9..2cc90525e 100644 --- a/libreplan-business/src/main/java/org/libreplan/business/orders/entities/Order.java +++ b/libreplan-business/src/main/java/org/libreplan/business/orders/entities/Order.java @@ -120,8 +120,6 @@ public class Order extends OrderLineGroup implements Comparable { private Set customerCommunications = new HashSet(); - private String importedLabel; - @Valid private SortedSet deliveringDates = new TreeSet( new DeliverDateComparator()); @@ -693,14 +691,6 @@ public class Order extends OrderLineGroup implements Comparable { return true; } - public String getImportedLabel() { - return importedLabel; - } - - public void setImportedLabel(String importedLabel) { - this.importedLabel = importedLabel; - } - public void calculateAndSetTotalHours() { int result = 0; for (OrderElement orderElement : this.getChildren()) { diff --git a/libreplan-business/src/main/java/org/libreplan/business/orders/entities/OrderElement.java b/libreplan-business/src/main/java/org/libreplan/business/orders/entities/OrderElement.java index 73fabd95e..367ec5bce 100644 --- a/libreplan-business/src/main/java/org/libreplan/business/orders/entities/OrderElement.java +++ b/libreplan-business/src/main/java/org/libreplan/business/orders/entities/OrderElement.java @@ -52,7 +52,7 @@ import org.libreplan.business.advance.exceptions.DuplicateValueTrueReportGlobalA import org.libreplan.business.common.IntegrationEntity; import org.libreplan.business.common.Registry; import org.libreplan.business.common.daos.IIntegrationEntityDAO; -import org.libreplan.business.common.entities.JiraConfiguration; +import org.libreplan.business.common.entities.PredefinedConnectorProperties; import org.libreplan.business.common.exceptions.ValidationException; import org.libreplan.business.labels.entities.Label; import org.libreplan.business.materials.entities.MaterialAssignment; @@ -1568,6 +1568,24 @@ public abstract class OrderElement extends IntegrationEntity implements return workReportLineDAO.findByOrderElementAndChildren(this, sortedByDate); } + /** + * Gets workReportLines of this order-element between the specified + * startDate and endDate + * + * @param startDate + * the startDate + * @param endDate + * the endDate + * @param sortedByDate + * @return list of workReportLines + */ + public List getWorkReportLines(Date startDate, + Date endDate, boolean sortedByDate) { + IWorkReportLineDAO workReportLineDAO = Registry.getWorkReportLineDAO(); + return workReportLineDAO.findByOrderElementAndChildrenFilteredByDate( + this, startDate, endDate, sortedByDate); + } + /** * Checks if it has nay consolidated advance, if not checks if any parent * has it @@ -1668,7 +1686,7 @@ public abstract class OrderElement extends IntegrationEntity implements if (code == null) { return false; } - return code.startsWith(JiraConfiguration.CODE_PREFIX); + return code.startsWith(PredefinedConnectorProperties.JIRA_CODE_PREFIX); } public boolean isConvertedToContainer() { diff --git a/libreplan-business/src/main/java/org/libreplan/business/orders/entities/OrderSyncInfo.java b/libreplan-business/src/main/java/org/libreplan/business/orders/entities/OrderSyncInfo.java new file mode 100644 index 000000000..3cec95fec --- /dev/null +++ b/libreplan-business/src/main/java/org/libreplan/business/orders/entities/OrderSyncInfo.java @@ -0,0 +1,130 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 St. Antoniusziekenhuis + * + * 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 . + */ + +package org.libreplan.business.orders.entities; + +import java.util.Date; + +import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang.Validate; +import org.hibernate.validator.AssertTrue; +import org.hibernate.validator.NotNull; +import org.libreplan.business.common.BaseEntity; +import org.libreplan.business.common.Registry; +import org.libreplan.business.common.entities.Connector; +import org.libreplan.business.orders.daos.IOrderSyncInfoDAO; + +/** + * OrderSyncInfo entity. This entity holds order synchronization info. Each time + * that order synchronization is successfully completed, an instance of this + * entity is created or updated and saved to DB to hold the synchronized info. + * This info is then displayed in UI. + * + * This entity contains the following fields: + *
    + *
  • lastSyncDate: last date where synchronization took place
  • + *
  • key: an identifier, which connector's key is last synchronized
  • + *
  • connectorName: the name of the {@link Connector} that has running the + * synchronization
  • + *
  • order: order that is synchronized
  • + *
+ * + * @author Miciele Ghiorghis + */ +public class OrderSyncInfo extends BaseEntity { + + private Date lastSyncDate; + private String key; + private String connectorName; + private Order order; + + public static OrderSyncInfo create(String key, Order order, + String connectorName) { + Validate.notEmpty(key); + Validate.notNull(order); + Validate.notEmpty(connectorName); + return create(new OrderSyncInfo(key, order, connectorName)); + } + + /** + * Constructor for Hibernate. Do not use! + */ + protected OrderSyncInfo() { + } + + private OrderSyncInfo(String key, Order order, String connectorName) { + this.lastSyncDate = new Date(); + this.key = key; + this.order = order; + this.connectorName = connectorName; + } + + @NotNull(message = "last synchronized date not specified") + public Date getLastSyncDate() { + return lastSyncDate; + } + + public void setLastSyncDate(Date lastSyncDate) { + this.lastSyncDate = lastSyncDate; + } + + @NotNull(message = "key not specified") + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + @NotNull(message = "connector name not specified") + public String getConnectorName() { + return connectorName; + } + + public void setConnectorName(String connectorName) { + this.connectorName = connectorName; + } + + public Order getOrder() { + return order; + } + + public void setOrder(Order order) { + this.order = order; + } + + @AssertTrue(message = "project sync info is already being used") + public boolean checkConstraintUniqueOrderSyncInfo() { + if (StringUtils.isBlank(key) && order == null + && StringUtils.isBlank(connectorName)) { + return true; + } + IOrderSyncInfoDAO orderSyncInfoDAO = Registry.getOrderSyncInfoDAO(); + if (isNewObject()) { + return !orderSyncInfoDAO + .existsByKeyOrderAndConnectorNameAnotherTransaction(this); + } else { + OrderSyncInfo found = orderSyncInfoDAO + .findUniqueByKeyOrderAndConnectorNameAnotherTransaction( + key, order, connectorName); + return found == null || found.getId().equals(getId()); + } + } +} diff --git a/libreplan-business/src/main/java/org/libreplan/business/users/bootstrap/PredefinedProfiles.java b/libreplan-business/src/main/java/org/libreplan/business/users/bootstrap/PredefinedProfiles.java index 897e321e0..b7c0f7fee 100644 --- a/libreplan-business/src/main/java/org/libreplan/business/users/bootstrap/PredefinedProfiles.java +++ b/libreplan-business/src/main/java/org/libreplan/business/users/bootstrap/PredefinedProfiles.java @@ -29,6 +29,7 @@ import static org.libreplan.business.users.entities.UserRole.ROLE_ESTIMATED_PLAN import static org.libreplan.business.users.entities.UserRole.ROLE_EXPENSES; import static org.libreplan.business.users.entities.UserRole.ROLE_HOURS_TYPES; import static org.libreplan.business.users.entities.UserRole.ROLE_HOURS_WORKED_PER_RESOURCE_REPORT; +import static org.libreplan.business.users.entities.UserRole.ROLE_JOB_SCHEDULING; import static org.libreplan.business.users.entities.UserRole.ROLE_LABELS; import static org.libreplan.business.users.entities.UserRole.ROLE_MACHINES; import static org.libreplan.business.users.entities.UserRole.ROLE_MAIN_SETTINGS; @@ -74,7 +75,7 @@ import org.libreplan.business.users.entities.UserRole; public enum PredefinedProfiles { SYSTEMS_ADMINISTRATOR("Systems Administrator", ROLE_MAIN_SETTINGS, - ROLE_USER_ACCOUNTS, ROLE_PROFILES), + ROLE_USER_ACCOUNTS, ROLE_PROFILES, ROLE_JOB_SCHEDULING), PROJECT_MANAGER("Project Manager", ROLE_READ_ALL_PROJECTS, ROLE_EDIT_ALL_PROJECTS, ROLE_CREATE_PROJECTS, ROLE_PLANNING, diff --git a/libreplan-business/src/main/java/org/libreplan/business/users/entities/UserRole.java b/libreplan-business/src/main/java/org/libreplan/business/users/entities/UserRole.java index d6ee74b29..20495916d 100644 --- a/libreplan-business/src/main/java/org/libreplan/business/users/entities/UserRole.java +++ b/libreplan-business/src/main/java/org/libreplan/business/users/entities/UserRole.java @@ -66,6 +66,7 @@ public enum UserRole { ROLE_MAIN_SETTINGS(_("Main Settings")), ROLE_USER_ACCOUNTS(_("User Accounts")), ROLE_PROFILES(_("Profiles")), + ROLE_JOB_SCHEDULING(_("Job Scheduling")), ROLE_COMPANIES(_("Companies")), ROLE_SEND_TO_SUBCONTRACTORS(_("Send To Subcontractors")), ROLE_RECEIVED_FROM_SUBCONTRACTORS(_("Received From Subcontractors")), diff --git a/libreplan-business/src/main/java/org/libreplan/business/workreports/daos/IWorkReportLineDAO.java b/libreplan-business/src/main/java/org/libreplan/business/workreports/daos/IWorkReportLineDAO.java index 39f8ddd1f..b05924bcc 100644 --- a/libreplan-business/src/main/java/org/libreplan/business/workreports/daos/IWorkReportLineDAO.java +++ b/libreplan-business/src/main/java/org/libreplan/business/workreports/daos/IWorkReportLineDAO.java @@ -76,5 +76,12 @@ public interface IWorkReportLineDAO extends List findByOrderElementAndWorkReports( OrderElement orderElement, List workReports); + /** + * Returns the {@link WorkReportLine}s of the specified + * orderElement specified between start date and + * end date + */ + List findByOrderElementAndChildrenFilteredByDate( + OrderElement orderElement, Date start, Date end, boolean sortByDate); } diff --git a/libreplan-business/src/main/java/org/libreplan/business/workreports/daos/WorkReportLineDAO.java b/libreplan-business/src/main/java/org/libreplan/business/workreports/daos/WorkReportLineDAO.java index 153e707c2..371aea2c3 100644 --- a/libreplan-business/src/main/java/org/libreplan/business/workreports/daos/WorkReportLineDAO.java +++ b/libreplan-business/src/main/java/org/libreplan/business/workreports/daos/WorkReportLineDAO.java @@ -216,4 +216,33 @@ public class WorkReportLineDAO extends IntegrationEntityDAO return (List) criteria.list(); } + @Transactional(readOnly = true) + public List findByOrderElementAndChildrenFilteredByDate( + OrderElement orderElement, Date start, Date end, boolean sortByDate) { + + if (orderElement.isNewObject()) { + return new ArrayList(); + } + + Collection orderElements = orderElement.getAllChildren(); + orderElements.add(orderElement); + + // Prepare criteria + final Criteria criteria = getSession().createCriteria( + WorkReportLine.class); + criteria.add(Restrictions.in("orderElement", orderElements)); + + if (start != null) { + criteria.add(Restrictions.ge("date", start)); + } + if (end != null) { + criteria.add(Restrictions.le("date", end)); + } + if (sortByDate) { + criteria.addOrder(org.hibernate.criterion.Order.asc("date")); + } + return criteria.list(); + + } + } diff --git a/libreplan-business/src/main/resources/db.changelog-1.3.xml b/libreplan-business/src/main/resources/db.changelog-1.3.xml index 72aa5c6a6..6221e8db3 100644 --- a/libreplan-business/src/main/resources/db.changelog-1.3.xml +++ b/libreplan-business/src/main/resources/db.changelog-1.3.xml @@ -221,106 +221,100 @@ columnDataType="INTEGER" /> - - - - Add new column jira_activated with default value FALSE to configuration table - - - - - - + + + Create new table order_sync_info + + + + + + + + + + + + + + + + + + + - - - Add new column jira_url in table configuration - - - - - + + + Create tables related to Connector entity + + + + + + + + + + + - - - Add new column jira_label_url in table configuration - - - - - + + + + + + + + + + + + - - - Add new column jira_user_id in table configuration - - - - - + - - - Add new column jira_user_id in table configuration - - - - - - - - - Add new column imported_label in table order_table - - - - - - - - - Add new column jira_connector_type_of_work_hours to configuration - table. - - - - + baseColumnNames="connector_id" + baseTableName="connector_property" + constraintName="connector_property_connector_id_fkey" + deferrable="false" initiallyDeferred="false" + onDelete="NO ACTION" onUpdate="NO ACTION" + referencedColumnNames="id" + referencedTableName="connector" + referencesUniqueColumn="false"/> - - - - Rename column jira_label_url to jira_labels in configuration table - - - - - - Change column jira_labels in configuration to TEXT - - - - - Change column jira_labels in configuration to TEXT in MySQL - ALTER TABLE configuration MODIFY jira_labels TEXT + + + Create new table job_scheduler_configuration + + + + + + + + + + + + + + + + + + + + + + - \ No newline at end of file + diff --git a/libreplan-business/src/main/resources/libreplan-business-spring-config.xml b/libreplan-business/src/main/resources/libreplan-business-spring-config.xml index 10dda88a4..e5e183a1b 100644 --- a/libreplan-business/src/main/resources/libreplan-business-spring-config.xml +++ b/libreplan-business/src/main/resources/libreplan-business-spring-config.xml @@ -90,7 +90,13 @@ org/libreplan/business/expensesheets/entities/ExpenseSheets.hbm.xml - + + org/libreplan/business/common/entities/Connector.hbm.xml + + + org/libreplan/business/common/entities/JobSchedulerConfiguration.hbm.xml + + diff --git a/libreplan-business/src/main/resources/org/libreplan/business/common/entities/Configuration.hbm.xml b/libreplan-business/src/main/resources/org/libreplan/business/common/entities/Configuration.hbm.xml index e06c71260..05c238015 100644 --- a/libreplan-business/src/main/resources/org/libreplan/business/common/entities/Configuration.hbm.xml +++ b/libreplan-business/src/main/resources/org/libreplan/business/common/entities/Configuration.hbm.xml @@ -120,18 +120,6 @@ - - - - - - - - - - - diff --git a/libreplan-business/src/main/resources/org/libreplan/business/common/entities/Connector.hbm.xml b/libreplan-business/src/main/resources/org/libreplan/business/common/entities/Connector.hbm.xml new file mode 100644 index 000000000..4a1b86870 --- /dev/null +++ b/libreplan-business/src/main/resources/org/libreplan/business/common/entities/Connector.hbm.xml @@ -0,0 +1,30 @@ + + + + + + + + 100 + + + + + + + + + + + + + + + + + + + + diff --git a/libreplan-business/src/main/resources/org/libreplan/business/common/entities/JobSchedulerConfiguration.hbm.xml b/libreplan-business/src/main/resources/org/libreplan/business/common/entities/JobSchedulerConfiguration.hbm.xml new file mode 100644 index 000000000..822636ef6 --- /dev/null +++ b/libreplan-business/src/main/resources/org/libreplan/business/common/entities/JobSchedulerConfiguration.hbm.xml @@ -0,0 +1,27 @@ + + + + + + + + 100 + + + + + + + + + + + org.libreplan.business.common.entities.JobClassNameEnum + + + + + + + diff --git a/libreplan-business/src/main/resources/org/libreplan/business/orders/entities/Orders.hbm.xml b/libreplan-business/src/main/resources/org/libreplan/business/orders/entities/Orders.hbm.xml index 6905d51a0..fc2f5abf5 100644 --- a/libreplan-business/src/main/resources/org/libreplan/business/orders/entities/Orders.hbm.xml +++ b/libreplan-business/src/main/resources/org/libreplan/business/orders/entities/Orders.hbm.xml @@ -126,9 +126,6 @@ - - - @@ -300,4 +297,20 @@ + + + + 100 + + + + + + + + + + + + diff --git a/libreplan-webapp/pom.xml b/libreplan-webapp/pom.xml index 95cd2e81b..5503136b6 100644 --- a/libreplan-webapp/pom.xml +++ b/libreplan-webapp/pom.xml @@ -330,6 +330,11 @@ org.springframework spring-test + + + org.springframework + spring-context-support + org.springframework.security @@ -481,5 +486,10 @@ commons-io commons-io + + + org.quartz-scheduler + quartz + diff --git a/libreplan-webapp/src/main/java/org/libreplan/importers/ExportTimesheetToTimJob.java b/libreplan-webapp/src/main/java/org/libreplan/importers/ExportTimesheetToTimJob.java new file mode 100644 index 000000000..b5add4f97 --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/ExportTimesheetToTimJob.java @@ -0,0 +1,62 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 St. Antoniusziekenhuis + * + * 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 . + */ + +package org.libreplan.importers; + +import java.util.List; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.libreplan.business.common.entities.ConnectorException; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; +import org.springframework.context.ApplicationContext; +import org.springframework.scheduling.quartz.QuartzJobBean; + +/** + * A job that exports time sheets to Tim SOAP server + * + * @author Miciele Ghiorghis + */ +public class ExportTimesheetToTimJob extends QuartzJobBean { + private static final Log LOG = LogFactory + .getLog(ExportTimesheetToTimJob.class); + + @Override + protected void executeInternal(JobExecutionContext context) + throws JobExecutionException { + ApplicationContext applicationContext = (ApplicationContext) context + .getJobDetail().getJobDataMap().get("applicationContext"); + + IExportTimesheetsToTim exportTimesheetsToTim = (IExportTimesheetsToTim) applicationContext + .getBean("exportTimesheetsToTim"); + + try { + List syncInfos = exportTimesheetsToTim + .exportTimesheets(); + + LOG.info("Export scuccessful: " + + (syncInfos == null || syncInfos.isEmpty())); + + } catch (ConnectorException e) { + LOG.error("Export timesheet to Tim failed", e); + } + } + +} diff --git a/libreplan-webapp/src/main/java/org/libreplan/importers/ExportTimesheetsToTim.java b/libreplan-webapp/src/main/java/org/libreplan/importers/ExportTimesheetsToTim.java new file mode 100644 index 000000000..8ad2a329d --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/ExportTimesheetsToTim.java @@ -0,0 +1,341 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 St. Antoniusziekenhuis + * + * 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 . + */ + +package org.libreplan.importers; + +import static org.libreplan.web.I18nHelper._; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.joda.time.LocalDate; +import org.libreplan.business.common.IAdHocTransactionService; +import org.libreplan.business.common.IOnTransaction; +import org.libreplan.business.common.daos.IConnectorDAO; +import org.libreplan.business.common.entities.Connector; +import org.libreplan.business.common.entities.ConnectorException; +import org.libreplan.business.common.entities.PredefinedConnectorProperties; +import org.libreplan.business.common.entities.PredefinedConnectors; +import org.libreplan.business.common.exceptions.InstanceNotFoundException; +import org.libreplan.business.orders.daos.IOrderSyncInfoDAO; +import org.libreplan.business.orders.entities.Order; +import org.libreplan.business.orders.entities.OrderSyncInfo; +import org.libreplan.business.resources.daos.IWorkerDAO; +import org.libreplan.business.resources.entities.Worker; +import org.libreplan.business.workreports.entities.WorkReportLine; +import org.libreplan.importers.tim.DurationDTO; +import org.libreplan.importers.tim.PersonDTO; +import org.libreplan.importers.tim.ProductDTO; +import org.libreplan.importers.tim.RegistrationDateDTO; +import org.libreplan.importers.tim.TimOptions; +import org.libreplan.importers.tim.TimeRegistrationDTO; +import org.libreplan.importers.tim.TimeRegistrationRequestDTO; +import org.libreplan.importers.tim.TimeRegistrationResponseDTO; +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; + +/** + * Implementation of export timesheets to tim + * + * @author Miciele Ghiorghis + */ +@Component +@Scope(BeanDefinition.SCOPE_PROTOTYPE) +public class ExportTimesheetsToTim implements IExportTimesheetsToTim { + + private static final Log LOG = LogFactory + .getLog(ExportTimesheetsToTim.class); + + @Autowired + private IWorkerDAO workerDAO; + + @Autowired + IOrderSyncInfoDAO orderSyncInfoDAO; + + @Autowired + private IAdHocTransactionService adHocTransactionService; + + @Autowired + private IConnectorDAO connectorDAO; + + private SynchronizationInfo synchronizationInfo; + + @Override + @Transactional(readOnly = true) + public List exportTimesheets() throws ConnectorException { + Connector connector = getTimConnector(); + if (connector == null) { + throw new ConnectorException(_("Tim connector not found")); + } + if (!connector.areConnectionValuesValid()) { + throw new ConnectorException( + _("Connection values of Tim connector are invalid")); + } + + synchronizationInfo = new SynchronizationInfo(_("Export")); + + List syncInfos = new ArrayList(); + + List orderSyncInfos = orderSyncInfoDAO.findByConnectorName(PredefinedConnectors.TIM.getName()); + if (orderSyncInfos == null || orderSyncInfos.isEmpty()) { + LOG.warn("No items found in 'OrderSyncInfo' to export to Tim"); + synchronizationInfo.addFailedReason(_("No items found in 'OrderSyncInfo' to export to Tim")); + syncInfos.add(synchronizationInfo); + return syncInfos; + } + + for (OrderSyncInfo orderSyncInfo : orderSyncInfos) { + LOG.info("Exporting '" + orderSyncInfo.getOrder().getName() + "'"); + exportTimesheets(orderSyncInfo.getKey(), orderSyncInfo.getOrder(), + connector); + if (!synchronizationInfo.isSuccessful()) { + syncInfos.add(synchronizationInfo); + } + } + return syncInfos; + } + + @Override + @Transactional(readOnly = true) + public void exportTimesheets(String productCode, Order order) + throws ConnectorException { + if (productCode == null || productCode.isEmpty()) { + throw new ConnectorException(_("Product code should not be empty")); + } + if (order == null) { + throw new ConnectorException(_("Order should not be empty")); + } + + Connector connector = getTimConnector(); + if (connector == null) { + throw new ConnectorException(_("Tim connector not found")); + } + + if (!connector.areConnectionValuesValid()) { + throw new ConnectorException( + _("Connection values of Tim connector are invalid")); + } + + exportTimesheets(productCode, order, connector); + } + + /** + * exports time sheets to Tim + * + * @param productCode + * the product code + * @param order + * the order + * @param connector + * the connector + * + * @return true if export is succeeded, false otherwise + */ + private void exportTimesheets(String productCode, Order order, + Connector connector) { + + synchronizationInfo = new SynchronizationInfo(_( + "Export product code {0}, project {1}", productCode, + order.getName())); + + Map properties = connector.getPropertiesAsMap(); + + String url = properties.get(PredefinedConnectorProperties.SERVER_URL); + String userName = properties + .get(PredefinedConnectorProperties.USERNAME); + String password = properties + .get(PredefinedConnectorProperties.PASSWORD); + int nrDaysTimesheetToTim = Integer.parseInt(properties + .get(PredefinedConnectorProperties.TIM_NR_DAYS_TIMESHEET)); + + LocalDate dateNrOfDaysBack = new LocalDate() + .minusDays(nrDaysTimesheetToTim); + + List workReportLines = order.getWorkReportLines( + dateNrOfDaysBack.toDateTimeAtStartOfDay().toDate(), new Date(), + true); + if (workReportLines == null || workReportLines.isEmpty()) { + LOG.warn("No work reportlines are found for order: '" + + order.getName() + "'"); + synchronizationInfo.addFailedReason(_( + "No work reportlines are found for order: \"{0}\"", + order.getName())); + return; + } + + List timeRegistrationDTOs = new ArrayList(); + + for (WorkReportLine workReportLine : workReportLines) { + TimeRegistrationDTO timeRegistrationDTO = createExportTimeRegistration( + productCode, workReportLine); + if (timeRegistrationDTO != null) { + timeRegistrationDTOs.add(timeRegistrationDTO); + } + } + + if (timeRegistrationDTOs.isEmpty()) { + LOG.warn("Unable to crate timeregistration for request"); + synchronizationInfo + .addFailedReason(_("Unable to crate time registration for request")); + return; + } + + TimeRegistrationRequestDTO timeRegistrationRequestDTO = new TimeRegistrationRequestDTO(); + timeRegistrationRequestDTO.setTimeRegistrations(timeRegistrationDTOs); + + TimeRegistrationResponseDTO timeRegistrationResponseDTO = TimSoapClient + .sendRequestReceiveResponse(url, userName, password, + timeRegistrationRequestDTO, TimeRegistrationResponseDTO.class); + + if (timeRegistrationResponseDTO == null) { + LOG.error("No response or exception in response"); + synchronizationInfo + .addFailedReason(_("No response or exception in response")); + return; + } + + if (isRefsListEmpty(timeRegistrationResponseDTO.getRefs())) { + LOG.warn("Registration response with empty refs"); + synchronizationInfo + .addFailedReason(_("Registration response with empty refs")); + return; + } + saveSyncInfoOnAnotherTransaction(productCode, order); + } + + /** + * checks if list of refs is empty + * + * @param refs + * the list of refs + * @return true if list is empty otherwise false + */ + private boolean isRefsListEmpty(List refs) { + if (refs == null) { + return true; + } + refs.removeAll(Collections.singleton(0)); + return refs.isEmpty(); + } + + /** + * Saves synchronization info + * + * @param productCode + * the productcode + * @param order + * the order + */ + private void saveSyncInfoOnAnotherTransaction(final String productCode, + final Order order) { + adHocTransactionService + .runOnAnotherTransaction(new IOnTransaction() { + @Override + public Void execute() { + OrderSyncInfo orderSyncInfo = orderSyncInfoDAO + .findByKeyOrderAndConnectorName(productCode, + order, + PredefinedConnectors.TIM.getName()); + if (orderSyncInfo == null) { + orderSyncInfo = OrderSyncInfo.create(productCode, + order, PredefinedConnectors.TIM.getName()); + } + orderSyncInfo.setLastSyncDate(new Date()); + orderSyncInfoDAO.save(orderSyncInfo); + return null; + } + }); + } + + /** + * Creates export time registration + * + * @param productCode + * the product code + * @param workReportLine + * the workreportLine + * @return timeRegistration DTO + */ + private TimeRegistrationDTO createExportTimeRegistration(String productCode, + WorkReportLine workReportLine) { + Worker worker; + String workerCode = workReportLine.getResource().getCode(); + try { + worker = workerDAO.findByCode(workerCode); + } catch (InstanceNotFoundException e) { + LOG.warn("Worker '" + workerCode + "' not found"); + synchronizationInfo.addFailedReason(_("Worker \"{0}\" not found", + workerCode)); + return null; + } + + PersonDTO personDTO = new PersonDTO(); + personDTO.setName(worker.getName()); + personDTO.setOptions(TimOptions.UPDATE_OR_INSERT); + + ProductDTO productDTO = new ProductDTO(); + productDTO.setOptions(TimOptions.UPDATE_OR_INSERT); + productDTO.setCode(productCode); + + RegistrationDateDTO registrationDTO = new RegistrationDateDTO(); + registrationDTO.setOptions(TimOptions.UPDATE_OR_INSERT); + registrationDTO.setDate(workReportLine.getLocalDate()); + + DurationDTO durationDTO = new DurationDTO(); + durationDTO.setOptions(TimOptions.DECIMAL); + durationDTO.setDuration(workReportLine.getEffort() + .toHoursAsDecimalWithScale(2).doubleValue()); + + TimeRegistrationDTO timeRegistrationDTO = new TimeRegistrationDTO(); + timeRegistrationDTO.setPerson(personDTO); + timeRegistrationDTO.setProduct(productDTO); + timeRegistrationDTO.setRegistrationDate(registrationDTO); + timeRegistrationDTO.setDuration(durationDTO); + return timeRegistrationDTO; + } + + @Override + @Transactional(readOnly = true) + public OrderSyncInfo getOrderLastSyncInfo(Order order) { + return orderSyncInfoDAO.findLastSynchronizedInfoByOrderAndConnectorName( + order, PredefinedConnectors.TIM.getName()); + } + + /** + * finds and returns a Tim connector + */ + private Connector getTimConnector() { + return connectorDAO + .findUniqueByName(PredefinedConnectors.TIM.getName()); + } + + @Override + public SynchronizationInfo getSynchronizationInfo() { + return synchronizationInfo; + } + +} diff --git a/libreplan-webapp/src/main/java/org/libreplan/importers/IExportTimesheetsToTim.java b/libreplan-webapp/src/main/java/org/libreplan/importers/IExportTimesheetsToTim.java new file mode 100644 index 000000000..15945e12d --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/IExportTimesheetsToTim.java @@ -0,0 +1,83 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 St. Antoniusziekenhuis + * + * 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 . + */ + +package org.libreplan.importers; + +import java.util.List; + +import org.libreplan.business.common.entities.Connector; +import org.libreplan.business.common.entities.ConnectorException; +import org.libreplan.business.orders.entities.Order; +import org.libreplan.business.orders.entities.OrderSyncInfo; + +/** + * Export time sheets of an existing order to Tim SOAP server using + * {@link TimSoapClient}. + * + * It exports the time sheets between periods current-date minus + * NrDaysTimesheetToTim specified in the Tim {@link Connector} and + * the current-date + * + * @author Miciele Ghiorghis + */ +public interface IExportTimesheetsToTim { + + /** + * Exports time sheets of the specified productCode and + * {@link Order} to Tim SOAP server + * + * @param productCode + * the Tim's productCode + * @param order + * an existing order + * @throws ConnectorException + * if connector is not valid + */ + void exportTimesheets(String productCode, Order order) throws ConnectorException; + + /** + * Exporting the time sheets to Tim SOAP server, if they are already + * exported using + * {@link IExportTimesheetsToTim#exportTimesheets(String, Order)}. + * + * It gets then an already exported time sheets from {@link OrderSyncInfo} + * and re-exporting them. + * + * @return a list of {@link SynchronizationInfo} + * + * @throws ConnectorException + * if connector is not valid + */ + List exportTimesheets() throws ConnectorException; + + /** + * Gets the most recent synchronized time sheet info + * + * @param order + * the order + * @return recent synchronized time sheet info + */ + OrderSyncInfo getOrderLastSyncInfo(Order order); + + /** + * Returns synchronization info, success of fail info + */ + SynchronizationInfo getSynchronizationInfo(); + +} diff --git a/libreplan-webapp/src/main/java/org/libreplan/importers/IImportRosterFromTim.java b/libreplan-webapp/src/main/java/org/libreplan/importers/IImportRosterFromTim.java new file mode 100644 index 000000000..82aabbf69 --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/IImportRosterFromTim.java @@ -0,0 +1,56 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 St. Antoniusziekenhuis + * + * 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 . + */ + +package org.libreplan.importers; + +import java.util.List; + +import org.libreplan.business.calendars.entities.CalendarException; +import org.libreplan.business.common.entities.Connector; +import org.libreplan.business.common.entities.ConnectorException; + +/** + * Import Rosters from Tim SOAP server using {@link TimSoapClient} and updates + * worker's Exception calendar accordingly + * + * It imports the Rosters between periods current-date and current-date plus + * NrDaysRosterFromTim specified in Tim {@link Connector}. + * + * @author Miciele Ghiorghis + */ +public interface IImportRosterFromTim { + + /** + * Import rosters from Tim and update workers {@link CalendarException} + * + * If worker calendar exception already exists it will be removed and added + * new one, in other cases a new calendar exception will be created + * + * @return a list of {@link SynchronizationInfo} + * + * @throws ConnectorException + * if connector is not valid + */ + List importRosters() throws ConnectorException; + + /** + * Returns synchronization info, success of fail info + */ + SynchronizationInfo getSynchronizationInfo(); +} diff --git a/libreplan-webapp/src/main/java/org/libreplan/importers/IJiraOrderElementSynchronizer.java b/libreplan-webapp/src/main/java/org/libreplan/importers/IJiraOrderElementSynchronizer.java index 4f643118e..04a7b5711 100755 --- a/libreplan-webapp/src/main/java/org/libreplan/importers/IJiraOrderElementSynchronizer.java +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/IJiraOrderElementSynchronizer.java @@ -23,8 +23,10 @@ import java.util.List; import org.libreplan.business.advance.entities.AdvanceMeasurement; import org.libreplan.business.advance.entities.DirectAdvanceAssignment; +import org.libreplan.business.common.entities.ConnectorException; import org.libreplan.business.orders.entities.Order; import org.libreplan.business.orders.entities.OrderElement; +import org.libreplan.business.orders.entities.OrderSyncInfo; import org.libreplan.importers.jira.IssueDTO; /** @@ -48,8 +50,10 @@ public interface IJiraOrderElementSynchronizer { * https://jira.atlassian.com/browse/JRA-29409 * * @return A list of labels + * @throws ConnectorException + * if connector not found */ - List getAllJiraLabels(); + List getAllJiraLabels() throws ConnectorException; /** * Get all jira issues based on the specified label parameter @@ -59,8 +63,10 @@ public interface IJiraOrderElementSynchronizer { * search criteria for jira issues * * @return list of jira issues + * @throws ConnectorException + * if connector not found or contains invalid connection values */ - List getJiraIssues(String label); + List getJiraIssues(String label) throws ConnectorException; /** * Synchronizes the list of {@link OrderElement}s, @@ -82,9 +88,42 @@ public interface IJiraOrderElementSynchronizer { */ void syncOrderElementsWithJiraIssues(List issues, Order order); + /** + * Saves synchronization info + * + * @param key + * the key(label) + * @param order + * an order which already synchronized + */ + void saveSyncInfo(String key, Order order); + + /** + * Gets the most recent synchronized info + * + * @param order + * the order + * @return recent synchronized time sheet info + */ + OrderSyncInfo getOrderLastSyncInfo(Order order); + /** * returns synchronization info, success or fail info */ - JiraSyncInfo getJiraSyncInfo(); + SynchronizationInfo getSynchronizationInfo(); + /** + * Synchronize order elements with JIRA issues if they already synchronized + * using + * {@link IJiraOrderElementSynchronizer#syncOrderElementsWithJiraIssues(List, Order) + * + * It gets then an already synchronized orders from the + * {@link OrderSyncInfo} and re-synchronize them + * + * @return a list of {@link SynchronizationInfo} + * + * @throws ConnectorException + * if connector not found or contains invalid connection values + */ + List syncOrderElementsWithJiraIssues() throws ConnectorException; } diff --git a/libreplan-webapp/src/main/java/org/libreplan/importers/IJiraTimesheetSynchronizer.java b/libreplan-webapp/src/main/java/org/libreplan/importers/IJiraTimesheetSynchronizer.java index 9c263923c..7611a75f3 100644 --- a/libreplan-webapp/src/main/java/org/libreplan/importers/IJiraTimesheetSynchronizer.java +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/IJiraTimesheetSynchronizer.java @@ -21,6 +21,7 @@ package org.libreplan.importers; import java.util.List; +import org.libreplan.business.common.entities.ConnectorException; import org.libreplan.business.orders.entities.Order; import org.libreplan.business.workreports.entities.WorkReportType; import org.libreplan.importers.jira.IssueDTO; @@ -42,7 +43,6 @@ public interface IJiraTimesheetSynchronizer { /** * Synchronize jira timesheet with the specified jira issues . * - * * Loop through all jira issues and check if timesheet is * already exist for the specified issue item. If it is, update the * timesheet with that issue item. If not create new one @@ -51,12 +51,14 @@ public interface IJiraTimesheetSynchronizer { * the jira issues * @param order * an existing order + * @throws ConnectorException + * if not valid connector or connector contains invalid values */ - void syncJiraTimesheetWithJiraIssues(List issues, Order order); + void syncJiraTimesheetWithJiraIssues(List issues, Order order) throws ConnectorException; /** * returns synchronization info, success or fail info */ - JiraSyncInfo getJiraSyncInfo(); + SynchronizationInfo getSynchronizationInfo(); } diff --git a/libreplan-webapp/src/main/java/org/libreplan/importers/ISchedulerManager.java b/libreplan-webapp/src/main/java/org/libreplan/importers/ISchedulerManager.java new file mode 100644 index 000000000..745ef51a3 --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/ISchedulerManager.java @@ -0,0 +1,102 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 St. Antoniusziekenhuis + * + * 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 . + */ + +package org.libreplan.importers; + +import org.libreplan.business.common.entities.JobSchedulerConfiguration; +import org.quartz.SchedulerException; +import org.springframework.scheduling.quartz.CronTriggerBean; +import org.springframework.scheduling.quartz.JobDetailBean; + +/** + * A manager(client) that dynamically creates jobs and cron-triggers using + * spring quartz library. + * + * The start and destroy of the scheduler itself is managed by the Spring + * framework. The scheduler starts automatically when the application starts and + * destroyed when the application stops. + * + * This manager (un)schedules the jobs based on the configuration + * {@link JobSchedulerConfiguration} entity once the scheduler starts. + * + *
    + *
  • Schedule job:create job {@link JobDetailBean} and cron-trigger + * {@link CronTriggerBean}, associated the trigger with the job and add it to + * the scheduler. + *
  • + *
  • Delete job: search the job in the scheduler and if found + * unschedule(delete) the job
  • + *
+ * + * @author Miciele Ghiorghis + */ +public interface ISchedulerManager { + + /** + * Reads the jobs to be scheduled from the {@link JobSchedulerConfiguration} + * and schedules the jobs based on the cron expression defined for each job + * in the {@link JobSchedulerConfiguration} + */ + void scheduleJobs(); + + /** + * Reads the jobs to be scheduled from the specified + * {@link JobSchedulerConfiguration} and (un)schedule it + * accordingly + * + * In the specified {@link JobSchedulerConfiguration} + * + *
    + *
  • {@link JobSchedulerConfiguration#getConnectorName()} + * check if job has a connector and the connector is activated
  • + *
  • {@link JobSchedulerConfiguration#isSchedule()} if true + * the job would be scheduled, if not job deleted
  • + *
+ * + * @param jobSchedulerConfiguration + * configuration for job to be (un)scheduled + * @throws SchedulerException + * if unable to (un)schedule + */ + void scheduleOrUnscheduleJob(JobSchedulerConfiguration jobSchedulerConfiguration) throws SchedulerException; + + /** + * Deletes the job from the scheduler for the specified job by + * {@link JobSchedulerConfiguration}, if the job is already in + * the scheduler + * + * @param jobSchedulerConfiguration + * configuration for job to be deleted + * @throws SchedulerException + * if unable to delete + */ + void deleteJob(JobSchedulerConfiguration jobSchedulerConfiguration) throws SchedulerException; + + /** + * gets the next fire time for the specified job from + * {@link JobSchedulerConfiguration} if job is already scheduled. This is + * only neede for UI + * + * @param jobSchedulerConfiguration + * configuration to check for next fire time + * @return next fire time or empty string + */ + String getNextFireTime(JobSchedulerConfiguration jobSchedulerConfiguration); + +} diff --git a/libreplan-webapp/src/main/java/org/libreplan/importers/ImportRosterFromTim.java b/libreplan-webapp/src/main/java/org/libreplan/importers/ImportRosterFromTim.java new file mode 100644 index 000000000..54987566e --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/ImportRosterFromTim.java @@ -0,0 +1,470 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 St. Antoniusziekenhuis + * + * 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 . + */ + +package org.libreplan.importers; + +import static org.libreplan.web.I18nHelper._; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.joda.time.LocalDate; +import org.libreplan.business.calendars.daos.ICalendarExceptionTypeDAO; +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.calendars.entities.PredefinedCalendarExceptionTypes; +import org.libreplan.business.calendars.entities.ResourceCalendar; +import org.libreplan.business.common.IAdHocTransactionService; +import org.libreplan.business.common.IOnTransaction; +import org.libreplan.business.common.daos.IConnectorDAO; +import org.libreplan.business.common.entities.Connector; +import org.libreplan.business.common.entities.ConnectorException; +import org.libreplan.business.common.entities.PredefinedConnectorProperties; +import org.libreplan.business.common.entities.PredefinedConnectors; +import org.libreplan.business.common.exceptions.InstanceNotFoundException; +import org.libreplan.business.resources.daos.IWorkerDAO; +import org.libreplan.business.resources.entities.Worker; +import org.libreplan.business.workingday.EffortDuration; +import org.libreplan.importers.RosterException.RosterExceptionItem; +import org.libreplan.importers.tim.DataDTO; +import org.libreplan.importers.tim.DepartmentDTO; +import org.libreplan.importers.tim.FilterDTO; +import org.libreplan.importers.tim.PeriodDTO; +import org.libreplan.importers.tim.PersonDTO; +import org.libreplan.importers.tim.RosterCategoryDTO; +import org.libreplan.importers.tim.RosterDTO; +import org.libreplan.importers.tim.RosterRequestDTO; +import org.libreplan.importers.tim.RosterResponseDTO; +import org.libreplan.web.calendars.IBaseCalendarModel; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +/** + * Implementation of import roosters from tim + * + * @author Miciele Ghiorghis + */ +@Component +@Scope(BeanDefinition.SCOPE_SINGLETON) +public class ImportRosterFromTim implements IImportRosterFromTim { + + private static final Log LOG = LogFactory.getLog(ImportRosterFromTim.class); + + @Autowired + private IWorkerDAO workerDAO; + + @Autowired + private IConnectorDAO connectorDAO; + + @Autowired + private IAdHocTransactionService adHocTransactionService; + + @Autowired + private ICalendarExceptionTypeDAO calendarExceptionTypeDAO; + + @Autowired + @Qualifier("subclass") + private IBaseCalendarModel baseCalendarModel; + + private SynchronizationInfo synchronizationInfo; + + /** + * Search criteria for roster exception days in RESPONSE message + * {@link RosterDTO} + */ + private static final String ABSENT = "Afwezig"; + + /** + * The word "Vakantie"(holiday) in RESPONSE message that would be translated + * to {@link PredefinedCalendarExceptionTypes#RESOURCE_HOLIDAY } + */ + private static final String HOLIDAY = "Vakantie"; + + /** + * The word "Feestdag"(bank holiday) in RESPONSE message that would be + * translated to {@link PredefinedCalendarExceptionTypes#BANK_HOLIDAY} + */ + private static final String BANK_HOLIDAY = "Feestdag"; + + + @Override + @Transactional + public List importRosters() throws ConnectorException { + Connector connector = connectorDAO + .findUniqueByName(PredefinedConnectors.TIM.getName()); + if (connector == null) { + throw new ConnectorException(_("Tim connector not found")); + } + + if (!connector.areConnectionValuesValid()) { + throw new ConnectorException( + _("Connection values of Tim connector are invalid")); + } + + Map properties = connector.getPropertiesAsMap(); + String url = properties.get(PredefinedConnectorProperties.SERVER_URL); + + String userName = properties + .get(PredefinedConnectorProperties.USERNAME); + + String password = properties + .get(PredefinedConnectorProperties.PASSWORD); + + int nrDaysRosterFromTim = Integer.parseInt(properties + .get(PredefinedConnectorProperties.TIM_NR_DAYS_ROSTER)); + + int productivityFactor = Integer.parseInt(properties + .get(PredefinedConnectorProperties.TIM_PRODUCTIVITY_FACTOR)); + + + String departmentIds = properties + .get(PredefinedConnectorProperties.TIM_DEPARTAMENTS_IMPORT_ROSTER); + + if (StringUtils.isBlank(departmentIds)) { + LOG.warn("No departments configured"); + throw new ConnectorException(_("No departments configured")); + } + + String[] departmentIdsArray = StringUtils.stripAll(StringUtils.split( + departmentIds, ",")); + + List syncInfos = new ArrayList(); + + for (String department : departmentIdsArray) { + LOG.info("Department: " + department); + + synchronizationInfo = new SynchronizationInfo(_( + "Import roster for department {0}", department)); + + RosterRequestDTO rosterRequestDTO = createRosterRequest(department, + nrDaysRosterFromTim); + RosterResponseDTO rosterResponseDTO = TimSoapClient + .sendRequestReceiveResponse(url, userName, password, + rosterRequestDTO, RosterResponseDTO.class); + + if (rosterResponseDTO != null) { + updateWorkersCalendarException(rosterResponseDTO, + productivityFactor); + if (!synchronizationInfo.isSuccessful()) { + syncInfos.add(synchronizationInfo); + } + } else { + LOG.error("No valid response for department " + department); + synchronizationInfo.addFailedReason(_( + "No valid response for department \"{0}\"", + department)); + syncInfos.add(synchronizationInfo); + } + } + return syncInfos; + } + + /** + * updates workers Exception calendar + * + * @param rosterResponse + * the response from Tim SOAP server + */ + private void updateWorkersCalendarException( + final RosterResponseDTO rosterResponse, final int productivityFactor) { + adHocTransactionService + .runOnAnotherTransaction(new IOnTransaction() { + + @Override + public Void execute() { + List rosterExceptions = getRosterExceptions( + rosterResponse, productivityFactor); + if (!rosterExceptions.isEmpty()) { + updateCalendarException(rosterExceptions); + } else { + LOG.info("No roster-exceptions found in the response"); + synchronizationInfo + .addFailedReason(_("No roster-exceptions found in the response")); + } + return null; + } + }); + } + + /** + * Loops through rosterResponseDTO and creates + * {@link RosterException}s and link them to the worker + * + * @param rosterResponseDTO + * the response + * @return a list of RosterExceptions + */ + private List getRosterExceptions( + RosterResponseDTO rosterResponseDTO, int productivityFactor) { + Map> map = getRosterExceptionPerWorker(rosterResponseDTO); + + List rosterExceptions = new ArrayList(); + + for (Map.Entry> entry : map.entrySet()) { + Worker worker = null; + String workerCode = entry.getKey(); + try { + worker = workerDAO.findUniqueByNif(workerCode); + } catch (InstanceNotFoundException e) { + LOG.warn("Worker '" + workerCode + "' not found"); + synchronizationInfo.addFailedReason(_( + "Worker \"{0}\" not found", + workerCode)); + } + if (worker != null) { + List list = entry.getValue(); + Collections.sort(list, new Comparator() { + @Override + public int compare(RosterDTO o1, RosterDTO o2) { + return o1.getDate().compareTo(o2.getDate()); + } + }); + RosterException re = new RosterException(worker, + productivityFactor); + re.addRosterExceptions(list); + rosterExceptions.add(re); + } + } + return rosterExceptions; + } + + /** + * Filters the roster on exceptions(absence) and creates a map with + * personsNetwork-name as key + * and list of roster-exception as value + * + * @param rosterResponseDTO + * the response + * @return person-roster exception map + */ + private Map> getRosterExceptionPerWorker( + RosterResponseDTO rosterResponseDTO) { + Map> rosterMap = new HashMap>(); + List rosterDTOs = rosterResponseDTO.getRosters(); + for (RosterDTO rosterDTO : rosterDTOs) { + if (rosterDTO.getPrecence().equals(ABSENT)) { + String personsNetWorkName = rosterDTO.getPersons().get(0) + .getNetworkName(); + if (!rosterMap.containsKey(personsNetWorkName)) { + rosterMap.put(personsNetWorkName, + new ArrayList()); + } + rosterMap.get(personsNetWorkName).add(rosterDTO); + } + } + return rosterMap; + } + + /** + * updates the workers calendar exception + * + * @param rosterExceptions + * list of roster exceptions + */ + private void updateCalendarException(List rosterExceptions) { + for (RosterException rosterException : rosterExceptions) { + List items = rosterException + .getRosterExceptionItems(); + for (RosterExceptionItem item : items) { + updateCalendarExceptionPerWorker(rosterException.getWorker(), + item.getDate(), item.getExceptionType(), + item.getEffortDuration()); + } + } + } + + + /** + * updates the calendar exception of the specified + * {@link Worker} for the specified date + * + * @param worker + * the worker + * @param date + * the date of the exception + * @param exceptionName + * the exception name + * @param effortDuration + * the exceptions effortDurtaion + */ + private void updateCalendarExceptionPerWorker(Worker worker, + LocalDate date, String exceptionName, EffortDuration effortDuration) { + CalendarExceptionType calendarExceptionType = getCalendarExceptionType(exceptionName); + if (calendarExceptionType == null) { + return; + } + ResourceCalendar resourceCalendar = (ResourceCalendar) worker + .getCalendarOrDefault(); + CalendarException calendarExceptionDay = resourceCalendar + .getExceptionDay(date); + Capacity capacity = Capacity.create(effortDuration); + if (calendarExceptionDay != null) { + resourceCalendar.removeExceptionDay(calendarExceptionDay.getDate()); + } + baseCalendarModel.initEdit(resourceCalendar); + baseCalendarModel.updateException(calendarExceptionType, date, date, + capacity); + baseCalendarModel.confirmSave(); + } + + /** + * Searches and returns the calendarExcptionType based on the specified + * name + * + * If the specified parameter name contains the word + * {@link ImportRosterFromTim#HOLIDAY}, the + * calendarExceptionType assumed to be the + * {@link PredefinedCalendarExceptionTypes#RESOURCE_HOLIDAY}, otherwise it + * searches in {@link CalendarExceptionType} for unique + * calendarExceptionType + * + * @param name + * the exception calendar name + */ + private CalendarExceptionType getCalendarExceptionType(String name) { + if (name == null || name.isEmpty()) { + LOG.error("Exception name should not be empty"); + synchronizationInfo + .addFailedReason(_("Exception name should not be empty")); + return null; + } + try { + String nameToSearch = name; + if (nameToSearch.contains(HOLIDAY)) { + nameToSearch = PredefinedCalendarExceptionTypes.RESOURCE_HOLIDAY + .toString(); + } else if (nameToSearch.equals(BANK_HOLIDAY)) { + nameToSearch = PredefinedCalendarExceptionTypes.BANK_HOLIDAY + .toString(); + } + return calendarExceptionTypeDAO.findUniqueByName(nameToSearch); + } catch (InstanceNotFoundException e) { + LOG.error("Calendar exceptionType not found", e); + synchronizationInfo + .addFailedReason(_("Calendar exception day not found")); + } + return null; + } + + + /** + * creates and returns {@link RosterRequestDTO} + * + * @param nrDaysRosterFromTim + * nr of days required to set the end date + */ + private RosterRequestDTO createRosterRequest(String department, + int nrDaysRosterFromTim) { + RosterDTO rosterDTO = createRoster(nrDaysRosterFromTim); + + PeriodDTO periodeDTO = new PeriodDTO(); + periodeDTO.setStart(new org.joda.time.DateTime()); + periodeDTO.setEnd(new org.joda.time.DateTime() + .plusDays(nrDaysRosterFromTim)); + List periodDTOs = new ArrayList(); + periodDTOs.add(periodeDTO); + + DepartmentDTO departmentDTO = new DepartmentDTO(); + departmentDTO.setRef(department); + + FilterDTO filterDTO = new FilterDTO(); + filterDTO.setPeriods(periodDTOs); + filterDTO.setDepartment(departmentDTO); + + rosterDTO.setFilter(filterDTO); + + rosterDTO.setPersons(createEmptyPerson()); + + rosterDTO.setRosterCategories(createEmptyRosterCategory()); + + rosterDTO.setDepartment(departmentDTO); + + rosterDTO.setPrecence(new String()); + rosterDTO.setPeriods(periodDTOs); + + RosterRequestDTO exportRosterRequestDTO = new RosterRequestDTO(); + DataDTO dataDTO = new DataDTO(); + dataDTO.setData(rosterDTO); + + exportRosterRequestDTO.setData(dataDTO); + return exportRosterRequestDTO; + + } + + /** + * creates and returns list of {@link PersonDTO} with empty + * {@link PersonDTO} + * + * This is an indication to Tim server that it should include this Person + * information in the RESPONSE message + */ + private List createEmptyPerson() { + List personDTOs = new ArrayList(); + personDTOs.add(new PersonDTO()); + return personDTOs; + } + + /** + * creates and returns list of {@link RosterCategoryDTO} with empty + * {@link RosterCategoryDTO} + * + * This is an indication to Tim server that it should include this + * RosterCategory information in the RESPONSE message + */ + private List createEmptyRosterCategory() { + List rosterCategorieDTOs = new ArrayList(); + RosterCategoryDTO rosterCategoryDTO = new RosterCategoryDTO(); + rosterCategoryDTO.setName(new String()); + rosterCategorieDTOs.add(rosterCategoryDTO); + return rosterCategorieDTOs; + } + + /** + * creates and returns {@link RosterDTO} + */ + private RosterDTO createRoster(int nrDaysRosterFromTim) { + RosterDTO rosterDTO = new RosterDTO(); + rosterDTO.setStartDate(new LocalDate()); + rosterDTO.setEndDate(new LocalDate().plusDays(nrDaysRosterFromTim)); + rosterDTO.setResourcePlanning(false); + rosterDTO.setDayPlanning(false); + rosterDTO.setCalendar(false); + rosterDTO.setNonPlaned(true); + rosterDTO.setFullDay(false); + rosterDTO.setConcept(false); + return rosterDTO; + } + + @Override + public SynchronizationInfo getSynchronizationInfo() { + return synchronizationInfo; + } +} diff --git a/libreplan-webapp/src/main/java/org/libreplan/importers/ImportRosterFromTimJob.java b/libreplan-webapp/src/main/java/org/libreplan/importers/ImportRosterFromTimJob.java new file mode 100644 index 000000000..b4e928eaa --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/ImportRosterFromTimJob.java @@ -0,0 +1,69 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 St. Antoniusziekenhuis + * + * 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 . + */ + +package org.libreplan.importers; + +import java.util.List; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.libreplan.business.common.entities.ConnectorException; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Scope; +import org.springframework.scheduling.quartz.QuartzJobBean; +import org.springframework.stereotype.Component; + +/** + * A job that import rosters from Tim SOAP server + * + * @author Miciele Ghiorghis + */ +@Component +@Scope(BeanDefinition.SCOPE_SINGLETON) +public class ImportRosterFromTimJob extends QuartzJobBean { + private static final Log LOG = LogFactory + .getLog(ImportRosterFromTimJob.class); + + @Override + protected void executeInternal(JobExecutionContext context) + throws JobExecutionException { + ApplicationContext applicationContext = (ApplicationContext) context + .getJobDetail().getJobDataMap().get("applicationContext"); + + IImportRosterFromTim importRosterFromTim = (IImportRosterFromTim) applicationContext + .getBean("importRosterFromTim"); + + try { + List syncInfos = importRosterFromTim + .importRosters(); + + LOG.info("Import scuccessful: " + + (syncInfos == null || syncInfos.isEmpty())); + + } catch (ConnectorException e) { + LOG.error("Import roster from Tim failed", e); + } + + } + + +} diff --git a/libreplan-webapp/src/main/java/org/libreplan/importers/JiraOrderElementSynchronizer.java b/libreplan-webapp/src/main/java/org/libreplan/importers/JiraOrderElementSynchronizer.java index b723ae2c6..354188e24 100755 --- a/libreplan-webapp/src/main/java/org/libreplan/importers/JiraOrderElementSynchronizer.java +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/JiraOrderElementSynchronizer.java @@ -19,6 +19,8 @@ package org.libreplan.importers; +import static org.libreplan.web.I18nHelper._; + import java.math.BigDecimal; import java.math.RoundingMode; import java.net.MalformedURLException; @@ -28,8 +30,11 @@ import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.List; +import java.util.Map; import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.joda.time.LocalDate; import org.libreplan.business.advance.bootstrap.PredefinedAdvancedTypes; import org.libreplan.business.advance.entities.AdvanceMeasurement; @@ -37,18 +42,26 @@ import org.libreplan.business.advance.entities.AdvanceType; import org.libreplan.business.advance.entities.DirectAdvanceAssignment; import org.libreplan.business.advance.exceptions.DuplicateAdvanceAssignmentForOrderElementException; import org.libreplan.business.advance.exceptions.DuplicateValueTrueReportGlobalAdvanceException; -import org.libreplan.business.common.daos.IConfigurationDAO; -import org.libreplan.business.common.entities.JiraConfiguration; +import org.libreplan.business.common.IAdHocTransactionService; +import org.libreplan.business.common.IOnTransaction; +import org.libreplan.business.common.daos.IConnectorDAO; +import org.libreplan.business.common.entities.Connector; +import org.libreplan.business.common.entities.ConnectorException; +import org.libreplan.business.common.entities.PredefinedConnectorProperties; +import org.libreplan.business.common.entities.PredefinedConnectors; +import org.libreplan.business.orders.daos.IOrderSyncInfoDAO; import org.libreplan.business.orders.entities.HoursGroup; 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.OrderSyncInfo; import org.libreplan.business.workingday.EffortDuration; import org.libreplan.importers.jira.IssueDTO; import org.libreplan.importers.jira.StatusDTO; import org.libreplan.importers.jira.TimeTrackingDTO; import org.libreplan.importers.jira.WorkLogDTO; import org.libreplan.importers.jira.WorkLogItemDTO; +import org.libreplan.web.orders.IOrderModel; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.annotation.Scope; @@ -64,17 +77,36 @@ import org.springframework.transaction.annotation.Transactional; @Scope(BeanDefinition.SCOPE_PROTOTYPE) public class JiraOrderElementSynchronizer implements IJiraOrderElementSynchronizer { + private static final Log LOG = LogFactory + .getLog(JiraOrderElementSynchronizer.class); + + private SynchronizationInfo synchronizationInfo; + @Autowired - private IConfigurationDAO configurationDAO; + private IConnectorDAO connectorDAO; - private JiraSyncInfo jiraSyncInfo; + @Autowired + private IOrderSyncInfoDAO orderSyncInfoDAO; + @Autowired + private IAdHocTransactionService adHocTransactionService; + + @Autowired + private IOrderModel orderModel; + + @Autowired + private IJiraTimesheetSynchronizer jiraTimesheetSynchronizer; @Override @Transactional(readOnly = true) - public List getAllJiraLabels() { - String jiraLabels = configurationDAO.getConfiguration() - .getJiraConfiguration().getJiraLabels(); + public List getAllJiraLabels() throws ConnectorException { + Connector connector = getJiraConnector(); + if (connector == null) { + throw new ConnectorException(_("JIRA connector not found")); + } + + String jiraLabels = connector.getPropertiesAsMap().get( + PredefinedConnectorProperties.JIRA_LABELS); String labels; try { @@ -88,13 +120,39 @@ public class JiraOrderElementSynchronizer implements IJiraOrderElementSynchroniz @Override @Transactional(readOnly = true) - public List getJiraIssues(String label) { - JiraConfiguration jiraConfiguration = configurationDAO - .getConfiguration().getJiraConfiguration(); + public List getJiraIssues(String label) throws ConnectorException { - String url = jiraConfiguration.getJiraUrl(); - String username = jiraConfiguration.getJiraUserId(); - String password = jiraConfiguration.getJiraPassword(); + Connector connector = getJiraConnector(); + if (connector == null) { + throw new ConnectorException(_("JIRA connector not found")); + } + + if (!connector.areConnectionValuesValid()) { + throw new ConnectorException( + _("Connection values of JIRA connector are invalid")); + } + + return getJiraIssues(label, connector); + } + + /** + * Gets all jira issues for the specified label + * + * @param label + * the search criteria + * @param connector + * where to read the configuration parameters + * @return a list of {@link IssueDTO} + */ + private List getJiraIssues(String label, Connector connector) { + Map properties = connector.getPropertiesAsMap(); + String url = properties.get(PredefinedConnectorProperties.SERVER_URL); + + String username = properties + .get(PredefinedConnectorProperties.USERNAME); + + String password = properties + .get(PredefinedConnectorProperties.PASSWORD); String path = JiraRESTClient.PATH_SEARCH; String query = "labels=" + label; @@ -109,17 +167,20 @@ public class JiraOrderElementSynchronizer implements IJiraOrderElementSynchroniz @Transactional(readOnly = true) public void syncOrderElementsWithJiraIssues(List issues, Order order) { - jiraSyncInfo = new JiraSyncInfo(); + synchronizationInfo = new SynchronizationInfo(_( + "Synchronization order {0}", order.getName())); for (IssueDTO issue : issues) { - String code = JiraConfiguration.CODE_PREFIX + order.getCode() + "-" + String code = PredefinedConnectorProperties.JIRA_CODE_PREFIX + + order.getCode() + "-" + issue.getKey(); String name = issue.getFields().getSummary(); OrderLine orderLine = syncOrderLine(order, code, name); if (orderLine == null) { - jiraSyncInfo.addSyncFailedReason("Order-element for '" - + issue.getKey() + "' issue not found"); + synchronizationInfo.addFailedReason(_( + "Order-element for \"{0}\" issue not found", + issue.getKey())); continue; } @@ -129,8 +190,9 @@ public class JiraOrderElementSynchronizer implements IJiraOrderElementSynchroniz .getTimetracking(), loggedHours); if (estimatedHours.isZero()) { - jiraSyncInfo.addSyncFailedReason("Estimated time for '" - + issue.getKey() + "' issue is 0"); + synchronizationInfo.addFailedReason(_( + "Estimated time for \"{0}\" issue is 0", + issue.getKey())); continue; } @@ -217,15 +279,16 @@ public class JiraOrderElementSynchronizer implements IJiraOrderElementSynchroniz WorkLogDTO workLog = issue.getFields().getWorklog(); if (workLog == null) { - jiraSyncInfo.addSyncFailedReason("No worklogs found for '" - + issue.getKey() + "' issue"); + synchronizationInfo.addFailedReason(_( + "No worklogs found for \"{0}\" issue", issue.getKey())); return; } List workLogItems = workLog.getWorklogs(); if (workLogItems.isEmpty()) { - jiraSyncInfo.addSyncFailedReason("No worklog items found for '" - + issue.getKey() + "' issue"); + synchronizationInfo.addFailedReason(_( + "No worklog items found for \"{0}\" issue", + issue.getKey())); return; } @@ -334,9 +397,10 @@ public class JiraOrderElementSynchronizer implements IJiraOrderElementSynchroniz } catch (DuplicateAdvanceAssignmentForOrderElementException e) { // This could happen if a parent or child of the current // OrderElement has an advance of type PERCENTAGE - jiraSyncInfo - .addSyncFailedReason("Duplicate value AdvanceAssignment for order element of '" - + orderElement.getCode() + "'"); + synchronizationInfo + .addFailedReason(_( + "Duplicate value AdvanceAssignment for order element of \"{0}\"", + orderElement.getCode())); return; } } @@ -393,8 +457,109 @@ public class JiraOrderElementSynchronizer implements IJiraOrderElementSynchroniz } @Override - public JiraSyncInfo getJiraSyncInfo() { - return jiraSyncInfo; + public SynchronizationInfo getSynchronizationInfo() { + return synchronizationInfo; } + /** + * returns JIRA connector + */ + private Connector getJiraConnector() { + return connectorDAO.findUniqueByName(PredefinedConnectors.JIRA + .getName()); + } + + @Override + @Transactional + public void saveSyncInfo(final String key, final Order order) { + adHocTransactionService + .runOnAnotherTransaction(new IOnTransaction() { + @Override + public Void execute() { + OrderSyncInfo orderSyncInfo = orderSyncInfoDAO + .findByKeyOrderAndConnectorName(key, order, + PredefinedConnectors.JIRA.getName()); + if (orderSyncInfo == null) { + orderSyncInfo = OrderSyncInfo.create(key, order, + PredefinedConnectors.JIRA.getName()); + } + orderSyncInfo.setLastSyncDate(new Date()); + orderSyncInfoDAO.save(orderSyncInfo); + return null; + } + }); + } + + + @Override + @Transactional(readOnly = true) + public OrderSyncInfo getOrderLastSyncInfo(Order order) { + return orderSyncInfoDAO.findLastSynchronizedInfoByOrderAndConnectorName( + order, PredefinedConnectors.JIRA.getName()); + + } + + @Override + @Transactional + public List syncOrderElementsWithJiraIssues() throws ConnectorException { + Connector connector = getJiraConnector(); + if (connector == null) { + throw new ConnectorException(_("JIRA connector not found")); + } + if (!connector.areConnectionValuesValid()) { + throw new ConnectorException( + _("Connection values of JIRA connector are invalid")); + } + + List orderSyncInfos = orderSyncInfoDAO + .findByConnectorName(PredefinedConnectors.JIRA.getName()); + + synchronizationInfo = new SynchronizationInfo(_("Synchronization")); + + List syncInfos = new ArrayList(); + + if (orderSyncInfos == null || orderSyncInfos.isEmpty()) { + LOG.warn("No items found in 'OrderSyncInfo' to synchronize with JIRA issues"); + synchronizationInfo + .addFailedReason(_("No items found in 'OrderSyncInfo' to synchronize with JIRA issues")); + syncInfos.add(synchronizationInfo); + return syncInfos; + } + + for (OrderSyncInfo orderSyncInfo : orderSyncInfos) { + Order order = orderSyncInfo.getOrder(); + LOG.info("Synchronizing '" + order.getName() + "'"); + synchronizationInfo = new SynchronizationInfo(_( + "Synchronization order {0}", order.getName())); + + List issueDTOs = getJiraIssues(orderSyncInfo.getKey(), + connector); + if (issueDTOs == null || issueDTOs.isEmpty()) { + LOG.warn("No JIRA issues found for '" + orderSyncInfo.getKey() + + "'"); + synchronizationInfo.addFailedReason(_( + "No JIRA issues found for key {0}", + orderSyncInfo.getKey())); + syncInfos.add(synchronizationInfo); + continue; + } + + orderModel.initEdit(order, null); + syncOrderElementsWithJiraIssues(issueDTOs, order); + if (!synchronizationInfo.isSuccessful()) { + syncInfos.add(synchronizationInfo); + continue; + } + orderModel.save(false); + + saveSyncInfo(orderSyncInfo.getKey(), order); + + jiraTimesheetSynchronizer.syncJiraTimesheetWithJiraIssues( + issueDTOs, order); + if (!synchronizationInfo.isSuccessful()) { + syncInfos.add(synchronizationInfo); + } + } + return syncInfos; + } } diff --git a/libreplan-webapp/src/main/java/org/libreplan/importers/JiraOrderElementSynchronizerJob.java b/libreplan-webapp/src/main/java/org/libreplan/importers/JiraOrderElementSynchronizerJob.java new file mode 100644 index 000000000..0b2a43520 --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/JiraOrderElementSynchronizerJob.java @@ -0,0 +1,63 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 St. Antoniusziekenhuis + * + * 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 . + */ + +package org.libreplan.importers; + +import java.util.List; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.libreplan.business.common.entities.ConnectorException; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; +import org.springframework.context.ApplicationContext; +import org.springframework.scheduling.quartz.QuartzJobBean; + +/** + * A job that synchronizes order elements and time sheets with JIRA issues + * + * @author Miciele Ghiorghis + */ +public class JiraOrderElementSynchronizerJob extends QuartzJobBean { + + private static final Log LOG = LogFactory + .getLog(JiraOrderElementSynchronizerJob.class); + + @Override + protected void executeInternal(JobExecutionContext context) + throws JobExecutionException { + ApplicationContext applicationContext = (ApplicationContext) context + .getJobDetail().getJobDataMap().get("applicationContext"); + + IJiraOrderElementSynchronizer jiraOrderElementSynchronizer = (IJiraOrderElementSynchronizer) applicationContext + .getBean("jiraOrderElementSynchronizer"); + + try { + List syncInfos = jiraOrderElementSynchronizer + .syncOrderElementsWithJiraIssues(); + + LOG.info("Synchronization scuccessful: " + + (syncInfos == null || syncInfos.isEmpty())); + + } catch (ConnectorException e) { + LOG.error("Synchronize order elements failed", e); + } + } + +} diff --git a/libreplan-webapp/src/main/java/org/libreplan/importers/JiraTimesheetSynchronizer.java b/libreplan-webapp/src/main/java/org/libreplan/importers/JiraTimesheetSynchronizer.java index 872ddb88a..fb3e68d6c 100644 --- a/libreplan-webapp/src/main/java/org/libreplan/importers/JiraTimesheetSynchronizer.java +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/JiraTimesheetSynchronizer.java @@ -19,24 +19,31 @@ package org.libreplan.importers; +import static org.libreplan.web.I18nHelper._; + import java.util.List; import java.util.Set; +import org.apache.commons.lang.StringUtils; import org.hibernate.NonUniqueResultException; import org.libreplan.business.common.IAdHocTransactionService; -import org.libreplan.business.common.daos.IConfigurationDAO; -import org.libreplan.business.common.entities.JiraConfiguration; +import org.libreplan.business.common.daos.IConnectorDAO; +import org.libreplan.business.common.entities.Connector; +import org.libreplan.business.common.entities.ConnectorException; +import org.libreplan.business.common.entities.PredefinedConnectorProperties; +import org.libreplan.business.common.entities.PredefinedConnectors; import org.libreplan.business.common.exceptions.InstanceNotFoundException; import org.libreplan.business.costcategories.daos.ITypeOfWorkHoursDAO; import org.libreplan.business.costcategories.entities.TypeOfWorkHours; +import org.libreplan.business.orders.daos.IOrderSyncInfoDAO; import org.libreplan.business.orders.entities.Order; import org.libreplan.business.orders.entities.OrderElement; +import org.libreplan.business.orders.entities.OrderSyncInfo; import org.libreplan.business.resources.daos.IWorkerDAO; import org.libreplan.business.resources.entities.Resource; import org.libreplan.business.resources.entities.Worker; import org.libreplan.business.workingday.EffortDuration; import org.libreplan.business.workreports.daos.IWorkReportDAO; -import org.libreplan.business.workreports.daos.IWorkReportLineDAO; import org.libreplan.business.workreports.daos.IWorkReportTypeDAO; import org.libreplan.business.workreports.entities.PredefinedWorkReportTypes; import org.libreplan.business.workreports.entities.WorkReport; @@ -63,7 +70,7 @@ import org.springframework.transaction.annotation.Transactional; @Scope(BeanDefinition.SCOPE_PROTOTYPE) public class JiraTimesheetSynchronizer implements IJiraTimesheetSynchronizer { - private JiraSyncInfo jiraSyncInfo; + private SynchronizationInfo synchronizationInfo; private List workers; @@ -80,9 +87,6 @@ public class JiraTimesheetSynchronizer implements IJiraTimesheetSynchronizer { @Autowired private IWorkReportDAO workReportDAO; - @Autowired - private IWorkReportLineDAO workReportLineDAO; - @Autowired private IWorkReportModel workReportModel; @@ -90,50 +94,69 @@ public class JiraTimesheetSynchronizer implements IJiraTimesheetSynchronizer { private ITypeOfWorkHoursDAO typeOfWorkHoursDAO; @Autowired - private IConfigurationDAO configurationDAO; + private IConnectorDAO connectorDAO; + + @Autowired + private IOrderSyncInfoDAO orderSyncInfoDAO; @Autowired private IAdHocTransactionService adHocTransactionService; @Override @Transactional - public void syncJiraTimesheetWithJiraIssues(List issues, Order order) { - jiraSyncInfo = new JiraSyncInfo(); + public void syncJiraTimesheetWithJiraIssues(List issues, Order order) throws ConnectorException { + synchronizationInfo = new SynchronizationInfo(_("Synchronization")); workReportType = getJiraTimesheetsWorkReportType(); typeOfWorkHours = getTypeOfWorkHours(); workers = getWorkers(); if (workers == null && workers.isEmpty()) { - jiraSyncInfo.addSyncFailedReason("No workers found"); + synchronizationInfo.addFailedReason(_("No workers found")); return; } - String code = order.getCode() + "-" + order.getImportedLabel(); + OrderSyncInfo orderSyncInfo = orderSyncInfoDAO + .findLastSynchronizedInfoByOrderAndConnectorName(order, + PredefinedConnectors.JIRA.getName()); + if (orderSyncInfo == null) { + synchronizationInfo.addFailedReason(_( + "Order \"{0}\" not found. Order probalbly not synchronized", + order.getName())); + return; + } + if (StringUtils.isBlank(orderSyncInfo.getKey())) { + synchronizationInfo.addFailedReason(_( + "Key for Order \"{0}\" is empty", + order.getName())); + return; + } + + String code = order.getCode() + "-" + orderSyncInfo.getKey(); WorkReport workReport = updateOrCreateWorkReport(code); for (IssueDTO issue : issues) { WorkLogDTO worklog = issue.getFields().getWorklog(); if (worklog == null) { - jiraSyncInfo.addSyncFailedReason("No worklogs found for '" - + issue.getKey() + "'"); + synchronizationInfo.addFailedReason(_( + "No worklogs found for \"{0}\" key", issue.getKey())); } else { List workLogItems = worklog.getWorklogs(); if (workLogItems == null || workLogItems.isEmpty()) { - jiraSyncInfo - .addSyncFailedReason("No worklog items found for '" - + issue.getKey() + "' issue"); + synchronizationInfo.addFailedReason(_( + "No worklog items found for \"{0}\" issue", + issue.getKey())); } else { - String codeOrderElement = JiraConfiguration.CODE_PREFIX + String codeOrderElement = PredefinedConnectorProperties.JIRA_CODE_PREFIX + order.getCode() + "-" + issue.getKey(); OrderElement orderElement = order.getOrderElement(codeOrderElement); if (orderElement == null) { - jiraSyncInfo.addSyncFailedReason("Order element(" - + code + ") not found"); + synchronizationInfo.addFailedReason(_( + "Order element \"{0}\" not found", code)); } else { updateOrCreateWorkReportLineAndAddToWorkReport(workReport, orderElement, workLogItems); @@ -291,10 +314,31 @@ public class JiraTimesheetSynchronizer implements IJiraTimesheetSynchronizer { * Returns {@link TypeOfWorkHours} configured for JIRA connector * * @return TypeOfWorkHours for JIRA connector + * @throws ConnectorException */ - private TypeOfWorkHours getTypeOfWorkHours() { - return configurationDAO.getConfiguration().getJiraConfiguration() - .getJiraConnectorTypeOfWorkHours(); + private TypeOfWorkHours getTypeOfWorkHours() throws ConnectorException { + Connector connector = connectorDAO + .findUniqueByName(PredefinedConnectors.JIRA.getName()); + if (connector == null) { + throw new ConnectorException(_("JIRA connector not found")); + } + + TypeOfWorkHours typeOfWorkHours; + String name = connector.getPropertiesAsMap().get( + PredefinedConnectorProperties.JIRA_HOURS_TYPE); + + if (StringUtils.isBlank(name)) { + throw new ConnectorException( + _("Hours type should not be empty to synchronine timesheets")); + } + + try { + typeOfWorkHours = typeOfWorkHoursDAO.findUniqueByName(name); + } catch (InstanceNotFoundException e) { + throw new RuntimeException(e); + } + + return typeOfWorkHours; } /** @@ -336,13 +380,13 @@ public class JiraTimesheetSynchronizer implements IJiraTimesheetSynchronizer { return worker; } } - jiraSyncInfo.addSyncFailedReason("Worker('" + nif + "') not found"); + synchronizationInfo.addFailedReason(_("Worker \"{0}\" not found", nif)); return null; } @Override - public JiraSyncInfo getJiraSyncInfo() { - return jiraSyncInfo; + public SynchronizationInfo getSynchronizationInfo() { + return synchronizationInfo; } } diff --git a/libreplan-webapp/src/main/java/org/libreplan/importers/RosterException.java b/libreplan-webapp/src/main/java/org/libreplan/importers/RosterException.java new file mode 100644 index 000000000..74e3630f7 --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/RosterException.java @@ -0,0 +1,188 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 St. Antoniusziekenhuis + * + * 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 . + */ + +package org.libreplan.importers; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +import org.joda.time.LocalDate; +import org.libreplan.business.resources.entities.Worker; +import org.libreplan.business.workingday.EffortDuration; +import org.libreplan.business.workingday.IntraDayDate.PartialDay; +import org.libreplan.importers.tim.RosterDTO; + +/** + * Class to convert the Roster response DTO to the RosterException + * + * @author Miciele Ghiorghis + */ +public class RosterException { + private Worker worker; + private int productivityFactor; + + List rosterExceptionItems = new ArrayList(); + + public RosterException(Worker worker, int productivityFactor) { + this.worker = worker; + this.productivityFactor = productivityFactor; + } + + /** + * Reads the rosters and add the exceptions to + * rosterExceptionItems + * + * @param rosterDTOs + * list of rosterDTO + */ + public void addRosterExceptions(List rosterDTOs) { + Map> mapDateRosterDTO = new TreeMap>(); + + for (RosterDTO rosterDTO : rosterDTOs) { + if (!mapDateRosterDTO.containsKey(rosterDTO.getDate())) { + mapDateRosterDTO.put(rosterDTO.getDate(), new ArrayList()); + } + mapDateRosterDTO.get(rosterDTO.getDate()).add(rosterDTO); + + } + + for (Map.Entry> entry : mapDateRosterDTO + .entrySet()) { + RosterExceptionItem item = new RosterExceptionItem(entry.getKey()); + updateExceptionTypeAndEffort(item, entry.getValue()); + rosterExceptionItems.add(item); + } + } + + /** + * updates the exceptionType and effortDuration + * + * In Tim you can divide your exception day in different + * exceptionType, for example on Monday: + *
    + *
  • 4 hours RESOURCE_HOLIDAY
  • + *
  • 2 hours STRIKE
  • + *
  • 2 hours BANK_HOLIDAY
  • + *
+ * + * But Libreplan allows only one exceptionType per day. + * + * In order to store different exceptionTypes per day as one + * exceptionType, this method gets the + * exceptionType from the rosterDTO with the + * highest duration, in this example RESOURCE_HOLIDAY as a valid exception + * type, but the total duration is the sum of all these exception types + * + * Again in Tim this total duration means that the worker is on holiday for + * that total duration, but Libreplan does the opposite. In Libreplan, the + * total duration in this case means that the worker is not on holiday. If + * he is then the total duration should be Zero. And this method does this + * translation + * + * @param rosterExceptionItem + * the rosterException item + * @param rosterDTOs + * list of rosterDTO + */ + private void updateExceptionTypeAndEffort( + RosterExceptionItem rosterExceptionItem, List rosterDTOs) { + EffortDuration max = EffortDuration.zero(); + EffortDuration sum = EffortDuration.zero(); + String rosterCatName = rosterDTOs.get(0).getRosterCategories().get(0) + .getName(); + for (RosterDTO rosterDTO : rosterDTOs) { + EffortDuration duration = EffortDuration + .parseFromFormattedString(rosterDTO.getDuration()); + if (duration.compareTo(max) > 0) { + rosterCatName = rosterDTO.getRosterCategories().get(0) + .getName(); + } + max = EffortDuration.max(max, duration); + sum = EffortDuration.sum(sum, duration); + + } + EffortDuration exceptionTime = EffortDuration.zero(); + + EffortDuration workableTime = worker.getCalendar().getCapacityOn( + PartialDay.wholeDay(rosterExceptionItem.getDate())); + + // Convert the total duration from Tim to the productivity time as is + // configured in resource calendar + EffortDuration productivityTime = sum.multiplyBy(productivityFactor) + .divideBy(100); + + // Calculate the exception time + if (workableTime.compareTo(productivityTime) >= 0) { + exceptionTime = workableTime.minus(productivityTime); + } + + rosterExceptionItem.setExceptionType(rosterCatName); + rosterExceptionItem.setEffortDuration(exceptionTime); + } + + /** + * returns {@link Worker} + */ + public Worker getWorker() { + return worker; + } + + /** + * returns list of {@link RosterExceptionItem} + */ + public List getRosterExceptionItems() { + return rosterExceptionItems; + } + + /** + * class representing RosterExceptionItem + */ + public class RosterExceptionItem { + private LocalDate date; + private String exceptionType; + private EffortDuration effortDuration; + + public RosterExceptionItem(LocalDate date) { + this.date = date; + } + + public LocalDate getDate() { + return date; + } + + public String getExceptionType() { + return exceptionType; + } + + public void setExceptionType(String exceptionType) { + this.exceptionType = exceptionType; + } + + public EffortDuration getEffortDuration() { + return effortDuration; + } + + public void setEffortDuration(EffortDuration effortDuration) { + this.effortDuration = effortDuration; + } + } + +} diff --git a/libreplan-webapp/src/main/java/org/libreplan/importers/SchedulerInfo.java b/libreplan-webapp/src/main/java/org/libreplan/importers/SchedulerInfo.java new file mode 100644 index 000000000..5cd0e15f1 --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/SchedulerInfo.java @@ -0,0 +1,60 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 St. Antoniusziekenhuis + * + * 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 . + */ + +package org.libreplan.importers; + +import org.libreplan.business.common.entities.JobSchedulerConfiguration; + +/** + * Holds information about the scheduler, The information comes partly form + * {@link JobSchedulerConfiguration} and partly form {@link SchedulerManager} + * + * @author Miciele Ghiorghis + */ +public class SchedulerInfo { + + private JobSchedulerConfiguration jobSchedulerConfiguration; + private String nextFireTime; + + public SchedulerInfo() { + + } + + public SchedulerInfo(JobSchedulerConfiguration jobSchedulerConfiguration) { + this.jobSchedulerConfiguration = jobSchedulerConfiguration; + } + + public String getNextFireTime() { + return nextFireTime; + } + + public void setNextFireTime(String nextFireTime) { + this.nextFireTime = nextFireTime; + } + + public JobSchedulerConfiguration getJobSchedulerConfiguration() { + return jobSchedulerConfiguration; + } + + public void setJobSchedulerConfiguration( + JobSchedulerConfiguration jobSchedulerConfiguration) { + this.jobSchedulerConfiguration = jobSchedulerConfiguration; + } + +} diff --git a/libreplan-webapp/src/main/java/org/libreplan/importers/SchedulerManager.java b/libreplan-webapp/src/main/java/org/libreplan/importers/SchedulerManager.java new file mode 100644 index 000000000..ef1b49da0 --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/SchedulerManager.java @@ -0,0 +1,336 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 St. Antoniusziekenhuis + * + * 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 . + */ + +package org.libreplan.importers; + +import java.text.ParseException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.libreplan.business.common.daos.IConnectorDAO; +import org.libreplan.business.common.daos.IJobSchedulerConfigurationDAO; +import org.libreplan.business.common.entities.Connector; +import org.libreplan.business.common.entities.JobClassNameEnum; +import org.libreplan.business.common.entities.JobSchedulerConfiguration; +import org.quartz.CronExpression; +import org.quartz.CronTrigger; +import org.quartz.JobExecutionContext; +import org.quartz.Scheduler; +import org.quartz.SchedulerException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Scope; +import org.springframework.scheduling.quartz.CronTriggerBean; +import org.springframework.scheduling.quartz.JobDetailBean; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +/** + * Implementation of scheduler manager + * + * @author Miciele Ghiorghis + */ +@Service +@Scope(BeanDefinition.SCOPE_SINGLETON) +public class SchedulerManager implements ISchedulerManager { + + private static final Log LOG = LogFactory.getLog(SchedulerManager.class); + + @Autowired + private Scheduler scheduler; + + @Autowired + private IJobSchedulerConfigurationDAO jobSchedulerConfigurationDAO; + + @Autowired + private ApplicationContext applicationContext; + + @Autowired + private IConnectorDAO connectorDAO; + + + /** + * suffix for trigger -group and -name + */ + private static final String TRIGGER_SUFFIX = "-TRIGGER"; + + public Scheduler getScheduler() { + return scheduler; + } + + public void setScheduler(Scheduler scheduler) { + this.scheduler = scheduler; + } + + @Override + public void scheduleJobs() { + List jobSchedulerConfigurations = jobSchedulerConfigurationDAO + .getAll(); + for (JobSchedulerConfiguration conf : jobSchedulerConfigurations) { + try { + scheduleOrUnscheduleJob(conf); + } catch (SchedulerException e) { + LOG.error("Unable to schedule", e); + } + } + } + + @Override + @Transactional(readOnly = true) + public void scheduleOrUnscheduleJob( + JobSchedulerConfiguration jobSchedulerConfiguration) throws SchedulerException { + + if (hasConnector(jobSchedulerConfiguration.getConnectorName())) { + if (isConnectorActivated(jobSchedulerConfiguration + .getConnectorName())) { + if (jobSchedulerConfiguration.isSchedule()) { + scheduleNewJob(jobSchedulerConfiguration); + return; + } + } + deleteJob(jobSchedulerConfiguration); + return; + } + if (!jobSchedulerConfiguration.isSchedule()) { + deleteJob(jobSchedulerConfiguration); + return; + } + scheduleNewJob(jobSchedulerConfiguration); + } + + /** + * Check if {@link JobSchedulerConfiguration} has a connector + * + * @param connectorName + * the connector to check for + * @return true if connector is not null or empty + */ + private boolean hasConnector(String connectorName) { + return !StringUtils.isBlank(connectorName); + } + + /** + * Check if the specified {@link Connector} is activated + * + * @param connectorName + * the connector to check for activated + * @return true if activated + */ + private boolean isConnectorActivated(String connectorName) { + Connector connector = connectorDAO.findUniqueByName(connectorName); + if (connector == null) { + return false; + } + return connector.isActivated(); + } + + @Override + public void deleteJob(JobSchedulerConfiguration jobSchedulerConfiguration) throws SchedulerException { + String triggerName = jobSchedulerConfiguration.getJobName() + + TRIGGER_SUFFIX; + String triggerGroup = jobSchedulerConfiguration.getJobGroup() + + TRIGGER_SUFFIX; + + CronTriggerBean trigger = getTriggerBean(triggerName, triggerGroup); + if (trigger == null) { + LOG.warn("Trigger not found"); + return; + } + + if (isJobCurrentlyExecuting(triggerName, triggerGroup)) { + LOG.warn("Job is currently executing..."); + return; + } + + // deleteJob doesn't work using unscheduleJob + this.scheduler.unscheduleJob(trigger.getName(), trigger.getGroup()); + } + + /** + * Checks if job is currently running for the specified + * triggerName and triggerGroup + * + * @param triggerName + * the triggerName + * @param triggerGroup + * the triggerGroup + * @return true if job is currently running, otherwise false + */ + @SuppressWarnings("unchecked") + private boolean isJobCurrentlyExecuting(String triggerName, + String triggerGroup) { + try { + List currentExecutingJobs = this.scheduler + .getCurrentlyExecutingJobs(); + for (JobExecutionContext jobExecutionContext : currentExecutingJobs) { + String name = jobExecutionContext.getTrigger().getName(); + String group = jobExecutionContext.getTrigger().getGroup(); + if (triggerName.equals(name) && triggerGroup.equals(group)) { + return true; + } + } + } catch (SchedulerException e) { + LOG.error("Unable to get currently executing jobs", e); + } + return false; + } + + /** + * Creates {@link CronTriggerBean} and {@link JobDetailBean} based on the + * specified {@link JobSchedulerConfiguration}. First delete + * job if exist and then schedule it + * + * @param jobSchedulerConfiguration + * where to reade jobs to be scheduled + * @throws SchedulerException + * if unable to delete and/or schedule job + */ + private void scheduleNewJob( + JobSchedulerConfiguration jobSchedulerConfiguration) throws SchedulerException { + CronTriggerBean cronTriggerBean = createCronTriggerBean(jobSchedulerConfiguration); + if (cronTriggerBean == null) { + return; + } + + JobDetailBean jobDetailBean = createJobDetailBean(jobSchedulerConfiguration); + if (jobDetailBean == null) { + return; + } + deleteJob(jobSchedulerConfiguration); + this.scheduler.scheduleJob(jobDetailBean, cronTriggerBean); + } + + /** + * Creates {@link CronTriggerBean} from the specified + * {@link JobSchedulerConfiguration} + * + * @param jobSchedulerConfiguration + * configuration to create CronTriggerBean + * @return the created CronTriggerBean or null if unable to + * create it + */ + private CronTriggerBean createCronTriggerBean( + JobSchedulerConfiguration jobSchedulerConfiguration) { + CronTriggerBean cronTriggerBean = new CronTriggerBean(); + cronTriggerBean.setName(jobSchedulerConfiguration.getJobName() + TRIGGER_SUFFIX); + cronTriggerBean.setGroup(jobSchedulerConfiguration.getJobGroup() + + TRIGGER_SUFFIX); + + try { + cronTriggerBean.setCronExpression(new CronExpression( + jobSchedulerConfiguration.getCronExpression())); + cronTriggerBean.setJobName(jobSchedulerConfiguration.getJobName()); + cronTriggerBean + .setJobGroup(jobSchedulerConfiguration.getJobGroup()); + return cronTriggerBean; + } catch (ParseException e) { + LOG.error("Unable to parse cron expression", e); + } + return null; + } + + /** + * Creates {@link JobDetailBean} from the specified + * {@link JobSchedulerConfiguration} + * + * @param jobSchedulerConfiguration + * configuration to create JobDetailBean + * @return the created JobDetailBean or null if unable to it + */ + private JobDetailBean createJobDetailBean( + JobSchedulerConfiguration jobSchedulerConfiguration) { + JobDetailBean jobDetailBean = new JobDetailBean(); + + Class jobClass = getJobClass(jobSchedulerConfiguration + .getJobClassName()); + if (jobClass == null) { + return null; + } + + jobDetailBean.setName(jobSchedulerConfiguration.getJobName()); + jobDetailBean.setGroup(jobSchedulerConfiguration.getJobGroup()); + jobDetailBean.setJobClass(jobClass); + + Map jobDataAsMap = new HashMap(); + jobDataAsMap.put("applicationContext", applicationContext); + jobDetailBean.setJobDataAsMap(jobDataAsMap); + return jobDetailBean; + } + + + /** + * returns jobClass based on jobClassName parameter + * + * @param jobClassName + * job className + */ + private Class getJobClass(JobClassNameEnum jobClassName) { + try { + return Class.forName(jobClassName.getPackageName() + "." + + jobClassName.getName()); + } catch (ClassNotFoundException e) { + LOG.error("Unable to get class object '" + jobClassName + "'", e); + } + return null; + } + + @Override + public String getNextFireTime( + JobSchedulerConfiguration jobSchedulerConfiguration) { + try { + CronTrigger trigger = (CronTrigger) this.scheduler.getTrigger( + jobSchedulerConfiguration.getJobName() + TRIGGER_SUFFIX, + jobSchedulerConfiguration.getJobGroup() + + TRIGGER_SUFFIX); + if (trigger != null) { + return trigger.getNextFireTime().toString(); + } + } catch (SchedulerException e) { + LOG.error("unable to get the trigger", e); + } + return ""; + } + + /** + * gets the {@link CronTriggerBean} for the specified + * triggerName and tirggerGroup + * + * @param triggerName + * the trigger name + * @param triggerGroup + * the trigger group + * @return CronTriggerBean if found, otherwise null + */ + private CronTriggerBean getTriggerBean(String triggerName, + String triggerGroup) { + try { + return (CronTriggerBean) this.scheduler.getTrigger(triggerName, + triggerGroup); + } catch (SchedulerException e) { + LOG.error("Unable to get job trigger", e); + } + return null; + } + +} diff --git a/libreplan-webapp/src/main/java/org/libreplan/importers/JiraSyncInfo.java b/libreplan-webapp/src/main/java/org/libreplan/importers/SynchronizationInfo.java similarity index 51% rename from libreplan-webapp/src/main/java/org/libreplan/importers/JiraSyncInfo.java rename to libreplan-webapp/src/main/java/org/libreplan/importers/SynchronizationInfo.java index fecf80d95..192710eb3 100644 --- a/libreplan-webapp/src/main/java/org/libreplan/importers/JiraSyncInfo.java +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/SynchronizationInfo.java @@ -24,38 +24,57 @@ import java.util.Collections; import java.util.List; /** - * Keeps track the synchronization info. + * Keeps track the success/failure of synchronization process * * @author Miciele Ghiorghis */ -public class JiraSyncInfo { - - private List syncFailedReasons = new ArrayList(); +public class SynchronizationInfo { /** - * Add the specified reason to syncFailedReasons list - * - * @param reason - * reason why synchronizition failed + * The action, a unique key for example synchronization, import or export + * etc action */ - public void addSyncFailedReason(String reason) { - syncFailedReasons.add(reason); + private String action; + + /** + * Holds failed reasons + */ + private List failedReasons = new ArrayList(); + + public SynchronizationInfo(String action) { + this.action = action; } /** - * Is synchronization successful - * - * @return + * Returns the action */ - public boolean isSyncSuccessful() { - return syncFailedReasons.isEmpty(); + public String getAction() { + return action; + } + + /** + * Adds the specified reason to failedReasons list + * + * @param reason + * reason why synchronization is failed + */ + public void addFailedReason(String reason) { + failedReasons.add(reason); + } + + /** + * Is synchronization succeeded + * + * @return true if failedReasons is empty + */ + public boolean isSuccessful() { + return failedReasons.isEmpty(); } /** * returns reasons why synchronization is failed */ - public List getSyncFailedReasons() { - return Collections.unmodifiableList(syncFailedReasons); + public List getFailedReasons() { + return Collections.unmodifiableList(failedReasons); } - } diff --git a/libreplan-webapp/src/main/java/org/libreplan/importers/TimSoapClient.java b/libreplan-webapp/src/main/java/org/libreplan/importers/TimSoapClient.java new file mode 100644 index 000000000..edd96c064 --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/TimSoapClient.java @@ -0,0 +1,347 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 St. Antoniusziekenhuis + * + * 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 . + */ + +package org.libreplan.importers; + +import java.io.File; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Marshaller; +import javax.xml.bind.Unmarshaller; +import javax.xml.soap.MessageFactory; +import javax.xml.soap.Node; +import javax.xml.soap.SOAPBody; +import javax.xml.soap.SOAPConnection; +import javax.xml.soap.SOAPConnectionFactory; +import javax.xml.soap.SOAPEnvelope; +import javax.xml.soap.SOAPException; +import javax.xml.soap.SOAPMessage; +import javax.xml.soap.SOAPPart; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.cxf.common.util.Base64Utility; +import org.libreplan.importers.tim.RosterResponseDTO; + +/** + * Client to interact with Tim SOAP server. + * + * This client creates SOAP message, makes connection to the SOAP server and + * sends the request. It is also the task of this client to convert the + * response(xml document) to java objects + * + * @author Miciele Ghiorghis + */ +public class TimSoapClient { + + private static final Log LOG = LogFactory.getLog(TimSoapClient.class); + + /** + * Creates request message to be send to the SOAP server + * + * @param clazz + * object to be marshaled + * @param userName + * the user name + * @param password + * the password + * @return the created soap message + * @throws SOAPException + * if unable to create message or envelope + * @throws JAXBException + * if unable to marshal the clazz + */ + private static SOAPMessage createRequest(T clazz, String userName, + String password) throws SOAPException, JAXBException { + + SOAPMessage message = createMessage(); + + addAuthorization(message, userName, password); + + SOAPEnvelope soapEnvelope = createEnvelope(message.getSOAPPart()); + + SOAPBody soapBody = soapEnvelope.getBody(); + marshal(clazz, soapBody); + + message.saveChanges(); + return message; + } + + /** + * Creates SOAP message to be send to the SOAP server + * + * @return the created SOAP message + * @throws SOAPException + * if unable to create soap message + */ + private static SOAPMessage createMessage() throws SOAPException { + MessageFactory messageFactory = MessageFactory.newInstance(); + SOAPMessage message = messageFactory.createMessage(); + return message; + } + + /** + * Adds authorization to the specified parameter message + * + * @param message + * the message + * @param username + * the user name + * @param password + * the password + */ + private static void addAuthorization(SOAPMessage message, String username, + String password) { + String encodeUserInfo = username + ":" + password; + encodeUserInfo = Base64Utility.encode(encodeUserInfo.getBytes()); + message.getMimeHeaders().setHeader("Authorization", + "Basic " + encodeUserInfo); + } + + /** + * Creates SOAP envelope and adds namespace declaration and sets encoding + * style + * + * @param soapPart + * the message part + * @return the SOAP envelope + * @throws SOAPException + */ + private static SOAPEnvelope createEnvelope(SOAPPart soapPart) + throws SOAPException { + SOAPEnvelope soapEnvelope = soapPart.getEnvelope(); + addNamespaceDeclaration(soapEnvelope); + setEncodingStyle(soapEnvelope); + return soapEnvelope; + } + + /** + * Adds namespace declaration to the specified parameter + * soapEnvelop + * + * @param soapEnvelope + * the SOAP envelope + * @throws SOAPException + */ + private static void addNamespaceDeclaration(SOAPEnvelope soapEnvelope) + throws SOAPException { + soapEnvelope.addNamespaceDeclaration("xsd", + "http://www.w3.org/2001/XMLSchema"); + soapEnvelope.addNamespaceDeclaration("xsi", + "http://www.w3.org/2001/XMLSchema-instance"); + soapEnvelope.addNamespaceDeclaration("enc", + "http://schemas.xmlsoap.org/soap/encoding/"); + soapEnvelope.addNamespaceDeclaration("env", + "http://schemas.xmlsoap.org/soap/envelop/"); + + } + + /** + * Sets the encoding style to the specified parameter + * soapEnvelop + * + * @param soapEnvelope + * the SOAP envelope + * @throws SOAPException + */ + private static void setEncodingStyle(SOAPEnvelope soapEnvelope) + throws SOAPException { + soapEnvelope + .setEncodingStyle("http://schemas.xmlsoap.org/soap/encoding/"); + } + + /** + * Marshals the specified parameter clazz to the specified + * soapBody + * + * @param clazz + * the object to be marshaled + * @param soapBody + * the SOAP body, result of marshal + * @throws JAXBException + * if marshaling failed + */ + private static void marshal(T clazz, SOAPBody soapBody) + throws JAXBException { + JAXBContext jaxbContext = JAXBContext.newInstance(clazz.getClass()); + Marshaller marshaller = jaxbContext.createMarshaller(); + marshaller.marshal(clazz, soapBody); + } + + /** + * Unmarshals the specified paramter soapBody to the specified + * clazz + * + * @param clazz + * object to hold unmarashal result + * @param soapBody + * the soap body to be unmarshalled + * @return the unmarashalled object + * @throws JAXBException + * if unmarshal failed + */ + @SuppressWarnings("unchecked") + private static T unmarshal(Class clazz, SOAPBody soapBody) + throws JAXBException { + JAXBContext jaxbContext = JAXBContext.newInstance(clazz); + + Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); + Node bindElement = (Node) soapBody.getFirstChild(); + while (bindElement.getNodeType() != Node.ELEMENT_NODE) { + bindElement = (Node) bindElement.getNextSibling(); + } + return unmarshaller.unmarshal(bindElement, clazz).getValue(); + } + + /** + * Sends the SOAP message request to the SOAP server + * + * @param url + * the endpoint of the web service + * @param message + * the SOAP message to be send + * @return the response, SOAP message + * @throws SOAPException + * if unable to send request + */ + private static SOAPMessage sendRequest(String url, SOAPMessage message) + throws SOAPException { + SOAPConnection connection = null; + SOAPMessage response = null; + try { + connection = createConnection(); + response = connection.call(message, url); + } finally { + if (connection != null) { + closeConnection(connection); + } + } + return response; + } + + + /** + * Creates a SOAP connection to the SOAP server + * + * @return the SOAPconnection object + * @throws SOAPException + * if unable to create connection + */ + private static SOAPConnection createConnection() throws SOAPException { + SOAPConnectionFactory soapConnectionFactory = SOAPConnectionFactory + .newInstance(); + SOAPConnection connection = soapConnectionFactory.createConnection(); + return connection; + } + + /** + * Closes the SOAP connection + * + * @param connection + * the SOAP connection + * @throws SOAPException + * if unable to close connection + */ + private static void closeConnection(SOAPConnection connection) + throws SOAPException { + connection.close(); + } + + /** + * Sends soap request to the SOAP server. Receives and unmarshals the + * response + * + * @param url + * the SOAP server url(endpoint) + * @param userName + * the user + * @param password + * the password + * @param request + * the request object + * @param response + * the response class + * @return the expected object or null + */ + public static T sendRequestReceiveResponse(String url, + String userName, String password, U request, Class response) { + try { + SOAPMessage requestMsg = createRequest(request, userName, password); + SOAPMessage responseMsg = sendRequest(url, requestMsg); + return unmarshal(response, responseMsg.getSOAPBody()); + } catch (SOAPException soapExp) { + LOG.error("SOAPException: ", soapExp); + } catch (JAXBException jaxbExp) { + LOG.error("JAXBException: ", jaxbExp); + } + + return null; + } + + /** + * Checks authorization for the specified username and + * password + * + * @param url + * webservices url + * @param username + * the user + * @param password + * the password + * @return true if user is authorized otherwise false + */ + public static boolean checkAuthorization(String url, String username, + String password) { + try { + SOAPMessage message = createMessage(); + addAuthorization(message, username, password); + sendRequest(url, message); + return true; + } catch (SOAPException e) { + LOG.error("SOAP Exception: ", e); + } + return false; + } + + /** + * simulates roster response, to be used for example by unit test + * + * unmarshals the roster xml from the specified file and + * returns {@link RosterResponseDTO} + * + * @param file + * file with xml contents + * @return exportRosterDTO if unmarshal succeeded otherwise null + */ + public static RosterResponseDTO unmarshalRosterFromFile(File file) { + try { + JAXBContext jaxbContext = JAXBContext + .newInstance(RosterResponseDTO.class); + + Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); + RosterResponseDTO exportResponseDTO = (RosterResponseDTO) unmarshaller + .unmarshal(file); + return exportResponseDTO; + } catch (JAXBException e) { + LOG.error("Error processing response: ", e); + } + return null; + } +} diff --git a/libreplan-webapp/src/main/java/org/libreplan/importers/tim/DataDTO.java b/libreplan-webapp/src/main/java/org/libreplan/importers/tim/DataDTO.java new file mode 100644 index 000000000..7cfb9e2fa --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/tim/DataDTO.java @@ -0,0 +1,49 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 St. Antoniusziekenhuis + * + * 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 . + */ + +package org.libreplan.importers.tim; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAnyElement; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlSeeAlso; + +/** + * DTO representing a tim-connector Data + * + * @author Miciele Ghiorghis + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlRootElement +@XmlSeeAlso({ RosterDTO.class }) +public class DataDTO { + + @XmlAnyElement + private T data; + + public T getData() { + return data; + } + + public void setData(T data) { + this.data = data; + } + +} diff --git a/libreplan-webapp/src/main/java/org/libreplan/importers/tim/DepartmentDTO.java b/libreplan-webapp/src/main/java/org/libreplan/importers/tim/DepartmentDTO.java new file mode 100644 index 000000000..57368e9de --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/tim/DepartmentDTO.java @@ -0,0 +1,50 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 St. Antoniusziekenhuis + * + * 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 . + */ + +package org.libreplan.importers.tim; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.adapters.CollapsedStringAdapter; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; + +/** + * DTO representing a tim-connector Department + * + * @author Miciele Ghiorghis + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlRootElement(name = "Afdeling") +public class DepartmentDTO { + + @XmlJavaTypeAdapter(CollapsedStringAdapter.class) + @XmlElement + private String ref; + + public String getRef() { + return ref; + } + + public void setRef(String ref) { + this.ref = ref; + } + +} diff --git a/libreplan-webapp/src/main/java/org/libreplan/importers/tim/DurationDTO.java b/libreplan-webapp/src/main/java/org/libreplan/importers/tim/DurationDTO.java new file mode 100644 index 000000000..9ed34bc86 --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/tim/DurationDTO.java @@ -0,0 +1,62 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 St. Antoniusziekenhuis + * + * 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 . + */ + +package org.libreplan.importers.tim; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlValue; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; + +/** + * DTO representing a tim-connector Duration + * + * @author Miciele Ghiorghis + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlRootElement +public class DurationDTO { + + @XmlAttribute(name = "options", required = true) + private String options; + + + @XmlValue + @XmlJavaTypeAdapter(TimDoubleAdapter.class) + private Double duration; + + public String getOptions() { + return options; + } + + public void setOptions(String options) { + this.options = options; + } + + public Double getDuration() { + return duration; + } + + public void setDuration(Double duration) { + this.duration = duration; + } + +} diff --git a/libreplan-webapp/src/main/java/org/libreplan/importers/tim/FilterDTO.java b/libreplan-webapp/src/main/java/org/libreplan/importers/tim/FilterDTO.java new file mode 100644 index 000000000..3027c475a --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/tim/FilterDTO.java @@ -0,0 +1,82 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 St. Antoniusziekenhuis + * + * 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 . + */ + +package org.libreplan.importers.tim; + +import java.util.List; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + +/** + * DTO representing a tim-connector filter + * + * @author Miciele Ghiorghis + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlRootElement +public class FilterDTO { + + @XmlElement(name = "persoon") + private PersonDTO person; + + @XmlElement(name = "periode") + private List periods; + + @XmlElement(name = "Afdeling") + private DepartmentDTO department; + + @XmlElement(name = "roostercategorie") + private RosterCategoryDTO rosterCategory; + + public PersonDTO getPerson() { + return person; + } + + public void setPerson(PersonDTO person) { + this.person = person; + } + + public List getPeriods() { + return periods; + } + + public void setPeriods(List periods) { + this.periods = periods; + } + + public DepartmentDTO getDepartment() { + return department; + } + + public void setDepartment(DepartmentDTO department) { + this.department = department; + } + + public RosterCategoryDTO getRosterCategory() { + return rosterCategory; + } + + public void setRosterCategory(RosterCategoryDTO rosterCategory) { + this.rosterCategory = rosterCategory; + } + +} diff --git a/libreplan-webapp/src/main/java/org/libreplan/importers/tim/PeriodDTO.java b/libreplan-webapp/src/main/java/org/libreplan/importers/tim/PeriodDTO.java new file mode 100644 index 000000000..7e1b81a64 --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/tim/PeriodDTO.java @@ -0,0 +1,59 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 St. Antoniusziekenhuis + * + * 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 . + */ + +package org.libreplan.importers.tim; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + +import org.joda.time.DateTime; + +/** + * DTO representing a tim-connector Period + * + * @author Miciele Ghiorghis + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlRootElement +public class PeriodDTO { + + @XmlElement(name = "startdate", required = true, nillable = true) + private DateTime start; + + @XmlElement(name = "enddate", required = true, nillable = true) + private DateTime end; + + public DateTime getStart() { + return start; + } + + public void setStart(DateTime start) { + this.start = start; + } + + public DateTime getEnd() { + return end; + } + + public void setEnd(DateTime end) { + this.end = end; + } +} diff --git a/libreplan-webapp/src/main/java/org/libreplan/importers/tim/PersonDTO.java b/libreplan-webapp/src/main/java/org/libreplan/importers/tim/PersonDTO.java new file mode 100644 index 000000000..61c112c02 --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/tim/PersonDTO.java @@ -0,0 +1,88 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 St. Antoniusziekenhuis + * + * 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 . + */ + +package org.libreplan.importers.tim; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlType; +import javax.xml.bind.annotation.adapters.CollapsedStringAdapter; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; + +/** + * DTO representing a tim-connector Person + * + * @author Miciele Ghiorghis + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlRootElement(name = "Persoon") +@XmlType(propOrder = { "networkName", "name", "emailAddress" }) +public class PersonDTO { + + @XmlAttribute(name = "options") + private String options; + + @XmlJavaTypeAdapter(CollapsedStringAdapter.class) + @XmlElement(name = "naam", nillable = true) + private String name; + + @XmlJavaTypeAdapter(CollapsedStringAdapter.class) + @XmlElement(name = "emaladres", nillable = true) + private String emailAddress; + + @XmlJavaTypeAdapter(CollapsedStringAdapter.class) + @XmlElement(name = "netwerknaam", nillable = true) + private String networkName; + + public String getOptions() { + return options; + } + + public void setOptions(String options) { + this.options = options; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getEmailAddress() { + return emailAddress; + } + + public void setEmailAddress(String emailAddress) { + this.emailAddress = emailAddress; + } + + public String getNetworkName() { + return networkName; + } + + public void setNetworkName(String networkName) { + this.networkName = networkName; + } + +} diff --git a/libreplan-webapp/src/main/java/org/libreplan/importers/tim/ProductDTO.java b/libreplan-webapp/src/main/java/org/libreplan/importers/tim/ProductDTO.java new file mode 100644 index 000000000..e657d249d --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/tim/ProductDTO.java @@ -0,0 +1,72 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 St. Antoniusziekenhuis + * + * 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 . + */ + +package org.libreplan.importers.tim; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.adapters.CollapsedStringAdapter; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; + +/** + * DTO representing a tim-connector Product + * + * @author Miciele Ghiorghis + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlRootElement +public class ProductDTO { + + @XmlAttribute(required = true) + private String options; + + @XmlJavaTypeAdapter(CollapsedStringAdapter.class) + @XmlElement + private String code; + + private String name; + + public String getOptions() { + return options; + } + + public void setOptions(String options) { + this.options = options; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + +} diff --git a/libreplan-webapp/src/main/java/org/libreplan/importers/tim/RegistrationDateDTO.java b/libreplan-webapp/src/main/java/org/libreplan/importers/tim/RegistrationDateDTO.java new file mode 100644 index 000000000..3cd66a8fe --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/tim/RegistrationDateDTO.java @@ -0,0 +1,61 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 St. Antoniusziekenhuis + * + * 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 . + */ + +package org.libreplan.importers.tim; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlValue; + +import org.joda.time.LocalDate; + +/** + * DTO representing a tim-connector RegistrationDate + * + * @author Miciele Ghiorghis + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlRootElement(name = "datum") +public class RegistrationDateDTO { + + @XmlAttribute(name = "options", required = true) + private String options; + + @XmlValue + private LocalDate date; + + public String getOptions() { + return options; + } + + public void setOptions(String options) { + this.options = options; + } + + public LocalDate getDate() { + return date; + } + + public void setDate(LocalDate date) { + this.date = date; + } + +} diff --git a/libreplan-webapp/src/main/java/org/libreplan/importers/tim/RosterCategoryDTO.java b/libreplan-webapp/src/main/java/org/libreplan/importers/tim/RosterCategoryDTO.java new file mode 100644 index 000000000..56045fff4 --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/tim/RosterCategoryDTO.java @@ -0,0 +1,76 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 St. Antoniusziekenhuis + * + * 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 . + */ + +package org.libreplan.importers.tim; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlType; +import javax.xml.bind.annotation.adapters.CollapsedStringAdapter; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; + +/** + * DTO representing a tim-connector RosterCategory + * + * @author Miciele Ghiorghis + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlRootElement(name = "roostercategorie") +@XmlType(propOrder = { "name", "presence", "status" }) +public class RosterCategoryDTO { + + @XmlJavaTypeAdapter(CollapsedStringAdapter.class) + @XmlElement(name = "naam", required = true, nillable = true) + private String name; + + @XmlJavaTypeAdapter(CollapsedStringAdapter.class) + @XmlElement(name = "aanwezigheid", required = true, nillable = true) + private String presence; + + @XmlJavaTypeAdapter(CollapsedStringAdapter.class) + @XmlElement(required = true, nillable = true) + private String status; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getPresence() { + return presence; + } + + public void setPresence(String presence) { + this.presence = presence; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + +} diff --git a/libreplan-webapp/src/main/java/org/libreplan/importers/tim/RosterDTO.java b/libreplan-webapp/src/main/java/org/libreplan/importers/tim/RosterDTO.java new file mode 100644 index 000000000..1bd831e0b --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/tim/RosterDTO.java @@ -0,0 +1,245 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 St. Antoniusziekenhuis + * + * 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 . + */ + +package org.libreplan.importers.tim; + +import java.util.List; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.adapters.CollapsedStringAdapter; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; + +import org.joda.time.LocalDate; +import org.joda.time.LocalTime; + +/** + * DTO representing a tim-connector Roster + * + * @author Miciele Ghiorghis + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlRootElement(name = "bezettingblok") +public class RosterDTO { + + @XmlAttribute(name = "startdate", required = true) + private LocalDate startDate; + + @XmlAttribute(name = "enddate", required = true) + private LocalDate endDate; + + @XmlAttribute(name = "resource_planning") + private Boolean resourcePlanning; + + @XmlAttribute(name = "day_planning") + private Boolean dayPlanning; + + @XmlAttribute + private Boolean calendar; + + @XmlAttribute(name = "non_planned") + private Boolean nonPlaned; + + @XmlAttribute(name = "full_day") + private Boolean fullDay; + + @XmlAttribute + private Boolean concept; + + @XmlElement + private FilterDTO filter; + + @XmlElement(name = "Persoon") + private List persons; + + @XmlElement(name = "Roostercategorie") + private List rosterCategories; + + @XmlElement(name = "Afdeling") + private DepartmentDTO department; + + @XmlElement(name = "Datum", required = true, nillable = true) + private LocalDate date; + + @XmlElement(name = "Tijd", required = true, nillable = true) + private LocalTime time; + + @XmlJavaTypeAdapter(CollapsedStringAdapter.class) + @XmlElement(name = "duur", required = true, nillable = true) + private String duration; + + @XmlJavaTypeAdapter(CollapsedStringAdapter.class) + @XmlElement(name = "aanwezigheid") + private String precence; + + @XmlElement(name = "periode") + private List periods; + + @XmlJavaTypeAdapter(CollapsedStringAdapter.class) + @XmlElement(name = "status") + private String status; + + public LocalDate getStartDate() { + return startDate; + } + + public void setStartDate(LocalDate startDate) { + this.startDate = startDate; + } + + public LocalDate getEndDate() { + return endDate; + } + + public void setEndDate(LocalDate endDate) { + this.endDate = endDate; + } + + public Boolean getResourcePlanning() { + return resourcePlanning; + } + + public void setResourcePlanning(Boolean resourcePlanning) { + this.resourcePlanning = resourcePlanning; + } + + public Boolean getDayPlanning() { + return dayPlanning; + } + + public void setDayPlanning(Boolean dayPlanning) { + this.dayPlanning = dayPlanning; + } + + public Boolean getCalendar() { + return calendar; + } + + public void setCalendar(Boolean calendar) { + this.calendar = calendar; + } + + public Boolean getNonPlaned() { + return nonPlaned; + } + + public void setNonPlaned(Boolean nonPlaned) { + this.nonPlaned = nonPlaned; + } + + public Boolean getFullDay() { + return fullDay; + } + + public void setFullDay(Boolean fullDay) { + this.fullDay = fullDay; + } + + public Boolean getConcept() { + return concept; + } + + public void setConcept(Boolean concept) { + this.concept = concept; + } + + public FilterDTO getFilter() { + return filter; + } + + public void setFilter(FilterDTO filter) { + this.filter = filter; + } + + public List getPersons() { + return persons; + } + + public void setPersons(List persons) { + this.persons = persons; + } + + public List getRosterCategories() { + return rosterCategories; + } + + public void setRosterCategories(List rosterCategories) { + this.rosterCategories = rosterCategories; + } + + public DepartmentDTO getDepartment() { + return department; + } + + public void setDepartment(DepartmentDTO department) { + this.department = department; + } + + public LocalDate getDate() { + return date; + } + + public void setDate(LocalDate date) { + this.date = date; + } + + public LocalTime getTime() { + return time; + } + + public void setTime(LocalTime time) { + this.time = time; + } + + public String getDuration() { + return duration; + } + + public void setDuration(String duration) { + this.duration = duration; + } + + public String getPrecence() { + return precence; + } + + public void setPrecence(String precence) { + this.precence = precence; + } + + public List getPeriods() { + return periods; + } + + public void setPeriods(List periods) { + this.periods = periods; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + +} diff --git a/libreplan-webapp/src/main/java/org/libreplan/importers/tim/RosterRequestDTO.java b/libreplan-webapp/src/main/java/org/libreplan/importers/tim/RosterRequestDTO.java new file mode 100644 index 000000000..5e035ffc3 --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/tim/RosterRequestDTO.java @@ -0,0 +1,47 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 St. Antoniusziekenhuis + * + * 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 . + */ + +package org.libreplan.importers.tim; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + +/** + * DTO representing a tim-connector RosterRequest + * + * @author Miciele Ghiorghis + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlRootElement(name = "export", namespace = "impexp.timn.aenova.nl") +public class RosterRequestDTO { + + @XmlElement + private DataDTO data; + + public DataDTO getData() { + return data; + } + + public void setData(DataDTO data) { + this.data = data; + } + +} diff --git a/libreplan-webapp/src/main/java/org/libreplan/importers/tim/RosterResponseDTO.java b/libreplan-webapp/src/main/java/org/libreplan/importers/tim/RosterResponseDTO.java new file mode 100644 index 000000000..7696f2cda --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/tim/RosterResponseDTO.java @@ -0,0 +1,51 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 St. Antoniusziekenhuis + * + * 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 . + */ + +package org.libreplan.importers.tim; + +import java.util.List; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlElementWrapper; +import javax.xml.bind.annotation.XmlRootElement; + +/** + * DTO representing a tim-connector RosterResponse + * + * @author Miciele Ghiorghis + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlRootElement(name = "exportResponse", namespace = "impexp.timn.aenova.nl") +public class RosterResponseDTO { + + @XmlElementWrapper(name = "return") + @XmlElement(name = "bezettingblok") + private List rosters; + + public List getRosters() { + return rosters; + } + + public void setRosters(List rosters) { + this.rosters = rosters; + } + +} diff --git a/libreplan-webapp/src/main/java/org/libreplan/importers/tim/TimDateTimeAdapter.java b/libreplan-webapp/src/main/java/org/libreplan/importers/tim/TimDateTimeAdapter.java new file mode 100644 index 000000000..643d4aba7 --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/tim/TimDateTimeAdapter.java @@ -0,0 +1,48 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 St. Antoniusziekenhuis + * + * 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 . + */ + +package org.libreplan.importers.tim; + +import javax.xml.bind.annotation.adapters.XmlAdapter; + +import org.joda.time.DateTime; +import org.joda.time.format.DateTimeFormat; +import org.joda.time.format.DateTimeFormatter; + +/** + * Adapter responsible for converting from DateTime to + * string(tim-string-datetime) and vice versa + * + * @author Miciele Ghiorghis + */ +public class TimDateTimeAdapter extends XmlAdapter { + + @Override + public String marshal(DateTime dateTime) throws Exception { + return dateTime.toString("dd-MM-yyyy"); + } + + @Override + public DateTime unmarshal(String dateTimeStr) throws Exception { + DateTimeFormatter fmt = DateTimeFormat + .forPattern("dd-MM-yyyy HH:mm:ss.SSS"); + return fmt.parseDateTime(dateTimeStr); + } + +} diff --git a/libreplan-webapp/src/main/java/org/libreplan/importers/tim/TimDoubleAdapter.java b/libreplan-webapp/src/main/java/org/libreplan/importers/tim/TimDoubleAdapter.java new file mode 100644 index 000000000..431f3c6d0 --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/tim/TimDoubleAdapter.java @@ -0,0 +1,48 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 St. Antoniusziekenhuis + * + * 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 . + */ + +package org.libreplan.importers.tim; + +import java.util.Locale; + +import javax.xml.bind.DatatypeConverter; +import javax.xml.bind.annotation.adapters.XmlAdapter; + +/** + * Adapter responsible for converting from Double to + * string(tim-string-double) and vice versa + * + * @author Miciele Ghiorghis + */ +public class TimDoubleAdapter extends XmlAdapter { + + @Override + public String marshal(Double value) throws Exception { + if(value == null) { + return null; + } + return String.format(Locale.GERMAN, "%1$,.2f", value); + } + + @Override + public Double unmarshal(String value) throws Exception { + return DatatypeConverter.parseDouble(value); + } + +} diff --git a/libreplan-webapp/src/main/java/org/libreplan/importers/tim/TimLocalDateAdapter.java b/libreplan-webapp/src/main/java/org/libreplan/importers/tim/TimLocalDateAdapter.java new file mode 100644 index 000000000..7edf3e3b9 --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/tim/TimLocalDateAdapter.java @@ -0,0 +1,47 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 St. Antoniusziekenhuis + * + * 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 . + */ + +package org.libreplan.importers.tim; + +import javax.xml.bind.annotation.adapters.XmlAdapter; + +import org.joda.time.LocalDate; +import org.joda.time.format.DateTimeFormat; +import org.joda.time.format.DateTimeFormatter; + +/** + * Adapter responsible for converting from LocalDate to + * string(tim-string-date) and vice versa + * + * @author Miciele Ghiorghis + */ +public class TimLocalDateAdapter extends XmlAdapter { + + @Override + public String marshal(LocalDate localDate) throws Exception { + return localDate.toString("dd-MM-yyyy"); + } + + @Override + public LocalDate unmarshal(String dateStr) throws Exception { + final DateTimeFormatter fmt = DateTimeFormat.forPattern("dd-MM-yyyy"); + return fmt.parseDateTime(dateStr).toLocalDate(); + } + +} diff --git a/libreplan-webapp/src/main/java/org/libreplan/importers/tim/TimOptions.java b/libreplan-webapp/src/main/java/org/libreplan/importers/tim/TimOptions.java new file mode 100644 index 000000000..8a0930a7b --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/tim/TimOptions.java @@ -0,0 +1,43 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 St. Antoniusziekenhuis + * + * 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 . + */ + +package org.libreplan.importers.tim; + +/** + * Class containing all constants for Tim-options. + * + * @author Miciele Ghiorghis + */ +public final class TimOptions { + + private TimOptions() { + + } + + public static final String UPDATE_OR_INSERT = "@"; + public static final String UPDATE = "%"; + public static final String AUTO_INSERT = "!"; + public static final String QUOTED = "''"; + public static final String DECIMAL = "#"; + public static final String SUM_DOUBLE = "+"; + public static final String SUM_LONG = "&"; + public static final String PARENT = "^"; + public static final String ANY_PARENT = "~"; + +} diff --git a/libreplan-webapp/src/main/java/org/libreplan/importers/tim/TimTimeAdapter.java b/libreplan-webapp/src/main/java/org/libreplan/importers/tim/TimTimeAdapter.java new file mode 100644 index 000000000..35f137ade --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/tim/TimTimeAdapter.java @@ -0,0 +1,44 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 St. Antoniusziekenhuis + * + * 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 . + */ + +package org.libreplan.importers.tim; + +import javax.xml.bind.annotation.adapters.XmlAdapter; + +import org.joda.time.LocalTime; + +/** + * Adapter responsible for converting from LocalTime to + * string(tim-string-time) and vice versa + * + * @author Miciele Ghiorghis + */ +public class TimTimeAdapter extends XmlAdapter { + + @Override + public String marshal(LocalTime localTime) throws Exception { + return localTime.toString(); + } + + @Override + public LocalTime unmarshal(String localTimeStr) throws Exception { + return new LocalTime(localTimeStr); + } + +} diff --git a/libreplan-webapp/src/main/java/org/libreplan/importers/tim/TimeRegistrationDTO.java b/libreplan-webapp/src/main/java/org/libreplan/importers/tim/TimeRegistrationDTO.java new file mode 100644 index 000000000..c4edc891e --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/tim/TimeRegistrationDTO.java @@ -0,0 +1,79 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 St. Antoniusziekenhuis + * + * 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 . + */ + +package org.libreplan.importers.tim; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + +/** + * DTO representing a tim-connector TimeRegistration + * + * @author Miciele Ghiorghis + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlRootElement(name = "tijdregistratie") +public class TimeRegistrationDTO { + + @XmlElement(name = "persoon") + private PersonDTO person; + + private ProductDTO product; + + @XmlElement(name = "datum") + private RegistrationDateDTO registrationDate; + + @XmlElement(name = "duur") + private DurationDTO duration; + + public PersonDTO getPerson() { + return person; + } + + public void setPerson(PersonDTO person) { + this.person = person; + } + + public ProductDTO getProduct() { + return product; + } + + public void setProduct(ProductDTO product) { + this.product = product; + } + + public RegistrationDateDTO getRegistrationDate() { + return registrationDate; + } + + public void setRegistrationDate(RegistrationDateDTO registrationDate) { + this.registrationDate = registrationDate; + } + + public DurationDTO getDuration() { + return duration; + } + + public void setDuration(DurationDTO duration) { + this.duration = duration; + } + +} diff --git a/libreplan-webapp/src/main/java/org/libreplan/importers/tim/TimeRegistrationRequestDTO.java b/libreplan-webapp/src/main/java/org/libreplan/importers/tim/TimeRegistrationRequestDTO.java new file mode 100644 index 000000000..e49b53fdd --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/tim/TimeRegistrationRequestDTO.java @@ -0,0 +1,50 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 St. Antoniusziekenhuis + * + * 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 . + */ + +package org.libreplan.importers.tim; + +import java.util.List; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlElementWrapper; +import javax.xml.bind.annotation.XmlRootElement; + +/** + * DTO representing a tim-connector TimeRegistrationRequest + * + * @author Miciele Ghiorghis + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlRootElement(name = "import", namespace = "impexp.timn.aenova.nl") +public class TimeRegistrationRequestDTO { + + @XmlElementWrapper(name = "data") + @XmlElement(name = "tijdregistratie") + private List timeRegistrations; + + public List getTimeRegistrations() { + return timeRegistrations; + } + + public void setTimeRegistrations(List timeRegistrations) { + this.timeRegistrations = timeRegistrations; + } +} diff --git a/libreplan-webapp/src/main/java/org/libreplan/importers/tim/TimeRegistrationResponseDTO.java b/libreplan-webapp/src/main/java/org/libreplan/importers/tim/TimeRegistrationResponseDTO.java new file mode 100644 index 000000000..23edc172a --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/tim/TimeRegistrationResponseDTO.java @@ -0,0 +1,51 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 St. Antoniusziekenhuis + * + * 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 . + */ + +package org.libreplan.importers.tim; + +import java.util.List; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlElementWrapper; +import javax.xml.bind.annotation.XmlRootElement; + +/** + * DTO representing a tim-connector TimeRegistrationResponse + * + * @author Miciele Ghiorghis + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlRootElement(name = "importResponse", namespace = "impexp.timn.aenova.nl") +public class TimeRegistrationResponseDTO { + + @XmlElementWrapper(name = "return") + @XmlElement(name = "ref") + private List ref; + + public List getRefs() { + return ref; + } + + public void setRefs(List ref) { + this.ref = ref; + } + +} diff --git a/libreplan-webapp/src/main/java/org/libreplan/importers/tim/package-info.java b/libreplan-webapp/src/main/java/org/libreplan/importers/tim/package-info.java new file mode 100644 index 000000000..3e67d0999 --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/importers/tim/package-info.java @@ -0,0 +1,34 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 St. Antoniusziekenhuis + * + * 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 . + */ + +/** + * An xmlAdapaters that will be applied within this package + * + * @author Miciele Ghiorghis + */ +@javax.xml.bind.annotation.adapters.XmlJavaTypeAdapters({ + @javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter(type = DateTime.class, value = org.libreplan.importers.tim.TimDateTimeAdapter.class), + @javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter(type = LocalDate.class, value = org.libreplan.importers.tim.TimLocalDateAdapter.class), + @javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter(type = Date.class, value = org.libreplan.importers.tim.TimTimeAdapter.class) }) +package org.libreplan.importers.tim; + +import java.util.Date; + +import org.joda.time.DateTime; +import org.joda.time.LocalDate; diff --git a/libreplan-webapp/src/main/java/org/libreplan/web/common/ConfigurationController.java b/libreplan-webapp/src/main/java/org/libreplan/web/common/ConfigurationController.java index 09b2fa8e4..bef2292c9 100644 --- a/libreplan-webapp/src/main/java/org/libreplan/web/common/ConfigurationController.java +++ b/libreplan-webapp/src/main/java/org/libreplan/web/common/ConfigurationController.java @@ -30,6 +30,7 @@ import java.util.Comparator; import java.util.ConcurrentModificationException; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import javax.ws.rs.core.MediaType; @@ -42,16 +43,20 @@ import org.apache.commons.logging.LogFactory; import org.apache.cxf.jaxrs.client.WebClient; import org.libreplan.business.calendars.entities.BaseCalendar; import org.libreplan.business.common.entities.Configuration; +import org.libreplan.business.common.entities.Connector; +import org.libreplan.business.common.entities.ConnectorProperty; import org.libreplan.business.common.entities.EntityNameEnum; import org.libreplan.business.common.entities.EntitySequence; -import org.libreplan.business.common.entities.JiraConfiguration; import org.libreplan.business.common.entities.LDAPConfiguration; import org.libreplan.business.common.entities.PersonalTimesheetsPeriodicityEnum; +import org.libreplan.business.common.entities.PredefinedConnectorProperties; +import org.libreplan.business.common.entities.PredefinedConnectors; import org.libreplan.business.common.entities.ProgressType; import org.libreplan.business.common.exceptions.ValidationException; import org.libreplan.business.costcategories.entities.TypeOfWorkHours; import org.libreplan.business.users.entities.UserRole; import org.libreplan.importers.JiraRESTClient; +import org.libreplan.importers.TimSoapClient; import org.libreplan.web.common.components.bandboxsearch.BandboxSearch; import org.springframework.ldap.core.DistinguishedName; import org.springframework.ldap.core.LdapTemplate; @@ -83,6 +88,7 @@ import org.zkoss.zul.Rows; import org.zkoss.zul.SimpleListModel; import org.zkoss.zul.Textbox; import org.zkoss.zul.api.Window; +import org.zkoss.zul.impl.InputElement; /** * Controller for {@link Configuration} entity. @@ -125,6 +131,12 @@ public class ConfigurationController extends GenericForwardComposer { private Radiogroup strategy; + private Combobox connectorCombo; + + private Grid connectorPropertriesGrid; + + private Connector selectedConnector; + @Override public void doAfterCompose(Component comp) throws Exception { super.doAfterCompose(comp); @@ -215,8 +227,16 @@ public class ConfigurationController extends GenericForwardComposer { configurationModel.confirm(); configurationModel.init(); messages.showMessage(Level.INFO, _("Changes saved")); + if (getSelectedConnector() != null + && !configurationModel + .scheduleOrUnscheduleJobs(getSelectedConnector())) { + messages.showMessage( + Level.ERROR, + _("Scheduling or unscheduling of jobs for this connector is not completed")); + } reloadWindow(); reloadEntitySequences(); + reloadConnectors(); } catch (ValidationException e) { messages.showInvalidValues(e); } catch (ConcurrentModificationException e) { @@ -224,6 +244,7 @@ public class ConfigurationController extends GenericForwardComposer { configurationModel.init(); reloadWindow(); reloadEntitySequences(); + reloadConnectors(); } } } @@ -233,6 +254,7 @@ public class ConfigurationController extends GenericForwardComposer { messages.showMessage(Level.INFO, _("Changes have been canceled")); reloadWindow(); reloadEntitySequences(); + reloadConnectors(); } public void testLDAPConnection() { @@ -267,22 +289,71 @@ public class ConfigurationController extends GenericForwardComposer { } /** - * tests jira connection + * Tests connection */ - public void testJiraConnection() { + public void testConnection() { + if (selectedConnector == null) { + messages.showMessage(Level.ERROR, + _("Please select a connector to test it")); + return; + } - JiraConfiguration jiraConfiguration = configurationModel - .getJiraConfiguration(); + Map properties = selectedConnector.getPropertiesAsMap(); + String url = properties.get(PredefinedConnectorProperties.SERVER_URL); + String username = properties + .get(PredefinedConnectorProperties.USERNAME); + String password = properties + .get(PredefinedConnectorProperties.PASSWORD); + + if (selectedConnector.getName().equals( + PredefinedConnectors.TIM.getName())) { + testTimConnection(url, username, password); + } else if (selectedConnector.getName().equals( + PredefinedConnectors.JIRA.getName())) { + testJiraConnection(url, username, password); + } else { + throw new RuntimeException("Unknown connector"); + } + } + + /** + * Test tim connection + * + * @param url + * the url of the server + * @param username + * the username + * @param password + * the password + */ + private void testTimConnection(String url, String username, String password) { + if (TimSoapClient.checkAuthorization(url, username, password)) { + messages.showMessage(Level.INFO, _("Tim connection was successful")); + } else { + messages.showMessage(Level.ERROR, _("Cannot connet to Tim server")); + } + } + + /** + * Test JIRA connection + * + * @param url + * the url + * @param username + * the username + * @param password + * the password + */ + private void testJiraConnection(String url, String username, String password) { try { - WebClient client = WebClient.create(jiraConfiguration.getJiraUrl()); + WebClient client = WebClient.create(url); client.path(JiraRESTClient.PATH_AUTH_SESSION).accept( MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML); org.libreplan.ws.common.impl.Util.addAuthorizationHeader(client, - jiraConfiguration.getJiraUserId(), - jiraConfiguration.getJiraPassword()); + username, password); Response response = client.get(); @@ -290,17 +361,16 @@ public class ConfigurationController extends GenericForwardComposer { messages.showMessage(Level.INFO, _("JIRA connection was successful")); } else { - LOG.info("Status code: " + response.getStatus()); + LOG.error("Status code: " + response.getStatus()); messages.showMessage(Level.ERROR, _("Cannot connect to JIRA server")); } } catch (Exception e) { - LOG.info(e); + LOG.error(e); messages.showMessage(Level.ERROR, _("Cannot connect to JIRA server")); } - } private boolean checkValidEntitySequenceRows() { @@ -346,6 +416,14 @@ public class ConfigurationController extends GenericForwardComposer { entitySequencesGrid.invalidate(); } + private void reloadConnectors() { + selectedConnector = configurationModel + .getConnectorByName(selectedConnector != null ? selectedConnector + .getName() : null); + Util.reloadBindings(connectorCombo); + Util.reloadBindings(connectorPropertriesGrid); + } + public String getCompanyCode() { return configurationModel.getCompanyCode(); } @@ -818,14 +896,6 @@ public class ConfigurationController extends GenericForwardComposer { configurationModel.setLdapConfiguration(ldapConfiguration); } - public JiraConfiguration getJiraConfiguration() { - return configurationModel.getJiraConfiguration(); - } - - public void setJiraConfiguration(JiraConfiguration jiraConfiguration) { - configurationModel.setJiraConfiguration(jiraConfiguration); - } - public RowRenderer getAllUserRolesRenderer() { return new RowRenderer() { @Override @@ -976,12 +1046,105 @@ public class ConfigurationController extends GenericForwardComposer { configurationModel.setSecondsPlanningWarning(secondsPlanningWarning); } - public TypeOfWorkHours getJiraConnectorTypeOfWorkHours() { - return configurationModel.getJiraConnectorTypeOfWorkHours(); + public List getConnectors() { + return configurationModel.getConnectors(); } - public void setJiraConnectorTypeOfWorkHours(TypeOfWorkHours typeOfWorkHours) { - configurationModel.setJiraConnectorTypeOfWorkHours(typeOfWorkHours); + public Connector getSelectedConnector() { + return selectedConnector; + } + + public void setSelectedConnector(Connector connector) { + selectedConnector = connector; + Util.reloadBindings(connectorPropertriesGrid); + } + + public List getConnectorPropertries() { + if (selectedConnector == null) { + return Collections.emptyList(); + } + return selectedConnector.getProperties(); + } + + public RowRenderer getConnectorPropertriesRenderer() { + return new RowRenderer() { + @Override + public void render(Row row, Object data) { + ConnectorProperty property = (ConnectorProperty) data; + row.setValue(property); + + Util.appendLabel(row, _(property.getKey())); + appendValueTextbox(row, property); + } + + private void appendValueTextbox(Row row, + final ConnectorProperty property) { + final Textbox textbox = new Textbox(); + textbox.setWidth("400px"); + textbox.setConstraint(checkPropertyValue(property)); + + Util.bind(textbox, new Util.Getter() { + + @Override + public String get() { + return property.getValue(); + } + }, new Util.Setter() { + + @Override + public void set(String value) { + property.setValue(value); + } + }); + if (property.getKey().equals( + PredefinedConnectorProperties.PASSWORD)) { + textbox.setType("password"); + } + + row.appendChild(textbox); + } + + public Constraint checkPropertyValue( + final ConnectorProperty property) { + final String key = property.getKey(); + return new Constraint() { + @Override + public void validate(Component comp, Object value) { + if (key.equals(PredefinedConnectorProperties.ACTIVATED)) { + if (!((String) value).equalsIgnoreCase("Y") + && !((String) value).equalsIgnoreCase("N")) { + throw new WrongValueException(comp, _( + "Only {0} allowed", "Y/N")); + } + } else if (key + .equals(PredefinedConnectorProperties.SERVER_URL) + || key.equals(PredefinedConnectorProperties.USERNAME) + || key.equals(PredefinedConnectorProperties.PASSWORD) + || key.equals(PredefinedConnectorProperties.JIRA_HOURS_TYPE)) { + ((InputElement) comp).setConstraint("no empty:" + + _("cannot be empty")); + } else if (key + .equals(PredefinedConnectorProperties.TIM_NR_DAYS_TIMESHEET) + || key.equals(PredefinedConnectorProperties.TIM_NR_DAYS_ROSTER)) { + if (!isNumeric((String) value)) { + throw new WrongValueException(comp, + _("Only digits allowed")); + } + } + } + }; + } + + private boolean isNumeric(String input) { + try { + Integer.parseInt(input); + return true; + } catch (NumberFormatException e) { + return false; + } + } + + }; } } diff --git a/libreplan-webapp/src/main/java/org/libreplan/web/common/ConfigurationModel.java b/libreplan-webapp/src/main/java/org/libreplan/web/common/ConfigurationModel.java index bb0fd97b1..fdbcada4c 100644 --- a/libreplan-webapp/src/main/java/org/libreplan/web/common/ConfigurationModel.java +++ b/libreplan-webapp/src/main/java/org/libreplan/web/common/ConfigurationModel.java @@ -25,6 +25,7 @@ import static org.libreplan.web.I18nHelper._; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.Currency; import java.util.HashMap; import java.util.HashSet; @@ -38,11 +39,12 @@ import org.apache.commons.lang.StringUtils; import org.libreplan.business.calendars.daos.IBaseCalendarDAO; import org.libreplan.business.calendars.entities.BaseCalendar; import org.libreplan.business.common.daos.IConfigurationDAO; +import org.libreplan.business.common.daos.IConnectorDAO; import org.libreplan.business.common.daos.IEntitySequenceDAO; import org.libreplan.business.common.entities.Configuration; +import org.libreplan.business.common.entities.Connector; import org.libreplan.business.common.entities.EntityNameEnum; import org.libreplan.business.common.entities.EntitySequence; -import org.libreplan.business.common.entities.JiraConfiguration; import org.libreplan.business.common.entities.LDAPConfiguration; import org.libreplan.business.common.entities.PersonalTimesheetsPeriodicityEnum; import org.libreplan.business.common.entities.ProgressType; @@ -76,6 +78,8 @@ public class ConfigurationModel implements IConfigurationModel { private static Map currencies = getAllCurrencies(); + private List connectors; + @Autowired private IConfigurationDAO configurationDAO; @@ -88,6 +92,12 @@ public class ConfigurationModel implements IConfigurationModel { @Autowired private IWorkReportDAO workReportDAO; + @Autowired + private IConnectorDAO connectorDAO; + + @Autowired + private IJobSchedulerModel jobSchedulerModel; + @Override @Transactional(readOnly = true) public List getCalendars() { @@ -107,6 +117,8 @@ public class ConfigurationModel implements IConfigurationModel { public void init() { this.configuration = getCurrentConfiguration(); initEntitySequences(); + initLdapConfiguration(); + initConnectorConfiguration(); } private void initEntitySequences() { @@ -120,6 +132,23 @@ public class ConfigurationModel implements IConfigurationModel { } } + private void initLdapConfiguration() { + if (null == configuration.getLdapConfiguration()) { + configuration.setLdapConfiguration(LDAPConfiguration.create()); + } + } + + private void initConnectorConfiguration() { + connectors = connectorDAO.getAll(); + forceLoadConnectors(); + } + + private void forceLoadConnectors() { + for (Connector connector : connectors) { + connector.getProperties().size(); + } + } + private Configuration getCurrentConfiguration() { Configuration configuration = configurationDAO.getConfiguration(); if (configuration == null) { @@ -132,8 +161,6 @@ public class ConfigurationModel implements IConfigurationModel { private void forceLoad(Configuration configuration) { forceLoad(configuration.getDefaultCalendar()); forceLoad(configuration.getPersonalTimesheetsTypeOfWorkHours()); - forceLoad(configuration.getJiraConfiguration() - .getJiraConnectorTypeOfWorkHours()); } private void forceLoad(BaseCalendar calendar) { @@ -160,6 +187,7 @@ public class ConfigurationModel implements IConfigurationModel { public void confirm() { checkEntitySequences(); configurationDAO.save(configuration); + saveConnectors(); try { storeAndRemoveEntitySequences(); } catch (IllegalStateException e) { @@ -663,36 +691,34 @@ public class ConfigurationModel implements IConfigurationModel { configuration.setSecondsPlanningWarning(secondsPlanningWarning); } - @Override - public void setJiraConfiguration(JiraConfiguration jiraConfiguration) { - configuration.setJiraConfiguration(jiraConfiguration); + private void saveConnectors() { + for (Connector connector : connectors) { + connectorDAO.save(connector); + } } @Override - public JiraConfiguration getJiraConfiguration() { - return configuration.getJiraConfiguration(); + public List getConnectors() { + return Collections.unmodifiableList(connectors); } @Override - public TypeOfWorkHours getJiraConnectorTypeOfWorkHours() { - JiraConfiguration jiraConfiguration = configuration - .getJiraConfiguration(); - if (jiraConfiguration != null) { - return jiraConfiguration.getJiraConnectorTypeOfWorkHours(); + public Connector getConnectorByName(String name) { + if (name == null || connectors == null) { + return null; + } + + for (Connector connector : connectors) { + if (connector.getName().equals(name)) { + return connector; + } } return null; } @Override - public void setJiraConnectorTypeOfWorkHours(TypeOfWorkHours typeOfWorkHours) { - if (configuration != null) { - JiraConfiguration jiraConfiguration = configuration - .getJiraConfiguration(); - if (jiraConfiguration != null) { - jiraConfiguration - .setJiraConnectorTypeOfWorkHours(typeOfWorkHours); - } - } + public boolean scheduleOrUnscheduleJobs(Connector connector) { + return jobSchedulerModel.scheduleOrUnscheduleJobs(connector); } } diff --git a/libreplan-webapp/src/main/java/org/libreplan/web/common/CustomMenuController.java b/libreplan-webapp/src/main/java/org/libreplan/web/common/CustomMenuController.java index d0806ff64..f1aad8ca4 100644 --- a/libreplan-webapp/src/main/java/org/libreplan/web/common/CustomMenuController.java +++ b/libreplan-webapp/src/main/java/org/libreplan/web/common/CustomMenuController.java @@ -414,6 +414,13 @@ public class CustomMenuController extends Div implements IMenuItemsRegister { "/profiles/profiles.zul", "13-usuarios.html#administraci-n-de-perfiles")); } + if (SecurityUtils + .isSuperuserOrUserInRoles(UserRole.ROLE_JOB_SCHEDULING)) { + configurationItems.add(subItem(_("Job Scheduling"), + "/common/job_scheduling.zul", + "16-ldap-authentication.html")); + } + if (!configurationItems.isEmpty()) { topItem(_("Configuration"), "/common/configuration.zul", "", configurationItems); diff --git a/libreplan-webapp/src/main/java/org/libreplan/web/common/IConfigurationModel.java b/libreplan-webapp/src/main/java/org/libreplan/web/common/IConfigurationModel.java index 71ffc5aca..c3f488355 100644 --- a/libreplan-webapp/src/main/java/org/libreplan/web/common/IConfigurationModel.java +++ b/libreplan-webapp/src/main/java/org/libreplan/web/common/IConfigurationModel.java @@ -25,9 +25,9 @@ import java.util.List; import java.util.Set; import org.libreplan.business.calendars.entities.BaseCalendar; +import org.libreplan.business.common.entities.Connector; import org.libreplan.business.common.entities.EntityNameEnum; import org.libreplan.business.common.entities.EntitySequence; -import org.libreplan.business.common.entities.JiraConfiguration; import org.libreplan.business.common.entities.LDAPConfiguration; import org.libreplan.business.common.entities.PersonalTimesheetsPeriodicityEnum; import org.libreplan.business.common.entities.ProgressType; @@ -186,12 +186,10 @@ public interface IConfigurationModel { void setSecondsPlanningWarning( Integer planningWarningExitWithoutSavingSeconds); - void setJiraConfiguration(JiraConfiguration jiraConfiguration); + List getConnectors(); - JiraConfiguration getJiraConfiguration(); + Connector getConnectorByName(String name); - TypeOfWorkHours getJiraConnectorTypeOfWorkHours(); - - void setJiraConnectorTypeOfWorkHours(TypeOfWorkHours typeOfWorkHours); + boolean scheduleOrUnscheduleJobs(Connector connector); } diff --git a/libreplan-webapp/src/main/java/org/libreplan/web/common/IJobSchedulerModel.java b/libreplan-webapp/src/main/java/org/libreplan/web/common/IJobSchedulerModel.java new file mode 100644 index 000000000..c8043e533 --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/web/common/IJobSchedulerModel.java @@ -0,0 +1,149 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 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 . + */ + +package org.libreplan.web.common; + +import java.util.List; + +import org.libreplan.business.common.entities.Connector; +import org.libreplan.business.common.entities.ConnectorException; +import org.libreplan.business.common.entities.JobSchedulerConfiguration; +import org.libreplan.business.common.entities.PredefinedConnectorProperties; +import org.libreplan.business.common.exceptions.ValidationException; +import org.libreplan.importers.SynchronizationInfo; + +/** + * Contract for {@link JobSchedulerModel}. + * + * @author Manuel Rego Casasnovas + * @author Miciele Ghiorghis + */ +public interface IJobSchedulerModel { + + /** + * returns all job scheduler configurations + * + * @return list of JobSchedulerConfiguration + */ + List getJobSchedulerConfigurations(); + + /** + * returns next fire time for the specified job from + * {@link JobSchedulerConfiguration} + * + * @param jobSchedulerConfiguration + * the job scheduler configuration + */ + String getNextFireTime(JobSchedulerConfiguration jobSchedulerConfiguration); + + /** + * Do manual action(replacement of scheduling) + * + * @param jobSchedulerConfiguration + * the job configuration + * @throws ConnectorException + * if connector is not valid + */ + void doManual(JobSchedulerConfiguration jobSchedulerConfiguration) + throws ConnectorException; + + /** + * Returns synchronization infos. Failures or successes info + */ + List getSynchronizationInfos(); + + /** + * Prepares for create a new {@link JobSchedulerConfiguration}. + */ + void initCreate(); + + /** + * Prepares for edit {@link JobSchedulerConfiguration} + * + * @param jobSchedulerConfiguration + * object to be edited + */ + void initEdit(JobSchedulerConfiguration jobSchedulerConfiguration); + + /** + * Gets the current {@link JobSchedulerConfiguration}. + * + * @return A {@link JobSchedulerConfiguration} + */ + JobSchedulerConfiguration getJobSchedulerConfiguration(); + + /** + * Saves the current {@link JobSchedulerConfiguration} + * + * @throws ValidationException + * if validation fails + */ + void confirmSave() throws ValidationException; + + /** + * Cancels the current {@link JobSchedulerConfiguration} + */ + void cancel(); + + /** + * Removes the current {@link JobSchedulerConfiguration} + * + * @param jobSchedulerConfiguration + * object to be removed + */ + void remove(JobSchedulerConfiguration jobSchedulerConfiguration); + + /** + * returns list of connectors + */ + List getConnectors(); + + /** + * Schedule or unschedule jobs for the specified connector + * + * schedule all jobs of the specified connector's property + * {@link PredefinedConnectorProperties#ACTIVATED} is 'Y', otherwise + * unschedule the jobs + * + * @param connector + * where to check if property is changed + * @return true if (un)scheduling is successful, false otherwise + */ + boolean scheduleOrUnscheduleJobs(Connector connector); + + /** + * schedule or unschedule job for the specified job in + * {@link JobSchedulerConfiguration} + * + * @return true if scheduling is succeeded, false otherwise + */ + boolean scheduleOrUnscheduleJob(); + + /** + * Delete job specified in {@link JobSchedulerConfiguration} + * + * @param jobSchedulerConfiguration + * configuration for the job to be deleted + * @return true if job is successfully deleted from the scheduler, false + * otherwise + */ + boolean deleteScheduledJob( + JobSchedulerConfiguration jobSchedulerConfiguration); + +} \ No newline at end of file diff --git a/libreplan-webapp/src/main/java/org/libreplan/web/common/JobSchedulerController.java b/libreplan-webapp/src/main/java/org/libreplan/web/common/JobSchedulerController.java new file mode 100644 index 000000000..0199cdacb --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/web/common/JobSchedulerController.java @@ -0,0 +1,438 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 St. Antoniusziekenhuis + * + * 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 . + */ + +package org.libreplan.web.common; + +import static org.libreplan.web.I18nHelper._; + +import java.text.ParseException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.libreplan.business.common.entities.Connector; +import org.libreplan.business.common.entities.ConnectorException; +import org.libreplan.business.common.entities.JobClassNameEnum; +import org.libreplan.business.common.entities.JobSchedulerConfiguration; +import org.libreplan.business.common.exceptions.InstanceNotFoundException; +import org.libreplan.business.common.exceptions.ValidationException; +import org.libreplan.importers.SynchronizationInfo; +import org.quartz.CronExpression; +import org.zkoss.zk.ui.Component; +import org.zkoss.zk.ui.Executions; +import org.zkoss.zk.ui.SuspendNotAllowedException; +import org.zkoss.zk.ui.WrongValueException; +import org.zkoss.zk.ui.event.Event; +import org.zkoss.zk.ui.event.EventListener; +import org.zkoss.zk.ui.event.Events; +import org.zkoss.zul.Button; +import org.zkoss.zul.Grid; +import org.zkoss.zul.Groupbox; +import org.zkoss.zul.Hbox; +import org.zkoss.zul.Label; +import org.zkoss.zul.Listbox; +import org.zkoss.zul.Popup; +import org.zkoss.zul.Row; +import org.zkoss.zul.RowRenderer; +import org.zkoss.zul.SimpleListModel; +import org.zkoss.zul.api.Caption; +import org.zkoss.zul.api.Textbox; +import org.zkoss.zul.api.Window; + +/** + * Controller for job scheduler manager + * + * @author Miciele Ghiorghis + */ +public class JobSchedulerController extends + BaseCRUDController { + + private static final Log LOG = LogFactory + .getLog(JobSchedulerController.class); + + private Grid listJobSchedulings; + private Grid cronExpressionGrid; + + private Popup cronExpressionInputPopup; + + private Label jobGroup; + private Label jobName; + + private Textbox cronExpressionTextBox; + private Textbox cronExpressionSeconds; + private Textbox cronExpressionMinutes; + private Textbox cronExpressionHours; + private Textbox cronExpressionDayOfMonth; + private Textbox cronExpressionMonth; + private Textbox cronExpressionDayOfWeek; + private Textbox cronExpressionYear; + + private IJobSchedulerModel jobSchedulerModel; + + @Override + public void doAfterCompose(Component comp) throws Exception { + super.doAfterCompose(comp); + listJobSchedulings = (Grid) listWindow + .getFellowIfAny("listJobSchedulings"); + listJobSchedulings.getModel(); + initCronExpressionPopup(); + } + + /** + * initializes cron expressions for popup + */ + private void initCronExpressionPopup() { + cronExpressionTextBox = (Textbox) editWindow + .getFellow("cronExpressionTextBox"); + + cronExpressionInputPopup = (Popup) editWindow + .getFellow("cronExpressionInputPopup"); + + jobGroup = (Label) cronExpressionInputPopup.getFellow("jobGroup"); + jobName = (Label) cronExpressionInputPopup.getFellow("jobName"); + + cronExpressionGrid = (Grid) cronExpressionInputPopup + .getFellow("cronExpressionGrid"); + + cronExpressionSeconds = (Textbox) cronExpressionGrid + .getFellow("cronExpressionSeconds"); + cronExpressionMinutes = (Textbox) cronExpressionGrid + .getFellow("cronExpressionMinutes"); + cronExpressionHours = (Textbox) cronExpressionGrid + .getFellow("cronExpressionHours"); + cronExpressionDayOfMonth = (Textbox) cronExpressionGrid + .getFellow("cronExpressionDayOfMonth"); + cronExpressionMonth = (Textbox) cronExpressionGrid + .getFellow("cronExpressionMonth"); + cronExpressionDayOfWeek = (Textbox) cronExpressionGrid + .getFellow("cronExpressionDayOfWeek"); + cronExpressionYear = (Textbox) cronExpressionGrid + .getFellow("cronExpressionYear"); + } + + /** + * returns a list of {@link JobSchedulerConfiguration} + */ + public List getJobSchedulerConfigurations() { + return jobSchedulerModel.getJobSchedulerConfigurations(); + } + + /** + * returns {@link JobSchedulerConfiguration} + */ + public JobSchedulerConfiguration getJobSchedulerConfiguration() { + return jobSchedulerModel.getJobSchedulerConfiguration(); + } + + /** + * returns all predefined jobs + */ + public JobClassNameEnum[] getJobNames() { + return JobClassNameEnum.values(); + } + + /** + * return list of connectorNames + */ + public List getConnectorNames() { + List connectors = jobSchedulerModel.getConnectors(); + List connectorNames = new ArrayList(); + for (Connector connector : connectors) { + connectorNames.add(connector.getName()); + } + return connectorNames; + } + + /** + * renders job scheduling and returns {@link RowRenderer} + */ + public RowRenderer getJobSchedulingRenderer() { + return new RowRenderer() { + + @Override + public void render(Row row, Object data) { + final JobSchedulerConfiguration jobSchedulerConfiguration = (JobSchedulerConfiguration) data; + row.setValue(data); + + Util.appendLabel(row, jobSchedulerConfiguration.getJobGroup()); + Util.appendLabel(row, jobSchedulerConfiguration.getJobName()); + Util.appendLabel(row, + jobSchedulerConfiguration.getCronExpression()); + Util.appendLabel(row, + getNextFireTime(jobSchedulerConfiguration)); + Hbox hbox = new Hbox(); + hbox.appendChild(createManualButton(new EventListener() { + + @Override + public void onEvent(Event event) throws Exception { + try { + jobSchedulerModel.doManual(jobSchedulerConfiguration); + showSynchronizationInfo(); + } catch (ConnectorException e) { + messagesForUser.showMessage(Level.ERROR, + e.getMessage()); + } + } + })); + hbox.appendChild(Util.createEditButton(new EventListener() { + @Override + public void onEvent(Event event) { + goToEditForm(jobSchedulerConfiguration); + } + })); + hbox.appendChild(Util.createRemoveButton(new EventListener() { + @Override + public void onEvent(Event event) { + confirmDelete(jobSchedulerConfiguration); + } + })); + row.appendChild(hbox); + + } + }; + } + + public RowRenderer getSynchronizationInfoRenderer() { + return new RowRenderer() { + + @Override + public void render(Row row, Object data) { + final SynchronizationInfo synchronizationInfo = (SynchronizationInfo) data; + row.setValue(data); + + Groupbox groupbox = new Groupbox(); + groupbox.setClosable(true); + Caption caption = new org.zkoss.zul.Caption(); + caption.setLabel(synchronizationInfo.getAction()); + groupbox.appendChild(caption); + row.appendChild(groupbox); + + if (synchronizationInfo.isSuccessful()) { + groupbox.appendChild(new Label(_("Completed"))); + } else { + + Listbox listbox = new Listbox(); + + listbox.setModel(new SimpleListModel(synchronizationInfo + .getFailedReasons())); + groupbox.appendChild(listbox); + } + } + }; + } + + + public List getSynchronizationInfos() { + return jobSchedulerModel.getSynchronizationInfos(); + } + + + private void showSynchronizationInfo() { + Map args = new HashMap(); + + Window win = (Window) Executions.createComponents( + "/orders/_synchronizationInfo.zul", null, args); + + Window syncInfoWin = (Window) win.getFellowIfAny("syncInfoWin"); + + Grid syncInfoGrid = (Grid) syncInfoWin.getFellowIfAny("syncInfoGrid"); + + syncInfoGrid.setModel(new SimpleListModel(getSynchronizationInfos())); + + syncInfoGrid.setRowRenderer(getSynchronizationInfoRenderer()); + + try { + win.doModal(); + } catch (SuspendNotAllowedException e) { + throw new RuntimeException(e); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + /** + * returns the next fire time for the specified job in + * {@link JobSchedulerConfiguration} + * + * @param jobSchedulerConfiguration + * the job scheduler configuration + */ + private String getNextFireTime( + JobSchedulerConfiguration jobSchedulerConfiguration) { + return jobSchedulerModel.getNextFireTime(jobSchedulerConfiguration); + } + + /** + * creates and returns a button + * + * @param eventListener + * Event listener for this button + */ + private static Button createManualButton(EventListener eventListener) { + Button button = new Button(_("Manual")); + button.setTooltiptext(_("Manual")); + button.addEventListener(Events.ON_CLICK, eventListener); + return button; + } + + /** + * Opens the cronExpressionInputPopup + */ + public void openPopup() { + setupCronExpressionPopup(getJobSchedulerConfiguration()); + cronExpressionInputPopup.open(cronExpressionTextBox, "after_start"); + } + + /** + * Sets the cronExpression values for cronExpressionInputPopup + * + * @param jobSchedulerConfiguration + * where to read the values + */ + private void setupCronExpressionPopup( + final JobSchedulerConfiguration jobSchedulerConfiguration) { + if (jobSchedulerConfiguration != null) { + jobGroup.setValue(jobSchedulerConfiguration.getJobGroup()); + jobName.setValue(jobSchedulerConfiguration.getJobName()); + + String cronExpression = jobSchedulerConfiguration + .getCronExpression(); + if (cronExpression == null || cronExpression.isEmpty()) { + return; + } + + String[] cronExpressionArray = StringUtils.split(cronExpression); + + cronExpressionSeconds.setValue(cronExpressionArray[0]); + cronExpressionMinutes.setValue(cronExpressionArray[1]); + cronExpressionHours.setValue(cronExpressionArray[2]); + cronExpressionDayOfMonth.setValue(cronExpressionArray[3]); + cronExpressionMonth.setValue(cronExpressionArray[4]); + cronExpressionDayOfWeek.setValue(cronExpressionArray[5]); + + if (cronExpressionArray.length == 7) { + cronExpressionYear.setValue(cronExpressionArray[6]); + } + } + } + + /** + * sets the cronExpressionTextBox value from the + * cronExpressionInputPopup + */ + public void updateCronExpression() { + String cronExpression = getCronExpressionString(); + try { + // Check cron expression format + new CronExpression(cronExpression); + } catch (ParseException e) { + LOG.info("Unable to parse cron expression", e); + throw new WrongValueException(cronExpressionInputPopup, + _("Unable to parse cron expression") + ":\n" + + e.getMessage()); + } + cronExpressionTextBox.setValue(cronExpression); + cronExpressionInputPopup.close(); + Util.saveBindings(cronExpressionTextBox); + } + + /** + * Concatenating the cronExpression values + * + * @return cronExpression string + */ + private String getCronExpressionString() { + String cronExpression = ""; + cronExpression += StringUtils.trimToEmpty(cronExpressionSeconds.getValue()) + " "; + cronExpression += StringUtils.trimToEmpty(cronExpressionMinutes.getValue()) + " "; + cronExpression += StringUtils.trimToEmpty(cronExpressionHours.getValue()) + " "; + cronExpression += StringUtils.trimToEmpty(cronExpressionDayOfMonth.getValue()) + " "; + cronExpression += StringUtils.trimToEmpty(cronExpressionMonth.getValue()) + " "; + cronExpression += StringUtils.trimToEmpty(cronExpressionDayOfWeek.getValue()); + + String year = StringUtils.trimToEmpty(cronExpressionYear.getValue()); + if (!year.isEmpty()) { + cronExpression += " " + year; + } + + return cronExpression; + } + + /** + * closes the popup + */ + public void cancelPopup() { + cronExpressionInputPopup.close(); + } + + @Override + protected String getEntityType() { + return _("Job scheduling"); + } + + @Override + protected String getPluralEntityType() { + return _("Job scheduling"); + } + + @Override + protected void initCreate() { + jobSchedulerModel.initCreate(); + + } + + @Override + protected void initEdit(JobSchedulerConfiguration entity) { + jobSchedulerModel.initEdit(entity); + } + + @Override + protected void save() throws ValidationException { + jobSchedulerModel.confirmSave(); + if (jobSchedulerModel.scheduleOrUnscheduleJob()) { + messagesForUser.showMessage(Level.INFO, + _("Job is scheduled/unscheduled")); + } + } + + @Override + protected void cancel() { + jobSchedulerModel.cancel(); + } + + @Override + protected JobSchedulerConfiguration getEntityBeingEdited() { + return jobSchedulerModel.getJobSchedulerConfiguration(); + } + + @Override + protected void delete(JobSchedulerConfiguration entity) + throws InstanceNotFoundException { + jobSchedulerModel.remove(entity); + if (jobSchedulerModel.deleteScheduledJob(entity)) { + messagesForUser.showMessage(Level.INFO, + _("Job is deleted from scheduler")); + } + } + +} diff --git a/libreplan-webapp/src/main/java/org/libreplan/web/common/JobSchedulerModel.java b/libreplan-webapp/src/main/java/org/libreplan/web/common/JobSchedulerModel.java new file mode 100644 index 000000000..5601cd3cb --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/web/common/JobSchedulerModel.java @@ -0,0 +1,195 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 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 . + */ + +package org.libreplan.web.common; + +import java.util.List; + +import org.libreplan.business.common.daos.IConnectorDAO; +import org.libreplan.business.common.daos.IJobSchedulerConfigurationDAO; +import org.libreplan.business.common.entities.Connector; +import org.libreplan.business.common.entities.ConnectorException; +import org.libreplan.business.common.entities.JobClassNameEnum; +import org.libreplan.business.common.entities.JobSchedulerConfiguration; +import org.libreplan.business.common.exceptions.InstanceNotFoundException; +import org.libreplan.business.common.exceptions.ValidationException; +import org.libreplan.importers.IExportTimesheetsToTim; +import org.libreplan.importers.IImportRosterFromTim; +import org.libreplan.importers.IJiraOrderElementSynchronizer; +import org.libreplan.importers.ISchedulerManager; +import org.libreplan.importers.SynchronizationInfo; +import org.libreplan.web.common.concurrentdetection.OnConcurrentModification; +import org.quartz.SchedulerException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +/** + * Model for UI operations related to {@link JobSchedulerConfiguration}. + * + * @author Manuel Rego Casasnovas + * @author Miciele Ghiorghis + */ +@Service +@Scope(BeanDefinition.SCOPE_PROTOTYPE) +@OnConcurrentModification(goToPage = "/common/job_scheduling.zul") +public class JobSchedulerModel implements IJobSchedulerModel { + + private JobSchedulerConfiguration jobSchedulerConfiguration; + + @Autowired + private ISchedulerManager schedulerManager; + + @Autowired + private IJobSchedulerConfigurationDAO jobSchedulerConfigurationDAO; + + @Autowired + private IConnectorDAO connectorDAO; + + @Autowired + private IImportRosterFromTim importRosterFromTim; + + @Autowired + private IExportTimesheetsToTim exportTimesheetsToTim; + + @Autowired + private IJiraOrderElementSynchronizer jiraOrderElementSynchronizer; + + private List synchronizationInfos; + + @Override + @Transactional(readOnly = true) + public List getJobSchedulerConfigurations() { + return jobSchedulerConfigurationDAO.getAll(); + } + + @Override + public String getNextFireTime( + JobSchedulerConfiguration jobSchedulerConfiguration) { + return schedulerManager.getNextFireTime(jobSchedulerConfiguration); + } + + @Override + public void doManual(JobSchedulerConfiguration jobSchedulerConfiguration) + throws ConnectorException { + String name = jobSchedulerConfiguration.getJobClassName().getName(); + if (name.equals(JobClassNameEnum.IMPORT_ROSTER_FROM_TIM_JOB.getName())) { + synchronizationInfos = importRosterFromTim.importRosters(); + return; + } + if (name.equals(JobClassNameEnum.EXPORT_TIMESHEET_TO_TIM_JOB.getName())) { + synchronizationInfos = exportTimesheetsToTim.exportTimesheets(); + return; + } + if (name.equals(JobClassNameEnum.SYNC_ORDERELEMENTS_WITH_JIRA_ISSUES_JOB + .getName())) { + synchronizationInfos = jiraOrderElementSynchronizer + .syncOrderElementsWithJiraIssues(); + return; + } + throw new RuntimeException("Unknown action"); + } + + @Override + public List getSynchronizationInfos() { + return synchronizationInfos; + } + + @Override + public void initCreate() { + this.jobSchedulerConfiguration = JobSchedulerConfiguration.create(); + } + + @Override + public void initEdit(JobSchedulerConfiguration jobSchedulerConfiguration) { + this.jobSchedulerConfiguration = jobSchedulerConfiguration; + } + + @Override + public JobSchedulerConfiguration getJobSchedulerConfiguration() { + return this.jobSchedulerConfiguration; + } + + @Override + @Transactional + public void confirmSave() throws ValidationException { + jobSchedulerConfigurationDAO.save(jobSchedulerConfiguration); + } + + @Override + public void cancel() { + jobSchedulerConfiguration = null; + } + + @Override + @Transactional + public void remove(JobSchedulerConfiguration jobSchedulerConfiguration) { + try { + jobSchedulerConfigurationDAO.remove(jobSchedulerConfiguration + .getId()); + } catch (InstanceNotFoundException e) { + throw new RuntimeException(e); + } + } + + @Override + @Transactional(readOnly = true) + public List getConnectors() { + return connectorDAO.getAll(); + } + + @Override + public boolean scheduleOrUnscheduleJobs(Connector connector) { + List jobSchedulerConfigurations = jobSchedulerConfigurationDAO + .findByConnectorName(connector.getName()); + + for (JobSchedulerConfiguration jobSchedulerConfiguration : jobSchedulerConfigurations) { + try { + schedulerManager.scheduleOrUnscheduleJob(jobSchedulerConfiguration); + } catch (SchedulerException e) { + return false; + } + } + return true; + } + + @Override + public boolean scheduleOrUnscheduleJob() { + try { + schedulerManager.scheduleOrUnscheduleJob(jobSchedulerConfiguration); + } catch (SchedulerException e) { + throw new RuntimeException("Failed to schedule job", e); + } + return true; + } + + @Override + public boolean deleteScheduledJob( + JobSchedulerConfiguration jobSchedulerConfiguration) { + try { + schedulerManager.deleteJob(jobSchedulerConfiguration); + } catch (SchedulerException e) { + throw new RuntimeException("Failed to delete job", e); + } + return true; + } + +} diff --git a/libreplan-webapp/src/main/java/org/libreplan/web/dashboard/DashboardController.java b/libreplan-webapp/src/main/java/org/libreplan/web/dashboard/DashboardController.java index c1c138c0a..a611459d0 100644 --- a/libreplan-webapp/src/main/java/org/libreplan/web/dashboard/DashboardController.java +++ b/libreplan-webapp/src/main/java/org/libreplan/web/dashboard/DashboardController.java @@ -26,6 +26,7 @@ import java.io.IOException; import java.io.InputStreamReader; import java.math.BigDecimal; import java.math.RoundingMode; +import java.text.DecimalFormat; import java.util.Collection; import java.util.List; import java.util.Map; @@ -161,10 +162,10 @@ public class DashboardController extends GenericForwardComposer { if ((lblAbsolute != null) && (absoluteMargin != null)) { lblAbsolute - .setValue(String - .format(_("There is a margin of %d days with the project global deadline (%.2f %%)."), - absoluteMargin + 0, - relativeMargin.doubleValue() * 100)); + .setValue(_( + "There is a margin of {0} days with the project global deadline ({1}%).", + absoluteMargin, (new DecimalFormat("#.##")) + .format(relativeMargin.doubleValue() * 100))); } else { lblAbsolute.setValue(_("No project deadline defined")); } diff --git a/libreplan-webapp/src/main/java/org/libreplan/web/limitingresources/ManualAllocationController.java b/libreplan-webapp/src/main/java/org/libreplan/web/limitingresources/ManualAllocationController.java index 9ed951d47..ba7eac2ea 100644 --- a/libreplan-webapp/src/main/java/org/libreplan/web/limitingresources/ManualAllocationController.java +++ b/libreplan-webapp/src/main/java/org/libreplan/web/limitingresources/ManualAllocationController.java @@ -573,7 +573,7 @@ public class ManualAllocationController extends GenericForwardComposer { public String getCurrentQueue() { if (getBeingEditedElement() == null || getBeingEditedElement().getLimitingResourceQueue() == null) { - return _("Unnasigned"); + return _("Unassigned"); } return getBeingEditedElement().getLimitingResourceQueue().getResource() .getName(); @@ -582,7 +582,7 @@ public class ManualAllocationController extends GenericForwardComposer { public String getCurrentStart() { if (getBeingEditedElement() == null || getBeingEditedElement().getStartDate() == null) { - return _("Unnasigned"); + return _("Unassigned"); } return getBeingEditedElement().getStartDate().toString(); } @@ -590,7 +590,7 @@ public class ManualAllocationController extends GenericForwardComposer { public String getCurrentEnd() { if (getBeingEditedElement() == null || getBeingEditedElement().getEndDate() == null) { - return _("Unnasigned"); + return _("Unassigned"); } return getBeingEditedElement().getEndDate().toString(); } diff --git a/libreplan-webapp/src/main/java/org/libreplan/web/orders/IOrderModel.java b/libreplan-webapp/src/main/java/org/libreplan/web/orders/IOrderModel.java index 158fcd049..0892692f9 100644 --- a/libreplan-webapp/src/main/java/org/libreplan/web/orders/IOrderModel.java +++ b/libreplan-webapp/src/main/java/org/libreplan/web/orders/IOrderModel.java @@ -156,6 +156,4 @@ public interface IOrderModel extends IIntegrationEntityModel { User getUser(); - boolean isJiraActivated(); - } diff --git a/libreplan-webapp/src/main/java/org/libreplan/web/orders/JiraSynchronizationController.java b/libreplan-webapp/src/main/java/org/libreplan/web/orders/JiraSynchronizationController.java new file mode 100644 index 000000000..8503a81e6 --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/web/orders/JiraSynchronizationController.java @@ -0,0 +1,361 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 St. Antoniusziekenhuis + * + * 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 . + */ + +package org.libreplan.web.orders; + +import static org.libreplan.web.I18nHelper._; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import javax.ws.rs.WebApplicationException; + +import org.apache.commons.logging.LogFactory; +import org.libreplan.business.common.daos.IConnectorDAO; +import org.libreplan.business.common.entities.Connector; +import org.libreplan.business.common.entities.ConnectorException; +import org.libreplan.business.common.entities.PredefinedConnectors; +import org.libreplan.business.orders.entities.Order; +import org.libreplan.business.orders.entities.OrderSyncInfo; +import org.libreplan.importers.IJiraOrderElementSynchronizer; +import org.libreplan.importers.IJiraTimesheetSynchronizer; +import org.libreplan.importers.SynchronizationInfo; +import org.libreplan.importers.jira.IssueDTO; +import org.libreplan.web.common.IMessagesForUser; +import org.libreplan.web.common.Level; +import org.libreplan.web.common.MessagesForUser; +import org.libreplan.web.common.Util; +import org.springframework.beans.factory.annotation.Autowired; +import org.zkoss.zk.ui.Component; +import org.zkoss.zk.ui.Executions; +import org.zkoss.zk.ui.SuspendNotAllowedException; +import org.zkoss.zk.ui.event.Event; +import org.zkoss.zk.ui.event.EventListener; +import org.zkoss.zk.ui.event.Events; +import org.zkoss.zk.ui.util.GenericForwardComposer; +import org.zkoss.zul.Button; +import org.zkoss.zul.Combobox; +import org.zkoss.zul.ListModel; +import org.zkoss.zul.Popup; +import org.zkoss.zul.SimpleListModel; +import org.zkoss.zul.Tab; +import org.zkoss.zul.Textbox; +import org.zkoss.zul.api.Groupbox; +import org.zkoss.zul.api.Window; + +/** + * Controller for JIRA synchronization + * + * @author Miciele Ghiorghis + */ +public class JiraSynchronizationController extends GenericForwardComposer { + + private static final org.apache.commons.logging.Log LOG = LogFactory + .getLog(JiraSynchronizationController.class); + + private OrderCRUDController orderController; + + private Window editWindow; + + private Groupbox jiraGroupBox; + + private Popup jirasyncPopup; + + private Button startJiraSyncButton, cancelJiraSyncButton, + syncWithJiraButton; + + private Textbox txtImportedLabel, txtLastSyncDate; + + private Combobox comboJiraLabel; + + private IMessagesForUser messagesForUser; + + private Component messagesContainer; + + @Autowired + private IJiraOrderElementSynchronizer jiraOrderElementSynchronizer; + + @Autowired + private IJiraTimesheetSynchronizer jiraTimesheetSynchronizer; + + @Autowired + private IConnectorDAO connectorDAO; + + @Override + public void doAfterCompose(Component comp) throws Exception { + super.doAfterCompose(comp); + comp.setVariable("jiraSynchroniaztionController", this, true); + loadComponentsEditWindow(); + showOrHideJiraEditWindow(); + updateOrderLastSyncInfoScreen(); + } + + /** + * Returns current {@link Order} + */ + private Order getOrder() { + return orderController.getOrder(); + } + + private void loadComponentsEditWindow() { + txtLastSyncDate = (Textbox) editWindow + .getFellowIfAny("txtLastSyncDate"); + txtImportedLabel = (Textbox) editWindow + .getFellowIfAny("txtImportedLabel"); + jiraGroupBox = (Groupbox) editWindow.getFellowIfAny("jiraGroupBox"); + syncWithJiraButton = (Button) editWindow + .getFellow("syncWithJiraButton"); + messagesForUser = new MessagesForUser(messagesContainer); + } + + /** + * Show or hide JiraEditWindow based on JIRA + * {@link Connector#isActivated()} + */ + private void showOrHideJiraEditWindow() { + jiraGroupBox.setVisible(isJiraActivated()); + } + + /** + * Updates the UI text last synchronized date and the text imported label + */ + private void updateOrderLastSyncInfoScreen() { + OrderSyncInfo orderSyncInfo = jiraOrderElementSynchronizer + .getOrderLastSyncInfo(getOrder()); + + if (orderSyncInfo != null) { + txtLastSyncDate.setValue(Util.formatDateTime(orderSyncInfo + .getLastSyncDate())); + txtImportedLabel.setValue(orderSyncInfo.getKey()); + } + } + + /** + * Returns true if jira is Activated. Used to show/hide Jira edit window + */ + public boolean isJiraActivated() { + Connector connector = connectorDAO + .findUniqueByName(PredefinedConnectors.JIRA.getName()); + if (connector == null) { + return false; + } + return connector.isActivated(); + } + + /** + * Synchronize with Jira + */ + public void syncWithJira() { + try { + List items = jiraOrderElementSynchronizer + .getAllJiraLabels(); + + if (!(txtImportedLabel.getText()).isEmpty()) { + startSyncWithJira(txtImportedLabel.getText()); + return; + } + + setupJiraSyncPopup(editWindow, new SimpleListModelExt(items)); + + jirasyncPopup.open(syncWithJiraButton, "before_start"); + + } catch (ConnectorException e) { + messagesForUser.showMessage(Level.ERROR, + _("Failed: {0}", e.getMessage())); + } catch (WebApplicationException e) { + LOG.info(e); + messagesForUser.showMessage(Level.ERROR, + _("Cannot connect to JIRA server")); + } + } + + /** + * Start synchronize with jira for the specified label + * + * @param label + * the jira label + */ + public void startSyncWithJira(String label) { + try { + Order order = getOrder(); + + List issues = jiraOrderElementSynchronizer + .getJiraIssues(label); + + if (issues == null || issues.isEmpty()) { + messagesForUser.showMessage(Level.ERROR, + _("No JIRA issues to import")); + return; + } + + order.setCodeAutogenerated(false); + + jiraOrderElementSynchronizer.syncOrderElementsWithJiraIssues( + issues, order); + + orderController.saveAndContinue(false); + + jiraOrderElementSynchronizer.saveSyncInfo(label, order); + + updateOrderLastSyncInfoScreen(); + + if (jirasyncPopup != null) { + jirasyncPopup.close(); + } + + jiraTimesheetSynchronizer.syncJiraTimesheetWithJiraIssues(issues, + order); + + showSyncInfo(); + + // Reload order info in all tabs + Tab previousTab = orderController.getCurrentTab(); + orderController.initEdit(order); + orderController.selectTab(previousTab.getId()); + } catch (ConnectorException e) { + messagesForUser.showMessage(Level.ERROR, + _("Failed: {0}", e.getMessage())); + } catch (WebApplicationException e) { + LOG.info(e); + messagesForUser.showMessage(Level.ERROR, + _("Cannot connect to JIRA server")); + } + } + + /** + * Shows the success or failure info of synchronization + */ + private void showSyncInfo() { + Map args = new HashMap(); + + SynchronizationInfo syncOrderElementInfo = jiraOrderElementSynchronizer + .getSynchronizationInfo(); + + boolean succeeded = isSyncSucceeded(syncOrderElementInfo); + + args.put("syncOrderElementSuccess", succeeded); + if (syncOrderElementInfo != null) { + args.put("syncOrderElementFailedReasons", new SimpleListModel( + syncOrderElementInfo.getFailedReasons())); + } + + SynchronizationInfo jiraSyncInfoTimesheet = jiraTimesheetSynchronizer + .getSynchronizationInfo(); + + succeeded = isSyncSucceeded(jiraSyncInfoTimesheet); + + args.put("syncTimesheetSuccess", succeeded); + if (jiraSyncInfoTimesheet != null) { + args.put("syncTimesheetFailedReasons", new SimpleListModel( + jiraSyncInfoTimesheet.getFailedReasons())); + } + + Window jiraSyncInfoWindow = (Window) Executions.createComponents( + "/orders/_jiraSyncInfo.zul", null, args); + + try { + jiraSyncInfoWindow.doModal(); + } catch (SuspendNotAllowedException e) { + throw new RuntimeException(e); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + private boolean isSyncSucceeded(SynchronizationInfo syncInfo) { + if (syncInfo == null) { + return false; + } + return syncInfo.isSuccessful(); + } + + /** + * Setups the pop-up components + * + * @param comp + * the compenent(editWidnow) + * @param model + * labels list model to render the combobox + * comboJiraLabel + */ + private void setupJiraSyncPopup(Component comp, ListModel model) { + + startJiraSyncButton = (Button) comp.getFellow("startJiraSyncButton"); + startJiraSyncButton.setLabel(_("Start sync")); + + startJiraSyncButton.addEventListener(Events.ON_CLICK, + new EventListener() { + + @Override + public void onEvent(Event event) { + startSyncWithJira(comboJiraLabel.getValue()); + } + }); + cancelJiraSyncButton = (Button) comp.getFellow("cancelJiraSyncButton"); + cancelJiraSyncButton.setLabel(_("Cancel")); + cancelJiraSyncButton.addEventListener(Events.ON_CLICK, + new EventListener() { + + @Override + public void onEvent(Event event) { + jirasyncPopup.close(); + } + }); + comboJiraLabel = (Combobox) comp.getFellowIfAny("comboJiraLabel"); + comboJiraLabel.setModel(model); + + jirasyncPopup = (Popup) comp.getFellow("jirasyncPopup"); + + } + + /** + * This class provides case insensitive search for the {@link Combobox}. + */ + private class SimpleListModelExt extends SimpleListModel { + + public SimpleListModelExt(List data) { + super(data); + } + + public ListModel getSubModel(Object value, int nRows) { + final String idx = value == null ? "" : objectToString(value); + if (nRows < 0) { + nRows = 10; + } + final LinkedList data = new LinkedList(); + for (int i = 0; i < getSize(); i++) { + if (idx.equals("") + || entryMatchesText(getElementAt(i).toString(), idx)) { + data.add(getElementAt(i)); + if (--nRows <= 0) { + break; + } + } + } + return new SimpleListModelExt(data); + } + + public boolean entryMatchesText(String entry, String text) { + return entry.toLowerCase().contains(text.toLowerCase()); + } + } + +} diff --git a/libreplan-webapp/src/main/java/org/libreplan/web/orders/OrderCRUDController.java b/libreplan-webapp/src/main/java/org/libreplan/web/orders/OrderCRUDController.java index 630801675..125ba6e5d 100644 --- a/libreplan-webapp/src/main/java/org/libreplan/web/orders/OrderCRUDController.java +++ b/libreplan-webapp/src/main/java/org/libreplan/web/orders/OrderCRUDController.java @@ -29,20 +29,17 @@ import java.util.ConcurrentModificationException; import java.util.Date; import java.util.HashMap; import java.util.Iterator; -import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.SortedSet; import java.util.TreeSet; import javax.annotation.Resource; -import javax.ws.rs.WebApplicationException; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.LogFactory; import org.joda.time.LocalDate; import org.libreplan.business.calendars.entities.BaseCalendar; -import org.libreplan.business.common.daos.IConfigurationDAO; import org.libreplan.business.common.exceptions.InstanceNotFoundException; import org.libreplan.business.externalcompanies.entities.DeadlineCommunication; import org.libreplan.business.externalcompanies.entities.DeliverDateComparator; @@ -58,10 +55,6 @@ import org.libreplan.business.resources.entities.Criterion; import org.libreplan.business.templates.entities.OrderTemplate; import org.libreplan.business.users.entities.User; import org.libreplan.business.users.entities.UserRole; -import org.libreplan.importers.IJiraOrderElementSynchronizer; -import org.libreplan.importers.IJiraTimesheetSynchronizer; -import org.libreplan.importers.JiraSyncInfo; -import org.libreplan.importers.jira.IssueDTO; import org.libreplan.web.common.ConfirmCloseUtil; import org.libreplan.web.common.FilterUtils; import org.libreplan.web.common.IMessagesForUser; @@ -92,7 +85,6 @@ import org.zkoss.ganttz.util.LongOperationFeedback; import org.zkoss.zk.ui.Component; import org.zkoss.zk.ui.Desktop; import org.zkoss.zk.ui.Executions; -import org.zkoss.zk.ui.SuspendNotAllowedException; import org.zkoss.zk.ui.WrongValueException; import org.zkoss.zk.ui.event.Event; import org.zkoss.zk.ui.event.EventListener; @@ -110,9 +102,7 @@ import org.zkoss.zul.Datebox; import org.zkoss.zul.Grid; import org.zkoss.zul.Hbox; import org.zkoss.zul.Label; -import org.zkoss.zul.ListModel; import org.zkoss.zul.Messagebox; -import org.zkoss.zul.Popup; import org.zkoss.zul.Row; import org.zkoss.zul.RowRenderer; import org.zkoss.zul.Rows; @@ -202,6 +192,10 @@ public class OrderCRUDController extends GenericForwardComposer { private ProjectDetailsController projectDetailsController; + private JiraSynchronizationController jiraSynchronizationController; + + private TimSynchronizationController timSynchronizationController; + @Autowired private IOrderDAO orderDAO; @@ -209,16 +203,6 @@ public class OrderCRUDController extends GenericForwardComposer { private EndDatesRenderer endDatesRenderer = new EndDatesRenderer(); - @Autowired - private IJiraOrderElementSynchronizer jiraOrderElementSynchronizer; - - @Autowired - private IJiraTimesheetSynchronizer jiraTimesheetSynchronizer; - - @Autowired - private IConfigurationDAO configurationDAO; - - @Override public void doAfterCompose(Component comp) throws Exception { super.doAfterCompose(comp); @@ -821,7 +805,7 @@ public class OrderCRUDController extends GenericForwardComposer { saveAndContinue(true); } - private void saveAndContinue(boolean showSaveMessage) { + protected void saveAndContinue(boolean showSaveMessage) { Order order = orderModel.getOrder(); final boolean isNewObject = order.isNewObject(); @@ -930,11 +914,11 @@ public class OrderCRUDController extends GenericForwardComposer { } } - private Tab getCurrentTab() { + protected Tab getCurrentTab() { return selectedTab; } - private void selectTab(String str) { + protected void selectTab(String str) { Tab tab = (Tab) editWindow.getFellowIfAny(str); if (tab != null) { tab.setSelected(true); @@ -1082,6 +1066,12 @@ public class OrderCRUDController extends GenericForwardComposer { } public void initEdit(Order order) { + checkUserCanRead(order); + orderModel.initEdit(order, getDesktop()); + prepareEditWindow(_("Edit project")); + } + + public void checkUserCanRead(Order order) { if (!orderModel.userCanRead(order, SecurityUtils.getSessionUserLoginName())) { try { Messagebox.show(_("Sorry, you do not have permissions to access this project"), @@ -1090,9 +1080,6 @@ public class OrderCRUDController extends GenericForwardComposer { throw new RuntimeException(e); } } - - orderModel.initEdit(order, getDesktop()); - prepareEditWindow(_("Edit project")); } public IOrderModel getOrderModel() { @@ -1169,6 +1156,8 @@ public class OrderCRUDController extends GenericForwardComposer { initializeCustomerComponent(); reloadOrderDetailsTab(); orderDatesHandler.chooseCurrentSchedulingMode(); + setupJiraSynchronizationController(); + setupTimSynchronizationController(); } public void reloadOrderDetailsTab() { @@ -1876,167 +1865,32 @@ public class OrderCRUDController extends GenericForwardComposer { loadLabels(); } - private Popup jirasyncPopup; - private Button startJiraSyncButton, cancelJiraSyncButton, syncWithJiraButton; - private Combobox comboJiraLabel; - - public boolean isJiraDeactivated() { - return !configurationDAO.getConfigurationWithReadOnlyTransaction() - .getJiraConfiguration().isJiraActivated(); - } - - public void syncWithJira() { - try { - List items = jiraOrderElementSynchronizer.getAllJiraLabels(); - - Textbox txtImportedLabel = (Textbox) editWindow - .getFellowIfAny("txtImportedLabel"); - - if (!(txtImportedLabel.getText()).isEmpty()) { - startSyncWithJira(txtImportedLabel.getText()); - return; - } - - setupJiraSyncPopup(editWindow, new SimpleListModelExt(items)); - - syncWithJiraButton = (Button) getCurrentTab().getFellow( - "syncWithJiraButton"); - - jirasyncPopup.open(syncWithJiraButton, "before_start"); - - } catch (WebApplicationException e) { - LOG.info(e); - messagesForUser.showMessage(Level.ERROR, - _("Cannot connect to JIRA server")); + /** + * Setup the connector, JiraSynchronization controller + */ + public void setupJiraSynchronizationController() { + if (jiraSynchronizationController == null) { + jiraSynchronizationController = new JiraSynchronizationController(); } - } - - - public void startSyncWithJira(String label) { try { - Order order = getOrder(); - - List issues = jiraOrderElementSynchronizer - .getJiraIssues(label); - - order.setCodeAutogenerated(false); - order.setImportedLabel(label); - - jiraOrderElementSynchronizer.syncOrderElementsWithJiraIssues( - issues, order); - - saveAndContinue(false); - if (jirasyncPopup != null) { - jirasyncPopup.close(); - } - - jiraTimesheetSynchronizer.syncJiraTimesheetWithJiraIssues(issues, - order); - - showSyncInfo(); - - // Reload order info in all tabs - Tab previousTab = getCurrentTab(); - initEdit(order); - selectTab(previousTab.getId()); - } catch (WebApplicationException e) { - LOG.info(e); - messagesForUser.showMessage(Level.ERROR, - _("Cannot connect to JIRA server")); - } - } - - private void showSyncInfo() { - Map args = new HashMap(); - - JiraSyncInfo jiraSyncInfoProgress = jiraOrderElementSynchronizer - .getJiraSyncInfo(); - args.put("showSyncProgressSuccess", - jiraSyncInfoProgress.isSyncSuccessful()); - args.put("jiraSyncProgressFailedReasons", new SimpleListModel( - jiraSyncInfoProgress.getSyncFailedReasons())); - - JiraSyncInfo jiraSyncInfoTimesheet = jiraTimesheetSynchronizer - .getJiraSyncInfo(); - args.put("showSyncTimesheetSuccess", - jiraSyncInfoTimesheet.isSyncSuccessful()); - args.put("jiraSyncTimesheetFailedReasons", new SimpleListModel( - jiraSyncInfoTimesheet.getSyncFailedReasons())); - - Window jiraSyncInfoWindow = (Window) Executions.createComponents( - "/orders/_jiraSyncInfo.zul", null, args); - - try { - jiraSyncInfoWindow.doModal(); - } catch (SuspendNotAllowedException e) { - throw new RuntimeException(e); - } catch (InterruptedException e) { + jiraSynchronizationController.doAfterCompose(editWindow); + } catch (Exception e) { throw new RuntimeException(e); } } - private void setupJiraSyncPopup(Component comp, ListModel model) { - - startJiraSyncButton = (Button) comp.getFellow("startJiraSyncButton"); - startJiraSyncButton.setLabel(_("Start sync")); - - startJiraSyncButton.addEventListener(Events.ON_CLICK, new EventListener() { - - @Override - public void onEvent(Event event) { - startSyncWithJira(comboJiraLabel.getValue()); - } - }); - cancelJiraSyncButton = (Button) comp.getFellow("cancelJiraSyncButton"); - cancelJiraSyncButton.setLabel(_("Cancel")); - cancelJiraSyncButton.addEventListener(Events.ON_CLICK, new EventListener() { - - @Override - public void onEvent(Event event) { - jirasyncPopup.close(); - } - }); - comboJiraLabel = (Combobox) comp.getFellowIfAny("comboJiraLabel"); - comboJiraLabel.setModel(model); - - jirasyncPopup = (Popup) comp.getFellow("jirasyncPopup"); - - } - /** - * This class provides case insensitive search for the {@link Combobox}. + * Setup the connector, TimSynchronization controller */ - private class SimpleListModelExt extends SimpleListModel { - - public SimpleListModelExt(List data) { - super(data); + public void setupTimSynchronizationController() { + if (timSynchronizationController == null) { + timSynchronizationController = new TimSynchronizationController(); } - - public ListModel getSubModel(Object value, int nRows) { - final String idx = value == null ? "" : objectToString(value); - if (nRows < 0) { - nRows = 10; - } - final LinkedList data = new LinkedList(); - for (int i = 0; i < getSize(); i++) { - if (idx.equals("") - || entryMatchesText(getElementAt(i).toString(), idx)) { - data.add(getElementAt(i)); - if (--nRows <= 0) { - break; - } - } - } - return new SimpleListModelExt(data); + try { + timSynchronizationController.doAfterCompose(editWindow); + } catch (Exception e) { + throw new RuntimeException(e); } - - public boolean entryMatchesText(String entry, String text) { - return entry.toLowerCase().contains(text.toLowerCase()); - } - } - - public boolean isJiraActivated() { - return orderModel.isJiraActivated(); } } diff --git a/libreplan-webapp/src/main/java/org/libreplan/web/orders/OrderElementTreeController.java b/libreplan-webapp/src/main/java/org/libreplan/web/orders/OrderElementTreeController.java index 700471b2f..1f2d4f025 100644 --- a/libreplan-webapp/src/main/java/org/libreplan/web/orders/OrderElementTreeController.java +++ b/libreplan-webapp/src/main/java/org/libreplan/web/orders/OrderElementTreeController.java @@ -37,9 +37,11 @@ import java.util.logging.Filter; import javax.annotation.Resource; import org.apache.commons.lang.StringUtils; -import org.libreplan.business.common.daos.IConfigurationDAO; +import org.libreplan.business.common.daos.IConnectorDAO; +import org.libreplan.business.common.entities.Connector; import org.libreplan.business.common.entities.EntitySequence; -import org.libreplan.business.common.entities.JiraConfiguration; +import org.libreplan.business.common.entities.PredefinedConnectorProperties; +import org.libreplan.business.common.entities.PredefinedConnectors; import org.libreplan.business.orders.entities.Order; import org.libreplan.business.orders.entities.OrderElement; import org.libreplan.business.orders.entities.OrderLine; @@ -126,7 +128,7 @@ public class OrderElementTreeController extends TreeController { private Popup filterOptionsPopup; @Autowired - private IConfigurationDAO configurationDAO; + private IConnectorDAO connectorDAO; public List getLabels() { return orderModel.getLabels(); @@ -510,9 +512,17 @@ public class OrderElementTreeController extends TreeController { String code = orderElement.getCode(); A hyperlink = new A(code); - String jiraUrl = configurationDAO.getConfigurationWithReadOnlyTransaction().getJiraConfiguration().getJiraUrl(); + Connector connector = connectorDAO + .findUniqueByName(PredefinedConnectors.JIRA.getName()); + if (connector == null) { + return; + } - String codeWithoutPrefix = StringUtils.removeStart(code, JiraConfiguration.CODE_PREFIX); + String jiraUrl = connector.getPropertiesAsMap().get( + PredefinedConnectorProperties.SERVER_URL); + + String codeWithoutPrefix = StringUtils.removeStart(code, + PredefinedConnectorProperties.JIRA_CODE_PREFIX); codeWithoutPrefix = StringUtils.removeStart(codeWithoutPrefix, orderElement.getOrder().getCode() + EntitySequence.CODE_SEPARATOR_CHILDREN); diff --git a/libreplan-webapp/src/main/java/org/libreplan/web/orders/OrderModel.java b/libreplan-webapp/src/main/java/org/libreplan/web/orders/OrderModel.java index c02101f4e..e57e0d932 100644 --- a/libreplan-webapp/src/main/java/org/libreplan/web/orders/OrderModel.java +++ b/libreplan-webapp/src/main/java/org/libreplan/web/orders/OrderModel.java @@ -962,11 +962,4 @@ public class OrderModel extends IntegrationEntityModel implements IOrderModel { return user; } - @Override - @Transactional(readOnly = true) - public boolean isJiraActivated() { - return configurationDAO.getConfiguration().getJiraConfiguration() - .isJiraActivated(); - } - } diff --git a/libreplan-webapp/src/main/java/org/libreplan/web/orders/TimSynchronizationController.java b/libreplan-webapp/src/main/java/org/libreplan/web/orders/TimSynchronizationController.java new file mode 100644 index 000000000..e74897469 --- /dev/null +++ b/libreplan-webapp/src/main/java/org/libreplan/web/orders/TimSynchronizationController.java @@ -0,0 +1,179 @@ +/* + * This file is part of LibrePlan + * + * Copyright (C) 2013 St. Antoniusziekenhuis + * + * 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 . + */ + +package org.libreplan.web.orders; + +import static org.libreplan.web.I18nHelper._; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.logging.LogFactory; +import org.libreplan.business.common.daos.IConnectorDAO; +import org.libreplan.business.common.entities.Connector; +import org.libreplan.business.common.entities.ConnectorException; +import org.libreplan.business.common.entities.PredefinedConnectors; +import org.libreplan.business.orders.entities.Order; +import org.libreplan.business.orders.entities.OrderSyncInfo; +import org.libreplan.importers.IExportTimesheetsToTim; +import org.libreplan.importers.SynchronizationInfo; +import org.libreplan.web.common.IMessagesForUser; +import org.libreplan.web.common.Level; +import org.libreplan.web.common.MessagesForUser; +import org.libreplan.web.common.Util; +import org.springframework.beans.factory.annotation.Autowired; +import org.zkoss.zk.ui.Component; +import org.zkoss.zk.ui.Executions; +import org.zkoss.zk.ui.SuspendNotAllowedException; +import org.zkoss.zk.ui.util.GenericForwardComposer; +import org.zkoss.zul.Label; +import org.zkoss.zul.SimpleListModel; +import org.zkoss.zul.Textbox; +import org.zkoss.zul.api.Groupbox; +import org.zkoss.zul.api.Window; + +/** + * Controller for Tim synchronization + * + * @author Miciele Ghiorghis + */ +public class TimSynchronizationController extends GenericForwardComposer { + + private static final org.apache.commons.logging.Log LOG = LogFactory + .getLog(TimSynchronizationController.class); + + private OrderCRUDController orderController; + + private Window editWindow; + + private Groupbox timGroupBox; + + private Textbox txtProductCode; + + private Label labelProductCode, labelLastSyncDate; + + @Autowired + private IExportTimesheetsToTim exportTimesheetsToTim; + + @Autowired + private IConnectorDAO connectorDAO; + + private Component messagesContainer; + + private IMessagesForUser messagesForUser; + + @Override + public void doAfterCompose(Component comp) throws Exception { + super.doAfterCompose(comp); + comp.setVariable("timSynchronizationController", this, true); + loadComponentsEditWindow(comp); + showOrHideTimEditWindow(); + updateOrderLastSyncInfoScreen(); + } + + /** + * Returns current {@link Order} + */ + private Order getOrder() { + return orderController.getOrder(); + } + + private void loadComponentsEditWindow(Component comp) { + txtProductCode = (Textbox) comp.getFellowIfAny("txtProductCode"); + labelLastSyncDate = (Label) comp + .getFellowIfAny("labelLastSyncDate"); + labelProductCode = (Label) comp + .getFellowIfAny("labelProductCode"); + timGroupBox = (Groupbox) comp.getFellowIfAny("timGroupBox"); + + messagesForUser = new MessagesForUser(messagesContainer); + } + + /** + * Show or hide TimEditWindow based on Tim + * {@link Connector#isActivated()} + */ + private void showOrHideTimEditWindow() { + timGroupBox.setVisible(isTimActivated()); + } + + /** + * Updates the UI text last synchronized date and the text product code + */ + private void updateOrderLastSyncInfoScreen() { + OrderSyncInfo orderSyncInfo = exportTimesheetsToTim + .getOrderLastSyncInfo(getOrder()); + if (orderSyncInfo != null) { + labelLastSyncDate.setValue(Util.formatDateTime(orderSyncInfo + .getLastSyncDate())); + labelProductCode.setValue("(" + orderSyncInfo.getKey() + ")"); + } + } + + /** + * Returns true if Tim is Activated. Used to show/hide Tim edit window + */ + public boolean isTimActivated() { + Connector connector = connectorDAO + .findUniqueByName(PredefinedConnectors.TIM.getName()); + if (connector == null) { + return false; + } + return connector.isActivated(); + } + + public void startExportToTim() { + txtProductCode.setConstraint("no empty:" + _("cannot be empty")); + try { + exportTimesheetsToTim.exportTimesheets(txtProductCode.getValue(), + getOrder()); + + updateOrderLastSyncInfoScreen(); + + shwoImpExpInfo(); + + } catch (ConnectorException e) { + messagesForUser.showMessage(Level.ERROR, + _("Exporting timesheets to Tim failed. Check the Tim connector")); + } + } + + + private void shwoImpExpInfo() { + Map args = new HashMap(); + + SynchronizationInfo synchronizationInfo = exportTimesheetsToTim.getSynchronizationInfo(); + args.put("action", synchronizationInfo.getAction()); + args.put("showSuccess", synchronizationInfo.isSuccessful()); + args.put("failedReasons", + new SimpleListModel(synchronizationInfo.getFailedReasons())); + + Window timImpExpInfoWindow = (Window) Executions.createComponents( + "/orders/_timImpExpInfo.zul", null, args); + + try { + timImpExpInfoWindow.doModal(); + } catch (SuspendNotAllowedException e) { + throw new RuntimeException(e); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/libreplan-webapp/src/main/java/org/libreplan/web/planner/tabs/PlanningTabCreator.java b/libreplan-webapp/src/main/java/org/libreplan/web/planner/tabs/PlanningTabCreator.java index 09aa3903e..793a0c1fa 100644 --- a/libreplan-webapp/src/main/java/org/libreplan/web/planner/tabs/PlanningTabCreator.java +++ b/libreplan-webapp/src/main/java/org/libreplan/web/planner/tabs/PlanningTabCreator.java @@ -273,6 +273,8 @@ public class PlanningTabCreator { breadcrumbs.appendChild(new Label(_("Project Scheduling"))); if (mode.isOf(ModeType.ORDER)) { + orderPlanningController.getOrderCRUDController() + .checkUserCanRead(order); Label nameLabel = new Label(order.getName()); nameLabel.setTooltiptext(order.getName() + "." + order.getDescription()); diff --git a/libreplan-webapp/src/main/resources/i18n/es.po b/libreplan-webapp/src/main/resources/i18n/es.po index d205e97d3..f8b98c7fe 100644 --- a/libreplan-webapp/src/main/resources/i18n/es.po +++ b/libreplan-webapp/src/main/resources/i18n/es.po @@ -5395,10 +5395,8 @@ msgid "Progress Evolution" msgstr "Evolución del progreso" #: libreplan-webapp/src/main/java/org/libreplan/web/dashboard/DashboardController.java:165 -msgid "" -"There is a margin of %d days with the project global deadline (%.2f %%)." -msgstr "" -"Hay un margen de %d días con la fecha límite global del proyecto (%.2f %%)." +msgid "There is a margin of {0} days with the project global deadline ({1}%)." +msgstr "Hay un margen de {0} días con la fecha límite global del proyecto ({1}%)." #: libreplan-webapp/src/main/webapp/planner/taskpanels/_tabPanelNonLimitingResourceAllocation.zul:91 msgid "Original" diff --git a/libreplan-webapp/src/main/resources/i18n/gl.po b/libreplan-webapp/src/main/resources/i18n/gl.po index aff54b45d..50932313e 100644 --- a/libreplan-webapp/src/main/resources/i18n/gl.po +++ b/libreplan-webapp/src/main/resources/i18n/gl.po @@ -5156,9 +5156,8 @@ msgid "" msgstr "Os valores de subcontratación son de só lectura porque foron notificados pola empresa subcontratista." #: libreplan-webapp/src/main/java/org/libreplan/web/dashboard/DashboardController.java:165 -msgid "" -"There is a margin of %d days with the project global deadline (%.2f %%)." -msgstr "Hai unha marxe de %d días coa data límite global do proxecto (%.2f %%)." +msgid "There is a margin of {0} days with the project global deadline ({1}%)." +msgstr "Hai unha marxe de {0} días coa data límite global do proxecto ({1}%)." #: libreplan-webapp/src/main/java/org/libreplan/web/planner/chart/EarnedValueChartFiller.java:250 msgid "CV" diff --git a/libreplan-webapp/src/main/resources/i18n/keys.pot b/libreplan-webapp/src/main/resources/i18n/keys.pot index 9ee0edb62..b6c2492b2 100644 --- a/libreplan-webapp/src/main/resources/i18n/keys.pot +++ b/libreplan-webapp/src/main/resources/i18n/keys.pot @@ -5203,7 +5203,7 @@ msgstr "" #: libreplan-webapp/src/main/java/org/libreplan/web/dashboard/DashboardController.java:165 msgid "" -"There is a margin of %d days with the project global deadline (%.2f %%)." +"There is a margin of {0} days with the project global deadline ({1}%)." msgstr "" #: libreplan-webapp/src/main/webapp/planner/taskpanels/_tabPanelNonLimitingResourceAllocation.zul:91 diff --git a/libreplan-webapp/src/main/resources/i18n/zh.po b/libreplan-webapp/src/main/resources/i18n/zh.po index c3b04fd56..e2de4e8a9 100644 --- a/libreplan-webapp/src/main/resources/i18n/zh.po +++ b/libreplan-webapp/src/main/resources/i18n/zh.po @@ -1,4 +1,4 @@ -# LibrePlan - Webapp module. +# LibrePlae - Webapp module. # Copyright (C) 2009-2010 Fundación para o Fomento da Calidade Industrial e # Desenvolvemento Tecnolóxico de Galicia # Copyright (C) 2010-2012 Igalia, S.L. @@ -72,7 +72,7 @@ msgid "Create Virtual Worker" msgstr "创建虚拟工作人员" #: libreplan-webapp/src/main/java/org/libreplan/web/workreports/WorkReportCRUDController.java:862 -#: libreplan-webapp/src/main/java/org/libreplan/web/tree/TreeComponent.java:105 +#: libreplan-webapp/src/main/java/org/libreplan/web/tree/TreeComponent.java:104 #: libreplan-webapp/src/main/webapp/labels/_editLabelType.zul:81 #: libreplan-webapp/src/main/webapp/labels/_listLabelTypes.zul:28 #: libreplan-webapp/src/main/webapp/advance/_listAdvanceTypes.zul:30 @@ -5156,8 +5156,8 @@ msgstr "阅读不仅是因为他们的报告由分包商公司的分包商值。 #: libreplan-webapp/src/main/java/org/libreplan/web/dashboard/DashboardController.java:165 msgid "" -"There is a margin of %d days with the project global deadline (%.2f %%)." -msgstr "总项目的截止日期(%.2f %%)前有%d天的余量。" +"There is a margin of {0} days with the project global deadline ({1}%)." +msgstr "总项目的截止日期({1}%)前有{0}天的余量。" #: libreplan-webapp/src/main/java/org/libreplan/web/planner/chart/EarnedValueChartFiller.java:250 msgid "CV" diff --git a/libreplan-webapp/src/main/resources/libreplan-webapp-spring-config.xml b/libreplan-webapp/src/main/resources/libreplan-webapp-spring-config.xml index da70d704a..a602dfd15 100644 --- a/libreplan-webapp/src/main/resources/libreplan-webapp-spring-config.xml +++ b/libreplan-webapp/src/main/resources/libreplan-webapp-spring-config.xml @@ -49,6 +49,16 @@ class="org.libreplan.web.scenarios.CurrentUserScenarioAwareManager" scope="singleton"/> + + + + + + + diff --git a/libreplan-webapp/src/main/webapp/common/_editJobScheduling.zul b/libreplan-webapp/src/main/webapp/common/_editJobScheduling.zul new file mode 100644 index 000000000..fba176462 --- /dev/null +++ b/libreplan-webapp/src/main/webapp/common/_editJobScheduling.zul @@ -0,0 +1,167 @@ + + + + + + + + + + + + + + + +