Merge branch 'master' into mpxj-import

Conflicts:
	libreplan-business/src/main/java/org/libreplan/business/orders/entities/Order.java
	libreplan-webapp/pom.xml
	pom.xml
This commit is contained in:
Lorenzo Tilve Álvaro 2013-04-16 09:25:07 +02:00
commit cc246eedfa
113 changed files with 8658 additions and 710 deletions

View file

@ -28,7 +28,9 @@ import org.libreplan.business.calendars.daos.ICalendarDataDAO;
import org.libreplan.business.calendars.daos.ICalendarExceptionDAO; import org.libreplan.business.calendars.daos.ICalendarExceptionDAO;
import org.libreplan.business.calendars.daos.ICalendarExceptionTypeDAO; import org.libreplan.business.calendars.daos.ICalendarExceptionTypeDAO;
import org.libreplan.business.common.daos.IConfigurationDAO; 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.IEntitySequenceDAO;
import org.libreplan.business.common.daos.IJobSchedulerConfigurationDAO;
import org.libreplan.business.costcategories.daos.ICostCategoryDAO; import org.libreplan.business.costcategories.daos.ICostCategoryDAO;
import org.libreplan.business.costcategories.daos.IHourCostDAO; import org.libreplan.business.costcategories.daos.IHourCostDAO;
import org.libreplan.business.costcategories.daos.IResourcesCostCategoryAssignmentDAO; 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.IHoursGroupDAO;
import org.libreplan.business.orders.daos.IOrderDAO; import org.libreplan.business.orders.daos.IOrderDAO;
import org.libreplan.business.orders.daos.IOrderElementDAO; 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.planner.daos.ITaskElementDAO;
import org.libreplan.business.qualityforms.daos.IQualityFormDAO; import org.libreplan.business.qualityforms.daos.IQualityFormDAO;
import org.libreplan.business.resources.daos.ICriterionDAO; import org.libreplan.business.resources.daos.ICriterionDAO;
@ -203,6 +206,15 @@ public class Registry {
@Autowired @Autowired
private IOrderAuthorizationDAO orderAuthorizationDAO; private IOrderAuthorizationDAO orderAuthorizationDAO;
@Autowired
private IConnectorDAO connectorDAO;
@Autowired
private IOrderSyncInfoDAO orderSyncInfoDAO;
@Autowired
private IJobSchedulerConfigurationDAO jobSchedulerConfigurationDAO;
@Autowired @Autowired
private IAdHocTransactionService transactionServiceDAO; private IAdHocTransactionService transactionServiceDAO;
@ -379,4 +391,15 @@ public class Registry {
return getInstance().orderAuthorizationDAO; return getInstance().orderAuthorizationDAO;
} }
public static IConnectorDAO getConnectorDAO() {
return getInstance().connectorDAO;
}
public static IOrderSyncInfoDAO getOrderSyncInfoDAO() {
return getInstance().orderSyncInfoDAO;
}
public static IJobSchedulerConfigurationDAO getJobSchedulerConfigurationDAO() {
return getInstance().jobSchedulerConfigurationDAO;
}
} }

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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 <m.ghiorghis@antoniusziekenhuis.nl>
* @author Manuel Rego Casasnovas <rego@igalia.com>
*/
@Repository
@Scope(BeanDefinition.SCOPE_SINGLETON)
public class ConnectorDAO extends GenericDAOHibernate<Connector, Long>
implements IConnectorDAO {
@Override
@Transactional(readOnly = true)
public List<Connector> 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);
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
package org.libreplan.business.common.daos;
import java.util.List;
import org.libreplan.business.common.entities.Connector;
/**
* Contract for {@link Conn}
*
* @author Miciele Ghiorghis <m.ghiorghis@antoniusziekenhuis.nl>
* @author Manuel Rego Casasnovas <rego@igalia.com>
*/
public interface IConnectorDAO extends IGenericDAO<Connector, Long> {
List<Connector> getAll();
Connector findUniqueByName(String name);
boolean existsByNameAnotherTransaction(Connector connector);
Connector findUniqueByNameAnotherTransaction(String name);
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
package org.libreplan.business.common.daos;
import java.util.List;
import org.libreplan.business.common.entities.JobSchedulerConfiguration;
/**
* Contract for {@link JobSchedulerConfigurationDAO}
*
* @author Miciele Ghiorghis <m.ghiorghis@antoniusziekenhuis.nl>
*/
public interface IJobSchedulerConfigurationDAO extends
IGenericDAO<JobSchedulerConfiguration, Long> {
/**
* Returns all {@link JobSchedulerConfiguration}
*/
List<JobSchedulerConfiguration> getAll();
/**
* Searches and returns {@link JobSchedulerConfiguration} for the given
* <code>connectorName</code>
*
* @param connectorName
* the name of the connector
*/
List<JobSchedulerConfiguration> findByConnectorName(String connectorName);
/**
* Searches and returns {@link JobSchedulerConfiguration} for the given
* <code>jobGroup</code> and <code>jobName</code>
*
* @param jobGroup
* @param jobName
*/
JobSchedulerConfiguration findByJobGroupAndJobName(String jobGroup,
String jobName);
/**
* Returns true if there exists other @{link JobSchedulerConfiguration} with
* the same <code>{@link JobSchedulerConfiguration#getJobGroup()}</code> and
* <code>{@link JobSchedulerConfiguration#getJobName()</code>
*
* @param jobSchedulerConfiguration
* the <code>{@link JobSchedulerConfiguration}</code>
*/
boolean existsByJobGroupAndJobNameAnotherTransaction(
JobSchedulerConfiguration jobSchedulerConfiguration);
/**
* Returns unique {@link JobSchedulerConfiguration} for the specified
* <code>JobGroup</code> and <code>JobName</code>
*
* @param jobGroup
* the jobGroup
* @param jobName
* the jobName
*/
JobSchedulerConfiguration findUniqueByJobGroupAndJobNameAnotherTransaction(
String jobGroup, String jobName);
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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 <m.ghiorghis@antoniusziekenhuis.nl>
*/
@Repository
@Scope(BeanDefinition.SCOPE_SINGLETON)
public class JobSchedulerConfigurationDAO extends
GenericDAOHibernate<JobSchedulerConfiguration, Long> implements
IJobSchedulerConfigurationDAO {
@Override
@Transactional(readOnly = true)
public List<JobSchedulerConfiguration> 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<JobSchedulerConfiguration> findByConnectorName(
String connectorName) {
Criteria c = getSession().createCriteria(
JobSchedulerConfiguration.class).add(
Restrictions.eq("connectorName", connectorName));
return ((List<JobSchedulerConfiguration>) 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 <code>{@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);
}
}

View file

@ -104,8 +104,6 @@ public class Configuration extends BaseEntity {
private Boolean generateCodeForExpenseSheets = true; private Boolean generateCodeForExpenseSheets = true;
private JiraConfiguration jiraConfiguration;
/** /**
* Currency code according to ISO-4217 (3 letters) * Currency code according to ISO-4217 (3 letters)
*/ */
@ -505,12 +503,4 @@ public class Configuration extends BaseEntity {
this.secondsPlanningWarning = secondsPlanningWarning; this.secondsPlanningWarning = secondsPlanningWarning;
} }
public JiraConfiguration getJiraConfiguration() {
return jiraConfiguration;
}
public void setJiraConfiguration(JiraConfiguration jiraConfiguration) {
this.jiraConfiguration = jiraConfiguration;
}
} }

View file

@ -81,13 +81,6 @@ public class ConfigurationBootstrap implements IConfigurationBootstrap {
} }
configuration.setLdapConfiguration(ldapConfiguration); configuration.setLdapConfiguration(ldapConfiguration);
JiraConfiguration jiraConfiguration = configuration
.getJiraConfiguration();
if (jiraConfiguration == null) {
jiraConfiguration = JiraConfiguration.create();
}
configuration.setJiraConfiguration(jiraConfiguration);
configurationDAO.save(configuration); configurationDAO.save(configuration);
} }

View file

@ -75,8 +75,6 @@ public class ConfigurationTypeOfWorkHoursBootstrap implements
} }
configuration.setPersonalTimesheetsTypeOfWorkHours(typeOfWorkHours); configuration.setPersonalTimesheetsTypeOfWorkHours(typeOfWorkHours);
configuration.getJiraConfiguration().setJiraConnectorTypeOfWorkHours(
typeOfWorkHours);
configurationDAO.save(configuration); configurationDAO.save(configuration);
} }

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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 <code>name</code> 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 <m.ghiorghis@antoniusziekenhuis.nl>
* @author Manuel Rego Casasnovas <rego@igalia.com>
*/
public class Connector extends BaseEntity {
public static Connector create(String name) {
return create(new Connector(name));
}
private String name;
private List<ConnectorProperty> properties = new ArrayList<ConnectorProperty>();
/**
* 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<ConnectorProperty> getProperties() {
return Collections.unmodifiableList(properties);
}
public void setProperties(List<ConnectorProperty> properties) {
this.properties = properties;
}
public void addProperty(ConnectorProperty property) {
properties.add(property);
}
public Map<String, String> getPropertiesAsMap() {
Map<String, String> map = new HashMap<String, String>();
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;
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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 <rego@igalia.com>
*/
@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);
}
}
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
package org.libreplan.business.common.entities;
/**
* Exception to ecapsulate connector(values) exceptions
*
* @author Miciele Ghiorghis <m.ghiorghis@antoniusziekenhuis.nl>
*/
public class ConnectorException extends Exception {
public ConnectorException(String message) {
super(message);
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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 <mrego@igalia.com>
*/
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;
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
package org.libreplan.business.common.entities;
import org.libreplan.business.IDataBootstrap;
/**
* Contract for {@link ConnectorBootstrap}.
*
* @author Manuel Rego Casasnovas <rego@igalia.com>
*/
public interface IConnectorBootstrap extends IDataBootstrap {
void loadRequiredData();
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
package org.libreplan.business.common.entities;
import org.libreplan.business.common.BaseEntity;
import org.libreplan.business.costcategories.entities.TypeOfWorkHours;
/**
* JiraConfiguration entity
*
* @author Miciele Ghiorghis <m.ghiorghis@antoniusziekenhuis.nl>
*/
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:
* <ul>
* <li>A comma-separated list of labels</li>
* <li>A URL that will return a comma-separated list of labels</li>
* </ul>
*/
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;
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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 <m.ghiorghis@antoniusziekenhuis.nl>
*/
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;
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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 <code>SchedulerManager</code> to
* schedule jobs and in UI to show the scheduler status.
*
* The <code>jobGroup</code> and <code>jobName</code> together forms a job key
* and non of the fields must be null. Moreover it should contain a valid
* <code>cronExpression</code>
*
* @author Miciele Ghiorghis <m.ghiorghis@antoniusziekenhuis.nl>
*/
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());
}
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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 <m.ghiorghis@antoniusziekenhuis.nl>
* @author Manuel Rego Casasnovas <rego@igalia.com>
*/
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-";
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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 <m.ghiorghis@antoniusziekenhuis.nl>
* @author Manuel Rego Casasnovas <rego@igalia.com>
*/
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<ConnectorProperty> properties;
private PredefinedConnectors(String name,
ConnectorProperty... properties) {
this.name = name;
this.properties = Arrays.asList(properties);
}
public String getName() {
return name;
}
public List<ConnectorProperty> getProperties() {
return properties;
}
}

View file

@ -29,8 +29,12 @@ import org.hibernate.Criteria;
import org.hibernate.criterion.Order; import org.hibernate.criterion.Order;
import org.hibernate.criterion.Restrictions; import org.hibernate.criterion.Restrictions;
import org.libreplan.business.common.daos.IConfigurationDAO; 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.daos.IntegrationEntityDAO;
import org.libreplan.business.common.entities.Configuration; 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.InstanceNotFoundException;
import org.libreplan.business.common.exceptions.ValidationException; import org.libreplan.business.common.exceptions.ValidationException;
import org.libreplan.business.costcategories.entities.HourCost; import org.libreplan.business.costcategories.entities.HourCost;
@ -56,6 +60,9 @@ public class TypeOfWorkHoursDAO extends IntegrationEntityDAO<TypeOfWorkHours>
@Autowired @Autowired
private IConfigurationDAO configurationDAO; private IConfigurationDAO configurationDAO;
@Autowired
private IConnectorDAO connectorDAO;
@Override @Override
public TypeOfWorkHours findUniqueByCode(TypeOfWorkHours typeOfWorkHours) public TypeOfWorkHours findUniqueByCode(TypeOfWorkHours typeOfWorkHours)
throws InstanceNotFoundException { throws InstanceNotFoundException {
@ -194,14 +201,18 @@ public class TypeOfWorkHoursDAO extends IntegrationEntityDAO<TypeOfWorkHours>
} }
private void checkIsJiraConnectorTypeOfWorkHours(TypeOfWorkHours type) { private void checkIsJiraConnectorTypeOfWorkHours(TypeOfWorkHours type) {
Configuration configuration = configurationDAO.getConfiguration(); Connector connector = connectorDAO
if (configuration.getJiraConfiguration() .findUniqueByName(PredefinedConnectors.JIRA.getName());
.getJiraConnectorTypeOfWorkHours().getId().equals(type.getId())) { if (connector != null) {
String name = connector.getPropertiesAsMap().get(
PredefinedConnectorProperties.JIRA_HOURS_TYPE);
if (name.equals(type.getName())) {
throw ValidationException throw ValidationException
.invalidValue( .invalidValue(
"Cannot delete the type of work hours. It is configured as type of work hours for JIRA connector.", "Cannot delete the type of work hours. It is configured as type of work hours for JIRA connector.",
type); type);
} }
} }
}
} }

View file

@ -30,6 +30,9 @@ import org.hibernate.validator.NotNull;
import org.libreplan.business.common.IHumanIdentifiable; import org.libreplan.business.common.IHumanIdentifiable;
import org.libreplan.business.common.IntegrationEntity; import org.libreplan.business.common.IntegrationEntity;
import org.libreplan.business.common.Registry; 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.common.exceptions.InstanceNotFoundException;
import org.libreplan.business.costcategories.daos.ITypeOfWorkHoursDAO; import org.libreplan.business.costcategories.daos.ITypeOfWorkHoursDAO;
@ -168,13 +171,15 @@ public class TypeOfWorkHours extends IntegrationEntity implements IHumanIdentifi
@AssertTrue(message = "type of work hours for JIRA connector cannot be disabled") @AssertTrue(message = "type of work hours for JIRA connector cannot be disabled")
public boolean checkJiraConnectorTypeOfWorkHoursNotDisabled() { public boolean checkJiraConnectorTypeOfWorkHoursNotDisabled() {
if (!isNewObject() && !getEnabled()) { if (!isNewObject() && !getEnabled()) {
TypeOfWorkHours typeOfWorkHours = Registry.getConfigurationDAO() Connector connector = Registry.getConnectorDAO().findUniqueByName(
.getConfiguration().getJiraConfiguration() PredefinedConnectors.JIRA.getName());
.getJiraConnectorTypeOfWorkHours(); if (connector != null) {
if (typeOfWorkHours.getId().equals(getId())) { if (this.name.equals(connector.getPropertiesAsMap().get(
PredefinedConnectorProperties.JIRA_HOURS_TYPE))) {
return false; return false;
} }
} }
}
return true; return true;
} }

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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 <m.ghiorghis@antoniusziekenhuis.nl>
*/
public interface IOrderSyncInfoDAO extends IGenericDAO<OrderSyncInfo, Long> {
/**
* Search last synchronized info for the specified
* <code>{@link Order}</code> and <code>connectorName</code>
*
* @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
* <code>{@link Order}</code> and <code>connectorName</code>
*
* @param order
* the order to search for
* @param connectorName
* the connector name
* @return list of last synchronized infos
*/
List<OrderSyncInfo> findLastSynchronizedInfosByOrderAndConnectorName(
Order order, String connectorName);
/**
* Searches and returns <code>{@link OrderSyncInfo}</code> for the specified
* <code>key</code> and <code>connectorName</code>
*
* @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
* <code>connectorName</code>
*
* @param connectorName
* the connector name
* @return a list of OrderSyncInfo if found and null if not
*/
List<OrderSyncInfo> findByConnectorName(String connectorName);
/**
* Returns true if there exists other {@link OrderSyncInfo} with the same
* <code>{@link OrderSyncInfo#getKey()}</code>,
* <code>{@link OrderSyncInfo#getOrder()}</code> and
* <code>{@link OrderSyncInfo#getConnectorName()}</code>
*
* @param orderSyncInfo
* the <code>{@link OrderSyncInfo}</code>
*/
boolean existsByKeyOrderAndConnectorNameAnotherTransaction(
OrderSyncInfo orderSyncInfo);
/**
* Returns unique {@link OrderSyncInfo} for the specified <code>key</code>,
* <code>order</code> and <code>connectorName</code>
*
* @param key
* the key
* @param order
* an order
* @param connectorName
* the name of the connector
*/
OrderSyncInfo findUniqueByKeyOrderAndConnectorNameAnotherTransaction(
String key, Order order, String connectorName);
}

View file

@ -442,12 +442,16 @@ public class OrderDAO extends IntegrationEntityDAO<Order> implements
} else { } else {
String strQuery = "SELECT oa.order.id " String strQuery = "SELECT oa.order.id "
+ "FROM OrderAuthorization oa " + "FROM OrderAuthorization oa "
+ "WHERE oa.user = :user " + "WHERE oa.user = :user ";
+ "OR oa.profile IN (:profiles) "; if (!user.getProfiles().isEmpty()) {
strQuery += "OR oa.profile IN (:profiles) ";
}
Query query = getSession().createQuery(strQuery); Query query = getSession().createQuery(strQuery);
query.setParameter("user", user); query.setParameter("user", user);
if (!user.getProfiles().isEmpty()) {
query.setParameterList("profiles", user.getProfiles()); query.setParameterList("profiles", user.getProfiles());
}
return query.list(); return query.list();
} }

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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 <m.ghiorghis@antoniusziekenhuis.nl>
*/
@Repository
@Scope(BeanDefinition.SCOPE_SINGLETON)
public class OrderSyncInfoDAO extends GenericDAOHibernate<OrderSyncInfo, Long>
implements IOrderSyncInfoDAO {
@Override
public OrderSyncInfo findLastSynchronizedInfoByOrderAndConnectorName(
Order order, String connectorName) {
List<OrderSyncInfo> orderSyncInfoList = findLastSynchronizedInfosByOrderAndConnectorName(
order, connectorName);
if (orderSyncInfoList == null || orderSyncInfoList.isEmpty()) {
return null;
}
return orderSyncInfoList.get(0);
}
@Override
@SuppressWarnings("unchecked")
public List<OrderSyncInfo> 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<OrderSyncInfo> 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 <code>{@link OrderSyncInfo}</code> 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);
}
}

View file

@ -120,8 +120,6 @@ public class Order extends OrderLineGroup implements Comparable {
private Set<CustomerCommunication> customerCommunications = new HashSet<CustomerCommunication>(); private Set<CustomerCommunication> customerCommunications = new HashSet<CustomerCommunication>();
private String importedLabel;
@Valid @Valid
private SortedSet<DeadlineCommunication> deliveringDates = new TreeSet<DeadlineCommunication>( private SortedSet<DeadlineCommunication> deliveringDates = new TreeSet<DeadlineCommunication>(
new DeliverDateComparator()); new DeliverDateComparator());
@ -693,14 +691,6 @@ public class Order extends OrderLineGroup implements Comparable {
return true; return true;
} }
public String getImportedLabel() {
return importedLabel;
}
public void setImportedLabel(String importedLabel) {
this.importedLabel = importedLabel;
}
public void calculateAndSetTotalHours() { public void calculateAndSetTotalHours() {
int result = 0; int result = 0;
for (OrderElement orderElement : this.getChildren()) { for (OrderElement orderElement : this.getChildren()) {

View file

@ -52,7 +52,7 @@ import org.libreplan.business.advance.exceptions.DuplicateValueTrueReportGlobalA
import org.libreplan.business.common.IntegrationEntity; import org.libreplan.business.common.IntegrationEntity;
import org.libreplan.business.common.Registry; import org.libreplan.business.common.Registry;
import org.libreplan.business.common.daos.IIntegrationEntityDAO; 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.common.exceptions.ValidationException;
import org.libreplan.business.labels.entities.Label; import org.libreplan.business.labels.entities.Label;
import org.libreplan.business.materials.entities.MaterialAssignment; import org.libreplan.business.materials.entities.MaterialAssignment;
@ -1568,6 +1568,24 @@ public abstract class OrderElement extends IntegrationEntity implements
return workReportLineDAO.findByOrderElementAndChildren(this, sortedByDate); return workReportLineDAO.findByOrderElementAndChildren(this, sortedByDate);
} }
/**
* Gets workReportLines of this order-element between the specified
* <code>startDate</code> and <code>endDate</code>
*
* @param startDate
* the startDate
* @param endDate
* the endDate
* @param sortedByDate
* @return list of workReportLines
*/
public List<WorkReportLine> 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 * Checks if it has nay consolidated advance, if not checks if any parent
* has it * has it
@ -1668,7 +1686,7 @@ public abstract class OrderElement extends IntegrationEntity implements
if (code == null) { if (code == null) {
return false; return false;
} }
return code.startsWith(JiraConfiguration.CODE_PREFIX); return code.startsWith(PredefinedConnectorProperties.JIRA_CODE_PREFIX);
} }
public boolean isConvertedToContainer() { public boolean isConvertedToContainer() {

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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:
* <ul>
* <li>lastSyncDate: last date where synchronization took place</li>
* <li>key: an identifier, which connector's key is last synchronized</li>
* <li>connectorName: the name of the {@link Connector} that has running the
* synchronization</li>
* <li>order: order that is synchronized</li>
* </ul>
*
* @author Miciele Ghiorghis <m.ghiorghis@antoniusziekenhuis.nl>
*/
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());
}
}
}

View file

@ -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_EXPENSES;
import static org.libreplan.business.users.entities.UserRole.ROLE_HOURS_TYPES; 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_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_LABELS;
import static org.libreplan.business.users.entities.UserRole.ROLE_MACHINES; import static org.libreplan.business.users.entities.UserRole.ROLE_MACHINES;
import static org.libreplan.business.users.entities.UserRole.ROLE_MAIN_SETTINGS; 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 { public enum PredefinedProfiles {
SYSTEMS_ADMINISTRATOR("Systems Administrator", ROLE_MAIN_SETTINGS, 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, PROJECT_MANAGER("Project Manager", ROLE_READ_ALL_PROJECTS,
ROLE_EDIT_ALL_PROJECTS, ROLE_CREATE_PROJECTS, ROLE_PLANNING, ROLE_EDIT_ALL_PROJECTS, ROLE_CREATE_PROJECTS, ROLE_PLANNING,

View file

@ -66,6 +66,7 @@ public enum UserRole {
ROLE_MAIN_SETTINGS(_("Main Settings")), ROLE_MAIN_SETTINGS(_("Main Settings")),
ROLE_USER_ACCOUNTS(_("User Accounts")), ROLE_USER_ACCOUNTS(_("User Accounts")),
ROLE_PROFILES(_("Profiles")), ROLE_PROFILES(_("Profiles")),
ROLE_JOB_SCHEDULING(_("Job Scheduling")),
ROLE_COMPANIES(_("Companies")), ROLE_COMPANIES(_("Companies")),
ROLE_SEND_TO_SUBCONTRACTORS(_("Send To Subcontractors")), ROLE_SEND_TO_SUBCONTRACTORS(_("Send To Subcontractors")),
ROLE_RECEIVED_FROM_SUBCONTRACTORS(_("Received From Subcontractors")), ROLE_RECEIVED_FROM_SUBCONTRACTORS(_("Received From Subcontractors")),

View file

@ -76,5 +76,12 @@ public interface IWorkReportLineDAO extends
List<WorkReportLine> findByOrderElementAndWorkReports( List<WorkReportLine> findByOrderElementAndWorkReports(
OrderElement orderElement, List<WorkReport> workReports); OrderElement orderElement, List<WorkReport> workReports);
/**
* Returns the {@link WorkReportLine}s of the specified
* <code>orderElement</code> specified between <code>start</code> date and
* <code>end</code> date
*/
List<WorkReportLine> findByOrderElementAndChildrenFilteredByDate(
OrderElement orderElement, Date start, Date end, boolean sortByDate);
} }

View file

@ -216,4 +216,33 @@ public class WorkReportLineDAO extends IntegrationEntityDAO<WorkReportLine>
return (List<WorkReportLine>) criteria.list(); return (List<WorkReportLine>) criteria.list();
} }
@Transactional(readOnly = true)
public List<WorkReportLine> findByOrderElementAndChildrenFilteredByDate(
OrderElement orderElement, Date start, Date end, boolean sortByDate) {
if (orderElement.isNewObject()) {
return new ArrayList<WorkReportLine>();
}
Collection<OrderElement> 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();
}
} }

View file

@ -221,106 +221,100 @@
columnDataType="INTEGER" /> columnDataType="INTEGER" />
</changeSet> </changeSet>
<!-- Jira configuration--> <!-- order sync info -->
<changeSet id="add-new-column-jira_activated" author="miciele"> <changeSet author="miciele" id="create-table-order-sync-info">
<comment> <comment>Create new table order_sync_info</comment>
Add new column jira_activated with default value FALSE to configuration table <createTable tableName="order_sync_info">
</comment> <column name="id" type="BIGINT">
<addColumn tableName="configuration"> <constraints nullable="false" primaryKey="true"/>
<column name="jira_activated" type="BOOLEAN" /> </column>
</addColumn> <column name="version" type="BIGINT">
<addDefaultValue tableName="configuration" columnName="jira_activated" <constraints nullable="false"/>
defaultValueBoolean="FALSE" /> </column>
<addNotNullConstraint tableName="configuration" <column name="last_sync_date" type="DATETIME" >
columnName="jira_activated" <constraints nullable="false"/>
defaultNullValue="FALSE" </column>
columnDataType="BOOLEAN" /> <column name="key" type="VARCHAR(255)" >
<constraints nullable="false"/>
</column>
<column name="connector_name" type="VARCHAR(255)" >
<constraints nullable="false"/>
</column>
<column name="order_element_id" type="BIGINT" />
</createTable>
<addForeignKeyConstraint constraintName="order_sync_info_order_table_fkey"
baseTableName="order_sync_info" baseColumnNames="order_element_id"
referencedTableName="order_table" referencedColumnNames="order_element_id" />
</changeSet> </changeSet>
<changeSet id="add-new-column-jira_url" author="miciele"> <!-- connector -->
<comment> <changeSet author="rego" id="create-tables-related-to-connector-entity">
Add new column jira_url in table configuration <comment>Create tables related to Connector entity</comment>
</comment> <createTable tableName="connector">
<addColumn tableName="configuration"> <column name="id" type="BIGINT" autoIncrement="true">
<column name="jira_url" type="VARCHAR(255)" /> <constraints nullable="false" primaryKey="true" primaryKeyName="connector_pkey" />
</addColumn> </column>
</changeSet> <column name="version" type="BIGINT">
<constraints nullable="false" />
</column>
<column name="name" type="VARCHAR(255)" >
<constraints nullable="false" />
</column>
</createTable>
<changeSet id="add-new-column-jira_label_url" author="miciele"> <createTable tableName="connector_property">
<comment> <column name="connector_id" type="BIGINT">
Add new column jira_label_url in table configuration <constraints nullable="false" />
</comment> </column>
<addColumn tableName="configuration"> <column name="connector_property_position" type="INTEGER">
<column name="jira_label_url" type="VARCHAR(255)" /> <constraints nullable="false" />
</addColumn> </column>
</changeSet> <column name="key" type="VARCHAR(255)">
<constraints nullable="false" />
</column>
<column name="value" type="VARCHAR(255)" />
</createTable>
<changeSet id="add-new-column-jira_user_id" author="miciele"> <addPrimaryKey
<comment> columnNames="connector_id,connector_property_position"
Add new column jira_user_id in table configuration constraintName="connector_property_pkey"
</comment> tableName="connector_property"/>
<addColumn tableName="configuration">
<column name="jira_user_id" type="VARCHAR(255)" />
</addColumn>
</changeSet>
<changeSet id="add-new-column-jira_password" author="miciele">
<comment>
Add new column jira_user_id in table configuration
</comment>
<addColumn tableName="configuration">
<column name="jira_password" type="VARCHAR(255)" />
</addColumn>
</changeSet>
<changeSet id="add-new-column-imported-label" author="miciele">
<comment>
Add new column imported_label in table order_table
</comment>
<addColumn tableName="order_table">
<column name="imported_label" type="VARCHAR(255)" />
</addColumn>
</changeSet>
<changeSet id="add-jira_connector_type_of_work_hours-to-configuration"
author="mrego">
<comment>
Add new column jira_connector_type_of_work_hours to configuration
table.
</comment>
<addColumn tableName="configuration">
<column name="jira_connector_type_of_work_hours" type="BIGINT" />
</addColumn>
<addForeignKeyConstraint <addForeignKeyConstraint
constraintName="configuration_jira_connector_type_of_work_hours_fkey" baseColumnNames="connector_id"
baseTableName="configuration" baseTableName="connector_property"
baseColumnNames="jira_connector_type_of_work_hours" constraintName="connector_property_connector_id_fkey"
referencedTableName="type_of_work_hours" deferrable="false" initiallyDeferred="false"
referencedColumnNames="id" /> onDelete="NO ACTION" onUpdate="NO ACTION"
referencedColumnNames="id"
referencedTableName="connector"
referencesUniqueColumn="false"/>
</changeSet> </changeSet>
<!-- scheduler configuration -->
<changeSet id="rename-column-jira_label_url-in-configuration" <changeSet author="miciele" id="create-table-job-scheduler-configuration">
author="mrego"> <comment>Create new table job_scheduler_configuration</comment>
<comment> <createTable tableName="job_scheduler_configuration">
Rename column jira_label_url to jira_labels in configuration table <column name="id" type="BIGINT" autoIncrement="true">
</comment> <constraints nullable="false" primaryKey="true" />
<renameColumn tableName="configuration" </column>
oldColumnName="jira_label_url" <column name="version" type="BIGINT">
newColumnName="jira_labels" <constraints nullable="false" />
columnDataType="VARCHAR(255)" /> </column>
</changeSet> <column name="job_group" type="VARCHAR(255)" >
<constraints nullable="false" />
<changeSet id="change-column-jira_labels-in-configuration-to-text" </column>
author="mrego" dbms="postgresql"> <column name="job_name" type="VARCHAR(255)" >
<comment>Change column jira_labels in configuration to TEXT</comment> <constraints nullable="false" />
<modifyDataType tableName="configuration" columnName="jira_labels" </column>
newDataType="TEXT" /> <column name="cron_expression" type="VARCHAR(255)" >
</changeSet> <constraints nullable="false" />
</column>
<changeSet id="change-column-jira_labels-in-configuration-to-text-in-mysql" <column name="job_class_name" type="INTEGER" >
author="mrego" dbms="mysql"> <constraints nullable="false" />
<comment>Change column jira_labels in configuration to TEXT in MySQL</comment> </column>
<sql>ALTER TABLE configuration MODIFY jira_labels TEXT</sql> <column name="connector_name" type="VARCHAR(255)" />
<column name="schedule" type="BOOLEAN" />
</createTable>
</changeSet> </changeSet>
<changeSet id="add-projects_filter_period_since-column-to-user_table" <changeSet id="add-projects_filter_period_since-column-to-user_table"

View file

@ -90,6 +90,12 @@
<value> <value>
org/libreplan/business/expensesheets/entities/ExpenseSheets.hbm.xml org/libreplan/business/expensesheets/entities/ExpenseSheets.hbm.xml
</value> </value>
<value>
org/libreplan/business/common/entities/Connector.hbm.xml
</value>
<value>
org/libreplan/business/common/entities/JobSchedulerConfiguration.hbm.xml
</value>
</list> </list>
</property> </property>
<property name="eventListeners"> <property name="eventListeners">

View file

@ -120,18 +120,6 @@
</set> </set>
</component> </component>
<!-- Jira configuration-->
<component name="jiraConfiguration" class="org.libreplan.business.common.entities.JiraConfiguration">
<property name="jiraActivated" column="jira_activated" not-null="true"/>
<property name="jiraUrl" column="jira_url"/>
<property name="jiraLabels" column="jira_labels" type="text"/>
<property name="jiraUserId" column="jira_user_id"/>
<property name="jiraPassword" column="jira_password"/>
<many-to-one name="jiraConnectorTypeOfWorkHours" cascade="none"
column="jira_connector_type_of_work_hours" />
</component>
</class> </class>
</hibernate-mapping> </hibernate-mapping>

View file

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping default-access="field"
package="org.libreplan.business.common.entities">
<class name="Connector" table="connector">
<id name="id" column="id" type="long" access="property">
<generator class="hilo">
<param name="max_lo">100</param>
</generator>
</id>
<version name="version" access="property" type="long" />
<property name="name" column="name" not-null="true" />
<list name="properties" table="connector_property" lazy="false">
<key column="connector_id" />
<list-index column="connector_property_position" />
<composite-element class="ConnectorProperty">
<property name="key" column="key"
not-null="true" />
<property name="value" column="value" />
</composite-element>
</list>
</class>
</hibernate-mapping>

View file

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping default-access="field"
package="org.libreplan.business.common.entities">
<class name="JobSchedulerConfiguration" table="job_scheduler_configuration">
<id name="id" column="id" type="long" access="property">
<generator class="hilo">
<param name="max_lo">100</param>
</generator>
</id>
<version name="version" access="property" type="long" />
<property name="jobGroup" column="job_group" not-null="true" />
<property name="jobName" column="job_name" not-null="true" />
<property name="cronExpression" column="cron_expression" not-null="true" />
<property name="jobClassName" access="field" column="job_class_name" not-null="true">
<type name="org.hibernate.type.EnumType">
<param name="enumClass">org.libreplan.business.common.entities.JobClassNameEnum</param>
</type>
</property>
<property name="connectorName" column="connector_name" />
<property name="schedule" column="schedule" />
</class>
</hibernate-mapping>

View file

@ -126,9 +126,6 @@
</type> </type>
</property> </property>
<!-- extra column to hold imported label -->
<property name="importedLabel" column="imported_label"/>
<!-- Not indexed --> <!-- Not indexed -->
<many-to-one name="customer" access="field" class="org.libreplan.business.externalcompanies.entities.ExternalCompany"/> <many-to-one name="customer" access="field" class="org.libreplan.business.externalcompanies.entities.ExternalCompany"/>
@ -300,4 +297,20 @@
<property name="totalIndirectExpenses" access="field" column="total_indirect_expenses"/> <property name="totalIndirectExpenses" access="field" column="total_indirect_expenses"/>
</class> </class>
<class name="OrderSyncInfo" table="order_sync_info">
<id name="id" access="property" type="long">
<generator class="hilo" >
<param name="max_lo">100</param>
</generator>
</id>
<version name="version" access="property" type="long" />
<property name="lastSyncDate" column="last_sync_date" access="field" not-null="true"/>
<property name="key" access="field" not-null="true"/>
<property name="connectorName" column="connector_name" access="field" not-null="true"/>
<many-to-one name="order" class="Order">
<column name="order_element_id" not-null="true"/>
</many-to-one>
</class>
</hibernate-mapping> </hibernate-mapping>

View file

@ -330,6 +330,11 @@
<groupId>org.springframework</groupId> <groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId> <artifactId>spring-test</artifactId>
</dependency> </dependency>
<!-- QuartzJobBean in spring-context-support.jar -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
<!-- Spring security --> <!-- Spring security -->
<dependency> <dependency>
<groupId>org.springframework.security</groupId> <groupId>org.springframework.security</groupId>
@ -481,5 +486,10 @@
<groupId>commons-io</groupId> <groupId>commons-io</groupId>
<artifactId>commons-io</artifactId> <artifactId>commons-io</artifactId>
</dependency> </dependency>
<!-- Quartz framework -->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
</dependency>
</dependencies> </dependencies>
</project> </project>

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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 <m.ghiorghis@antoniusziekenhuis.nl>
*/
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<SynchronizationInfo> syncInfos = exportTimesheetsToTim
.exportTimesheets();
LOG.info("Export scuccessful: "
+ (syncInfos == null || syncInfos.isEmpty()));
} catch (ConnectorException e) {
LOG.error("Export timesheet to Tim failed", e);
}
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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 <m.ghiorghis@antoniusziekenhuis.nl>
*/
@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<SynchronizationInfo> 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<SynchronizationInfo> syncInfos = new ArrayList<SynchronizationInfo>();
List<OrderSyncInfo> 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<String, String> 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<WorkReportLine> 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<TimeRegistrationDTO> timeRegistrationDTOs = new ArrayList<TimeRegistrationDTO>();
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<Integer> 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<Void>() {
@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;
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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
* <code>NrDaysTimesheetToTim</code> specified in the Tim {@link Connector} and
* the current-date
*
* @author Miciele Ghiorghis <m.ghiorghis@antoniusziekenhuis.nl>
*/
public interface IExportTimesheetsToTim {
/**
* Exports time sheets of the specified <code>productCode</code> and
* <code>{@link Order}</code> 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<SynchronizationInfo> 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();
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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
* <code>NrDaysRosterFromTim</code> specified in Tim {@link Connector}.
*
* @author Miciele Ghiorghis <m.ghiorghis@antoniusziekenhuis.nl>
*/
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<SynchronizationInfo> importRosters() throws ConnectorException;
/**
* Returns synchronization info, success of fail info
*/
SynchronizationInfo getSynchronizationInfo();
}

View file

@ -23,8 +23,10 @@ import java.util.List;
import org.libreplan.business.advance.entities.AdvanceMeasurement; import org.libreplan.business.advance.entities.AdvanceMeasurement;
import org.libreplan.business.advance.entities.DirectAdvanceAssignment; 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.Order;
import org.libreplan.business.orders.entities.OrderElement; import org.libreplan.business.orders.entities.OrderElement;
import org.libreplan.business.orders.entities.OrderSyncInfo;
import org.libreplan.importers.jira.IssueDTO; import org.libreplan.importers.jira.IssueDTO;
/** /**
@ -48,8 +50,10 @@ public interface IJiraOrderElementSynchronizer {
* https://jira.atlassian.com/browse/JRA-29409 * https://jira.atlassian.com/browse/JRA-29409
* *
* @return A list of labels * @return A list of labels
* @throws ConnectorException
* if connector not found
*/ */
List<String> getAllJiraLabels(); List<String> getAllJiraLabels() throws ConnectorException;
/** /**
* Get all jira issues based on the specified <code>label</code> parameter * Get all jira issues based on the specified <code>label</code> parameter
@ -59,8 +63,10 @@ public interface IJiraOrderElementSynchronizer {
* search criteria for jira issues * search criteria for jira issues
* *
* @return list of jira issues * @return list of jira issues
* @throws ConnectorException
* if connector not found or contains invalid connection values
*/ */
List<IssueDTO> getJiraIssues(String label); List<IssueDTO> getJiraIssues(String label) throws ConnectorException;
/** /**
* Synchronizes the list of {@link OrderElement}s, * Synchronizes the list of {@link OrderElement}s,
@ -82,9 +88,42 @@ public interface IJiraOrderElementSynchronizer {
*/ */
void syncOrderElementsWithJiraIssues(List<IssueDTO> issues, Order order); void syncOrderElementsWithJiraIssues(List<IssueDTO> 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 * 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<SynchronizationInfo> syncOrderElementsWithJiraIssues() throws ConnectorException;
} }

View file

@ -21,6 +21,7 @@ package org.libreplan.importers;
import java.util.List; import java.util.List;
import org.libreplan.business.common.entities.ConnectorException;
import org.libreplan.business.orders.entities.Order; import org.libreplan.business.orders.entities.Order;
import org.libreplan.business.workreports.entities.WorkReportType; import org.libreplan.business.workreports.entities.WorkReportType;
import org.libreplan.importers.jira.IssueDTO; import org.libreplan.importers.jira.IssueDTO;
@ -42,7 +43,6 @@ public interface IJiraTimesheetSynchronizer {
/** /**
* Synchronize jira timesheet with the specified jira <code>issues</code> . * Synchronize jira timesheet with the specified jira <code>issues</code> .
* *
*
* Loop through all jira <code>issues</code> and check if timesheet is * Loop through all jira <code>issues</code> and check if timesheet is
* already exist for the specified issue item. If it is, update the * already exist for the specified issue item. If it is, update the
* timesheet with that issue item. If not create new one * timesheet with that issue item. If not create new one
@ -51,12 +51,14 @@ public interface IJiraTimesheetSynchronizer {
* the jira issues * the jira issues
* @param order * @param order
* an existing order * an existing order
* @throws ConnectorException
* if not valid connector or connector contains invalid values
*/ */
void syncJiraTimesheetWithJiraIssues(List<IssueDTO> issues, Order order); void syncJiraTimesheetWithJiraIssues(List<IssueDTO> issues, Order order) throws ConnectorException;
/** /**
* returns synchronization info, success or fail info * returns synchronization info, success or fail info
*/ */
JiraSyncInfo getJiraSyncInfo(); SynchronizationInfo getSynchronizationInfo();
} }

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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.
*
* <ul>
* <li>Schedule job:create job {@link JobDetailBean} and cron-trigger
* {@link CronTriggerBean}, associated the trigger with the job and add it to
* the scheduler.
* <li>
* <li>Delete job: search the job in the scheduler and if found
* unschedule(delete) the job</li>
* </ul>
*
* @author Miciele Ghiorghis <m.ghiorghis@antoniusziekenhuis.nl>
*/
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
* <code>{@link JobSchedulerConfiguration}</code> and (un)schedule it
* accordingly
*
* In the specified <code>{@link JobSchedulerConfiguration}</code>
*
* <ul>
* <li><code>{@link JobSchedulerConfiguration#getConnectorName()}</code>
* check if job has a connector and the connector is activated</li>
* <li><code>{@link JobSchedulerConfiguration#isSchedule()}</code> if true
* the job would be scheduled, if not job deleted</li>
* </ul>
*
* @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
* <code>{@link JobSchedulerConfiguration}</code>, 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);
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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 <m.ghiorghis@antoniusziekenhuis.nl>
*/
@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<SynchronizationInfo> 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<String, String> 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<SynchronizationInfo> syncInfos = new ArrayList<SynchronizationInfo>();
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<Void>() {
@Override
public Void execute() {
List<RosterException> 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 <code>rosterResponseDTO</code> and creates
* {@link RosterException}s and link them to the <code>worker</code>
*
* @param rosterResponseDTO
* the response
* @return a list of RosterExceptions
*/
private List<RosterException> getRosterExceptions(
RosterResponseDTO rosterResponseDTO, int productivityFactor) {
Map<String, List<RosterDTO>> map = getRosterExceptionPerWorker(rosterResponseDTO);
List<RosterException> rosterExceptions = new ArrayList<RosterException>();
for (Map.Entry<String, List<RosterDTO>> 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<RosterDTO> list = entry.getValue();
Collections.sort(list, new Comparator<RosterDTO>() {
@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
* <code>personsNetwork-name</name> as key
* and list of <code>roster-exception</code> as value
*
* @param rosterResponseDTO
* the response
* @return person-roster exception map
*/
private Map<String, List<RosterDTO>> getRosterExceptionPerWorker(
RosterResponseDTO rosterResponseDTO) {
Map<String, List<RosterDTO>> rosterMap = new HashMap<String, List<RosterDTO>>();
List<RosterDTO> 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<RosterDTO>());
}
rosterMap.get(personsNetWorkName).add(rosterDTO);
}
}
return rosterMap;
}
/**
* updates the workers calendar exception
*
* @param rosterExceptions
* list of roster exceptions
*/
private void updateCalendarException(List<RosterException> rosterExceptions) {
for (RosterException rosterException : rosterExceptions) {
List<RosterExceptionItem> items = rosterException
.getRosterExceptionItems();
for (RosterExceptionItem item : items) {
updateCalendarExceptionPerWorker(rosterException.getWorker(),
item.getDate(), item.getExceptionType(),
item.getEffortDuration());
}
}
}
/**
* updates the calendar exception of the specified
* <code>{@link Worker}</code> for the specified <code>date</code>
*
* @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
* <code>name</code>
*
* If the specified parameter <code>name</code> contains the word
* {@link ImportRosterFromTim#HOLIDAY}, the
* <code>calendarExceptionType</code> assumed to be the
* {@link PredefinedCalendarExceptionTypes#RESOURCE_HOLIDAY}, otherwise it
* searches in {@link CalendarExceptionType} for unique
* <code>calendarExceptionType</code>
*
* @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<PeriodDTO> periodDTOs = new ArrayList<PeriodDTO>();
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<RosterDTO> dataDTO = new DataDTO<RosterDTO>();
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<PersonDTO> createEmptyPerson() {
List<PersonDTO> personDTOs = new ArrayList<PersonDTO>();
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<RosterCategoryDTO> createEmptyRosterCategory() {
List<RosterCategoryDTO> rosterCategorieDTOs = new ArrayList<RosterCategoryDTO>();
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;
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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 <m.ghiorghis@antoniusziekenhuis.nl>
*/
@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<SynchronizationInfo> syncInfos = importRosterFromTim
.importRosters();
LOG.info("Import scuccessful: "
+ (syncInfos == null || syncInfos.isEmpty()));
} catch (ConnectorException e) {
LOG.error("Import roster from Tim failed", e);
}
}
}

View file

@ -19,6 +19,8 @@
package org.libreplan.importers; package org.libreplan.importers;
import static org.libreplan.web.I18nHelper._;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.net.MalformedURLException; import java.net.MalformedURLException;
@ -28,8 +30,11 @@ import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Map;
import org.apache.commons.lang.StringUtils; 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.joda.time.LocalDate;
import org.libreplan.business.advance.bootstrap.PredefinedAdvancedTypes; import org.libreplan.business.advance.bootstrap.PredefinedAdvancedTypes;
import org.libreplan.business.advance.entities.AdvanceMeasurement; 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.entities.DirectAdvanceAssignment;
import org.libreplan.business.advance.exceptions.DuplicateAdvanceAssignmentForOrderElementException; import org.libreplan.business.advance.exceptions.DuplicateAdvanceAssignmentForOrderElementException;
import org.libreplan.business.advance.exceptions.DuplicateValueTrueReportGlobalAdvanceException; import org.libreplan.business.advance.exceptions.DuplicateValueTrueReportGlobalAdvanceException;
import org.libreplan.business.common.daos.IConfigurationDAO; import org.libreplan.business.common.IAdHocTransactionService;
import org.libreplan.business.common.entities.JiraConfiguration; 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.HoursGroup;
import org.libreplan.business.orders.entities.Order; import org.libreplan.business.orders.entities.Order;
import org.libreplan.business.orders.entities.OrderElement; import org.libreplan.business.orders.entities.OrderElement;
import org.libreplan.business.orders.entities.OrderLine; import org.libreplan.business.orders.entities.OrderLine;
import org.libreplan.business.orders.entities.OrderSyncInfo;
import org.libreplan.business.workingday.EffortDuration; import org.libreplan.business.workingday.EffortDuration;
import org.libreplan.importers.jira.IssueDTO; import org.libreplan.importers.jira.IssueDTO;
import org.libreplan.importers.jira.StatusDTO; import org.libreplan.importers.jira.StatusDTO;
import org.libreplan.importers.jira.TimeTrackingDTO; import org.libreplan.importers.jira.TimeTrackingDTO;
import org.libreplan.importers.jira.WorkLogDTO; import org.libreplan.importers.jira.WorkLogDTO;
import org.libreplan.importers.jira.WorkLogItemDTO; import org.libreplan.importers.jira.WorkLogItemDTO;
import org.libreplan.web.orders.IOrderModel;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.Scope;
@ -64,17 +77,36 @@ import org.springframework.transaction.annotation.Transactional;
@Scope(BeanDefinition.SCOPE_PROTOTYPE) @Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class JiraOrderElementSynchronizer implements IJiraOrderElementSynchronizer { public class JiraOrderElementSynchronizer implements IJiraOrderElementSynchronizer {
private static final Log LOG = LogFactory
.getLog(JiraOrderElementSynchronizer.class);
private SynchronizationInfo synchronizationInfo;
@Autowired @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 @Override
@Transactional(readOnly = true) @Transactional(readOnly = true)
public List<String> getAllJiraLabels() { public List<String> getAllJiraLabels() throws ConnectorException {
String jiraLabels = configurationDAO.getConfiguration() Connector connector = getJiraConnector();
.getJiraConfiguration().getJiraLabels(); if (connector == null) {
throw new ConnectorException(_("JIRA connector not found"));
}
String jiraLabels = connector.getPropertiesAsMap().get(
PredefinedConnectorProperties.JIRA_LABELS);
String labels; String labels;
try { try {
@ -88,13 +120,39 @@ public class JiraOrderElementSynchronizer implements IJiraOrderElementSynchroniz
@Override @Override
@Transactional(readOnly = true) @Transactional(readOnly = true)
public List<IssueDTO> getJiraIssues(String label) { public List<IssueDTO> getJiraIssues(String label) throws ConnectorException {
JiraConfiguration jiraConfiguration = configurationDAO
.getConfiguration().getJiraConfiguration();
String url = jiraConfiguration.getJiraUrl(); Connector connector = getJiraConnector();
String username = jiraConfiguration.getJiraUserId(); if (connector == null) {
String password = jiraConfiguration.getJiraPassword(); 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 <code>label</code>
*
* @param label
* the search criteria
* @param connector
* where to read the configuration parameters
* @return a list of {@link IssueDTO}
*/
private List<IssueDTO> getJiraIssues(String label, Connector connector) {
Map<String, String> 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 path = JiraRESTClient.PATH_SEARCH;
String query = "labels=" + label; String query = "labels=" + label;
@ -109,17 +167,20 @@ public class JiraOrderElementSynchronizer implements IJiraOrderElementSynchroniz
@Transactional(readOnly = true) @Transactional(readOnly = true)
public void syncOrderElementsWithJiraIssues(List<IssueDTO> issues, Order order) { public void syncOrderElementsWithJiraIssues(List<IssueDTO> issues, Order order) {
jiraSyncInfo = new JiraSyncInfo(); synchronizationInfo = new SynchronizationInfo(_(
"Synchronization order {0}", order.getName()));
for (IssueDTO issue : issues) { for (IssueDTO issue : issues) {
String code = JiraConfiguration.CODE_PREFIX + order.getCode() + "-" String code = PredefinedConnectorProperties.JIRA_CODE_PREFIX
+ order.getCode() + "-"
+ issue.getKey(); + issue.getKey();
String name = issue.getFields().getSummary(); String name = issue.getFields().getSummary();
OrderLine orderLine = syncOrderLine(order, code, name); OrderLine orderLine = syncOrderLine(order, code, name);
if (orderLine == null) { if (orderLine == null) {
jiraSyncInfo.addSyncFailedReason("Order-element for '" synchronizationInfo.addFailedReason(_(
+ issue.getKey() + "' issue not found"); "Order-element for \"{0}\" issue not found",
issue.getKey()));
continue; continue;
} }
@ -129,8 +190,9 @@ public class JiraOrderElementSynchronizer implements IJiraOrderElementSynchroniz
.getTimetracking(), loggedHours); .getTimetracking(), loggedHours);
if (estimatedHours.isZero()) { if (estimatedHours.isZero()) {
jiraSyncInfo.addSyncFailedReason("Estimated time for '" synchronizationInfo.addFailedReason(_(
+ issue.getKey() + "' issue is 0"); "Estimated time for \"{0}\" issue is 0",
issue.getKey()));
continue; continue;
} }
@ -217,15 +279,16 @@ public class JiraOrderElementSynchronizer implements IJiraOrderElementSynchroniz
WorkLogDTO workLog = issue.getFields().getWorklog(); WorkLogDTO workLog = issue.getFields().getWorklog();
if (workLog == null) { if (workLog == null) {
jiraSyncInfo.addSyncFailedReason("No worklogs found for '" synchronizationInfo.addFailedReason(_(
+ issue.getKey() + "' issue"); "No worklogs found for \"{0}\" issue", issue.getKey()));
return; return;
} }
List<WorkLogItemDTO> workLogItems = workLog.getWorklogs(); List<WorkLogItemDTO> workLogItems = workLog.getWorklogs();
if (workLogItems.isEmpty()) { if (workLogItems.isEmpty()) {
jiraSyncInfo.addSyncFailedReason("No worklog items found for '" synchronizationInfo.addFailedReason(_(
+ issue.getKey() + "' issue"); "No worklog items found for \"{0}\" issue",
issue.getKey()));
return; return;
} }
@ -334,9 +397,10 @@ public class JiraOrderElementSynchronizer implements IJiraOrderElementSynchroniz
} catch (DuplicateAdvanceAssignmentForOrderElementException e) { } catch (DuplicateAdvanceAssignmentForOrderElementException e) {
// This could happen if a parent or child of the current // This could happen if a parent or child of the current
// OrderElement has an advance of type PERCENTAGE // OrderElement has an advance of type PERCENTAGE
jiraSyncInfo synchronizationInfo
.addSyncFailedReason("Duplicate value AdvanceAssignment for order element of '" .addFailedReason(_(
+ orderElement.getCode() + "'"); "Duplicate value AdvanceAssignment for order element of \"{0}\"",
orderElement.getCode()));
return; return;
} }
} }
@ -393,8 +457,109 @@ public class JiraOrderElementSynchronizer implements IJiraOrderElementSynchroniz
} }
@Override @Override
public JiraSyncInfo getJiraSyncInfo() { public SynchronizationInfo getSynchronizationInfo() {
return jiraSyncInfo; 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<Void>() {
@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<SynchronizationInfo> 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<OrderSyncInfo> orderSyncInfos = orderSyncInfoDAO
.findByConnectorName(PredefinedConnectors.JIRA.getName());
synchronizationInfo = new SynchronizationInfo(_("Synchronization"));
List<SynchronizationInfo> syncInfos = new ArrayList<SynchronizationInfo>();
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<IssueDTO> 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;
}
} }

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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 <m.ghiorghis@antoniusziekenhuis.nl>
*/
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<SynchronizationInfo> syncInfos = jiraOrderElementSynchronizer
.syncOrderElementsWithJiraIssues();
LOG.info("Synchronization scuccessful: "
+ (syncInfos == null || syncInfos.isEmpty()));
} catch (ConnectorException e) {
LOG.error("Synchronize order elements failed", e);
}
}
}

View file

@ -19,24 +19,31 @@
package org.libreplan.importers; package org.libreplan.importers;
import static org.libreplan.web.I18nHelper._;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.hibernate.NonUniqueResultException; import org.hibernate.NonUniqueResultException;
import org.libreplan.business.common.IAdHocTransactionService; import org.libreplan.business.common.IAdHocTransactionService;
import org.libreplan.business.common.daos.IConfigurationDAO; import org.libreplan.business.common.daos.IConnectorDAO;
import org.libreplan.business.common.entities.JiraConfiguration; 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.common.exceptions.InstanceNotFoundException;
import org.libreplan.business.costcategories.daos.ITypeOfWorkHoursDAO; import org.libreplan.business.costcategories.daos.ITypeOfWorkHoursDAO;
import org.libreplan.business.costcategories.entities.TypeOfWorkHours; 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.Order;
import org.libreplan.business.orders.entities.OrderElement; 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.daos.IWorkerDAO;
import org.libreplan.business.resources.entities.Resource; import org.libreplan.business.resources.entities.Resource;
import org.libreplan.business.resources.entities.Worker; import org.libreplan.business.resources.entities.Worker;
import org.libreplan.business.workingday.EffortDuration; import org.libreplan.business.workingday.EffortDuration;
import org.libreplan.business.workreports.daos.IWorkReportDAO; 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.daos.IWorkReportTypeDAO;
import org.libreplan.business.workreports.entities.PredefinedWorkReportTypes; import org.libreplan.business.workreports.entities.PredefinedWorkReportTypes;
import org.libreplan.business.workreports.entities.WorkReport; import org.libreplan.business.workreports.entities.WorkReport;
@ -63,7 +70,7 @@ import org.springframework.transaction.annotation.Transactional;
@Scope(BeanDefinition.SCOPE_PROTOTYPE) @Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class JiraTimesheetSynchronizer implements IJiraTimesheetSynchronizer { public class JiraTimesheetSynchronizer implements IJiraTimesheetSynchronizer {
private JiraSyncInfo jiraSyncInfo; private SynchronizationInfo synchronizationInfo;
private List<Worker> workers; private List<Worker> workers;
@ -80,9 +87,6 @@ public class JiraTimesheetSynchronizer implements IJiraTimesheetSynchronizer {
@Autowired @Autowired
private IWorkReportDAO workReportDAO; private IWorkReportDAO workReportDAO;
@Autowired
private IWorkReportLineDAO workReportLineDAO;
@Autowired @Autowired
private IWorkReportModel workReportModel; private IWorkReportModel workReportModel;
@ -90,50 +94,69 @@ public class JiraTimesheetSynchronizer implements IJiraTimesheetSynchronizer {
private ITypeOfWorkHoursDAO typeOfWorkHoursDAO; private ITypeOfWorkHoursDAO typeOfWorkHoursDAO;
@Autowired @Autowired
private IConfigurationDAO configurationDAO; private IConnectorDAO connectorDAO;
@Autowired
private IOrderSyncInfoDAO orderSyncInfoDAO;
@Autowired @Autowired
private IAdHocTransactionService adHocTransactionService; private IAdHocTransactionService adHocTransactionService;
@Override @Override
@Transactional @Transactional
public void syncJiraTimesheetWithJiraIssues(List<IssueDTO> issues, Order order) { public void syncJiraTimesheetWithJiraIssues(List<IssueDTO> issues, Order order) throws ConnectorException {
jiraSyncInfo = new JiraSyncInfo(); synchronizationInfo = new SynchronizationInfo(_("Synchronization"));
workReportType = getJiraTimesheetsWorkReportType(); workReportType = getJiraTimesheetsWorkReportType();
typeOfWorkHours = getTypeOfWorkHours(); typeOfWorkHours = getTypeOfWorkHours();
workers = getWorkers(); workers = getWorkers();
if (workers == null && workers.isEmpty()) { if (workers == null && workers.isEmpty()) {
jiraSyncInfo.addSyncFailedReason("No workers found"); synchronizationInfo.addFailedReason(_("No workers found"));
return; 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); WorkReport workReport = updateOrCreateWorkReport(code);
for (IssueDTO issue : issues) { for (IssueDTO issue : issues) {
WorkLogDTO worklog = issue.getFields().getWorklog(); WorkLogDTO worklog = issue.getFields().getWorklog();
if (worklog == null) { if (worklog == null) {
jiraSyncInfo.addSyncFailedReason("No worklogs found for '" synchronizationInfo.addFailedReason(_(
+ issue.getKey() + "'"); "No worklogs found for \"{0}\" key", issue.getKey()));
} else { } else {
List<WorkLogItemDTO> workLogItems = worklog.getWorklogs(); List<WorkLogItemDTO> workLogItems = worklog.getWorklogs();
if (workLogItems == null || workLogItems.isEmpty()) { if (workLogItems == null || workLogItems.isEmpty()) {
jiraSyncInfo synchronizationInfo.addFailedReason(_(
.addSyncFailedReason("No worklog items found for '" "No worklog items found for \"{0}\" issue",
+ issue.getKey() + "' issue"); issue.getKey()));
} else { } else {
String codeOrderElement = JiraConfiguration.CODE_PREFIX String codeOrderElement = PredefinedConnectorProperties.JIRA_CODE_PREFIX
+ order.getCode() + "-" + issue.getKey(); + order.getCode() + "-" + issue.getKey();
OrderElement orderElement = order.getOrderElement(codeOrderElement); OrderElement orderElement = order.getOrderElement(codeOrderElement);
if (orderElement == null) { if (orderElement == null) {
jiraSyncInfo.addSyncFailedReason("Order element(" synchronizationInfo.addFailedReason(_(
+ code + ") not found"); "Order element \"{0}\" not found", code));
} else { } else {
updateOrCreateWorkReportLineAndAddToWorkReport(workReport, orderElement, updateOrCreateWorkReportLineAndAddToWorkReport(workReport, orderElement,
workLogItems); workLogItems);
@ -291,10 +314,31 @@ public class JiraTimesheetSynchronizer implements IJiraTimesheetSynchronizer {
* Returns {@link TypeOfWorkHours} configured for JIRA connector * Returns {@link TypeOfWorkHours} configured for JIRA connector
* *
* @return TypeOfWorkHours for JIRA connector * @return TypeOfWorkHours for JIRA connector
* @throws ConnectorException
*/ */
private TypeOfWorkHours getTypeOfWorkHours() { private TypeOfWorkHours getTypeOfWorkHours() throws ConnectorException {
return configurationDAO.getConfiguration().getJiraConfiguration() Connector connector = connectorDAO
.getJiraConnectorTypeOfWorkHours(); .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; return worker;
} }
} }
jiraSyncInfo.addSyncFailedReason("Worker('" + nif + "') not found"); synchronizationInfo.addFailedReason(_("Worker \"{0}\" not found", nif));
return null; return null;
} }
@Override @Override
public JiraSyncInfo getJiraSyncInfo() { public SynchronizationInfo getSynchronizationInfo() {
return jiraSyncInfo; return synchronizationInfo;
} }
} }

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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 <code>RosterException<code>
*
* @author Miciele Ghiorghis <m.ghiorghis@antoniusziekenhuis.nl>
*/
public class RosterException {
private Worker worker;
private int productivityFactor;
List<RosterExceptionItem> rosterExceptionItems = new ArrayList<RosterExceptionItem>();
public RosterException(Worker worker, int productivityFactor) {
this.worker = worker;
this.productivityFactor = productivityFactor;
}
/**
* Reads the rosters and add the exceptions to
* <code>rosterExceptionItems</code>
*
* @param rosterDTOs
* list of rosterDTO
*/
public void addRosterExceptions(List<RosterDTO> rosterDTOs) {
Map<LocalDate, List<RosterDTO>> mapDateRosterDTO = new TreeMap<LocalDate, List<RosterDTO>>();
for (RosterDTO rosterDTO : rosterDTOs) {
if (!mapDateRosterDTO.containsKey(rosterDTO.getDate())) {
mapDateRosterDTO.put(rosterDTO.getDate(), new ArrayList<RosterDTO>());
}
mapDateRosterDTO.get(rosterDTO.getDate()).add(rosterDTO);
}
for (Map.Entry<LocalDate, List<RosterDTO>> entry : mapDateRosterDTO
.entrySet()) {
RosterExceptionItem item = new RosterExceptionItem(entry.getKey());
updateExceptionTypeAndEffort(item, entry.getValue());
rosterExceptionItems.add(item);
}
}
/**
* updates the <code>exceptionType</code> and <code>effortDuration</code>
*
* In Tim you can divide your exception day in different
* <code>exceptionType</code>, for example on Monday:
* <ul>
* <li>4 hours RESOURCE_HOLIDAY</li>
* <li>2 hours STRIKE</li>
* <li>2 hours BANK_HOLIDAY</li>
* </ul>
*
* But Libreplan allows only one <code>exceptionType</code> per day.
*
* In order to store different <code>exceptionTypes</code> per day as one
* <code>exceptionType</code>, this method gets the
* <code>exceptionType</code> from the <code>rosterDTO</code> 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<RosterDTO> 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<RosterExceptionItem> 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;
}
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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 <m.ghiorghis@antoniusziekenhuis.nl>
*/
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;
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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 <m.ghiorghis@antoniusziekenhuis.nl>
*/
@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<JobSchedulerConfiguration> 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 <code>{@link Connector}</code> 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
* <code>triggerName</code> and <code>triggerGroup</code>
*
* @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<JobExecutionContext> 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 <code>{@link JobSchedulerConfiguration}</code>. 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
* <code>{@link JobSchedulerConfiguration}</code>
*
* @param jobSchedulerConfiguration
* configuration to create <code>CronTriggerBean</>
* @return the created <code>CronTriggerBean</code> 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
* <code>{@link JobSchedulerConfiguration}</code>
*
* @param jobSchedulerConfiguration
* configuration to create <code>JobDetailBean</>
* @return the created <code>JobDetailBean</code> 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<String, Object> jobDataAsMap = new HashMap<String, Object>();
jobDataAsMap.put("applicationContext", applicationContext);
jobDetailBean.setJobDataAsMap(jobDataAsMap);
return jobDetailBean;
}
/**
* returns jobClass based on <code>jobClassName</code> 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
* <code>triggerName</code> and <code>tirggerGroup</code>
*
* @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;
}
}

View file

@ -24,38 +24,57 @@ import java.util.Collections;
import java.util.List; import java.util.List;
/** /**
* Keeps track the synchronization info. * Keeps track the success/failure of synchronization process
* *
* @author Miciele Ghiorghis <m.ghiorghis@antoniusziekenhuis.nl> * @author Miciele Ghiorghis <m.ghiorghis@antoniusziekenhuis.nl>
*/ */
public class JiraSyncInfo { public class SynchronizationInfo {
private List<String> syncFailedReasons = new ArrayList<String>();
/** /**
* Add the specified <code>reason</code> to syncFailedReasons list * The action, a unique key for example synchronization, import or export
* * etc action
* @param reason
* reason why synchronizition failed
*/ */
public void addSyncFailedReason(String reason) { private String action;
syncFailedReasons.add(reason);
/**
* Holds failed reasons
*/
private List<String> failedReasons = new ArrayList<String>();
public SynchronizationInfo(String action) {
this.action = action;
} }
/** /**
* Is synchronization successful * Returns the action
*
* @return
*/ */
public boolean isSyncSuccessful() { public String getAction() {
return syncFailedReasons.isEmpty(); return action;
}
/**
* Adds the specified <code>reason</code> to <code>failedReasons<code> list
*
* @param reason
* reason why synchronization is failed
*/
public void addFailedReason(String reason) {
failedReasons.add(reason);
}
/**
* Is synchronization succeeded
*
* @return true if <code>failedReasons</code> is empty
*/
public boolean isSuccessful() {
return failedReasons.isEmpty();
} }
/** /**
* returns reasons why synchronization is failed * returns reasons why synchronization is failed
*/ */
public List<String> getSyncFailedReasons() { public List<String> getFailedReasons() {
return Collections.unmodifiableList(syncFailedReasons); return Collections.unmodifiableList(failedReasons);
} }
} }

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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 <m.ghiorghis@antoniusziekenhuis.nl>
*/
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 <T> 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 <code>message</code>
*
* @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
* <code>soapEnvelop</code>
*
* @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
* <code>soapEnvelop</code>
*
* @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 <code>clazz</code> to the specified
* <code>soapBody</code>
*
* @param clazz
* the object to be marshaled
* @param soapBody
* the SOAP body, result of marshal
* @throws JAXBException
* if marshaling failed
*/
private static <T> 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 <code>soapBody</code> to the specified
* <code>clazz</code>
*
* @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> T unmarshal(Class<T> 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, U> T sendRequestReceiveResponse(String url,
String userName, String password, U request, Class<T> 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 <code>username</code> and
* <code>password</code>
*
* @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 <code>file</code> 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;
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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 <m.ghiorghis@antoniusziekenhuis.nl>
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement
@XmlSeeAlso({ RosterDTO.class })
public class DataDTO<T> {
@XmlAnyElement
private T data;
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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 <m.ghiorghis@antoniusziekenhuis.nl>
*/
@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;
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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 <m.ghiorghis@antoniusziekenhuis.nl>
*/
@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;
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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 <m.ghiorghis@antoniusziekenhuis.nl>
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement
public class FilterDTO {
@XmlElement(name = "persoon")
private PersonDTO person;
@XmlElement(name = "periode")
private List<PeriodDTO> 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<PeriodDTO> getPeriods() {
return periods;
}
public void setPeriods(List<PeriodDTO> 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;
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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 <m.ghiorghis@antoniusziekenhuis.nl>
*/
@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;
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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 <m.ghiorghis@antoniusziekenhuis.nl>
*/
@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;
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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 <m.ghiorghis@antoniusziekenhuis.nl>
*/
@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;
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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 <m.ghiorghis@antoniusziekenhuis.nl>
*/
@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;
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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 <m.ghiorghis@antoniusziekenhuis.nl>
*/
@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;
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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 <m.ghiorghis@antoniusziekenhuis.nl>
*/
@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<PersonDTO> persons;
@XmlElement(name = "Roostercategorie")
private List<RosterCategoryDTO> 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<PeriodDTO> 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<PersonDTO> getPersons() {
return persons;
}
public void setPersons(List<PersonDTO> persons) {
this.persons = persons;
}
public List<RosterCategoryDTO> getRosterCategories() {
return rosterCategories;
}
public void setRosterCategories(List<RosterCategoryDTO> 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<PeriodDTO> getPeriods() {
return periods;
}
public void setPeriods(List<PeriodDTO> periods) {
this.periods = periods;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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 <m.ghiorghis@antoniusziekenhuis.nl>
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "export", namespace = "impexp.timn.aenova.nl")
public class RosterRequestDTO {
@XmlElement
private DataDTO<RosterDTO> data;
public DataDTO<RosterDTO> getData() {
return data;
}
public void setData(DataDTO<RosterDTO> data) {
this.data = data;
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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 <m.ghiorghis@antoniusziekenhuis.nl>
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "exportResponse", namespace = "impexp.timn.aenova.nl")
public class RosterResponseDTO {
@XmlElementWrapper(name = "return")
@XmlElement(name = "bezettingblok")
private List<RosterDTO> rosters;
public List<RosterDTO> getRosters() {
return rosters;
}
public void setRosters(List<RosterDTO> rosters) {
this.rosters = rosters;
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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 <code>DateTime</code> to
* string(tim-string-datetime) and vice versa
*
* @author Miciele Ghiorghis <m.ghiorghis@antoniusziekenhuis.nl>
*/
public class TimDateTimeAdapter extends XmlAdapter<String, DateTime> {
@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);
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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 <code>Double</code> to
* string(tim-string-double) and vice versa
*
* @author Miciele Ghiorghis <m.ghiorghis@antoniusziekenhuis.nl>
*/
public class TimDoubleAdapter extends XmlAdapter<String, Double> {
@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);
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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 <code>LocalDate</code> to
* string(tim-string-date) and vice versa
*
* @author Miciele Ghiorghis <m.ghiorghis@antoniusziekenhuis.nl>
*/
public class TimLocalDateAdapter extends XmlAdapter<String, LocalDate> {
@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();
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
package org.libreplan.importers.tim;
/**
* Class containing all constants for Tim-options.
*
* @author Miciele Ghiorghis <m.ghiorghis@antoniusziekenhuis.nl>
*/
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 = "~";
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
package org.libreplan.importers.tim;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import org.joda.time.LocalTime;
/**
* Adapter responsible for converting from <code>LocalTime</code> to
* string(tim-string-time) and vice versa
*
* @author Miciele Ghiorghis <m.ghiorghis@antoniusziekenhuis.nl>
*/
public class TimTimeAdapter extends XmlAdapter<String, LocalTime> {
@Override
public String marshal(LocalTime localTime) throws Exception {
return localTime.toString();
}
@Override
public LocalTime unmarshal(String localTimeStr) throws Exception {
return new LocalTime(localTimeStr);
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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 <m.ghiorghis@antoniusziekenhuis.nl>
*/
@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;
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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 <m.ghiorghis@antoniusziekenhuis.nl>
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "import", namespace = "impexp.timn.aenova.nl")
public class TimeRegistrationRequestDTO {
@XmlElementWrapper(name = "data")
@XmlElement(name = "tijdregistratie")
private List<TimeRegistrationDTO> timeRegistrations;
public List<TimeRegistrationDTO> getTimeRegistrations() {
return timeRegistrations;
}
public void setTimeRegistrations(List<TimeRegistrationDTO> timeRegistrations) {
this.timeRegistrations = timeRegistrations;
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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 <m.ghiorghis@antoniusziekenhuis.nl>
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "importResponse", namespace = "impexp.timn.aenova.nl")
public class TimeRegistrationResponseDTO {
@XmlElementWrapper(name = "return")
@XmlElement(name = "ref")
private List<Integer> ref;
public List<Integer> getRefs() {
return ref;
}
public void setRefs(List<Integer> ref) {
this.ref = ref;
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
/**
* An xmlAdapaters that will be applied within this package
*
* @author Miciele Ghiorghis <m.ghiorghis@antoniusziekenhuis.nl>
*/
@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;

View file

@ -30,6 +30,7 @@ import java.util.Comparator;
import java.util.ConcurrentModificationException; import java.util.ConcurrentModificationException;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import javax.ws.rs.core.MediaType; 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.apache.cxf.jaxrs.client.WebClient;
import org.libreplan.business.calendars.entities.BaseCalendar; import org.libreplan.business.calendars.entities.BaseCalendar;
import org.libreplan.business.common.entities.Configuration; 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.EntityNameEnum;
import org.libreplan.business.common.entities.EntitySequence; 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.LDAPConfiguration;
import org.libreplan.business.common.entities.PersonalTimesheetsPeriodicityEnum; 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.entities.ProgressType;
import org.libreplan.business.common.exceptions.ValidationException; import org.libreplan.business.common.exceptions.ValidationException;
import org.libreplan.business.costcategories.entities.TypeOfWorkHours; import org.libreplan.business.costcategories.entities.TypeOfWorkHours;
import org.libreplan.business.users.entities.UserRole; import org.libreplan.business.users.entities.UserRole;
import org.libreplan.importers.JiraRESTClient; import org.libreplan.importers.JiraRESTClient;
import org.libreplan.importers.TimSoapClient;
import org.libreplan.web.common.components.bandboxsearch.BandboxSearch; import org.libreplan.web.common.components.bandboxsearch.BandboxSearch;
import org.springframework.ldap.core.DistinguishedName; import org.springframework.ldap.core.DistinguishedName;
import org.springframework.ldap.core.LdapTemplate; import org.springframework.ldap.core.LdapTemplate;
@ -83,6 +88,7 @@ import org.zkoss.zul.Rows;
import org.zkoss.zul.SimpleListModel; import org.zkoss.zul.SimpleListModel;
import org.zkoss.zul.Textbox; import org.zkoss.zul.Textbox;
import org.zkoss.zul.api.Window; import org.zkoss.zul.api.Window;
import org.zkoss.zul.impl.InputElement;
/** /**
* Controller for {@link Configuration} entity. * Controller for {@link Configuration} entity.
@ -125,6 +131,12 @@ public class ConfigurationController extends GenericForwardComposer {
private Radiogroup strategy; private Radiogroup strategy;
private Combobox connectorCombo;
private Grid connectorPropertriesGrid;
private Connector selectedConnector;
@Override @Override
public void doAfterCompose(Component comp) throws Exception { public void doAfterCompose(Component comp) throws Exception {
super.doAfterCompose(comp); super.doAfterCompose(comp);
@ -215,8 +227,16 @@ public class ConfigurationController extends GenericForwardComposer {
configurationModel.confirm(); configurationModel.confirm();
configurationModel.init(); configurationModel.init();
messages.showMessage(Level.INFO, _("Changes saved")); 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(); reloadWindow();
reloadEntitySequences(); reloadEntitySequences();
reloadConnectors();
} catch (ValidationException e) { } catch (ValidationException e) {
messages.showInvalidValues(e); messages.showInvalidValues(e);
} catch (ConcurrentModificationException e) { } catch (ConcurrentModificationException e) {
@ -224,6 +244,7 @@ public class ConfigurationController extends GenericForwardComposer {
configurationModel.init(); configurationModel.init();
reloadWindow(); reloadWindow();
reloadEntitySequences(); reloadEntitySequences();
reloadConnectors();
} }
} }
} }
@ -233,6 +254,7 @@ public class ConfigurationController extends GenericForwardComposer {
messages.showMessage(Level.INFO, _("Changes have been canceled")); messages.showMessage(Level.INFO, _("Changes have been canceled"));
reloadWindow(); reloadWindow();
reloadEntitySequences(); reloadEntitySequences();
reloadConnectors();
} }
public void testLDAPConnection() { 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 Map<String, String> properties = selectedConnector.getPropertiesAsMap();
.getJiraConfiguration(); 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 { try {
WebClient client = WebClient.create(jiraConfiguration.getJiraUrl()); WebClient client = WebClient.create(url);
client.path(JiraRESTClient.PATH_AUTH_SESSION).accept( client.path(JiraRESTClient.PATH_AUTH_SESSION).accept(
MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML); MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML);
org.libreplan.ws.common.impl.Util.addAuthorizationHeader(client, org.libreplan.ws.common.impl.Util.addAuthorizationHeader(client,
jiraConfiguration.getJiraUserId(), username, password);
jiraConfiguration.getJiraPassword());
Response response = client.get(); Response response = client.get();
@ -290,17 +361,16 @@ public class ConfigurationController extends GenericForwardComposer {
messages.showMessage(Level.INFO, messages.showMessage(Level.INFO,
_("JIRA connection was successful")); _("JIRA connection was successful"));
} else { } else {
LOG.info("Status code: " + response.getStatus()); LOG.error("Status code: " + response.getStatus());
messages.showMessage(Level.ERROR, messages.showMessage(Level.ERROR,
_("Cannot connect to JIRA server")); _("Cannot connect to JIRA server"));
} }
} catch (Exception e) { } catch (Exception e) {
LOG.info(e); LOG.error(e);
messages.showMessage(Level.ERROR, messages.showMessage(Level.ERROR,
_("Cannot connect to JIRA server")); _("Cannot connect to JIRA server"));
} }
} }
private boolean checkValidEntitySequenceRows() { private boolean checkValidEntitySequenceRows() {
@ -346,6 +416,14 @@ public class ConfigurationController extends GenericForwardComposer {
entitySequencesGrid.invalidate(); entitySequencesGrid.invalidate();
} }
private void reloadConnectors() {
selectedConnector = configurationModel
.getConnectorByName(selectedConnector != null ? selectedConnector
.getName() : null);
Util.reloadBindings(connectorCombo);
Util.reloadBindings(connectorPropertriesGrid);
}
public String getCompanyCode() { public String getCompanyCode() {
return configurationModel.getCompanyCode(); return configurationModel.getCompanyCode();
} }
@ -818,14 +896,6 @@ public class ConfigurationController extends GenericForwardComposer {
configurationModel.setLdapConfiguration(ldapConfiguration); configurationModel.setLdapConfiguration(ldapConfiguration);
} }
public JiraConfiguration getJiraConfiguration() {
return configurationModel.getJiraConfiguration();
}
public void setJiraConfiguration(JiraConfiguration jiraConfiguration) {
configurationModel.setJiraConfiguration(jiraConfiguration);
}
public RowRenderer getAllUserRolesRenderer() { public RowRenderer getAllUserRolesRenderer() {
return new RowRenderer() { return new RowRenderer() {
@Override @Override
@ -976,12 +1046,105 @@ public class ConfigurationController extends GenericForwardComposer {
configurationModel.setSecondsPlanningWarning(secondsPlanningWarning); configurationModel.setSecondsPlanningWarning(secondsPlanningWarning);
} }
public TypeOfWorkHours getJiraConnectorTypeOfWorkHours() { public List<Connector> getConnectors() {
return configurationModel.getJiraConnectorTypeOfWorkHours(); return configurationModel.getConnectors();
} }
public void setJiraConnectorTypeOfWorkHours(TypeOfWorkHours typeOfWorkHours) { public Connector getSelectedConnector() {
configurationModel.setJiraConnectorTypeOfWorkHours(typeOfWorkHours); return selectedConnector;
}
public void setSelectedConnector(Connector connector) {
selectedConnector = connector;
Util.reloadBindings(connectorPropertriesGrid);
}
public List<ConnectorProperty> 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<String>() {
@Override
public String get() {
return property.getValue();
}
}, new Util.Setter<String>() {
@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;
}
}
};
} }
} }

View file

@ -25,6 +25,7 @@ import static org.libreplan.web.I18nHelper._;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.Currency; import java.util.Currency;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; 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.daos.IBaseCalendarDAO;
import org.libreplan.business.calendars.entities.BaseCalendar; import org.libreplan.business.calendars.entities.BaseCalendar;
import org.libreplan.business.common.daos.IConfigurationDAO; 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.IEntitySequenceDAO;
import org.libreplan.business.common.entities.Configuration; 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.EntityNameEnum;
import org.libreplan.business.common.entities.EntitySequence; 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.LDAPConfiguration;
import org.libreplan.business.common.entities.PersonalTimesheetsPeriodicityEnum; import org.libreplan.business.common.entities.PersonalTimesheetsPeriodicityEnum;
import org.libreplan.business.common.entities.ProgressType; import org.libreplan.business.common.entities.ProgressType;
@ -76,6 +78,8 @@ public class ConfigurationModel implements IConfigurationModel {
private static Map<String, String> currencies = getAllCurrencies(); private static Map<String, String> currencies = getAllCurrencies();
private List<Connector> connectors;
@Autowired @Autowired
private IConfigurationDAO configurationDAO; private IConfigurationDAO configurationDAO;
@ -88,6 +92,12 @@ public class ConfigurationModel implements IConfigurationModel {
@Autowired @Autowired
private IWorkReportDAO workReportDAO; private IWorkReportDAO workReportDAO;
@Autowired
private IConnectorDAO connectorDAO;
@Autowired
private IJobSchedulerModel jobSchedulerModel;
@Override @Override
@Transactional(readOnly = true) @Transactional(readOnly = true)
public List<BaseCalendar> getCalendars() { public List<BaseCalendar> getCalendars() {
@ -107,6 +117,8 @@ public class ConfigurationModel implements IConfigurationModel {
public void init() { public void init() {
this.configuration = getCurrentConfiguration(); this.configuration = getCurrentConfiguration();
initEntitySequences(); initEntitySequences();
initLdapConfiguration();
initConnectorConfiguration();
} }
private void initEntitySequences() { 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() { private Configuration getCurrentConfiguration() {
Configuration configuration = configurationDAO.getConfiguration(); Configuration configuration = configurationDAO.getConfiguration();
if (configuration == null) { if (configuration == null) {
@ -132,8 +161,6 @@ public class ConfigurationModel implements IConfigurationModel {
private void forceLoad(Configuration configuration) { private void forceLoad(Configuration configuration) {
forceLoad(configuration.getDefaultCalendar()); forceLoad(configuration.getDefaultCalendar());
forceLoad(configuration.getPersonalTimesheetsTypeOfWorkHours()); forceLoad(configuration.getPersonalTimesheetsTypeOfWorkHours());
forceLoad(configuration.getJiraConfiguration()
.getJiraConnectorTypeOfWorkHours());
} }
private void forceLoad(BaseCalendar calendar) { private void forceLoad(BaseCalendar calendar) {
@ -160,6 +187,7 @@ public class ConfigurationModel implements IConfigurationModel {
public void confirm() { public void confirm() {
checkEntitySequences(); checkEntitySequences();
configurationDAO.save(configuration); configurationDAO.save(configuration);
saveConnectors();
try { try {
storeAndRemoveEntitySequences(); storeAndRemoveEntitySequences();
} catch (IllegalStateException e) { } catch (IllegalStateException e) {
@ -663,36 +691,34 @@ public class ConfigurationModel implements IConfigurationModel {
configuration.setSecondsPlanningWarning(secondsPlanningWarning); configuration.setSecondsPlanningWarning(secondsPlanningWarning);
} }
@Override private void saveConnectors() {
public void setJiraConfiguration(JiraConfiguration jiraConfiguration) { for (Connector connector : connectors) {
configuration.setJiraConfiguration(jiraConfiguration); connectorDAO.save(connector);
}
} }
@Override @Override
public JiraConfiguration getJiraConfiguration() { public List<Connector> getConnectors() {
return configuration.getJiraConfiguration(); return Collections.unmodifiableList(connectors);
} }
@Override @Override
public TypeOfWorkHours getJiraConnectorTypeOfWorkHours() { public Connector getConnectorByName(String name) {
JiraConfiguration jiraConfiguration = configuration if (name == null || connectors == null) {
.getJiraConfiguration(); return null;
if (jiraConfiguration != null) { }
return jiraConfiguration.getJiraConnectorTypeOfWorkHours();
for (Connector connector : connectors) {
if (connector.getName().equals(name)) {
return connector;
}
} }
return null; return null;
} }
@Override @Override
public void setJiraConnectorTypeOfWorkHours(TypeOfWorkHours typeOfWorkHours) { public boolean scheduleOrUnscheduleJobs(Connector connector) {
if (configuration != null) { return jobSchedulerModel.scheduleOrUnscheduleJobs(connector);
JiraConfiguration jiraConfiguration = configuration
.getJiraConfiguration();
if (jiraConfiguration != null) {
jiraConfiguration
.setJiraConnectorTypeOfWorkHours(typeOfWorkHours);
}
}
} }
} }

View file

@ -414,6 +414,13 @@ public class CustomMenuController extends Div implements IMenuItemsRegister {
"/profiles/profiles.zul", "/profiles/profiles.zul",
"13-usuarios.html#administraci-n-de-perfiles")); "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()) { if (!configurationItems.isEmpty()) {
topItem(_("Configuration"), "/common/configuration.zul", "", topItem(_("Configuration"), "/common/configuration.zul", "",
configurationItems); configurationItems);

View file

@ -25,9 +25,9 @@ import java.util.List;
import java.util.Set; import java.util.Set;
import org.libreplan.business.calendars.entities.BaseCalendar; 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.EntityNameEnum;
import org.libreplan.business.common.entities.EntitySequence; 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.LDAPConfiguration;
import org.libreplan.business.common.entities.PersonalTimesheetsPeriodicityEnum; import org.libreplan.business.common.entities.PersonalTimesheetsPeriodicityEnum;
import org.libreplan.business.common.entities.ProgressType; import org.libreplan.business.common.entities.ProgressType;
@ -186,12 +186,10 @@ public interface IConfigurationModel {
void setSecondsPlanningWarning( void setSecondsPlanningWarning(
Integer planningWarningExitWithoutSavingSeconds); Integer planningWarningExitWithoutSavingSeconds);
void setJiraConfiguration(JiraConfiguration jiraConfiguration); List<Connector> getConnectors();
JiraConfiguration getJiraConfiguration(); Connector getConnectorByName(String name);
TypeOfWorkHours getJiraConnectorTypeOfWorkHours(); boolean scheduleOrUnscheduleJobs(Connector connector);
void setJiraConnectorTypeOfWorkHours(TypeOfWorkHours typeOfWorkHours);
} }

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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 <rego@igalia.com>
* @author Miciele Ghiorghis <m.ghiorghis@antoniusziekenhuis.nl>
*/
public interface IJobSchedulerModel {
/**
* returns all job scheduler configurations
*
* @return list of <code>JobSchedulerConfiguration</code>
*/
List<JobSchedulerConfiguration> getJobSchedulerConfigurations();
/**
* returns next fire time for the specified job from
* <code>{@link JobSchedulerConfiguration}</code>
*
* @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<SynchronizationInfo> 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<Connector> getConnectors();
/**
* Schedule or unschedule jobs for the specified <code>connector</code>
*
* schedule all jobs of the specified <code>connector</code>'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
* <code>{@link JobSchedulerConfiguration}</code>
*
* @return true if scheduling is succeeded, false otherwise
*/
boolean scheduleOrUnscheduleJob();
/**
* Delete job specified in <code>{@link JobSchedulerConfiguration}</code>
*
* @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);
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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 <m.ghiorghis@antoniusziekenhuis.nl>
*/
public class JobSchedulerController extends
BaseCRUDController<JobSchedulerConfiguration> {
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<JobSchedulerConfiguration> 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<String> getConnectorNames() {
List<Connector> connectors = jobSchedulerModel.getConnectors();
List<String> connectorNames = new ArrayList<String>();
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<SynchronizationInfo> getSynchronizationInfos() {
return jobSchedulerModel.getSynchronizationInfos();
}
private void showSynchronizationInfo() {
Map<String, Object> args = new HashMap<String, Object>();
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 <code>cronExpressionInputPopup</code>
*/
public void openPopup() {
setupCronExpressionPopup(getJobSchedulerConfiguration());
cronExpressionInputPopup.open(cronExpressionTextBox, "after_start");
}
/**
* Sets the cronExpression values for <code>cronExpressionInputPopup</code>
*
* @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 <code>cronExpressionTextBox</code> value from the
* <code>cronExpressionInputPopup</code>
*/
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"));
}
}
}

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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 <rego@igalia.com>
* @author Miciele Ghiorghis <m.ghiorghis@antoniusziekenhuis.nl>
*/
@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<SynchronizationInfo> synchronizationInfos;
@Override
@Transactional(readOnly = true)
public List<JobSchedulerConfiguration> 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<SynchronizationInfo> 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<Connector> getConnectors() {
return connectorDAO.getAll();
}
@Override
public boolean scheduleOrUnscheduleJobs(Connector connector) {
List<JobSchedulerConfiguration> 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;
}
}

View file

@ -26,6 +26,7 @@ import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -161,10 +162,10 @@ public class DashboardController extends GenericForwardComposer {
if ((lblAbsolute != null) && (absoluteMargin != null)) { if ((lblAbsolute != null) && (absoluteMargin != null)) {
lblAbsolute lblAbsolute
.setValue(String .setValue(_(
.format(_("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}%).",
absoluteMargin + 0, absoluteMargin, (new DecimalFormat("#.##"))
relativeMargin.doubleValue() * 100)); .format(relativeMargin.doubleValue() * 100)));
} else { } else {
lblAbsolute.setValue(_("No project deadline defined")); lblAbsolute.setValue(_("No project deadline defined"));
} }

View file

@ -573,7 +573,7 @@ public class ManualAllocationController extends GenericForwardComposer {
public String getCurrentQueue() { public String getCurrentQueue() {
if (getBeingEditedElement() == null if (getBeingEditedElement() == null
|| getBeingEditedElement().getLimitingResourceQueue() == null) { || getBeingEditedElement().getLimitingResourceQueue() == null) {
return _("Unnasigned"); return _("Unassigned");
} }
return getBeingEditedElement().getLimitingResourceQueue().getResource() return getBeingEditedElement().getLimitingResourceQueue().getResource()
.getName(); .getName();
@ -582,7 +582,7 @@ public class ManualAllocationController extends GenericForwardComposer {
public String getCurrentStart() { public String getCurrentStart() {
if (getBeingEditedElement() == null if (getBeingEditedElement() == null
|| getBeingEditedElement().getStartDate() == null) { || getBeingEditedElement().getStartDate() == null) {
return _("Unnasigned"); return _("Unassigned");
} }
return getBeingEditedElement().getStartDate().toString(); return getBeingEditedElement().getStartDate().toString();
} }
@ -590,7 +590,7 @@ public class ManualAllocationController extends GenericForwardComposer {
public String getCurrentEnd() { public String getCurrentEnd() {
if (getBeingEditedElement() == null if (getBeingEditedElement() == null
|| getBeingEditedElement().getEndDate() == null) { || getBeingEditedElement().getEndDate() == null) {
return _("Unnasigned"); return _("Unassigned");
} }
return getBeingEditedElement().getEndDate().toString(); return getBeingEditedElement().getEndDate().toString();
} }

View file

@ -156,6 +156,4 @@ public interface IOrderModel extends IIntegrationEntityModel {
User getUser(); User getUser();
boolean isJiraActivated();
} }

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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 <m.ghiorghis@antoniusziekenhuis.nl>
*/
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 <code>JiraEditWindow</code> 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<String> 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 <code>label</code>
*
* @param label
* the jira label
*/
public void startSyncWithJira(String label) {
try {
Order order = getOrder();
List<IssueDTO> 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<String, Object> args = new HashMap<String, Object>();
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
* <code>comboJiraLabel</code>
*/
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());
}
}
}

View file

@ -29,20 +29,17 @@ import java.util.ConcurrentModificationException;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.SortedSet; import java.util.SortedSet;
import java.util.TreeSet; import java.util.TreeSet;
import javax.annotation.Resource; import javax.annotation.Resource;
import javax.ws.rs.WebApplicationException;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
import org.libreplan.business.calendars.entities.BaseCalendar; 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.common.exceptions.InstanceNotFoundException;
import org.libreplan.business.externalcompanies.entities.DeadlineCommunication; import org.libreplan.business.externalcompanies.entities.DeadlineCommunication;
import org.libreplan.business.externalcompanies.entities.DeliverDateComparator; 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.templates.entities.OrderTemplate;
import org.libreplan.business.users.entities.User; import org.libreplan.business.users.entities.User;
import org.libreplan.business.users.entities.UserRole; 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.ConfirmCloseUtil;
import org.libreplan.web.common.FilterUtils; import org.libreplan.web.common.FilterUtils;
import org.libreplan.web.common.IMessagesForUser; 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.Component;
import org.zkoss.zk.ui.Desktop; import org.zkoss.zk.ui.Desktop;
import org.zkoss.zk.ui.Executions; import org.zkoss.zk.ui.Executions;
import org.zkoss.zk.ui.SuspendNotAllowedException;
import org.zkoss.zk.ui.WrongValueException; import org.zkoss.zk.ui.WrongValueException;
import org.zkoss.zk.ui.event.Event; import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.EventListener; 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.Grid;
import org.zkoss.zul.Hbox; import org.zkoss.zul.Hbox;
import org.zkoss.zul.Label; import org.zkoss.zul.Label;
import org.zkoss.zul.ListModel;
import org.zkoss.zul.Messagebox; import org.zkoss.zul.Messagebox;
import org.zkoss.zul.Popup;
import org.zkoss.zul.Row; import org.zkoss.zul.Row;
import org.zkoss.zul.RowRenderer; import org.zkoss.zul.RowRenderer;
import org.zkoss.zul.Rows; import org.zkoss.zul.Rows;
@ -202,6 +192,10 @@ public class OrderCRUDController extends GenericForwardComposer {
private ProjectDetailsController projectDetailsController; private ProjectDetailsController projectDetailsController;
private JiraSynchronizationController jiraSynchronizationController;
private TimSynchronizationController timSynchronizationController;
@Autowired @Autowired
private IOrderDAO orderDAO; private IOrderDAO orderDAO;
@ -209,16 +203,6 @@ public class OrderCRUDController extends GenericForwardComposer {
private EndDatesRenderer endDatesRenderer = new EndDatesRenderer(); private EndDatesRenderer endDatesRenderer = new EndDatesRenderer();
@Autowired
private IJiraOrderElementSynchronizer jiraOrderElementSynchronizer;
@Autowired
private IJiraTimesheetSynchronizer jiraTimesheetSynchronizer;
@Autowired
private IConfigurationDAO configurationDAO;
@Override @Override
public void doAfterCompose(Component comp) throws Exception { public void doAfterCompose(Component comp) throws Exception {
super.doAfterCompose(comp); super.doAfterCompose(comp);
@ -821,7 +805,7 @@ public class OrderCRUDController extends GenericForwardComposer {
saveAndContinue(true); saveAndContinue(true);
} }
private void saveAndContinue(boolean showSaveMessage) { protected void saveAndContinue(boolean showSaveMessage) {
Order order = orderModel.getOrder(); Order order = orderModel.getOrder();
final boolean isNewObject = order.isNewObject(); final boolean isNewObject = order.isNewObject();
@ -930,11 +914,11 @@ public class OrderCRUDController extends GenericForwardComposer {
} }
} }
private Tab getCurrentTab() { protected Tab getCurrentTab() {
return selectedTab; return selectedTab;
} }
private void selectTab(String str) { protected void selectTab(String str) {
Tab tab = (Tab) editWindow.getFellowIfAny(str); Tab tab = (Tab) editWindow.getFellowIfAny(str);
if (tab != null) { if (tab != null) {
tab.setSelected(true); tab.setSelected(true);
@ -1082,6 +1066,12 @@ public class OrderCRUDController extends GenericForwardComposer {
} }
public void initEdit(Order order) { 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())) { if (!orderModel.userCanRead(order, SecurityUtils.getSessionUserLoginName())) {
try { try {
Messagebox.show(_("Sorry, you do not have permissions to access this project"), 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); throw new RuntimeException(e);
} }
} }
orderModel.initEdit(order, getDesktop());
prepareEditWindow(_("Edit project"));
} }
public IOrderModel getOrderModel() { public IOrderModel getOrderModel() {
@ -1169,6 +1156,8 @@ public class OrderCRUDController extends GenericForwardComposer {
initializeCustomerComponent(); initializeCustomerComponent();
reloadOrderDetailsTab(); reloadOrderDetailsTab();
orderDatesHandler.chooseCurrentSchedulingMode(); orderDatesHandler.chooseCurrentSchedulingMode();
setupJiraSynchronizationController();
setupTimSynchronizationController();
} }
public void reloadOrderDetailsTab() { public void reloadOrderDetailsTab() {
@ -1876,167 +1865,32 @@ public class OrderCRUDController extends GenericForwardComposer {
loadLabels(); loadLabels();
} }
private Popup jirasyncPopup; /**
private Button startJiraSyncButton, cancelJiraSyncButton, syncWithJiraButton; * Setup the connector, JiraSynchronization controller
private Combobox comboJiraLabel; */
public void setupJiraSynchronizationController() {
public boolean isJiraDeactivated() { if (jiraSynchronizationController == null) {
return !configurationDAO.getConfigurationWithReadOnlyTransaction() jiraSynchronizationController = new JiraSynchronizationController();
.getJiraConfiguration().isJiraActivated();
} }
public void syncWithJira() {
try { try {
List<String> items = jiraOrderElementSynchronizer.getAllJiraLabels(); jiraSynchronizationController.doAfterCompose(editWindow);
} catch (Exception e) {
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"));
}
}
public void startSyncWithJira(String label) {
try {
Order order = getOrder();
List<IssueDTO> 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<String, Object> args = new HashMap<String, Object>();
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) {
throw new RuntimeException(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 void setupTimSynchronizationController() {
if (timSynchronizationController == null) {
public SimpleListModelExt(List data) { timSynchronizationController = new TimSynchronizationController();
super(data);
} }
try {
public ListModel getSubModel(Object value, int nRows) { timSynchronizationController.doAfterCompose(editWindow);
final String idx = value == null ? "" : objectToString(value); } catch (Exception e) {
if (nRows < 0) { throw new RuntimeException(e);
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());
} }
} }
public boolean isJiraActivated() {
return orderModel.isJiraActivated();
}
} }

View file

@ -37,9 +37,11 @@ import java.util.logging.Filter;
import javax.annotation.Resource; import javax.annotation.Resource;
import org.apache.commons.lang.StringUtils; 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.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.Order;
import org.libreplan.business.orders.entities.OrderElement; import org.libreplan.business.orders.entities.OrderElement;
import org.libreplan.business.orders.entities.OrderLine; import org.libreplan.business.orders.entities.OrderLine;
@ -126,7 +128,7 @@ public class OrderElementTreeController extends TreeController<OrderElement> {
private Popup filterOptionsPopup; private Popup filterOptionsPopup;
@Autowired @Autowired
private IConfigurationDAO configurationDAO; private IConnectorDAO connectorDAO;
public List<org.libreplan.business.labels.entities.Label> getLabels() { public List<org.libreplan.business.labels.entities.Label> getLabels() {
return orderModel.getLabels(); return orderModel.getLabels();
@ -510,9 +512,17 @@ public class OrderElementTreeController extends TreeController<OrderElement> {
String code = orderElement.getCode(); String code = orderElement.getCode();
A hyperlink = new A(code); 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, codeWithoutPrefix = StringUtils.removeStart(codeWithoutPrefix,
orderElement.getOrder().getCode() orderElement.getOrder().getCode()
+ EntitySequence.CODE_SEPARATOR_CHILDREN); + EntitySequence.CODE_SEPARATOR_CHILDREN);

View file

@ -962,11 +962,4 @@ public class OrderModel extends IntegrationEntityModel implements IOrderModel {
return user; return user;
} }
@Override
@Transactional(readOnly = true)
public boolean isJiraActivated() {
return configurationDAO.getConfiguration().getJiraConfiguration()
.isJiraActivated();
}
} }

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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 <m.ghiorghis@antoniusziekenhuis.nl>
*/
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 <code>TimEditWindow</code> 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<String, Object> args = new HashMap<String, Object>();
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);
}
}
}

View file

@ -273,6 +273,8 @@ public class PlanningTabCreator {
breadcrumbs.appendChild(new Label(_("Project Scheduling"))); breadcrumbs.appendChild(new Label(_("Project Scheduling")));
if (mode.isOf(ModeType.ORDER)) { if (mode.isOf(ModeType.ORDER)) {
orderPlanningController.getOrderCRUDController()
.checkUserCanRead(order);
Label nameLabel = new Label(order.getName()); Label nameLabel = new Label(order.getName());
nameLabel.setTooltiptext(order.getName() + "." nameLabel.setTooltiptext(order.getName() + "."
+ order.getDescription()); + order.getDescription());

View file

@ -5395,10 +5395,8 @@ msgid "Progress Evolution"
msgstr "Evolución del progreso" msgstr "Evolución del progreso"
#: libreplan-webapp/src/main/java/org/libreplan/web/dashboard/DashboardController.java:165 #: libreplan-webapp/src/main/java/org/libreplan/web/dashboard/DashboardController.java:165
msgid "" msgid "There is a margin of {0} days with the project global deadline ({1}%)."
"There is a margin of %d days with the project global deadline (%.2f %%)." msgstr "Hay un margen de {0} días con la fecha límite global del proyecto ({1}%)."
msgstr ""
"Hay un margen de %d días con la fecha límite global del proyecto (%.2f %%)."
#: libreplan-webapp/src/main/webapp/planner/taskpanels/_tabPanelNonLimitingResourceAllocation.zul:91 #: libreplan-webapp/src/main/webapp/planner/taskpanels/_tabPanelNonLimitingResourceAllocation.zul:91
msgid "Original" msgid "Original"

View file

@ -5156,9 +5156,8 @@ msgid ""
msgstr "Os valores de subcontratación son de só lectura porque foron notificados pola empresa subcontratista." 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 #: libreplan-webapp/src/main/java/org/libreplan/web/dashboard/DashboardController.java:165
msgid "" msgid "There is a margin of {0} days with the project global deadline ({1}%)."
"There is a margin of %d days with the project global deadline (%.2f %%)." msgstr "Hai unha marxe de {0} días coa data límite global do proxecto ({1}%)."
msgstr "Hai unha marxe de %d días coa data límite global do proxecto (%.2f %%)."
#: libreplan-webapp/src/main/java/org/libreplan/web/planner/chart/EarnedValueChartFiller.java:250 #: libreplan-webapp/src/main/java/org/libreplan/web/planner/chart/EarnedValueChartFiller.java:250
msgid "CV" msgid "CV"

View file

@ -5203,7 +5203,7 @@ msgstr ""
#: libreplan-webapp/src/main/java/org/libreplan/web/dashboard/DashboardController.java:165 #: libreplan-webapp/src/main/java/org/libreplan/web/dashboard/DashboardController.java:165
msgid "" 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 "" msgstr ""
#: libreplan-webapp/src/main/webapp/planner/taskpanels/_tabPanelNonLimitingResourceAllocation.zul:91 #: libreplan-webapp/src/main/webapp/planner/taskpanels/_tabPanelNonLimitingResourceAllocation.zul:91

View file

@ -1,4 +1,4 @@
# LibrePlan - Webapp module. # LibrePlae - Webapp module.
# Copyright (C) 2009-2010 Fundación para o Fomento da Calidade Industrial e # Copyright (C) 2009-2010 Fundación para o Fomento da Calidade Industrial e
# Desenvolvemento Tecnolóxico de Galicia # Desenvolvemento Tecnolóxico de Galicia
# Copyright (C) 2010-2012 Igalia, S.L. # Copyright (C) 2010-2012 Igalia, S.L.
@ -72,7 +72,7 @@ msgid "Create Virtual Worker"
msgstr "创建虚拟工作人员" msgstr "创建虚拟工作人员"
#: libreplan-webapp/src/main/java/org/libreplan/web/workreports/WorkReportCRUDController.java:862 #: 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/_editLabelType.zul:81
#: libreplan-webapp/src/main/webapp/labels/_listLabelTypes.zul:28 #: libreplan-webapp/src/main/webapp/labels/_listLabelTypes.zul:28
#: libreplan-webapp/src/main/webapp/advance/_listAdvanceTypes.zul:30 #: 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 #: libreplan-webapp/src/main/java/org/libreplan/web/dashboard/DashboardController.java:165
msgid "" 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 "总项目的截止日期(%.2f %%)前有%d天的余量。" msgstr "总项目的截止日期({1}%)前有{0}天的余量。"
#: libreplan-webapp/src/main/java/org/libreplan/web/planner/chart/EarnedValueChartFiller.java:250 #: libreplan-webapp/src/main/java/org/libreplan/web/planner/chart/EarnedValueChartFiller.java:250
msgid "CV" msgid "CV"

View file

@ -49,6 +49,16 @@
class="org.libreplan.web.scenarios.CurrentUserScenarioAwareManager" class="org.libreplan.web.scenarios.CurrentUserScenarioAwareManager"
scope="singleton"/> scope="singleton"/>
<!-- Scheduler -->
<bean id="schedulerFactoryBean" class="org.springframework.scheduling.quartz.SchedulerFactoryBean" lazy-init="false" />
<bean id="schedulerManager"
class="org.libreplan.importers.SchedulerManager"
scope="singleton"
init-method="scheduleJobs">
<property name="scheduler" ref="schedulerFactoryBean"/>
</bean>
<context:component-scan base-package="org.libreplan"/> <context:component-scan base-package="org.libreplan"/>
<!-- CXF --> <!-- CXF -->

View file

@ -0,0 +1,167 @@
<!--
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 <http://www.gnu.org/licenses/>.
-->
<window id="${arg.id}">
<caption id="caption" sclass="caption-title" />
<grid id="jobSchedulerGrid" fixedLayout="true">
<columns>
<column width="200px" />
<column/>
</columns>
<rows>
<row>
<label value="${i18n:_('Job group')}" />
<textbox id="jobGroupTextBox" width="400px"
constraint="no empty:${i18n:_('cannot be empty')}"
value="@{controller.jobSchedulerConfiguration.jobGroup}"/>
</row>
<row>
<label value="${i18n:_('Job name')}" />
<textbox id="jobNameTextBox" width="400px"
constraint="no empty:${i18n:_('cannot be empty')}"
value="@{controller.jobSchedulerConfiguration.jobName}"/>
</row>
<row>
<label value="${i18n:_('Cron expression')}" />
<hbox>
<textbox id="cronExpressionTextBox"
value="@{controller.jobSchedulerConfiguration.cronExpression}"
constraint="no empty:${i18n:_('cannot be empty')}"
width="400px" disabled="true"/>
<button sclass="icono" image="/common/img/ico_editar1.png"
hoverImage="/common/img/ico_editar.png"
tooltiptext="${i18n:_('Edit')}"
onClick="controller.openPopup()"/>
</hbox>
</row>
<row>
<label value="${i18n:_('Job class name')}" />
<combobox id="jobCombo" autodrop="true" width="400px"
constraint="no empty:${i18n:_('cannot be empty')}"
model="@{controller.jobNames}"
selectedItem="@{controller.jobSchedulerConfiguration.jobClassName}">
<comboitem
self="@{each=jobNames}"
label="@{jobNames.name}"
value="@{jobNames}" />
</combobox>
</row>
<row>
<label value="${i18n:_('Connector')}" />
<combobox id="connectorCombo" autodrop="true" width="400px"
model="@{controller.connectorNames}"
selectedItem="@{controller.jobSchedulerConfiguration.connectorName}">
<comboitem
self="@{each=connectorNames}"
label="@{connectorNames.name}"
value="@{connectorNames}" />
</combobox>
</row>
<row>
<label value="${i18n:_('Schedule')}" />
<checkbox checked="@{controller.jobSchedulerConfiguration.schedule}" />
</row>
</rows>
</grid>
<!-- Control buttons -->
<button onClick="controller.saveAndExit()"
label="${i18n:_('Save')}"
sclass="save-button global-action" />
<button onClick="controller.saveAndContinue()"
label="${i18n:_('Save and Continue')}"
sclass="save-button global-action" />
<button onClick="controller.cancelForm()"
label="${i18n:_('Cancel')}"
sclass="cancel-button global-action" />
<div>
<groupbox closable="false" >
<caption label="${i18n:_('Cron expression format')}" />
<grid width="500px">
<columns>
<column label="${i18n:_('Field')}" width="150px"/>
<column label="${i18n:_('Allowed values')}" />
</columns>
<rows>
<row>
<label value="${i18n:_('Seconds')}"/>
<label value="0-59"/>
</row>
<row>
<label value="${i18n:_('Minutes')}"/>
<label value="0-59"/>
</row>
<row>
<label value="${i18n:_('Hours')}"/>
<label value="0-23"/>
</row>
<row>
<label value="${i18n:_('Day of month')}"/>
<label value="1-31"/>
</row>
<row>
<label value="${i18n:_('Month')}"/>
<span>1-12 (<label value="${i18n:_('or names')}"/> [JAN-DEC])</span>
</row>
<row>
<label value="${i18n:_('Day of week')}"/>
<span>0-7 (<label value="${i18n:_('0 or 7 is Sunday, or use names')}"/> [SUN-SAT])</span>
</row>
<row>
<label value="${i18n:_('Year (optional)')}"/>
<label value="1970-2099"/>
</row>
</rows>
</grid>
<div>
${i18n:_("For more details on cron expression click")}
<a href="http://www.manpagez.com/man/5/crontab/">${i18n:_('here')}.</a>
</div>
</groupbox>
</div>
<popup id="cronExpressionInputPopup" width="525px">
<label id="jobGroup" value="@{jobSchedulerController.jobGroup}"/>
<label id="jobName" value="@{jobSchedulerController.jobName}"/>
<grid id="cronExpressionGrid">
<columns>
<column label="${i18n:_('Seconds')}"/>
<column label="${i18n:_('Minutes')}" />
<column label="${i18n:_('Hours')}"/>
<column label="${i18n:_('Day of month')}"/>
<column label="${i18n:_('Month')}"/>
<column label="${i18n:_('Day of week')}"/>
<column label="${i18n:_('Year')}"/>
</columns>
<rows>
<row>
<textbox id="cronExpressionSeconds" width="60px" />
<textbox id="cronExpressionMinutes" width="60px" />
<textbox id="cronExpressionHours" width="60px" />
<textbox id="cronExpressionDayOfMonth" width="60px" />
<textbox id="cronExpressionMonth" width="60px" />
<textbox id="cronExpressionDayOfWeek" width="60px" />
<textbox id="cronExpressionYear" width="60px" />
</row>
</rows>
</grid>
<button onClick="controller.updateCronExpression();" label="${i18n:_('OK')}" />
<button onClick="controller.cancelPopup();" label="${i18n:_('Cancel')}" />
</popup>
</window>

View file

@ -0,0 +1,36 @@
<!--
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 <http://www.gnu.org/licenses/>.
-->
<window id="${arg.id}" title="${i18n:_('Job Scheduling List')}">
<grid id="listJobSchedulings"
rowRenderer="@{controller.jobSchedulingRenderer}"
model="@{controller.jobSchedulerConfigurations}"
mold="paging" pageSize="10" fixedLayout="true">
<columns>
<column label="${i18n:_('Job group')}" />
<column label="${i18n:_('Job name')}" />
<column label="${i18n:_('Cron expression')}" />
<column label="${i18n:_('Next fire time')}" />
<column label="${i18n:_('Operations')}" />
</columns>
</grid>
<button label="${i18n:_('Create')}" onClick="controller.goToCreateForm()"
sclass="create-button-global-action"/>
</window>

View file

@ -42,7 +42,7 @@
<tab label="${i18n:_('Main preferences')}" /> <tab label="${i18n:_('Main preferences')}" />
<tab label="${i18n:_('Entity sequences')}" /> <tab label="${i18n:_('Entity sequences')}" />
<tab label="${i18n:_('LDAP configuration')}" /> <tab label="${i18n:_('LDAP configuration')}" />
<tab label="${i18n:_('JIRA connector')}" /> <tab label="${i18n:_('Connectors')}" />
</tabs> </tabs>
<tabpanels> <tabpanels>
<tabpanel id="panelConfiguration"> <tabpanel id="panelConfiguration">
@ -414,47 +414,35 @@
</vbox> </vbox>
</groupbox> </groupbox>
</tabpanel> </tabpanel>
<tabpanel id="panelConnectors">
<tabpanel id="panelJiraConfiguration"> <groupbox style="margin-top: 5px" closable="false">
<grid> <caption label="${i18n:_('Application properties')}" />
<vbox>
<hbox pack="center">
<label value="${i18n:_('Select connector')}" />
<combobox id="connectorCombo" autodrop="true"
model="@{configurationController.connectors}"
selectedItem="@{configurationController.selectedConnector}">
<comboitem
self="@{each=connectors}"
label="@{connectors.name}"
value="@{connectors}" />
</combobox>
</hbox>
<separator />
<grid id="connectorPropertriesGrid"
model="@{configurationController.connectorPropertries}"
rowRenderer="@{configurationController.connectorPropertriesRenderer}">
<columns> <columns>
<column width="200px" /> <column label="${i18n:_('Name')}" width="200px"/>
<column /> <column label="${i18n:_('Value')}" />
</columns> </columns>
<rows>
<row>
<label value="${i18n:_('Activated')}" />
<checkbox id="jiraActivated" checked="@{configurationController.jiraConfiguration.jiraActivated}" width="300px" />
</row>
<row>
<label value="${i18n:_('JIRA URL')}" />
<textbox id="jiraURL" value="@{configurationController.jiraConfiguration.jiraUrl}" width="300px"/>
</row>
<row>
<label value="${i18n:_('JIRA sync account')}" />
<textbox id="jiraSyncAccount" value="@{configurationController.jiraConfiguration.jiraUserId}" width="300px"/>
</row>
<row>
<label value="${i18n:_('JIRA sync password')}" />
<textbox id="jiraSyncPassword" value="@{configurationController.jiraConfiguration.jiraPassword}" type="password" width="300px"/>
</row>
<row>
<label value="${i18n:_('JIRA labels: comma-separated list of labels or URL')}" />
<textbox id="jiraLabels" value="@{configurationController.jiraConfiguration.jiraLabels}" width="600px"/>
</row>
<row>
<label value="${i18n:_('Hours type for JIRA connector')}" />
<bandboxSearch
finder="TypeOfWorkHoursBandboxFinder"
selectedElement="@{configurationController.jiraConnectorTypeOfWorkHours, access='both'}" />
</row>
</rows>
</grid> </grid>
</vbox>
<separator /> <separator />
<button label="${i18n:_('Test connection')}" <button label="${i18n:_('Test connection')}"
onClick="configurationController.testJiraConnection()" /> onClick="configurationController.testConnection()" />
<separator /> </groupbox>
</tabpanel> </tabpanel>
</tabpanels> </tabpanels>
</tabbox> </tabbox>

View file

@ -0,0 +1,38 @@
<!--
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 <http://www.gnu.org/licenses/>.
-->
<?page id="jobSchedulingList" title="${i18n:_('LibrePlan: Job Scheduling')}" ?>
<?init class="org.zkoss.zkplus.databind.AnnotateDataBinderInit" ?>
<?init class="org.zkoss.zk.ui.util.Composition" arg0="/common/layout/template.zul"?>
<?link rel="shortcut icon" href="/common/img/favicon.ico" type="image/x-icon"?>
<?link rel="stylesheet" type="text/css" href="/common/css/libreplan.css"?>
<?link rel="stylesheet" type="text/css" href="/common/css/libreplan_zk.css"?>
<?component name="list" inline="true" macroURI="_listJobScheduling.zul"?>
<?component name="edit" inline="true" macroURI="_editJobScheduling.zul"?>
<zk>
<window self="@{define(content)}"
apply="org.libreplan.web.common.JobSchedulerController">
<vbox id="messagesContainer" />
<list id="listWindow" />
<edit id="editWindow" />
</window>
</zk>

Some files were not shown because too many files have changed in this diff Show more